|
8 | 8 | http://www.domologic.de
|
9 | 9 | """
|
10 | 10 | import logging
|
| 11 | +import os |
11 | 12 | import select
|
12 | 13 | import socket
|
13 | 14 | import time
|
@@ -75,10 +76,42 @@ def connect_to_server(s, host, port):
|
75 | 76 |
|
76 | 77 |
|
77 | 78 | class SocketCanDaemonBus(can.BusABC):
|
78 |
| - def __init__(self, channel, host, port, can_filters=None, **kwargs): |
| 79 | + def __init__(self, channel, host, port, tcp_tune=False, can_filters=None, **kwargs): |
| 80 | + """Connects to a CAN bus served by socketcand. |
| 81 | +
|
| 82 | + It will attempt to connect to the server for up to 10s, after which a |
| 83 | + TimeoutError exception will be thrown. |
| 84 | +
|
| 85 | + If the handshake with the socketcand server fails, a CanError exception |
| 86 | + is thrown. |
| 87 | +
|
| 88 | + :param channel: |
| 89 | + The can interface name served by socketcand. |
| 90 | + An example channel would be 'vcan0' or 'can0'. |
| 91 | + :param host: |
| 92 | + The host address of the socketcand server. |
| 93 | + :param port: |
| 94 | + The port of the socketcand server. |
| 95 | + :param tcp_tune: |
| 96 | + This tunes the TCP socket for low latency (TCP_NODELAY, and |
| 97 | + TCP_QUICKACK). |
| 98 | + This option is not available under windows. |
| 99 | + :param can_filters: |
| 100 | + See :meth:`can.BusABC.set_filters`. |
| 101 | + """ |
79 | 102 | self.__host = host
|
80 | 103 | self.__port = port
|
| 104 | + |
| 105 | + self.__tcp_tune = tcp_tune |
81 | 106 | self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 107 | + |
| 108 | + if self.__tcp_tune: |
| 109 | + if os.name == "nt": |
| 110 | + self.__tcp_tune = False |
| 111 | + log.warning("'tcp_tune' not available in Windows. Setting to False") |
| 112 | + else: |
| 113 | + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) |
| 114 | + |
82 | 115 | self.__message_buffer = deque()
|
83 | 116 | self.__receive_buffer = "" # i know string is not the most efficient here
|
84 | 117 | self.channel = channel
|
@@ -120,6 +153,8 @@ def _recv_internal(self, timeout):
|
120 | 153 | ascii_msg = self.__socket.recv(1024).decode(
|
121 | 154 | "ascii"
|
122 | 155 | ) # may contain multiple messages
|
| 156 | + if self.__tcp_tune: |
| 157 | + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) |
123 | 158 | self.__receive_buffer += ascii_msg
|
124 | 159 | log.debug(f"Received Ascii Message: {ascii_msg}")
|
125 | 160 | buffer_view = self.__receive_buffer
|
@@ -173,16 +208,26 @@ def _recv_internal(self, timeout):
|
173 | 208 | def _tcp_send(self, msg: str):
|
174 | 209 | log.debug(f"Sending TCP Message: '{msg}'")
|
175 | 210 | self.__socket.sendall(msg.encode("ascii"))
|
| 211 | + if self.__tcp_tune: |
| 212 | + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) |
176 | 213 |
|
177 | 214 | def _expect_msg(self, msg):
|
178 | 215 | ascii_msg = self.__socket.recv(256).decode("ascii")
|
| 216 | + if self.__tcp_tune: |
| 217 | + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) |
179 | 218 | if not ascii_msg == msg:
|
180 | 219 | raise can.CanError(f"{msg} message expected!")
|
181 | 220 |
|
182 | 221 | def send(self, msg, timeout=None):
|
| 222 | + """Transmit a message to the CAN bus. |
| 223 | +
|
| 224 | + :param msg: A message object. |
| 225 | + :param timeout: Ignored |
| 226 | + """ |
183 | 227 | ascii_msg = convert_can_message_to_ascii_message(msg)
|
184 | 228 | self._tcp_send(ascii_msg)
|
185 | 229 |
|
186 | 230 | def shutdown(self):
|
| 231 | + """Stops all active periodic tasks and closes the socket.""" |
187 | 232 | super().shutdown()
|
188 | 233 | self.__socket.close()
|
0 commit comments