def display_remote_io(ip_address): ''' display remote I/O information at the given IP address ''' try: client = ModbusTcpClient(ip_address) client.write_coil(HW_CY_006, False) ip_holding_regs = client.read_holding_registers(HR_CI_006_CV, 6) client.write_registers(HW_CI_006_PV, ip_holding_regs.registers) cur_ip = format_modbus_ip_address(ip_holding_regs.registers[0], ip_holding_regs.registers[1]) cur_gateway = format_modbus_ip_address(ip_holding_regs.registers[2], ip_holding_regs.registers[3]) cur_subnet = format_modbus_ip_address(ip_holding_regs.registers[4], ip_holding_regs.registers[5]) ip_holding_regs = client.read_holding_registers(HR_CI_009_CV, 4) cur_mac = format_mac(ip_holding_regs.registers) ip_holding_regs = client.read_holding_registers(HR_KI_003, 2) cur_version = format_version(ip_holding_regs.registers[0]) print("{0} - {1}, version:{2}.{3}.{4} ".format( ip_address, device_type_name(ip_holding_regs.registers[1]), cur_version[1], cur_version[2], cur_version[3]), end='') print("gateway:{0}, subnet:{1} mac:{2}".format(cur_gateway, cur_subnet, cur_mac)) client.close() except ConnectionException: print("{0} - unavailable".format(ip_address))
def runModBus(IOVariables): client = ModbusTcpClient('192.168.1.9') Digital_Out_1 = ModbusDigitalOutputIOCard(2048, client, IOVariables) try: Digital_Out_1.WriteStatus() except ConnectionException: print('A connection error to the modbus occured at {}'.format( datetime.datetime.now() ) ) pass client.close()
def read_sensors(request): sensors = Sensor.objects.all() sensor_data = [] dictionary = {} for sensor in sensors: client = ModbusTcpClient(sensor.address.server, port=502, timeout=1) try: client.connect() if sensor.sensor_type == Sensor.SensorType.DIGITAL: reply = client.read_discrete_inputs(sensor.channel, 1, unit=1) sensor_reading = reply.bits[0] sensor_response = { 'sensor_name': sensor.name, 'sensor_reading': sensor_reading } if sensor.digital_sensor_alert and sensor.email and str( sensor_reading) != sensor.last_value: if sensor.digital_alert_value and sensor_reading: send_sensor_alert_email(sensor, str(sensor_reading)) #send_sensor_alert_email() elif not sensor.digital_alert_value and not sensor_reading: send_sensor_alert_email(sensor, str(sensor_reading)) #send_sensor_alert_email() else: reply = client.read_input_registers(sensor.channel, 1, unit=1) sensor_reading = round( reply.registers[0] * sensor.conversion_factor, 2) if sensor.high_alert_value and sensor.email: if sensor_reading > sensor.high_alert_value and float( sensor.last_value) <= sensor.high_alert_value: send_sensor_alert_email(sensor, str(sensor_reading)) #send_sensor_alert_email() if sensor.low_alert_value and sensor.email: if sensor_reading < sensor.low_alert_value and float( sensor.last_value) >= sensor.low_alert_value: send_sensor_alert_email(sensor, str(sensor_reading)) #send_sensor_alert_email() sensor.last_value = str(sensor_reading) sensor.save() client.close() except: sensor_reading = "Could Not Connect" sensor_response = { 'sensor_name': sensor.name, 'sensor_reading': sensor_reading } sensor_data.append(sensor_response) client.close() dictionary['sensor_data'] = sensor_data return render(request, 'sensors/sensor_data.html', dictionary)
def setRelayState(interlock: Interlock_model, state: {0, 1}) -> Interlock_model.State: client = ModbusTcpClient(interlock.card.server) client.connect() client.write_coil(interlock.channel, state, unit=1) sleep(0.3) reply = client.read_coils(interlock.channel, 1, unit=1) state = reply.bits[0] client.close() if state == ModbusInterlock.MODBUS_OFF: return Interlock_model.State.LOCKED elif state == ModbusInterlock.MODBUS_ON: return Interlock_model.State.UNLOCKED
def get_1wire_config(ip_address): try: client = ModbusTcpClient(ip_address) wire_config = [] for i in range(MAX_1WIRE): holding_regs = client.read_holding_registers( HR_TI_001_ID_H + i * 4, 4) cur_uuid = format_uuid(holding_regs.registers) holding_regs = client.read_holding_registers(HR_TI_001 + i, 1) wire_config.append( [i, cur_uuid, i, to_signed(holding_regs.registers[0]) / 10.0]) client.close() return wire_config except ConnectionException: print("{0} - unavailable".format(cuip))
def test_basic_sync_tcp_client(self): """ Test the basic methods for the tcp sync client""" # receive/send client = ModbusTcpClient() client.socket = MockSocket() self.assertEqual(0, client.send(None)) self.assertEqual(1, client.send('\x00')) self.assertEqual('\x00', client.receive(1)) # connect/disconnect self.assertTrue(client.connect()) client.close() # already closed socket client.socket = False client.close() self.assertEqual("127.0.0.1:502", str(client))
def write_network_config(cuip, aip, agw, asn, amac): try: client = ModbusTcpClient(cuip) client.write_register(HW_CI_006_PV, aip >> 16) client.write_register(HW_CI_006_PV + 1, aip & 0xffff) client.write_register(HW_CI_007_PV, agw >> 16) client.write_register(HW_CI_007_PV + 1, agw & 0xffff) client.write_register(HW_CI_008_PV, asn >> 16) client.write_register(HW_CI_008_PV + 1, asn & 0xffff) client.write_register(HW_CI_009_PV, 0) client.write_register(HW_CI_009_PV + 1, amac >> 32) client.write_register(HW_CI_009_PV + 2, (amac & 0xFFFFFFFF) >> 16) client.write_register(HW_CI_009_PV + 3, (amac & 0xFFFFFFFF) & 0xffff) client.write_coil(HW_CY_006, True) client.close() except ConnectionException: print("{0} - unavailable".format(cuip))
def runModBus(IOVariables): #---------------------------------------------------------------------------# # 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. #---------------------------------------------------------------------------# client = ModbusTcpClient('192.168.1.9') #rq = client.write_registers(2048, [0]) #rr = client.read_input_registers(000, 1) #print (rr.registers) #---------------------------------------------------------------------------# # configure io card #---------------------------------------------------------------------------# #Digital_In_1 = ModbusDigitalInputIOCard(0, client) #print('IOVariables in modbus.py: {IOVariables} '.format(IOVariables=IOVariables)) Digital_Out_1 = ModbusDigitalOutputIOCard(2048, client, IOVariables) #---------------------------------------------------------------------------# # Run io card #---------------------------------------------------------------------------# #Digital_In_1.ReadStatus() #---------------------------------------------------------------------------# # Run io card #---------------------------------------------------------------------------# try: Digital_Out_1.WriteStatus() except ConnectionException: print('A connection error to the modbus occured at {}'.format( datetime.datetime.now() ) ) pass #---------------------------------------------------------------------------# # close the client #---------------------------------------------------------------------------# client.close()
class HeliosTCP(SmartPlugin): PLUGIN_VERSION = "1.0.2" MODBUS_SLAVE = 180 PORT = 502 START_REGISTER = 1 _items = {} def __init__(self, sh): from bin.smarthome import VERSION if '.'.join(VERSION.split('.', 2)[:2]) <= '1.5': self.logger = logging.getLogger(__name__) self._helios_ip = self.get_parameter_value('helios_ip') self._client = ModbusTcpClient(self._helios_ip) self.alive = False self._is_connected = False self._update_cycle = self.get_parameter_value('update_cycle') def run(self): """ Run method for the plugin """ self.logger.debug("Run method called") self._is_connected = self._client.connect() if not self._is_connected: self.logger.error( "Helios TCP: Failed to connect to Modbus Server at {0}".format( self._helios_ip)) self.scheduler_add('Helios TCP', self._update_values, cycle=self._update_cycle) self.alive = True def stop(self): """ Stop method for the plugin """ self.logger.debug("Stop method called") self.scheduler_remove('Helios TCP') self._client.close() self.alive = False def parse_item(self, item): if 'helios_tcp' in item.conf: varname = item.conf['helios_tcp'] if varname in VARLIST.keys(): self._items[varname] = item return self.update_item else: self.logger.warning( "Helios TCP: Ignoring unknown variable '{0}'".format( varname)) def _update_values(self): for item in self._items: self._read_value(self._items[item]) @staticmethod def _string_to_registers(instr: str): l = bytearray(instr, 'ascii') return [k[0] * 256 + k[1] for k in zip(l[::2], l[1::2])] + [0] def _read_value(self, item): try: var = item.conf['helios_tcp'] except ValueError: return try: varprop = VARLIST[var] except KeyError: self.logger.error( "Helios TCP: Failed to find variable '{0}'".format(var)) return # At first we write the variable name to read into the input registers: payload = self._string_to_registers(varprop['var']) request = self._client.write_registers(self.START_REGISTER, payload, unit=self.MODBUS_SLAVE) if request is None: self.logger.warning( "Helios TCP: Failed to send read request for variable '{0}'". format(var)) return # Now we may read the holding registers: response = self._client.read_holding_registers(self.START_REGISTER, varprop['length'], unit=self.MODBUS_SLAVE) if response is None: self.logger.warning( "Helios TCP: Failed to send read response for variable '{0}'". format(var)) return # Now we may dedocde the result # Note that we immediatly strip the varname from the result. result = response.encode().decode('ascii')[8:] result = list(result) # Remove trailing zeros: while result[-1] == '\x00': result.pop() result = ''.join(result) # Finally we may cast the result and return the obtained value: try: item(varprop["type"](result), self.get_shortname()) except ValueError: self.logger.warning( "Helios TCP: Could not assign {0} to item {1}".format( varprop["type"](result), item.id())) return def update_item(self, item, caller=None, source=None, dest=None): """ Item has been updated This method is called, if the value of an item has been updated by SmartHomeNG. It should write the changed value out to the device (hardware/interface) that is managed by this plugin. :param item: item to be updated towards the plugin :param caller: if given it represents the callers name :param source: if given it represents the source :param dest: if given it represents the dest """ if self.alive and caller != self.get_shortname(): try: var = item.conf['helios_tcp'] except ValueError: return newval = item() try: varprop = VARLIST[var] except KeyError: self.logger.error( "Helios TCP: Failed to find variable '{0}'".format(var)) return if not varprop["write"]: return if type(newval) != varprop["type"]: self.logger.error( "Helios TCP: Type mismatch for variable '{0}'".format(var)) return if newval < varprop["min"] or newval > varprop["max"]: self.logger.error( "Helios TCP: Variable '{0}' out of bounds. The allowed range is [{1}, {2}]" .format(var, varprop["min"], varprop["max"])) return if varprop["type"] == bool: payload_string = "{0}={1}".format(varprop["var"], int(newval)) elif varprop["type"] == int: payload_string = "{0}={1}".format(varprop["var"], int(newval)) elif varprop["type"] == float: payload_string = "{0}={1:.1f}".format(varprop["var"], newval) else: self.logger.error( "Helios TCP: Type {0} of varible '{1}' not known".format( varprop["type"], var)) return payload = self._string_to_registers(payload_string) request = self._client.write_registers(self.START_REGISTER, payload, unit=self.MODBUS_SLAVE) if request is None: self.logger.warning( "Helios TCP: Failed to send write request for variable '{0}'" .format(var)) return
class MBPLC(PLC): """PLC con comunicación Modbus. Args: address (str): Dirección IP (o nombre) del controlador. port (int): Puerto de conexión. Típicamente el 502. unit (int): Número de esclavo. method (str): Método de conexión (RTU/ASCII) retries (int): Reintentos de lectura/escritura. client (ModbusClient): Cliente modbus. pollingtime (float): Segundos entre escaneos. Attributes: coil (Memory): Memoria de bobinas. input (Memory): Memoria de entradas digitales. holding (Memory): Memoria de registros de retención. register (Memory): Memoria de registros de entrada. client (ModbusClient): Cliente Modbus. thread (Thread): Hebra de escaneo. """ class COIL: """Tipo de memoria COIL.""" pass class INPUT: """Tipo de memoria INPUT.""" pass class HOLDING: """Tipo de memoria HOLDING.""" pass class REGISTER: """Tipo de memoria REGISTER.""" pass class Memory(PLC.Memory): ''' Representacióne un área de memoria. Args: plc (PLC): Controlador al que pertenece. memorytype (type): Tipo de memoria (COIL, INPUT, HOLDING, REGISTER). Attributes: minindex (int): Dirección más baja de la memoria leída. maxindex (int): Dirección más alta de la memoria leída. ''' class Tag(PLC.Memory.Tag): ''' Variable de controlador Modbus. Args: memory (Memory): Memoria a la que pertenece. key (str): Nombre. description (str): Descripción. address: Dirección (None si no esta asociada). Attributes: value: Valor. subscriptor (Subcriptor[]): Objetos suscritos a los cambios. ''' def __init__(self, memory:PLC.Memory, key:str, description:str="", address=None): if type(address)==str: address=int(address) super().__init__(memory,key,description,address) if memory.minindex is None or memory.minindex>address: memory.minindex=address if memory.maxindex is None or memory.maxindex<address: memory.maxindex=address def set(self, value): ''' Modifica el valor de una variable. Args: value: Nuevo valor de la variable. ''' plc=self.memory.plc if plc.connected: try: if self.memory.memorytype==MBPLC.COIL: if isinstance(value,str): if value.upper()=="TRUE" or value=="1": value=True elif value.upper()=="FALSE" or value=="0": value=False rw = plc.client.write_coil(self.address,value,unit=plc.unit) if self.memory.memorytype==MBPLC.HOLDING: if isinstance(value,str): value=int(value) rw = plc.client.write_register(self.address,value,unit=plc.unit) self.update(value) except Exception as e: self.memory.plc.connected=False printexception(e,"Error writing to PLC") def __init__(self, plc:PLC, memorytype:type): self.memorytype=memorytype self.minindex=None self.maxindex=None super().__init__(plc) def __init__(self, address:str, port:int=502, unit:int=1, method:str="rtu", retries:int=3, pollingtime:float=1.0): super().__init__() self.coil=self.create("coil",MBPLC.COIL) self.input=self.create("input",MBPLC.INPUT) self.holding=self.create("holding",MBPLC.HOLDING) self.register=self.create("register",MBPLC.REGISTER) self.address=address self.unit=unit self.port=port self.method=method self.retries=retries self.pollingtime=pollingtime self.client = ModbusClient(address, port, method=method, retries=retries) self.thread=Thread(target=self.__Polling, args=()) def create(self,memory_key, memorytype:type): ''' Crea una memoria en el controlador. Args: memory_key (str): Nombre o identificador de la memoria. memorytype (type): Tipo de memoria (COIL, INPUT, HOLDING, REGISTER). Returns (Memory): Memoria que se ha creado. ''' return self.set(memory_key, self.Memory(self, memorytype)) def connect(self): ''' Conexión con el controlador ''' self.thread.start() def disconnect(self): ''' Termina la conexión con el controlador. ''' self.connected=false self.client.close() def read(self): ''' Lectura de un área de memoria del controlador real. Si falla la lectura (tras el número de reintentos) se actualiza el estado de conexión a desconectado. ''' try: if not self.coil.minindex is None: rr = self.client.read_coils(self.coil.minindex, self.coil.maxindex-self.coil.minindex+1,unit=self.unit) for i in range(self.coil.minindex,self.coil.maxindex+1): if i in self.coil.tagbyaddress: self.coil.tagbyaddress[i].update(rr.bits[i-self.coil.minindex]) if not self.input.minindex is None: rr = self.client.read_discrete_inputs(self.input.minindex, self.input.maxindex-self.input.minindex+1,unit=self.unit) for i in range(self.input.minindex,self.input.maxindex+1): if i in self.input.tagbyaddress: self.input.tagbyaddress[i].update(rr.bits[i-self.input.minindex]) if not self.holding.minindex is None: rr = self.client.read_holding_registers(self.holding.minindex, self.holding.maxindex-self.holding.minindex+1,unit=self.unit) for i in range(self.holding.minindex,self.holding.maxindex+1): if i in self.holding.tagbyaddress: self.holding.tagbyaddress[i].update(rr.registers[i-self.holding.minindex]) if not self.register.minindex is None: rr = self.client.read_input_registers(self.register.minindex, self.register.maxindex-self.register.minindex+1,unit=self.unit) for i in range(self.register.minindex,self.register.maxindex+1): if i in self.register.tagbyaddress: self.register.tagbyaddress[i].update(rr.registers[i-self.register.minindex]) except Exception as e: self.coil.plc.disconnect() printexception(e,"Error reading from PLC") def __Polling(plc): ''' Lectura de todas las áreas de escaneo. Establece la conexión con los controladores, si no se ha hecho antes, o se ha perdido. ''' while True: if plc.connected: plc.read() time.sleep(plc.pollingtime) else: print("Connecting to MBPLC "+plc.address+":"+str(plc.port)+"("+str(plc.unit)+")") plc.connected=plc.client.connect()
from pymodbus3.client.sync import ModbusTcpClient inp = input( u"Press any key and enter to send a packet... (Just enter to quit)") client = ModbusTcpClient('100.100.100.2') while (inp): client.write_coil(1, True) result = client.read_coils(1, 1) print(result.bits[0]) client.close() inp = input( u"Press any key and enter to send a packet... (Just enter to quit)")
from pymodbus3.client.sync import ModbusTcpClient # instead of this # 192.168.1.22:502 #PLC Mod Bus Regs 40001-40100 #40096-40100 monnth day hour ..... #Reg 2 Bytes --> not 4 bytes plc_client = ModbusTcpClient('192.168.1.22') # result =client.read_holding_registers(0,5,unit=0X01) # print (str(result)) try: for x in range(5,6): result = plc_client.write_registers( 9,[int(x) ],unit=0X01) result = plc_client.write_registers(40007,[int(x) ],unit=0X01) result = plc_client.write_registers(40010,[int(1)],unit=0X01) print (str(result)) except Exception as e: print("error sending data to plc " + str(e)) plc_client.close()
class COMx(MAQ20Module): """ COM Module: Takes care of the communication backend and provides functions that read the COMx register map. """ def __init__(self, ip_address, port): if ip_address is not None or port is not None: if MODBUS_BACKEND == PYMODBUS3: self._client = ModbusTcpClient(ip_address, port=port) if MODBUS_BACKEND == UMODBUS: self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.settimeout(2) # seconds self._sock.connect((ip_address, port)) super(COMx, self).__init__(com=self, registration_number=0) def read_register(self, address): """ Low level register access. Performs a modbus read register request to the MAQ20 :param address: requested address :return: int - [-32767, 32767] """ return self.read_registers(address, 1)[0] def read_registers(self, address, number_of_registers): """ Low level register access. Performs a modbus read registers request to the MAQ20 :param address: starting address. :param number_of_registers: number of registers to be read in sequence. :return: list(int) [-32767, 32767] """ if MODBUS_BACKEND == PYMODBUS3: result = self._client.read_holding_registers( address=address, count=number_of_registers) # converts the returned values to signed. try: return [ unsigned16_to_signed16(value) for value in result.registers ] except AttributeError: return None if MODBUS_BACKEND == UMODBUS: message = tcp.read_holding_registers(0, address, number_of_registers) result = tcp.send_message(message, self._sock) return [unsigned16_to_signed16(value) for value in result] def write_register(self, address, value): """ Low level register access. Performs a modbus write register request to the MAQ20 :param address: starting address. :param value: int [-32767, 32767] or a str of size 1 :return: modbus response. """ if type(value) is str: value = ord(value[0]) value = signed16_to_unsigned16(value) if MODBUS_BACKEND == PYMODBUS3: return self._client.write_register(address, value) if MODBUS_BACKEND == UMODBUS: message = tcp.write_single_register(slave_id=0, address=address, value=value) return tcp.send_message(message, self._sock) def write_registers(self, address, values=None): """ Low level register access. Performs a modbus write registers request to the MAQ20 :param address: starting address. :param values: list(int) [-32767, 32767] or a str :return: modbus response. """ ints = [] if type(values) is str: for c in values: ints.append(ord(c)) else: ints = [signed16_to_unsigned16(x) for x in values] if MODBUS_BACKEND == PYMODBUS3: return self._client.write_registers(address, ints) if MODBUS_BACKEND == UMODBUS: message = tcp.write_multiple_registers(slave_id=0, starting_address=address, values=ints) return tcp.send_message(message, self._sock) def read_ip_address(self): return self._com.read_registers(50, 4) def write_ip_address(self, ip_address): if type(ip_address) is str: first = ip_address.partition('.') second = first[2].partition('.') third = second[2].partition('.') numbers = [first[0], second[0], third[0], third[2]] numbers = [int(number) for number in numbers] return self.write_registers(50, numbers) elif type(ip_address) is list: return self.write_registers(50, ip_address) return None def read_ethernet_subnet_mask(self): return self._com.read_registers(55, 4) def write_ethernet_subnet_mask(self, mask): if type(mask) is str: first = mask.partition('.') second = first[2].partition('.') third = second[2].partition('.') numbers = [first[0], second[0], third[0], third[2]] numbers = [int(number) for number in numbers] return self.write_registers(55, numbers) elif type(mask) is list: return self.write_registers(55, mask) return None def read_serial_port_baud(self) -> int: return { 0: 1200, 1: 2400, 2: 4800, 3: 9600, 4: 19200, 5: 38400, 6: 57600, 7: 115200, 8: 230400, 9: 460800, 10: 921600 }[self._com.read_register(60)] def write_serial_port_baud(self, baud): if baud > 10: baud = { 1200: 0, 2400: 1, 4800: 2, 9600: 3, 19200: 4, 38400: 5, 57600: 6, 115200: 7, 230400: 8, 460800: 9, 921600: 10 }[baud] return self.write_register(60, baud) def read_serial_port_parity(self) -> str: return {0: 'None', 1: 'Odd', 2: 'Even'}[self._com.read_register(65)] def write_serial_port_parity(self, parity): if type(parity) is str: parity = parity.lower() parity = {'none': 0, 'odd': 1, 'even': 2}[parity] self.write_register(65, parity) def read_rs485_type(self): return {0: '4-wire', 1: '2-wire'}[self.read_register(66)] def write_rs485_type(self, rs485_type): if type(rs485_type) is str: if rs485_type[0] == '4': rs485_type = 0 elif rs485_type[1] == '2': rs485_type = 1 else: return None return self.write_register(66, rs485_type) def read_termination(self): return {0: False, 1: True}[self.read_register(67)] def write_termination(self, termination): if type(termination) is str: if termination[0] == 'D' or termination[0] == 'd': termination = 0 elif termination[0] == 'E' or termination[0] == 'e': termination = 1 else: return None if type(termination) is bool: termination = {False: 0, True: 1}[termination] return self.write_register(67, termination) def read_slave_id(self): return self.read_register(68) def write_slave_id(self, slave_id): return self.write_register(68, slave_id) def write_save_port_and_server_settings(self): """Saves the com port and file server information into the COM's EEPROM memory. Note: Changes apply after power cycle.""" return self.write_register(70, 1) def read_file_server_username(self) -> str: return response_to_string(self.read_registers(71, 10)) def write_file_server_username(self, username): if type(username) is not str: return False else: return self.write_registers(71, username) def read_file_server_password(self): return response_to_string(self.read_registers(81, 10)) def write_file_server_password(self, password): if type(password) is not str: return False else: return self.write_registers(81, password) def read_file_server_anonymous_login(self): return self.read_register(91) def write_file_server_anonymous_login(self, input_value): return self.write_register(91, input_value) ####################### # Module Configuration. ####################### def read_module_status(self): return self.read_registers(100, 24) def write_module_status(self, input_value): # TODO: Write this function. pass def read_ethernet_port_present(self): return self.read_register(130) def read_usb_port_present(self): return self.read_register(131) def read_rs485_port_present(self): return self.read_register(132) def read_rs232_port_present(self): return self.read_register(133) def read_can_port_present(self): return self.read_register(134) ############################### # Registration and Data Logger. ############################### # TODO: Decide how to handle manual registration. def auto_registration(self, enable): """ Enables or disables auto registration. :param enable: Boolean :return: response from modbus backend. """ result = None if enable is True: result = self.write_register(1020, 1) # enable auto registration elif enable is False: result = self.write_register(1020, 0) # disable auto registration # """This for loop """ # for i in range(24): # self.write_register(1022, i+1) return result def delete_registration_numbers(self, numbers): if numbers == "all": for i in range(24): self.write_register(1022, i + 1) try: for number in numbers: self.write_register(1022, number) except TypeError: self.write_register(1022, number) return True def register_module(self, serial_number, registration_number): pass # SD CARD def read_log_file_name(self): return utils.response_to_string(self.read_registers(1100, 11)) def write_log_file_name(self, name: str): return self.write_registers(1100, name) if len(name) < 12 else False def read_log_start_address_1(self): """ Default = 2000 (Start Address of I/O Module in Slot 1. Data for this module is at Start Address 3000) :return: int """ return self.read_register(1120) def write_log_start_address_1(self, value): """ Default = 2000 (Start Address of I/O Module in Slot 1. Data for this module is at Start Address 3000) :return: modbus response. """ return self.write_register(1120, value) def read_number_of_registers_to_log_1(self): """ Number of Registers to Log starting at Log Start Address 1. Maximum = 100, Default = 8 :return: int """ return self.read_register(1121) def write_number_of_registers_to_log_1(self, value): """ Number of Registers to Log starting at Log Start Address 1. Maximum = 100, Default = 8 :param value: number to write :return: modbus response """ return self.write_register(1121, value) def read_log_start_address_2(self): """ Default = 4000 (Start Address of I/O Module in Slot 2. Data for this module is at Start Address 5000) :return: int """ return self.read_register(1122) def write_log_start_address_2(self, value): """ Default = 4000 (Start Address of I/O Module in Slot 2. Data for this module is at Start Address 5000) :return: modbus response. """ return self.write_register(1122, value) def read_number_of_registers_to_log_2(self): """ Number of Registers to Log starting at Log Start Address 2. Maximum = 100, Default = 8 :return: int """ return self.read_register(1123) def write_number_of_registers_to_log_2(self, value): """ Number of Registers to Log starting at Log Start Address 2. Maximum = 100, Default = 8 :param value: number to write :return: modbus response """ return self.write_register(1123, value) def read_log_start_address_3(self): """ Default = 6000 (Start Address of I/O Module in Slot 3. Data for this module is at Start Address 7000) :return: int """ return self.read_register(1124) def write_log_start_address_3(self, value): """ Default = 6000 (Start Address of I/O Module in Slot 3. Data for this module is at Start Address 7000) :return: modbus response. """ return self.write_register(1124, value) def read_number_of_registers_to_log_3(self): """ Number of Registers to Log starting at Log Start Address 3. Maximum = 100, Default = 8 :return: int """ return self.read_register(1125) def write_number_of_registers_to_log_3(self, value): """ Number of Registers to Log starting at Log Start Address 3. Maximum = 100, Default = 8 :param value: number to write :return: modbus response """ return self.write_register(1125, value) def read_log_start_address_4(self): """ Default = 8000 (Start Address of I/O Module in Slot 4. Data for this module is at Start Address 9000) :return: int """ return self.read_register(1126) def write_log_start_address_4(self, value): """ Default = 8000 (Start Address of I/O Module in Slot 4. Data for this module is at Start Address 9000) :return: modbus response. """ return self.write_register(1126, value) def read_number_of_registers_to_log_4(self): """ Number of Registers to Log starting at Log Start Address 4. Maximum = 100, Default = 8 :return: int """ return self.read_register(1127) def write_number_of_registers_to_log_4(self, value): """ Number of Registers to Log starting at Log Start Address 4. Maximum = 100, Default = 8 :param value: number to write :return: modbus response """ return self.write_register(1127, value) def read_log_interval(self): return utils.int16_to_int32(self.read_registers(1130, 2)) def write_log_interval(self, value): return self.write_registers(1130, utils.int32_to_int16s(value)) def read_log_number_of_samples(self): return utils.int16_to_int32(self.read_registers(1132, 2)) def write_log_number_of_samples(self, value): return self.write_registers(1132, utils.int32_to_int16s(value)) def read_log_enable(self): return self.read_register(1140) def write_log_enable(self, enable): return self.write_register(1140, enable) def read_card_available(self): return self.read_register(1150) def read_total_space(self): return utils.int16_to_int32(self.read_registers(1151, 2)) def read_free_space(self): return utils.int16_to_int32(self.read_registers(1153, 2)) ############################# # Module RTC and Temperature. ############################# def read_second(self): """0-59""" return self.read_register(1200) def write_second(self, second): """0-59""" return self.write_register(1200, second) def read_minute(self): """0-59""" return self.read_register(1201) def write_minute(self, minute): """0-59""" return self.write_register(1201, minute) def read_hour(self): """0-23""" return self.read_register(1202) def write_hour(self, hour): """0-23""" return self.write_register(1202, hour) def read_day(self): """1-7, 1 = Sunday""" return self.read_register(1203) def write_day(self, day): """1-7, 1 = Sunday""" return self.write_register(1203, day) def read_date(self): """1-31""" return self.read_register(1204) def write_date(self, date): """1-31""" return self.write_register(1204, date) def read_month(self): """1-12""" return self.read_register(1205) def write_month(self, month): """1-12""" return self.write_register(1205, month) def read_year(self): """0-99""" return self.read_register(1206) def write_year(self, year): """0-99""" return self.write_register(1206, year) def read_internal_temperature_sensor(self): """0-59, Degree C""" return self.read_register(1210) ###################### # PID Loop Controllers ###################### # TODO: Implement the write functions for PID loop controllers. # TODO: Document write functions. def read_pid_id(self): """ Unique instance ID of Controller :return: 0 to 31 """ return self.read_register(1300) def write_pid_id(self, input_id): """ Unique instance ID of Controller :return: modbus response """ return self.write_register(1300, input_id) if 0 <= input_id <= 31 else False def read_pid_enable(self): """ Enable/Disable Controller :return: 0 or 1 """ return self.read_register(1301) def write_pid_enable(self, enable): """ Enable/Disable Controller :return: modbus response """ return self.write_register(1301, enable) def read_pid_name(self): """ PID Controller name, 10 characters max. :return: string of length 10 """ return response_to_string(self.read_registers(1310, 10)) def write_pid_name(self, name): """ PID Controller name, 10 characters max. :return: modbus response """ return self.write_registers(1310, name) def read_pid_description(self): """ PID Controller Description, 10 characters max. :return: string of length 10 """ return response_to_string(self.read_registers(1330, 10)) def write_pid_description(self, description): """ PID Controller Description, 10 characters max. :return: string of length 10 """ return self.write_registers(1330, description) def read_pid_engineering_units(self): """ Engineering Units (EU) chosen for the Controller, 5 characters max. :return: string of length 5 """ return response_to_string(self.read_registers(1350, 5)) def write_pid_engineering_units(self, engineering_units): """ Engineering Units (EU) chosen for the Controller, 5 characters max. :return: string of length 5 """ return self.write_registers(1350, engineering_units) def read_pid_pv_range_unit(self): """ Units chosen for Process Variable. 5 characters max. Standard unit = "%". :return: string of length 5 """ return response_to_string(self.read_registers(1355, 5)) def write_pid_pv_range_unit(self, pv_range_unit): """ Units chosen for Process Variable. 5 characters max. Standard unit = "%". :return: string of length 5 """ return self.write_registers(1355, pv_range_unit) def read_pid_co_range_unit(self): """ Units chosen for Control Output. 5 characters max. Standard unit = "%". :return: string of length 5 """ return response_to_string(self.read_registers(1360, 5)) def write_pid_co_range_unit(self, co_range_unit): """ Units chosen for Control Output. 5 characters max. Standard unit = "%". :return: string of length 5 """ return self.write_registers(1360, co_range_unit) def read_pid_pv_modbus_address(self): """ System Address where Process Variable is obtained from. :return: 0 to 65,535 """ return self.read_register(1366) def read_pid_co_modbus_address(self): """ System Address where Control Output is sent to. :return: 0 to 65,535 """ return self.read_register(1367) def read_pid_pv_count_maximum(self): """ Process Variable maximum count value. MSB at Address 1368, LSB at Address 1369. :return: 0 to 2^32-1 """ return int16_to_int32(self.read_registers(1368, 2)) def write_pid_pv_count_maximum(self, pv_count_maximum): """ Process Variable maximum count value. MSB at Address 1368, LSB at Address 1369. :return: 0 to 2^32-1 """ return self.write_registers(1368, int32_to_int16s(pv_count_maximum)) def read_pid_pv_count_minimum(self): """ Process Variable minimum count value. MSB at Address 1370, LSB at Address 1371. :return: 0 to 2^32-1 """ return int16_to_int32(self.read_registers(1370, 2)) def write_pid_pv_count_minimum(self, pv_count_minimum): """ Process Variable minimum count value. MSB at Address 1370, LSB at Address 1371. :return: 0 to 2^32-1 """ return self.write_registers(1370, int32_to_int16s(pv_count_minimum)) def read_pid_co_count_maximum(self): """ Control Output maximum count value. MSB at Address 1372, LSB at Address 1373. :return: 0 to 2^32-1 """ return int16_to_int32(self.read_registers(1372, 2)) def write_pid_co_count_maximum(self, co_count_maximum): """ Control Output maximum count value. MSB at Address 1372, LSB at Address 1373. :return: 0 to 2^32-1 """ return self.write_registers(1372, int32_to_int16s(co_count_maximum)) def read_pid_co_count_minimum(self): """ Control Output minimum count value. MSB at Address 1374, LSB at Address 1375. :return: 0 to 2^32-1 """ return int16_to_int32(self.read_registers(1374, 2)) def write_pid_co_count_minimum(self, co_count_minimum): """ Control Output minimum count value. MSB at Address 1374, LSB at Address 1375. :return: 0 to 2^32-1 """ return self.write_registers(1374, int32_to_int16s(co_count_minimum)) def read_pid_pv_range_maximum(self): """ Process Variable Range maximum value. Integer part at Address 1376, fractional part at Address 1377. :return: float type number """ return ints_to_float(self.read_registers(1376, 2)) def write_pid_pv_range_maximum(self, pv_range_maximum): """ Process Variable Range maximum value. Integer part at Address 1376, fractional part at Address 1377. :return: float type number """ return self.write_registers(1376, float_to_ints(pv_range_maximum)) def read_pid_pv_range_minimum(self): """ Process Variable Range minimum value. Integer part at Address 1378, fractional part at Address 1379. :return: float type number """ return ints_to_float(self.read_registers(1378, 2)) def write_pid_pv_range_minimum(self, pv_range_minimum): """ Process Variable Range minimum value. Integer part at Address 1378, fractional part at Address 1379. :return: float type number """ return self.write_registers(1378, float_to_ints(pv_range_minimum)) def read_pid_co_range_maximum(self): """ Control Output Range maximum value. Integer part at Address 1380, fractional part at Address 1381. :return: float type number """ return ints_to_float(self.read_registers(1380, 2)) def write_pid_co_range_maximum(self, co_range_maximum): """ Control Output Range maximum value. Integer part at Address 1380, fractional part at Address 1381. :return: float type number """ return self.write_registers(1380, float_to_ints(co_range_maximum)) def read_pid_co_range_minimum(self): """ Control Output Range minimum value. Integer part at Address 1382, fractional part at Address 1383. :return: float type number """ return ints_to_float(self.read_registers(1382, 2)) def write_pid_co_range_minimum(self, co_range_minimum): """ Control Output Range minimum value. Integer part at Address 1382, fractional part at Address 1383. :return: float type number """ return self.write_registers(1382, float_to_ints(co_range_minimum)) def read_pid_algorithm(self): """ PID Control Algorithm. 0 = Noninteractive, 1 = Parallel :return: string """ return {0: "Noninteractive", 1: "Parallel"}[self.read_register(1386)] def write_pid_algorithm(self, algorithm): """ PID Control Algorithm. 0 = Noninteractive, 1 = Parallel :return: string """ return self.read_register(1386) # TODO: Finish this. def read_pid_control_direction(self): """ Control Direction. 0 = Reverse Acting, 1 = Direct Acting :return: string """ return { 0: "Reverse Acting", 1: "Direct Acting" }[self.read_register(1387)] def read_pid_setpoint_action(self): """ Setpoint Action. 0 = Proportional & Derivative on Error, 1 = Proportional on Error / Derivative on PV, 2 = Proportional & Derivative on PV. :return: string """ return { 0: "Proportional & Derivative on Error", 1: "Proportional on Error / Derivative on PV", 2: "Proportional & Derivative on PV", }[self.read_register(1388)] def write_pid_setpoint_action(self, setpoint_action): """ Setpoint Action. 0 = Proportional & Derivative on Error, 1 = Proportional on Error / Derivative on PV, 2 = Proportional & Derivative on PV. :return: string """ return self.read_register(1388) def read_pid_mode(self): """ Operational Mode. 0 = Manual, 1 = Automatic :return: string """ return {0: "Manual", 1: "Automatic"}[self.read_register(1389)] def read_pid_output_type(self): """ Control Output Signal Type. 0 = Voltage, 1 = Current, 2 = Discrete Output (PWM) :return: string """ return { 0: "Voltage", 1: "Current", 2: "Discrete Output (PWM)" }[self.read_register(1390)] def read_pid_setpoint(self): """ Setpoint. Integer part at Address 1396, fractional part at Address 1397. :return: float type number """ return ints_to_float(self.read_registers(1396, 2)) def read_pid_process_variable(self): """ Process Variable. Integer part at Address 1398, fractional part at Address 1399. :return: float type number """ return ints_to_float(self.read_registers(1398, 2)) def read_pid_control_output(self): """ Control Output. Integer part at Address 1400, fractional part at Address 1401. :return: float type number """ return ints_to_float(self.read_registers(1400, 2)) def read_pid_pv_maximum(self): """ Process Variable maximum value. Integer part at Address 1402, fractional part at Address 1403. :return: float type number """ return ints_to_float(self.read_registers(1402, 2)) def read_pid_pv_minimum(self): """ Process Variable minimum value. Integer part at Address 1404, fractional part at Address 1405. :return: float type number """ return ints_to_float(self.read_registers(1404, 2)) def read_pid_co_maximum(self): """ Control Output maximum value. Integer part at Address 1406, fractional part at Address 1407. :return: float type number """ return ints_to_float(self.read_registers(1406, 2)) def read_pid_co_minimum(self): """ Control Output minimum value. Integer part at Address 1408, fractional part at Address 1409. :return: float type number """ return ints_to_float(self.read_registers(1408, 2)) def read_pid_kc(self): """ Controller Gain (%/%). Integer part at Address 1410, fractional part at Address 1411. :return: float type number """ return ints_to_float(self.read_registers(1410, 2)) def read_pid_ti(self): """ Integral Time (minutes). Integer part at Address 1412, fractional part at Address 1413. Fractional part is in 10,000ths of a second. :return: float type number """ return ints_to_float(self.read_registers(1412, 2)) def read_pid_td(self): """ Derivative Time (minutes). Integer part at Address 1414, fractional part at Address 1415. Fractional part is in 10,000ths of a second. :return: float type number """ return ints_to_float(self.read_registers(1414, 2)) def read_pid_scan_time(self): """ PID Controller Update Rate (seconds). Integer part at Address 1416, fractional part at Address 1417. This value is fixed at 1s. :return: float type number """ return ints_to_float(self.read_registers(1416, 2)) def read_pid_co_high_clamp(self): """ Controller Output upper limit (%). Integer part at Address 1418, fractional part at Address 1419. :return: float type number """ return ints_to_float(self.read_registers(1418, 2)) def read_pid_co_low_clamp(self): """ Controller Output lower limit (%). Integer part at Address 1420, fractional part at Address 1421. :return: float type number """ return ints_to_float(self.read_registers(1420, 2)) def read_pid_pv_tracking(self): """ Track Process Variable in Manual Mode. 0 = Do Not Track PV, 1 = Track PV :return: string """ return {0: "Do Not Track PV", 1: "Track PV"}[self.read_register(1422)] def read_pid_gap_width(self): """ Gap around setpoint (Engineering Units). Integer part at Address 1423, fractional part at Address 1424. :return: float type number """ return ints_to_float(self.read_registers(1423, 2)) def read_pid_gap_multiplier(self): """ Gain multiplier inside Gap. Integer part at Address 1425, fractional part at Address 1426. :return: float type number """ return ints_to_float(self.read_registers(1425, 2)) def read_pid_filter_time_constant(self): """ PV Filter Time Constant (minutes). Integer part at Address 1427, fractional part at Address 1428. Fractional part is in 10,000ths of a second. :return: float type number """ return ints_to_float(self.read_registers(1427, 2)) def read_pid_active_alarm(self): """ Indicates which alarm condition is active. 0 = Low-Low, 1 = Low, 2 = None, 3 = High-High, 4 = High. :return: string """ return { 0: "Low-Low", 1: "Low", 2: "None", 3: "High-High", 4: "High", }[self.read_register(1441)] def read_pid_alarm_deadband(self): """ Deadband or Hysteresis. Adds to low limits, subtracts from high limits. Integer part at Address 1442, fractional part at Address 1443. :return: float type number """ return ints_to_float(self.read_registers(1442, 2)) def read_pid_high_high_alarm_limit(self): """ High-High Alarm Limit (Engineering Units). Integer part at Address 1444, fractional part at Address 1445. :return: float type number """ return ints_to_float(self.read_registers(1444, 2)) def read_pid_high_alarm_limit(self): """ High Alarm Limit (Engineering Units). Integer part at Address 1446, fractional part at Address 1447. :return: float type number """ return ints_to_float(self.read_registers(1446, 2)) def read_pid_low_alarm_limit(self): """ Low Alarm Limit (Engineering Units). Integer part at Address 1448, fractional part at Address 1449. :return: float type number """ return ints_to_float(self.read_registers(1448, 2)) def read_pid_low_low_alarm_limit(self): """ Low-Low Alarm Limit (Engineering Units). Integer part at Address 1450, fractional part at Address 1451. :return: float type number """ return ints_to_float(self.read_registers(1450, 2)) ################### # Helper Functions. ################### def ftp_settings(self) -> str: # TODO: Document this function. result = "Username: "******"\n" result += "Password: "******"\n" result += "Anonymous Login: "******"Enabled" elif anonymous_login == 0: result += "Disabled" return result def __del__(self): if MODBUS_BACKEND == PYMODBUS3: self._client.close() if MODBUS_BACKEND == UMODBUS: self._sock.close()
class ModbusClient(threading.Thread): """Define Tag engine to poll MODBUS servers. """ def __init__(self, asset_read_dict, asset_write_dict, interface_config): """ interface config ('ip_add', '0.0.0.0') ('endian', '>') ('registers', { 'type': '32bit_float', 'name': 'kw', 'scale': 0.001, 'mod_add': 50052}) ('update_rate', 1) """ threading.Thread.__init__(self) self.config = interface_config self.daemon = True # TODO: this may be threading in 2.7, daemon is method now? self.client = None self.cvt = dict() self.process_stop = False self.connected = False self.timestamp = str() # Initialize Current Value Table (CVT) for reg in self.config['registers']: self.cvt.update({reg['name']: None}) def __del__(self): """Teardown """ print('MODBUS CLIENT:', self.process_name, '-- deconstructed') def run(self): """Connect to target and maintain client while loop. Call with Thread.start() """ print('MODBUS CLIENT', self.process_name, '-- started') self.connect() while not self.process_stop: if self.connected: self._update() time.sleep(self.config['update_rate']) self.disconnect() self.__del__() def stop(self): """Stop process """ self.process_stop = True def connect(self): """Connect to target MODBUS server. """ try: self.client = ModbusTcpClient(self.config['ip_add']) self.client.connect() self.connected = True except: print('MODBUS CLIENT:', self.process_name, '-- unable to connect to target server.') def disconnect(self): """Disconnect from target MODBUS server. """ try: self.client.close() self.connected = False print('MODBUS CLIENT:', self.process_name, '-- disconnected') except: print('MODBUS CLIENT:', self.process_name, '-- failed to disconnect from server') def _update(self): """Poll MODBUS target server. Store results in self.cvt """ for reg in self.config['registers']: try: """TODO: filter by register_name_list""" if reg['type'] == '32bit_float': read_data = self.client.read_holding_registers( reg['mod_add'], 2, unit=1) decoded_data = BinaryPayloadDecoder.from_registers( list(reversed(read_data.registers)), endian=self.config['endian']) self.cvt[reg['name']] = decoded_data.decode_32bit_float( ) * reg['scale'] elif reg['type'] == '32bit_int': read_data = self.client.read_holding_registers( reg['mod_add'], 2, unit=1) decoded_data = BinaryPayloadDecoder.from_registers( read_data.registers, endian=self.config['endian']) self.cvt[reg['name']] = decoded_data.decode_32bit_int( ) * reg['scale'] elif reg['type'] == '32bit_uint': read_data = self.client.read_holding_registers( reg['mod_add'], 2, unit=1) decoded_data = BinaryPayloadDecoder.from_registers( read_data.registers, endian=self.config['endian']) self.cvt[reg['name']] = decoded_data.decode_32bit_uint( ) * reg['scale'] elif reg['type'] == '16bit_int': read_data = self.client.read_holding_registers( reg['mod_add'], 1, unit=1) decoded_data = BinaryPayloadDecoder.from_registers( read_data.registers, endian=self.config['endian']) self.cvt[reg['name']] = decoded_data.decode_16bit_int( ) * reg['scale'] elif reg['type'] == '16bit_uint': read_data = self.client.read_holding_registers( reg['mod_add'], 1, unit=1) decoded_data = BinaryPayloadDecoder.from_registers( read_data.registers, endian=self.config['endian']) self.cvt[reg['name']] = decoded_data.decode_16bit_uint( ) * reg['scale'] else: print(reg['type'], 'data type not supported') except AttributeError: print(self.process_name, 'MODBUS CLIENT: Read error') # TODO: How to import pymobus3 exceptions? self.timestamp = time.ctime() def write(self): pass # TODO: write holding registers