|
1 | 1 | """TCPInterface class for interfacing with http endpoint
|
2 | 2 | """
|
3 | 3 | # pylint: disable=R0917
|
| 4 | +import contextlib |
4 | 5 | import logging
|
5 | 6 | import socket
|
6 |
| -from typing import Optional, cast |
| 7 | +import time |
| 8 | +from typing import Optional |
7 | 9 |
|
8 | 10 | from meshtastic.stream_interface import StreamInterface
|
9 | 11 |
|
@@ -35,52 +37,63 @@ def __init__(
|
35 | 37 | self.socket: Optional[socket.socket] = None
|
36 | 38 |
|
37 | 39 | if connectNow:
|
38 |
| - logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe] |
39 |
| - server_address: tuple[str, int] = (hostname, portNumber) |
40 |
| - sock: Optional[socket.socket] = socket.create_connection(server_address) |
41 |
| - self.socket = sock |
| 40 | + self.myConnect() |
42 | 41 | else:
|
43 | 42 | self.socket = None
|
44 | 43 |
|
45 |
| - StreamInterface.__init__( |
46 |
| - self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes |
47 |
| - ) |
| 44 | + super().__init__(debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes) |
48 | 45 |
|
49 | 46 | def _socket_shutdown(self) -> None:
|
50 | 47 | """Shutdown the socket.
|
51 | 48 | Note: Broke out this line so the exception could be unit tested.
|
52 | 49 | """
|
53 |
| - if self.socket: #mian: please check that this should be "if self.socket:" |
54 |
| - cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR) |
| 50 | + if self.socket is not None: |
| 51 | + self.socket.shutdown(socket.SHUT_RDWR) |
55 | 52 |
|
56 | 53 | def myConnect(self) -> None:
|
57 | 54 | """Connect to socket"""
|
58 |
| - server_address: tuple[str, int] = (self.hostname, self.portNumber) |
59 |
| - sock: Optional[socket.socket] = socket.create_connection(server_address) |
60 |
| - self.socket = sock |
| 55 | + logging.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe] |
| 56 | + server_address = (self.hostname, self.portNumber) |
| 57 | + self.socket = socket.create_connection(server_address) |
61 | 58 |
|
62 | 59 | def close(self) -> None:
|
63 | 60 | """Close a connection to the device"""
|
64 | 61 | logging.debug("Closing TCP stream")
|
65 |
| - StreamInterface.close(self) |
| 62 | + super().close() |
66 | 63 | # Sometimes the socket read might be blocked in the reader thread.
|
67 | 64 | # Therefore we force the shutdown by closing the socket here
|
68 |
| - self._wantExit: bool = True |
69 |
| - if not self.socket is None: |
70 |
| - try: |
| 65 | + self._wantExit = True |
| 66 | + if self.socket is not None: |
| 67 | + with contextlib.suppress(Exception): # Ignore errors in shutdown, because we might have a race with the server |
71 | 68 | self._socket_shutdown()
|
72 |
| - except: |
73 |
| - pass # Ignore errors in shutdown, because we might have a race with the server |
74 | 69 | self.socket.close()
|
75 | 70 |
|
| 71 | + self.socket = None |
| 72 | + |
76 | 73 | def _writeBytes(self, b: bytes) -> None:
|
77 | 74 | """Write an array of bytes to our stream and flush"""
|
78 |
| - if self.socket: |
| 75 | + if self.socket is not None: |
79 | 76 | self.socket.send(b)
|
80 | 77 |
|
81 | 78 | def _readBytes(self, length) -> Optional[bytes]:
|
82 | 79 | """Read an array of bytes from our stream"""
|
83 |
| - if self.socket: |
84 |
| - return self.socket.recv(length) |
85 |
| - else: |
86 |
| - return None |
| 80 | + if self.socket is not None: |
| 81 | + data = self.socket.recv(length) |
| 82 | + # empty byte indicates a disconnected socket, |
| 83 | + # we need to handle it to avoid an infinite loop reading from null socket |
| 84 | + if data == b'': |
| 85 | + logging.debug("dead socket, re-connecting") |
| 86 | + # cleanup and reconnect socket without breaking reader thread |
| 87 | + with contextlib.suppress(Exception): |
| 88 | + self._socket_shutdown() |
| 89 | + self.socket.close() |
| 90 | + self.socket = None |
| 91 | + time.sleep(1) |
| 92 | + self.myConnect() |
| 93 | + self._startConfig() |
| 94 | + return None |
| 95 | + return data |
| 96 | + |
| 97 | + # no socket, break reader thread |
| 98 | + self._wantExit = True |
| 99 | + return None |
0 commit comments