|
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