def handle(self): ''' Callback when we receive any data ''' while self.running: try: data = self.request.recv(1024) if data: if _logger.isEnabledFor(logging.DEBUG): _logger.debug( "recv: " + " ".join([hex(byte2int(x)) for x in data])) if isinstance(self.framer, ModbusAsciiFramer): unit_address = int(data[1:3], 16) elif isinstance(self.framer, ModbusBinaryFramer): unit_address = byte2int(data[1]) else: unit_address = byte2int(data[0]) if unit_address in self.server.context: self.framer.processIncomingPacket(data, self.execute) except Exception as msg: # since we only have a single socket, we cannot exit # Clear frame buffer self.framer.resetFrame() _logger.error("Socket error occurred %s" % msg)
def checkFrame(self): """ Check if the next frame is available. Return True if we were successful. 1. Populate header 2. Discard frame if UID does not match """ try: self.populateHeader() frame_size = self._header['len'] # print(frame_size) # print(len(self._buffer)) data = self._buffer[:frame_size - 2] crc = self._buffer[frame_size - 2:frame_size] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) # print(crc) # print(self._buffer.hex()) # print(crc_val) # print(len(data)) if checkCRC(data, crc_val): return True else: _logger.debug("CRC invalid, discarding header!!") self.resetFrame() return False except (IndexError, KeyError, struct.error): return False
def execute(self, request): ''' Starts the producer to send the next request to consumer.write(Frame(request)) ''' retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) expected_response_length = None if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() if response_pdu_size: expected_response_length = self._calculate_response_length( response_pdu_size) while retries > 0: try: last_exception = None self.client.connect() packet = self.client.framer.buildPacket(request) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.client._send(packet) exception = False result = self.client._recv(expected_response_length or 1024) while result and expected_response_length and len( result) < expected_response_length: if not exception and not self._check_response(result): exception = True expected_response_length = self._calculate_exception_length( ) continue result += self.client._recv(expected_response_length - len(result)) if not result and self.retry_on_empty: retries -= 1 continue if _logger.isEnabledFor(logging.DEBUG): _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result])) self.client.framer.processIncomingPacket( result, self.addTransaction) break except (socket.error, ModbusIOException, InvalidResponseRecievedException) as msg: self.client.close() _logger.debug("Transaction failed. (%s) " % msg) retries -= 1 last_exception = msg response = self.getTransaction(request.transaction_id) if not response: last_exception = last_exception or ( "No Response " "received from the remote unit") response = ModbusIOException(last_exception) return response
def calculateRtuFrameSize(cls, buffer): ''' Calculates the size of the message :param buffer: A buffer containing the data that have been received. :returns: The number of bytes in the response. ''' hi_byte = byte2int(buffer[2]) lo_byte = byte2int(buffer[3]) return (hi_byte << 16) + lo_byte + 6
def calculateRtuFrameSize(cls, data): ''' Calculates the size of the message :param data: A buffer containing the data that have been received. :returns: The number of bytes in the response. ''' hi_byte = byte2int(data[2]) lo_byte = byte2int(data[3]) return (hi_byte << 16) + lo_byte + 6
def dataReceived(self, data): ''' Callback when we receive any data :param data: The data sent by the client ''' if _logger.isEnabledFor(logging.DEBUG): _logger.debug(' '.join([hex(byte2int(x)) for x in data])) if not self.factory.control.ListenOnly: unit_address = byte2int(data[0]) if unit_address in self.factory.store: self.framer.processIncomingPacket(data, self._execute)
def decode(self, data): ''' Decodes a the response Since the identifier is device dependent, we just return the raw value that a user can decode to whatever it should be. :param data: The packet data to decode ''' self.byte_count = byte2int(data[0]) self.identifier = data[1:self.byte_count + 1] status = byte2int(data[-1]) self.status = status == ModbusStatus.SlaveOn
def hexlify_packets(packet): """ Returns hex representation of bytestring recieved :param packet: :return: """ if not packet: return '' if IS_PYTHON3: return " ".join([hex(byte2int(x)) for x in packet]) else: return u" ".join([hex(byte2int(x)) for x in packet])
def checkFrame(self): ''' Check if the next frame is available. Return True if we were successful. ''' try: self.populateHeader() frame_size = self.__header['len'] data = self.__buffer[:frame_size - 2] crc = self.__buffer[frame_size - 2:frame_size] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) return checkCRC(data, crc_val) except (IndexError, KeyError): return False
def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' length = byte2int(data[0]) status = struct.unpack('>H', data[1:3])[0] self.status = (status == ModbusStatus.Ready) self.event_count = struct.unpack('>H', data[3:5])[0] self.message_count = struct.unpack('>H', data[5:7])[0] self.events = [] for e in range(7, length + 1): self.events.append(byte2int(data[e]))
def execute(self, request): ''' Starts the producer to send the next request to consumer.write(Frame(request)) ''' retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) self.client.framer.resetFrame() expected_response_length = None if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() if isinstance(self.client.framer, ModbusAsciiFramer): response_pdu_size = response_pdu_size * 2 if response_pdu_size: expected_response_length = self._calculate_response_length(response_pdu_size) while retries > 0: try: last_exception = None self.client.connect() packet = self.client.framer.buildPacket(request) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self._send(packet) # exception = False result = self._recv(expected_response_length or 1024) if not result and self.retry_on_empty: retries -= 1 continue if _logger.isEnabledFor(logging.DEBUG): _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result])) self.client.framer.processIncomingPacket(result, self.addTransaction) break except (socket.error, ModbusIOException, InvalidMessageRecievedException) as msg: self.client.close() _logger.debug("Transaction failed. (%s) " % msg) retries -= 1 last_exception = msg response = self.getTransaction(request.transaction_id) if not response: if len(self.transactions): response = self.getTransaction(tid=0) else: last_exception = last_exception or ("No Response " "received from the remote unit") response = ModbusIOException(last_exception) return response
def _check_response(self, response): ''' Checks if the response is a Modbus Exception. ''' if isinstance(self.client.framer, ModbusSocketFramer): if len(response) >= 8 and byte2int(response[7]) > 128: return False elif isinstance(self.client.framer, ModbusAsciiFramer): if len(response) >= 5 and int(response[3:5], 16) > 128: return False elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)): if len(response) >= 2 and byte2int(response[1]) > 128: return False return True
def checkFrame(self): ''' Check if the next frame is available. Return True if we were successful. ''' try: self.populateHeader() frame_size = self._header['len'] data = self._buffer[:frame_size - 2] crc = self._buffer[frame_size - 2:frame_size] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) return checkCRC(data, crc_val) except (IndexError, KeyError): return False
def handle(self): '''Callback when we receive any data, until self.running becomes not True. Blocks indefinitely awaiting data. If shutdown is required, then the global socket.settimeout(<seconds>) may be used, to allow timely checking of self.running. However, since this also affects socket connects, if there are outgoing socket connections used in the same program, then these will be prevented, if the specfied timeout is too short. Hence, this is unreliable. To respond to Modbus...Server.server_close() (which clears each handler's self.running), derive from this class to provide an alternative handler that awakens from time to time when no input is available and checks self.running. Use Modbus...Server( handler=... ) keyword to supply the alternative request handler class. ''' while self.running: try: data = self.request.recv(1024) if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug(' '.join([hex(byte2int(x)) for x in data])) # if not self.server.control.ListenOnly: self.framer.processIncomingPacket(data, self.execute) except socket.timeout as msg: if _logger.isEnabledFor(logging.DEBUG): _logger.debug("Socket timeout occurred %s", msg) pass except socket.error as msg: _logger.error("Socket error occurred %s" % msg) self.running = False except: _logger.error("Socket exception occurred %s" % traceback.format_exc() ) self.running = False
def handle(self): ''' Callback when we receive any data ''' reset_frame = False while self.running: try: data, self.socket = self.request if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug(' '.join([hex(byte2int(x)) for x in data])) # if not self.server.control.ListenOnly: self.framer.processIncomingPacket(data, self.execute) except socket.timeout: pass except socket.error as msg: _logger.error("Socket error occurred %s" % msg) self.running = False reset_frame = True except Exception as msg: _logger.error(msg) self.running = False reset_frame = True finally: if reset_frame: self.framer.resetFrame() reset_frame = False
def _helper(self, data): """ This factory is used to generate the correct request object from a valid request packet. This decodes from a list of the currently implemented request types. :param data: The request packet to decode :returns: The decoded request or illegal function request object """ function_code = byte2int(data[0]) request = self.__lookup.get(function_code, lambda: None)() if not request: _logger.debug("Factory Request[%d]" % function_code) request = IllegalFunctionRequest(function_code) else: fc_string = "%s: %s" % ( str(self.__lookup[function_code]).split('.')[-1].rstrip( "'>"), function_code ) _logger.debug("Factory Request[%s]" % fc_string) request.decode(data[1:]) if hasattr(request, 'sub_function_code'): lookup = self.__sub_lookup.get(request.function_code, {}) subtype = lookup.get(request.sub_function_code, None) if subtype: request.__class__ = subtype return request
def processIncomingPacket(self, data, callback): ''' The new packet processing pattern This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That is, checks for complete messages, and once found, will process all that exist. This handles the case when we read N + 1 or 1 / N messages at a time instead of 1. The processed and decoded messages are pushed to the callback function to process and send. :param data: The new packet data :param callback: The function to send results to ''' _logger.debug(' '.join([hex(byte2int(x)) for x in data])) self.addToFrame(data) while self.isFrameReady(): if self.checkFrame(): result = self.decoder.decode(self.getFrame()) if result is None: raise ModbusIOException("Unable to decode request") self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread? else: break
def _helper(self, data, client_address=None): ''' This factory is used to generate the correct request object from a valid request packet. This decodes from a list of the currently implemented request types. :param data: The request packet to decode :returns: The decoded request or illegal function request object ''' function_code = byte2int(data[0]) if client_address != None : extra = { 'ip_source' : client_address[0], 'port_source' : client_address[1], 'fonction_code' : function_code, 'requete' : str(data) } else : extra = { 'fonction_code' : function_code, 'requete' : str(data) } #_logger.debug("test:"+ str(data)) _logger.debug("traité " + str(extra),extra=extra) request = self.__lookup.get(function_code, lambda: None)() if not request: request = IllegalFunctionRequest(function_code) request.decode(data[1:]) if hasattr(request, 'sub_function_code'): lookup = self.__sub_lookup.get(request.function_code, {}) subtype = lookup.get(request.sub_function_code, None) if subtype: request.__class__ = subtype return request
def handle(self): ''' Callback when we receive any data ''' reset_frame = False while self.running: try: data, self.request = self.request if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug(' '.join([hex(byte2int(x)) for x in data])) # if not self.server.control.ListenOnly: self.framer.processIncomingPacket(data, self.execute) except socket.timeout: pass except socket.error as msg: _logger.error("Socket error occurred %s" % msg) self.running = False reset_frame = True except Exception as msg: _logger.error(msg) self.running = False reset_frame = True finally: if reset_frame: self.framer.resetFrame() reset_frame = False
def _helper(self, data): ''' This factory is used to generate the correct response object from a valid response packet. This decodes from a list of the currently implemented request types. :param data: The response packet to decode :returns: The decoded request or an exception response object ''' function_code = byte2int(data[0]) _logger.debug("Factory Response[%d]" % function_code) response = self.__lookup.get(function_code, lambda: None)() if function_code > 0x80: code = function_code & 0x7f # strip error portion response = ExceptionResponse(code, ecode.IllegalFunction) if not response: raise ModbusException("Unknown response %d" % function_code) response.decode(data[1:]) if hasattr(response, 'sub_function_code'): lookup = self.__sub_lookup.get(response.function_code, {}) subtype = lookup.get(response.sub_function_code, None) if subtype: response.__class__ = subtype return response
def decode(self, data): ''' Decodes response pdu :param data: The packet data to decode ''' self.byte_count = byte2int(data[0]) self.bits = unpack_bitstring(data[1:])
def _helper(self, data): ''' This factory is used to generate the correct response object from a valid response packet. This decodes from a list of the currently implemented request types. :param data: The response packet to decode :returns: The decoded request or an exception response object ''' fc_string = function_code = byte2int(data[0]) if function_code in self.__lookup: fc_string = "%s: %s" % ( str(self.__lookup[function_code]).split('.')[-1].rstrip("'>"), function_code ) _logger.debug("Factory Response[%s]" % fc_string) response = self.__lookup.get(function_code, lambda: None)() if function_code > 0x80: code = function_code & 0x7f # strip error portion response = ExceptionResponse(code, ecode.IllegalFunction) if not response: raise ModbusException("Unknown response %d" % function_code) response.decode(data[1:]) if hasattr(response, 'sub_function_code'): lookup = self.__sub_lookup.get(response.function_code, {}) subtype = lookup.get(response.sub_function_code, None) if subtype: response.__class__ = subtype return response
def handle(self): '''Callback when we receive any data, until self.running becomes not True. Blocks indefinitely awaiting data. If shutdown is required, then the global socket.settimeout(<seconds>) may be used, to allow timely checking of self.running. However, since this also affects socket connects, if there are outgoing socket connections used in the same program, then these will be prevented, if the specfied timeout is too short. Hence, this is unreliable. To respond to Modbus...Server.server_close() (which clears each handler's self.running), derive from this class to provide an alternative handler that awakens from time to time when no input is available and checks self.running. Use Modbus...Server( handler=... ) keyword to supply the alternative request handler class. ''' while self.running: try: data = self.request.recv(1024) if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug(' '.join([hex(byte2int(x)) for x in data])) # if not self.server.control.ListenOnly: self.framer.processIncomingPacket(data, self.execute) except socket.timeout as msg: if _logger.isEnabledFor(logging.DEBUG): _logger.debug("Socket timeout occurred %s", msg) pass except socket.error as msg: _logger.error("Socket error occurred %s" % msg) self.running = False except: _logger.error("Socket exception occurred %s" % traceback.format_exc()) self.running = False
def execute(self, request): ''' Starts the producer to send the next request to consumer.write(Frame(request)) ''' retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() expected_response_length = self._calculate_response_length( response_pdu_size) else: expected_response_length = 1024 while retries > 0: try: self.client.connect() self.client._send(self.client.framer.buildPacket(request)) result = self.client._recv(expected_response_length) if not result and self.retry_on_empty: retries -= 1 continue if _logger.isEnabledFor(logging.DEBUG): _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result])) self.client.framer.processIncomingPacket( result, self.addTransaction) break except socket.error as msg: self.client.close() _logger.debug("Transaction failed. (%s) " % msg) retries -= 1 return self.getTransaction(request.transaction_id)
def _helper(self, data): """ This factory is used to generate the correct request object from a valid request packet. This decodes from a list of the currently implemented request types. :param data: The request packet to decode :returns: The decoded request or illegal function request object """ function_code = byte2int(data[0]) request = self.__lookup.get(function_code, lambda: None)() if not request: log.event("Modbus, Illegal Function Request[%d]" % function_code) request = IllegalFunctionRequest(function_code) else: fc_string = "%s: %s" % (str( self.__lookup[function_code]).split('.')[-1].rstrip("'>"), function_code) log.event("Modbus, Request[%s]" % fc_string) request.decode(data[1:]) if hasattr(request, 'sub_function_code'): lookup = self.__sub_lookup.get(request.function_code, {}) subtype = lookup.get(request.sub_function_code, None) if subtype: request.__class__ = subtype return request
def populateHeader(self): ''' Try to set the headers `uid`, `len` and `crc`. This method examines `self._buffer` and writes meta information into `self._header`. It calculates only the values for headers that are not already in the dictionary. Beware that this method will raise an IndexError if `self._buffer` is not yet long enough. ''' self._header['uid'] = byte2int(self._buffer[0]) func_code = byte2int(self._buffer[1]) pdu_class = self.decoder.lookupPduClass(func_code) size = pdu_class.calculateRtuFrameSize(self._buffer) self._header['len'] = size self._header['crc'] = self._buffer[size - 2:size]
def processIncomingPacket(self, data, callback): ''' The new packet processing pattern This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That is, checks for complete messages, and once found, will process all that exist. This handles the case when we read N + 1 or 1 / N messages at a time instead of 1. The processed and decoded messages are pushed to the callback function to process and send. :param data: The new packet data :param callback: The function to send results to ''' _logger.debug(' '.join([hex(byte2int(x)) for x in data])) self.addToFrame(data) while True: if self.isFrameReady(): if self.checkFrame(): self._process(callback) else: self.resetFrame() else: if len(self._buffer): # Possible error ??? if self._header['len'] < 2: self._process(callback, error=True) break
def processIncomingPacket(self, data, callback): ''' The new packet processing pattern This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That is, checks for complete messages, and once found, will process all that exist. This handles the case when we read N + 1 or 1 / N messages at a time instead of 1. The processed and decoded messages are pushed to the callback function to process and send. :param data: The new packet data :param callback: The function to send results to ''' _logger.debug(' '.join([hex(byte2int(x)) for x in data])) self.addToFrame(data) while True: if self.isFrameReady(): if self.checkFrame(): self._process(callback) else: self.resetFrame() else: if len(self.__buffer): # Possible error ??? if self.__header['len'] < 2: self._process(callback, error=True) break
def populateHeader(self): ''' Try to set the headers `uid`, `len` and `crc`. This method examines `self.__buffer` and writes meta information into `self.__header`. It calculates only the values for headers that are not already in the dictionary. Beware that this method will raise an IndexError if `self.__buffer` is not yet long enough. ''' self.__header['uid'] = byte2int(self.__buffer[0]) func_code = byte2int(self.__buffer[1]) pdu_class = self.decoder.lookupPduClass(func_code) size = pdu_class.calculateRtuFrameSize(self.__buffer) self.__header['len'] = size self.__header['crc'] = self.__buffer[size - 2:size]
def execute(self, request=None): """ Executes a transaction :param request: Request to be written on to the bus :return: """ request.transaction_id = self.transaction.getNextTID() def callback(*args): LOGGER.debug("in callback - {}".format(request.transaction_id)) while True: waiting = self.stream.connection.in_waiting if waiting: data = self.stream.connection.read(waiting) LOGGER.debug( "recv: " + " ".join([hex(byte2int(x)) for x in data])) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket( data, self._handle_response, unit, tid=request.transaction_id ) break packet = self.framer.buildPacket(request) LOGGER.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.stream.write(packet, callback=callback) f = self._build_response(request.transaction_id) return f
def decode(self, data): ''' Decode the register response packet :param data: The response to decode ''' bytecount = byte2int(data[0]) for i in range(1, bytecount, 2): self.registers.append(struct.unpack('>H', data[i:i + 2])[0])
def decode(self, data): """ Decodes response pdu :param data: The packet data to decode """ byte_count = byte2int(data[0]) self.values = [] for i in range(1, byte_count + 1, 2): self.values.append(struct.unpack('>H', data[i:i + 2])[0])
def _dataReceived(self, data): ''' Get response, check for valid message, decode result :param data: The data returned from the server ''' _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in data])) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit)
def checkFrame(self): """ Check if the next frame is available. Return True if we were successful. 1. Populate header 2. Discard frame if UID does not match """ try: self.populateHeader() frame_size = self._header['len'] data = self._buffer[:frame_size - 2] crc = self._buffer[frame_size - 2:frame_size] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) return checkCRC(data, crc_val) except (IndexError, KeyError): return False
def checkFrame(self): """ Check if the next frame is available. Return True if we were successful. 1. Populate header 2. Discard frame if UID does not match """ try: self.populateHeader() frame_size = self._header['len'] data = self._buffer[:frame_size - 2] crc = self._header['crc'] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) return checkCRC(data, crc_val) except (IndexError, KeyError, struct.error): return False
def hexlify_packets(packet): """ Returns hex representation of bytestring recieved :param packet: :return: """ if not packet: return '' return " ".join([hex(byte2int(x)) for x in packet])
def execute(self, request): ''' Starts the producer to send the next request to consumer.write(Frame(request)) ''' retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) expected_response_length = None if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() if response_pdu_size: expected_response_length = self._calculate_response_length(response_pdu_size) while retries > 0: try: self.client.connect() packet = self.client.framer.buildPacket(request) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.client._send(packet) exception = False result = self.client._recv(expected_response_length or 1024) while result and expected_response_length and len(result) < expected_response_length: if not exception and not self._check_response(result): exception = True expected_response_length = self._calculate_exception_length() continue result += self.client._recv(expected_response_length - len(result)) if not result and self.retry_on_empty: retries -= 1 continue if _logger.isEnabledFor(logging.DEBUG): _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result])) self.client.framer.processIncomingPacket(result, self.addTransaction) break except socket.error as msg: self.client.close() _logger.debug("Transaction failed. (%s) " % msg) retries -= 1 return self.getTransaction(request.transaction_id)
def execute(self, request, **kwargs): """ Starts the producer to send the next request to consumer.write(Frame(request)) """ request.transaction_id = self.transaction.getNextTID() packet = self.framer.buildPacket(request) _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.transport.write(packet) return self._buildResponse(request.transaction_id)
def datagramReceived(self, data, addr): ''' Callback when we receive any data :param data: The data sent by the client ''' _logger.debug("Client Connected [%s]" % addr) if _logger.isEnabledFor(logging.DEBUG): _logger.debug(" ".join([hex(byte2int(x)) for x in data])) if not self.control.ListenOnly: continuation = lambda request: self._execute(request, addr) self.framer.processIncomingPacket(data, continuation)
def execute(self, request=None): """ Executes a transaction :param request: :return: """ request.transaction_id = self.transaction.getNextTID() packet = self.framer.buildPacket(request) LOGGER.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.stream.write(packet) return self._build_response(request.transaction_id)
def populateHeader(self, data=None): """ Try to set the headers `uid`, `len` and `crc`. This method examines `self._buffer` and writes meta information into `self._header`. Beware that this method will raise an IndexError if `self._buffer` is not yet long enough. """ data = data if data is not None else self._buffer self._header['uid'] = byte2int(data[0]) func_code = byte2int(data[1]) pdu_class = self.decoder.lookupPduClass(func_code) size = pdu_class.calculateRtuFrameSize(data) self._header['len'] = size if len(data) < size: # crc yet not available raise IndexError self._header['crc'] = data[size - 2:size]
def unpack_bitstring(string): """ Creates bit array out of a string :param string: The modbus data packet to decode example:: bytes = 'bytes to decode' result = unpack_bitstring(bytes) """ byte_count = len(string) bits = [] for byte in range(byte_count): if IS_PYTHON3: value = byte2int(int(string[byte])) else: value = byte2int(string[byte]) for _ in range(8): bits.append((value & 1) == 1) value >>= 1 return bits
def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' self.records = [] byte_count = byte2int(data[0]) for count in range(1, byte_count, 7): decoded = struct.unpack('>BHHH', data[count:count+7]) record = FileRecord(file_number=decoded[1], record_number=decoded[2], record_length=decoded[3]) if decoded[0] == 0x06: self.records.append(record)
def on_receive(self, *args): """ On data recieve call back :param args: data received :return: """ data = args[0] if len(args) > 0 else None if not data: return LOGGER.debug("recv: " + " ".join([hex(byte2int(x)) for x in data])) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handle_response, unit=unit)
def checkFrame(self): """ Check if the next frame is available. Return True if we were successful. 1. Populate header 2. Discard frame if UID does not match """ try: self.populateHeader() frame_size = self._header['len'] data = self._buffer[:frame_size - 2] crc = self._buffer[frame_size - 2:frame_size] crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) if checkCRC(data, crc_val): return True else: _logger.debug("CRC invalid, discarding header!!") self.resetFrame() return False except (IndexError, KeyError, struct.error): return False
def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' count, self.records = 1, [] byte_count = byte2int(data[0]) while count < byte_count: response_length, reference_type = struct.unpack('>BB', data[count:count+2]) count += response_length + 1 # the count is not included record = FileRecord(response_length=response_length, record_data=data[count - response_length + 1:count]) if reference_type == 0x06: self.records.append(record)
def computeLRC(data): """ Used to compute the longitudinal redundancy check against a string. This is only used on the serial ASCII modbus protocol. A full description of this implementation can be found in appendex B of the serial line modbus description. :param data: The data to apply a lrc to :returns: The calculated LRC """ lrc = sum(byte2int(a) for a in data) & 0xff lrc = (lrc ^ 0xff) + 1 return lrc & 0xff