Skip to content

Conversation

@christiansandberg
Copy link
Collaborator

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 socket constants and provided a libc fallback for socket.bind and socket.connect.

Will update docs.

@felixdivo
Copy link
Collaborator

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.

Copy link
Collaborator

@felixdivofelixdivo left a 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):
Copy link
Collaborator

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
Copy link
Collaborator

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.

Copy link
CollaboratorAuthor

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?

Copy link
Collaborator

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 ...

Copy link
Owner

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

Copy link
CollaboratorAuthor

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?

Copy link
Owner

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.

Copy link
CollaboratorAuthor

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

Copy link
Collaborator

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

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
Copy link
Collaborator

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"?

Copy link
CollaboratorAuthor

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.

Copy link
Collaborator

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.

Copy link
CollaboratorAuthor

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

"""
Copy link
Collaborator

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')
Copy link
Collaborator

@felixdivofelixdivoJun 14, 2018

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
Copy link
Collaborator

@felixdivofelixdivoJun 14, 2018

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?

Copy link
CollaboratorAuthor

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.

Copy link
Owner

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:
Copy link
Collaborator

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?

Copy link
CollaboratorAuthor

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.

Copy link
Collaborator

@felixdivofelixdivo left a 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")
Copy link
Collaborator

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).

Copy link
CollaboratorAuthor

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?

Copy link
Collaborator

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')
Copy link
Collaborator

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)
Copy link
Collaborator

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).

Copy link
CollaboratorAuthor

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?

Copy link
Collaborator

@felixdivofelixdivoJun 14, 2018

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
Copy link
Collaborator

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
Copy link
Collaborator

socketcan.rst should still mention that filters and periodic sending are quite efficient because of in-kernel handling of that tasks. You could just copy an explanation together from the deleted parts of 6ce8565.

: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.
Copy link
Collaborator

@felixdivofelixdivoJun 15, 2018

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?

Copy link
CollaboratorAuthor

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)
Copy link
Collaborator

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?

Copy link
CollaboratorAuthor

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, ))
Copy link
Collaborator

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
Copy link
CollaboratorAuthor

christiansandberg commented Jun 16, 2018

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)
Copy link
Collaborator

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?

Copy link
CollaboratorAuthor

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.

Copy link
Collaborator

@felixdivofelixdivo left a 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
Copy link
CollaboratorAuthor

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
@christiansandbergchristiansandberg merged commit 66540e3 into hardbyte:developJun 26, 2018
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@christiansandberg@felixdivo@hardbyte