Skip to content

udp_multicast interface: support windows #1914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions can/interfaces/udp_multicast/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import select
import socket
import struct
import time
import warnings
from typing import List, Optional, Tuple, Union

Expand All @@ -12,9 +13,12 @@

from .utils import check_msgpack_installed, pack_message, unpack_message

ioctl_supported = True

try:
from fcntl import ioctl
except ModuleNotFoundError: # Missing on Windows
ioctl_supported = False
pass


Expand Down Expand Up @@ -272,7 +276,11 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket:
try:
sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1)
except OSError as error:
if error.errno == errno.ENOPROTOOPT: # It is unavailable on macOS
if (
error.errno == errno.ENOPROTOOPT
or error.errno == errno.EINVAL
or error.errno == errno.WSAEINVAL
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could cause an AttributeError on Linux. You could redefine WSAEINVAL as a module constant.

): # It is unavailable on macOS (ENOPROTOOPT) or windows(EINVAL/WSAEINVAL)
self.timestamp_nanosecond = False
else:
raise error
Expand Down Expand Up @@ -353,18 +361,18 @@ def recv(
) from exc

if ready_receive_sockets: # not empty
# fetch data & source address
(
raw_message_data,
ancillary_data,
_, # flags
sender_address,
) = self._socket.recvmsg(
self.max_buffer, self.received_ancillary_buffer_size
)

# fetch timestamp; this is configured in _create_socket()
if self.timestamp_nanosecond:
# fetch data, timestamp & source address
(
raw_message_data,
ancillary_data,
_, # flags
sender_address,
) = self._socket.recvmsg(
self.max_buffer, self.received_ancillary_buffer_size
)

# Very similar to timestamp handling in can/interfaces/socketcan/socketcan.py -> capture_message()
if len(ancillary_data) != 1:
raise can.CanOperationError(
Expand All @@ -385,14 +393,28 @@ def recv(
)
timestamp = seconds + nanoseconds * 1.0e-9
else:
result_buffer = ioctl(
self._socket.fileno(),
SIOCGSTAMP,
bytes(self.received_timestamp_struct_size),
)
seconds, microseconds = struct.unpack(
self.received_timestamp_struct, result_buffer
# fetch data & source address
(raw_message_data, sender_address) = self._socket.recvfrom(
self.max_buffer
)

if ioctl_supported:
result_buffer = ioctl(
self._socket.fileno(),
SIOCGSTAMP,
bytes(self.received_timestamp_struct_size),
)
seconds, microseconds = struct.unpack(
self.received_timestamp_struct, result_buffer
)
else:
# fallback to time.time_ns
now = time.time()

# Extract seconds and microseconds
seconds = int(now)
microseconds = int((now - seconds) * 1000000)

if microseconds >= 1e6:
raise can.CanOperationError(
f"Timestamp microseconds field was out of range: {microseconds} not less than 1e6"
Expand Down
3 changes: 3 additions & 0 deletions doc/interfaces/udp_multicast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ from ``bus_1`` to ``bus_2``:
# give the notifier enough time to get triggered by the second bus
time.sleep(2.0)

# clean-up
notifier.stop()


Bus Class Documentation
-----------------------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"wrapt~=1.10",
"packaging >= 23.1",
"typing_extensions>=3.10.0.0",
"msgpack~=1.1.0; platform_system != 'Windows'",
"msgpack~=1.1.0",
]
requires-python = ">=3.8"
license = { text = "LGPL v3" }
Expand Down
9 changes: 5 additions & 4 deletions test/back2back_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
IS_PYPY,
IS_TRAVIS,
IS_UNIX,
IS_WINDOWS,
TEST_CAN_FD,
TEST_INTERFACE_SOCKETCAN,
)
Expand Down Expand Up @@ -303,8 +304,8 @@ class BasicTestSocketCan(Back2BackTestCase):
# this doesn't even work on Travis CI for macOS; for example, see
# https://travis-ci.org/github/hardbyte/python-can/jobs/745389871
@unittest.skipUnless(
IS_UNIX and not (IS_CI and IS_OSX),
"only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)",
(IS_UNIX and not (IS_CI and IS_OSX)) or IS_WINDOWS,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace this with @unittest.skipIf(IS_CI and IS_OSX, ...

"only supported on Unix and Windows systems (but not on macOS at Travis CI and GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv4(Back2BackTestCase):
INTERFACE_1 = "udp_multicast"
Expand All @@ -320,8 +321,8 @@ def test_unique_message_instances(self):
# this doesn't even work for loopback multicast addresses on Travis CI; for example, see
# https://travis-ci.org/github/hardbyte/python-can/builds/745065503
@unittest.skipUnless(
IS_UNIX and not (IS_TRAVIS or (IS_CI and IS_OSX)),
"only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)",
(IS_UNIX and not (IS_TRAVIS or (IS_CI and IS_OSX))) or IS_WINDOWS,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, we're not using Travis anymore anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment suggests skipping the UdpMulticast tests on macOS was necessary because of an issue with Travis CI. However, the tests may well work in the .github/workflow macOS environment, and skipping them is no longer necessary. Would you agree this is worth a test?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that might work. There's still a formatting issue, use black to fix it.

"only supported on Unix and Windows systems (but not on Travis CI; and not on macOS at GitHub Actions)",
)
class BasicTestUdpMulticastBusIPv6(Back2BackTestCase):
HOST_LOCAL_MCAST_GROUP_IPv6 = "ff11:7079:7468:6f6e:6465:6d6f:6d63:6173"
Expand Down
Loading