Merge socketcan_native and socketcan_ctypes into one#326
Merge socketcan_native and socketcan_ctypes into one#326christiansandberg merged 16 commits intohardbyte:developfrom
Conversation
|
Hm, interesting! This would actually remove some more edge cases/ugly clutter all over the library. I will have a look at it the next days. |
There was a problem hiding this comment.
I think you should rebase you branch first, there are some pending changes that already remove the subprocess32 module.
In _detect_available_configs(): The interface should be socketcan, not socketcan_native.
|
|
||
| message = can.Message(arbitration_id=0x4321, data=[1, 2, 3], extended_id=True) | ||
| self.bus1.send(message) | ||
| class BasicTestSocketCan(Back2BackTestCase): |
| from .util import load_config | ||
| from .interfaces import BACKENDS | ||
|
|
||
| from can.interfaces.socketcan.socketcan import 8000 CyclicSendTask, MultiRateCyclicSendTask |
There was a problem hiding this comment.
The module name socketcan.socketcan is a bit redundant.
There was a problem hiding this comment.
Agree. Do you have a suggestion for a new name?
There was a problem hiding this comment.
Uhm; impl? Not really better though ...
There was a problem hiding this comment.
Why not just hoist them into can.interfaces.socketcan?
In can.interfaces.socketcan.__init__ we can import the classes and functions that make up the implementation
There was a problem hiding this comment.
Do you mean that we should make the current socketcan.py init.py?
There was a problem hiding this comment.
Nah (although that would work), I was just more commenting that no matter what file the classes end up in we should expose them via can.interfaces.socketcan e.g., can.interfaces.socketcan.CyclicSendTask.
There was a problem hiding this comment.
That’s already done so this is more for internal architecture.
| result += available | ||
|
|
||
| return result | ||
|
|
There was a problem hiding this comment.
Good to see this being cleaned up
can/interfaces/socketcan/__init__.py
Outdated
| from can.interfaces.socketcan import socketcan_constants as constants | ||
| from can.interfaces.socketcan.socketcan_ctypes import SocketcanCtypes_Bus | ||
| from can.interfaces.socketcan.socketcan_native import SocketcanNative_Bus | ||
| from can.interfaces.socketcan import constants |
There was a problem hiding this comment.
Should these really be "public"?
There was a problem hiding this comment.
I don't see a reason but since it was exported before I didn't want to break anything.
There was a problem hiding this comment.
Hm. But this really is just an implementation detail that nobody should care about. I doubt we would touch a lot of code with that change.
There was a problem hiding this comment.
Sure, I'll remove it. Since the module is now named "constants" it will work anyway.
| #!/usr/bin/env python | ||
| # coding: utf-8 | ||
|
|
||
| """ |
There was a problem hiding this comment.
Maybe keep a note that this was merged?
| addr = get_addr(s, channel) | ||
| error = libc.connect(s.fileno(), addr, len(addr)) | ||
| if error < 0: | ||
| raise can.CanError('Could not connect to socket') |
There was a problem hiding this comment.
Maybe also print some better error message. See error_code_to_str(exc.errno).
| # this is done later too but better safe than sorry | ||
| if config['interface'] == 'socketcan': | ||
| config['interface'] = choose_socketcan_implementation() | ||
| # deprecated socketcan types |
There was a problem hiding this comment.
Maybe print a warning to make users switch to simply `socketcan´. Should this eventually be removed?
There was a problem hiding this comment.
Good idea. Yes we should eventually remove this but first a logged warning is good, then maybe a DeprecationWarning.
|
|
||
| def _recv_internal(self, timeout): | ||
| if timeout: | ||
| if timeout is not None: |
There was a problem hiding this comment.
A timeout of 0 should not try to invoke select(), or should it?
There was a problem hiding this comment.
It should, because otherwise a timeout of 0 will go directly to recv which will block with infinite timeout.
| if error < 0: | ||
| raise can.CanError('Could not connect to socket') | ||
| except OSError as e: | ||
| log.error("Couldn't connect a broadcast manager socket") |
There was a problem hiding this comment.
Maybe also print some better error message. See error_code_to_str(exc.errno).
There was a problem hiding this comment.
Won't that information be in the exception that is raised after?
| addr = get_addr(sock, channel) | ||
| error = libc.bind(sock.fileno(), addr, len(addr)) | ||
| if error < 0: | ||
| raise can.CanError('Could not bind socket') |
There was a problem hiding this comment.
Maybe also print some better error message. See error_code_to_str(exc.errno).
| CAN_RAW_RECV_OWN_MSGS, | ||
| struct.pack('i', receive_own_messages)) | ||
| except socket.error as e: | ||
| log.error("Could not receive own messages (%s)", e) |
There was a problem hiding this comment.
Maybe also print some better error message. See error_code_to_str(exc.errno).
There was a problem hiding this comment.
Isn't that also in the exception message?
There was a problem hiding this comment.
Ah right. It creates something like: socket.error: [Errno 107] Transport endpoint is not connected. This seems to be only required with the libc fallback.
|
Sorry for the many comments, but this really nice PR touches quite a bit of code / code structure. Once everything code-related is cleaned up, we should also update the docs where appropriate. |
|
|
Support broadcast channel
| :param str channel: | ||
| The can interface name with which to create this bus. An example channel | ||
| would be 'vcan0' or 'can0'. | ||
| An empty string '' will receive messages from all channels. |
There was a problem hiding this comment.
And what about sending? Is it supported on both versions?
There was a problem hiding this comment.
No not currently. But it shouldn't require too much work so I'll fix that.
| if self.channel == "" and HAS_NATIVE_SUPPORT and msg.channel: | ||
| self.socket.sendto(data, (msg.channel, )) | ||
| else: | ||
| self.socket.send(data) |
There was a problem hiding this comment.
Shouldn't we use sendall(), since send() might not transmit everything?
There was a problem hiding this comment.
The default behaviour for all other interfaces and previously for socketcan is to not block on send(). If it can't be transmitted, it should be raised as an exception and the application can decide if it wants to try again, move on to the next message or signal as an error. The most common scenario is that something is wrong with the bus and no nodes are ACK:ing your messages. If we would be using sendall then the application will just freeze.
I added the timeout argument for making send blocking, but usually you want to give up after a certain time. One problem with my change will arise if it is possible to queue only part of a message. Then we want to go back to select and loop until all bytes are sent, but only for the timeout period. I'll give that a go.
| try: | ||
| self.socket.sendall(build_can_frame(msg)) | ||
| if self.channel == "" and HAS_NATIVE_SUPPORT and msg.channel: | ||
| self.socket.sendto(data, (msg.channel, )) |
There was a problem hiding this comment.
Return the number of bytes sent.
I guess we should make sure the rest gets sent as well (in a loop or handled completely in a auxiliary method) if the returned number of bytes is less than len(data).
|
Sorry for all the commits. I'll squash them when merging. Currently I have to push the ch 38BA anges in order to test them on the Linux machine. I should have done the broadcast channel thing later but it seemed simpler at first. Now it seems to finally work however. |
| if not ready: | ||
| # Timeout | ||
| break | ||
| sent = self._send_once(data, msg.channel) |
There was a problem hiding this comment.
This is a nice solution! But maybe it would be more efficient to use socket.sendall() when there is native support?
There was a problem hiding this comment.
The problem is that sendall doesn't have a timeout argument. And it is probably very unusual, if even impossible, that a message could be only partially cued. For most cases it will succeed on first try, or it will fail completely on first select.
There was a problem hiding this comment.
Does sending to all channels work now? If not, it should be implemented or a note / issue should be added. That's all I see that needs to be done.
|
Do you mean if broadcast channel is used? Then you currently need to address each message to a specific channel, otherwise it will fail. I don't think there is a way to send to all channels as far as I can see. I can add a note about that. |
Add notes about deprecation of old bustypes Add note about sending to broadcast bus setsockopt should supports integers
Since it takes a lot of effort to maintain both implementations I thought it might be a good idea to merge them into one. I took the native implementation, removed reliance on
socketconstants and provided a libc fallback forsocket.bindandsocket.connect.Will update docs.