Skip to content

Commit

Permalink
Add feature to detect socketcand beacon
Browse files Browse the repository at this point in the history
  • Loading branch information
faisal-shah committed Nov 30, 2023
1 parent 2e58a21 commit 2b869b2
Showing 1 changed file with 88 additions and 0 deletions.
88 changes: 88 additions & 0 deletions can/interfaces/socketcand/socketcand.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,87 @@

log = logging.getLogger(__name__)

DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS = ""
DEFAULT_SOCKETCAND_DISCOVERY_PORT = 42000


def detect_beacon():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS, DEFAULT_SOCKETCAND_DISCOVERY_PORT))
log.info(
f"Listening on for socketcand UDP advertisement on {DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS}:{DEFAULT_SOCKETCAND_DISCOVERY_PORT}"
)

# Time between beacons no more than 3 seconds. Allow for at least 3
timeout_ms = 12000
now = time.time() * 1000
end_time = now + timeout_ms
while (time.time() * 1000) < end_time:
try:
# get all sockets that are ready (can be a list with a single value
# being self.socket or an empty list if self.socket is not ready)
ready_receive_sockets, _, _ = select.select([sock], [], [], 1)

if not ready_receive_sockets:
log.debug("No advertisement received")
continue

msg = sock.recv(1024).decode("utf-8")
root = ET.fromstring(msg)
if root.tag != "CANBeacon":
log.debug("Unexpected message received over UDP")
continue

det_devs = []
det_host = None
det_port = None
for child in root:
if child.tag == "Bus":
bus_name = child.attrib["name"]
det_devs.append(bus_name)
elif child.tag == "URL":
url = urlparselib.urlparse(child.text)
det_host = url.hostname
det_port = url.port

if not det_devs:
log.debug(
"Got advertisement, but no SocketCAN devices advertised by socketcand"
)
continue

if (det_host is None) or (det_port is None):
det_host = None
det_port = None
log.debug(
"Got advertisement, but no SocketCAN URL advertised by socketcand"
)
continue

log.info(f"Found SocketCAN devices: {det_devs}")
return [
{
"interface": "socketcand",
"host": det_host,
"port": det_port,
"channel": channel,
}
for channel in det_devs
]

except ET.ParseError:
log.debug("Unexpected message received over UDP")
continue

except Exception as exc:
# something bad happened (e.g. the interface went down)
log.error(f"Failed to detect beacon: {exc} {traceback.format_exc()}")
raise OSError(f"Failed to detect beacon: {exc} {traceback.format_exc()}")

raise TimeoutError(
f"detect_beacon: Failed to detect udp beacon for {timeout_ms} ms"
)


def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message:
if not ascii_msg.startswith("< frame ") or not ascii_msg.endswith(" >"):
Expand Down Expand Up @@ -79,6 +160,9 @@ class SocketCanDaemonBus(can.BusABC):
def __init__(self, channel, host, port, tcp_tune=False, can_filters=None, **kwargs):
"""Connects to a CAN bus served by socketcand.
It implements :meth:`can.BusABC._detect_available_configs` to search for
available interfaces.
It will attempt to connect to the server for up to 10s, after which a
TimeoutError exception will be thrown.
Expand Down Expand Up @@ -231,3 +315,7 @@ def shutdown(self):
"""Stops all active periodic tasks and closes the socket."""
super().shutdown()
self.__socket.close()

@staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
return detect_beacon()

0 comments on commit 2b869b2

Please sign in to comment.