-
Notifications
You must be signed in to change notification settings - Fork 666
Add support for USR-CANET200 interface #1347
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
Open
+202
−1
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
712a648
added usr_canet interface
478b2b2
added canet inferface and docs
020ddbb
fix dependency, add san check to receive
64bd88d
add to contributors
ea9c051
added error checking and reconnect.
01ef3d0
stability fix by limiting recv packet to 13 bytes
0887b81
code cleanup and bug fix of return without resetting timeout
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
171 changes: 171 additions & 0 deletions
can/interfaces/usr_canet.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| import socket | ||
| import struct | ||
| import logging | ||
| from time import sleep | ||
| from typing import Optional, Dict, Any, Tuple | ||
| from can import BitTiming, BusABC, Message, typechecking | ||
| from can.typechecking import CanFilters | ||
|
|
||
|
|
||
| class UsrCanetBus(BusABC): | ||
|
|
||
| def __init__(self, | ||
| host: str = '127.0.0.1', | ||
| port: int = 20001, | ||
| can_filters: Optional[typechecking.CanFilters] = None, | ||
| reconnect=True, | ||
| reconnect_delay=2, | ||
| **kwargs: Dict[str, Any]): | ||
| """ | ||
|
|
||
| :param host: | ||
| IP adddress of USR-CANET200 Device in TCP Server mode. | ||
| :param port: | ||
| TCP port of the corresponding CANbus port on the device configured. | ||
| :param can_filters: | ||
| Passed in for super class' filter. | ||
| """ | ||
|
|
||
| super().__init__(can_filters=can_filters, **kwargs, channel=0) | ||
|
|
||
| self.reconnect = reconnect | ||
| self.host = host | ||
| self.port = port | ||
| self.connected = False | ||
| self.reconnect_delay = reconnect_delay | ||
|
|
||
| # Create a socket and connect to host | ||
| while not self.connected: | ||
| try: | ||
| self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| self.s.connect((host, port)) | ||
| self.connected = True | ||
| except socket.error as e: | ||
| self.connected = False | ||
| logging.error(f"Could not connect: {e}. Retrying...") | ||
| self.s.close() | ||
| sleep(reconnect_delay) | ||
|
|
||
| def send(self, msg: Message, timeout: Optional[float] = None) -> None: | ||
| """Send a CAN message to the bus | ||
|
|
||
| :param msg: message to send | ||
|
|
||
| :param timeout: timeout (in seconds) to wait for expected ACK pack. | ||
| If set to ``None`` (default) will wait indefinitely. | ||
|
|
||
| """ | ||
| frame_information = 0x00 | ||
| frame_information |= (1 if msg.is_extended_id else 0) << 7 # First bit indicates if is extended id | ||
| frame_information |= (1 if msg.is_remote_frame else 0) << 6 # Second bit indicates if is remote frame | ||
| frame_information |= msg.dlc & 0x0F # Last 4 bits indicate the length of frame | ||
| frame_information = bytearray([frame_information]) | ||
|
|
||
| frame_id = bytearray(4) | ||
| struct.pack_into('>L', frame_id, 0, int(msg.arbitration_id)) # Next 4 bytes contain CAN ID | ||
|
|
||
| frame_data = bytearray(8) | ||
| frame_data[0:len(msg.data)] = msg.data # Following 8 bytes contain CAN data | ||
|
|
||
| raw_message = frame_information + frame_id + frame_data # Combine to make full message | ||
|
|
||
| # Set timeout for sending | ||
| if timeout is not None: | ||
| self.s.settimeout(timeout) | ||
| try: | ||
| self.s.send(raw_message) | ||
| except TimeoutError: | ||
| # Timeout | ||
| msg = None | ||
| pass | ||
| except socket.error as e: | ||
| self.connected = False | ||
| logging.error(f"Socket error: {e}") | ||
| if self.reconnect: | ||
| self.do_reconnect() | ||
| else: | ||
| msg = None | ||
| finally: | ||
| # Reset timeout | ||
| if timeout is not None: | ||
| self.s.settimeout(None) | ||
|
|
||
| return (msg, False) | ||
|
|
||
| def do_reconnect(self): | ||
| while not self.connected: | ||
| try: | ||
| logging.error("Reconnecting...") | ||
| self.s.close() | ||
| self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| self.s.connect((self.host, self.port)) | ||
| self.connected = True | ||
| logging.error("Reconnected.") | ||
| except Exception as e: | ||
| logging.error(f"Could not reconnect: {e}. Retrying...") | ||
| sleep(self.reconnect_delay) | ||
|
|
||
| def _recv_internal(self, timeout: Optional[float]) -> Tuple[Optional[Message], bool]: | ||
| """Expect a message to receive from the socket | ||
| :param timeout: timeout (in seconds) to wait for expected data to come in. | ||
| If set to ``None`` (default) will wait indefinitely. | ||
|
|
||
| """ | ||
|
|
||
| # Set timeout and receive data | ||
| if timeout is not None: | ||
| self.s.settimeout(timeout) | ||
|
|
||
| flag_success = False | ||
| while not flag_success: | ||
| try: | ||
| # The USR-CANET will always return 13 bytes per CAN packet | ||
| # But sometimes will return TCP packets with 2 CAN packets sandwiched together | ||
| # This will seperate the sandwich. | ||
| data = self.s.recv(13) | ||
| flag_success = True | ||
| except TimeoutError: | ||
| self.s.settimeout(None) | ||
| return(None, False) | ||
| except socket.error as e: | ||
| self.connected = False | ||
| logging.error(f"Socket error: {e}") | ||
| if self.reconnect: | ||
| self.do_reconnect() | ||
| else: | ||
| self.s.settimeout(None) | ||
| return(None, False) | ||
|
|
||
|
|
||
| # Check received length | ||
| if len(data) == 0: | ||
| self.s.settimeout(None) | ||
| return (None, False) | ||
|
|
||
| # Decode CAN frame | ||
| # CAN frame from USR-CANET200 looks like: | ||
| # --------------------- ------------------ -------------------- | ||
| # | frame_info (1 byte) | can_id (4 bytes) | can_data (8 bytes) | | ||
| # --------------------- ------------------ -------------------- | ||
| CAN_FRAME = struct.Struct('>BI8s') | ||
| frame_info, can_id, can_data = CAN_FRAME.unpack_from(data) | ||
| dlc = frame_info & 0x0F # Last 4 bits indicate data length | ||
| is_extended_id = frame_info & 0x80 # First bit indicate if is extended ID | ||
| is_remote_frame = frame_info & 0x40 # Second bit indicate if is remote frame | ||
| can_data = can_data[:dlc] # Trim message | ||
| msg = Message(arbitration_id=can_id, | ||
| data=can_data, dlc=dlc, | ||
| is_extended_id=is_extended_id, | ||
| is_remote_frame=is_remote_frame) | ||
|
|
||
| # Reset timeout | ||
| if timeout is not None: | ||
| self.s.settimeout(None) | ||
| return (msg, False) | ||
|
|
||
| def shutdown(self) -> None: | ||
| """Close down the socket and release resources immediately.""" | ||
|
|
||
| super().shutdown() | ||
| self.s.close() | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
doc/interfaces/usr_canet.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| USR-CANET | ||
| =========== | ||
|
|
||
| USR-CANET200 is an Ethernet to CANbus and RS485 converter manufactured by PUSR. This device features 2 CANbus ports with TCP/UDP server or client to relay CANbus over Ethernet. The interface | ||
| uses TCP server mode and need to be configured on the web management portal. | ||
|
|
||
| The device's official documentation and transparent transmission protocol information can be found `here <https://www.pusr.com/download/CANET/USR-CANET200-User-Manual_V1.0.4.01.pdf>` | ||
|
|
||
| Install: ``pip install "python-can[usr_canet]"`` | ||
|
|
||
| Supported platform | ||
| ------------------ | ||
|
|
||
| Windows, Linux and Mac. | ||
|
|
||
| Limitations | ||
| ----------- | ||
|
|
||
| This interface has a simplified implementation of timeout and may not work well in high capacity multi-threaded applications. | ||
|
|
||
|
|
||
| Bus | ||
| --- | ||
|
|
||
| .. autoclass:: can.interfaces.usr_canet.UsrCanetBus | ||
|
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.