Skip to content

Commit e402e11

Browse files
authored
Merge branch 'develop' into mypy_extensions_fix
2 parents 163805e + e767285 commit e402e11

13 files changed

Lines changed: 292 additions & 15 deletions

File tree

.mergify.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
pull_request_rules:
2-
- name: Automatic merge on up to date branch with dual approval
2+
- name: Automatic merge passing PR on up to date branch with approving CR
33
conditions:
4-
- "#approved-reviews-by>=2"
4+
- "#approved-reviews-by>=1"
5+
- "#review-requested=0"
6+
- "#changes-requested-reviews-by=0"
7+
- "#commented-reviews-by=0"
58
- "status-success=continuous-integration/travis-ci/pr"
69
- "label!=work-in-progress"
710
actions:
811
merge:
912
method: merge
10-
strict: true
13+
strict: smart+fasttrack
14+
- name: Request Brian to review changes on core api.
15+
conditions:
16+
- "-files~=^can/interfaces/$"
17+
- "-closed"
18+
- "author!=hardbyte"
19+
actions:
20+
request_reviews:
21+
users:
22+
- hardbyte
23+
24+
- name: delete head branch after merge
25+
conditions:
26+
- merged
27+
actions:
28+
delete_head_branch: {}

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ python-can
3737
:target: https://codecov.io/gh/hardbyte/python-can/branch/develop
3838
:alt: Test coverage reports on Codecov.io
3939

40+
.. image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat
41+
:target: https://mergify.io
42+
:alt: Mergify Status
43+
4044
The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed
4145
to allow microcontrollers and devices to communicate with each other. It
4246
has priority based bus arbitration and reliable deterministic

can/interfaces/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"systec": ("can.interfaces.systec", "UcanBus"),
2626
"seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
2727
"cantact": ("can.interfaces.cantact", "CantactBus"),
28+
"gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
2829
}
2930

3031
BACKENDS.update(

can/interfaces/gs_usb.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union
2+
3+
from gs_usb.gs_usb import GsUsb
4+
from gs_usb.gs_usb_frame import GsUsbFrame
5+
from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC
6+
import can
7+
import usb
8+
import logging
9+
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class GsUsbBus(can.BusABC):
15+
def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs):
16+
"""
17+
:param channel: usb device name
18+
:param bus: number of the bus that the device is connected to
19+
:param address: address of the device on the bus it is connected to
20+
:param can_filters: not supported
21+
:param bitrate: CAN network bandwidth (bits/s)
22+
"""
23+
gs_usb = GsUsb.find(bus=bus, address=address)
24+
if not gs_usb:
25+
raise can.CanError("Can not find device {}".format(channel))
26+
self.gs_usb = gs_usb
27+
self.channel_info = channel
28+
29+
self.gs_usb.set_bitrate(bitrate)
30+
self.gs_usb.start()
31+
32+
super().__init__(channel=channel, can_filters=can_filters, **kwargs)
33+
34+
def send(self, msg: can.Message, timeout: Optional[float] = None):
35+
"""Transmit a message to the CAN bus.
36+
37+
:param Message msg: A message object.
38+
:param timeout: timeout is not supported.
39+
The function won't return until message is sent or exception is raised.
40+
41+
:raises can.CanError:
42+
if the message could not be sent
43+
"""
44+
can_id = msg.arbitration_id
45+
46+
if msg.is_extended_id:
47+
can_id = can_id | CAN_EFF_FLAG
48+
49+
if msg.is_remote_frame:
50+
can_id = can_id | CAN_RTR_FLAG
51+
52+
if msg.is_error_frame:
53+
can_id = can_id | CAN_ERR_FLAG
54+
55+
# Pad message data
56+
msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data)))
57+
58+
frame = GsUsbFrame()
59+
frame.can_id = can_id
60+
frame.can_dlc = msg.dlc
61+
frame.timestamp_us = int(msg.timestamp * 1000000)
62+
frame.data = list(msg.data)
63+
64+
try:
65+
self.gs_usb.send(frame)
66+
except usb.core.USBError:
67+
raise can.CanError("The message can not be sent")
68+
69+
def _recv_internal(
70+
self, timeout: Optional[float]
71+
) -> Tuple[Optional[can.Message], bool]:
72+
"""
73+
Read a message from the bus and tell whether it was filtered.
74+
This methods may be called by :meth:`~can.BusABC.recv`
75+
to read a message multiple times if the filters set by
76+
:meth:`~can.BusABC.set_filters` do not match and the call has
77+
not yet timed out.
78+
79+
:param float timeout: seconds to wait for a message,
80+
see :meth:`~can.BusABC.send`
81+
0 and None will be converted to minimum value 1ms.
82+
83+
:return:
84+
1. a message that was read or None on timeout
85+
2. a bool that is True if message filtering has already
86+
been done and else False. In this interface it is always False
87+
since filtering is not available
88+
89+
:raises can.CanError:
90+
if an error occurred while reading
91+
"""
92+
frame = GsUsbFrame()
93+
94+
# Do not set timeout as None or zero here to avoid blocking
95+
timeout_ms = round(timeout * 1000) if timeout else 1
96+
if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms):
97+
return None, False
98+
99+
msg = can.Message(
100+
timestamp=frame.timestamp,
101+
arbitration_id=frame.arbitration_id,
102+
is_extended_id=frame.can_dlc,
103+
is_remote_frame=frame.is_remote_frame,
104+
is_error_frame=frame.is_error_frame,
105+
channel=self.channel_info,
106+
dlc=frame.can_dlc,
107+
data=bytearray(frame.data)[0 : frame.can_dlc],
108+
is_rx=True,
109+
)
110+
111+
return msg, False
112+
113+
def shutdown(self):
114+
"""
115+
Called to carry out any interface specific cleanup required
116+
in shutting down a bus.
117+
"""
118+
self.gs_usb.stop()

