def __init__(self, config: conf.ConfigType): super().__init__(config=conf.CONFIG_SCHEMA(config)) self._znp = None # It's easier to deal with this if it's never None self._reconnect_task = asyncio.Future() self._reconnect_task.cancel()
async def probe(cls, device_config: conf.ConfigType) -> bool: znp = ZNP(conf.CONFIG_SCHEMA({conf.CONF_DEVICE: device_config})) LOGGER.debug("Probing %s", znp._port_path) try: await znp.connect() return True except Exception as e: LOGGER.warning("Failed to probe ZNP radio with config %s", device_config, exc_info=e) return False finally: znp.close()
async def probe(cls, device_config: conf.ConfigType) -> bool: new_schema = conf.CONFIG_SCHEMA({ conf.CONF_DEVICE: device_config, conf.CONF_ZNP_CONFIG: { conf.CONF_AUTO_RECONNECT: False }, }) znp = ZNP(new_schema) LOGGER.debug("Probing %s", znp._port_path) try: await znp.connect() return True except Exception: return False finally: znp.close()
def config_for_port_path(path): return conf.CONFIG_SCHEMA( {conf.CONF_DEVICE: { conf.CONF_DEVICE_PATH: path }})
async def test_api_reconnect(event_loop, mocker): SREQ_TIMEOUT = 0.2 port_path = "/dev/ttyUSB1" config = conf.CONFIG_SCHEMA( { conf.CONF_DEVICE: {conf.CONF_DEVICE_PATH: port_path}, conf.CONF_ZNP_CONFIG: { conf.CONF_SREQ_TIMEOUT: SREQ_TIMEOUT, conf.CONF_AUTO_RECONNECT_RETRY_DELAY: 0.01, }, } ) transport = mocker.Mock() def dummy_serial_conn(loop, protocol_factory, url, *args, **kwargs): fut = loop.create_future() assert url == port_path protocol = protocol_factory() protocol.connection_made(transport) fut.set_result((transport, protocol)) return fut mocker.patch("serial_asyncio.create_serial_connection", new=dummy_serial_conn) mocker.patch("zigpy_znp.uart.connect", wraps=zigpy_znp.uart.connect) app = mocker.Mock() app.startup = Mock(return_value=asyncio.sleep(0)) api = ZNP(config) api.set_application(app) connect_fut = event_loop.create_future() connect_task = asyncio.create_task(api.connect()) connect_task.add_done_callback(lambda _: connect_fut.set_result(None)) while transport.write.call_count < 1: await asyncio.sleep(0.01) # XXX: not ideal # We should have receiving a ping transport.write.assert_called_once_with(bytes.fromhex("FE 00 21 01 20")) # Send a ping response api._uart.data_received(bytes.fromhex("FE 02 61 01 00 01 63")) # Wait to connect await connect_fut assert api._port_path == port_path transport.reset_mock() # Now that we're connected, close the connection due to an error assert transport.write.call_count == 0 api.connection_lost(RuntimeError("Uh oh")) # We should get another ping request soon while transport.write.call_count != 1: await asyncio.sleep(0.01) # XXX: not ideal transport.write.assert_called_once_with(bytes.fromhex("FE 00 21 01 20")) # Reply incorrectly to the ping request api._uart.data_received(b"bad response") # We should still have the old connection info assert api._port_path == port_path # Wait for the SREQ_TIMEOUT to pass, we should fail to reconnect await asyncio.sleep(SREQ_TIMEOUT + 0.1) transport.reset_mock() # We wait a bit again for another ping while transport.write.call_count != 1: await asyncio.sleep(0.01) # XXX: not ideal transport.write.assert_called_once_with(bytes.fromhex("FE 00 21 01 20")) # Our reconnect task should complete after we send the ping reply reconnect_fut = event_loop.create_future() api._reconnect_task.add_done_callback(lambda _: reconnect_fut.set_result(None)) # App re-startup should not have happened, we've never reconnected before assert api._app.startup.call_count == 0 api._uart.data_received(bytes.fromhex("FE 02 61 01 00 01 63")) # We should be reconnected soon and the app should have been restarted await reconnect_fut assert api._app.startup.call_count == 1
def __init__(self, config: conf.ConfigType): super().__init__(config=conf.CONFIG_SCHEMA(config)) self._znp = None