Skip to content

Commit

Permalink
kiss: Catch async open errors on serial devices
Browse files Browse the repository at this point in the history
  • Loading branch information
sjlongland committed May 4, 2024
1 parent d31fa43 commit dd5b8c4
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 45 deletions.
64 changes: 38 additions & 26 deletions aioax25/kiss.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,21 +541,25 @@ def __init__(self, device, baudrate, *args, **kwargs):
self._baudrate = baudrate

async def _open_connection(self):
await create_serial_connection(
self._loop,
self._make_protocol,
self._device,
baudrate=self._baudrate,
bytesize=EIGHTBITS,
parity=PARITY_NONE,
stopbits=STOPBITS_ONE,
timeout=None,
xonxoff=False,
rtscts=False,
write_timeout=None,
dsrdtr=False,
inter_byte_timeout=None,
)
try:
await create_serial_connection(
self._loop,
self._make_protocol,
self._device,
baudrate=self._baudrate,
bytesize=EIGHTBITS,
parity=PARITY_NONE,
stopbits=STOPBITS_ONE,
timeout=None,
xonxoff=False,
rtscts=False,
write_timeout=None,
dsrdtr=False,
inter_byte_timeout=None,
)
except:
self._log.warning("Failed to open serial connection", exc_info=1)
self._on_fail("open", exc_info())


class TCPKISSDevice(BaseTransportDevice):
Expand Down Expand Up @@ -625,9 +629,13 @@ def __init__(
)

async def _open_connection(self):
await self._loop.create_connection(
self._make_protocol, **self._conn_args
)
try:
await self._loop.create_connection(
self._make_protocol, **self._conn_args
)
except:
self._log.warning("Failed to open TCP connection", exc_info=1)
self._on_fail("open", exc_info())


class SubprocKISSDevice(BaseTransportDevice):
Expand Down Expand Up @@ -665,14 +673,18 @@ def _make_protocol(self):
)

async def _open_connection(self):
if self._shell:
await self._loop.subprocess_shell(
self._make_protocol, " ".join(self._command)
)
else:
await self._loop.subprocess_exec(
self._make_protocol, *self._command
)
try:
if self._shell:
await self._loop.subprocess_shell(
self._make_protocol, " ".join(self._command)
)
else:
await self._loop.subprocess_exec(
self._make_protocol, *self._command
)
except:
self._log.warning("Failed to call subprocess", exc_info=1)
self._on_fail("open", exc_info())

def _send_raw_data(self, data):
self._transport.get_pipe_transport(0).write(data)
Expand Down
81 changes: 62 additions & 19 deletions tests/test_kiss/test_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
inter_byte_timeout,
):

assert port == "/dev/ttyS0"
assert port in ("/dev/ttyS0", "/dev/ttyS1")
assert baudrate == 9600
assert bytesize == EIGHTBITS
assert parity == PARITY_NONE
Expand Down Expand Up @@ -96,37 +96,45 @@ def write(self, *args, **kwargs):

# Stub the serial port connection factory
async def dummy_create_serial_connection(
loop, proto_factory, *args, **kwargs
loop, proto_factory, device, *args, **kwargs
):
future = loop.create_future()
create_serial_conn_log.debug(
"Creating new serial connection: "
"loop=%r proto=%r args=%r kwargs=%r",
"loop=%r proto=%r device=%r args=%r kwargs=%r",
loop,
proto_factory,
device,
args,
kwargs,
)

def _open():
create_serial_conn_log.debug("Creating objects")
# Create the objects
protocol = proto_factory()
port = DummySerial(*args, **kwargs)
transport = DummyTransport(loop, port)

# Record the created object references
connections.append(
PortConnection(port=port, protocol=protocol, transport=transport)
)
if device == "/dev/ttyS0":
create_serial_conn_log.debug("Creating objects")
# Create the objects
protocol = proto_factory()
port = DummySerial(device, *args, **kwargs)
transport = DummyTransport(loop, port)

# Record the created object references
connections.append(
PortConnection(
port=port, protocol=protocol, transport=transport
)
)

# Pass the protocol the transport object
create_serial_conn_log.debug("Passing transport to protocol")
protocol.connection_made(transport)
# Pass the protocol the transport object
create_serial_conn_log.debug("Passing transport to protocol")
protocol.connection_made(transport)

# Finish up the future
create_serial_conn_log.debug("Finishing up")
future.set_result((protocol, transport))
# Finish up the future
create_serial_conn_log.debug("Finishing up")
future.set_result((protocol, transport))
else:
# Abort with an error
create_serial_conn_log.debug("Failing")
future.set_exception(IOError("Open device failed"))

create_serial_conn_log.debug("Scheduled in IOLoop")
loop.call_soon(_open)
Expand Down Expand Up @@ -170,6 +178,41 @@ async def test_open():
assert kissdev.init_called


@asynctest
async def test_open_fail():
"""
Test open failures are handled.
"""
loop = get_event_loop()
kissdev = TestDevice(device="/dev/ttyS1", baudrate=9600, loop=loop)
assert kissdev._transport is None

failures = []

def _on_fail(**kwargs):
failures.append(kwargs)

kissdev.failed.connect(_on_fail)

kissdev.open()
await sleep(0.01)

# We should NOT have created a new port
assert len(connections) == 0

# Connection should be in the failed state
assert kissdev.state == kiss.KISSDeviceState.FAILED

# Failure should have been reported
assert failures
failure = failures.pop(0)

assert failure.pop("action") == "open"
(ex_c, ex_v, _) = failure.pop("exc_info")
assert ex_c is IOError
assert str(ex_v) == "Open device failed"


@asynctest
async def test_close():
"""
Expand Down

0 comments on commit dd5b8c4

Please sign in to comment.