- Notifications
You must be signed in to change notification settings - Fork 665
Merge socketcan_native and socketcan_ctypes into one#326
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
Merge socketcan_native and socketcan_ctypes into one #326
Uh oh!
There was an error while loading. Please reload this page.
Conversation
felixdivo commented Jun 13, 2018
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. |
felixdivo left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one!
| from .util import load_config | ||
| from .interfaces import BACKENDS | ||
| from can.interfaces.socketcan.socketcan import CyclicSendTask, MultiRateCyclicSendTask |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module name socketcan.socketcan is a bit redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Do you have a suggestion for a new name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhm; impl? Not really better though ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that we should make the current socketcan.py init.py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s already done so this is more for internal architecture.
| result += available | ||
| return result | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these really be "public"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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') |
felixdivoJun 14, 2018 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
felixdivoJun 14, 2018 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe print a warning to make users switch to simply `socketcan´. Should this eventually be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Yes we should eventually remove this but first a logged warning is good, then maybe a DeprecationWarning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a comment like this one 07be6b5
| def _recv_internal(self, timeout): | ||
| if timeout: | ||
| if timeout is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A timeout of 0 should not try to invoke select(), or should it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, because otherwise a timeout of 0 will go directly to recv which will block with infinite timeout.
felixdivo left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated review.
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also print some better error message. See error_code_to_str(exc.errno).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't that information be in the exception that is raised after?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. See below.
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also print some better error message. See error_code_to_str(exc.errno).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that also in the exception message?
felixdivoJun 14, 2018 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
felixdivo commented Jun 14, 2018
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. |
felixdivo commented Jun 15, 2018
|
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. |
felixdivoJun 15, 2018 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what about sending? Is it supported on both versions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use sendall(), since send() might not transmit everything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
christiansandberg commented Jun 16, 2018 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Sorry for all the commits. I'll squash them when merging. Currently I have to push the changes 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
felixdivo left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
christiansandberg commented Jun 20, 2018
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.