can/interfaces/ixxat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
Copyright (C) 2016 Giuseppe Corbelli <giuseppe.corbelli@weightpack.com>
55
"""
66

7-
from can.interfaces.ixxat.canlib import IXXATBus
7+
from can.interfaces.ixxat.canlib import IXXATBus, get_ixxat_hwids

can/interfaces/ixxat/canlib.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,3 +800,22 @@ def _format_can_status(status_flags: int):
800800
return "CAN status message: {}".format(", ".join(states))
801801
else:
802802
return "Empty CAN status message"
803+
804+
805+
def get_ixxat_hwids():
806+
"""Get a list of hardware ids of all available IXXAT devices."""
807+
hwids = []
808+
device_handle = HANDLE()
809+
device_info = structures.VCIDEVICEINFO()
810+
811+
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
812+
while True:
813+
try:
814+
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
815+
except StopIteration:
816+
break
817+
else:
818+
hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii"))
819+
_canlib.vciEnumDeviceClose(device_handle)
820+
821+
return hwids

can/interfaces/pcan/basic.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
PCAN-Basic API
33
44
Author: Keneth Wagner
5-
Last change: 13.11.2017 Wagner
5+
Last change: 02.07.2020 Wagner
66
77
Language: Python 2.7, 3.5
88
9-
Copyright (C) 1999-2017 PEAK-System Technik GmbH, Darmstadt, Germany
9+
Copyright (C) 1999-2020 PEAK-System Technik GmbH, Darmstadt, Germany
1010
http://www.peak-system.com
1111
"""
1212

