def parse_response(self, response): if len(response) < 3: raise ModbusInvalidResponseError( "Response length is invalid {0}".format(len(response))) # check for max length problem, the iCharger HID based Modbus protocol handles only # 64 byte packets. If you want to read more, then send multiple read requests. (self.response_length, self.adu_constant, self.response_func_code) = struct.unpack(">BBB", response[0:3]) if self.adu_constant != 0x30: raise ModbusInvalidResponseError( "Response doesn't containt constant 0x30 in ADU portion, constant value found is {0}" .format(self.adu_constant)) if self.response_func_code != self.func_code: raise ModbusInvalidResponseError( "Response func_code {0} isn't the same as the request func_code {1}" .format(self.response_func_code, self.func_code)) # primitive byte swap the entire thing... header = response[2:4] # LOGGER.debug(get_log_buffer("header <- ", header)) data = response[4:] # LOGGER.debug(get_log_buffer("data <- ", data)) final = header + ''.join( [c for t in zip(data[1::2], data[::2]) for c in t]) # LOGGER.debug(get_log_buffer("final <- ", final)) return final
def parse_response(self, response): if len(response) < 3: raise ModbusInvalidResponseError( "Response length is invalid {0}".format(len(response))) (self.response_length, self.adu_constant, self.response_func_code, self.modbus_error) = \ struct.unpack("=BBBB", response[0:4]) if self.response_length > MAX_READWRITE_LEN: raise ModbusInvalidResponseError( "Response length is greater than {0}".format( MAX_READWRITE_LEN)) if self.response_func_code == self.func_code: # primitive byte swap the entire thing... but only if this is the READ INPUT/HOLDING type if self.func_code == cst.READ_HOLDING_REGISTERS or self.func_code == cst.READ_INPUT_REGISTERS: header = response[2:4] data = response[4:] response = header + ''.join( [c for t in zip(data[1::2], data[::2]) for c in t]) else: # skip len/adu const - returning everything else response = response[2:] self.modbus_error = 0 else: if self.response_func_code == self.func_code | 0x80: raise ModbusInvalidResponseError( "Response contains error code {0}: {1}".format( self.modbus_error, self._modbus_error_string(self.modbus_error))) # else: # # skip len/adu const - returning everything else - this avoids bitching about invalid reads, but # # of course does not solve the root problem. # response = response[2:] self.modbus_error = 0 return response
def parse_response(self, response): if len(response) < 3: raise ModbusInvalidResponseError( "Response length is invalid {0}".format(len(response))) (self.response_length, self.adu_constant, self.response_func_code, self.modbus_error) = \ struct.unpack("=BBBB", response[0:4]) if self.adu_constant != MODBUS_HID_FRAME_TYPE: raise ModbusInvalidResponseError( "Response does not contain the expected frame type constant (0x30) in ADU portion of the result, constant value found is {0}" .format(self.adu_constant)) if self.response_func_code != self.func_code: if self.response_func_code == self.func_code | 0x80: raise ModbusInvalidResponseError( "Response contains error code {0}: {1}".format( self.modbus_error, self._modbus_error_string(self.modbus_error))) raise ModbusInvalidResponseError( "Response func_code {0} isn't the same as the request func_code {1}" .format(self.response_func_code, self.func_code)) else: self.modbus_error = 0 # primitive byte swap the entire thing... but only if this is the READ INPUT/HOLDING type if self.func_code == cst.READ_HOLDING_REGISTERS or self.func_code == cst.READ_INPUT_REGISTERS: header = response[2:4] data = response[4:] response = header + ''.join( [c for t in zip(data[1::2], data[::2]) for c in t]) else: response = response[2:] return response
def execute( self, slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="", expected_length=-1): """ Execute a modbus query and returns the data part of the answer as a tuple The returned tuple depends on the query function code. see modbus protocol specification for details data_format makes possible to extract the data like defined in the struct python module documentation """ pdu = "" is_read_function = False nb_of_digits = 0 # open the connection if it is not already done self.open() # Build the modbus pdu and the format of the expected data. # It depends of function code. see modbus specifications for details. if function_code == defines.READ_COILS or function_code == defines.READ_DISCRETE_INPUTS: is_read_function = True pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x) byte_count = quantity_of_x // 8 if (quantity_of_x % 8) > 0: byte_count += 1 nb_of_digits = quantity_of_x if not data_format: data_format = ">" + (byte_count * "B") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode + crc1 + crc2 expected_length = byte_count + 5 elif function_code == defines.READ_INPUT_REGISTERS or function_code == defines.READ_HOLDING_REGISTERS: is_read_function = True pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x) if not data_format: data_format = ">" + (quantity_of_x * "H") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2 expected_length = 2 * quantity_of_x + 5 elif (function_code == defines.WRITE_SINGLE_COIL) or (function_code == defines.WRITE_SINGLE_REGISTER): if function_code == defines.WRITE_SINGLE_COIL: if output_value != 0: output_value = 0xff00 fmt = ">BH"+("H" if output_value >= 0 else "h") pdu = struct.pack(fmt, function_code, starting_address, output_value) if not data_format: data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + value1+value2 + crc1 + crc2 expected_length = 8 elif function_code == defines.WRITE_MULTIPLE_COILS: byte_count = len(output_value) // 8 if (len(output_value) % 8) > 0: byte_count += 1 pdu = struct.pack(">BHHB", function_code, starting_address, len(output_value), byte_count) i, byte_value = 0, 0 for j in output_value: if j > 0: byte_value += pow(2, i) if i == 7: pdu += struct.pack(">B", byte_value) i, byte_value = 0, 0 else: i += 1 if i > 0: pdu += struct.pack(">B", byte_value) if not data_format: data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2 expected_length = 8 elif function_code == defines.WRITE_MULTIPLE_REGISTERS: if output_value and data_format: byte_count = struct.calcsize(data_format) else: byte_count = 2 * len(output_value) pdu = struct.pack(">BHHB", function_code, starting_address, byte_count / 2, byte_count) if output_value and data_format: pdu += struct.pack(data_format, *output_value) else: for j in output_value: fmt = "H" if j >= 0 else "h" pdu += struct.pack(">" + fmt, j) # data_format is now used to process response which is always 2 registers: # 1) data address of first register, 2) number of registers written data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2 expected_length = 8 elif function_code == defines.READ_EXCEPTION_STATUS: pdu = struct.pack(">B", function_code) data_format = ">B" if expected_length < 0: # No length was specified and calculated length can be used: expected_length = 5 elif function_code == defines.DIAGNOSTIC: # SubFuncCode are in starting_address pdu = struct.pack(">BH", function_code, starting_address) if len(output_value) > 0: for j in output_value: # copy data in pdu pdu += struct.pack(">B", j) if not data_format: data_format = ">" + (len(output_value) * "B") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + SubFunc1 + SubFunc2 + Data + crc1 + crc2 expected_length = len(output_value) + 6 elif function_code == defines.READ_WRITE_MULTIPLE_REGISTERS: is_read_function = True byte_count = 2 * len(output_value) pdu = struct.pack( ">BHHHHB", function_code, starting_address, quantity_of_x, defines.READ_WRITE_MULTIPLE_REGISTERS, len(output_value), byte_count ) for j in output_value: fmt = "H" if j >= 0 else "h" # copy data in pdu pdu += struct.pack(">"+fmt, j) if not data_format: data_format = ">" + (quantity_of_x * "H") if expected_length < 0: # No lenght was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2 expected_length = 2 * quantity_of_x + 5 else: raise ModbusFunctionNotSupportedError("The {0} function code is not supported. ".format(function_code)) # instantiate a query which implements the MAC (TCP or RTU) part of the protocol query = self._make_query() # add the mac part of the protocol to the request request = query.build_request(pdu, slave) # send the request to the slave retval = call_hooks("modbus.Master.before_send", (self, request)) if retval is not None: request = retval if self._verbose: LOGGER.debug(get_log_buffer("-> ", request)) self._send(request) call_hooks("modbus.Master.after_send", (self, )) if slave != 0: # receive the data from the slave response = self._recv(expected_length) retval = call_hooks("modbus.Master.after_recv", (self, response)) if retval is not None: response = retval if self._verbose: LOGGER.debug(get_log_buffer("<- ", response)) # extract the pdu part of the response response_pdu = query.parse_response(response) # analyze the received data (return_code, byte_2) = struct.unpack(">BB", response_pdu[0:2]) if return_code > 0x80: # the slave has returned an error exception_code = byte_2 raise ModbusError(exception_code) else: if is_read_function: # get the values returned by the reading function byte_count = byte_2 data = response_pdu[2:] if byte_count != len(data): # the byte count in the pdu is invalid raise ModbusInvalidResponseError( "Byte count is {0} while actual number of bytes is {1}. ".format(byte_count, len(data)) ) else: # returns what is returned by the slave after a writing function data = response_pdu[1:] # returns the data as a tuple according to the data_format # (calculated based on the function or user-defined) result = struct.unpack(data_format, data) if nb_of_digits > 0: digits = [] for byte_val in result: for i in range(8): if len(digits) >= nb_of_digits: break digits.append(byte_val % 2) byte_val = byte_val >> 1 result = tuple(digits) return result
def execute( self, slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="", expected_length=-1, write_starting_address_fc23=0, number_file=None, pdu="" ): """ Execute a modbus query and returns the data part of the answer as a tuple The returned tuple depends on the query function code. see modbus protocol specification for details data_format makes possible to extract the data like defined in the struct python module documentation For function Read_File_Record starting_address, quantity_of_x, number_file must be tuple () of one long (by the number of requested sub_seq) the result will be ((sub _ seq_0 _ data), (sub_seq_1_data),... (sub_seq_N_data)). """ is_read_function = False nb_of_digits = 0 if number_file is None: number_file = tuple() # open the connection if it is not already done self.open() # Build the modbus pdu and the format of the expected data. # It depends of function code. see modbus specifications for details. if function_code == defines.READ_COILS or function_code == defines.READ_DISCRETE_INPUTS: is_read_function = True pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x) byte_count = quantity_of_x // 8 if (quantity_of_x % 8) > 0: byte_count += 1 nb_of_digits = quantity_of_x if not data_format: data_format = ">" + (byte_count * "B") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode + crc1 + crc2 expected_length = byte_count + 5 elif function_code == defines.READ_INPUT_REGISTERS or function_code == defines.READ_HOLDING_REGISTERS: is_read_function = True pdu = struct.pack(">BHH", function_code, starting_address, quantity_of_x) if not data_format: data_format = ">" + (quantity_of_x * "H") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2 expected_length = 2 * quantity_of_x + 5 elif function_code == defines.READ_FILE_RECORD: is_read_function = True if ( isinstance(number_file, tuple) and isinstance(starting_address, tuple) and isinstance(quantity_of_x, tuple) and len(number_file) == len(starting_address) == len(quantity_of_x) > 0 ): count_seq = len(number_file) else: raise ModbusInvalidRequestError( 'For function READ_FILE_RECORD param' 'starting_address, quantity_of_x, number_file must be tuple()' 'of one length > 0 (by the number of requested sub_seq)' ) pdu = struct.pack(">BB", function_code, count_seq * 7) + b''.join(map(lambda zip_param: struct.pack(">BHHH", *zip_param), zip(count_seq * (6, ), number_file, starting_address, quantity_of_x))) if not data_format: data_format = ">BB" + 'BB'.join(map(lambda x: x*'H', quantity_of_x)) if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + (byteLenSubReq+byteref+bytecode[] x 2)*countSubReq + crc1 + crc2 expected_length = 2 * sum(quantity_of_x) + 2 * count_seq + 5 elif (function_code == defines.WRITE_SINGLE_COIL) or (function_code == defines.WRITE_SINGLE_REGISTER): if function_code == defines.WRITE_SINGLE_COIL: if output_value != 0: output_value = 0xff00 fmt = ">BHH" else: fmt = ">BH"+("H" if output_value >= 0 else "h") pdu = struct.pack(fmt, function_code, starting_address, output_value) if not data_format: data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + value1+value2 + crc1 + crc2 expected_length = 8 elif function_code == defines.WRITE_MULTIPLE_COILS: byte_count = len(output_value) // 8 if (len(output_value) % 8) > 0: byte_count += 1 pdu = struct.pack(">BHHB", function_code, starting_address, len(output_value), byte_count) i, byte_value = 0, 0 for j in output_value: if j > 0: byte_value += pow(2, i) if i == 7: pdu += struct.pack(">B", byte_value) i, byte_value = 0, 0 else: i += 1 if i > 0: pdu += struct.pack(">B", byte_value) if not data_format: data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2 expected_length = 8 elif function_code == defines.WRITE_MULTIPLE_REGISTERS: if output_value and data_format: byte_count = struct.calcsize(data_format) else: byte_count = 2 * len(output_value) pdu = struct.pack(">BHHB", function_code, starting_address, byte_count // 2, byte_count) if output_value and data_format: pdu += struct.pack(data_format, *output_value) else: for j in output_value: fmt = "H" if j >= 0 else "h" pdu += struct.pack(">" + fmt, j) # data_format is now used to process response which is always 2 registers: # 1) data address of first register, 2) number of registers written data_format = ">HH" if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + adress1 + adress2 + outputQuant1 + outputQuant2 + crc1 + crc2 expected_length = 8 elif function_code == defines.READ_EXCEPTION_STATUS: pdu = struct.pack(">B", function_code) data_format = ">B" if expected_length < 0: # No length was specified and calculated length can be used: expected_length = 5 elif function_code == defines.DIAGNOSTIC: # SubFuncCode are in starting_address pdu = struct.pack(">BH", function_code, starting_address) if len(output_value) > 0: for j in output_value: # copy data in pdu pdu += struct.pack(">B", j) if not data_format: data_format = ">" + (len(output_value) * "B") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + SubFunc1 + SubFunc2 + Data + crc1 + crc2 expected_length = len(output_value) + 6 elif function_code == defines.READ_WRITE_MULTIPLE_REGISTERS: is_read_function = True byte_count = 2 * len(output_value) pdu = struct.pack( ">BHHHHB", function_code, starting_address, quantity_of_x, write_starting_address_fc23, len(output_value), byte_count ) for j in output_value: fmt = "H" if j >= 0 else "h" # copy data in pdu pdu += struct.pack(">"+fmt, j) if not data_format: data_format = ">" + (quantity_of_x * "H") if expected_length < 0: # No length was specified and calculated length can be used: # slave + func + bytcodeLen + bytecode x 2 + crc1 + crc2 expected_length = 2 * quantity_of_x + 5 elif function_code == defines.RAW: # caller has to set arguments "pdu", "expected_length", and "data_format" pass elif function_code == defines.DEVICE_INFO: # is_read_function = True mei_type = 0x0E pdu = struct.pack( ">BBBB", # function_code = 43 (0x2B) # MEI Type = 0x0E (Read Device Identification) # output_value[0] = Read Device ID code # output_value[1] = Object Id function_code, mei_type, output_value[0], output_value[1] ) else: raise ModbusFunctionNotSupportedError("The {0} function code is not supported. ".format(function_code)) # instantiate a query which implements the MAC (TCP or RTU) part of the protocol query = self._make_query() # add the mac part of the protocol to the request request = query.build_request(pdu, slave) # send the request to the slave retval = call_hooks("modbus.Master.before_send", (self, request)) if retval is not None: request = retval if self._verbose: LOGGER.debug(get_log_buffer("-> ", request)) self._send(request) call_hooks("modbus.Master.after_send", (self, )) if slave != 0: # receive the data from the slave response = self._recv(expected_length) retval = call_hooks("modbus.Master.after_recv", (self, response)) if retval is not None: response = retval if self._verbose: LOGGER.debug(get_log_buffer("<- ", response)) # extract the pdu part of the response response_pdu = query.parse_response(response) # analyze the received data (return_code, byte_2) = struct.unpack(">BB", response_pdu[0:2]) if return_code > 0x80: # the slave has returned an error exception_code = byte_2 raise ModbusError(exception_code) else: if is_read_function: # get the values returned by the reading function byte_count = byte_2 data = response_pdu[2:] if byte_count != len(data): # the byte count in the pdu is invalid raise ModbusInvalidResponseError( "Byte count is {0} while actual number of bytes is {1}. ".format(byte_count, len(data)) ) elif function_code == defines.DEVICE_INFO: data = response_pdu[1:] data_format = ">" + (len(data) * "B") else: # returns what is returned by the slave after a writing function data = response_pdu[1:] # returns the data as a tuple according to the data_format # (calculated based on the function or user-defined) if (re.match("[>]?[sp]?",data_format)): result = data.decode() else: result = struct.unpack(data_format, data) if nb_of_digits > 0: digits = [] for byte_val in result: for i in range(8): if len(digits) >= nb_of_digits: break digits.append(byte_val % 2) byte_val = byte_val >> 1 result = tuple(digits) if function_code == defines.READ_FILE_RECORD: sub_seq = list() ptr = 0 while ptr < len(result): sub_seq += ((ptr + 2, ptr + 2 + result[ptr] // 2), ) ptr += result[ptr] // 2 + 2 result = tuple(map(lambda sub_seq_x: result[sub_seq_x[0]:sub_seq_x[1]], sub_seq)) return result