def testExecute(self): client = MagicMock() client.framer = self._ascii client.framer._buffer = b'deadbeef' client.framer.processIncomingPacket = MagicMock() client.framer.processIncomingPacket.return_value = None client.framer.buildPacket = MagicMock() client.framer.buildPacket.return_value = b'deadbeef' client.framer.sendPacket = MagicMock() client.framer.sendPacket.return_value = len(b'deadbeef') request = MagicMock() request.get_response_pdu_size.return_value = 10 request.unit_id = 1 tm = ModbusTransactionManager(client) tm._recv = MagicMock(return_value=b'abcdef') self.assertEqual(tm.retries, 3) self.assertEqual(tm.retry_on_empty, False) # tm._transact = MagicMock() # some response # tm._transact.return_value = (b'abcdef', None) tm.getTransaction = MagicMock() tm.getTransaction.return_value = 'response' response = tm.execute(request) self.assertEqual(response, 'response') # No response tm._recv = MagicMock(return_value=b'abcdef') # tm._transact.return_value = (b'', None) tm.transactions = [] tm.getTransaction = MagicMock() tm.getTransaction.return_value = None response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # No response with retries tm.retry_on_empty = True tm._recv = MagicMock(side_effect=iter([b'', b'abcdef'])) # tm._transact.side_effect = [(b'', None), (b'abcdef', None)] response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # retry on invalid response tm.retry_on_invalid = True tm._recv = MagicMock(side_effect=iter([b'', b'abcdef', b'deadbe', b'123456'])) # tm._transact.side_effect = [(b'', None), (b'abcdef', None)] response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # Unable to decode response tm._recv = MagicMock(side_effect=ModbusIOException()) # tm._transact.side_effect = [(b'abcdef', None)] client.framer.processIncomingPacket.side_effect = MagicMock(side_effect=ModbusIOException()) self.assertIsInstance(tm.execute(request), ModbusIOException)
def run(self): """ тут производится обновление """ if self._logger is not None: self._logger.info("[{n}]: Попытка запуска испытания".format(n=self.name)) try: self.reset() except ConnectionException: if self._logger is not None: self._logger.error("[{n}]: Невозможно подключиться к хосту [{h}]".format(n=self.name, h=self._host)) raise ConnectionException("Не удалось подключиться к хосту {h}".format(h=self._host)) if self._logger is not None: self._logger.info("[{n}]: Испытание запущено".format(n=self.name)) try: while not self.__exit: self._update() time.sleep(self._invfreq) except AttributeError: if self._logger is not None: self._logger.error("[{n}]: Связь была прервана, необходим перезапуск испытания".format(n=self.name)) raise ModbusIOException("Связь была оборвана") except ConnectionException: if self._logger is not None: self._logger.error("[{n}]: Невозможно подключиться к хосту [{h}]".format(n=self.name, h=self._host)) raise ConnectionException("Не удалось подключиться к хосту {h}".format(h=self._host))
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 ''' self.addToFrame(data) while self.isFrameReady(): if self.checkFrame(): result = self.decoder.decode(self.getFrame()) if result is None: raise ModbusIOException("Unable to decode response") self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread? else: break
def sync_write(self, value: int | float | str, obj: ModbusObj) -> None: """Write value to Modbus object. Args: value: Value to write obj: Object instance. """ if obj.func_write is None: raise ModbusException("Object cannot be overwritten") if isinstance(value, str): raise NotImplementedError( "Modbus expected numbers to write. Got `str`.") payload = self._build_payload(value=value, obj=obj) if obj.func_write is ModbusWriteFunc.WRITE_REGISTER and isinstance( payload, list): # FIXME: hotfix assert len(payload) == 1 payload = payload[0] # type: ignore request = self.write_funcs[obj.func_write]( obj.address, payload, unit=self._device_obj.property_list.rtu.unit, # type: ignore ) if request.isError(): raise ModbusIOException("0x80") # todo: resp.string self._LOG.debug("Successfully write", extra={ "object": obj, "value": value })
def execute(self, request): _logger.info("AsyncFifoTransactionManager.execute(%s)", str(request)) self.request = request self.request.transaction_id = self.getNextTID() self.frame = self.client.framer.buildPacket(self.request) try: self.recvsize = self.client.framer.getResponseSize(self.request) except NotImplementedException: self.recvsize = 0 time_since_last_read = time.time() - self.client._last_frame_end _logger.info(" time_since_last_read: %f, _silent_interval: %f", time_since_last_read, self.client._silent_interval) if time_since_last_read < self.client._silent_interval: _logger.debug(" will delay to wait for 3.5 char") self.client.ioloop.add_timeout( datetime.timedelta(seconds=(self.client._silent_interval - time_since_last_read)), partial(self._sendAsyncRequest, self.frame)) else: try: self._sendSyncRequest(self.frame) except socket.error as ex: self.delTransaction(self.request.transaction_id) raise ModbusIOException(str(ex)) return self._buildResponse(self.request.transaction_id)
def _sendPacket(self, message, callback): """ Sends packets on the bus with 3.5char delay between frames :param message: Message to be sent over the bus :return: """ @gen.coroutine def sleep(timeout): yield gen.sleep(timeout) try: waiting = self.stream.connection.in_waiting if waiting: result = self.stream.connection.read(waiting) LOGGER.info("Cleanup recv buffer before send: " + hexlify_packets(result)) except OSError as e: self.transaction.getTransaction( message.transaction_id).set_exception(ModbusIOException(e)) return start = time.time() if self.last_frame_end: waittime = self.last_frame_end + self.silent_interval - start if waittime > 0: LOGGER.debug("Waiting for 3.5 char before next send - %f ms", waittime) sleep(waittime) self.state = ModbusTransactionState.SENDING LOGGER.debug("send: " + hexlify_packets(message)) self.stream.write(message, callback)
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 test_read_slave_registers_on_ModbusIOException_exits(self, MockExit): slave_id = 1 self.mock_client.read_holding_registers.side_effect = ModbusIOException( ) self.reader.read_slave(slave_id) MockExit.assert_called()
def _process(self, callback, error=False): """ Process incoming packets irrespective error condition """ data = self.getRawFrame() if error else self.getFrame() result = self.decoder.decode(data) if result is None: raise ModbusIOException("Unable to decode request") self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread?
def _process(self, callback, error=False, client_address=None): """ Process incoming packets irrespective error condition """ data = self.getRawFrame() if error else self.getFrame() result = self.decoder.decode(data, client_address) if result is None: raise ModbusIOException("Unable to decode request") elif error and result.function_code < 0x80: raise InvalidMessageRecievedException(result) else: self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread?
def processIncomingPacket(self, data, callback): _logger.info("AsyncModbusRtuFramer.processIncomingPacket(%s)", str(data)) _logger.debug(" FRAME READY? " + str(self.isFrameReady())) while self.isFrameReady(): _logger.debug(" FRAME CHECK? " + str(self.checkFrame())) if self.checkFrame(): result = self.decoder.decode(self.getFrame()) if result is None: raise ModbusIOException("Unable to decode response") self.populateResult(result) self.advanceFrame() self.resetFrame() callback(result) # defer or push to a thread? return else: self.resetFrame() # clear possible errors
def sync_read(self, obj: ModbusObj) -> ModbusObj: """Read data from Modbus object. Updates object and return value. """ resp = self.read_funcs[obj.func_read]( address=obj.address, count=obj.quantity, unit=self._device_obj.property_list.rtu.unit, # type: ignore ) if resp.isError(): obj.set_property(value=ModbusIOException(str(resp))) else: value = self._decode_response(resp=resp, obj=obj) obj.set_property(value=value) return obj
def processIncomingPacket(self, data, callback, unit, **kwargs): """ 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 :param unit: Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or single unit id(client/server) :param single: True or False (If True, ignore unit address validation) """ self.addToFrame(data) if not isinstance(unit, (list, tuple)): unit = [unit] single = kwargs.get('single', False) while self.isFrameReady(): if self.checkFrame(): if self._validate_unit_id(unit, single): result = self.decoder.decode(self.getFrame()) if result is None: raise ModbusIOException("Unable to decode response") self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread? else: _logger.debug("Not a valid unit id - {}, " "ignoring!!".format(self._header['uid'])) self.resetFrame() break else: _logger.debug("Frame check failed, ignoring!!") self.resetFrame() break
def testTcpExecuteFailure(self): protocol = ModbusTcpProtocol() protocol.store = MagicMock() request = MagicMock() protocol._send = MagicMock() # CASE-1: test NoSuchSlaveException exceptions request.execute.side_effect = NoSuchSlaveException() self.assertRaises(NoSuchSlaveException, protocol._execute(request)) self.assertTrue(request.doException.called) # CASE-2: NoSuchSlaveException with ignore_missing_slaves = true protocol.ignore_missing_slaves = True request.execute.side_effect = NoSuchSlaveException() self.assertEqual(protocol._execute(request), None) # test other exceptions request.execute.side_effect = ModbusIOException() self.assertRaises(ModbusIOException, protocol._execute(request)) self.assertTrue(protocol._send.called)
def _on_timeout(): LOGGER.warning("timeout") _clear_timer() if self.stream: self.io_loop.remove_handler(self.stream.fileno()) self.framer.resetFrame() transaction = self.transaction.getTransaction(request.transaction_id) if self.state != ModbusTransactionState.IDLE: self.state = ModbusTransactionState.IDLE if self.stream: try: waiting = self.stream.connection.in_waiting if waiting: result = self.stream.connection.read(waiting) LOGGER.info( "Cleanup recv buffer after timeout: " + hexlify_packets(result)) except OSError as ex: self.close() if transaction: transaction.set_exception(ModbusIOException(ex)) return if transaction: transaction.set_exception(TimeOutException())
def _on_fd_error(fd, *args): _clear_timer() self.io_loop.remove_handler(fd) self.close() self.transaction.getTransaction( request.transaction_id).set_exception(ModbusIOException(*args))
def testExecute(self, mock_time): mock_time.time.side_effect = count() client = MagicMock() client.framer = self._ascii client.framer._buffer = b'deadbeef' client.framer.processIncomingPacket = MagicMock() client.framer.processIncomingPacket.return_value = None client.framer.buildPacket = MagicMock() client.framer.buildPacket.return_value = b'deadbeef' client.framer.sendPacket = MagicMock() client.framer.sendPacket.return_value = len(b'deadbeef') client.framer.decode_data = MagicMock() client.framer.decode_data.return_value = { "unit": 1, "fcode": 222, "length": 27 } request = MagicMock() request.get_response_pdu_size.return_value = 10 request.unit_id = 1 request.function_code = 222 tm = ModbusTransactionManager(client) tm._recv = MagicMock(return_value=b'abcdef') self.assertEqual(tm.retries, 3) self.assertEqual(tm.retry_on_empty, False) # tm._transact = MagicMock() # some response # tm._transact.return_value = (b'abcdef', None) tm.getTransaction = MagicMock() tm.getTransaction.return_value = 'response' response = tm.execute(request) self.assertEqual(response, 'response') # No response tm._recv = MagicMock(return_value=b'abcdef') # tm._transact.return_value = (b'', None) tm.transactions = [] tm.getTransaction = MagicMock() tm.getTransaction.return_value = None response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # No response with retries tm.retry_on_empty = True tm._recv = MagicMock(side_effect=iter([b'', b'abcdef'])) # tm._transact.side_effect = [(b'', None), (b'abcdef', None)] response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # wrong handle_local_echo tm._recv = MagicMock( side_effect=iter([b'abcdef', b'deadbe', b'123456'])) client.handle_local_echo = True tm.retry_on_empty = False tm.retry_on_invalid = False self.assertEqual( tm.execute(request).message, '[Input/Output] Wrong local echo') client.handle_local_echo = False # retry on invalid response tm.retry_on_invalid = True tm._recv = MagicMock( side_effect=iter([b'', b'abcdef', b'deadbe', b'123456'])) # tm._transact.side_effect = [(b'', None), (b'abcdef', None)] response = tm.execute(request) self.assertIsInstance(response, ModbusIOException) # Unable to decode response tm._recv = MagicMock(side_effect=ModbusIOException()) # tm._transact.side_effect = [(b'abcdef', None)] client.framer.processIncomingPacket.side_effect = MagicMock( side_effect=ModbusIOException()) self.assertIsInstance(tm.execute(request), ModbusIOException) # Broadcast client.broadcast_enable = True request.unit_id = 0 response = tm.execute(request) self.assertEqual(response, b'Broadcast write sent - ' b'no response expected')
def execute(self, request): """ Starts the producer to send the next request to consumer.write(Frame(request)) """ with self._transaction_lock: try: _logger.debug("Current transaction state - {}".format( ModbusTransactionState.to_string(self.client.state)) ) retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction " "{}".format(request.transaction_id)) _buffer = hexlify_packets(self.client.framer._buffer) if _buffer: _logger.debug("Clearing current Frame " ": - {}".format(_buffer)) self.client.framer.resetFrame() broadcast = (self.client.broadcast_enable and request.unit_id == 0) if broadcast: self._transact(request, None, broadcast=True) response = b'Broadcast write sent - no response expected' else: expected_response_length = None if not isinstance(self.client.framer, ModbusSocketFramer): 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) if request.unit_id in self._no_response_devices: full = True else: full = False c_str = str(self.client) if "modbusudpclient" in c_str.lower().strip(): full = True if not expected_response_length: expected_response_length = Defaults.ReadSize response, last_exception = self._transact( request, expected_response_length, full=full, broadcast=broadcast ) if not response and ( request.unit_id not in self._no_response_devices): self._no_response_devices.append(request.unit_id) elif request.unit_id in self._no_response_devices and response: self._no_response_devices.remove(request.unit_id) if not response and self.retry_on_empty and retries: while retries > 0: if hasattr(self.client, "state"): _logger.debug("RESETTING Transaction state to " "'IDLE' for retry") self.client.state = ModbusTransactionState.IDLE _logger.debug("Retry on empty - {}".format(retries)) response, last_exception = self._transact( request, expected_response_length ) if not response: retries -= 1 continue # Remove entry self._no_response_devices.remove(request.unit_id) break addTransaction = partial(self.addTransaction, tid=request.transaction_id) self.client.framer.processIncomingPacket(response, addTransaction, request.unit_id) 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" "/Unable to decode response") response = ModbusIOException(last_exception, request.function_code) if hasattr(self.client, "state"): _logger.debug("Changing transaction state from " "'PROCESSING REPLY' to " "'TRANSACTION_COMPLETE'") self.client.state = ( ModbusTransactionState.TRANSACTION_COMPLETE) return response except ModbusIOException as ex: # Handle decode errors in processIncomingPacket method _logger.exception(ex) self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE return ex
def _on_receive(fd, events): LOGGER.debug("_on_receive: %s, %s", fd, events) try: waiting = self.stream.connection.in_waiting if waiting: LOGGER.debug("waiting = %d", waiting) data = self.stream.connection.read(waiting) LOGGER.debug( "recv: " + hexlify_packets(data)) except OSError as ex: _clear_timer() self.close() self.transaction.getTransaction(request.transaction_id).set_exception(ModbusIOException(ex)) return self.framer.addToFrame(data) # check if we have regular frame or modbus exception fcode = self.framer.decode_data(self.framer.getRawFrame()).get("fcode", 0) if fcode and ( (fcode > 0x80 and len(self.framer.getRawFrame()) == exception_response_length) or (len(self.framer.getRawFrame()) == expected_response_length) ): _clear_timer() self.io_loop.remove_handler(fd) self.state = ModbusTransactionState.IDLE self.framer.processIncomingPacket( b'', # already sent via addToFrame() self._handle_response, 0, # don't care for `single=True` single=True, tid=request.transaction_id )