class PyModbusClient(BaseModbusClient): def __init__(self, *args, **kwargs): super(PyModbusClient, self).__init__(*args, **kwargs) if self.method == "tcp": from pymodbus.client.sync import ModbusTcpClient self.client = ModbusTcpClient(self.host, self.port) else: from pymodbus.client.sync import ModbusSerialClient argnames = ("stopbits", "bytesize", "parity", "baudrate", "timeout") kwargs = dict((k, getattr(self, k)) for k in argnames) kwargs.update({ "port": self.host, }) self.client = ModbusSerialClient(self.method, **kwargs) def read_coils(self, address, count): resp = self.client.read_coils(address, count, unit=self.unit) if resp is None: raise NoDeviceResponse() return resp.bits[:count] def write_coil(self, address, value): resp = self.client.write_coil(address, int(value), unit=self.unit) if resp is None: raise NoDeviceResponse() return resp.value
class EPsolarTracerClient: ''' EPsolar Tracer client ''' def __init__(self, unit = 1, serialclient = None, **kwargs): ''' Initialize a serial client instance ''' self.unit = unit if serialclient == None: port = kwargs.get('port', '/dev/ttyXRUSB0') baudrate = kwargs.get('baudrate', 115200) self.client = ModbusClient(method = 'rtu', port = port, baudrate = baudrate, kwargs = kwargs) else: self.client = serialclient def connect(self): ''' Connect to the serial :returns: True if connection succeeded, False otherwise ''' return self.client.connect() def close(self): ''' Closes the underlying connection ''' return self.client.close() def read_device_info(self): request = ReadDeviceInformationRequest (unit = self.unit) response = self.client.execute(request) return response def read_input(self, name): register = registerByName(name) if register.is_coil(): response = self.client.read_coils(register.address, register.size, unit = self.unit) elif register.is_discrete_input(): response = self.client.read_discrete_inputs(register.address, register.size, unit = self.unit) elif register.is_input_register(): response = self.client.read_input_registers(register.address, register.size, unit = self.unit) else: response = self.client.read_holding_registers(register.address, register.size, unit = self.unit) return register.decode(response) def write_output(self, name, value): register = registerByName(name) values = register.encode(value) response = False if register.is_coil(): self.client.write_coil(register.address, values, unit = self.unit) response = True elif register.is_discrete_input(): _logger.error("Cannot write discrete input " + repr(name)) pass elif register.is_input_register(): _logger.error("Cannot write input register " + repr(name)) pass else: self.client.write_registers(register.address, values, unit = self.unit) response = True return response
class PyModbusClient(BaseModbusClient): def __init__(self, *args, **kwargs): super(PyModbusClient, self).__init__(*args, **kwargs) if self.method == "tcp": from pymodbus.client.sync import ModbusTcpClient self.client = ModbusTcpClient(self.host, self.port) else: from pymodbus.client.sync import ModbusSerialClient argnames = ("stopbits", "bytesize", "parity", "baudrate", "timeout") kwargs = dict((k, getattr(self, k)) for k in argnames) kwargs.update({ "port": self.host, }) self.client = ModbusSerialClient(self.method, **kwargs) def read_coils(self, address, count): resp = self.client.read_coils(address, count, unit=self.unit) if resp is None: raise NoDeviceResponse() return resp.bits[:count] def write_coil(self, address, value): resp = self.client.write_coil(address, int(value), unit=self.unit) if resp is None: raise NoDeviceResponse() return resp.value
def write_via_rtu_modbus(state): client = ModbusSerialClient(method='rtu', port=PORT, timeout=1, baudrate=460800) log.debug("Writing Coils") rq = client.write_coil(1, state, unit=UNIT) log.debug(rq) log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) log.debug(rr) client.close()
class IOController(): __client = None """Instance of the controller. """ __coils = [False] * 12 def __init__(self): self.__client = ModbusClient(method="rtu", port="COM5", timeout=1, stopbits=1, bytesize=8, parity="N", baudrate=9600) # Create a connection with the controller connection = self.__client.connect() if connection: print("Connected with the controller") else: print("No connection with controller") def set_gpio(self, pin, state): """Write coil if given token is whitelisted. Args: pin (int): Relay index state (bool): Relay state """ # Pin is index 0 by default self.__coils[pin] = state response = self.__client.write_coils(0, self.__coils, unit=1) print("set_gpio({}, {})".format(pin, state)) # Read all the coils response = self.__client.read_coils(address=0, count=12, unit=1) print(response.bits[:12])
def read_coils(unit, baud_value): """Function to read the coils. Args: unit (int): The ID of the device. """ global client # Make a connection client = ModbusClient(method="rtu", port="COM3", timeout=0.5, stopbits=1, bytesize=8, parity="N", baudrate=baud_value) connection = client.connect() response = client.read_coils(address=16, count=4, unit=unit) print(response.bits[:4])
# examples how to write to coil registers # https://pymodbus.readthedocs.io/en/v1.3.2/examples/synchronous-client.html from pymodbus.client.sync import ModbusSerialClient as ModbusClient import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) client = ModbusClient(method='rtu', port='/dev/ttyUSB0', timeout=1) client.connect() log.debug("Reading Coils") # starting register, number of registers, slave unit id rr = client.read_coils(12, 1, unit=0x01) # read holding registers from register 1 to 45 next ones hh = client.read_holding_registers(1,45,unit=0x01) print "holding registers:" print hh.registers print "coild register:" print rr.bits print("{}: {}".format("summernight cooling status:", rr.bits[0])) #write coil # turn on ylipaineistus/overpressure program #rq = client.write_coil(10, 1, unit=0x01)
logging.info("Holding registers: %s" % ex.registers) else: logging.error("Error pidiendo HRs") continue regs = ex.registers led_value = 0 if regs[0] == 1 else 1 count += 1 regs[2] = count regs[0] = led_value ex = client.write_registers(0, regs, unit=client_id) if ex is not None: logging.debug("Nuevo valor para el led: %d, respuesta: %s" % (led_value, ex)) else: logging.error("Error seteando led con valor %d" % led_value) logging.info("%02d/%02d/%d %02d:%02d:%02d:\tLed %03s, Light: %s ohms, Counter: %d" % (regs[4], regs[5], regs[6], regs[7], regs[8], regs[9], ("ON" if led_value == 1 else "OFF"), regs[1], regs[3])) # testeo errores en invocacion a funcion invalida ex = client.read_coils(address=0, count=20, unit=client_id); if ex is not None: logging.debug("read_coils with error: %s" % ex) else: logging.error("Error invocando read_coils con argumentos invalidos") client.close()
def onHeartbeat(self): Domoticz.Log("onHeartbeat called") ######################################## # SET HARDWARE - pymodbus: RTU / ASCII ######################################## if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii"): Domoticz.Debug("MODBUS DEBUG - INTERFACE: Port=" + self.Domoticz_Setting_Serial_Port + ", BaudRate=" + self.Domoticz_Setting_Baudrate + ", StopBits=" + str(self.StopBits) + ", ByteSize=" + str(self.ByteSize) + " Parity=" + self.Parity) Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method=" + self.Domoticz_Setting_Communication_Mode + ", Device ID=" + self.Domoticz_Setting_Device_ID + ", Register=" + self.Domoticz_Setting_Register_Number + ", Function=" + self.Domoticz_Setting_Modbus_Function + ", Data type=" + self.Domoticz_Setting_Data_Type + ", Pollrate=" + self.Domoticz_Setting_Device_Pollrate) try: client = ModbusSerialClient( method=self.Domoticz_Setting_Communication_Mode, port=self.Domoticz_Setting_Serial_Port, stopbits=self.StopBits, bytesize=self.ByteSize, parity=self.Parity, baudrate=int(self.Domoticz_Setting_Baudrate), timeout=2, retries=2) except: Domoticz.Error("Error opening Serial interface on " + self.Domoticz_Setting_Serial_Port) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # SET HARDWARE - pymodbus: RTU over TCP ######################################## if (self.Domoticz_Setting_Communication_Mode == "rtutcp"): Domoticz.Debug("MODBUS DEBUG - INTERFACE: IP=" + self.Domoticz_Setting_TCP_IP + ", Port=" + self.Domoticz_Setting_TCP_PORT) Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method=" + self.Domoticz_Setting_Communication_Mode + ", Device ID=" + self.Domoticz_Setting_Device_ID + ", Register=" + self.Domoticz_Setting_Register_Number + ", Function=" + self.Domoticz_Setting_Modbus_Function + ", Data type=" + self.Domoticz_Setting_Data_Type + ", Pollrate=" + self.Domoticz_Setting_Device_Pollrate) try: client = ModbusTcpClient(host=self.Domoticz_Setting_TCP_IP, port=int( self.Domoticz_Setting_TCP_PORT), framer=ModbusRtuFramer, auto_open=True, auto_close=True, timeout=2) except: Domoticz.Error( "Error opening RTU over TCP interface on address: " + self.Domoticz_Setting_TCP_IPPORT) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # SET HARDWARE - pymodbusTCP: TCP/IP ######################################## if (self.Domoticz_Setting_Communication_Mode == "tcpip"): Domoticz.Debug("MODBUS DEBUG - INTERFACE: IP=" + self.Domoticz_Setting_TCP_IP + ", Port=" + self.Domoticz_Setting_TCP_PORT) Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method=" + self.Domoticz_Setting_Communication_Mode + ", Device ID=" + self.Domoticz_Setting_Device_ID + ", Register=" + self.Domoticz_Setting_Register_Number + ", Function=" + self.Domoticz_Setting_Modbus_Function + ", Data type=" + self.Domoticz_Setting_Data_Type + ", Pollrate=" + self.Domoticz_Setting_Device_Pollrate) try: client = ModbusClient(host=self.Domoticz_Setting_TCP_IP, port=int(self.Domoticz_Setting_TCP_PORT), unit_id=int( self.Domoticz_Setting_Device_ID), auto_open=True, auto_close=True, timeout=2) except: Domoticz.Error("Error opening TCP/IP interface on address: " + self.Domoticz_Setting_TCP_IPPORT) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # GET DATA - pymodbus: RTU / ASCII / RTU over TCP ######################################## if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii" or self.Domoticz_Setting_Communication_Mode == "rtutcp"): try: # Function to execute if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils( int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID)) if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs( int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID)) if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers( int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID)) if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers( int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID)) if (self.Read_Scale_Factor == 1): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Big) decoder.skip_bytes((self.Register_Count - 1) * 2) sf_value = decoder.decode_16bit_int() data = data[0:self.Register_Count - 1] else: sf_value = 0 Domoticz.Debug("MODBUS DEBUG - RESPONSE: " + str(data)) except: Domoticz.Error( "Modbus error communicating! (RTU/ASCII/RTU over TCP), check your settings!" ) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # GET DATA - pymodbusTCP: TCP/IP ######################################## if (self.Domoticz_Setting_Communication_Mode == "tcpip"): try: # Function to execute if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils( int(self.Domoticz_Setting_Register_Number), self.Register_Count) if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs( int(self.Domoticz_Setting_Register_Number), self.Register_Count) if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers( int(self.Domoticz_Setting_Register_Number), self.Register_Count) if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers( int(self.Domoticz_Setting_Register_Number), self.Register_Count) if (self.Read_Scale_Factor == 1): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Big) decoder.skip_bytes((self.Register_Count - 1) * 2) sf_value = decoder.decode_16bit_int() data = data[0:self.Register_Count - 1] else: sf_value = 0 Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data)) except: Domoticz.Error( "Modbus error communicating! (TCP/IP), check your settings!" ) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # DECODE DATA TYPE ######################################## # pymodbus (RTU/ASCII/RTU over TCP) will reponse in ARRAY, no matter what values read e.g. MODBUS DEBUG RESPONSE: [2] = data.registers # pymodbusTCP (TCP/IP) will give the value back e.g. MODBUS DEBUG RESPONSE: [61, 44] = data if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii" or self.Domoticz_Setting_Communication_Mode == "rtutcp"): try: Domoticz.Debug("MODBUS DEBUG - VALUE before conversion: " + str(data.registers[0])) # Added option to swap bytes (little endian) if (self.Domoticz_Setting_Data_Type == "int16s" or self.Domoticz_Setting_Data_Type == "uint16s"): decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Little, wordorder=Endian.Big) # Added option to swap words (little endian) elif (self.Domoticz_Setting_Data_Type == "int32s" or self.Domoticz_Setting_Data_Type == "uint32s" or self.Domoticz_Setting_Data_Type == "int64s" or self.Domoticz_Setting_Data_Type == "uint64s" or self.Domoticz_Setting_Data_Type == "float32s" or self.Domoticz_Setting_Data_Type == "float64s"): decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Big, wordorder=Endian.Little) # Otherwise always big endian else: decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Big, wordorder=Endian.Big) except: Domoticz.Error( "Modbus error decoding or received no data (RTU/ASCII/RTU over TCP)!, check your settings!" ) Devices[1].Update(1, "0") # Set value to 0 (error) if (self.Domoticz_Setting_Communication_Mode == "tcpip"): try: Domoticz.Debug("MODBUS DEBUG - VALUE before conversion: " + str(data)) #value = data[0] # Added option to swap bytes (little endian) if (self.Domoticz_Setting_Data_Type == "int16s" or self.Domoticz_Setting_Data_Type == "uint16s"): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Little, wordorder=Endian.Big) # Added option to swap words (little endian) elif (self.Domoticz_Setting_Data_Type == "int32s" or self.Domoticz_Setting_Data_Type == "uint32s" or self.Domoticz_Setting_Data_Type == "int64s" or self.Domoticz_Setting_Data_Type == "uint64s" or self.Domoticz_Setting_Data_Type == "float32s" or self.Domoticz_Setting_Data_Type == "float64s"): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Little) # Otherwise always big endian else: decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Big) except: Domoticz.Error( "Modbus error decoding or received no data (TCP/IP)!, check your settings!" ) Devices[1].Update(1, "0") # Set value to 0 (error) ######################################## # DECODE DATA VALUE ######################################## try: if (self.Domoticz_Setting_Data_Type == "noco"): value = data.registers[0] if (self.Domoticz_Setting_Data_Type == "bool"): value = bool(data.registers[0]) if (self.Domoticz_Setting_Data_Type == "int8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_int() if (self.Domoticz_Setting_Data_Type == "int8MSB"): value = decoder.decode_8bit_int() if (self.Domoticz_Setting_Data_Type == "int16"): value = decoder.decode_16bit_int() if (self.Domoticz_Setting_Data_Type == "int16s"): value = decoder.decode_16bit_int() if (self.Domoticz_Setting_Data_Type == "int32"): value = decoder.decode_32bit_int() if (self.Domoticz_Setting_Data_Type == "int32s"): value = decoder.decode_32bit_int() if (self.Domoticz_Setting_Data_Type == "int64"): value = decoder.decode_64bit_int() if (self.Domoticz_Setting_Data_Type == "int64s"): value = decoder.decode_64bit_int() if (self.Domoticz_Setting_Data_Type == "uint8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_uint() if (self.Domoticz_Setting_Data_Type == "uint8MSB"): value = decoder.decode_8bit_uint() if (self.Domoticz_Setting_Data_Type == "uint16"): value = decoder.decode_16bit_uint() if (self.Domoticz_Setting_Data_Type == "uint16s"): value = decoder.decode_16bit_uint() if (self.Domoticz_Setting_Data_Type == "uint32"): value = decoder.decode_32bit_uint() if (self.Domoticz_Setting_Data_Type == "uint32s"): value = decoder.decode_32bit_uint() if (self.Domoticz_Setting_Data_Type == "uint64"): value = decoder.decode_64bit_uint() if (self.Domoticz_Setting_Data_Type == "uint64s"): value = decoder.decode_64bit_uint() if (self.Domoticz_Setting_Data_Type == "float32"): value = decoder.decode_32bit_float() if (self.Domoticz_Setting_Data_Type == "float32s"): value = decoder.decode_32bit_float() if (self.Domoticz_Setting_Data_Type == "float64"): value = decoder.decode_64bit_float() if (self.Domoticz_Setting_Data_Type == "float64s"): value = decoder.decode_64bit_float() if (self.Domoticz_Setting_Data_Type == "string2"): value = decoder.decode_string(2) if (self.Domoticz_Setting_Data_Type == "string4"): value = decoder.decode_string(4) if (self.Domoticz_Setting_Data_Type == "string6"): value = decoder.decode_string(6) if (self.Domoticz_Setting_Data_Type == "string8"): value = decoder.decode_string(8) # Apply a scale factor (decimal) if (self.Domoticz_Setting_Scale_Factor == "div0"): value = str(value) if (self.Domoticz_Setting_Scale_Factor == "div10"): value = str(round(value / 10, 1)) if (self.Domoticz_Setting_Scale_Factor == "div100"): value = str(round(value / 100, 2)) if (self.Domoticz_Setting_Scale_Factor == "div1000"): value = str(round(value / 1000, 3)) if (self.Domoticz_Setting_Scale_Factor == "div10000"): value = str(round(value / 10000, 4)) if (self.Domoticz_Setting_Scale_Factor == "mul10"): value = str(value * 10) if (self.Domoticz_Setting_Scale_Factor == "mul100"): value = str(value * 100, 2) if (self.Domoticz_Setting_Scale_Factor == "mul1000"): value = str(value * 1000, 3) if (self.Domoticz_Setting_Scale_Factor == "mul10000"): value = str(value * 10000, 4) if (self.Domoticz_Setting_Scale_Factor == "sfnextreg"): if (sf_value == 0): value = str(value) if (sf_value == 1): value = str(round(value * 10, 1)) if (sf_value == 2): value = str(round(value * 100, 1)) if (sf_value == -1): value = str(round(value / 10, 1)) if (sf_value == -2): value = str(round(value / 100, 1)) Domoticz.Debug("MODBUS DEBUG - VALUE after conversion: " + str(value)) Devices[1].Update(1, value) # Update value except: Domoticz.Error( "Modbus error decoding or received no data!, check your settings!" ) Devices[1].Update(1, "0") # Set value to 0 (error)
response = client.execute(request) print repr(response.information) for reg in registers: print print "reg.address: %x" % reg.address rr = client.read_input_registers(reg.address, 1, unit=1) if hasattr(rr, "getRegister"): print "read_input_registers:", rr.getRegister(0) else: print "read_input_registers", str(rr) rr = client.read_holding_registers(reg.address, 1, unit=1) if hasattr(rr, "getRegister"): print "read_holding_registers:", rr.getRegister(0) else: print "read_holding_registers:", str(rr) for reg in coils: print print reg rr = client.read_coils(reg.address, unit=1) if hasattr(rr, "bits"): print "read_coils:", str(rr.bits) else: print "read_coils:", str(rr) rr = client.read_discrete_inputs(reg.address, unit=1) if hasattr(rr, "bits"): print "read_discrete_inputs:", str(rr.bits) else: print "read_discrete_inputs:", str(rr)
#Connect to the serial modbus server connection = client.connect() print(connection) #Starting add, num of reg to read, slave unit. # result= client.read_holding_registers(0x00, 5 ,unit= 0x01) # print(result) #Starting add, num of reg to read, slave unit. t1 = time.perf_counter() for x in range(0, 1): #print ("sending..") #time.sleep(1) # result= client.read_holding_registers(41,1,unit= 1) #print(result) result= client.read_coils(0,1,unit=1) #result=client.read_discrete_inputs(40132,3,unit=1) # result=client.read_input_registers(251,2,unit=1) #result=client.write_coil(3,1,unit=1) #result=client.write_register(11,70000,unit=1) #result=client.write_coils(1, [1,0,1], unit= 0x01) #result=client.write_registers(1, [1,2,3], unit= 0x01) t2 = time.perf_counter() print('Time %f' % (t2 - t1)) # result= client.read_holding_registers(1, 10 ,unit= 0x0a) # print(result) #rr = client.read_discrete_inputs(1, 1, unit=0x01) #print(rr)
class EPsolarTracerClient: ''' EPsolar Tracer client ''' def __init__(self, unit=1, serialclient=None, **kwargs): ''' Initialize a serial client instance ''' self.unit = unit if serialclient is None: port = kwargs.get('port', 'COM1') baudrate = kwargs.get('baudrate', 115200) self.client = ModbusClient( method="rtu", port=port, stopbits=1, bytesize=8, parity='N', baudrate=baudrate, timeout = 1.0 ) else: self.client = serialclient def connect(self): ''' Connect to the serial :returns: True if connection succeeded, False otherwise ''' cc = self.client.connect() if cc is False: print("Unable to open port. Quitting") quit() return cc def close(self): ''' Closes the underlying connection ''' return self.client.close() def read_device_info(self): #request = ReadDeviceInformationRequest(unit = self.unit) request = ReadDeviceInformationRequest(unit=self.unit) response = self.client.execute(request) return response def read_input(self, name): register = registerByName(name) if register.is_coil(): response = self.client.read_coils(register.address, register.size, unit=self.unit) elif register.is_discrete_input(): response = self.client.read_discrete_inputs(register.address, register.size, unit=self.unit) elif register.is_input_register(): response = self.client.read_input_registers(register.address, register.size, unit=self.unit) else: response = self.client.read_holding_registers(register.address, register.size, unit=self.unit) return register.decode(response) def write_output(self, name, value): register = registerByName(name) values = register.encode(value) response = False if register.is_coil(): self.client.write_coil(register.address, values, unit=self.unit) response = True elif register.is_discrete_input(): log.error("Cannot write discrete input " + repr(name)) pass elif register.is_input_register(): log.error("Cannot write input register " + repr(name)) pass else: self.client.write_registers(register.address, values, unit=self.unit) response = True return response def __enter__(self): self.connect() print("Context mngr connect") return self def __exit__(self,type,value,traceback): self.close() print("Context mngr close")
def onHeartbeat(self): #Domoticz.Log("onHeartbeat called") # Wich serial port settings to use? if (Parameters["Mode3"] == "S1B7PN"): StopBits, ByteSize, Parity = 1, 7, "N" if (Parameters["Mode3"] == "S1B7PE"): StopBits, ByteSize, Parity = 1, 7, "E" if (Parameters["Mode3"] == "S1B7PO"): StopBits, ByteSize, Parity = 1, 7, "O" if (Parameters["Mode3"] == "S1B8PN"): StopBits, ByteSize, Parity = 1, 8, "N" if (Parameters["Mode3"] == "S1B8PE"): StopBits, ByteSize, Parity = 1, 8, "E" if (Parameters["Mode3"] == "S1B8PO"): StopBits, ByteSize, Parity = 1, 8, "O" if (Parameters["Mode3"] == "S2B7PN"): StopBits, ByteSize, Parity = 2, 7, "N" if (Parameters["Mode3"] == "S2B7PE"): StopBits, ByteSize, Parity = 2, 7, "E" if (Parameters["Mode3"] == "S2B7PO"): StopBits, ByteSize, Parity = 2, 7, "O" if (Parameters["Mode3"] == "S2B8PN"): StopBits, ByteSize, Parity = 2, 8, "N" if (Parameters["Mode3"] == "S2B8PE"): StopBits, ByteSize, Parity = 2, 8, "E" if (Parameters["Mode3"] == "S2B8PO"): StopBits, ByteSize, Parity = 2, 8, "O" # How many registers to read (depending on data type)? # Added additional options for byte/word swapping registercount = 1 # Default if (Parameters["Mode6"] == "noco"): registercount = 1 if (Parameters["Mode6"] == "int8LSB"): registercount = 1 if (Parameters["Mode6"] == "int8MSB"): registercount = 1 if (Parameters["Mode6"] == "int16"): registercount = 1 if (Parameters["Mode6"] == "int16s"): registercount = 1 if (Parameters["Mode6"] == "int32"): registercount = 2 if (Parameters["Mode6"] == "int32s"): registercount = 2 if (Parameters["Mode6"] == "int64"): registercount = 4 if (Parameters["Mode6"] == "int64s"): registercount = 4 if (Parameters["Mode6"] == "uint8LSB"): registercount = 1 if (Parameters["Mode6"] == "uint8MSB"): registercount = 1 if (Parameters["Mode6"] == "uint16"): registercount = 1 if (Parameters["Mode6"] == "uint16s"): registercount = 1 if (Parameters["Mode6"] == "uint32"): registercount = 2 if (Parameters["Mode6"] == "uint32s"): registercount = 2 if (Parameters["Mode6"] == "uint64"): registercount = 4 if (Parameters["Mode6"] == "uint64s"): registercount = 4 if (Parameters["Mode6"] == "float32"): registercount = 2 if (Parameters["Mode6"] == "float32s"): registercount = 2 if (Parameters["Mode6"] == "float64"): registercount = 4 if (Parameters["Mode6"] == "float64s"): registercount = 4 if (Parameters["Mode6"] == "string2"): registercount = 2 if (Parameters["Mode6"] == "string4"): registercount = 4 if (Parameters["Mode6"] == "string6"): registercount = 6 if (Parameters["Mode6"] == "string8"): registercount = 8 # Split address to support TCP/IP device ID AddressData = Parameters["Address"].split("/") # Split on "/" UnitAddress = AddressData[0] # Is there a unit ID given after the IP? (e.g. 192.168.2.100/56) UnitIdForIp = 1 # Default if len(AddressData) > 1: UnitIdForIp = AddressData[1] ################################### # pymodbus: RTU / ASCII ################################### if (Parameters["Mode1"] == "rtu" or Parameters["Mode1"] == "ascii"): Domoticz.Debug("MODBUS DEBUG USB SERIAL HW - Port=" + Parameters["SerialPort"] + ", BaudRate=" + Parameters["Mode2"] + ", StopBits=" + str(StopBits) + ", ByteSize=" + str(ByteSize) + " Parity=" + Parity) Domoticz.Debug("MODBUS DEBUG USB SERIAL CMD - Method=" + Parameters["Mode1"] + ", Address=" + UnitAddress + ", Register=" + Parameters["Password"] + ", Function=" + Parameters["Username"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusSerialClient(method=Parameters["Mode1"], port=Parameters["SerialPort"], stopbits=StopBits, bytesize=ByteSize, parity=Parity, baudrate=int(Parameters["Mode2"]), timeout=1, retries=2) except: Domoticz.Log("Error opening Serial interface on " + Parameters["SerialPort"]) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbus: RTU over TCP ################################### if (Parameters["Mode1"] == "rtutcp"): Domoticz.Debug("MODBUS DEBUG TCP CMD - Method=" + Parameters["Mode1"] + ", Address=" + UnitAddress + ", Port=" + Parameters["Port"] + ", Register=" + Parameters["Password"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusTcpClient(host=UnitAddress, port=int(Parameters["Port"]), framer=ModbusRtuFramer, auto_open=True, auto_close=True, timeout=5) except: Domoticz.Log("Error opening TCP interface on address: " + UnitAddress) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbusTCP: TCP/IP ################################### if (Parameters["Mode1"] == "tcpip"): Domoticz.Debug("MODBUS DEBUG TCP CMD - Method=" + Parameters["Mode1"] + ", Address=" + UnitAddress + ", Port=" + Parameters["Port"] + ", Unit ID=" + UnitIdForIp + ", Register=" + Parameters["Password"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusClient(host=UnitAddress, port=int(Parameters["Port"]), unit_id=UnitIdForIp, auto_open=True, auto_close=True, timeout=5) except: Domoticz.Log("Error opening TCP/IP interface on address: " + UnitAddress) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbus section ################################### if (Parameters["Mode1"] == "rtu" or Parameters["Mode1"] == "ascii" or Parameters["Mode1"] == "rtutcp"): try: # Which function to execute? RTU/ASCII/RTU over TCP if (Parameters["Username"] == "1"): data = client.read_coils(int(Parameters["Password"]), registercount, unit=int(UnitIdForIp)) if (Parameters["Username"] == "2"): data = client.read_discrete_inputs(int( Parameters["Password"]), registercount, unit=int(UnitIdForIp)) if (Parameters["Username"] == "3"): data = client.read_holding_registers(int( Parameters["Password"]), registercount, unit=int(UnitIdForIp)) if (Parameters["Username"] == "4"): data = client.read_input_registers(int( Parameters["Password"]), registercount, unit=int(UnitIdForIp)) Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data)) except: Domoticz.Log( "Modbus error communicating! (RTU/ASCII/RTU over TCP), check your settings!" ) Devices[1].Update(0, "0") # Update device to OFF in Domoticz try: # How to decode the input? # Added option to swap bytes (little endian) if (Parameters["Mode6"] == "int16s" or Parameters["Mode6"] == "uint16s"): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Little, wordorder=Endian.Big) # Added option to swap words (little endian) elif (Parameters["Mode6"] == "int32s" or Parameters["Mode6"] == "uint32s" or Parameters["Mode6"] == "int64s" or Parameters["Mode6"] == "uint64s" or Parameters["Mode6"] == "float32s" or Parameters["Mode6"] == "float64s"): decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Little) # Otherwise always big endian else: decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Big) if (Parameters["Mode6"] == "noco"): value = data if (Parameters["Mode6"] == "int8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int8MSB"): value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int16"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int16s"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int32"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int32s"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int64"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "int64s"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "uint8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint8MSB"): value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint16"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint16s"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint32"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint32s"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint64"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "uint64s"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "float32"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float32s"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float64"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "float64s"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "string2"): value = decoder.decode_string(2) if (Parameters["Mode6"] == "string4"): value = decoder.decode_string(4) if (Parameters["Mode6"] == "string6"): value = decoder.decode_string(6) if (Parameters["Mode6"] == "string8"): value = decoder.decode_string(8) Domoticz.Debug("MODBUS DEBUG VALUE: " + str(value)) # Divide the value (decimal)? if (Parameters["Mode5"] == "div0"): value = str(value) if (Parameters["Mode5"] == "div10"): value = str(round(value / 10, 1)) if (Parameters["Mode5"] == "div100"): value = str(round(value / 100, 2)) if (Parameters["Mode5"] == "div1000"): value = str(round(value / 1000, 3)) if (Parameters["Mode5"] == "div10000"): value = str(round(value / 10000, 4)) Devices[1].Update(0, value) # Update value in Domoticz except: Domoticz.Log( "Modbus error decoding or received no data (RTU/ASCII/RTU over TCP)!, check your settings!" ) Devices[1].Update(0, "0") # Update value in Domoticz ################################### # pymodbusTCP section ################################### if (Parameters["Mode1"] == "tcpip"): try: # Which function to execute? TCP/IP if (Parameters["Username"] == "1"): data = client.read_coils(int(Parameters["Password"]), registercount) if (Parameters["Username"] == "2"): data = client.read_discrete_inputs( int(Parameters["Password"]), registercount) if (Parameters["Username"] == "3"): data = client.read_holding_registers( int(Parameters["Password"]), registercount) if (Parameters["Username"] == "4"): data = client.read_input_registers( int(Parameters["Password"]), registercount) Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data)) except: Domoticz.Log( "Modbus error communicating! (TCP/IP), check your settings!" ) Devices[1].Update(0, "0") # Update device to OFF in Domoticz try: # How to decode the input? # Added option to swap bytes (little endian) if (Parameters["Mode6"] == "int16s" or Parameters["Mode6"] == "uint16s"): decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Little, wordorder=Endian.Big) # Added option to swap words (little endian) elif (Parameters["Mode6"] == "int32s" or Parameters["Mode6"] == "uint32s" or Parameters["Mode6"] == "int64s" or Parameters["Mode6"] == "uint64s" or Parameters["Mode6"] == "float32s" or Parameters["Mode6"] == "float64s"): decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Big, wordorder=Endian.Little) # Otherwise always big endian else: decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Big, wordorder=Endian.Big) if (Parameters["Mode6"] == "noco"): value = data if (Parameters["Mode6"] == "int8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int8MSB"): value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int16"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int16s"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int32"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int32s"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int64"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "int64s"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "uint8LSB"): ignored = decoder.skip_bytes(1) value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint8MSB"): value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint16"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint16s"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint32"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint32s"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint64"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "uint64s"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "float32"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float32s"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float64"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "float64s"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "string2"): value = decoder.decode_string(2) if (Parameters["Mode6"] == "string4"): value = decoder.decode_string(4) if (Parameters["Mode6"] == "string6"): value = decoder.decode_string(6) if (Parameters["Mode6"] == "string8"): value = decoder.decode_string(8) Domoticz.Debug("MODBUS DEBUG VALUE: " + str(value)) # Divide the value (decimal)? if (Parameters["Mode5"] == "div0"): value = str(value) if (Parameters["Mode5"] == "div10"): value = str(round(value / 10, 1)) if (Parameters["Mode5"] == "div100"): value = str(round(value / 100, 2)) if (Parameters["Mode5"] == "div1000"): value = str(round(value / 1000, 3)) if (Parameters["Mode5"] == "div10000"): value = str(round(value / 10000, 4)) if (value != "0"): Devices[1].Update(1, value) # Update value in Domoticz except: Domoticz.Log( "Modbus error decoding or received no data (TCP/IP)!, check your settings!" ) Devices[1].Update(0, "0") # Update value in Domoticz
class modbusHandler: def __init__(self, type, tcpIp, tcpPort, Method, Port, Stopbits, Bytesize, Parity, Baudrate): self.modValConverter = ModValConverter() self.exceptionHandler = ModbusHandlerExceptions() self.tcpNumberOfRetries = 20 if (type == "TCP"): self.ModbusClient = ModbusTcpClient(tcpIp, tcpPort, timeout=0.5) else: if (type == "RTU"): if (len(Port) == 0): self.exceptionHandler.generateException( "NoPortSpecifiedError") self.ModbusClient = ModbusClient(method=Method, port=Port, stopbits=Stopbits, bytesize=Bytesize, parity=Parity, baudrate=Baudrate, timeout=1) self.ModbusClient.register(self.ReadFifoRequest.ReadFifoResponse) self.ModbusClient.register(self.Read2FifosRequest.Read2FifosResponse) self.ModbusClient.register( self.ReadFifoAndTimeRequest.ReadFifoAndTimeResponse) def setNumberOfRetriesTCP(self, number): self.tcpNumberOfRetries = number class DataOut: registers = [] class DataOutTwoRegs: registers0 = [] registers1 = [] class Read2FifosRequest(ModbusRequest): function_code = 66 _rtu_frame_size = 8 class Read2FifosResponse(ModbusResponse): function_code = 65 _rtu_byte_count_pos = 3 def __init__(self, values=None, **kwargs): ModbusResponse.__init__(self, **kwargs) self.values = values or [] def encode(self): result = bytes([len(self.values) * 2]) for register in self.values: result += struct.pack('>H', register) return result def decode(self, data): byte_count = int(data[1]) self.values = [] for i in range(2, byte_count + 1, 2): self.values.append(struct.unpack('>H', data[i:i + 2])[0]) def __init__(self, address=None, **kwargs): ModbusRequest.__init__(self, **kwargs) self.address = address self.count = 2 def encode(self): return struct.pack('>HH', self.address, self.count) def decode(self, data): self.address, self.count = struct.unpack('>HH', data) def execute(self, context): if not (1 <= self.count <= 0x7d0): return self.doException(ModbusExceptions.IllegalValue) if not context.validate(self.function_code, self.address, self.count): return self.doException(ModbusExceptions.IllegalAddress) values = context.getValues(self.function_code, self.address, self.count) return self.Read2FifosResponse(values) class ReadFifoRequest(ModbusRequest): function_code = 66 _rtu_frame_size = 8 class ReadFifoResponse(ModbusResponse): function_code = 24 _rtu_byte_count_pos = 3 def __init__(self, values=None, **kwargs): ModbusResponse.__init__(self, **kwargs) self.values = values or [] def encode(self): """ Encodes response pdu :returns: The encoded packet message """ # print(len(self.values)) result = bytes([len(self.values) * 2]) for register in self.values: result += struct.pack('>H', register) return result def decode(self, data): """ Decodes response pdu :param data: The packet data to decode """ # print(data) byte_count = int(data[1]) self.values = [] for i in range(2, byte_count + 1, 2): self.values.append(struct.unpack('>H', data[i:i + 2])[0]) class ReadFifoAndTimeRequest(ModbusRequest): function_code = 67 # _rtu_frame_size = 8 # fifoFreq = 100 class ReadFifoAndTimeResponse(ModbusResponse): function_code = 67 _rtu_double_byte_count_pos = 10 def __init__(self, values=None, **kwargs): ModbusResponse.__init__(self, **kwargs) self.values = values or [] # print(kwargs) def encode(self): """ Encodes response pdu :returns: The encoded packet message """ return self.values def decode(self, data): """ Decodes response pdu :param data: The packet data to decode """ timeFromStart = struct.unpack('>Q', data[0:8])[0] byte_count = struct.unpack('>H', data[8:10])[0] # print(data.hex()) # print(timeFromStart) # print(byte_count) byteCounter = 10 fifoAllRegs = [] sampleFreqs = [] while (byteCounter - 6 < byte_count): sampleFreqs.append( struct.unpack('>H', data[byteCounter:byteCounter + 2])[0]) byteCounter = byteCounter + 2 nextRegCount = struct.unpack( '>H', data[byteCounter:byteCounter + 2])[0] byteCounter = byteCounter + 2 fifoRegs = [] for i in range(nextRegCount): fifoRegs.append( struct.unpack( '>H', data[byteCounter + i * 2:byteCounter + i * 2 + 2])[0]) # fifoTimes.append(timeFromStart - (1/self.fifoFreq)*1000000*i) byteCounter = byteCounter + nextRegCount * 2 fifoAllRegs.append(fifoRegs) # print(fifoAllRegs) self.values = [timeFromStart, fifoAllRegs, sampleFreqs] def __init__(self, address=None, count=1, **kwargs): ModbusRequest.__init__(self, **kwargs) self.address = address self.count = count # print(kwargs) def encode(self): return struct.pack('>HH', self.address, self.count) def decode(self, data): self.address, self.count = struct.unpack('>HH', data) def execute(self, context): if not (1 <= self.count <= 0x7d0): return self.doException(ModbusExceptions.IllegalValue) if not context.validate(self.function_code, self.address, self.count): return self.doException(ModbusExceptions.IllegalAddress) values = context.getValues(self.function_code, self.address, self.count) return self.ReadFifoResponse(values) def connect(self): self.ModbusClient.connect() def isSlaveIsNotResponding(self, result): if (type(result) == pymodbus.exceptions.ModbusIOException): return True return False def writeCoils(self, addresS, valuE, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.write_coils(address=addresS, values=valuE, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def write16BitUnsignedRegister(self, addresS, valuE, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.write_register(address=addresS, value=valuE, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def write16BitUnsignedRegisters(self, addresS, valuE, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.write_registers(address=addresS, values=valuE, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def readCoils(self, addresS, counT, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.read_coils(address=addresS, count=counT, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def readDiscreteInputs(self, addresS, counT, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.read_discrete_inputs( address=addresS, count=counT, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def readInputRegisters(self, addresS, counT, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.read_input_registers( address=addresS, count=counT, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def readHoldingRegisters(self, addresS, counT, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): try: result = self.ModbusClient.read_holding_registers( address=addresS, count=counT, unit=uniT) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") return result def getFifoAndTime(self, addresS, number, uniT): addresS = addresS - 1 # when I ask 30001, python sends request for 30002, why??? Bug??? for x in range(self.tcpNumberOfRetries): # print(x) try: request = self.ReadFifoAndTimeRequest(address=addresS, count=number, unit=uniT) result = self.ModbusClient.execute(request) if (not self.isSlaveIsNotResponding(result)): break except pymodbus.exceptions.ConnectionException: pass if (type(result) == pymodbus.exceptions.ModbusIOException): self.exceptionHandler.generateException("ModbusIOError") try: encoded = result.encode() except AttributeError: self.exceptionHandler.generateException("EncodeError") return result if type(encoded) is not list: if (type(result) == pymodbus.pdu.ExceptionResponse): self.exceptionHandler.generateException(encoded) else: self.exceptionHandler.generateException("NotAListReturned") dataOut = [] # encoded[0] = encoded[0] # print(encoded) for i in range(number): fifoLocal = [] timeLineLocal = [] rang = len(encoded[1][i]) timeStep = (1 / encoded[2][i]) * 1000 for j in range(rang): fifoLocal.append(encoded[1][i][j]) timeLineLocal.append(encoded[0] - (rang - j) * timeStep) newAr = [fifoLocal, timeLineLocal] dataOut.append(newAr) return dataOut def getFifoAndTime16BitUnsigned(self, addresS, number, uniT): result = self.getFifoAndTime(addresS, number, uniT) result = list(map(tuple, result)) return result def getFifoAndTime16BitSigned(self, addresS, number, uniT): result = self.getFifoAndTime(addresS, number, uniT) for x in range(len(result)): result[x][0] = list( map(self.modValConverter.modTo16BitSigned, result[x][0])) result = list(map(tuple, result)) return result def getFifoAndTime32BitUnsigned(self, addresS, number, uniT): result = self.getFifoAndTime(addresS, number * 2, uniT) outData = [] for x in range(number): timeLine = result[x * 2][1] if (len(result[x * 2][0]) != len(result[x * 2][1])): self.exceptionHandler.generateException( "RegsFIFOsHaveDifferentSizes") for i in range(len(result[x * 2][0])): result[x * 2][0][i] = self.modValConverter.modTo32BitUnsigned( result[x * 2][0][i], result[x * 2 + 1][0][i]) dataLine = result[x * 2][0] outData.append((dataLine, timeLine)) return outData def getFifoAndTime32BitSigned(self, addresS, number, uniT): result = self.getFifoAndTime(addresS, number * 2, uniT) outData = [] for x in range(number): timeLine = result[x * 2][1] if (len(result[x * 2][0]) != len(result[x * 2][1])): self.exceptionHandler.generateException( "RegsFIFOsHaveDifferentSizes") for i in range(len(result[x * 2][0])): result[x * 2][0][i] = self.modValConverter.modTo32BitSigned( result[x * 2][0][i], result[x * 2 + 1][0][i]) dataLine = result[x * 2][0] outData.append((dataLine, timeLine)) return outData def getFifoAndTimeFloat(self, addresS, number, uniT): result = self.getFifoAndTime(addresS, number * 2, uniT) outData = [] for x in range(number): timeLine = result[x * 2][1] if (len(result[x * 2][0]) != len(result[x * 2][1])): self.exceptionHandler.generateException( "RegsFIFOsHaveDifferentSizes") for i in range(len(result[x * 2][0])): result[x * 2][0][i] = self.modValConverter.modToFloat( result[x * 2][0][i], result[x * 2 + 1][0][i]) dataLine = result[x * 2][0] outData.append((dataLine, timeLine)) return outData
def onHeartbeat(self): #Domoticz.Log("onHeartbeat called") # Wich serial port settings to use? if (Parameters["Mode3"] == "S1B7PN"): StopBits, ByteSize, Parity = 1, 7, "N" if (Parameters["Mode3"] == "S1B7PE"): StopBits, ByteSize, Parity = 1, 7, "E" if (Parameters["Mode3"] == "S1B7PO"): StopBits, ByteSize, Parity = 1, 7, "O" if (Parameters["Mode3"] == "S1B8PN"): StopBits, ByteSize, Parity = 1, 8, "N" if (Parameters["Mode3"] == "S1B8PE"): StopBits, ByteSize, Parity = 1, 8, "E" if (Parameters["Mode3"] == "S1B8PO"): StopBits, ByteSize, Parity = 1, 8, "O" if (Parameters["Mode3"] == "S2B7PN"): StopBits, ByteSize, Parity = 2, 7, "N" if (Parameters["Mode3"] == "S2B7PE"): StopBits, ByteSize, Parity = 2, 7, "E" if (Parameters["Mode3"] == "S2B7PO"): StopBits, ByteSize, Parity = 2, 7, "O" if (Parameters["Mode3"] == "S2B8PN"): StopBits, ByteSize, Parity = 2, 8, "N" if (Parameters["Mode3"] == "S2B8PE"): StopBits, ByteSize, Parity = 2, 8, "E" if (Parameters["Mode3"] == "S2B8PO"): StopBits, ByteSize, Parity = 2, 8, "O" # How many registers to read (depending on data type)? registercount = 1 # Default if (Parameters["Mode6"] == "noco"): registercount = 1 if (Parameters["Mode6"] == "int8"): registercount = 1 if (Parameters["Mode6"] == "int16"): registercount = 1 if (Parameters["Mode6"] == "int32"): registercount = 2 if (Parameters["Mode6"] == "int64"): registercount = 4 if (Parameters["Mode6"] == "uint8"): registercount = 1 if (Parameters["Mode6"] == "uint16"): registercount = 1 if (Parameters["Mode6"] == "uint32"): registercount = 2 if (Parameters["Mode6"] == "uint64"): registercount = 4 if (Parameters["Mode6"] == "float32"): registercount = 2 if (Parameters["Mode6"] == "float64"): registercount = 4 if (Parameters["Mode6"] == "string2"): registercount = 2 if (Parameters["Mode6"] == "string4"): registercount = 4 if (Parameters["Mode6"] == "string6"): registercount = 6 if (Parameters["Mode6"] == "string8"): registercount = 8 ################################### # pymodbus: RTU / ASCII ################################### if (Parameters["Mode1"] == "rtu" or Parameters["Mode1"] == "ascii"): Domoticz.Debug("MODBUS DEBUG USB SERIAL HW - Port=" + Parameters["SerialPort"] + ", BaudRate=" + Parameters["Mode2"] + ", StopBits=" + str(StopBits) + ", ByteSize=" + str(ByteSize) + " Parity=" + Parity) Domoticz.Debug("MODBUS DEBUG USB SERIAL CMD - Method=" + Parameters["Mode1"] + ", Address=" + Parameters["Address"] + ", Register=" + Parameters["Password"] + ", Function=" + Parameters["Username"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusSerialClient(method=Parameters["Mode1"], port=Parameters["SerialPort"], stopbits=StopBits, bytesize=ByteSize, parity=Parity, baudrate=int(Parameters["Mode2"]), timeout=1, retries=2) except: Domoticz.Log("Error opening Serial interface on " + Parameters["SerialPort"]) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbus: RTU over TCP ################################### if (Parameters["Mode1"] == "rtutcp"): Domoticz.Debug("MODBUS DEBUG TCP CMD - Method=" + Parameters["Mode1"] + ", Address=" + Parameters["Address"] + ", Port=" + Parameters["Port"] + ", Register=" + Parameters["Password"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusTcpClient(host=Parameters["Address"], port=int(Parameters["Port"]), timeout=5) except: Domoticz.Log("Error opening TCP interface on address: " + Parameters["Address"]) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbusTCP: TCP/IP ################################### if (Parameters["Mode1"] == "tcpip"): Domoticz.Debug("MODBUS DEBUG TCP CMD - Method=" + Parameters["Mode1"] + ", Address=" + Parameters["Address"] + ", Port=" + Parameters["Port"] + ", Register=" + Parameters["Password"] + ", Data type=" + Parameters["Mode6"]) try: client = ModbusClient(host=Parameters["Address"], port=int(Parameters["Port"]), auto_open=True, auto_close=True, timeout=5) except: Domoticz.Log("Error opening TCP/IP interface on address: " + Parameters["Address"]) Devices[1].Update(0, "0") # Update device in Domoticz ################################### # pymodbus section ################################### if (Parameters["Mode1"] == "rtu" or Parameters["Mode1"] == "ascii" or Parameters["Mode1"] == "rtutcp"): try: # Which function to execute? RTU/ASCII/RTU over TCP if (Parameters["Username"] == "1"): data = client.read_coils(int(Parameters["Password"]), registercount, unit=int(Parameters["Address"])) if (Parameters["Username"] == "2"): data = client.read_discrete_inputs( int(Parameters["Password"]), registercount, unit=int(Parameters["Address"])) if (Parameters["Username"] == "3"): data = client.read_holding_registers( int(Parameters["Password"]), registercount, unit=int(Parameters["Address"])) if (Parameters["Username"] == "4"): data = client.read_input_registers( int(Parameters["Password"]), registercount, unit=int(Parameters["Address"])) Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data)) except: Domoticz.Log( "Modbus error communicating! (RTU/ASCII/RTU over TCP), check your settings!" ) Devices[1].Update(0, "0") # Update device to OFF in Domoticz try: # How to decode the input? decoder = BinaryPayloadDecoder.fromRegisters( data.registers, byteorder=Endian.Big, wordorder=Endian.Big) if (Parameters["Mode6"] == "noco"): value = data.registers[0] if (Parameters["Mode6"] == "int8"): value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int16"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int32"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int64"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "uint8"): value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint16"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint32"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint64"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "float32"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float64"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "string2"): value = decoder.decode_string(2) if (Parameters["Mode6"] == "string4"): value = decoder.decode_string(4) if (Parameters["Mode6"] == "string6"): value = decoder.decode_string(6) if (Parameters["Mode6"] == "string8"): value = decoder.decode_string(8) Domoticz.Debug("MODBUS DEBUG VALUE: " + str(value)) # Divide the value (decimal)? if (Parameters["Mode5"] == "div0"): value = str(value) if (Parameters["Mode5"] == "div10"): value = str(round(value / 10, 1)) if (Parameters["Mode5"] == "div100"): value = str(round(value / 100, 2)) if (Parameters["Mode5"] == "div1000"): value = str(round(value / 1000, 3)) Devices[1].Update(0, value) # Update value in Domoticz except: Domoticz.Log( "Modbus error decoding or recieved no data (RTU/ASCII/RTU over TCP)!, check your settings!" ) Devices[1].Update(0, "0") # Update value in Domoticz ################################### # pymodbusTCP section ################################### if (Parameters["Mode1"] == "tcpip"): try: # Which function to execute? TCP/IP if (Parameters["Username"] == "1"): data = client.read_coils(int(Parameters["Password"]), registercount) if (Parameters["Username"] == "2"): data = client.read_discrete_inputs( int(Parameters["Password"]), registercount) if (Parameters["Username"] == "3"): data = client.read_holding_registers( int(Parameters["Password"]), registercount) if (Parameters["Username"] == "4"): data = client.read_input_registers( int(Parameters["Password"]), registercount) Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data[0])) except: Domoticz.Log( "Modbus error communicating! (TCP/IP), check your settings!" ) Devices[1].Update(0, "0") # Update device to OFF in Domoticz try: # How to decode the input? decoder = BinaryPayloadDecoder.fromRegisters( data, byteorder=Endian.Big, wordorder=Endian.Big) if (Parameters["Mode6"] == "noco"): value = data[0] if (Parameters["Mode6"] == "int8"): value = decoder.decode_8bit_int() if (Parameters["Mode6"] == "int16"): value = decoder.decode_16bit_int() if (Parameters["Mode6"] == "int32"): value = decoder.decode_32bit_int() if (Parameters["Mode6"] == "int64"): value = decoder.decode_64bit_int() if (Parameters["Mode6"] == "uint8"): value = decoder.decode_8bit_uint() if (Parameters["Mode6"] == "uint16"): value = decoder.decode_16bit_uint() if (Parameters["Mode6"] == "uint32"): value = decoder.decode_32bit_uint() if (Parameters["Mode6"] == "uint64"): value = decoder.decode_64bit_uint() if (Parameters["Mode6"] == "float32"): value = decoder.decode_32bit_float() if (Parameters["Mode6"] == "float64"): value = decoder.decode_64bit_float() if (Parameters["Mode6"] == "string2"): value = decoder.decode_string(2) if (Parameters["Mode6"] == "string4"): value = decoder.decode_string(4) if (Parameters["Mode6"] == "string6"): value = decoder.decode_string(6) if (Parameters["Mode6"] == "string8"): value = decoder.decode_string(8) Domoticz.Debug("MODBUS DEBUG VALUE: " + str(value)) # Divide the value (decimal)? if (Parameters["Mode5"] == "div0"): value = str(value) if (Parameters["Mode5"] == "div10"): value = str(round(value / 10, 1)) if (Parameters["Mode5"] == "div100"): value = str(round(value / 100, 2)) if (Parameters["Mode5"] == "div1000"): value = str(round(value / 1000, 3)) Devices[1].Update(0, value) # Update value in Domoticz except: Domoticz.Log( "Modbus error decoding or recieved no data (TCP/IP)!, check your settings!" ) Devices[1].Update(0, "0") # Update value in Domoticz
class EPsolarTracerClient: ''' EPsolar Tracer client ''' def __init__(self, unit=1, serialclient=None, **kwargs): ''' Initialize a serial client instance ''' self.unit = unit if serialclient == None: port = kwargs.get('port', '/dev/ttyXRUSB0') baudrate = kwargs.get('baudrate', 115200) self.client = ModbusClient(method='rtu', port=port, baudrate=baudrate, kwargs=kwargs) else: self.client = serialclient def connect(self): ''' Connect to the serial :returns: True if connection succeeded, False otherwise ''' return self.client.connect() def close(self): ''' Closes the underlying connection ''' return self.client.close() def read_device_info(self): request = ReadDeviceInformationRequest(unit=self.unit) response = self.client.execute(request) return response def read_input(self, name): register = registerByName(name) if register.is_coil(): response = self.client.read_coils(register.address, register.size, unit=self.unit) elif register.is_discrete_input(): response = self.client.read_discrete_inputs(register.address, register.size, unit=self.unit) elif register.is_input_register(): response = self.client.read_input_registers(register.address, register.size, unit=self.unit) else: response = self.client.read_holding_registers(register.address, register.size, unit=self.unit) return register.decode(response) def write_output(self, name, value): register = registerByName(name) values = register.encode(value) response = False if register.is_coil(): self.client.write_coil(register.address, values, unit=self.unit) response = True elif register.is_discrete_input(): _logger.error("Cannot write discrete input " + repr(name)) pass elif register.is_input_register(): _logger.error("Cannot write input register " + repr(name)) pass else: print(name, register.address, values, self.unit) registers = self.client.write_registers(register.address, values, unit=self.unit) print(registers) response = True return response def readRTC(self): register = registerByName('Real time clock 1') sizeAddress = 3 result = self.client.read_holding_registers(register.address, sizeAddress, unit=self.unit) return self.decodeRTC(result.registers) def writeRTC(self, datetime): register = registerByName('Real time clock 1') values = self.encodeRTC(datetime) self.client.write_registers(register.address, values, unit=self.unit) return True def decodeRTC(self, rtc): s = 2000 secMin = rtc[0] hourDay = rtc[1] monthYear = rtc[2] secs = (secMin & 0xff) hour = (hourDay & 0xff) month = (monthYear & 0xff) minut = secMin >> 8 day = hourDay >> 8 year = monthYear >> 8 return datetime(s + year, month, day, hour, minut, secs) def encodeRTC(self, datetime): s = 2000 rtc1 = int((datetime.minute << 8) | datetime.second) rtc2 = int((datetime.day << 8) | datetime.hour) rtc3 = int((datetime.year - s << 8) | datetime.month) return [rtc1, rtc2, rtc3]
class Modbus_Device(): def __init__(self, method='rtu', port='/dev/ttyUSB0', baudrate=9600, timeout=3, parity='E', stopbits=1, bytesize=8, slaveAddress=0): self.method = method self.port = port self.baudrate = baudrate self.timeout = timeout self.parity = parity self.stopbits = stopbits self.bytesize = bytesize self.slaveAddress = slaveAddress self.client = ModbusSerialClient( method=self.method, port=self.port, #'/dev/ttyUSB0'or #'COM9', baudrate=self.baudrate, timeout=self.timeout, parity=self.parity, stopbits=self.stopbits, bytesize=self.bytesize) def readRegister(self, function_Code=3, register=0, count=1, Print=True): if self.client.connect(): print('Client connected') if function_Code == 1: response = self.client.read_coils(address=register, count=count, unit=self.slaveAddress) if not response.isError(): print(decode(response.registers)) return decode(response.registers) else: print(response) if function_Code == 2: response = self.client.read_discrete_inputs( address=register, count=count, unit=self.slaveAddress) if not response.isError(): print(decode(response.registers)) return decode(response.registers) else: print(response) if function_Code == 3: print('Reading holding regisster') response = self.client.read_holding_registers( address=register, count=count, unit=self.slaveAddress) if not response.isError(): print(decode(response.registers)) return decode(response.registers) else: print(response) if function_Code == 4: response = self.client.read_input_registers( address=register, count=count, unit=self.slaveAddress) if not response.isError(): print(decode(response.registers)) return decode(response.registers) else: print(response) else: print('Cannot connect to the Modbus Server/Slave') def writeRegister(self, function_Code=6, register=0, count=1, Print=True, value=0): if self.client.connect(): if function_Code == 6: if count > 1: response = self.client.write_registers( address=register, values=value, unit=self.slaveAddress) if not response.isError(): print("Written Successfully!") else: print("Error Writing") else: response = self.client.write_register( address=register, value=value, unit=self.slaveAddress) if not response.isError(): print("Written Successfully!") else: print("Error Writing") if function_Code == 5: response = self.client.write_coil(address=register, values=value, unit=self.slaveAddress) if not response.isError(): print("Written Successfully!") else: print("Error Writing")
class reader: def __init__(self, xmlFile): """Parses the xml file and creates the reader""" self.xml = xmlFile validationInt = validateXml(self.xml) if validationInt == -1: raise Exception('XML File Error: devicData node missing') elif validationInt == -2: raise Exception('XML File Error: modbus type not set') elif validationInt == -3: raise Exception('XML File Error: ip address missing') elif validationInt == -4: raise Exception('XML File Error: comm port missing') elif validationInt == -5: raise Exception('XML File Error: baud rate missing') elif validationInt == -10: raise Exception('XML File Error: No register mappings') elif validationInt == -11: raise Exception('XML File Error: Duplicated Input register mapping') elif validationInt == -12: raise Exception('XML File Error: Duplicated Discrete input mapping') elif validationInt == -13: raise Exception('XML File Error: Duplicated Holding register mapping') elif validationInt == -14: raise Exception('XML File Error: Duplicated Coil mapping') self.xmlData = self._parseXml() if self.xmlData.get('modbusType') == 'tcp/ip': self.client = ModbusTcpClient(self.xmlData.get('ip'), self.xmlData.get('port'), timeout=1) elif self.xmlData.get('modbusType') == 'rtu': self.client = ModbusSerialClient(method = 'rtu', port = self.xmlData.get('com'), baudrate = self.xmlData.get('baud'), bytesize = self.xmlData.get('bytesize'), stopbits = self.xmlData.get('stopbits'), parity = self.xmlData.get('parity'), timeout = self.xmlData.get('timeout'), ) def update_all(self): #Update register values if self.client.connect(): #Input registers for ir in self.xmlData.get('registers').get('ir'): self._update_reg(ir) self._writeToLog(ir) #ir['str_repr'] = f"INPUT REGISTER | REGISTER: {int(ir.get('register'))} | DESCRIPTION: {ir.get('description')} | VALUE: {ir.get('value')}" #Holding registers for hr in self.xmlData.get('registers').get('hr'): self._update_reg(hr) self._writeToLog(hr) #hr['str_repr'] = f"HOLDING REGISTER | REGISTER: {int(hr.get('register'))} | DESCRIPTION: {hr.get('description')} | VALUE: {hr.get('value')}" #Coils for co in self.xmlData.get('registers').get('co'): self._update_reg(co) self._writeToLog(co) #co['str_repr'] = f"COIL | REGISTER: {int(co.get('register'))} |DESCRIPTION: {co.get('description')} | VALUE: {co.get('value')}" #Discrete inputs for di in self.xmlData.get('registers').get('di'): self._update_reg(di) self._writeToLog(di) #di['str_repr'] = f"DISCRETE INPUT | REGISTER: {int(di.get('register'))} | DESCRIPTION: {di.get('description')} | VALUE: {di.get('value')}" else: print('Connection failed') def _writeToLog(self, reg=None): if not self.xmlData.get('log'): return if self.xmlData.get('log') not in ['all', 'partial']: return if type(reg)==dict: if self.xmlData.get('log') == 'partial' and not reg.get('log'): return logstr = f"READ;{datetime.datetime.now()};{reg.get('type')};{reg.get('register')};{reg.get('description')};{reg.get('value')};\n" elif type(reg)==tuple: logstr = f"WRITE;{datetime.datetime.now()};{reg[0]};{reg[1]};;{reg[2]};\n" else: #fault return #Check if the logging file exists already if os.path.isfile('modbusLog.csv'): with open('modbusLog.csv', 'a') as f: f.write(logstr) else: with open('modbusLog.csv', 'w') as f: f.write(f"Operation;Timestamp;Register Type;Register #; Description;Register Value;\n") f.write(logstr) def _parseXml(self): """Parses xml file and validates the registers""" data = {} tree = ET.parse(self.xml) root = tree.getroot() registers = root.find('registers') #Discrete inputs (booleans) di = [] diNode = registers.find('di') if diNode != None: for mapping in diNode.findall('mapping'): mapDict = { 'register' : int(mapping.get('register')), 'type' : 'di', 'description' : mapping.get('description', '-'), 'value' : 0, 'log' : mapping.get('log', False) } di.append(mapDict) #Input registers (analogs) ir = [] irNode = registers.find('ir') if irNode != None: for mapping in irNode.findall('mapping'): mapDict = { 'register' : int(mapping.get('register')), 'type' : 'ir', 'description' : mapping.get('description', '-'), 'bit0' : mapping.get('bit0', '-'), 'bit1' : mapping.get('bit1', '-'), 'bit2' : mapping.get('bit2', '-'), 'bit3' : mapping.get('bit3', '-'), 'bit4' : mapping.get('bit4', '-'), 'bit5' : mapping.get('bit5', '-'), 'bit6' : mapping.get('bit6', '-'), 'bit7' : mapping.get('bit7', '-'), 'bit8' : mapping.get('bit8', '-'), 'bit9' : mapping.get('bit9', '-'), 'bit10' : mapping.get('bit10', '-'), 'bit11' : mapping.get('bit11', '-'), 'bit12' : mapping.get('bit12', '-'), 'bit13' : mapping.get('bit13', '-'), 'bit14' : mapping.get('bit14', '-'), 'bit15' : mapping.get('bit15', '-'), 'value' : 0, 'log' : mapping.get('log', False) } ir.append(mapDict) #Holding registers (analogs) hr = [] hrNode = registers.find('hr') if hrNode != None: for mapping in hrNode.findall('mapping'): mapDict = { 'register' : int(mapping.get('register')), 'type' : 'hr', 'description' : mapping.get('description', '-'), 'bit0' : mapping.get('bit0', '-'), 'bit1' : mapping.get('bit1', '-'), 'bit2' : mapping.get('bit2', '-'), 'bit3' : mapping.get('bit3', '-'), 'bit4' : mapping.get('bit4', '-'), 'bit5' : mapping.get('bit5', '-'), 'bit6' : mapping.get('bit6', '-'), 'bit7' : mapping.get('bit7', '-'), 'bit8' : mapping.get('bit8', '-'), 'bit9' : mapping.get('bit9', '-'), 'bit10' : mapping.get('bit10', '-'), 'bit11' : mapping.get('bit11', '-'), 'bit12' : mapping.get('bit12', '-'), 'bit13' : mapping.get('bit13', '-'), 'bit14' : mapping.get('bit14', '-'), 'bit15' : mapping.get('bit15', '-'), 'value' : 0, 'log' : mapping.get('log', False) } hr.append(mapDict) #Coils (booleans) co = [] coNode = registers.find('co') if coNode != None: for mapping in coNode.findall('mapping'): mapDict = { 'register' : int(mapping.get('register')), 'type' : 'co', 'description' : mapping.get('description', '-'), 'value' : 0, 'log' : mapping.get('log', False) } co.append(mapDict) data['registers'] = { 'di' : di, 'ir' : ir, 'hr' : hr, 'co' : co } #Parse device data deviceData = root.find('deviceData') data['vendorName'] = deviceData.get("vendorName", '') data['productCode'] = deviceData.get("productCode", '') data['vendorUrl'] = deviceData.get("vendorUrl", '') data['productName'] = deviceData.get("productName", '') data['modelName'] = deviceData.get("modelName", '') data['version'] = deviceData.get("version", '0.0-1') data['modbusType'] = deviceData.get('modbusType') data['com'] = deviceData.get("com", None) data['baud'] = int(deviceData.get("baud", "9600")) data['stopbits'] = int(deviceData.get("stopbits", "1")) data['bytesize'] = int(deviceData.get("bytesize", "8")) data['parity'] = deviceData.get("parity", "E") data['ip'] = deviceData.get("ip", "localhost") data['port'] = int(deviceData.get("port", 502)) data['timeout'] = int(deviceData.get('timeout', "2")) #Log to file can be all or partial #all logs all, partial only those with the flag data['log'] = deviceData.get('log', False) return data def connect(self): return self.client.connect() def _update_reg(self, register): try: if register.get('type') == 'di': data = self.client.read_discrete_inputs(address=register.get('register'), count=1) if not isinstance(data, ExceptionResponse): register['value'] = data.getBit(0) else: register['value'] = str(data) elif register.get('type') == 'co': data = self.client.read_coils(address=register.get('register'), count=1) if not isinstance(data, ExceptionResponse): register['value'] = data.getBit(0) else: register['value'] = str(data) elif register.get('type') == 'ir': data = self.client.read_input_registers(address=register.get('register'), count=1) if not isinstance(data, ExceptionResponse): register['value'] = data.getRegister(0) else: register['value'] = str(data) elif register.get('type') == 'hr': data = self.client.read_holding_registers(address=register.get('register'), count=1) if not isinstance(data, ExceptionResponse): register['value'] = data.getRegister(0) else: register['value'] = str(data) except Exception as e: register['value'] = str(e) + ' ' + str(data) def get_ir(self, register): #gets the value of the register, regardless if it exists if self.connect(): try: data = self.client.read_input_registers(address=register, count=1) if not isinstance(data, ExceptionResponse): return data.getRegister(0) else: return str(data) except Exception as e: return str(e) + str(data) def get_hr(self, register): #gets the value of the register, regardless if it exists if self.connect(): try: data = self.client.read_holding_registers(address=register, count=1) if not isinstance(data, ExceptionResponse): return data.getRegister(0) else: return str(data) except Exception as e: return str(e) + str(data) def get_di(self, register): #gets the value of the register, regardless if it exists if self.connect(): try: data = self.client.read_discrete_inputs(address=register, count=1) if not isinstance(data, ExceptionResponse): return data.getBit(0) else: return str(data) except Exception as e: return str(e) + ' ' + str(data) def get_co(self, register): #gets the value of the register, regardless if it exists if self.connect(): try: data = self.client.read_coils(address=register, count=1) if not isinstance(data, ExceptionResponse): return data.getBit(0) else: return str(data) except Exception as e: return str(e) + ' ' + str(data) def write_coil(self, register, value): if self.connect(): if value > 1: value = 1 self._writeToLog(reg=('co', register, value)) return self.client.write_coil(address=register, value=value) def write_register(self, register, value): if self.connect(): self._writeToLog(reg=('hr', register, value)) return self.client.write_register(address=register, value=value)
baudrate=9600) connection = client.connect() pinout = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] try: time.sleep(2) plc01.ready() while True: result = client.read_discrete_inputs(1024, 16, unit=1) for x in range(0, 16): if x < 8: plc01['x' + str(x)] = result.bits[x] else: plc01['x' + str(x + 2)] = result.bits[x] result = client.read_coils(1280, 16, unit=1) for x in range(0, 16): if x < 8: plc01['y' + str(x)] = result.bits[x] else: plc01['y' + str(x + 2)] = result.bits[x] result = client.read_coils(2560, 16, unit=1) for x in range(0, 8): plc01['M' + str(512 + x)] = result.bits[x] for x in range(0, 16): pinout[x] = plc01['M' + str(100 + x)] client.write_coils(2148, pinout, unit=1) time.sleep(.025)
class EPsolarTracerClient: """ EPsolar Tracer client """ def __init__(self, unit: int=1, serialclient: ModbusClient=None, **kwargs): """ Initialize a serial client instance """ self.unit = unit if serialclient is None: if not 'port' in kwargs: kwargs['port'] = '/dev/ttyXRUSB0' if not 'baudrate' in kwargs: kwargs['baudrate'] = 115200 self.client = ModbusClient(method='rtu', **kwargs) else: self.client = serialclient def connect(self): """ Connect to the serial :returns: True if connection succeeded, False otherwise """ try: return self.client.connect() except AttributeError: # !FIXME there is bug in pymodbus when rtu mode is used and connection is not made: # !FIXME AttributeError: 'NoneType' object has no attribute 'interCharTimeout' that simply means connection failed return False def close(self): """ Closes the underlying connection """ return self.client.close() def read_device_info(self): request = ReadDeviceInformationRequest(unit=self.unit) response = self.client.execute(request) return response def read_input(self, register_type: Union[RegisterTypeEnum, CoilTypeEnum]): if type(register_type) == 'str': register = registerByName(register_type) elif type(register_type) == RegisterTypeEnum: register = registers.get(register_type) elif type(register_type) == CoilTypeEnum: register = coils.get(register_type) if not register: raise Exception("Unknown register {}".format(register_type.name)) if register.is_coil(): response = self.client.read_coils(register.address, register.size, unit=self.unit) elif register.is_discrete_input(): response = self.client.read_discrete_inputs(register.address, register.size, unit=self.unit) elif register.is_input_register(): response = self.client.read_input_registers(register.address, register.size, unit=self.unit) else: response = self.client.read_holding_registers(register.address, register.size, unit=self.unit) return register.decode(response) def write_output(self, register_type: Union[RegisterTypeEnum, CoilTypeEnum], value): if type(register_type) == 'str': register = registerByName(register_type) elif type(register_type) == RegisterTypeEnum: register = registers.get(register_type) elif type(register_type) == CoilTypeEnum: register = coils.get(register_type) if not register: raise Exception("Unknown register {}".format(register_type.name)) values = register.encode(value) response = False if register.is_coil(): self.client.write_coil(register.address, values, unit=self.unit) response = True elif register.is_discrete_input(): _logger.error("Cannot write discrete input {}".format(register_type.name)) pass elif register.is_input_register(): _logger.error("Cannot write input register {}".format(register_type.name)) pass else: self.client.write_registers(register.address, values, unit=self.unit) response = True return response
class EPsolarTracerClient: ''' EPsolar Tracer client ''' def __init__(self, unit=1, serialclient=None, **kwargs): ''' Initialize a serial client instance ''' self.unit = unit if serialclient == None: port = kwargs.get('port', '/dev/ttyXRUSB0') baudrate = kwargs.get('baudrate', 115200) self.client = ModbusClient(method='rtu', port=port, baudrate=baudrate, kwargs=kwargs) else: self.client = serialclient def connect(self): ''' Connect to the serial :returns: True if connection succeeded, False otherwise ''' return self.client.connect() def close(self): ''' Closes the underlying connection ''' return self.client.close() def read_device_info(self): request = ReadDeviceInformationRequest(unit=self.unit) response = self.client.execute(request) return response def read_input(self, name): register = registerByName(name) if register.is_coil(): response = self.client.read_coils(register.address, register.size, unit=self.unit) elif register.is_discrete_input(): response = self.client.read_discrete_inputs(register.address, register.size, unit=self.unit) elif register.is_input_register(): response = self.client.read_input_registers(register.address, register.size, unit=self.unit) else: response = self.client.read_holding_registers(register.address, register.size, unit=self.unit) return register.decode(response) def write_output(self, name, value): register = registerByName(name) values = register.encode(value) response = False if register.is_coil(): self.client.write_coil(register.address, values, unit=self.unit) response = True elif register.is_discrete_input(): _logger.error("Cannot write discrete input " + repr(name)) pass elif register.is_input_register(): _logger.error("Cannot write input register " + repr(name)) pass else: self.client.write_registers(register.address, values, unit=self.unit) response = True return response
from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder from pymodbus.constants import Endian import time onOff = True #client.write_coil(0, True) isOk = True count = 1 while isOk: # read the setable devices result = client.read_coils(count-1, count) if not result.isError(): print(result.bits) else: print('ERROR') break count += 1 ''' for i in range(0, 8): # read only inputs result = client.read_discrete_inputs(i, 2) if not result.isError(): print(result.bits) else:
# are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). #---------------------------------------------------------------------------# #rq = client.write_coil(1, True) var = 1 try: conn=psycopg2.connect("dbname='hcam' user='******' password='******'") except: print "I am unable to connect to the database." cur = conn.cursor() while var == 1 : rr1 = client.read_coils(2048,10,unit=0x01) #print rr1.bits[0] #print rr1.bits[1] #print rr1.bits[2] #print rr1.bits[8] rr2 = client.read_coils(3099,2,unit=0x01) #print rr2.bits[0] #print rr2.bits[1] rr3 = client.read_holding_registers(1088,2,unit=0x01) #print hex(rr3.registers[0]).lstrip("0x") #print hex(rr3.registers[1]).lstrip("0x") try: query = "insert into presion_flujo(id,fecha,presion,flujo) values(nextval('seq_presion_flujo'), current_timestamp, %s, %s)"
class M98: def __init__(self, **kwargs): self.conn = ModbusSerialClient('rtu', **kwargs) self.conn.connect() model = self.conn.read_holding_registers(Reg.MODEL, unit=1).registers[0] try: self.model = Model(model) except ValueError: raise RuntimeError('unknown device model: %d' % model) def _cmd(self, cmd: Cmd): self.conn.write_registers(Reg.CMD, [cmd], unit=1) class EnableContext: def __init__(self, main, prev_state): self.main = main self.prev_state = prev_state def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): self.main._cmd(Cmd.ON if self.prev_state else Cmd.OFF) def enable(self, state: bool): before_state = self.enabled() self._cmd(Cmd.ON if state else Cmd.OFF) return self.EnableContext(self, before_state) def enabled(self): return self.conn.read_coils(Coil.ISTATE, unit=1).getBit(0) def cc_mode(self, current, risetime=None): self.conn.write_registers(Reg.IFIX, _float(current), skip_encode=True, unit=1) if risetime: self.conn.write_registers(Reg.TMCCS, _float(risetime * 1000), skip_encode=True, unit=1) self._cmd(Cmd.CC_Soft) else: self._cmd(Cmd.CC) return (self.voltage, self.current) def battery_mode(self, current, end_voltage, start_capacity=0): self.conn.write_registers(Reg.IFIX, _float(current), skip_encode=True, unit=1) # set end voltage and reset start capacity self.conn.write_registers(Reg.UBATTEND, _float(end_voltage, start_capacity), skip_encode=True, unit=1) self._cmd(Cmd.Battery_Test) return (self.voltage, self.current, self.capacity) def cw_mode(self, power): self.conn.write_registers(Reg.PFIX, _float(power), skip_encode=True, unit=1) self._cmd(Cmd.CW) return (self.voltage, self.current) def voltage(self): return _defloat( self.conn.read_holding_registers(Reg.U, 2, unit=1).registers)[0] def current(self): return _defloat( self.conn.read_holding_registers(Reg.I, 2, unit=1).registers)[0] def capacity(self): return _defloat( self.conn.read_holding_registers(Reg.BATT, 2, unit=1).registers)[0]
def run_sync_client(): client = ModbusClient(method='rtu', port=PORT, timeout=1, baudrate=BAUD_RATE, parity=PARITY, stopbits=STOP_BITS, bytesize=N_DATA_BITS) client.connect() # ------------------------------------------------------------------------# # Specify slave to query # ------------------------------------------------------------------------# # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Read coils") read_resp = client.read_coils(1, 1, unit=UNIT) log.debug(read_resp) # ----------------------------------------------------------------------- # # Example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some Modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied asynchronous Modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a coil and read back") write_resp = client.write_coil(0, True, unit=UNIT) read_resp = client.read_coils(0, 1, unit=UNIT) assert not write_resp.isError() # test that we are not an error assert read_resp.bits[0] # test the expected value log.debug("Write to multiple coils and read back- test 1") write_resp = client.write_coils(1, [True] * 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error read_resp = client.read_coils(1, 21, unit=UNIT) assert not read_resp.isError() # test that we are not an error resp = [True] * 21 # If the returned output quantity is not a multiple of eight, # the remaining bits in the final data byte will be padded with zeros # (toward the high order end of the byte) resp.extend([False] * 3) assert read_resp.bits == resp # test the expected value log.debug("Write to multiple coils and read back - test 2") write_resp = client.write_coils(1, [False] * 8, unit=UNIT) read_resp = client.read_coils(1, 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error assert read_resp.bits == [False] * 8 # test the expected value log.debug("Read discrete inputs") read_resp = client.read_discrete_inputs(0, 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error log.debug("Write to a holding register and read back") write_resp = client.write_register(1, 10, unit=UNIT) read_resp = client.read_holding_registers(1, 1, unit=UNIT) assert not write_resp.isError() # test that we are not an error assert read_resp.registers[0] == 10 # test the expected value log.debug("Write to multiple holding registers and read back") write_resp = client.write_registers(1, [10] * 8, unit=UNIT) read_resp = client.read_holding_registers(1, 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error assert read_resp.registers == [10] * 8 # test the expected value log.debug("Read input registers") read_resp = client.read_input_registers(1, 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20] * 8, } log.debug("Read write registers simultaneously") write_resp = client.readwrite_registers(unit=UNIT, **arguments) read_resp = client.read_holding_registers(1, 8, unit=UNIT) assert not write_resp.isError() # test that we are not an error assert write_resp.registers == [20] * 8 # test the expected value assert read_resp.registers == [20] * 8 # test the expected value # ----------------------------------------------------------------------- # # Close the client # ----------------------------------------------------------------------- # client.close()
def run_sync_client(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # If you use the UDP or TCP clients, you can override the framer being used # to use a custom implementation (say RTU over TCP). By default they use # the socket framer:: # # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # # There are also other options that can be set on the client that controls # how transactions are performed. The current ones are: # # * retries - Specify how many retries to allow per transaction (default=3) # * retry_on_empty - Is an empty response a retry (default = False) # * source_address - Specifies the TCP source address to bind to # * strict - Applicable only for Modbus RTU clients. # Adheres to modbus protocol for timing restrictions # (default = True). # Setting this to False would disable the inter char timeout # restriction (t1.5) for Modbus RTU # # # Here is an example of using these options:: # # client = ModbusClient('localhost', retries=3, retry_on_empty=True) # ------------------------------------------------------------------------# #client = ModbusClient('localhost', port=5020) # from pymodbus.transaction import ModbusRtuFramer # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # client = ModbusClient(method='binary', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='ascii', port='/dev/ptyp0', timeout=1) client = ModbusClient(method='rtu', port=sys.argv[1], timeout=1, baudrate=int(sys.argv[2])) client.connect() # ------------------------------------------------------------------------# # specify slave to query # ------------------------------------------------------------------------# # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) log.debug(rr) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied asynchronous modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = client.write_coil(0, True, unit=UNIT) rr = client.read_coils(0, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = client.write_coils(1, [True]*8, unit=UNIT) assert(not rq.isError()) # test that we are not an error rr = client.read_coils(1, 21, unit=UNIT) assert(not rr.isError()) # test that we are not an error resp = [True]*21 # If the returned output quantity is not a multiple of eight, # the remaining bits in the final data byte will be padded with zeros # (toward the high order end of the byte). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = client.read_discrete_inputs(0, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error log.debug("Write to a holding register and read back") rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = client.read_input_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = client.readwrite_registers(unit=UNIT, **arguments) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close()
def run_sync_client(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # If you use the UDP or TCP clients, you can override the framer being used # to use a custom implementation (say RTU over TCP). By default they use # the socket framer:: # # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # # There are also other options that can be set on the client that controls # how transactions are performed. The current ones are: # # * retries - Specify how many retries to allow per transaction (default=3) # * retry_on_empty - Is an empty response a retry (default = False) # * source_address - Specifies the TCP source address to bind to # * strict - Applicable only for Modbus RTU clients. # Adheres to modbus protocol for timing restrictions # (default = True). # Setting this to False would disable the inter char timeout # restriction (t1.5) for Modbus RTU # # # Here is an example of using these options:: # # client = ModbusClient('localhost', retries=3, retry_on_empty=True) # ------------------------------------------------------------------------# client = ModbusClient('localhost', port=5020) # from pymodbus.transaction import ModbusRtuFramer # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # client = ModbusClient(method='binary', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='ascii', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='rtu', port='/dev/ptyp0', timeout=1, # baudrate=9600) client.connect() # ------------------------------------------------------------------------# # specify slave to query # ------------------------------------------------------------------------# # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) log.debug(rr) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied asynchronous modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = client.write_coil(0, True, unit=UNIT) rr = client.read_coils(0, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = client.write_coils(1, [True]*8, unit=UNIT) assert(not rq.isError()) # test that we are not an error rr = client.read_coils(1, 21, unit=UNIT) assert(not rr.isError()) # test that we are not an error resp = [True]*21 # If the returned output quantity is not a multiple of eight, # the remaining bits in the final data byte will be padded with zeros # (toward the high order end of the byte). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = client.read_discrete_inputs(0, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error log.debug("Write to a holding register and read back") rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = client.read_input_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = client.readwrite_registers(unit=UNIT, **arguments) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close()
class modbusport(object): """ this class handles the communications with all the modbus devices""" def __init__(self, sendmessage, adderror, addserial): self.sendmessage = sendmessage # function to create an Artisan message to the user in the message line self.adderror = adderror # signal an error to the user self.addserial = addserial # add to serial log # retries self.readRetries = 1 #default initial settings. They are changed by settingsload() at initiation of program acording to the device chosen self.comport = "COM5" #NOTE: this string should not be translated. self.baudrate = 115200 self.bytesize = 8 self.parity = 'N' self.stopbits = 1 self.timeout = 1.0 self.PID_slave_ID = 0 self.PID_SV_register = 0 self.PID_p_register = 0 self.PID_i_register = 0 self.PID_d_register = 0 self.PID_ON_action = "" self.PID_OFF_action = "" self.input1slave = 0 self.input1register = 0 self.input1float = False self.input1bcd = False self.input1code = 3 self.input1div = 0 # 0: none, 1: 1/10, 2:1/100 self.input1mode = "C" self.input2slave = 0 self.input2register = 0 self.input2float = False self.input2bcd = False self.input2code = 3 self.input2div = 0 self.input2mode = "C" self.input3slave = 0 self.input3register = 0 self.input3float = False self.input3bcd = False self.input3code = 3 self.input3div = 0 self.input3mode = "C" self.input4slave = 0 self.input4register = 0 self.input4float = False self.input4bcd = False self.input4code = 3 self.input4div = 0 self.input4mode = "C" self.input5slave = 0 self.input5register = 0 self.input5float = False self.input5bcd = False self.input5code = 3 self.input5div = 0 self.input5mode = "C" self.input6slave = 0 self.input6register = 0 self.input6float = False self.input6bcd = False self.input6code = 3 self.input6div = 0 self.input6mode = "C" self.SVmultiplier = 0 self.PIDmultiplier = 0 self.byteorderLittle = False self.wordorderLittle = True self.master = None self.COMsemaphore = QSemaphore(1) self.host = '127.0.0.1' # the TCP/UDP host self.port = 502 # the TCP/UDP port self.type = 0 # type = # 0: Serial RTU # 1: Serial ASCII # 2: Serial Binary # 3: TCP # 4: UDP self.lastReadResult = 0 # this is set by eventaction following some custom button/slider Modbus actions with "read" command self.commError = False # True after a communication error was detected and not yet cleared by receiving proper data # this garantees a minimum of 30 miliseconds between readings and 80ms between writes (according to the Modbus spec) on serial connections # this sleep delays between requests seems to be beneficial on slow RTU serial connections like those of the FZ-94 def sleepBetween(self, write=False): if write: # if self.type in [3,4]: # TCP or UDP # time.sleep(0.040) pass # handled in MODBUS lib # else: time.sleep(0.035) else: if self.type in [ 3, 4 ]: # delay between writes only on serial connections pass else: time.sleep(0.035) def address2register(self, addr, code=3): if code == 3 or code == 6: return addr - 40001 else: return addr - 30001 def isConnected(self): return not (self.master is None) and self.master.socket def disconnect(self): try: self.master.close() except Exception: pass self.master = None def connect(self): # if self.master and not self.master.socket: # self.master = None if self.master is None: self.commError = False try: # as in the following the port is None, no port is opened on creation of the (py)serial object if self.type == 1: # Serial ASCII from pymodbus.client.sync import ModbusSerialClient self.master = ModbusSerialClient(method='ascii', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=True, timeout=self.timeout) elif self.type == 2: # Serial Binary from pymodbus.client.sync import ModbusSerialClient self.master = ModbusSerialClient(method='binary', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=True, timeout=self.timeout) elif self.type == 3: # TCP from pymodbus.client.sync import ModbusTcpClient try: self.master = ModbusTcpClient( host=self.host, port=self.port, retry_on_empty=True, retries=1, timeout=0.9, #self.timeout ) self.readRetries = 0 except: self.master = ModbusTcpClient( host=self.host, port=self.port, ) elif self.type == 4: # UDP from pymodbus.client.sync import ModbusUdpClient try: self.master = ModbusUdpClient( host=self.host, port=self.port, retry_on_empty=True, retries=3, timeout=0.7, #self.timeout ) except: # older versions of pymodbus don't support the retries, timeout nor the retry_on_empty arguments self.master = ModbusUdpClient( host=self.host, port=self.port, ) else: # Serial RTU from pymodbus.client.sync import ModbusSerialClient self.master = ModbusSerialClient(method='rtu', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=False, timeout=self.timeout) self.readRetries = 1 self.master.connect() self.adderror( QApplication.translate("Error Message", "Connected via MODBUS", None)) time.sleep(.5) # avoid possible hickups on startup except Exception as ex: _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " connect() {0}").format( str(ex)), exc_tb.tb_lineno) # function 15 (Write Multiple Coils) def writeCoils(self, slave, register, values): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_coils(int(register), list(values), unit=int(slave)) time.sleep(.3) # avoid possible hickups on startup except Exception as ex: # self.disconnect() # import traceback # traceback.print_exc(file=sys.stdout) _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeCoils() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 5 (Write Single Coil) def writeCoil(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_coil(int(register), value, unit=int(slave)) time.sleep(.3) # avoid possible hickups on startup except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeCoil() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # write value to register on slave (function 6 for int or function 16 for float) # value can be one of string (containing an int or float), an int or a float def writeRegister(self, slave, register, value): # print("writeRegister",slave,register,value) if stringp(value): if "." in value: self.writeWord(slave, register, value) else: self.writeSingleRegister(slave, register, value) elif isinstance(value, int): self.writeSingleRegister(slave, register, value) elif isinstance(value, float): self.writeWord(slave, register, value) # function 6 (Write Single Holding Register) def writeSingleRegister(self, slave, register, value): # _logger.debug("writeSingleRegister(%d,%d,%d)" % (slave,register,value)) try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_register(int(register), int(value), unit=int(slave)) time.sleep(.03) # avoid possible hickups on startup except Exception as ex: # _logger.debug("writeSingleRegister exception: %s" % str(ex)) # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeSingleRegister() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 22 (Mask Write Register) def maskWriteRegister(self, slave, register, and_mask, or_mask): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.mask_write_register(int(register), int(and_mask), int(or_mask), unit=int(slave)) time.sleep(.03) except Exception as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeMask() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 16 (Write Multiple Holding Registers) # values is a list of integers or one integer def writeRegisters(self, slave, register, values): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_registers(int(register), values, unit=int(slave)) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeRegisters() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 16 (Write Multiple Holding Registers) # value=int or float # writes a single precision 32bit float (2-registers) def writeWord(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() builder = getBinaryPayloadBuilder(self.byteorderLittle, self.wordorderLittle) builder.add_32bit_float(float(value)) payload = builder.build() # .tolist() self.master.write_registers(int(register), payload, unit=int(slave), skip_encode=True) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # translates given int value int a 16bit BCD and writes it into one register def writeBCD(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() builder = getBinaryPayloadBuilder(self.byteorderLittle, self.wordorderLittle) r = convert_to_bcd(int(value)) builder.add_16bit_uint(r) payload = builder.build() # .tolist() self.master.write_registers(int(register), payload, unit=int(slave), skip_encode=True) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers) def readFloat(self, slave, register, code=3): r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() retry = self.readRetries while True: if code == 3: res = self.master.read_holding_registers(int(register), 2, unit=int(slave)) else: res = self.master.read_input_registers(int(register), 2, unit=int(slave)) if res is None or res.isError(): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 #time.sleep(0.020) else: raise Exception("Exception response") else: break decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_32bit_float() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate("Error Message", "Modbus Communication Resumed", None)) return r except Exception: # as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readFloat() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary settings = str(self.comport) + "," + str( self.baudrate) + "," + str(self.bytesize) + "," + str( self.parity) + "," + str(self.stopbits) + "," + str( self.timeout) ser_str = "MODBUS readFloat :" + settings + " || Slave = " + str( slave) + " || Register = " + str( register) + " || Code = " + str(code) if r is not None: ser_str = ser_str + " || Rx = " + str(r) self.addserial(ser_str) # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers) def readBCD(self, slave, register, code=3): r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() retry = self.readRetries while True: if code == 3: res = self.master.read_holding_registers(int(register), 1, unit=int(slave)) else: res = self.master.read_input_registers(int(register), 1, unit=int(slave)) if res is None or res.isError(): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 #time.sleep(0.020) else: raise Exception("Exception response") else: break decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_16bit_uint() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate("Error Message", "Modbus Communication Resumed", None)) time.sleep( 0.020 ) # we add a small sleep between requests to help out the slow Loring electronic return convert_from_bcd(r) except Exception: # as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readBCD() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary settings = str(self.comport) + "," + str( self.baudrate) + "," + str(self.bytesize) + "," + str( self.parity) + "," + str(self.stopbits) + "," + str( self.timeout) ser_str = "MODBUS readBCD :" + settings + " || Slave = " + str( slave) + " || Register = " + str( register) + " || Code = " + str(code) if r is not None: ser_str = ser_str + " || Rx = " + str(r) self.addserial(ser_str) # as readSingleRegister, but does not retry nor raise and error and returns a None instead # also does not reserve the port via a semaphore! def peekSingleRegister(self, slave, register, code=3): try: if code == 1: res = self.master.read_coils(int(register), 1, unit=int(slave)) elif code == 2: res = self.master.read_discrete_inputs(int(register), 1, unit=int(slave)) elif code == 4: res = self.master.read_input_registers(int(register), 1, unit=int(slave)) else: # code==3 res = self.master.read_holding_registers(int(register), 1, unit=int(slave)) except Exception: res = None if res is not None and not res.isError(): # requires pymodbus v1.5.1 if code in [1, 2]: if res is not None and res.bits[0]: return 1 else: return 0 else: decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_16bit_uint() return r else: return None # function 1 (Read Coil) # function 2 (Read Discrete Input) # function 3 (Read Multiple Holding Registers) and # function 4 (Read Input Registers) def readSingleRegister(self, slave, register, code=3): # import logging # logging.basicConfig() # log = logging.getLogger() # log.setLevel(logging.DEBUG) r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() retry = self.readRetries while True: try: if code == 1: res = self.master.read_coils(int(register), 1, unit=int(slave)) elif code == 2: res = self.master.read_discrete_inputs(int(register), 1, unit=int(slave)) elif code == 4: res = self.master.read_input_registers(int(register), 1, unit=int(slave)) else: # code==3 res = self.master.read_holding_registers( int(register), 1, unit=int(slave)) except Exception: res = None if res is None or res.isError(): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 time.sleep(0.020) else: raise Exception("Exception response") else: break if code in [1, 2]: if res is not None and res.bits[0]: r = 1 else: r = 0 return r else: decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_16bit_uint() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate("Error Message", "Modbus Communication Resumed", None)) return r except Exception: # as ex: # self.disconnect() # import traceback # traceback.print_exc(file=sys.stdout) # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) self.commError = True finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary settings = str(self.comport) + "," + str( self.baudrate) + "," + str(self.bytesize) + "," + str( self.parity) + "," + str(self.stopbits) + "," + str( self.timeout) ser_str = "MODBUS readSingleRegister :" + settings + " || Slave = " + str( slave) + " || Register = " + str( register) + " || Code = " + str(code) if r is not None: ser_str = ser_str + " || Rx = " + str(r) self.addserial(ser_str) def setTarget(self, sv): if self.PID_slave_ID: multiplier = 1. if self.SVmultiplier == 1: multiplier = 10. elif self.SVmultiplier == 2: multiplier = 100. self.writeSingleRegister(self.PID_slave_ID, self.PID_SV_register, int(round(sv * multiplier))) def setPID(self, p, i, d): if self.PID_slave_ID and not (self.PID_p_register == self.PID_i_register == self.PID_d_register == 0): multiplier = 1. if self.PIDmultiplier == 1: multiplier = 10. elif self.PIDmultiplier == 2: multiplier = 100. self.writeSingleRegister(self.PID_slave_ID, self.PID_p_register, p * multiplier) self.writeSingleRegister(self.PID_slave_ID, self.PID_i_register, i * multiplier) self.writeSingleRegister(self.PID_slave_ID, self.PID_d_register, d * multiplier)
# Can write registers # registers = builder.to_registers() # client.write_registers(address, registers, unit=1) # Or can write encoded binary string #client.write_register() #request = ModbusSerialClient.(address, values, **kwargs) #client.execute(request) #client.write_register(1, value=payload, skip_encode=True) import time onOff = True while True: if onOff: payload = on() else: payload = off() onOff = not onOff print(payload) #client.write_registers(register_address, payload, skip_encode=True, unit=5) client.write_registers(0, values=payload, skip_encode=True) time.sleep(1) result = client.read_coils(1, 1) print(result) client.close()
class modbusClient: """Class to carry out MODBUS read/write requests Usage: Ensure all params are setup in the 'modbusSettings' file Call 'openConnection' to connect to the assigned server Use 'dataHandler' to read or write data to the server Call 'closeConnection' to safely close the connection """ def __init__(self): """Load settings and connect to the designated slave""" self.modbusCfg = yamlImport.importYAML("./cfg/modbusSettings.yaml") if self.modbusCfg['logging'] == "enable": self.log = self.__logging() if self.__setupClient() == 0: return 0 if self.openConnection() == 0: return 0 def __logging(self): """Setup and enable logging on the client :return: enabled log instance """ import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.INFO) return log def __setupClient(self): """Setup MODBUS client object""" if self.modbusCfg['method'] == "tcp": try: self.client = ModbusTcpClient(self.modbusCfg['ip'],\ self.modbusCfg['tcpPort']) except: raise return 0 elif self.modbusCfg['method'] == "rtu": try: self.client = ModbusSerialClient(self.modbusCfg['method'],\ self.modbusCfg['rtuPort'],\ self.modbusCfg['stopbits'],\ self.modbusCfg['bytesize'],\ self.modbusCfg['parity'],\ self.modbusCfg['baudrate'],\ self.modbusCfg['timeout']) except: raise return 0 else: raise NameError("Unsupported method") return 0 def openConnection(self): """Attempt connection with the MODBUS server""" for i in range(3): if self.client.connect() == True: return 1 else: print "Attempt " + str(i) + " failed" if i == 2: raise IOError("Failed to connect to specified server") return 0 time.sleep(0.5) def closeConnection(self): """Close connection with the MODBUS server""" try: self.client.close() except: print("Error - See log for details") return 0 return 1 def dataHandler(self, op, reg, addr, length=None, data=None, encoding=1): """Handle the MODBUS read/write requests and pass to the appropriate function Arguments: :param op: Operation to perform (R/W) :param reg: Modbus register to access (1-4) :param addr: Address to start operation at :type op: string :type reg: int :type addr: int Keyword Arguments: :param length: Length of registers to read (default None) :param data: Data to write to the slave :type length: int :type data: list :return: List containing the requested data or confimation of send. """ if op == 'r': for i in range(3): r = self.__readData(reg, addr, length, encoding) if (r == ConnectionException) or (r == ModbusIOException): print("Read attempt " + str(i) + " failed") if i == 2: #TODO: remove sys exit and handle properly raise SystemExit('Modbus Error: Failed 3 Attemps') elif r == ValueError: #TODO: remove sys exit and handle properly raise SystemExit('Invalid Register - Use 15 or 16') else: return r if op == 'w': for i in range(3): w = self.__writeData(reg, addr, data, encoding) if (w == ConnectionException) or (w == ModbusIOException): print("Write attempt " + str(i) + " failed") if i == 2: #TODO: remove sys exit and handle properly raise SystemExit('Modbus Error: Failed 3 Attemps') elif w == ValueError: #TODO: remove sys exit and handle properly raise SystemExit('Invalid Register - Use 15 or 16') else: return w return ValueError('Invalid Operation') def __readData(self, reg, addr, length, encoding): """Read data from the MODBUS Slave Called by 'dataHandler' in modbusClient.py Arguments: :param reg: Modbus register to access (1-4) :param addr: Address to start reading from :param length: Quantity of registers to read :param encoding: States whether data should be decoded :type reg: int :type addr: int :type length: int :type encoding: int :return: List containing the requested data or failure exception. """ data = [] if 1 <= reg <= 2: try: if reg == 1: co = self.client.read_coils(addr,length,unit=0x01) else: co = self.client.read_discrete_inputs(addr,length,unit=0x01) except ConnectionException: return ConnectionException if co.function_code != reg: return ModbusIOException for i in range(length): data.append(co.getBit(i)) return data elif 3 <= reg <= 4: try: if reg == 3: hr = self.client.read_holding_registers(addr,length,unit=0x01) else: hr = self.client.read_input_registers(addr,length,unit=0x01) except ConnectionException: return ConnectionException if hr.function_code != reg: return ModbusIOException for i in range(length): data.append(hr.getRegister(i)) if encoding == 1: return self.__decodeData(data) return data else: return ValueError def __writeData(self, reg, addr, data, encoding): """Write data to the MODBUS slave Called by 'dataHandler' in modbusClient.py Arguments: :param reg: Modbus register to access (15 or 16) :param addr: Address to start writing to :param data: List of data to write to the device :param encoding: States whether data should be encoded first :type reg: int :type addr: int :type length: int :type encoding: int :return: success or failure exception """ if reg == 15: try: co = self.client.write_coils(addr,data,unit=0x01) except ConnectionException: return ConnectionException if co.function_code != reg: return ModbusIOException elif reg == 16: if encoding == 1: data = self.__encodeData(data) try: hr = self.client.write_registers(addr,data,unit=0x01) except ConnectionException: return ConnectionException if hr.function_code != reg: return ModbusIOException else: return ValueError def __encodeData(self, data): """Encode data to 32bit float Function encodes a list of data passed to it into a 32 bit float packet that can be written directly to the MODBUS server table. Arguments: :param data: Float to be encoded :type data: list """ builder = BinaryPayloadBuilder(endian=Endian.Little) try: for i in range(0,len(data)): builder.add_32bit_float(data[i]) except TypeError: builder.add_32bit_float(data) return builder.to_registers() def __decodeData(self, data): """Decode MODBUS data to float Function decodes a list of MODBUS 32bit float data passed to it into its respective list of floats. Arguments: :param data: Data to be decoded :type data: list """ returnData = [0]*(len(data)/2) decoder = BinaryPayloadDecoder.fromRegisters(data, endian=Endian.Little) for i in range(0,len(data)/2): returnData[i] = round(decoder.decode_32bit_float(),2) return returnData
def run_sync_client(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # If you use the UDP or TCP clients, you can override the framer being used # to use a custom implementation (say RTU over TCP). By default they use # the socket framer:: # # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # # There are also other options that can be set on the client that controls # how transactions are performed. The current ones are: # # * retries - Specify how many retries to allow per transaction (default=3) # * retry_on_empty - Is an empty response a retry (default = False) # * source_address - Specifies the TCP source address to bind to # # Here is an example of using these options:: # # client = ModbusClient('localhost', retries=3, retry_on_empty=True) # ------------------------------------------------------------------------# # client = ModbusClient('localhost', port=5020) # client = ModbusClient(method='ascii', port='/dev/pts/2', timeout=1) client = ModbusClient(method='rtu', port='/dev/ttyS3', baudrate=9600, timeout=1) client.connect() # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). # ----------------------------------------------------------------------- # # 读模块型号 rr = client.read_holding_registers(40001, 1, unit=UNIT) print(rr.registers[0]) # 读两路输入(寄存器地址200,共2个) log.debug("Read discrete inputs") rr = client.read_discrete_inputs(200, 2, unit=UNIT) print(rr.bits) # bit0表示DI1的状态,bit1表示DI2 # 写单个输出DO1(寄存器地址100) log.debug("Write to a Coil and read back") rq = client.write_coil(100, True, unit=UNIT) rr = client.read_coils(100, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits[0] == True) # test the expected value # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close()
#!/usr/bin/env python # test script to write to coils on enervent ventilation unit from pymodbus.client.sync import ModbusSerialClient as ModbusClient import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) client = ModbusClient(method='rtu', port='/dev/ttyUSB0', timeout=1) client.connect() log.debug("Reading Coils") # starting register, number of registers, slave unit id rr = client.read_coils(12, 1, unit=0x01) #hh = client.read_holding_registers(1,45,unit=0x01) #print "holding registers:" #print hh.registers #print "coild register:" #print rr.bits #print("{}: {}".format("summernight cooling status:", rr.bits[0])) #rq = client.write_register(53, 70, unit=0x01) #rqq = client.write_register(53, 71, unit=0x01) #print rqq kk = client.read_holding_registers(53,1,unit=0x01) print "ventilation:"
def poll(self): types = { 'b': 1, 'B': 1, 'h': 1, 'H': 1, 'i': 2, 'I': 2, 'q': 4, 'Q': 4, 'f': 2, 'd': 4, 's': 0, 'c': 1 } measures = {} self.__lacquire() client = ModbusClient(method=self.method, port=self.port, stopbits=self.stopbits, bytesize=self.bytesize, partity=self.parity, baudrate=self.baudrate, timeout=self.timeout, retry_on_empty=True, retry_on_invalid=True) if not client.connect(): logger.error("Cannot connect to device %s" % (self.port)) self.__lrelease() return None ut = time.time() for row in self.mapping: (name, descr, unit, datatype, rw, scale, offset, register) = row register = int(register) scale = float(scale) offset = float(offset) length = types.get(datatype) string = re.match(r'^s(\d*)$', datatype) if string: length = int(string.group(1)) >> 1 try: if register > 40000: addr = register - 40001 result = client.read_holding_registers(addr, length, unit=self.slaveid) elif register > 30000: addr = register - 30001 result = client.read_input_registers(addr, length, unit=self.slaveid) elif register > 10000: addr = register - 10001 result = client.read_discrete_inputs(addr, length, unit=self.slaveid) else: addr = register - 1 result = client.read_coils(addr, length, unit=self.slaveid) except ConnectionException as e: logger.error( "Error reading modbus device %s slave %d register %d: %s" % (self.host, self.slaveid, register, str(e))) client.close() self.__lrelease() return None if type(result) == ExceptionResponse: logger.error( "Error reading modbus device %s slave %d register %d: %s" % (self.port, self.slaveid, register, result)) client.close() self.__lrelease() return None if result.isError(): logger.error( "Error reading modbus device %s slave %d register %d" % (self.port, self.slaveid, register)) client.close() self.__lrelease() return None if hasattr(result, 'bits'): measures[name] = result.bits[0] logger.debug( 'Modbus device: %s slave: %s register: %s (%s) value: %s' % (self.port, self.slaveid, register, name, result.bits[0])) continue try: decoder = BinaryPayloadDecoder.fromRegisters( result.registers, byteorder=Endian.Big, wordorder=self.endian) if string: value = decoder.decode_string(255).decode() measures[name] = value logger.debug( 'Modbus device: %s slave: %s register: %s (%s) value: %s' % (self.port, self.slaveid, register, name, value)) continue if datatype == 'b': value = decoder.decode_8bit_int() if datatype == 'B': value = decoder.decode_8bit_uint() if datatype == 'h': value = decoder.decode_16bit_int() if datatype == 'H': value = decoder.decode_16bit_uint() if datatype == 'i': value = decoder.decode_32bit_int() if datatype == 'I': value = decoder.decode_32bit_uint() if datatype == 'q': value = decoder.decode_64bit_int() if datatype == 'Q': value = decoder.decode_64bit_uint() if datatype == 'f': value = decoder.decode_32bit_float() if datatype == 'd': value = decoder.decode_64bit_float() except (AttributeError, ValueError, struct.error): logger.error( "Error reading modbus device %s slave %d register %d" % (self.port, self.slaveid, register)) client.close() self.__lrelease() return None if math.isnan(value) or type(value) == bool: logger.error( "Error reading modbus device %s slave %d register %d" % (self.port, self.slaveid, register)) client.close() self.__lrelease() return None measures[name] = round(value * scale, 14) + offset logger.debug( 'Modbus device: %s slave: %s register: %s (%s) value: %s %s' % (self.port, self.slaveid, register, name, value, unit)) client.close() self.__lrelease() data = { 'ts': ut, 'client_id': self.clientid, 'device_id': self.deviceid, 'measures': measures } return data
print "[01] version ................................... : ",convert_bit_to_string(result.bits[1],"STANDARD+ABSENCE","ALLEMAGNE+STANDBY") print "[02] free contact .............................. : ",convert_bit_to_string(result.bits[2],"NO","NC") print "[03] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[3],"False","True") print "[04] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[4],"False","True") print "[05] defroost mode ............................. : ",convert_bit_to_string(result.bits[5],"desactivated","actived") print "[06] extract motor state ....................... : ",convert_bit_to_string(result.bits[6],"ok","error") print "[07] input motor state ......................... : ",convert_bit_to_string(result.bits[7],"ok","error") print "[08] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[8],"False","True") print "[09] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[9],"False","True") print "[10] inside temperature sensor state (tint) .... : ",convert_bit_to_string(result.bits[10],"ok","error") print "[11] outside temperature sensor state (tout) ... : ",convert_bit_to_string(result.bits[11],"ok","error") print "[12] extract temperature sensor state (text) ... : ",convert_bit_to_string(result.bits[12],"ok","error") print "[13] input temperature sensor state (tinp) ..... : ",convert_bit_to_string(result.bits[13],"ok","error") print "[14] alarm filters state ....................... : ",convert_bit_to_string(result.bits[14],"off","on") result = client.read_coils(address=0x00, count=14, unit=0x01) print "====================================================" print "COILS" print "====================================================" print "[00] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[0],"False","True") print "[01] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[1],"False","True") print "[02] pre heating battery ....................... : ",convert_bit_to_string(result.bits[2],"not installed","installed") print "[03] post heating battery ...................... : ",convert_bit_to_string(result.bits[3],"not installed","installed") print "[04] sense of switch ........................... : ",convert_bit_to_string(result.bits[4],"no","nc") print "[05] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[5],"False","True") print "[06] selection of version ...................... : ",convert_bit_to_string(result.bits[6],"STANDBY+ABSENCE","ALLEMAGNE+STANDBY") print "[07] activation mode stanby absence ............ : ",convert_bit_to_string(result.bits[7],"STANBY/ABSENCE ON","STANDBY/ABSENCE OFF") print "[08] bypass auto control ....................... : ",convert_bit_to_string(result.bits[8],"actived","desactivated") print "[09] manual bypass ............................. : ",convert_bit_to_string(result.bits[9],"desactivated","activated") print "[10] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[10],"False","True") print "[11] ** UNDOCUMENTED / UNSED ** ................ : ",convert_bit_to_string(result.bits[11],"False","True")
class modbusport(object): """ this class handles the communications with all the modbus devices""" def __init__(self, sendmessage, adderror, addserial, aw): self.sendmessage = sendmessage # function to create an Artisan message to the user in the message line self.adderror = adderror # signal an error to the user self.addserial = addserial # add to serial log self.aw = aw # retries self.readRetries = 1 #default initial settings. They are changed by settingsload() at initiation of program acording to the device chosen self.comport = "COM5" #NOTE: this string should not be translated. self.baudrate = 115200 self.bytesize = 8 self.parity = 'N' self.stopbits = 1 self.timeout = 1.0 self.PID_slave_ID = 0 self.PID_SV_register = 0 self.PID_p_register = 0 self.PID_i_register = 0 self.PID_d_register = 0 self.PID_ON_action = "" self.PID_OFF_action = "" self.channels = 8 self.inputSlaves = [0] * self.channels self.inputRegisters = [0] * self.channels self.inputFloats = [False] * self.channels self.inputBCDs = [False] * self.channels self.inputCodes = [3] * self.channels self.inputDivs = [0] * self.channels # 0: none, 1: 1/10, 2:1/100 self.inputModes = ["C"] * self.channels self.optimizer = True # if set, values of consecutive register addresses are requested in single requests # MODBUS functions associated to dicts associating MODBUS slave ids to registers in use # for optimized read of full register segments with single requests # this dict is re-computed on each connect() by a call to updateActiveRegisters() # NOTE: for registers of type float and BCD (32bit = 2x16bit) also the succeeding register is registered here self.activeRegisters = {} # the readings cache that is filled by requesting sequences of values in blocks self.readingsCache = {} self.SVmultiplier = 0 self.PIDmultiplier = 0 self.byteorderLittle = False self.wordorderLittle = True self.master = None self.COMsemaphore = QSemaphore(1) self.host = '127.0.0.1' # the TCP/UDP host self.port = 502 # the TCP/UDP port self.type = 0 # type = # 0: Serial RTU # 1: Serial ASCII # 2: Serial Binary # 3: TCP # 4: UDP self.lastReadResult = 0 # this is set by eventaction following some custom button/slider Modbus actions with "read" command self.commError = False # True after a communication error was detected and not yet cleared by receiving proper data # this garantees a minimum of 30 miliseconds between readings and 80ms between writes (according to the Modbus spec) on serial connections # this sleep delays between requests seems to be beneficial on slow RTU serial connections like those of the FZ-94 def sleepBetween(self, write=False): if write: # if self.type in [3,4]: # TCP or UDP # time.sleep(0.040) pass # handled in MODBUS lib # else: time.sleep(0.035) else: if self.type in [ 3, 4 ]: # delay between writes only on serial connections pass else: time.sleep(0.035) def address2register(self, addr, code=3): if code == 3 or code == 6: return addr - 40001 else: return addr - 30001 def isConnected(self): return not (self.master is None) and self.master.socket def disconnect(self): try: self.master.close() except Exception: pass self.master = None def connect(self): # if self.master and not self.master.socket: # self.master = None if self.master is None: self.commError = False try: # as in the following the port is None, no port is opened on creation of the (py)serial object if self.type == 1: # Serial ASCII from pymodbus.client.sync import ModbusSerialClient self.master = ModbusSerialClient(method='ascii', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=True, timeout=self.timeout) elif self.type == 2: # Serial Binary from pymodbus.client.sync import ModbusSerialClient # @Reimport self.master = ModbusSerialClient(method='binary', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=True, timeout=self.timeout) elif self.type == 3: # TCP from pymodbus.client.sync import ModbusTcpClient try: self.master = ModbusTcpClient( host=self.host, port=self.port, retry_on_empty=True, retries=1, timeout=0.9, #self.timeout ) self.readRetries = 0 except: self.master = ModbusTcpClient( host=self.host, port=self.port, ) elif self.type == 4: # UDP from pymodbus.client.sync import ModbusUdpClient try: self.master = ModbusUdpClient( host=self.host, port=self.port, retry_on_empty=True, retries=3, timeout=0.7, #self.timeout ) except: # older versions of pymodbus don't support the retries, timeout nor the retry_on_empty arguments self.master = ModbusUdpClient( host=self.host, port=self.port, ) else: # Serial RTU from pymodbus.client.sync import ModbusSerialClient # @Reimport self.master = ModbusSerialClient( method='rtu', port=self.comport, baudrate=self.baudrate, bytesize=self.bytesize, parity=self.parity, stopbits=self.stopbits, retry_on_empty=False, strict= False, # settings this to False disables the inter char timeout restriction timeout=self.timeout) # self.master.inter_char_timeout = 0.05 self.readRetries = 1 self.master.connect() self.updateActiveRegisters() time.sleep(.5) # avoid possible hickups on startup if self.isConnected() != None: self.sendmessage( QApplication.translate("Message", "Connected via MODBUS", None)) except Exception as ex: _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " connect() {0}").format( str(ex)), exc_tb.tb_lineno) ########## MODBUS optimizer for fetching register data in batches # MODBUS code => slave => [registers] def updateActiveRegisters(self): self.activeRegisters = {} for c in range(self.channels): slave = self.inputSlaves[c] if slave != 0: register = self.inputRegisters[c] code = self.inputCodes[c] registers = [register] if self.inputFloats[c] or self.inputBCDs[c]: registers.append(register + 1) if not (code in self.activeRegisters): self.activeRegisters[code] = {} if slave in self.activeRegisters[code]: self.activeRegisters[code][slave].extend(registers) else: self.activeRegisters[code][slave] = registers def clearReadingsCache(self): self.readingsCache = {} def cacheReadings(self, code, slave, register, results): if not (code in self.readingsCache): self.readingsCache[code] = {} if not slave in self.readingsCache[code]: self.readingsCache[code][slave] = {} for i, v in enumerate(results): self.readingsCache[code][slave][register + i] = v def readActiveRegisters(self): if not self.optimizer: return try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.clearReadingsCache() for code in self.activeRegisters: for slave in self.activeRegisters[code]: registers = sorted(self.activeRegisters[code][slave]) # split in successive sequences gaps = [[s, e] for s, e in zip(registers, registers[1:]) if s + 1 < e] edges = iter(registers[:1] + sum(gaps, []) + registers[-1:]) sequences = list( zip(edges, edges) ) # list of pairs of the form (start-register,end-register) just_send = False for seq in sequences: retry = self.readRetries register = seq[0] count = seq[1] - seq[0] + 1 res = None if just_send: self.sleepBetween( ) # we start with a sleep, as it could be that just a send command happend before the semaphore was catched just_send = True while True: try: # we cache only MODBUS function 3 and 4 (not 1 and 2!) if code == 3: res = self.master.read_holding_registers( register, count, unit=slave) elif code == 4: res = self.master.read_input_registers( register, count, unit=slave) except Exception: res = None if res is None or res.isError( ): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 time.sleep(0.020) else: raise Exception("Exception response") else: break if res is not None: if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate( "Error Message", "Modbus Communication Resumed", None)) self.cacheReadings(code, slave, register, res.registers) #note: logged chars should be unicode not binary if self.aw.seriallogflag: ser_str = "MODBUS readSingleRegister : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format( self.comport, self.baudrate, self.bytesize, self.parity, self.stopbits, self.timeout, slave, register, code, res) self.addserial(ser_str) except Exception: # as ex: # self.disconnect() # import traceback # traceback.print_exc(file=sys.stdout) # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) self.commError = True finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) ########## # function 15 (Write Multiple Coils) def writeCoils(self, slave, register, values): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_coils(int(register), list(values), unit=int(slave)) time.sleep(.3) # avoid possible hickups on startup except Exception as ex: # self.disconnect() # import traceback # traceback.print_exc(file=sys.stdout) _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeCoils() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 5 (Write Single Coil) def writeCoil(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_coil(int(register), value, unit=int(slave)) time.sleep(.3) # avoid possible hickups on startup except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeCoil() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # write value to register on slave (function 6 for int or function 16 for float) # value can be one of string (containing an int or float), an int or a float def writeRegister(self, slave, register, value): if stringp(value): if "." in value: self.writeWord(slave, register, value) else: self.writeSingleRegister(slave, register, value) elif isinstance(value, int): self.writeSingleRegister(slave, register, value) elif isinstance(value, float): self.writeWord(slave, register, value) # function 6 (Write Single Holding Register) def writeSingleRegister(self, slave, register, value): # _logger.debug("writeSingleRegister(%d,%d,%d)" % (slave,register,value)) try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_register(int(register), int(value), unit=int(slave)) time.sleep(.03) # avoid possible hickups on startup except Exception as ex: # _logger.debug("writeSingleRegister exception: %s" % str(ex)) # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeSingleRegister() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 22 (Mask Write Register) # bits to be modified are "masked" with a 0 in the and_mask (not and_mask) # new bit values to be written are taken from the or_mask def maskWriteRegister(self, slave, register, and_mask, or_mask): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.mask_write_register(int(register), int(and_mask), int(or_mask), unit=int(slave)) time.sleep(.03) except Exception as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeMask() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # a local variant of function 22 (Mask Write Register) # the masks are evaluated locally on the given integer value and the result is send via # using function 6 def localMaskWriteRegister(self, slave, register, and_mask, or_mask, value): new_val = (int(round(value)) & and_mask) | (or_mask & (and_mask ^ 0xFFFF)) self.writeSingleRegister(slave, register, int(new_val)) # function 16 (Write Multiple Holding Registers) # values is a list of integers or one integer def writeRegisters(self, slave, register, values): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() self.master.write_registers(int(register), values, unit=int(slave)) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeRegisters() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 16 (Write Multiple Holding Registers) # value=int or float # writes a single precision 32bit float (2-registers) def writeWord(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() builder = getBinaryPayloadBuilder(self.byteorderLittle, self.wordorderLittle) builder.add_32bit_float(float(value)) payload = builder.build() # .tolist() self.master.write_registers(int(register), payload, unit=int(slave), skip_encode=True) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # translates given int value int a 16bit BCD and writes it into one register def writeBCD(self, slave, register, value): try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() builder = getBinaryPayloadBuilder(self.byteorderLittle, self.wordorderLittle) r = convert_to_bcd(int(value)) builder.add_16bit_uint(r) payload = builder.build() # .tolist() self.master.write_registers(int(register), payload, unit=int(slave), skip_encode=True) time.sleep(.03) except Exception as ex: # self.disconnect() _, _, exc_tb = sys.exc_info() self.adderror( (QApplication.translate("Error Message", "Modbus Error:", None) + " writeWord() {0}").format(str(ex)), exc_tb.tb_lineno) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers) def readFloat(self, slave, register, code=3): r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() if code in self.readingsCache and slave in self.readingsCache[code] and register in self.readingsCache[code][slave] \ and register+1 in self.readingsCache[code][slave]: # cache hit res = [ self.readingsCache[code][slave][register], self.readingsCache[code][slave][register + 1] ] decoder = getBinaryPayloadDecoderFromRegisters( res, self.byteorderLittle, self.wordorderLittle) return decoder.decode_32bit_float() else: retry = self.readRetries while True: if code == 3: res = self.master.read_holding_registers( int(register), 2, unit=int(slave)) else: res = self.master.read_input_registers(int(register), 2, unit=int(slave)) if res is None or res.isError( ): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 #time.sleep(0.020) else: raise Exception("Exception response") else: break decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_32bit_float() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate("Error Message", "Modbus Communication Resumed", None)) return r except Exception: # as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readFloat() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary if self.aw.seriallogflag: ser_str = "MODBUS readFloat :{},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format( self.comport, self.baudrate, self.bytesize, self.parity, self.stopbits, self.timeout, slave, register, code, r) self.addserial(ser_str) # function 3 (Read Multiple Holding Registers) and 4 (Read Input Registers) def readBCD(self, slave, register, code=3): r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() if code in self.readingsCache and slave in self.readingsCache[code] and register in self.readingsCache[code][slave] \ and register+1 in self.readingsCache[code][slave]: # cache hit res = [ self.readingsCache[code][slave][register], self.readingsCache[code][slave][register + 1] ] decoder = getBinaryPayloadDecoderFromRegisters( [res], self.byteorderLittle, self.wordorderLittle) r = decoder.decode_32bit_uint() return convert_from_bcd(r) else: retry = self.readRetries while True: if code == 3: res = self.master.read_holding_registers( int(register), 2, unit=int(slave)) else: res = self.master.read_input_registers(int(register), 2, unit=int(slave)) if res is None or res.isError( ): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 #time.sleep(0.020) else: raise Exception("Exception response") else: break decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_32bit_uint() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate("Error Message", "Modbus Communication Resumed", None)) time.sleep( 0.020 ) # we add a small sleep between requests to help out the slow Loring electronic return convert_from_bcd(r) except Exception: # as ex: # import traceback # traceback.print_exc(file=sys.stdout) # self.disconnect() # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readBCD() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary if self.aw.seriallogflag: ser_str = "MODBUS readBCD : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format( self.comport, self.baudrate, self.bytesize, self.parity, self.stopbits, self.timeout, slave, register, code, r) self.addserial(ser_str) # as readSingleRegister, but does not retry nor raise and error and returns a None instead # also does not reserve the port via a semaphore! def peekSingleRegister(self, slave, register, code=3): try: if code == 1: res = self.master.read_coils(int(register), 1, unit=int(slave)) elif code == 2: res = self.master.read_discrete_inputs(int(register), 1, unit=int(slave)) elif code == 4: res = self.master.read_input_registers(int(register), 1, unit=int(slave)) else: # code==3 res = self.master.read_holding_registers(int(register), 1, unit=int(slave)) except Exception: res = None if res is not None and not res.isError(): # requires pymodbus v1.5.1 if code in [1, 2]: if res is not None and res.bits[0]: return 1 else: return 0 else: decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_16bit_uint() return r else: return None # function 1 (Read Coil) # function 2 (Read Discrete Input) # function 3 (Read Multiple Holding Registers) and # function 4 (Read Input Registers) def readSingleRegister(self, slave, register, code=3): # import logging # logging.basicConfig() # log = logging.getLogger() # log.setLevel(logging.DEBUG) r = None try: #### lock shared resources ##### self.COMsemaphore.acquire(1) self.connect() if code in self.readingsCache and slave in self.readingsCache[ code] and register in self.readingsCache[code][slave]: # cache hit res = self.readingsCache[code][slave][register] decoder = getBinaryPayloadDecoderFromRegisters( [res], self.byteorderLittle, self.wordorderLittle) return decoder.decode_16bit_uint() else: retry = self.readRetries while True: try: if code == 1: res = self.master.read_coils(int(register), 1, unit=int(slave)) elif code == 2: res = self.master.read_discrete_inputs( int(register), 1, unit=int(slave)) elif code == 4: res = self.master.read_input_registers( int(register), 1, unit=int(slave)) else: # code==3 res = self.master.read_holding_registers( int(register), 1, unit=int(slave)) except Exception: res = None if res is None or res.isError( ): # requires pymodbus v1.5.1 if retry > 0: retry = retry - 1 time.sleep(0.020) else: raise Exception("Exception response") else: break if code in [1, 2]: if res is not None and res.bits[0]: r = 1 else: r = 0 if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate( "Error Message", "Modbus Communication Resumed", None)) return r else: decoder = getBinaryPayloadDecoderFromRegisters( res.registers, self.byteorderLittle, self.wordorderLittle) r = decoder.decode_16bit_uint() if self.commError: # we clear the previous error and send a message self.commError = False self.adderror( QApplication.translate( "Error Message", "Modbus Communication Resumed", None)) return r except Exception: # as ex: # self.disconnect() # import traceback # traceback.print_exc(file=sys.stdout) # _, _, exc_tb = sys.exc_info() # self.adderror((QApplication.translate("Error Message","Modbus Error:",None) + " readSingleRegister() {0}").format(str(ex)),exc_tb.tb_lineno) self.adderror( QApplication.translate("Error Message", "Modbus Communication Error", None)) self.commError = True finally: if self.COMsemaphore.available() < 1: self.COMsemaphore.release(1) #note: logged chars should be unicode not binary if self.aw.seriallogflag: ser_str = "MODBUS readSingleRegister : {},{},{},{},{},{} || Slave = {} || Register = {} || Code = {} || Rx = {}".format( self.comport, self.baudrate, self.bytesize, self.parity, self.stopbits, self.timeout, slave, register, code, r) self.addserial(ser_str) def setTarget(self, sv): if self.PID_slave_ID: multiplier = 1. if self.SVmultiplier == 1: multiplier = 10. elif self.SVmultiplier == 2: multiplier = 100. self.writeSingleRegister(self.PID_slave_ID, self.PID_SV_register, int(round(sv * multiplier))) def setPID(self, p, i, d): if self.PID_slave_ID and not (self.PID_p_register == self.PID_i_register == self.PID_d_register == 0): multiplier = 1. if self.PIDmultiplier == 1: multiplier = 10. elif self.PIDmultiplier == 2: multiplier = 100. self.writeSingleRegister(self.PID_slave_ID, self.PID_p_register, p * multiplier) self.writeSingleRegister(self.PID_slave_ID, self.PID_i_register, i * multiplier) self.writeSingleRegister(self.PID_slave_ID, self.PID_d_register, d * multiplier)
class DemeterClient(object): def __init__(self, unit, modbus_config): self.client = ModbusSerialClient(**modbus_config) self.unit = unit self.valid_commands = { 'read_holding_registers': self.read_holding_registers, 'write_registers': self.write_holding_registers, 'read_input_registers': self.read_input_registers, 'read_coils': self.read_coils, 'write_coils': self.write_coils, 'get_datetime': self.get_datetime, 'set_datetime': self.set_datetime, 'get_loginterval': self.get_loginterval, 'set_loginterval': self.set_loginterval, 'read_event': self.read_event, 'write_event': self.write_event, 'enable_event': self.enable_event, 'disable_event': self.disable_event, 'read_events': self.read_events, 'disable_event': self.disable_event, 'enable_event': self.enable_event, 'disable_relay': self.disable_relay, 'enable_relay': self.enable_relay, 'temperatura': self.get_temperature, 'humedad': self.get_humidity, 'luz': self.get_light, } def is_valid(self, command): return command in self.valid_commands def read_holding_registers(self, arguments): if len(arguments) != 2: return "Modo de uso: read_holding_registers <start-address> <registers>" for i in range(0, len(arguments)): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.read_holding_registers(address=values[0], count=values[1], unit=self.unit) def write_holding_registers(self, arguments): if len(arguments) < 2: return "Modo de uso: write_holding_registers <start-address> <register-1> <register-2> ... <register-n>" for i in range(0, len(arguments)): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.write_registers(values[0], values[1:], unit=self.unit) def get_temperature(self, arguments): value = self.client.read_input_registers(address=0, count=1, unit=self.unit) if isinstance(value, ReadRegistersResponseBase): value = "%d,%d °C" % (value.registers[0]/10, value.registers[0]%10) return value def get_humidity(self, arguments): value = self.client.read_input_registers(address=1, count=1, unit=self.unit) if isinstance(value, ReadRegistersResponseBase): value = "%d,%d %%" % (value.registers[0]/10, value.registers[0]%10) return value def get_light(self, arguments): value = self.client.read_input_registers(address=2, count=1, unit=self.unit) if isinstance(value, ReadRegistersResponseBase): value = value.registers[0] return value def read_input_registers(self, arguments): if len(arguments) != 2: return "Modo de uso: read_input_registers <start-address> <registers>" for i in range(0, len(arguments)): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.read_input_registers(address=values[0], count=values[1], unit=self.unit) def read_coils(self, arguments): if len(arguments) != 2: return "Modo de uso: read_coils <start-address> <coils>" for i in range(0, len(arguments)): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.read_coils(address=values[0], count=values[1], unit=self.unit) def write_coils(self, arguments): if len(arguments) < 2: return "Modo de uso: write_coils <start-address> <bit-1> <bit-2> ... <bit-n>" for i in range(0, len(arguments)): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.write_coils(address=values[0], values=values[1:], unit=self.unit) def get_datetime(self, arguments): response = self.client.read_holding_registers(address=1, count=6, unit=self.unit) if isinstance(response, ReadRegistersResponseBase): r = response.registers return "%s/%02d/%02d %02d:%02d:%02d" % (r[0], r[1], r[2], r[3], r[4], r[5]) return response def set_datetime(self, arguments): # yyy:mm:dd hh:mm:ss if len(arguments) != 6: return "Modo de uso: set_datetime <yyyy> <mm> <dd> <hh> <mm> <ss>" for i in range(0, 6): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] values = [int(x) for x in arguments] return self.client.write_registers(address=1, values=values, unit=self.unit) def get_loginterval(self, arguments): response = self.client.read_holding_registers(address=0, count=1, unit=self.unit) if isinstance(response, ReadRegistersResponseBase): return response.registers[0] return response def set_loginterval(self, arguments): if len(arguments) != 1: return "Modo de uso: set_loginterval <segunos>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] return self.client.write_registers(address=0, values=[int(arguments[0])], unit=self.unit) def read_event(self, arguments): if len(arguments) != 1: return "Modo de uso: read_event <numero de evento>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] number = int(arguments[0]) response = self.client.read_holding_registers(address=number * 6 + 7, count=6, unit=self.unit) if isinstance(response, ReadRegistersResponseBase): return self.__print_event(number, response.registers) return response def write_event(self, arguments): # nro de evento + 6 campos if len(arguments) != (1 + 6): return "Modo de uso: write_event <numero de evento> <hh>:<mm>:<ss> <duracion> <relay> <1|0>" for i in range(0, 7): if not self.__is_number(arguments[i]): return "%s: se esperaba un entero" % arguments[i] number = int(arguments[0]) values = [int(x) for x in arguments[1:]] if values[5] not in [0,1]: return "%s: el valor debe ser cero o uno" % values[5] return self.client.write_registers(address=number * 6 + 7, values=values, unit=self.unit) def disable_event(self, arguments): # nro de evento if len(arguments) != 1: return "Modo de uso: disable_event <numero de evento>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[i] number = int(arguments[0]) return self.client.write_registers(address=number * 6 + 5, values=[0], unit=self.unit) def enable_event(self, arguments): # nro de evento if len(arguments) != 1: return "Modo de uso: disable_event <numero de evento>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[i] number = int(arguments[0]) return self.client.write_registers(address=number * 6 + 5, values=[0], unit=self.unit) def read_events(self, arguments): response = self.client.read_holding_registers(address=7, count=6 * 10, unit=self.unit) if isinstance(response, ReadRegistersResponseBase): str = "" for i in range(0, 10): str += self.__print_event(i, response.registers[i * 6:(i + 1) * 6]) str += "\n" return str return response def disable_event(self, arguments): if len(arguments) != 1: return "Modo de uso: disable_event <numero de evento>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] number = int(arguments[0]) return self.client.write_registers(address=number * 6 + 7 + 5, values=[0], unit=self.unit) def enable_event(self, arguments): if len(arguments) != 1: return "Modo de uso: enable_event <numero de evento>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] number = int(arguments[0]) return self.client.write_registers(address=number * 6 + 7 + 5, values=[1], unit=self.unit) def disable_relay(self, arguments): if len(arguments) != 1: return "Modo de uso: disable_relay <numero de relay>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] number = int(arguments[0]) if number not in [0, 1]: return "%d: solo hay dos relays" % number return self.client.write_coils(address=number, values=[0], unit=self.unit) def enable_relay(self, arguments): if len(arguments) != 1: return "Modo de uso: disable_relay <numero de relay>" if not self.__is_number(arguments[0]): return "%s: se esperaba un entero" % arguments[0] number = int(arguments[0]) if number not in [0, 1]: return "%d: solo hay dos relays" % number return self.client.write_coils(address=number, values=[1], unit=self.unit) def dummy(self, arguments): return None def execute(self, command, arguments): return self.valid_commands.get(command, self.dummy)(arguments) def __print_event(self, number, data): return "Evento #%d (%s) => Inicia a las %02d:%02d:%02d hs., dura: %d segs. y ejecuta en relay #%d" % ( number, ("habilitado" if data[5] != 0 else "deshabilitado"), data[0], data[1], data[2], data[3], data[4]) def __is_number(self, str): try: int(str) except ValueError: return False return True