@@ -157,6 +157,9 @@
157157
PCAN_ERROR_ILLPARAMVAL = TPCANStatus(0x08000) # Invalid parameter value
158158
PCAN_ERROR_UNKNOWN = TPCANStatus(0x10000) # Unknown error
159159
PCAN_ERROR_ILLDATA = TPCANStatus(0x20000) # Invalid data, function, or action
160+
PCAN_ERROR_ILLMODE = TPCANStatus(
161+
0x80000
162+
) # Driver object state is wrong for the attempted operation
160163
PCAN_ERROR_CAUTION = TPCANStatus(
161164
0x2000000
162165
) # An operation was successfully carried out, however, irregularities were registered
@@ -183,7 +186,7 @@
183186
PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices
184187

185188
# PCAN parameters
186-
PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter
189+
PCAN_DEVICE_ID = TPCANParameter(0x01) # PCAN-USB device identifier parameter
187190
PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter
188191
PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter
189192
PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter
@@ -263,6 +266,18 @@
263266
) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High
264267
PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0
265268
PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin
269+
PCAN_FIRMWARE_VERSION = TPCANParameter(
270+
0x29
271+
) # Get the version of the firmware used by the device associated with a PCAN-Channel
272+
PCAN_ATTACHED_CHANNELS_COUNT = TPCANParameter(
273+
0x2A
274+
) # Get the amount of PCAN channels attached to a system
275+
PCAN_ATTACHED_CHANNELS = TPCANParameter(
276+
0x2B
277+
) # Get information about PCAN channels attached to a system
278+
279+
# DEPRECATED parameters
280+
PCAN_DEVICE_NUMBER = PCAN_DEVICE_ID # DEPRECATED. Use PCAN_DEVICE_ID instead
266281

267282
# PCAN parameter values
268283
PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive)
@@ -324,6 +339,14 @@
324339
SERVICE_STATUS_STOPPED = int(0x01) # The service is not running
325340
SERVICE_STATUS_RUNNING = int(0x04) # The service is running
326341

342+
# Other constants
343+
MAX_LENGTH_HARDWARE_NAME = int(
344+
33
345+
) # Maximum length of the name of a device: 32 characters + terminator
346+
MAX_LENGTH_VERSION_STRING = int(
347+
18
348+
) # Maximum length of a version string: 17 characters + terminator
349+
327350
# PCAN message types
328351
PCAN_MESSAGE_STANDARD = TPCANMessageType(
329352
0x00
@@ -524,6 +547,22 @@ class TPCANMsgFDMac(Structure):
524547
] # Data of the message (DATA[0]..DATA[63])
525548

526549

550+
class TPCANChannelInformation(Structure):
551+
"""
552+
Describes an available PCAN channel
553+
"""
554+
555+
_fields_ = [
556+
("channel_handle", TPCANHandle), # PCAN channel handle
557+
("device_type", TPCANDevice), # Kind of PCAN device
558+
("controller_number", c_ubyte), # CAN-Controller number
559+
("device_features", c_uint), # Device capabilities flag (see FEATURE_*)
560+
("device_name", c_char * MAX_LENGTH_HARDWARE_NAME), # Device name
561+
("device_id", c_uint), # Device number
562+
("channel_condition", c_uint),
563+
] # Availability status of a PCAN-Channel
564+
565+
527566
# ///////////////////////////////////////////////////////////
528567
# PCAN-Basic API function declarations
529568
# ///////////////////////////////////////////////////////////
@@ -834,15 +873,24 @@ def GetValue(self, Channel, Parameter):
834873
PCAN_TRACE_LOCATION,
835874
PCAN_BITRATE_INFO_FD,
836875
PCAN_IP_ADDRESS,
876+
PCAN_FIRMWARE_VERSION,
837877
):
838878
mybuffer = create_string_buffer(256)
879+
elif Parameter == PCAN_ATTACHED_CHANNELS:
880+
res = self.GetValue(Channel, PCAN_ATTACHED_CHANNELS_COUNT)
881+
if TPCANStatus(res[0]) != PCAN_ERROR_OK:
882+
return (TPCANStatus(res[0]),)
883+
mybuffer = (TPCANChannelInformation * res[1])()
839884
else:
840885
mybuffer = c_int(0)
841886

842887
res = self.__m_dllBasic.CAN_GetValue(
843888
Channel, Parameter, byref(mybuffer), sizeof(mybuffer)
844889
)
845-
return TPCANStatus(res), mybuffer.value
890+
if Parameter == PCAN_ATTACHED_CHANNELS:
891+
return TPCANStatus(res), mybuffer
892+
else:
893+
return TPCANStatus(res), mybuffer.value
846894
except:
847895
logger.error("Exception on PCANBasic.GetValue")
848896
raise

can/interfaces/slcan.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import Any, Optional, Tuple
1111
from can import typechecking
1212

13+
import io
1314
import time
1415
import logging
1516

@@ -252,10 +253,12 @@ def shutdown(self) -> None:
252253
self.serialPortOrig.close()
253254

254255
def fileno(self) -> int:
255-
if hasattr(self.serialPortOrig, "fileno"):
256+
try:
256257
return self.serialPortOrig.fileno()
257-
# Return an invalid file descriptor on Windows
258-
return -1
258+
except io.UnsupportedOperation:
259+
raise NotImplementedError(
260+
"fileno is not implemented using current CAN bus on this platform"
261+
)
259262

260263
def get_version(
261264
self, timeout: Optional[float]

doc/interfaces.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The available interfaces are:
2727
interfaces/canalystii
2828
interfaces/systec
2929
interfaces/seeedstudio
30+
interfaces/gs_usb
3031

3132
Additional interfaces can be added via a plugin interface. An external package
3233
can register a new interface by using the ``can.interface`` entry point in its setup.py.

doc/interfaces/gs_usb.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.. _gs_usb:
2+
3+
CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces
4+
==================================================================================================================
5+
6+
Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces.
7+
8+
Install: ``pip install "python-can[gs_usb]"``
9+
10+
Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` as shown below:
11+
12+
::
13+
14+
import usb
15+
import can
16+
17+
dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F)
18+
bus = can.Bus(bustype="gs_usb", channel=dev.product, bus=dev.bus, address=dev.address, bitrate=250000)
19+
20+
21+
Supported devices
22+
-----------------
23+
24+
Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces such as candleLight, canable, cantact, etc.
25+
26+
27+
Supported platform
28+
------------------
29+
30+
Windows, Linux and Mac.
31+
32+
Note: Since ``pyusb`` with ```libusb0``` as backend is used, ``libusb-win32`` usb driver is required to be installed in Windows.
33+
34+
35+
Supplementary Info on ``gs_usb``
36+
-----------------------------------
37+
38+
The firmware implementation for Geschwister Schneider USB/CAN devices and candleLight USB CAN can be found in `candle-usb/candleLight_fw <https://github.com/candle-usb/candleLight_fw>`_.
39+
The Linux kernel driver can be found in `linux/drivers/net/can/usb/gs_usb.c <https://github.com/torvalds/linux/blob/master/drivers/net/can/usb/gs_usb.c>`_.
40+
41+
The ``gs_usb`` interface in ``PythonCan`` relys on upstream ``gs_usb`` package, which can be found in `https://pypi.org/project/gs-usb/ <https://pypi.org/project/gs-usb/>`_ or `https://github.com/jxltom/gs_usb <https://github.com/jxltom/gs_usb>`_.
42+
The ``gs_usb`` package is using ``pyusb`` as backend, which brings better crossplatform compatibility.
43+
44+
Note: The bitrate ``10K``, ``20K``, ``50K``, ``83.333K``, ``100K``, ``125K``, ``250K``, ``500K``, ``800K`` and ``1M`` are supported in this interface, as implemented in the upstream ``gs_usb`` package's ``set_bitrate`` method.
45+
46+
Note: Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces.
47+
48+
Bus
49+
---
50+
51+
.. autoclass:: can.interfaces.gs_usb.GsUsbBus
52+
:members:

0 commit comments

Comments
 (0)