pax_global_header 0000666 0000000 0000000 00000000064 14722003266 0014514 g ustar 00root root 0000000 0000000 52 comment=654a02ae24bfc50bf1bb1fad7aab4aa88763d302
python-can-4.5.0/ 0000775 0000000 0000000 00000000000 14722003266 0013602 5 ustar 00root root 0000000 0000000 python-can-4.5.0/CHANGELOG.md 0000664 0000000 0000000 00000110255 14722003266 0015417 0 ustar 00root root 0000000 0000000 Version 4.5.0
=============
Features
--------
* gs_usb command-line support (and documentation updates and stability fixes) by @BenGardiner in https://github.com/hardbyte/python-can/pull/1790
* Faster and more general MF4 support by @cssedev in https://github.com/hardbyte/python-can/pull/1892
* ASCWriter speed improvement by @pierreluctg in https://github.com/hardbyte/python-can/pull/1856
* Faster Message string representation by @pierreluctg in https://github.com/hardbyte/python-can/pull/1858
* Added Netronic's CANdo and CANdoISO adapters interface by @belliriccardo in https://github.com/hardbyte/python-can/pull/1887
* Add autostart option to BusABC.send_periodic() to fix issue #1848 by @SWolfSchunk in https://github.com/hardbyte/python-can/pull/1853
* Improve TestBusConfig by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1804
* Improve speed of TRCReader by @lebuni in https://github.com/hardbyte/python-can/pull/1893
Bug Fixes
---------
* Fix Kvaser timestamp by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1878
* Set end_time in ThreadBasedCyclicSendTask.start() by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1871
* Fix regex in _parse_additional_config() by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1868
* Fix for #1849 (PCAN fails when PCAN_ERROR_ILLDATA is read via ReadFD) by @bures in https://github.com/hardbyte/python-can/pull/1850
* Period must be >= 1ms for BCM using Win32 API by @pierreluctg in https://github.com/hardbyte/python-can/pull/1847
* Fix ASCReader Crash on "Start of Measurement" Line by @RitheeshBaradwaj in https://github.com/hardbyte/python-can/pull/1811
* Resolve AttributeError within NicanError by @vijaysubbiah20 in https://github.com/hardbyte/python-can/pull/1806
Miscellaneous
-------------
* Fix CI by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1889
* Update msgpack dependency by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1875
* Add tox environment for doctest by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1870
* Use typing_extensions.TypedDict on python < 3.12 for pydantic support by @NickCao in https://github.com/hardbyte/python-can/pull/1845
* Replace PyPy3.8 with PyPy3.10 by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1838
* Fix slcan tests by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1834
* Test on Python 3.13 by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1833
* Stop notifier in examples by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1814
* Use setuptools_scm by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1810
* Added extra info for Kvaser dongles by @FedericoSpada in https://github.com/hardbyte/python-can/pull/1797
* Socketcand: show actual response as well as expected in error by @liamkinne in https://github.com/hardbyte/python-can/pull/1807
* Refactor CLI filter parsing, add tests by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1805
* Add zlgcan to docs by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1839
Version 4.4.2
=============
Bug Fixes
---------
* Remove `abstractmethod` decorator from `Listener.stop()` (#1770, #1795)
* Fix `SizedRotatingLogger` file suffix bug (#1792, #1793)
* gs_usb: Use `BitTiming` class internally to configure bitrate (#1747, #1748)
* pcan: Fix unpack error in `PcanBus._detect_available_configs()` (#1767)
* socketcan: Improve error handling in `SocketcanBus.__init__()` (#1771)
* socketcan: Do not log exception on non-linux platforms (#1800)
* vector, kvaser: Activate channels after CAN filters were applied (#1413, #1708, #1796)
Features
--------
* kvaser: Add support for non-ISO CAN FD (#1752)
* neovi: Return timestamps relative to epoch (#1789)
* slcan: Support CANdapter extended length arbitration ID (#1506, #1528)
* slcan: Add support for `listen_only` mode (#1496)
* vector: Add support for `listen_only` mode (#1764)
Version 4.4.0
=============
Features
--------
* TRC 1.3 Support: Added support for .trc log files as generated by PCAN Explorer v5 and other tools, expanding compatibility with common log file formats (#1753).
* ASCReader refactor: improved the ASCReader code (#1717).
* SYSTEC Interface Enhancements: Added the ability to pass an explicit DLC value to the send() method when using the SYSTEC interface, enhancing flexibility for message definitions (#1756).
* Socketcand Beacon Detection: Introduced a feature for detecting socketcand beacons, facilitating easier connection and configuration with socketcand servers (#1687).
* PCAN Driver Echo Frames: Enabled echo frames in the PCAN driver when receive_own_messages is set, improving feedback for message transmissions (#1723).
* CAN FD Bus Connection for VectorBus: Enabled connecting to CAN FD buses without specifying bus timings, simplifying the connection process for users (#1716).
* Neousys Configs Detection: Updated the detection mechanism for available Neousys configurations, ensuring more accurate and comprehensive configuration discovery (#1744).
Bug Fixes
---------
* Send Periodic Messages: Fixed an issue where fixed-duration periodic messages were sent one extra time beyond their intended count (#1713).
* Vector Interface on Windows 11: Addressed compatibility issues with the Vector interface on Windows 11, ensuring stable operation across the latest OS version (#1731).
* ASCWriter Millisecond Handling: Corrected the handling of milliseconds in ASCWriter, ensuring accurate time representation in log files (#1734).
* Various minor bug fixes: Addressed several minor bugs to improve overall stability and performance.
Miscellaneous
-------------
* Invert default value logic for BusABC._is_shutdown. (#1774)
* Implemented various logging enhancements to provide more detailed and useful operational insights (#1703).
* Updated CI to use OIDC for connecting GitHub Actions to PyPi, improving security and access control for CI workflows.
* Fix CI to work for MacOS (#1772).
*
The release also includes various other minor enhancements and bug fixes aimed at improving the reliability and performance of the software.
Version 4.3.1
=============
Bug Fixes
---------
* Fix socketcand erroneously discarding frames (#1700)
* Fix initialization order in EtasBus (#1693, #1704)
Documentation
-------------
* Fix install instructions for neovi (#1694, #1697)
Version 4.3.0
=============
Breaking Changes
----------------
* Raise Minimum Python Version to 3.8 (#1597)
* Do not stop notifier if exception was handled (#1645)
Bug Fixes
---------
* Vector: channel detection fails, if there is an active flexray channel (#1634)
* ixxat: Fix exception in 'state' property on bus coupling errors (#1647)
* NeoVi: Fixed serial number range (#1650)
* PCAN: Fix timestamp offset due to timezone (#1651)
* Catch `pywintypes.error` in broadcast manager (#1659)
* Fix BLFReader error for incomplete or truncated stream (#1662)
* PCAN: remove Windows registry check to fix 32bit compatibility (#1672)
* Vector: Skip the `can_op_mode check` if the device reports `can_op_mode=0` (#1678)
* Vector: using the config from `detect_available_configs` might raise XL_ERR_INVALID_CHANNEL_MASK error (#1681)
Features
--------
### API
* Add `modifier_callback` parameter to `BusABC.send_periodic` for auto-modifying cyclic tasks (#703)
* Add `protocol` property to BusABC to determine active CAN Protocol (#1532)
* Change Bus constructor implementation and typing (#1557)
* Add optional `strict` parameter to relax BitTiming & BitTimingFd Validation (#1618)
* Add `BitTiming.iterate_from_sample_point` static methods (#1671)
### IO
* Can Player compatibility with interfaces that use additional configuration (#1610)
### Interface Improvements
* Kvaser: Add BitTiming/BitTimingFd support to KvaserBus (#1510)
* Ixxat: Implement `detect_available_configs` for the Ixxat bus. (#1607)
* NeoVi: Enable send and receive on network ID above 255 (#1627)
* Vector: Send HighPriority Message to flush Tx buffer (#1636)
* PCAN: Optimize send performance (#1640)
* PCAN: Support version string of older PCAN basic API (#1644)
* Kvaser: add parameter exclusive and `override_exclusive` (#1660)
* socketcand: Add parameter `tcp_tune` to reduce latency (#1683)
### Miscellaneous
* Distinguish Text/Binary-IO for Reader/Writer classes. (#1585)
* Convert setup.py to pyproject.toml (#1592)
* activate ruff pycodestyle checks (#1602)
* Update linter instructions in development.rst (#1603)
* remove unnecessary script files (#1604)
* BigEndian test fixes (#1625)
* align `ID:` in can.Message string (#1635)
* Use same configuration file as Linux on macOS (#1657)
* We do not need to account for drift when we `USE_WINDOWS_EVENTS` (#1666, #1679)
* Update linters, activate more ruff rules (#1669)
* Add Python 3.12 Support / Test Python 3.12 (#1673)
Version 4.2.2
=============
Bug Fixes
---------
* Fix socketcan KeyError (#1598, #1599).
* Fix IXXAT not properly shutdown message (#1606).
* Fix Mf4Reader and TRCReader incompatibility with extra CLI args (#1610).
* Fix decoding error in Kvaser constructor for non-ASCII product name (#1613).
Version 4.2.1
=============
Bug Fixes
---------
* The ASCWriter now logs the correct channel for error frames (#1578, #1583).
* Fix PCAN library detection (#1579, #1580).
* On Windows, the first two periodic frames were sent without delay (#1590).
Version 4.2.0
=============
Breaking Changes
----------------
* The ``can.BitTiming`` class was replaced with the new
``can.BitTiming`` and `can.BitTimingFd` classes (#1468, #1515).
Early adopters of ``can.BitTiming`` will need to update their code. Check the
[documentation](https://python-can.readthedocs.io/en/develop/bit_timing.html)
for more information. Currently, the following interfaces support the new classes:
* canalystii (#1468)
* cantact (#1468)
* nixnet (#1520)
* pcan (#1514)
* vector (#1470, #1516)
There are open pull requests for kvaser (#1510), slcan (#1512) and usb2can (#1511). Testing
and reviewing of these open PRs would be most appreciated.
Features
--------
### IO
* Add support for MF4 files (#1289).
* Add support for version 2 TRC files and other TRC file enhancements (#1530).
### Type Annotations
* Export symbols to satisfy type checkers (#1547, #1551, #1558, #1568).
### Interface Improvements
* Add ``__del__`` method to ``can.BusABC`` to automatically release resources (#1489, #1564).
* pcan: Update PCAN Basic to 4.6.2.753 (#1481).
* pcan: Use select instead of polling on Linux (#1410).
* socketcan: Use ip link JSON output in ``find_available_interfaces`` (#1478).
* socketcan: Enable SocketCAN interface tests in GitHub CI (#1484).
* slcan: improve receiving performance (#1490).
* usb2can: Stop using root logger (#1483).
* usb2can: Faster channel detection on Windows (#1480).
* vector: Only check sample point instead of tseg & sjw (#1486).
* vector: add VN5611 hwtype (#1501).
Documentation
-------------
* Add new section about related tools to documentation. Add a list of
plugin interface packages (#1457).
Bug Fixes
---------
* Automatic type conversion for config values (#1498, #1499).
* pcan: Fix ``Bus.__new__`` for CAN-FD interfaces (#1458, #1460).
* pcan: Fix Detection of Library on Windows on ARM (#1463).
* socketcand: extended ID bug fixes (#1504, #1508).
* vector: improve robustness against unknown HardwareType values (#1500, #1502).
Deprecations
------------
* The ``bustype`` parameter of ``can.Bus`` is deprecated and will be
removed in version 5.0, use ``interface`` instead. (#1462).
* The ``context`` parameter of ``can.Bus`` is deprecated and will be
removed in version 5.0, use ``config_context`` instead. (#1474).
* The ``bit_timing`` parameter of ``CantactBus`` is deprecated and will be
removed in version 5.0, use ``timing`` instead. (#1468).
* The ``bit_timing`` parameter of ``CANalystIIBus`` is deprecated and will be
removed in version 5.0, use ``timing`` instead. (#1468).
* The ``brs`` and ``log_errors`` parameters of `` NiXNETcanBus`` are deprecated
and will be removed in version 5.0. (#1520).
Miscellaneous
-------------
* Use high resolution timer on Windows to improve
timing precision for BroadcastManager (#1449).
* Improve ThreadBasedCyclicSendTask timing (#1539).
* Make code examples executable on Linux (#1452).
* Fix CanFilter type annotation (#1456).
* Fix ``The entry_points().get`` deprecation warning and improve
type annotation of ``can.interfaces.BACKENDS`` (#1465).
* Add ``ignore_config`` parameter to ``can.Bus`` (#1474).
* Add deprecation period to utility function ``deprecated_args_alias`` (#1477).
* Add `ruff` to the CI system (#1551)
Version 4.1.0
=============
Breaking Changes
----------------
* ``windows-curses`` was moved to optional dependencies (#1395).
Use ``pip install python-can[viewer]`` if you are using the ``can.viewer``
script on Windows.
* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed
from camelCase to snake_case (#1422).
Features
--------
### IO
* The canutils logger preserves message direction (#1244)
and uses common interface names (e.g. can0) instead of just
channel numbers (#1271).
* The ``can.logger`` script accepts the ``-a, --append`` option
to add new data to an existing log file (#1326, #1327, #1361).
Currently only the blf-, canutils- and csv-formats are supported.
* All CLI ``extra_args`` are passed to the bus, logger
and player initialisation (#1366).
* Initial support for TRC files (#1217)
### Type Annotations
* python-can now includes the ``py.typed`` marker to support type checking
according to PEP 561 (#1344).
### Interface Improvements
* The gs_usb interface can be selected by device index instead
of USB bus/address. Loopback frames are now correctly marked
with the ``is_rx`` flag (#1270).
* The PCAN interface can be selected by its device ID instead
of just the channel name (#1346).
* The PCAN Bus implementation supports auto bus-off reset (#1345).
* SocketCAN: Make ``find_available_interfaces()`` find slcanX interfaces (#1369).
* Vector: Add xlGetReceiveQueueLevel, xlGenerateSyncPulse and
xlFlushReceiveQueue to xldriver (#1387).
* Vector: Raise a CanInitializationError, if the CAN settings can not
be applied according to the arguments of ``VectorBus.__init__`` (#1426).
* Ixxat bus now implements BusState api and detects errors (#1141)
Bug Fixes
---------
* Improve robustness of USB2CAN serial number detection (#1129).
* Fix channel2int conversion (#1268, #1269).
* Fix BLF timestamp conversion (#1266, #1273).
* Fix timestamp handling in udp_multicast on macOS (#1275, #1278).
* Fix failure to initiate the Neousys DLL (#1281).
* Fix AttributeError in IscanError (#1292, #1293).
* Add missing vector devices (#1296).
* Fix error for DLC > 8 in ASCReader (#1299, #1301).
* Set default mode for FileIOMessageWriter to wt instead of rt (#1303).
* Fix conversion for port number from config file (#1309).
* Fix fileno error on Windows (#1312, #1313, #1333).
* Remove redundant ``writer.stop()`` call that throws error (#1316, #1317).
* Detect and cast types of CLI ``extra_args`` (#1280, #1328).
* Fix ASC/CANoe incompatibility due to timestamp format (#1315, #1362).
* Fix MessageSync timings (#1372, #1374).
* Fix file name for compressed files in SizedRotatingLogger (#1382, #1683).
* Fix memory leak in neoVI bus where message_receipts grows with no limit (#1427).
* Raise ValueError if gzip is used with incompatible log formats (#1429).
* Allow restarting of transmission tasks for socketcan (#1440)
Miscellaneous
-------------
* Allow ICSApiError to be pickled and un-pickled (#1341)
* Sort interface names in CLI API to make documentation reproducible (#1342)
* Exclude repository-configuration from git-archive (#1343)
* Improve documentation (#1397, #1401, #1405, #1420, #1421, #1434)
* Officially support Python 3.11 (#1423)
* Migrate code coverage reporting from Codecov to Coveralls (#1430)
* Migrate building docs and publishing releases to PyPi from Travis-CI to GitHub Actions (#1433)
Version 4.0.0
====
TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching.
For more than two years, there was no major release of *python-can*.
However, development was very much active over most of this time, and many parts were switched out and improved.
Over this time, over 530 issues and PRs have been resolved or merged, and discussions took place in even more.
Statistics of the final diff: About 200 files changed due to ~22k additions and ~7k deletions from more than thirty contributors.
This changelog diligently lists the major changes but does not promise to be the complete list of changes.
Therefore, users are strongly advised to thoroughly test their programs against this new version.
Re-reading the documentation for your interfaces might be helpful too as limitations and capabilities might have changed or are more explicit.
While we did try to avoid breaking changes, in some cases it was not feasible and in particular, many implementation details have changed.
Major features
--------------
* Type hints for the core library and some interfaces (#652 and many others)
* Support for Python 3.7-3.10+ only (dropped support for Python 2.* and 3.5-3.6) (#528 and many others)
* [Granular and unified exceptions](https://python-can.readthedocs.io/en/develop/api.html#errors) (#356, #562, #1025; overview in #1046)
* [Support for automatic configuration detection](https://python-can.readthedocs.io/en/develop/api.html#can.detect_available_configs) in most interfaces (#303, #640, #641, #811, #1077, #1085)
* Better alignment of interfaces and IO to common conventions and semantics
New interfaces
--------------
* udp_multicast (#644)
* robotell (#731)
* cantact (#853)
* gs_usb (#905)
* nixnet (#968, #1154)
* neousys (#980, #1076)
* socketcand (#1140)
* etas (#1144)
Improved interfaces
-------------------
* socketcan
* Support for multiple Cyclic Messages in Tasks (#610)
* Socketcan crash when attempting to stop CyclicSendTask with same arbitration ID (#605, #638, #720)
* Relax restriction of arbitration ID uniqueness for CyclicSendTask (#721, #785, #930)
* Add nanosecond resolution time stamping to socketcan (#938, #1015)
* Add support for changing the loopback flag (#960)
* Socketcan timestamps are missing sub-second precision (#1021, #1029)
* Add parameter to ignore CAN error frames (#1128)
* socketcan_ctypes
* Removed and replaced by socketcan after deprecation period
* socketcan_native
* Removed and replaced by socketcan after deprecation period
* vector
* Add chip state API (#635)
* Add methods to handle non message events (#708)
* Implement XLbusParams (#718)
* Add support for VN8900 xlGetChannelTime function (#732, #733)
* Add vector hardware config popup (#774)
* Fix Vector CANlib treatment of empty app name (#796, #814)
* Make VectorError pickleable (#848)
* Add methods get_application_config(), set_application_config() and set_timer_rate() to VectorBus (#849)
* Interface arguments are now lowercase (#858)
* Fix errors using multiple Vector devices (#898, #971, #977)
* Add more interface information to channel config (#917)
* Improve timestamp accuracy on Windows (#934, #936)
* Fix error with VN8900 (#1184)
* Add static typing (#1229)
* PCAN
* Do not incorrectly reset CANMsg.MSGTYPE on remote frame (#659, #681)
* Add support for error frames (#711)
* Added keycheck for windows platform for better error message (#724)
* Added status_string method to return simple status strings (#725)
* Fix timestamp timezone offset (#777, #778)
* Add [Cygwin](https://www.cygwin.com/) support (#840)
* Update PCAN basic Python file to February 7, 2020 (#929)
* Fix compatibility with the latest macOS SDK (#947, #948, #957, #976)
* Allow numerical channel specifier (#981, #982)
* macOS: Try to find libPCBUSB.dylib before loading it (#983, #984)
* Disable command PCAN_ALLOW_ERROR_FRAMES on macOS (#985)
* Force english error messages (#986, #993, #994)
* Add set/get device number (#987)
* Timestamps are silently incorrect on Windows without uptime installed (#1053, #1093)
* Implement check for minimum version of pcan library (#1065, #1188)
* Handle case where uptime is imported successfully but returns None (#1102, #1103)
* slcan
* Fix bitrate setting (#691)
* Fix fileno crash on Windows (#924)
* ics_neovi
* Filter out Tx error messages (#854)
* Adding support for send timeout (#855)
* Raising more precise API error when set bitrate fails (#865)
* Avoid flooding the logger with many errors when they are the same (#1125)
* Omit the transmit exception cause for brevity (#1086)
* Raise ValueError if message data is over max frame length (#1177, #1181)
* Setting is_error_frame message property (#1189)
* ixxat
* Raise exception on busoff in recv() (#856)
* Add support for 666 kbit/s bitrate (#911)
* Add function to list hwids of available devices (#926)
* Add CAN FD support (#1119)
* seeed
* Fix fileno crash on Windows (#902)
* kvaser
* Improve timestamp accuracy on Windows (#934, #936)
* usb2can
* Fix "Error 8" on Windows and provide better error messages (#989)
* Fix crash on initialization (#1248, #1249)
* Pass flags instead of flags_t type upon initialization (#1252)
* serial
* Fix "TypeError: cannot unpack non-iterable NoneType" and more robust error handling (#1000, #1010)
* canalystii
* Fix is_extended_id (#1006)
* Fix transmitting onto a busy bus (#1114)
* Replace binary library with python driver (#726, #1127)
Other API changes and improvements
----------------------------------
* CAN FD frame support is pretty complete (#963)
* ASCWriter (#604) and ASCReader (#741)
* Canutils reader and writer (#1042)
* Logger, viewer and player tools can handle CAN FD (#632)
* Many bugfixes and more testing coverage
* IO
* [Log rotation](https://python-can.readthedocs.io/en/develop/listeners.html#can.SizedRotatingLogger) (#648, #874, #881, #1147)
* Transparent (de)compression of [gzip](https://docs.python.org/3/library/gzip.html) files for all formats (#1221)
* Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783)
* ASCReader/Writer enhancements like increased robustness (#820, #1223, #1256, #1257)
* Adding absolute timestamps to ASC reader (#761)
* Support other base number (radix) at ASCReader (#764)
* Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194)
* Adding support for gzipped ASC logging file (.asc.gz) (#1138)
* Improve [IO class hierarchy](https://python-can.readthedocs.io/en/develop/internal-api.html#module-can.io.generic) (#1147)
* An [overview over various "virtual" interfaces](https://python-can.readthedocs.io/en/develop/interfaces/virtual.html#other-virtual-interfaces) (#644)
* Make ThreadBasedCyclicSendTask event based & improve timing accuracy (#656)
* Ignore error frames in can.player by default, add --error-frames option (#690)
* Add an error callback to ThreadBasedCyclicSendTask (#743, #781)
* Add direction to CAN messages (#773, #779, #780, #852, #966)
* Notifier no longer raises handled exceptions in rx_thread (#775, #789) but does so if no listener handles them (#1039, #1040)
* Changes to serial device number decoding (#869)
* Add a default fileno function to the BusABC (#877)
* Disallow Messages to simultaneously be "FD" and "remote" (#1049)
* Speed up interface plugin imports by avoiding pkg_resources (#1110)
* Allowing for extra config arguments in can.logger (#1142, #1170)
* Add changed byte highlighting to viewer.py (#1159)
* Change DLC to DL in Message.\_\_str\_\_() (#1212)
Other Bugfixes
--------------
* BLF PDU padding (#459)
* stop_all_periodic_tasks skipping every other task (#634, #637, #645)
* Preserve capitalization when reading config files (#702, #1062)
* ASCReader: Skip J1939Tp messages (#701)
* Fix crash in Canutils Log Reader when parsing RTR frames (#713)
* Various problems with the installation of the library
* ASCWriter: Fix date format to show correct day of month (#754)
* Fixes that some BLF files can't be read ( #763, #765)
* Seek for start of object instead of calculating it (#786, #803, #806)
* Only import winreg when on Windows (#800, #802)
* Find the correct Reader/Writer independently of the file extension case (#895)
* RecursionError when unpickling message object (#804, #885, #904)
* Move "filelock" to neovi dependencies (#943)
* Bus() with "fd" parameter as type bool always resolved to fd-enabled configuration (#954, #956)
* Asyncio code hits error due to deprecated loop parameter (#1005, #1013)
* Catch time before 1970 in ASCReader (#1034)
* Fix a bug where error handlers were not called correctly (#1116)
* Improved user interface of viewer script (#1118)
* Correct app_name argument in logger (#1151)
* Calling stop_all_periodic_tasks() in BusABC.shutdown() and all interfaces call it on shutdown (#1174)
* Timing configurations do not allow int (#1175)
* Some smaller bugfixes are not listed here since the problems were never part of a proper release
* ASCReader & ASCWriter using DLC as data length (#1245, #1246)
Behind the scenes & Quality assurance
-------------------------------------
* We publish both source distributions (`sdist`) and binary wheels (`bdist_wheel`) (#1059, #1071)
* Many interfaces were partly rewritten to modernize the code or to better handle errors
* Performance improvements
* Dependencies have changed
* Derive type information in Sphinx docs directly from type hints (#654)
* Better documentation in many, many places; This includes the examples, README and python-can developer resources
* Add issue templates (#1008, #1017, #1018, #1178)
* Many continuous integration (CI) discussions & improvements (for example: #951, #940, #1032)
* Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651)
* Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870)
* Use [Mergify](https://mergify.com/) (#821, #835, #937)
* Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with mostly [GitHub Actions](https://docs.github.com/en/actions) (#827, #1224)
* Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950)
* [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces
* Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198)
Version 3.3.4
====
Last call for Python2 support.
* #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3.
Version 3.3.3
====
Backported fixes from 4.x development branch which targets Python 3.
* #798 Backport caching msg.data value in neovi interface.
* #796 Fix Vector CANlib treatment of empty app name.
* #771 Handle empty CSV file.
* #741 ASCII reader can now handle FD frames.
* #740 Exclude test packages from distribution.
* #713 RTR crash fix in canutils log reader parsing RTR frames.
* #701 Skip J1939 messages in ASC Reader.
* #690 Exposes a configuration option to allow the CAN message player to send error frames
(and sets the default to not send error frames).
* #638 Fixes the semantics provided by periodic tasks in SocketCAN interface.
* #628 Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes.
* #617 Fixes the broken CANalyst-II interface.
* #605 Socketcan BCM status fix.
Version 3.3.2
====
Minor bug fix release addressing issue in PCAN RTR.
Version 3.3.1
====
Minor fix to setup.py to only require pytest-runner when necessary.
Version 3.3.0
====
* Adding CAN FD 64 frame support to blf reader
* Updates to installation instructions
* Clean up bits generator in PCAN interface #588
* Minor fix to use latest tools when building wheels on travis.
Version 3.2.1
====
* CAN FD 64 frame support to blf reader
* Minor fix to use latest tools when building wheels on travis.
* Updates links in documentation.
Version 3.2.0
====
Major features
--------------
* FD support added for Pcan by @bmeisels with input from
@markuspi, @christiansandberg & @felixdivo in PR #537
* This is the last version of python-can which will support Python 2.7
and Python 3.5. Support has been removed for Python 3.4 in this
release in PR #532
Other notable changes
---------------------
* #533 BusState is now an enum.
* #535 This release should automatically be published to PyPi by travis.
* #577 Travis-ci now uses stages.
* #548 A guide has been added for new io formats.
* #550 Finish moving from nose to pytest.
* #558 Fix installation on Windows.
* #561 Tests for MessageSync added.
General fixes, cleanup and docs changes can be found on the GitHub milestone
https://github.com/hardbyte/python-can/milestone/7?closed=1
Pulls: #522, #526, #527, #536, #540, #546, #547, #548, #533, #559, #569, #571, #572, #575
Backend Specific Changes
------------------------
pcan
~~~~
* FD
slcan
~~~~
* ability to set custom can speed instead of using predefined speed values. #553
socketcan
~~~~
* Bug fix to properly support 32bit systems. #573
usb2can
~~~~
* slightly better error handling
* multiple serial devices can be found
* support for the `_detect_available_configs()` API
Pulls #511, #535
vector
~~~~
* handle `app_name`. #525
Version 3.1.1
====
Major features
--------------
Two new interfaces this release:
- SYSTEC contributed by @idaniel86 in PR #466
- CANalyst-II contributed by @smeng9 in PR #476
Other notable changes
---------------------
* #477 The kvaser interface now supports bus statistics via a custom bus method.
* #434 neovi now supports receiving own messages
* #490 Adding option to override the neovi library name
* #488 Allow simultaneous access to IXXAT cards
* #447 Improvements to serial interface:
* to allow receiving partial messages
* to fix issue with DLC of remote frames
* addition of unit tests
* #497 Small API changes to `Message` and added unit tests
* #471 Fix CAN FD issue in kvaser interface
* #462 Fix `Notifier` issue with asyncio
* #481 Fix PCAN support on OSX
* #455 Fix to `Message` initializer
* Small bugfixes and improvements
Version 3.1.0
====
Version 3.1.0 was built with old wheel and/or setuptools
packages and was replaced with v3.1.1 after an installation
but was discovered.
Version 3.0.0
====
Major features
--------------
* Adds support for developing `asyncio` applications with `python-can` more easily. This can be useful
when implementing protocols that handles simultaneous connections to many nodes since you can write
synchronous looking code without handling multiple threads and locking mechanisms. #388
* New can viewer terminal application. (`python -m can.viewer`) #390
* More formally adds task management responsibility to the `Bus`. By default tasks created with
`bus.send_periodic` will have a reference held by the bus - this means in many cases the user
doesn't need to keep the task in scope for their periodic messages to continue being sent. If
this behavior isn't desired pass `store_task=False` to the `send_periodic` method. Stop all tasks
by calling the bus's new `stop_all_periodic_tasks` method. #412
Breaking changes
----------------
* Interfaces should no longer override `send_periodic` and instead implement
`_send_periodic_internal` to allow the Bus base class to manage tasks. #426
* writing to closed writers is not supported any more (it was supported only for some)
* the file in the reader/writer is now always stored in the attribute uniformly called `file`, and not in
something like `fp`, `log_file` or `output_file`. Changed the name of the first parameter of the
read/writer constructors from `filename` to `file`.
Other notable changes
---------------------
* can.Message class updated #413
- Addition of a `Message.equals` method.
- Deprecate id_type in favor of is_extended_id
- Initializer parameter extended_id deprecated in favor of is_extended_id
- documentation, testing and example updates
- Addition of support for various builtins: __repr__, __slots__, __copy__
* IO module updates to bring consistency to the different CAN message writers and readers. #348
- context manager support for all readers and writers
- they share a common super class called `BaseIOHandler`
- all file handles can now be closed with the `stop()` method
- the table name in `SqliteReader`/`SqliteWriter` can be adjusted
- append mode added in `CSVWriter` and `CanutilsLogWriter`
- [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) and
[path-like](https://docs.python.org/3/glossary.html#term-path-like-object) objects can now be passed to
the readers and writers (except to the Sqlite handlers)
- add a `__ne__()` method to the `Message` class (this was required by the tests)
- added a `stop()` method for `BufferedReader`
- `SqliteWriter`: this now guarantees that all messages are being written, exposes some previously internal metrics
and only buffers messages up to a certain limit before writing/committing to the database.
- the unused `header_line` attribute from `CSVReader` has been removed
- privatized some attributes that are only to be used internally in the classes
- the method `Listener.on_message_received()` is now abstract (using `@abc.abstractmethod`)
* Start testing against Python 3.7 #380
* All scripts have been moved into `can/scripts`. #370, #406
* Added support for additional sections to the config #338
* Code coverage reports added. #346, #374
* Bug fix to thread safe bus. #397
General fixes, cleanup and docs changes: (#347, #348, #367, #368, #370, #371, #373, #420, #417, #419, #432)
Backend Specific Changes
------------------------
3rd party interfaces
~~~~~~~~~~~~~~~~~~~~
* Deprecated `python_can.interface` entry point instead use `can.interface`. #389
neovi
~~~~~
* Added support for CAN-FD #408
* Fix issues checking if bus is open. #381
* Adding multiple channels support. #415
nican
~~~~~
* implements reset instead of custom `flush_tx_buffer`. #364
pcan
~~~~
* now supported on OSX. #365
serial
~~~~~~
* Removed TextIOWrapper from serial. #383
* switch to `serial_for_url` enabling using remote ports via `loop://`, ``socket://` and `rfc2217://` URLs. #393
* hardware handshake using `rtscts` kwarg #402
socketcan
~~~~~~~~~
* socketcan tasks now reuse a bcm socket #404, #425, #426,
* socketcan bugfix to receive error frames #384
vector
~~~~~~
* Vector interface now implements `_detect_available_configs`. #362
* Added support to select device by serial number. #387
Version 2.2.1 (2018-07-12)
=====
* Fix errors and warnings when importing library on Windows
* Fix Vector backend raising ValueError when hardware is not connected
Version 2.2.0 (2018-06-30)
=====
* Fallback message filtering implemented in Python for interfaces that don't offer better accelerated mechanism.
* SocketCAN interfaces have been merged (Now use `socketcan` instead of either `socketcan_native` and `socketcan_ctypes`),
this is now completely transparent for the library user.
* automatic detection of available configs/channels in supported interfaces.
* Added synchronized (thread-safe) Bus variant.
* context manager support for the Bus class.
* Dropped support for Python 3.3 (officially reached end-of-life in Sept. 2017)
* Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in an upcoming major version)
Version 2.1.0 (2018-02-17)
=====
* Support for out of tree can interfaces with pluggy.
* Initial support for CAN-FD for socketcan_native and kvaser interfaces.
* Neovi interface now uses Intrepid Control Systems's own interface library.
* Improvements and new documentation for SQL reader/writer.
* Fix bug in neovi serial number decoding.
* Add testing on OSX to TravisCI
* Fix non english decoding error on pcan
* Other misc improvements and bug fixes
Version 2.0.0 (2018-01-05
=====
After an extended baking period we have finally tagged version 2.0.0!
Quite a few major changes from v1.x:
* New interfaces:
* Vector
* NI-CAN
* isCAN
* neoVI
* Simplified periodic send API with initial support for SocketCAN
* Protocols module including J1939 support removed
* Logger script moved to module `can.logger`
* New `can.player` script to replay log files
* BLF, ASC log file support added in new `can.io` module
You can install from [PyPi](https://pypi.python.org/pypi/python-can/2.0.0) with pip:
```
pip install python-can==2.0.0
```
The documentation for v2.0.0 is available at http://python-can.readthedocs.io/en/2.0.0/
python-can-4.5.0/CONTRIBUTING.md 0000664 0000000 0000000 00000000236 14722003266 0016034 0 ustar 00root root 0000000 0000000 Please read the [Development - Contributing](https://python-can.readthedocs.io/en/stable/development.html#contributing) guidelines in the documentation site.
python-can-4.5.0/CONTRIBUTORS.txt 0000664 0000000 0000000 00000002302 14722003266 0016275 0 ustar 00root root 0000000 0000000 https://github.com/hardbyte/python-can/graphs/contributors
Ben Powell
Brian Thorne
Geert Linders
Mark Catley
Phillip Dixon
Rose Lu
Karl van Workum
Albert Bloomfield
Sam Bristow
Ethan Zonca
Robert Kaye
Andrew Beal
Jonas Frid
Tynan McAuley
Bruno Pennati
Jack Jester-Weinstein
Joshua Villyard
Giuseppe Corbelli
Christian Sandberg
Eduard Bröcker
Boris Wenzlaff
Pierre-Luc Tessier Gagné
Felix Divo
Kristian Sloth Lauszus
Shaoyu Meng
Alexander Mueller
Jan Goeteyn
ykzheng
Lear Corporation
Nick Black
Francisco Javier Burgos Macia
Felix Nieuwenhuizen
@marcel-kanter
@bessman
@koberbe
@tamenol
@deonvdw
@ptoews
@chrisoro
@sou1hacker
@auden-rovellequartz
@typecprint
@tysonite
@Joey5337
@Aajn
@josko7452
@leventerevesz
@ericevenchick
@ixygo
@Gussy
@altendky
@philsc
@rliebscher
@jxltom
@kdschlosser
@tojoh511
@s0kr4t3s
@jaesc
@NiallDarwin
@sdorre
@gordon-epc
@willson556
@jjguti
@wiboticalex
@illuusio
@cperkulator
@simontegelid
@DawidRosinski
@fabiocrestani
@ChrisSweetKT
@ausserlesh
@wbarnha
@projectgus
@samsmith94
@alexey
@MattWoodhead
@nbusser
@domologic
@fjburgos
@pkess
@felixn
@Tbruno25
@RitheeshBaradwaj
@vijaysubbiah20
@liamkinne
@RitheeshBaradwaj
@BenGardiner
@bures
@NickCao
@SWolfSchunk
@belliriccardo
@cssedev
python-can-4.5.0/LICENSE.txt 0000664 0000000 0000000 00000016743 14722003266 0015440 0 ustar 00root root 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
python-can-4.5.0/MANIFEST.in 0000664 0000000 0000000 00000000122 14722003266 0015333 0 ustar 00root root 0000000 0000000 include *.txt
include test/*.py
include test/data/*.*
recursive-include doc *.rst
python-can-4.5.0/README.rst 0000664 0000000 0000000 00000012126 14722003266 0015273 0 ustar 00root root 0000000 0000000 python-can
==========
|pypi| |conda| |python_implementation| |downloads| |downloads_monthly|
|docs| |github-actions| |coverage| |mergify| |formatter|
.. |pypi| image:: https://img.shields.io/pypi/v/python-can.svg
:target: https://pypi.python.org/pypi/python-can/
:alt: Latest Version on PyPi
.. |conda| image:: https://img.shields.io/conda/v/conda-forge/python-can
:target: https://github.com/conda-forge/python-can-feedstock
:alt: Latest Version on conda-forge
.. |python_implementation| image:: https://img.shields.io/pypi/implementation/python-can
:target: https://pypi.python.org/pypi/python-can/
:alt: Supported Python implementations
.. |downloads| image:: https://static.pepy.tech/badge/python-can
:target: https://pepy.tech/project/python-can
:alt: Downloads on PePy
.. |downloads_monthly| image:: https://static.pepy.tech/badge/python-can/month
:target: https://pepy.tech/project/python-can
:alt: Monthly downloads on PePy
.. |formatter| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/python/black
:alt: This project uses the black formatter.
.. |docs| image:: https://readthedocs.org/projects/python-can/badge/?version=stable
:target: https://python-can.readthedocs.io/en/stable/
:alt: Documentation
.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/ci.yml/badge.svg
:target: https://github.com/hardbyte/python-can/actions/workflows/ci.yml
:alt: Github Actions workflow status
.. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=develop
:target: https://coveralls.io/github/hardbyte/python-can?branch=develop
:alt: Test coverage reports on Coveralls.io
.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat
:target: https://mergify.io
:alt: Mergify Status
The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed
to allow microcontrollers and devices to communicate with each other. It
has priority based bus arbitration and reliable deterministic
communication. It is used in cars, trucks, boats, wheelchairs and more.
The ``can`` package provides controller area network support for
Python developers; providing common abstractions to
different hardware devices, and a suite of utilities for sending and receiving
messages on a can bus.
The library currently supports CPython as well as PyPy and runs on Mac, Linux and Windows.
============================== ===========
Library Version Python
------------------------------ -----------
2.x 2.6+, 3.4+
3.x 2.7+, 3.5+
4.0+ 3.7+
4.3+ 3.8+
============================== ===========
Features
--------
- common abstractions for CAN communication
- support for many different backends (see the `docs `__)
- receiving, sending, and periodically sending messages
- normal and extended arbitration IDs
- `CAN FD `__ support
- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), MF4 (Measurement Data Format v4 by ASAM), TRC, CSV, SQLite, and Canutils log
- efficient in-kernel or in-hardware filtering of messages on supported interfaces
- bus configuration reading from a file or from environment variables
- command line tools for working with CAN buses (see the `docs `__)
- more
Example usage
-------------
``pip install python-can``
.. code:: python
# import the library
import can
# create a bus instance using 'with' statement,
# this will cause bus.shutdown() to be called on the block exit;
# many other interfaces are supported as well (see documentation)
with can.Bus(interface='socketcan',
channel='vcan0',
receive_own_messages=True) as bus:
# send a message
message = can.Message(arbitration_id=123, is_extended_id=True,
data=[0x11, 0x22, 0x33])
bus.send(message, timeout=0.2)
# iterate over received messages
for msg in bus:
print(f"{msg.arbitration_id:X}: {msg.data}")
# or use an asynchronous notifier
notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()])
You can find more information in the documentation, online at
`python-can.readthedocs.org `__.
Discussion
----------
If you run into bugs, you can file them in our
`issue tracker `__ on GitHub.
`Stackoverflow `__ has several
questions and answers tagged with ``python+can``.
Wherever we interact, we strive to follow the
`Python Community Code of Conduct `__.
Contributing
------------
See `doc/development.rst `__ for getting started.
python-can-4.5.0/can/ 0000775 0000000 0000000 00000000000 14722003266 0014343 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/__init__.py 0000664 0000000 0000000 00000005615 14722003266 0016463 0 ustar 00root root 0000000 0000000 """
The ``can`` package provides controller area network support for
Python developers; providing common abstractions to
different hardware devices, and a suite of utilities for sending and receiving
messages on a can bus.
"""
import contextlib
import logging
from importlib.metadata import PackageNotFoundError, version
from typing import Any, Dict
__all__ = [
"ASCReader",
"ASCWriter",
"AsyncBufferedReader",
"BitTiming",
"BitTimingFd",
"BLFReader",
"BLFWriter",
"BufferedReader",
"Bus",
"BusABC",
"BusState",
"CanError",
"CanInitializationError",
"CanInterfaceNotImplementedError",
"CanOperationError",
"CanProtocol",
"CanTimeoutError",
"CanutilsLogReader",
"CanutilsLogWriter",
"CSVReader",
"CSVWriter",
"CyclicSendTaskABC",
"LimitedDurationCyclicSendTaskABC",
"Listener",
"Logger",
"LogReader",
"ModifiableCyclicTaskABC",
"Message",
"MessageSync",
"MF4Reader",
"MF4Writer",
"Notifier",
"Printer",
"RedirectReader",
"RestartableCyclicTaskABC",
"SizedRotatingLogger",
"SqliteReader",
"SqliteWriter",
"ThreadSafeBus",
"TRCFileVersion",
"TRCReader",
"TRCWriter",
"VALID_INTERFACES",
"bit_timing",
"broadcastmanager",
"bus",
"ctypesutil",
"detect_available_configs",
"exceptions",
"interface",
"interfaces",
"io",
"listener",
"logconvert",
"log",
"logger",
"message",
"notifier",
"player",
"set_logging_level",
"thread_safe_bus",
"typechecking",
"util",
"viewer",
]
from . import typechecking # isort:skip
from . import util # isort:skip
from . import broadcastmanager, interface
from .bit_timing import BitTiming, BitTimingFd
from .broadcastmanager import (
CyclicSendTaskABC,
LimitedDurationCyclicSendTaskABC,
ModifiableCyclicTaskABC,
RestartableCyclicTaskABC,
)
from .bus import BusABC, BusState, CanProtocol
from .exceptions import (
CanError,
CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
CanTimeoutError,
)
from .interface import Bus, detect_available_configs
from .interfaces import VALID_INTERFACES
from .io import (
ASCReader,
ASCWriter,
BLFReader,
BLFWriter,
CanutilsLogReader,
CanutilsLogWriter,
CSVReader,
CSVWriter,
Logger,
LogReader,
MessageSync,
MF4Reader,
MF4Writer,
Printer,
SizedRotatingLogger,
SqliteReader,
SqliteWriter,
TRCFileVersion,
TRCReader,
TRCWriter,
)
from .listener import AsyncBufferedReader, BufferedReader, Listener, RedirectReader
from .message import Message
from .notifier import Notifier
from .thread_safe_bus import ThreadSafeBus
from .util import set_logging_level
with contextlib.suppress(PackageNotFoundError):
__version__ = version("python-can")
log = logging.getLogger("can")
rc: Dict[str, Any] = {}
python-can-4.5.0/can/_entry_points.py 0000664 0000000 0000000 00000001563 14722003266 0017616 0 ustar 00root root 0000000 0000000 import importlib
import sys
from dataclasses import dataclass
from importlib.metadata import entry_points
from typing import Any, List
@dataclass
class _EntryPoint:
key: str
module_name: str
class_name: str
def load(self) -> Any:
module = importlib.import_module(self.module_name)
return getattr(module, self.class_name)
# See https://docs.python.org/3/library/importlib.metadata.html#entry-points,
# "Compatibility Note".
if sys.version_info >= (3, 10):
def read_entry_points(group: str) -> List[_EntryPoint]:
return [
_EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group)
]
else:
def read_entry_points(group: str) -> List[_EntryPoint]:
return [
_EntryPoint(ep.name, *ep.value.split(":", maxsplit=1))
for ep in entry_points().get(group, [])
]
python-can-4.5.0/can/bit_timing.py 0000664 0000000 0000000 00000123026 14722003266 0017046 0 ustar 00root root 0000000 0000000 # pylint: disable=too-many-lines
import math
from typing import TYPE_CHECKING, Iterator, List, Mapping, cast
if TYPE_CHECKING:
from can.typechecking import BitTimingDict, BitTimingFdDict
class BitTiming(Mapping):
"""Representation of a bit timing configuration for a CAN 2.0 bus.
The class can be constructed in multiple ways, depending on the information
available. The preferred way is using CAN clock frequency, prescaler, tseg1, tseg2 and sjw::
can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=1, sjw=1)
Alternatively you can set the bitrate instead of the bit rate prescaler::
can.BitTiming.from_bitrate_and_segments(
f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=1, sjw=1
)
It is also possible to specify BTR registers::
can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14)
or to calculate the timings for a given sample point::
can.BitTiming.from_sample_point(f_clock=8_000_000, bitrate=1_000_000, sample_point=75.0)
"""
def __init__(
self,
f_clock: int,
brp: int,
tseg1: int,
tseg2: int,
sjw: int,
nof_samples: int = 1,
strict: bool = False,
) -> None:
"""
:param int f_clock:
The CAN system clock frequency in Hz.
:param int brp:
Bit rate prescaler.
:param int tseg1:
Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int tseg2:
Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int sjw:
The Synchronization Jump Width. Decides the maximum number of time quanta
that the controller can resynchronize every bit.
:param int nof_samples:
Either 1 or 3. Some CAN controllers can also sample each bit three times.
In this case, the bit will be sampled three quanta in a row,
with the last sample being taken in the edge between TSEG1 and TSEG2.
Three samples should only be used for relatively slow baudrates.
:param bool strict:
If True, restrict bit timings to the minimum required range as defined in
ISO 11898. This can be used to ensure compatibility across a wide variety
of CAN hardware.
:raises ValueError:
if the arguments are invalid.
"""
self._data: BitTimingDict = {
"f_clock": f_clock,
"brp": brp,
"tseg1": tseg1,
"tseg2": tseg2,
"sjw": sjw,
"nof_samples": nof_samples,
}
self._validate()
if strict:
self._restrict_to_minimum_range()
def _validate(self) -> None:
if not 1 <= self.brp <= 64:
raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].")
if not 1 <= self.tseg1 <= 16:
raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].")
if not 1 <= self.tseg2 <= 8:
raise ValueError(f"tseg2 (={self.tseg2}) must be in [1...8].")
if not 1 <= self.sjw <= 4:
raise ValueError(f"sjw (={self.sjw}) must be in [1...4].")
if self.sjw > self.tseg2:
raise ValueError(
f"sjw (={self.sjw}) must not be greater than tseg2 (={self.tseg2})."
)
if self.sample_point < 50.0:
raise ValueError(
f"The sample point must be greater than or equal to 50% "
f"(sample_point={self.sample_point:.2f}%)."
)
if self.nof_samples not in (1, 3):
raise ValueError("nof_samples must be 1 or 3")
def _restrict_to_minimum_range(self) -> None:
if not 8 <= self.nbt <= 25:
raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].")
if not 1 <= self.brp <= 32:
raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...32].")
if not 5_000 <= self.bitrate <= 1_000_000:
raise ValueError(
f"bitrate (={self.bitrate}) must be in [5,000...1,000,000]."
)
@classmethod
def from_bitrate_and_segments(
cls,
f_clock: int,
bitrate: int,
tseg1: int,
tseg2: int,
sjw: int,
nof_samples: int = 1,
strict: bool = False,
) -> "BitTiming":
"""Create a :class:`~can.BitTiming` instance from bitrate and segment lengths.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int bitrate:
Bitrate in bit/s.
:param int tseg1:
Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int tseg2:
Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int sjw:
The Synchronization Jump Width. Decides the maximum number of time quanta
that the controller can resynchronize every bit.
:param int nof_samples:
Either 1 or 3. Some CAN controllers can also sample each bit three times.
In this case, the bit will be sampled three quanta in a row,
with the last sample being taken in the edge between TSEG1 and TSEG2.
Three samples should only be used for relatively slow baudrates.
:param bool strict:
If True, restrict bit timings to the minimum required range as defined in
ISO 11898. This can be used to ensure compatibility across a wide variety
of CAN hardware.
:raises ValueError:
if the arguments are invalid.
"""
try:
brp = int(round(f_clock / (bitrate * (1 + tseg1 + tseg2))))
except ZeroDivisionError:
raise ValueError("Invalid inputs") from None
bt = cls(
f_clock=f_clock,
brp=brp,
tseg1=tseg1,
tseg2=tseg2,
sjw=sjw,
nof_samples=nof_samples,
strict=strict,
)
if abs(bt.bitrate - bitrate) > bitrate / 256:
raise ValueError(
f"the effective bitrate (={bt.bitrate}) diverges "
f"from the requested bitrate (={bitrate})"
)
return bt
@classmethod
def from_registers(
cls,
f_clock: int,
btr0: int,
btr1: int,
) -> "BitTiming":
"""Create a :class:`~can.BitTiming` instance from registers btr0 and btr1.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int btr0:
The BTR0 register value used by many CAN controllers.
:param int btr1:
The BTR1 register value used by many CAN controllers.
:raises ValueError:
if the arguments are invalid.
"""
if not 0 <= btr0 < 2**16:
raise ValueError(f"Invalid btr0 value. ({btr0})")
if not 0 <= btr1 < 2**16:
raise ValueError(f"Invalid btr1 value. ({btr1})")
brp = (btr0 & 0x3F) + 1
sjw = (btr0 >> 6) + 1
tseg1 = (btr1 & 0xF) + 1
tseg2 = ((btr1 >> 4) & 0x7) + 1
nof_samples = 3 if btr1 & 0x80 else 1
return cls(
brp=brp,
f_clock=f_clock,
tseg1=tseg1,
tseg2=tseg2,
sjw=sjw,
nof_samples=nof_samples,
)
@classmethod
def iterate_from_sample_point(
cls, f_clock: int, bitrate: int, sample_point: float = 69.0
) -> Iterator["BitTiming"]:
"""Create a :class:`~can.BitTiming` iterator with all the solutions for a sample point.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int bitrate:
Bitrate in bit/s.
:param int sample_point:
The sample point value in percent.
:raises ValueError:
if the arguments are invalid.
"""
if sample_point < 50.0:
raise ValueError(f"sample_point (={sample_point}) must not be below 50%.")
for brp in range(1, 65):
nbt = round(int(f_clock / (bitrate * brp)))
if nbt < 8:
break
effective_bitrate = f_clock / (nbt * brp)
if abs(effective_bitrate - bitrate) > bitrate / 256:
continue
tseg1 = int(round(sample_point / 100 * nbt)) - 1
# limit tseg1, so tseg2 is at least 1 TQ
tseg1 = min(tseg1, nbt - 2)
tseg2 = nbt - tseg1 - 1
sjw = min(tseg2, 4)
try:
bt = BitTiming(
f_clock=f_clock,
brp=brp,
tseg1=tseg1,
tseg2=tseg2,
sjw=sjw,
strict=True,
)
yield bt
except ValueError:
continue
@classmethod
def from_sample_point(
cls, f_clock: int, bitrate: int, sample_point: float = 69.0
) -> "BitTiming":
"""Create a :class:`~can.BitTiming` instance for a sample point.
This function tries to find bit timings, which are close to the requested
sample point. It does not take physical bus properties into account, so the
calculated bus timings might not work properly for you.
The :func:`oscillator_tolerance` function might be helpful to evaluate the
bus timings.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int bitrate:
Bitrate in bit/s.
:param int sample_point:
The sample point value in percent.
:raises ValueError:
if the arguments are invalid.
"""
if sample_point < 50.0:
raise ValueError(f"sample_point (={sample_point}) must not be below 50%.")
possible_solutions: List[BitTiming] = list(
cls.iterate_from_sample_point(f_clock, bitrate, sample_point)
)
if not possible_solutions:
raise ValueError("No suitable bit timings found.")
# sort solutions
for key, reverse in (
# prefer low prescaler
(lambda x: x.brp, False),
# prefer low sample point deviation from requested values
(lambda x: abs(x.sample_point - sample_point), False),
):
possible_solutions.sort(key=key, reverse=reverse)
return possible_solutions[0]
@property
def f_clock(self) -> int:
"""The CAN system clock frequency in Hz."""
return self._data["f_clock"]
@property
def bitrate(self) -> int:
"""Bitrate in bits/s."""
return int(round(self.f_clock / (self.nbt * self.brp)))
@property
def brp(self) -> int:
"""Bit Rate Prescaler."""
return self._data["brp"]
@property
def tq(self) -> int:
"""Time quantum in nanoseconds"""
return int(round(self.brp / self.f_clock * 1e9))
@property
def nbt(self) -> int:
"""Nominal Bit Time."""
return 1 + self.tseg1 + self.tseg2
@property
def tseg1(self) -> int:
"""Time segment 1.
The number of quanta from (but not including) the Sync Segment to the sampling point.
"""
return self._data["tseg1"]
@property
def tseg2(self) -> int:
"""Time segment 2.
The number of quanta from the sampling point to the end of the bit.
"""
return self._data["tseg2"]
@property
def sjw(self) -> int:
"""Synchronization Jump Width."""
return self._data["sjw"]
@property
def nof_samples(self) -> int:
"""Number of samples (1 or 3)."""
return self._data["nof_samples"]
@property
def sample_point(self) -> float:
"""Sample point in percent."""
return 100.0 * (1 + self.tseg1) / (1 + self.tseg1 + self.tseg2)
@property
def btr0(self) -> int:
"""Bit timing register 0 for SJA1000."""
return (self.sjw - 1) << 6 | self.brp - 1
@property
def btr1(self) -> int:
"""Bit timing register 1 for SJA1000."""
sam = 1 if self.nof_samples == 3 else 0
return sam << 7 | (self.tseg2 - 1) << 4 | self.tseg1 - 1
def oscillator_tolerance(
self,
node_loop_delay_ns: float = 250.0,
bus_length_m: float = 10.0,
) -> float:
"""Oscillator tolerance in percent according to ISO 11898-1.
:param float node_loop_delay_ns:
Transceiver loop delay in nanoseconds.
:param float bus_length_m:
Bus length in meters.
"""
delay_per_meter = 5
bidirectional_propagation_delay_ns = 2 * (
node_loop_delay_ns + delay_per_meter * bus_length_m
)
prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.tq)
nom_phase_seg1 = self.tseg1 - prop_seg
nom_phase_seg2 = self.tseg2
df_clock_list = [
_oscillator_tolerance_condition_1(nom_sjw=self.sjw, nbt=self.nbt),
_oscillator_tolerance_condition_2(
nbt=self.nbt,
nom_phase_seg1=nom_phase_seg1,
nom_phase_seg2=nom_phase_seg2,
),
]
return max(0.0, min(df_clock_list) * 100)
def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
"""Return a new :class:`~can.BitTiming` instance with the given *f_clock* but the same
bit rate and sample point.
:param int f_clock:
The CAN system clock frequency in Hz.
:raises ValueError:
if no suitable bit timings were found.
"""
# try the most simple solution first: another bitrate prescaler
try:
return BitTiming.from_bitrate_and_segments(
f_clock=f_clock,
bitrate=self.bitrate,
tseg1=self.tseg1,
tseg2=self.tseg2,
sjw=self.sjw,
nof_samples=self.nof_samples,
strict=True,
)
except ValueError:
pass
# create a new timing instance with the same sample point
bt = BitTiming.from_sample_point(
f_clock=f_clock, bitrate=self.bitrate, sample_point=self.sample_point
)
if abs(bt.sample_point - self.sample_point) > 1.0:
raise ValueError(
"f_clock change failed because of sample point discrepancy."
)
# adapt synchronization jump width, so it has the same size relative to bit time as self
sjw = int(round(self.sjw / self.nbt * bt.nbt))
sjw = max(1, min(4, bt.tseg2, sjw))
bt._data["sjw"] = sjw # pylint: disable=protected-access
bt._data["nof_samples"] = self.nof_samples # pylint: disable=protected-access
bt._validate() # pylint: disable=protected-access
return bt
def __str__(self) -> str:
segments = [
f"BR: {self.bitrate:_} bit/s",
f"SP: {self.sample_point:.2f}%",
f"BRP: {self.brp}",
f"TSEG1: {self.tseg1}",
f"TSEG2: {self.tseg2}",
f"SJW: {self.sjw}",
f"BTR: {self.btr0:02X}{self.btr1:02X}h",
f"CLK: {self.f_clock / 1e6:.0f}MHz",
]
return ", ".join(segments)
def __repr__(self) -> str:
args = ", ".join(f"{key}={value}" for key, value in self.items())
return f"can.{self.__class__.__name__}({args})"
def __getitem__(self, key: str) -> int:
return cast(int, self._data.__getitem__(key))
def __len__(self) -> int:
return self._data.__len__()
def __iter__(self) -> Iterator[str]:
return self._data.__iter__()
def __eq__(self, other: object) -> bool:
if not isinstance(other, BitTiming):
return False
return self._data == other._data
def __hash__(self) -> int:
return tuple(self._data.values()).__hash__()
class BitTimingFd(Mapping):
"""Representation of a bit timing configuration for a CAN FD bus.
The class can be constructed in multiple ways, depending on the information
available. The preferred way is using CAN clock frequency, bit rate prescaler, tseg1,
tseg2 and sjw for both the arbitration (nominal) and data phase::
can.BitTimingFd(
f_clock=80_000_000,
nom_brp=1,
nom_tseg1=59,
nom_tseg2=20,
nom_sjw=10,
data_brp=1,
data_tseg1=6,
data_tseg2=3,
data_sjw=2,
)
Alternatively you can set the bit rates instead of the bit rate prescalers::
can.BitTimingFd.from_bitrate_and_segments(
f_clock=80_000_000,
nom_bitrate=1_000_000,
nom_tseg1=59,
nom_tseg2=20,
nom_sjw=10,
data_bitrate=8_000_000,
data_tseg1=6,
data_tseg2=3,
data_sjw=2,
)
It is also possible to calculate the timings for a given
pair of arbitration and data sample points::
can.BitTimingFd.from_sample_point(
f_clock=80_000_000,
nom_bitrate=1_000_000,
nom_sample_point=75.0,
data_bitrate=8_000_000,
data_sample_point=70.0,
)
"""
def __init__( # pylint: disable=too-many-arguments
self,
f_clock: int,
nom_brp: int,
nom_tseg1: int,
nom_tseg2: int,
nom_sjw: int,
data_brp: int,
data_tseg1: int,
data_tseg2: int,
data_sjw: int,
strict: bool = False,
) -> None:
"""
Initialize a BitTimingFd instance with the specified parameters.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int nom_brp:
Nominal (arbitration) phase bitrate prescaler.
:param int nom_tseg1:
Nominal phase Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int nom_tseg2:
Nominal phase Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int nom_sjw:
The Synchronization Jump Width for the nominal phase. This value determines
the maximum number of time quanta that the controller can resynchronize every bit.
:param int data_brp:
Data phase bitrate prescaler.
:param int data_tseg1:
Data phase Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int data_tseg2:
Data phase Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int data_sjw:
The Synchronization Jump Width for the data phase. This value determines
the maximum number of time quanta that the controller can resynchronize every bit.
:param bool strict:
If True, restrict bit timings to the minimum required range as defined in
ISO 11898. This can be used to ensure compatibility across a wide variety
of CAN hardware.
:raises ValueError:
if the arguments are invalid.
"""
self._data: BitTimingFdDict = {
"f_clock": f_clock,
"nom_brp": nom_brp,
"nom_tseg1": nom_tseg1,
"nom_tseg2": nom_tseg2,
"nom_sjw": nom_sjw,
"data_brp": data_brp,
"data_tseg1": data_tseg1,
"data_tseg2": data_tseg2,
"data_sjw": data_sjw,
}
self._validate()
if strict:
self._restrict_to_minimum_range()
def _validate(self) -> None:
for param, value in self._data.items():
if value < 0: # type: ignore[operator]
err_msg = f"'{param}' (={value}) must not be negative."
raise ValueError(err_msg)
if self.nom_brp < 1:
raise ValueError(
f"nominal bitrate prescaler (={self.nom_brp}) must be at least 1."
)
if self.data_brp < 1:
raise ValueError(
f"data bitrate prescaler (={self.data_brp}) must be at least 1."
)
if self.data_bitrate < self.nom_bitrate:
raise ValueError(
f"data_bitrate (={self.data_bitrate}) must be greater than or "
f"equal to nom_bitrate (={self.nom_bitrate})"
)
if self.nom_sjw > self.nom_tseg2:
raise ValueError(
f"nom_sjw (={self.nom_sjw}) must not be "
f"greater than nom_tseg2 (={self.nom_tseg2})."
)
if self.data_sjw > self.data_tseg2:
raise ValueError(
f"data_sjw (={self.data_sjw}) must not be "
f"greater than data_tseg2 (={self.data_tseg2})."
)
if self.nom_sample_point < 50.0:
raise ValueError(
f"The arbitration sample point must be greater than or equal to 50% "
f"(nom_sample_point={self.nom_sample_point:.2f}%)."
)
if self.data_sample_point < 50.0:
raise ValueError(
f"The data sample point must be greater than or equal to 50% "
f"(data_sample_point={self.data_sample_point:.2f}%)."
)
def _restrict_to_minimum_range(self) -> None:
# restrict to minimum required range as defined in ISO 11898
if not 8 <= self.nbt <= 80:
raise ValueError(f"Nominal bit time (={self.nbt}) must be in [8...80]")
if not 5 <= self.dbt <= 25:
raise ValueError(f"Nominal bit time (={self.dbt}) must be in [5...25]")
if not 1 <= self.data_tseg1 <= 16:
raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...16].")
if not 2 <= self.data_tseg2 <= 8:
raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [2...8].")
if not 1 <= self.data_sjw <= 8:
raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...8].")
if self.nom_brp == self.data_brp:
# shared prescaler
if not 2 <= self.nom_tseg1 <= 128:
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...128].")
if not 2 <= self.nom_tseg2 <= 32:
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...32].")
if not 1 <= self.nom_sjw <= 32:
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...32].")
else:
# separate prescaler
if not 2 <= self.nom_tseg1 <= 64:
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...64].")
if not 2 <= self.nom_tseg2 <= 16:
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...16].")
if not 1 <= self.nom_sjw <= 16:
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...16].")
@classmethod
def from_bitrate_and_segments( # pylint: disable=too-many-arguments
cls,
f_clock: int,
nom_bitrate: int,
nom_tseg1: int,
nom_tseg2: int,
nom_sjw: int,
data_bitrate: int,
data_tseg1: int,
data_tseg2: int,
data_sjw: int,
strict: bool = False,
) -> "BitTimingFd":
"""
Create a :class:`~can.BitTimingFd` instance with the bitrates and segments lengths.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int nom_bitrate:
Nominal (arbitration) phase bitrate in bit/s.
:param int nom_tseg1:
Nominal phase Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int nom_tseg2:
Nominal phase Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int nom_sjw:
The Synchronization Jump Width for the nominal phase. This value determines
the maximum number of time quanta that the controller can resynchronize every bit.
:param int data_bitrate:
Data phase bitrate in bit/s.
:param int data_tseg1:
Data phase Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
:param int data_tseg2:
Data phase Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int data_sjw:
The Synchronization Jump Width for the data phase. This value determines
the maximum number of time quanta that the controller can resynchronize every bit.
:param bool strict:
If True, restrict bit timings to the minimum required range as defined in
ISO 11898. This can be used to ensure compatibility across a wide variety
of CAN hardware.
:raises ValueError:
if the arguments are invalid.
"""
try:
nom_brp = int(round(f_clock / (nom_bitrate * (1 + nom_tseg1 + nom_tseg2))))
data_brp = int(
round(f_clock / (data_bitrate * (1 + data_tseg1 + data_tseg2)))
)
except ZeroDivisionError:
raise ValueError("Invalid inputs.") from None
bt = cls(
f_clock=f_clock,
nom_brp=nom_brp,
nom_tseg1=nom_tseg1,
nom_tseg2=nom_tseg2,
nom_sjw=nom_sjw,
data_brp=data_brp,
data_tseg1=data_tseg1,
data_tseg2=data_tseg2,
data_sjw=data_sjw,
strict=strict,
)
if abs(bt.nom_bitrate - nom_bitrate) > nom_bitrate / 256:
raise ValueError(
f"the effective nom. bitrate (={bt.nom_bitrate}) diverges "
f"from the requested nom. bitrate (={nom_bitrate})"
)
if abs(bt.data_bitrate - data_bitrate) > data_bitrate / 256:
raise ValueError(
f"the effective data bitrate (={bt.data_bitrate}) diverges "
f"from the requested data bitrate (={data_bitrate})"
)
return bt
@classmethod
def iterate_from_sample_point(
cls,
f_clock: int,
nom_bitrate: int,
nom_sample_point: float,
data_bitrate: int,
data_sample_point: float,
) -> Iterator["BitTimingFd"]:
"""Create an :class:`~can.BitTimingFd` iterator with all the solutions for a sample point.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int nom_bitrate:
Nominal bitrate in bit/s.
:param int nom_sample_point:
The sample point value of the arbitration phase in percent.
:param int data_bitrate:
Data bitrate in bit/s.
:param int data_sample_point:
The sample point value of the data phase in percent.
:raises ValueError:
if the arguments are invalid.
"""
if nom_sample_point < 50.0:
raise ValueError(
f"nom_sample_point (={nom_sample_point}) must not be below 50%."
)
if data_sample_point < 50.0:
raise ValueError(
f"data_sample_point (={data_sample_point}) must not be below 50%."
)
sync_seg = 1
for nom_brp in range(1, 257):
nbt = round(int(f_clock / (nom_bitrate * nom_brp)))
if nbt < 1:
break
effective_nom_bitrate = f_clock / (nbt * nom_brp)
if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256:
continue
nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1
# limit tseg1, so tseg2 is at least 2 TQ
nom_tseg1 = min(nom_tseg1, nbt - sync_seg - 2)
nom_tseg2 = nbt - nom_tseg1 - 1
nom_sjw = min(nom_tseg2, 128)
for data_brp in range(1, 257):
dbt = round(int(f_clock / (data_bitrate * data_brp)))
if dbt < 1:
break
effective_data_bitrate = f_clock / (dbt * data_brp)
if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256:
continue
data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1
# limit tseg1, so tseg2 is at least 2 TQ
data_tseg1 = min(data_tseg1, dbt - sync_seg - 2)
data_tseg2 = dbt - data_tseg1 - 1
data_sjw = min(data_tseg2, 16)
try:
bt = BitTimingFd(
f_clock=f_clock,
nom_brp=nom_brp,
nom_tseg1=nom_tseg1,
nom_tseg2=nom_tseg2,
nom_sjw=nom_sjw,
data_brp=data_brp,
data_tseg1=data_tseg1,
data_tseg2=data_tseg2,
data_sjw=data_sjw,
strict=True,
)
yield bt
except ValueError:
continue
@classmethod
def from_sample_point(
cls,
f_clock: int,
nom_bitrate: int,
nom_sample_point: float,
data_bitrate: int,
data_sample_point: float,
) -> "BitTimingFd":
"""Create a :class:`~can.BitTimingFd` instance for a sample point.
This function tries to find bit timings, which are close to the requested
sample points. It does not take physical bus properties into account, so the
calculated bus timings might not work properly for you.
The :func:`oscillator_tolerance` function might be helpful to evaluate the
bus timings.
:param int f_clock:
The CAN system clock frequency in Hz.
:param int nom_bitrate:
Nominal bitrate in bit/s.
:param int nom_sample_point:
The sample point value of the arbitration phase in percent.
:param int data_bitrate:
Data bitrate in bit/s.
:param int data_sample_point:
The sample point value of the data phase in percent.
:raises ValueError:
if the arguments are invalid.
"""
if nom_sample_point < 50.0:
raise ValueError(
f"nom_sample_point (={nom_sample_point}) must not be below 50%."
)
if data_sample_point < 50.0:
raise ValueError(
f"data_sample_point (={data_sample_point}) must not be below 50%."
)
possible_solutions: List[BitTimingFd] = list(
cls.iterate_from_sample_point(
f_clock,
nom_bitrate,
nom_sample_point,
data_bitrate,
data_sample_point,
)
)
if not possible_solutions:
raise ValueError("No suitable bit timings found.")
# prefer using the same prescaler for arbitration and data phase
same_prescaler = list(
filter(lambda x: x.nom_brp == x.data_brp, possible_solutions)
)
if same_prescaler:
possible_solutions = same_prescaler
# sort solutions
for key, reverse in (
# prefer low prescaler
(lambda x: x.nom_brp + x.data_brp, False),
# prefer same prescaler for arbitration and data
(lambda x: abs(x.nom_brp - x.data_brp), False),
# prefer low sample point deviation from requested values
(
lambda x: (
abs(x.nom_sample_point - nom_sample_point)
+ abs(x.data_sample_point - data_sample_point)
),
False,
),
):
possible_solutions.sort(key=key, reverse=reverse)
return possible_solutions[0]
@property
def f_clock(self) -> int:
"""The CAN system clock frequency in Hz."""
return self._data["f_clock"]
@property
def nom_bitrate(self) -> int:
"""Nominal (arbitration phase) bitrate."""
return int(round(self.f_clock / (self.nbt * self.nom_brp)))
@property
def nom_brp(self) -> int:
"""Prescaler value for the arbitration phase."""
return self._data["nom_brp"]
@property
def nom_tq(self) -> int:
"""Nominal time quantum in nanoseconds"""
return int(round(self.nom_brp / self.f_clock * 1e9))
@property
def nbt(self) -> int:
"""Number of time quanta in a bit of the arbitration phase."""
return 1 + self.nom_tseg1 + self.nom_tseg2
@property
def nom_tseg1(self) -> int:
"""Time segment 1 value of the arbitration phase.
This is the sum of the propagation time segment and the phase buffer segment 1.
"""
return self._data["nom_tseg1"]
@property
def nom_tseg2(self) -> int:
"""Time segment 2 value of the arbitration phase. Also known as phase buffer segment 2."""
return self._data["nom_tseg2"]
@property
def nom_sjw(self) -> int:
"""Synchronization jump width of the arbitration phase.
The phase buffer segments may be shortened or lengthened by this value.
"""
return self._data["nom_sjw"]
@property
def nom_sample_point(self) -> float:
"""Sample point of the arbitration phase in percent."""
return 100.0 * (1 + self.nom_tseg1) / (1 + self.nom_tseg1 + self.nom_tseg2)
@property
def data_bitrate(self) -> int:
"""Bitrate of the data phase in bit/s."""
return int(round(self.f_clock / (self.dbt * self.data_brp)))
@property
def data_brp(self) -> int:
"""Prescaler value for the data phase."""
return self._data["data_brp"]
@property
def data_tq(self) -> int:
"""Data time quantum in nanoseconds"""
return int(round(self.data_brp / self.f_clock * 1e9))
@property
def dbt(self) -> int:
"""Number of time quanta in a bit of the data phase."""
return 1 + self.data_tseg1 + self.data_tseg2
@property
def data_tseg1(self) -> int:
"""TSEG1 value of the data phase.
This is the sum of the propagation time segment and the phase buffer segment 1.
"""
return self._data["data_tseg1"]
@property
def data_tseg2(self) -> int:
"""TSEG2 value of the data phase. Also known as phase buffer segment 2."""
return self._data["data_tseg2"]
@property
def data_sjw(self) -> int:
"""Synchronization jump width of the data phase.
The phase buffer segments may be shortened or lengthened by this value.
"""
return self._data["data_sjw"]
@property
def data_sample_point(self) -> float:
"""Sample point of the data phase in percent."""
return 100.0 * (1 + self.data_tseg1) / (1 + self.data_tseg1 + self.data_tseg2)
def oscillator_tolerance(
self,
node_loop_delay_ns: float = 250.0,
bus_length_m: float = 10.0,
) -> float:
"""Oscillator tolerance in percent according to ISO 11898-1.
:param float node_loop_delay_ns:
Transceiver loop delay in nanoseconds.
:param float bus_length_m:
Bus length in meters.
"""
delay_per_meter = 5
bidirectional_propagation_delay_ns = 2 * (
node_loop_delay_ns + delay_per_meter * bus_length_m
)
prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.nom_tq)
nom_phase_seg1 = self.nom_tseg1 - prop_seg
nom_phase_seg2 = self.nom_tseg2
data_phase_seg2 = self.data_tseg2
df_clock_list = [
_oscillator_tolerance_condition_1(nom_sjw=self.nom_sjw, nbt=self.nbt),
_oscillator_tolerance_condition_2(
nbt=self.nbt,
nom_phase_seg1=nom_phase_seg1,
nom_phase_seg2=nom_phase_seg2,
),
_oscillator_tolerance_condition_3(data_sjw=self.data_sjw, dbt=self.dbt),
_oscillator_tolerance_condition_4(
nom_phase_seg1=nom_phase_seg1,
nom_phase_seg2=nom_phase_seg2,
data_phase_seg2=data_phase_seg2,
nbt=self.nbt,
dbt=self.dbt,
data_brp=self.data_brp,
nom_brp=self.nom_brp,
),
_oscillator_tolerance_condition_5(
data_sjw=self.data_sjw,
data_brp=self.data_brp,
nom_brp=self.nom_brp,
data_phase_seg2=data_phase_seg2,
nom_phase_seg2=nom_phase_seg2,
nbt=self.nbt,
dbt=self.dbt,
),
]
return max(0.0, min(df_clock_list) * 100)
def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd":
"""Return a new :class:`~can.BitTimingFd` instance with the given *f_clock* but the same
bit rates and sample points.
:param int f_clock:
The CAN system clock frequency in Hz.
:raises ValueError:
if no suitable bit timings were found.
"""
# try the most simple solution first: another bitrate prescaler
try:
return BitTimingFd.from_bitrate_and_segments(
f_clock=f_clock,
nom_bitrate=self.nom_bitrate,
nom_tseg1=self.nom_tseg1,
nom_tseg2=self.nom_tseg2,
nom_sjw=self.nom_sjw,
data_bitrate=self.data_bitrate,
data_tseg1=self.data_tseg1,
data_tseg2=self.data_tseg2,
data_sjw=self.data_sjw,
strict=True,
)
except ValueError:
pass
# create a new timing instance with the same sample points
bt = BitTimingFd.from_sample_point(
f_clock=f_clock,
nom_bitrate=self.nom_bitrate,
nom_sample_point=self.nom_sample_point,
data_bitrate=self.data_bitrate,
data_sample_point=self.data_sample_point,
)
if (
abs(bt.nom_sample_point - self.nom_sample_point) > 1.0
or abs(bt.data_sample_point - self.data_sample_point) > 1.0
):
raise ValueError(
"f_clock change failed because of sample point discrepancy."
)
# adapt synchronization jump width, so it has the same size relative to bit time as self
nom_sjw = int(round(self.nom_sjw / self.nbt * bt.nbt))
nom_sjw = max(1, min(bt.nom_tseg2, nom_sjw))
bt._data["nom_sjw"] = nom_sjw # pylint: disable=protected-access
data_sjw = int(round(self.data_sjw / self.dbt * bt.dbt))
data_sjw = max(1, min(bt.data_tseg2, data_sjw))
bt._data["data_sjw"] = data_sjw # pylint: disable=protected-access
bt._validate() # pylint: disable=protected-access
return bt
def __str__(self) -> str:
segments = [
f"NBR: {self.nom_bitrate:_} bit/s",
f"NSP: {self.nom_sample_point:.2f}%",
f"NBRP: {self.nom_brp}",
f"NTSEG1: {self.nom_tseg1}",
f"NTSEG2: {self.nom_tseg2}",
f"NSJW: {self.nom_sjw}",
f"DBR: {self.data_bitrate:_} bit/s",
f"DSP: {self.data_sample_point:.2f}%",
f"DBRP: {self.data_brp}",
f"DTSEG1: {self.data_tseg1}",
f"DTSEG2: {self.data_tseg2}",
f"DSJW: {self.data_sjw}",
f"CLK: {self.f_clock / 1e6:.0f}MHz",
]
return ", ".join(segments)
def __repr__(self) -> str:
args = ", ".join(f"{key}={value}" for key, value in self.items())
return f"can.{self.__class__.__name__}({args})"
def __getitem__(self, key: str) -> int:
return cast(int, self._data.__getitem__(key))
def __len__(self) -> int:
return self._data.__len__()
def __iter__(self) -> Iterator[str]:
return self._data.__iter__()
def __eq__(self, other: object) -> bool:
if not isinstance(other, BitTimingFd):
return False
return self._data == other._data
def __hash__(self) -> int:
return tuple(self._data.values()).__hash__()
def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float:
"""Arbitration phase - resynchronization"""
return nom_sjw / (2 * 10 * nbt)
def _oscillator_tolerance_condition_2(
nbt: int, nom_phase_seg1: int, nom_phase_seg2: int
) -> float:
"""Arbitration phase - sampling of bit after error flag"""
return min(nom_phase_seg1, nom_phase_seg2) / (2 * (13 * nbt - nom_phase_seg2))
def _oscillator_tolerance_condition_3(data_sjw: int, dbt: int) -> float:
"""Data phase - resynchronization"""
return data_sjw / (2 * 10 * dbt)
def _oscillator_tolerance_condition_4(
nom_phase_seg1: int,
nom_phase_seg2: int,
data_phase_seg2: int,
nbt: int,
dbt: int,
data_brp: int,
nom_brp: int,
) -> float:
"""Data phase - sampling of bit after error flag"""
return min(nom_phase_seg1, nom_phase_seg2) / (
2 * ((6 * dbt - data_phase_seg2) * data_brp / nom_brp + 7 * nbt)
)
def _oscillator_tolerance_condition_5(
data_sjw: int,
data_brp: int,
nom_brp: int,
nom_phase_seg2: int,
data_phase_seg2: int,
nbt: int,
dbt: int,
) -> float:
"""Data phase - bit rate switch"""
max_correctable_phase_shift = data_sjw - max(0.0, nom_brp / data_brp - 1)
time_between_resync = 2 * (
(2 * nbt - nom_phase_seg2) * nom_brp / data_brp + data_phase_seg2 + 4 * dbt
)
return max_correctable_phase_shift / time_between_resync
python-can-4.5.0/can/broadcastmanager.py 0000664 0000000 0000000 00000031662 14722003266 0020222 0 ustar 00root root 0000000 0000000 """
Exposes several methods for transmitting cyclic messages.
The main entry point to these classes should be through
:meth:`can.BusABC.send_periodic`.
"""
import abc
import logging
import platform
import sys
import threading
import time
import warnings
from typing import (
TYPE_CHECKING,
Callable,
Final,
Optional,
Sequence,
Tuple,
Union,
cast,
)
from can import typechecking
from can.message import Message
if TYPE_CHECKING:
from can.bus import BusABC
log = logging.getLogger("can.bcm")
NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000
class _Pywin32Event:
handle: int
class _Pywin32:
def __init__(self) -> None:
import pywintypes # pylint: disable=import-outside-toplevel,import-error
import win32event # pylint: disable=import-outside-toplevel,import-error
self.pywintypes = pywintypes
self.win32event = win32event
def create_timer(self) -> _Pywin32Event:
try:
event = self.win32event.CreateWaitableTimerEx(
None,
None,
self.win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
self.win32event.TIMER_ALL_ACCESS,
)
except (
AttributeError,
OSError,
self.pywintypes.error, # pylint: disable=no-member
):
event = self.win32event.CreateWaitableTimer(None, False, None)
return cast(_Pywin32Event, event)
def set_timer(self, event: _Pywin32Event, period_ms: int) -> None:
self.win32event.SetWaitableTimer(event.handle, 0, period_ms, None, None, False)
def stop_timer(self, event: _Pywin32Event) -> None:
self.win32event.SetWaitableTimer(event.handle, 0, 0, None, None, False)
def wait_0(self, event: _Pywin32Event) -> None:
self.win32event.WaitForSingleObject(event.handle, 0)
def wait_inf(self, event: _Pywin32Event) -> None:
self.win32event.WaitForSingleObject(
event.handle,
self.win32event.INFINITE,
)
PYWIN32: Optional[_Pywin32] = None
if sys.platform == "win32" and sys.version_info < (3, 11):
try:
PYWIN32 = _Pywin32()
except ImportError:
pass
class CyclicTask(abc.ABC):
"""
Abstract Base for all cyclic tasks.
"""
@abc.abstractmethod
def stop(self) -> None:
"""Cancel this periodic task.
:raises ~can.exceptions.CanError:
If stop is called on an already stopped task.
"""
class CyclicSendTaskABC(CyclicTask, abc.ABC):
"""
Message send task with defined period
"""
def __init__(
self, messages: Union[Sequence[Message], Message], period: float
) -> None:
"""
:param messages:
The messages to be sent periodically.
:param period: The rate in seconds at which to send the messages.
:raises ValueError: If the given messages are invalid
"""
messages = self._check_and_convert_messages(messages)
# Take the Arbitration ID of the first element
self.arbitration_id = messages[0].arbitration_id
self.period = period
self.period_ns = int(round(period * 1e9))
self.messages = messages
@staticmethod
def _check_and_convert_messages(
messages: Union[Sequence[Message], Message]
) -> Tuple[Message, ...]:
"""Helper function to convert a Message or Sequence of messages into a
tuple, and raises an error when the given value is invalid.
Performs error checking to ensure that all Messages have the same
arbitration ID and channel.
Should be called when the cyclic task is initialized.
:raises ValueError: If the given messages are invalid
"""
if not isinstance(messages, (list, tuple)):
if isinstance(messages, Message):
messages = [messages]
else:
raise ValueError("Must be either a list, tuple, or a Message")
if not messages:
raise ValueError("Must be at least a list or tuple of length 1")
messages = tuple(messages)
all_same_id = all(
message.arbitration_id == messages[0].arbitration_id for message in messages
)
if not all_same_id:
raise ValueError("All Arbitration IDs should be the same")
all_same_channel = all(
message.channel == messages[0].channel for message in messages
)
if not all_same_channel:
raise ValueError("All Channel IDs should be the same")
return messages
class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC):
def __init__(
self,
messages: Union[Sequence[Message], Message],
period: float,
duration: Optional[float],
) -> None:
"""Message send task with a defined duration and period.
:param messages:
The messages to be sent periodically.
:param period: The rate in seconds at which to send the messages.
:param duration:
Approximate duration in seconds to continue sending messages. If
no duration is provided, the task will continue indefinitely.
:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, period)
self.duration = duration
self.end_time: Optional[float] = None
class RestartableCyclicTaskABC(CyclicSendTaskABC, abc.ABC):
"""Adds support for restarting a stopped cyclic task"""
@abc.abstractmethod
def start(self) -> None:
"""Restart a stopped periodic task."""
class ModifiableCyclicTaskABC(CyclicSendTaskABC, abc.ABC):
def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None:
"""Helper function to perform error checking when modifying the data in
the cyclic task.
Performs error checking to ensure the arbitration ID and the number of
cyclic messages hasn't changed.
Should be called when modify_data is called in the cyclic task.
:raises ValueError: If the given messages are invalid
"""
if len(self.messages) != len(messages):
raise ValueError(
"The number of new cyclic messages to be sent must be equal to "
"the number of messages originally specified for this task"
)
if self.arbitration_id != messages[0].arbitration_id:
raise ValueError(
"The arbitration ID of new cyclic messages cannot be changed "
"from when the task was created"
)
def modify_data(self, messages: Union[Sequence[Message], Message]) -> None:
"""Update the contents of the periodically sent messages, without
altering the timing.
:param messages:
The messages with the new :attr:`Message.data`.
Note: The arbitration ID cannot be changed.
Note: The number of new cyclic messages to be sent must be equal
to the original number of messages originally specified for this
task.
:raises ValueError: If the given messages are invalid
"""
messages = self._check_and_convert_messages(messages)
self._check_modified_messages(messages)
self.messages = messages
class MultiRateCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC):
"""A Cyclic send task that supports switches send frequency after a set time."""
def __init__(
self,
channel: typechecking.Channel,
messages: Union[Sequence[Message], Message],
count: int, # pylint: disable=unused-argument
initial_period: float, # pylint: disable=unused-argument
subsequent_period: float,
) -> None:
"""
Transmits a message `count` times at `initial_period` then continues to
transmit messages at `subsequent_period`.
:param channel: See interface specific documentation.
:param messages:
:param count:
:param initial_period:
:param subsequent_period:
:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, subsequent_period)
self._channel = channel
class ThreadBasedCyclicSendTask(
LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC
):
"""Fallback cyclic send task using daemon thread."""
def __init__(
self,
bus: "BusABC",
lock: threading.Lock,
messages: Union[Sequence[Message], Message],
period: float,
duration: Optional[float] = None,
on_error: Optional[Callable[[Exception], bool]] = None,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> None:
"""Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`.
The `on_error` is called if any error happens on `bus` while sending `messages`.
If `on_error` present, and returns ``False`` when invoked, thread is
stopped immediately, otherwise, thread continuously tries to send `messages`
ignoring errors on a `bus`. Absence of `on_error` means that thread exits immediately
on error.
:param on_error: The callable that accepts an exception if any
error happened on a `bus` while sending `messages`,
it shall return either ``True`` or ``False`` depending
on desired behaviour of `ThreadBasedCyclicSendTask`.
:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, period, duration)
self.bus = bus
self.send_lock = lock
self.stopped = True
self.thread: Optional[threading.Thread] = None
self.on_error = on_error
self.modifier_callback = modifier_callback
self.period_ms = int(round(period * 1000, 0))
self.event: Optional[_Pywin32Event] = None
if PYWIN32:
if self.period_ms == 0:
# A period of 0 would mean that the timer is signaled only once
raise ValueError("The period cannot be smaller than 0.001 (1 ms)")
self.event = PYWIN32.create_timer()
elif (
sys.platform == "win32"
and sys.version_info < (3, 11)
and platform.python_implementation() == "CPython"
):
warnings.warn(
f"{self.__class__.__name__} may achieve better timing accuracy "
f"if the 'pywin32' package is installed.",
RuntimeWarning,
stacklevel=1,
)
if autostart:
self.start()
def stop(self) -> None:
self.stopped = True
if self.event and PYWIN32:
# Reset and signal any pending wait by setting the timer to 0
PYWIN32.stop_timer(self.event)
def start(self) -> None:
self.stopped = False
if self.thread is None or not self.thread.is_alive():
name = f"Cyclic send task for 0x{self.messages[0].arbitration_id:X}"
self.thread = threading.Thread(target=self._run, name=name)
self.thread.daemon = True
self.end_time: Optional[float] = (
time.perf_counter() + self.duration if self.duration else None
)
if self.event and PYWIN32:
PYWIN32.set_timer(self.event, self.period_ms)
self.thread.start()
def _run(self) -> None:
msg_index = 0
msg_due_time_ns = time.perf_counter_ns()
if self.event and PYWIN32:
# Make sure the timer is non-signaled before entering the loop
PYWIN32.wait_0(self.event)
while not self.stopped:
if self.end_time is not None and time.perf_counter() >= self.end_time:
self.stop()
break
try:
if self.modifier_callback is not None:
self.modifier_callback(self.messages[msg_index])
with self.send_lock:
# Prevent calling bus.send from multiple threads
self.bus.send(self.messages[msg_index])
except Exception as exc: # pylint: disable=broad-except
log.exception(exc)
# stop if `on_error` callback was not given
if self.on_error is None:
self.stop()
raise exc
# stop if `on_error` returns False
if not self.on_error(exc):
self.stop()
break
if not self.event:
msg_due_time_ns += self.period_ns
msg_index = (msg_index + 1) % len(self.messages)
if self.event and PYWIN32:
PYWIN32.wait_inf(self.event)
else:
# Compensate for the time it takes to send the message
delay_ns = msg_due_time_ns - time.perf_counter_ns()
if delay_ns > 0:
time.sleep(delay_ns / NANOSECONDS_IN_SECOND)
python-can-4.5.0/can/bus.py 0000664 0000000 0000000 00000046140 14722003266 0015513 0 ustar 00root root 0000000 0000000 """
Contains the ABC bus implementation and its documentation.
"""
import contextlib
import logging
import threading
from abc import ABC, ABCMeta, abstractmethod
from enum import Enum, auto
from time import time
from types import TracebackType
from typing import (
Any,
Callable,
Iterator,
List,
Optional,
Sequence,
Tuple,
Type,
Union,
cast,
)
from typing_extensions import Self
import can
import can.typechecking
from can.broadcastmanager import CyclicSendTaskABC, ThreadBasedCyclicSendTask
from can.message import Message
LOG = logging.getLogger(__name__)
class BusState(Enum):
"""The state in which a :class:`can.BusABC` can be."""
ACTIVE = auto()
PASSIVE = auto()
ERROR = auto()
class CanProtocol(Enum):
"""The CAN protocol type supported by a :class:`can.BusABC` instance"""
CAN_20 = auto()
CAN_FD = auto() # ISO Mode
CAN_FD_NON_ISO = auto()
CAN_XL = auto()
class BusABC(metaclass=ABCMeta):
"""The CAN Bus Abstract Base Class that serves as the basis
for all concrete interfaces.
This class may be used as an iterator over the received messages
and as a context manager for auto-closing the bus when done using it.
Please refer to :ref:`errors` for possible exceptions that may be
thrown by certain operations on this bus.
"""
#: a string describing the underlying bus and/or channel
channel_info = "unknown"
#: Log level for received messages
RECV_LOGGING_LEVEL = 9
#: Assume that no cleanup is needed until something was initialized
_is_shutdown: bool = True
_can_protocol: CanProtocol = CanProtocol.CAN_20
@abstractmethod
def __init__(
self,
channel: Any,
can_filters: Optional[can.typechecking.CanFilters] = None,
**kwargs: object,
):
"""Construct and open a CAN bus instance of the specified type.
Subclasses should call though this method with all given parameters
as it handles generic tasks like applying filters.
:param channel:
The can interface identifier. Expected type is backend dependent.
:param can_filters:
See :meth:`~can.BusABC.set_filters` for details.
:param dict kwargs:
Any backend dependent configurations are passed in this dictionary
:raises ValueError: If parameters are out of range
:raises ~can.exceptions.CanInterfaceNotImplementedError:
If the driver cannot be accessed
:raises ~can.exceptions.CanInitializationError:
If the bus cannot be initialized
"""
self._periodic_tasks: List[_SelfRemovingCyclicTask] = []
self.set_filters(can_filters)
# Flip the class default value when the constructor finishes. That
# usually means the derived class constructor was also successful,
# since it calls this parent constructor last.
self._is_shutdown: bool = False
def __str__(self) -> str:
return self.channel_info
def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
"""Block waiting for a message from the Bus.
:param timeout:
seconds to wait for a message or None to wait indefinitely
:return:
:obj:`None` on timeout or a :class:`~can.Message` object.
:raises ~can.exceptions.CanOperationError:
If an error occurred while reading
"""
start = time()
time_left = timeout
while True:
# try to get a message
msg, already_filtered = self._recv_internal(timeout=time_left)
# return it, if it matches
if msg and (already_filtered or self._matches_filters(msg)):
LOG.log(self.RECV_LOGGING_LEVEL, "Received: %s", msg)
return msg
# if not, and timeout is None, try indefinitely
elif timeout is None:
continue
# try next one only if there still is time, and with
# reduced timeout
else:
time_left = timeout - (time() - start)
if time_left > 0:
continue
return None
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
"""
Read a message from the bus and tell whether it was filtered.
This methods may be called by :meth:`~can.BusABC.recv`
to read a message multiple times if the filters set by
:meth:`~can.BusABC.set_filters` do not match and the call has
not yet timed out.
New implementations should always override this method instead of
:meth:`~can.BusABC.recv`, to be able to take advantage of the
software based filtering provided by :meth:`~can.BusABC.recv`
as a fallback. This method should never be called directly.
.. note::
This method is not an `@abstractmethod` (for now) to allow older
external implementations to continue using their existing
:meth:`~can.BusABC.recv` implementation.
.. note::
The second return value (whether filtering was already done) may
change over time for some interfaces, like for example in the
Kvaser interface. Thus it cannot be simplified to a constant value.
:param float timeout: seconds to wait for a message,
see :meth:`~can.BusABC.send`
:return:
1. a message that was read or None on timeout
2. a bool that is True if message filtering has already
been done and else False
:raises ~can.exceptions.CanOperationError:
If an error occurred while reading
:raises NotImplementedError:
if the bus provides it's own :meth:`~can.BusABC.recv`
implementation (legacy implementation)
"""
raise NotImplementedError("Trying to read from a write only bus?")
@abstractmethod
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""Transmit a message to the CAN bus.
Override this method to enable the transmit path.
:param Message msg: A message object.
:param timeout:
If > 0, wait up to this many seconds for message to be ACK'ed or
for transmit queue to be ready depending on driver implementation.
If timeout is exceeded, an exception will be raised.
Might not be supported by all interfaces.
None blocks indefinitely.
:raises ~can.exceptions.CanOperationError:
If an error occurred while sending
"""
raise NotImplementedError("Trying to write to a readonly bus?")
def send_periodic(
self,
msgs: Union[Message, Sequence[Message]],
period: float,
duration: Optional[float] = None,
store_task: bool = True,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> can.broadcastmanager.CyclicSendTaskABC:
"""Start sending messages at a given period on this bus.
The task will be active until one of the following conditions are met:
- the (optional) duration expires
- the Bus instance goes out of scope
- the Bus instance is shutdown
- :meth:`stop_all_periodic_tasks` is called
- the task's :meth:`~can.broadcastmanager.CyclicTask.stop` method is called.
:param msgs:
Message(s) to transmit
:param period:
Period in seconds between each message
:param duration:
Approximate duration in seconds to continue sending messages. If
no duration is provided, the task will continue indefinitely.
:param store_task:
If True (the default) the task will be attached to this Bus instance.
Disable to instead manage tasks manually.
:param autostart:
If True (the default) the sending task will immediately start after creation.
Otherwise, the task has to be started by calling the
tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it.
:param modifier_callback:
Function which should be used to modify each message's data before
sending. The callback modifies the :attr:`~can.Message.data` of the
message and returns ``None``.
:return:
A started task instance. Note the task can be stopped (and depending on
the backend modified) by calling the task's
:meth:`~can.broadcastmanager.CyclicTask.stop` method.
.. note::
Note the duration before the messages stop being sent may not
be exactly the same as the duration specified by the user. In
general the message will be sent at the given rate until at
least **duration** seconds.
.. note::
For extremely long-running Bus instances with many short-lived
tasks the default api with ``store_task==True`` may not be
appropriate as the stopped tasks are still taking up memory as they
are associated with the Bus instance.
"""
if isinstance(msgs, Message):
msgs = [msgs]
elif isinstance(msgs, Sequence):
# A Sequence does not necessarily provide __bool__ we need to use len()
if len(msgs) == 0:
raise ValueError("Must be a sequence at least of length 1")
else:
raise ValueError("Must be either a message or a sequence of messages")
# Create a backend specific task; will be patched to a _SelfRemovingCyclicTask later
task = cast(
_SelfRemovingCyclicTask,
self._send_periodic_internal(
msgs, period, duration, autostart, modifier_callback
),
)
# we wrap the task's stop method to also remove it from the Bus's list of tasks
periodic_tasks = self._periodic_tasks
original_stop_method = task.stop
def wrapped_stop_method(remove_task: bool = True) -> None:
nonlocal task, periodic_tasks, original_stop_method
if remove_task:
try:
periodic_tasks.remove(task)
except ValueError:
pass # allow the task to be already removed
original_stop_method()
task.stop = wrapped_stop_method # type: ignore[method-assign]
if store_task:
self._periodic_tasks.append(task)
return task
def _send_periodic_internal(
self,
msgs: Union[Sequence[Message], Message],
period: float,
duration: Optional[float] = None,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> can.broadcastmanager.CyclicSendTaskABC:
"""Default implementation of periodic message sending using threading.
Override this method to enable a more efficient backend specific approach.
:param msgs:
Messages to transmit
:param period:
Period in seconds between each message
:param duration:
The duration between sending each message at the given rate. If
no duration is provided, the task will continue indefinitely.
:param autostart:
If True (the default) the sending task will immediately start after creation.
Otherwise, the task has to be started by calling the
tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it.
:return:
A started task instance. Note the task can be stopped (and
depending on the backend modified) by calling the
:meth:`~can.broadcastmanager.CyclicTask.stop` method.
"""
if not hasattr(self, "_lock_send_periodic"):
# Create a send lock for this bus, but not for buses which override this method
self._lock_send_periodic = ( # pylint: disable=attribute-defined-outside-init
threading.Lock()
)
task = ThreadBasedCyclicSendTask(
bus=self,
lock=self._lock_send_periodic,
messages=msgs,
period=period,
duration=duration,
autostart=autostart,
modifier_callback=modifier_callback,
)
return task
def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None:
"""Stop sending any messages that were started using :meth:`send_periodic`.
.. note::
The result is undefined if a single task throws an exception while being stopped.
:param remove_tasks:
Stop tracking the stopped tasks.
"""
if not hasattr(self, "_periodic_tasks"):
# avoid AttributeError for partially initialized BusABC instance
return
for task in self._periodic_tasks:
# we cannot let `task.stop()` modify `self._periodic_tasks` while we are
# iterating over it (#634)
task.stop(remove_task=False)
if remove_tasks:
self._periodic_tasks.clear()
def __iter__(self) -> Iterator[Message]:
"""Allow iteration on messages as they are received.
.. code-block:: python
for msg in bus:
print(msg)
:yields:
:class:`Message` msg objects.
"""
while True:
msg = self.recv(timeout=1.0)
if msg is not None:
yield msg
@property
def filters(self) -> Optional[can.typechecking.CanFilters]:
"""
Modify the filters of this bus. See :meth:`~can.BusABC.set_filters`
for details.
"""
return self._filters
@filters.setter
def filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
self.set_filters(filters)
def set_filters(
self, filters: Optional[can.typechecking.CanFilters] = None
) -> None:
"""Apply filtering to all messages received by this Bus.
All messages that match at least one filter are returned.
If `filters` is `None` or a zero length sequence, all
messages are matched.
Calling without passing any filters will reset the applied
filters to ``None``.
:param filters:
A iterable of dictionaries each containing a "can_id",
a "can_mask", and an optional "extended" key::
[{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
A filter matches, when
`` & can_mask == can_id & can_mask``.
If ``extended`` is set as well, it only matches messages where
`` == extended``. Else it matches every
messages based only on the arbitration ID and mask.
"""
self._filters = filters or None
with contextlib.suppress(NotImplementedError):
self._apply_filters(self._filters)
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
"""
Hook for applying the filters to the underlying kernel or
hardware if supported/implemented by the interface.
:param filters:
See :meth:`~can.BusABC.set_filters` for details.
"""
raise NotImplementedError
def _matches_filters(self, msg: Message) -> bool:
"""Checks whether the given message matches at least one of the
current filters. See :meth:`~can.BusABC.set_filters` for details
on how the filters work.
This method should not be overridden.
:param msg:
the message to check if matching
:return: whether the given message matches at least one filter
"""
# if no filters are set, all messages are matched
if self._filters is None:
return True
for _filter in self._filters:
# check if this filter even applies to the message
if "extended" in _filter:
_filter = cast(can.typechecking.CanFilterExtended, _filter)
if _filter["extended"] != msg.is_extended_id:
continue
# then check for the mask and id
can_id = _filter["can_id"]
can_mask = _filter["can_mask"]
# basically, we compute
# `msg.arbitration_id & can_mask == can_id & can_mask`
# by using the shorter, but equivalent from below:
if (can_id ^ msg.arbitration_id) & can_mask == 0:
return True
# nothing matched
return False
def flush_tx_buffer(self) -> None:
"""Discard every message that may be queued in the output buffer(s)."""
raise NotImplementedError
def shutdown(self) -> None:
"""
Called to carry out any interface specific cleanup required in shutting down a bus.
This method can be safely called multiple times.
"""
if self._is_shutdown:
LOG.debug("%s is already shut down", self.__class__)
return
self._is_shutdown = True
self.stop_all_periodic_tasks()
def __enter__(self) -> Self:
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.shutdown()
def __del__(self) -> None:
if not self._is_shutdown:
LOG.warning("%s was not properly shut down", self.__class__.__name__)
# We do some best-effort cleanup if the user
# forgot to properly close the bus instance
with contextlib.suppress(AttributeError):
self.shutdown()
@property
def state(self) -> BusState:
"""
Return the current state of the hardware
"""
return BusState.ACTIVE
@state.setter
def state(self, new_state: BusState) -> None:
"""
Set the new state of the hardware
"""
raise NotImplementedError("Property is not implemented.")
@property
def protocol(self) -> CanProtocol:
"""Return the CAN protocol used by this bus instance.
This value is set at initialization time and does not change
during the lifetime of a bus instance.
"""
return self._can_protocol
@staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
"""Detect all configurations/channels that this interface could
currently connect with.
This might be quite time consuming.
May not to be implemented by every interface on every platform.
:return: an iterable of dicts, each being a configuration suitable
for usage in the interface's bus constructor.
"""
raise NotImplementedError()
def fileno(self) -> int:
raise NotImplementedError("fileno is not implemented using current CAN bus")
class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC):
"""Removes itself from a bus.
Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate.
"""
def stop(self, remove_task: bool = True) -> None:
raise NotImplementedError()
python-can-4.5.0/can/ctypesutil.py 0000664 0000000 0000000 00000004637 14722003266 0017134 0 ustar 00root root 0000000 0000000 """
This module contains common `ctypes` utils.
"""
import ctypes
import logging
import sys
from typing import Any, Callable, Optional, Tuple, Union
log = logging.getLogger("can.ctypesutil")
__all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"]
if sys.platform == "win32":
_LibBase = ctypes.WinDLL
_FUNCTION_TYPE = ctypes.WINFUNCTYPE
else:
_LibBase = ctypes.CDLL
_FUNCTION_TYPE = ctypes.CFUNCTYPE
class CLibrary(_LibBase):
def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None:
self.func_name: Any
if isinstance(library_or_path, str):
super().__init__(library_or_path)
else:
super().__init__(library_or_path._name, library_or_path._handle)
def map_symbol(
self,
func_name: str,
restype: Any = None,
argtypes: Tuple[Any, ...] = (),
errcheck: Optional[Callable[..., Any]] = None,
) -> Any:
"""
Map and return a symbol (function) from a C library. A reference to the
mapped symbol is also held in the instance
:param func_name:
symbol_name
:param ctypes.c_* restype:
function result type (i.e. ctypes.c_ulong...), defaults to void
:param tuple(ctypes.c_* ... ) argtypes:
argument types, defaults to no args
:param callable errcheck:
optional error checking function, see ctypes docs for _FuncPtr
"""
if argtypes:
prototype = _FUNCTION_TYPE(restype, *argtypes)
else:
prototype = _FUNCTION_TYPE(restype)
try:
func = prototype((func_name, self))
except AttributeError:
raise ImportError(
f'Could not map function "{func_name}" from library {self._name}'
) from None
func._name = func_name # type: ignore[attr-defined] # pylint: disable=protected-access
log.debug(
'Wrapped function "%s", result type: %s, error_check %s',
func_name,
type(restype),
errcheck,
)
if errcheck is not None:
func.errcheck = errcheck
setattr(self, func_name, func)
return func
if sys.platform == "win32":
HRESULT = ctypes.HRESULT
elif sys.platform == "cygwin":
class HRESULT(ctypes.c_long):
pass
# Common win32 definitions
class HANDLE(ctypes.c_void_p):
pass
PHANDLE = ctypes.POINTER(HANDLE)
python-can-4.5.0/can/exceptions.py 0000664 0000000 0000000 00000010001 14722003266 0017066 0 ustar 00root root 0000000 0000000 """
There are several specific :class:`Exception` classes to allow user
code to react to specific scenarios related to CAN busses::
Exception (Python standard library)
+-- ...
+-- CanError (python-can)
+-- CanInterfaceNotImplementedError
+-- CanInitializationError
+-- CanOperationError
+-- CanTimeoutError
Keep in mind that some functions and methods may raise different exceptions.
For example, validating typical arguments and parameters might result in a
:class:`ValueError`. This should always be documented for the function at hand.
"""
import sys
from contextlib import contextmanager
from typing import Optional, Type
if sys.version_info >= (3, 9):
from collections.abc import Generator
else:
from typing import Generator
class CanError(Exception):
"""Base class for all CAN related exceptions.
If specified, the error code is automatically appended to the message:
.. testsetup:: canerror
from can import CanError, CanOperationError
.. doctest:: canerror
>>> # With an error code (it also works with a specific error):
>>> error = CanOperationError(message="Failed to do the thing", error_code=42)
>>> str(error)
'Failed to do the thing [Error Code 42]'
>>>
>>> # Missing the error code:
>>> plain_error = CanError(message="Something went wrong ...")
>>> str(plain_error)
'Something went wrong ...'
:param error_code:
An optional error code to narrow down the cause of the fault
:arg error_code:
An optional error code to narrow down the cause of the fault
"""
def __init__(
self,
message: str = "",
error_code: Optional[int] = None,
) -> None:
self.error_code = error_code
super().__init__(
message if error_code is None else f"{message} [Error Code {error_code}]"
)
class CanInterfaceNotImplementedError(CanError, NotImplementedError):
"""Indicates that the interface is not supported on the current platform.
Example scenarios:
- No interface with that name exists
- The interface is unsupported on the current operating system or interpreter
- The driver could not be found or has the wrong version
"""
class CanInitializationError(CanError):
"""Indicates an error the occurred while initializing a :class:`can.BusABC`.
If initialization fails due to a driver or platform missing/being unsupported,
a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead.
If initialization fails due to a value being out of range, a :class:`ValueError`
is raised.
Example scenarios:
- Try to open a non-existent device and/or channel
- Try to use an invalid setting, which is ok by value, but not ok for the interface
- The device or other resources are already used
"""
class CanOperationError(CanError):
"""Indicates an error while in operation.
Example scenarios:
- A call to a library function results in an unexpected return value
- An invalid message was received
- The driver rejected a message that was meant to be sent
- Cyclic redundancy check (CRC) failed
- A message remained unacknowledged
- A buffer is full
"""
class CanTimeoutError(CanError, TimeoutError):
"""Indicates the timeout of an operation.
Example scenarios:
- Some message could not be sent after the timeout elapsed
- No message was read within the given time
"""
@contextmanager
def error_check(
error_message: Optional[str] = None,
exception_type: Type[CanError] = CanOperationError,
) -> Generator[None, None, None]:
"""Catches any exceptions and turns them into the new type while preserving the stack trace."""
try:
yield
except Exception as error: # pylint: disable=broad-except
if error_message is None:
raise exception_type(str(error)) from error
else:
raise exception_type(error_message) from error
python-can-4.5.0/can/interface.py 0000664 0000000 0000000 00000015377 14722003266 0016672 0 ustar 00root root 0000000 0000000 """
This module contains the base implementation of :class:`can.BusABC` as well
as a list of all available backends and some implemented
CyclicSendTasks.
"""
import importlib
import logging
from typing import Any, Iterable, List, Optional, Type, Union, cast
from . import util
from .bus import BusABC
from .exceptions import CanInterfaceNotImplementedError
from .interfaces import BACKENDS
from .typechecking import AutoDetectedConfig, Channel
log = logging.getLogger("can.interface")
log_autodetect = log.getChild("detect_available_configs")
def _get_class_for_interface(interface: str) -> Type[BusABC]:
"""
Returns the main bus class for the given interface.
:raises:
NotImplementedError if the interface is not known
:raises CanInterfaceNotImplementedError:
if there was a problem while importing the interface or the bus class within that
"""
# Find the correct backend
try:
module_name, class_name = BACKENDS[interface]
except KeyError:
raise NotImplementedError(
f"CAN interface '{interface}' not supported"
) from None
# Import the correct interface module
try:
module = importlib.import_module(module_name)
except Exception as e:
raise CanInterfaceNotImplementedError(
f"Cannot import module {module_name} for CAN interface '{interface}': {e}"
) from None
# Get the correct class
try:
bus_class = getattr(module, class_name)
except Exception as e:
raise CanInterfaceNotImplementedError(
f"Cannot import class {class_name} from module {module_name} for CAN interface "
f"'{interface}': {e}"
) from None
return cast(Type[BusABC], bus_class)
@util.deprecated_args_alias(
deprecation_start="4.2.0",
deprecation_end="5.0.0",
bustype="interface",
context="config_context",
)
def Bus( # noqa: N802
channel: Optional[Channel] = None,
interface: Optional[str] = None,
config_context: Optional[str] = None,
ignore_config: bool = False,
**kwargs: Any,
) -> BusABC:
"""Create a new bus instance with configuration loading.
Instantiates a CAN Bus of the given ``interface``, falls back to reading a
configuration file from default locations.
.. note::
Please note that while the arguments provided to this class take precedence
over any existing values from configuration, it is possible that other parameters
from the configuration may be added to the bus instantiation.
This could potentially have unintended consequences. To prevent this,
you may use the *ignore_config* parameter to ignore any existing configurations.
:param channel:
Channel identification. Expected type is backend dependent.
Set to ``None`` to let it be resolved automatically from the default
:ref:`configuration`.
:param interface:
See :ref:`interface names` for a list of supported interfaces.
Set to ``None`` to let it be resolved automatically from the default
:ref:`configuration`.
:param config_context:
Extra 'context', that is passed to config sources.
This can be used to select a section other than 'default' in the configuration file.
:param ignore_config:
If ``True``, only the given arguments will be used for the bus instantiation. Existing
configuration sources will be ignored.
:param kwargs:
``interface`` specific keyword arguments.
:raises ~can.exceptions.CanInterfaceNotImplementedError:
if the ``interface`` isn't recognized or cannot be loaded
:raises ~can.exceptions.CanInitializationError:
if the bus cannot be instantiated
:raises ValueError:
if the ``channel`` could not be determined
"""
# figure out the rest of the configuration; this might raise an error
if interface is not None:
kwargs["interface"] = interface
if channel is not None:
kwargs["channel"] = channel
if not ignore_config:
kwargs = util.load_config(config=kwargs, context=config_context)
# resolve the bus class to use for that interface
cls = _get_class_for_interface(kwargs["interface"])
# remove the "interface" key, so it doesn't get passed to the backend
del kwargs["interface"]
# make sure the bus can handle this config format
channel = kwargs.pop("channel", channel)
if channel is None:
# Use the default channel for the backend
bus = cls(**kwargs)
else:
bus = cls(channel, **kwargs)
return bus
def detect_available_configs(
interfaces: Union[None, str, Iterable[str]] = None
) -> List[AutoDetectedConfig]:
"""Detect all configurations/channels that the interfaces could
currently connect with.
This might be quite time-consuming.
Automated configuration detection may not be implemented by
every interface on every platform. This method will not raise
an error in that case, but with rather return an empty list
for that interface.
:param interfaces: either
- the name of an interface to be searched in as a string,
- an iterable of interface names to search in, or
- `None` to search in all known interfaces.
:rtype: list[dict]
:return: an iterable of dicts, each suitable for usage in
the constructor of :class:`can.BusABC`.
"""
# Figure out where to search
if interfaces is None:
interfaces = BACKENDS
elif isinstance(interfaces, str):
interfaces = (interfaces,)
# else it is supposed to be an iterable of strings
result = []
for interface in interfaces:
try:
bus_class = _get_class_for_interface(interface)
except CanInterfaceNotImplementedError:
log_autodetect.debug(
'interface "%s" cannot be loaded for detection of available configurations',
interface,
)
continue
# get available channels
try:
available = list(
bus_class._detect_available_configs() # pylint: disable=protected-access
)
except NotImplementedError:
log_autodetect.debug(
'interface "%s" does not support detection of available configurations',
interface,
)
else:
log_autodetect.debug(
'interface "%s" detected %i available configurations',
interface,
len(available),
)
# add the interface name to the configs if it is not already present
for config in available:
if "interface" not in config:
config["interface"] = interface
# append to result
result += available
return result
python-can-4.5.0/can/interfaces/ 0000775 0000000 0000000 00000000000 14722003266 0016466 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/__init__.py 0000664 0000000 0000000 00000004216 14722003266 0020602 0 ustar 00root root 0000000 0000000 """
Interfaces contain low level implementations that interact with CAN hardware.
"""
from typing import Dict, Tuple
from can._entry_points import read_entry_points
__all__ = [
"BACKENDS",
"VALID_INTERFACES",
"canalystii",
"cantact",
"etas",
"gs_usb",
"ics_neovi",
"iscan",
"ixxat",
"kvaser",
"neousys",
"nican",
"nixnet",
"pcan",
"robotell",
"seeedstudio",
"serial",
"slcan",
"socketcan",
"socketcand",
"systec",
"udp_multicast",
"usb2can",
"vector",
"virtual",
]
# interface_name => (module, classname)
BACKENDS: Dict[str, Tuple[str, str]] = {
"kvaser": ("can.interfaces.kvaser", "KvaserBus"),
"socketcan": ("can.interfaces.socketcan", "SocketcanBus"),
"serial": ("can.interfaces.serial.serial_can", "SerialBus"),
"pcan": ("can.interfaces.pcan", "PcanBus"),
"usb2can": ("can.interfaces.usb2can", "Usb2canBus"),
"ixxat": ("can.interfaces.ixxat", "IXXATBus"),
"nican": ("can.interfaces.nican", "NicanBus"),
"iscan": ("can.interfaces.iscan", "IscanBus"),
"virtual": ("can.interfaces.virtual", "VirtualBus"),
"udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"),
"neovi": ("can.interfaces.ics_neovi", "NeoViBus"),
"vector": ("can.interfaces.vector", "VectorBus"),
"slcan": ("can.interfaces.slcan", "slcanBus"),
"robotell": ("can.interfaces.robotell", "robotellBus"),
"canalystii": ("can.interfaces.canalystii", "CANalystIIBus"),
"systec": ("can.interfaces.systec", "UcanBus"),
"seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"),
"cantact": ("can.interfaces.cantact", "CantactBus"),
"gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"),
"nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"),
"neousys": ("can.interfaces.neousys", "NeousysBus"),
"etas": ("can.interfaces.etas", "EtasBus"),
"socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"),
}
BACKENDS.update(
{
interface.key: (interface.module_name, interface.class_name)
for interface in read_entry_points(group="can.interface")
}
)
VALID_INTERFACES = frozenset(sorted(BACKENDS.keys()))
python-can-4.5.0/can/interfaces/canalystii.py 0000664 0000000 0000000 00000017601 14722003266 0021205 0 ustar 00root root 0000000 0000000 import logging
import time
from collections import deque
from ctypes import c_ubyte
from typing import Any, Deque, Dict, Optional, Sequence, Tuple, Union
import canalystii as driver
from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message
from can.exceptions import CanTimeoutError
from can.typechecking import CanFilters
from can.util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
class CANalystIIBus(BusABC):
@deprecated_args_alias(
deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing"
)
def __init__(
self,
channel: Union[int, Sequence[int], str] = (0, 1),
device: int = 0,
bitrate: Optional[int] = None,
timing: Optional[Union[BitTiming, BitTimingFd]] = None,
can_filters: Optional[CanFilters] = None,
rx_queue_size: Optional[int] = None,
**kwargs: Dict[str, Any],
):
"""
:param channel:
Optional channel number, list/tuple of multiple channels, or comma
separated string of channels. Default is to configure both
channels.
:param device:
Optional USB device number. Default is 0 (first device found).
:param bitrate:
CAN bitrate in bits/second. Required unless the bit_timing argument is set.
:param timing:
Optional :class:`~can.BitTiming` instance to use for custom bit timing setting.
If this argument is set then it overrides the bitrate argument. The
`f_clock` value of the timing instance must be set to 8_000_000 (8MHz)
for standard CAN.
CAN FD and the :class:`~can.BitTimingFd` class are not supported.
:param can_filters:
Optional filters for received CAN messages.
:param rx_queue_size:
If set, software received message queue can only grow to this many
messages (for all channels) before older messages are dropped
"""
if not (bitrate or timing):
raise ValueError("Either bitrate or timing argument is required")
# Do this after the error handling
super().__init__(
channel=channel,
can_filters=can_filters,
**kwargs,
)
if isinstance(channel, str):
# Assume comma separated string of channels
self.channels = [int(ch.strip()) for ch in channel.split(",")]
elif isinstance(channel, int):
self.channels = [channel]
else: # Sequence[int]
self.channels = list(channel)
self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}"
self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size)
self.device = driver.CanalystDevice(device_index=device)
self._can_protocol = CanProtocol.CAN_20
for single_channel in self.channels:
if isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000])
self.device.init(
single_channel, timing0=timing.btr0, timing1=timing.btr1
)
elif isinstance(timing, BitTimingFd):
raise NotImplementedError(
f"CAN FD is not supported by {self.__class__.__name__}."
)
else:
self.device.init(single_channel, bitrate=bitrate)
# Delay to use between each poll for new messages
#
# The timeout is deliberately kept low to avoid the possibility of
# a hardware buffer overflow. This value was determined
# experimentally, but the ideal value will depend on the specific
# system.
RX_POLL_DELAY = 0.020
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 the TX queue to clear.
If set to ``None`` (default) the function returns immediately.
Note: Due to limitations in the device firmware and protocol, the
timeout will not trigger if there are problems with CAN arbitration,
but only if the device is overloaded with a backlog of too many
messages to send.
"""
raw_message = driver.Message(
msg.arbitration_id,
0, # timestamp
1, # time_flag
0, # send_type
msg.is_remote_frame,
msg.is_extended_id,
msg.dlc,
(c_ubyte * 8)(*msg.data),
)
if msg.channel is not None:
channel = msg.channel
elif len(self.channels) == 1:
channel = self.channels[0]
else:
raise ValueError(
"Message channel must be set when using multiple channels."
)
send_result = self.device.send(channel, [raw_message], timeout)
if timeout is not None and not send_result:
raise CanTimeoutError(f"Send timed out after {timeout} seconds")
def _recv_from_queue(self) -> Tuple[Message, bool]:
"""Return a message from the internal receive queue"""
channel, raw_msg = self.rx_queue.popleft()
# Protocol timestamps are in units of 100us, convert to seconds as
# float
timestamp = raw_msg.timestamp * 100e-6
return (
Message(
channel=channel,
timestamp=timestamp,
arbitration_id=raw_msg.can_id,
is_extended_id=raw_msg.extended,
is_remote_frame=raw_msg.remote,
dlc=raw_msg.data_len,
data=bytes(raw_msg.data),
),
False,
)
def poll_received_messages(self) -> None:
"""Poll new messages from the device into the rx queue but don't
return any message to the caller
Calling this function isn't necessary as polling the device is done
automatically when calling recv(). This function is for the situation
where an application needs to empty the hardware receive buffer without
consuming any message.
"""
for channel in self.channels:
self.rx_queue.extend(
(channel, raw_msg) for raw_msg in self.device.receive(channel)
)
def _recv_internal(
self, timeout: Optional[float] = None
) -> Tuple[Optional[Message], bool]:
"""
:param timeout: float in seconds
:return:
"""
if self.rx_queue:
return self._recv_from_queue()
deadline = None
while deadline is None or time.time() < deadline:
if deadline is None and timeout is not None:
deadline = time.time() + timeout
self.poll_received_messages()
if self.rx_queue:
return self._recv_from_queue()
# If blocking on a timeout, add a sleep before we loop again
# to reduce CPU usage.
if deadline is None or deadline - time.time() > 0.050:
time.sleep(self.RX_POLL_DELAY)
return (None, False)
def flush_tx_buffer(self, channel: Optional[int] = None) -> None:
"""Flush the TX buffer of the device.
:param channel:
Optional channel number to flush. If set to None, all initialized
channels are flushed.
Note that because of protocol limitations this function returning
doesn't mean that messages have been sent, it may also mean they
failed to send.
"""
if channel:
self.device.flush_tx_buffer(channel, float("infinity"))
else:
for ch in self.channels:
self.device.flush_tx_buffer(ch, float("infinity"))
def shutdown(self) -> None:
super().shutdown()
for channel in self.channels:
self.device.stop(channel)
self.device = None
python-can-4.5.0/can/interfaces/cantact.py 0000664 0000000 0000000 00000013524 14722003266 0020462 0 ustar 00root root 0000000 0000000 """
Interface for CANtact devices from Linklayer Labs
"""
import logging
import time
from typing import Any, Optional, Union
from unittest.mock import Mock
from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message
from ..exceptions import (
CanInitializationError,
CanInterfaceNotImplementedError,
error_check,
)
from ..util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
try:
import cantact
except ImportError:
cantact = None
logger.warning(
"The CANtact module is not installed. Install it using `pip install cantact`"
)
class CantactBus(BusABC):
"""CANtact interface"""
@staticmethod
def _detect_available_configs():
try:
interface = cantact.Interface()
except (NameError, SystemError, AttributeError):
logger.debug(
"Could not import or instantiate cantact, so no configurations are available"
)
return []
channels = []
for i in range(0, interface.channel_count()):
channels.append({"interface": "cantact", "channel": f"ch:{i}"})
return channels
@deprecated_args_alias(
deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing"
)
def __init__(
self,
channel: int,
bitrate: int = 500_000,
poll_interval: float = 0.01,
monitor: bool = False,
timing: Optional[Union[BitTiming, BitTimingFd]] = None,
**kwargs: Any,
) -> None:
"""
:param int channel:
Channel number (zero indexed, labeled on multi-channel devices)
:param int bitrate:
Bitrate in bits/s
:param bool monitor:
If true, operate in listen-only monitoring mode
:param timing:
Optional :class:`~can.BitTiming` instance to use for custom bit timing setting.
If this argument is set then it overrides the bitrate argument. The
`f_clock` value of the timing instance must be set to 24_000_000 (24MHz)
for standard CAN.
CAN FD and the :class:`~can.BitTimingFd` class are not supported.
"""
if kwargs.get("_testing", False):
self.interface = MockInterface()
else:
if cantact is None:
raise CanInterfaceNotImplementedError(
"The CANtact module is not installed. "
"Install it using `python -m pip install cantact`"
)
with error_check(
"Cannot create the cantact.Interface", CanInitializationError
):
self.interface = cantact.Interface()
self.channel = int(channel)
self.channel_info = f"CANtact: ch:{channel}"
self._can_protocol = CanProtocol.CAN_20
# Configure the interface
with error_check("Cannot setup the cantact.Interface", CanInitializationError):
if isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, valid_clocks=[24_000_000])
# use custom bit timing
self.interface.set_bit_timing(
int(channel),
int(timing.brp),
int(timing.tseg1),
int(timing.tseg2),
int(timing.sjw),
)
elif isinstance(timing, BitTimingFd):
raise NotImplementedError(
f"CAN FD is not supported by {self.__class__.__name__}."
)
else:
# use bitrate
self.interface.set_bitrate(int(channel), int(bitrate))
self.interface.set_enabled(int(channel), True)
self.interface.set_monitor(int(channel), monitor)
self.interface.start()
super().__init__(
channel=channel,
bitrate=bitrate,
poll_interval=poll_interval,
**kwargs,
)
def _recv_internal(self, timeout):
with error_check("Cannot receive message"):
frame = self.interface.recv(int(timeout * 1000))
if frame is None:
# timeout occurred
return None, False
msg = Message(
arbitration_id=frame["id"],
is_extended_id=frame["extended"],
timestamp=frame["timestamp"],
is_remote_frame=frame["rtr"],
dlc=frame["dlc"],
data=frame["data"][: frame["dlc"]],
channel=frame["channel"],
is_rx=(not frame["loopback"]), # received if not loopback frame
)
return msg, False
def send(self, msg, timeout=None):
with error_check("Cannot send message"):
self.interface.send(
self.channel,
msg.arbitration_id,
bool(msg.is_extended_id),
bool(msg.is_remote_frame),
msg.dlc,
msg.data,
)
def shutdown(self):
super().shutdown()
with error_check("Cannot shutdown interface"):
self.interface.stop()
def mock_recv(timeout):
if timeout > 0:
return {
"id": 0x123,
"extended": False,
"timestamp": time.time(),
"loopback": False,
"rtr": False,
"dlc": 8,
"data": [1, 2, 3, 4, 5, 6, 7, 8],
"channel": 0,
}
else:
# simulate timeout when timeout = 0
return None
class MockInterface:
"""
Mock interface to replace real interface when testing.
This allows for tests to run without actual hardware.
"""
start = Mock()
set_bitrate = Mock()
set_bit_timing = Mock()
set_enabled = Mock()
set_monitor = Mock()
stop = Mock()
send = Mock()
channel_count = Mock(return_value=1)
recv = Mock(side_effect=mock_recv)
python-can-4.5.0/can/interfaces/etas/ 0000775 0000000 0000000 00000000000 14722003266 0017422 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/etas/__init__.py 0000664 0000000 0000000 00000027475 14722003266 0021552 0 ustar 00root root 0000000 0000000 import time
from typing import Dict, List, Optional, Tuple
import can
from can.exceptions import CanInitializationError
from .boa import *
class EtasBus(can.BusABC):
def __init__(
self,
channel: str,
can_filters: Optional[can.typechecking.CanFilters] = None,
receive_own_messages: bool = False,
bitrate: int = 1000000,
fd: bool = True,
data_bitrate: int = 2000000,
**kwargs: Dict[str, any],
):
self.receive_own_messages = receive_own_messages
self._can_protocol = can.CanProtocol.CAN_FD if fd else can.CanProtocol.CAN_20
nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX)
self.tree = ctypes.POINTER(CSI_Tree)()
CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree))
oci_can_v = BOA_Version(1, 4, 0, 0)
self.ctrl = OCI_ControllerHandle()
OCI_CreateCANControllerNoSearch(
channel.encode(),
ctypes.byref(oci_can_v),
self.tree,
ctypes.byref(self.ctrl),
)
ctrlConf = OCI_CANConfiguration()
ctrlConf.baudrate = bitrate
ctrlConf.samplePoint = 80
ctrlConf.samplesPerBit = OCI_CAN_THREE_SAMPLES_PER_BIT
ctrlConf.BTL_Cycles = 10
ctrlConf.SJW = 1
ctrlConf.syncEdge = OCI_CAN_SINGLE_SYNC_EDGE
ctrlConf.physicalMedia = OCI_CAN_MEDIA_HIGH_SPEED
if receive_own_messages:
ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_ON
else:
ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF
ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE
if fd:
ctrlConf.canFDEnabled = True
ctrlConf.canFDConfig.dataBitRate = data_bitrate
ctrlConf.canFDConfig.dataBTL_Cycles = 10
ctrlConf.canFDConfig.dataSamplePoint = 80
ctrlConf.canFDConfig.dataSJW = 1
ctrlConf.canFDConfig.flags = 0
ctrlConf.canFDConfig.canFdTxConfig = OCI_CANFDTX_USE_CAN_AND_CANFD_FRAMES
ctrlConf.canFDConfig.canFdRxConfig.canRxMode = (
OCI_CAN_RXMODE_CAN_FRAMES_USING_CAN_MESSAGE
)
ctrlConf.canFDConfig.canFdRxConfig.canFdRxMode = (
OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE
)
ctrlProp = OCI_CANControllerProperties()
ctrlProp.mode = OCI_CONTROLLER_MODE_RUNNING
ec = OCI_OpenCANController(
self.ctrl, ctypes.byref(ctrlConf), ctypes.byref(ctrlProp)
)
if ec != 0x0 and ec != 0x40004000: # accept BOA_WARN_PARAM_ADAPTED
raise CanInitializationError(
f"OCI_OpenCANController failed with error 0x{ec:X}"
)
# RX
rxQConf = OCI_CANRxQueueConfiguration()
rxQConf.onFrame.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg)
rxQConf.onFrame.userData = None
rxQConf.onEvent.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg)
rxQConf.onEvent.userData = None
if receive_own_messages:
rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_ON
else:
rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF
self.rxQueue = OCI_QueueHandle()
OCI_CreateCANRxQueue(
self.ctrl, ctypes.byref(rxQConf), ctypes.byref(self.rxQueue)
)
self._oci_filters = None
self.filters = can_filters
# TX
txQConf = OCI_CANTxQueueConfiguration()
txQConf.reserved = 0
self.txQueue = OCI_QueueHandle()
OCI_CreateCANTxQueue(
self.ctrl, ctypes.byref(txQConf), ctypes.byref(self.txQueue)
)
# Common
timerCapabilities = OCI_TimerCapabilities()
OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities))
self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second
# all timestamps are hardware timestamps relative to the CAN device powerup
# calculate an offset to make them relative to epoch
now = OCI_Time()
OCI_GetTimerValue(self.ctrl, ctypes.byref(now))
self.timeOffset = time.time() - (float(now.value) / self.tickFrequency)
self.channel_info = channel
# Super call must be after child init since super calls set_filters
super().__init__(channel=channel, **kwargs)
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[can.Message], bool]:
ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)()
ociMsg = OCI_CANMessageEx()
ociMsgs[0] = ctypes.pointer(ociMsg)
count = ctypes.c_uint32()
if timeout is not None: # wait for specified time
t = OCI_Time(round(timeout * self.tickFrequency))
else: # wait indefinitely
t = OCI_NO_TIME
OCI_ReadCANDataEx(
self.rxQueue,
t,
ociMsgs,
1,
ctypes.byref(count),
None,
)
msg = None
if count.value != 0:
if ociMsg.type == OCI_CANFDRX_MESSAGE.value:
ociRxMsg = ociMsg.data.canFDRxMessage
msg = can.Message(
timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency
+ self.timeOffset,
arbitration_id=ociRxMsg.frameID,
is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED),
is_remote_frame=bool(
ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME
),
# is_error_frame=False,
# channel=None,
dlc=ociRxMsg.size,
data=ociRxMsg.data[0 : ociRxMsg.size],
is_fd=True,
is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION),
bitrate_switch=bool(
ociRxMsg.flags & OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE
),
# error_state_indicator=False,
# check=False,
)
elif ociMsg.type == OCI_CAN_RX_MESSAGE.value:
ociRxMsg = ociMsg.data.rxMessage
msg = can.Message(
timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency
+ self.timeOffset,
arbitration_id=ociRxMsg.frameID,
is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED),
is_remote_frame=bool(
ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME
),
# is_error_frame=False,
# channel=None,
dlc=ociRxMsg.dlc,
data=ociRxMsg.data[0 : ociRxMsg.dlc],
# is_fd=False,
is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION),
# bitrate_switch=False,
# error_state_indicator=False,
# check=False,
)
return (msg, True)
def send(self, msg: can.Message, timeout: Optional[float] = None) -> None:
ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)()
ociMsg = OCI_CANMessageEx()
ociMsgs[0] = ctypes.pointer(ociMsg)
if msg.is_fd:
ociMsg.type = OCI_CANFDTX_MESSAGE
ociTxMsg = ociMsg.data.canFDTxMessage
ociTxMsg.size = msg.dlc
else:
ociMsg.type = OCI_CAN_TX_MESSAGE
ociTxMsg = ociMsg.data.txMessage
ociTxMsg.dlc = msg.dlc
# set fields common to CAN / CAN-FD
ociTxMsg.frameID = msg.arbitration_id
ociTxMsg.flags = 0
if msg.is_extended_id:
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_EXTENDED
if msg.is_remote_frame:
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME
ociTxMsg.data = tuple(msg.data)
if msg.is_fd:
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA
if msg.bitrate_switch:
ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE
OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None)
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
if self._oci_filters:
OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1)
# "accept all" filter
if filters is None:
filters = [{"can_id": 0x0, "can_mask": 0x0}]
self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))()
for i, filter_ in enumerate(filters):
f = OCI_CANRxFilterEx()
f.frameIDValue = filter_["can_id"]
f.frameIDMask = filter_["can_mask"]
f.tag = 0
f.flagsValue = 0
if self.receive_own_messages:
# mask out the SR bit, i.e. ignore the bit -> receive all
f.flagsMask = 0
else:
# enable the SR bit in the mask. since the bit is 0 in flagsValue -> do not self-receive
f.flagsMask = OCI_CAN_MSG_FLAG_SELFRECEPTION
if filter_.get("extended"):
f.flagsValue |= OCI_CAN_MSG_FLAG_EXTENDED
f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED
self._oci_filters[i].contents = f
OCI_AddCANFrameFilterEx(self.rxQueue, self._oci_filters, len(self._oci_filters))
def flush_tx_buffer(self) -> None:
OCI_ResetQueue(self.txQueue)
def shutdown(self) -> None:
super().shutdown()
# Cleanup TX
if self.txQueue:
OCI_DestroyCANTxQueue(self.txQueue)
self.txQueue = None
# Cleanup RX
if self.rxQueue:
OCI_DestroyCANRxQueue(self.rxQueue)
self.rxQueue = None
# Cleanup common
if self.ctrl:
OCI_CloseCANController(self.ctrl)
OCI_DestroyCANController(self.ctrl)
self.ctrl = None
if self.tree:
CSI_DestroyProtocolTree(self.tree)
self.tree = None
@property
def state(self) -> can.BusState:
status = OCI_CANControllerStatus()
OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status))
if status.stateCode & OCI_CAN_STATE_ACTIVE:
return can.BusState.ACTIVE
elif status.stateCode & OCI_CAN_STATE_PASSIVE:
return can.BusState.PASSIVE
@state.setter
def state(self, new_state: can.BusState) -> None:
# disabled, OCI_AdaptCANConfiguration does not allow changing the bus mode
# if new_state == can.BusState.ACTIVE:
# self.ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE
# else:
# self.ctrlConf.busParticipationMode = OCI_BUSMODE_PASSIVE
# ec = OCI_AdaptCANConfiguration(self.ctrl, ctypes.byref(self.ctrlConf))
# if ec != 0x0:
# raise CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}")
raise NotImplementedError("Setting state is not implemented.")
@staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX)
tree = ctypes.POINTER(CSI_Tree)()
CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree))
nodes: List[Dict[str, str]] = []
def _findNodes(tree, prefix):
uri = f"{prefix}/{tree.contents.item.uriName.decode()}"
if "CAN:" in uri:
nodes.append({"interface": "etas", "channel": uri})
elif tree.contents.child:
_findNodes(
tree.contents.child,
f"{prefix}/{tree.contents.item.uriName.decode()}",
)
if tree.contents.sibling:
_findNodes(tree.contents.sibling, prefix)
_findNodes(tree, "ETAS:/")
CSI_DestroyProtocolTree(tree)
return nodes
python-can-4.5.0/can/interfaces/etas/boa.py 0000664 0000000 0000000 00000050244 14722003266 0020542 0 ustar 00root root 0000000 0000000 import ctypes
from ...exceptions import CanInitializationError, CanOperationError
try:
# try to load libraries from the system default paths
_csi = ctypes.windll.LoadLibrary("dll-csiBind")
_oci = ctypes.windll.LoadLibrary("dll-ocdProxy")
except FileNotFoundError:
# try to load libraries with hardcoded paths
if ctypes.sizeof(ctypes.c_voidp) == 4:
# 32 bit
path = "C:/Program Files (x86)/ETAS/BOA_V2/Bin/Win32/Dll/Framework/"
elif ctypes.sizeof(ctypes.c_voidp) == 8:
# 64 bit
path = "C:/Program Files/ETAS/BOA_V2/Bin/x64/Dll/Framework/"
_csi = ctypes.windll.LoadLibrary(path + "dll-csiBind")
_oci = ctypes.windll.LoadLibrary(path + "dll-ocdProxy")
# define helper functions to use with errcheck
def errcheck_init(result, func, _arguments):
# unfortunately, we can't use OCI_GetError here
# because we don't always have a handle to use
# text = ctypes.create_string_buffer(500)
# OCI_GetError(self.ctrl, ec, text, 500)
if result != 0x0:
raise CanInitializationError(f"{func.__name__} failed with error 0x{result:X}")
return result
def errcheck_oper(result, func, _arguments):
if result != 0x0:
raise CanOperationError(f"{func.__name__} failed with error 0x{result:X}")
return result
# Common (BOA)
BOA_ResultCode = ctypes.c_uint32
BOA_Handle = ctypes.c_int32
BOA_Time = ctypes.c_int64
BOA_NO_VALUE = -1
BOA_NO_HANDLE = BOA_Handle(BOA_NO_VALUE)
BOA_NO_TIME = BOA_Time(BOA_NO_VALUE)
class BOA_UuidBin(ctypes.Structure):
_fields_ = [("data", ctypes.c_uint8 * 16)]
class BOA_Version(ctypes.Structure):
_fields_ = [
("majorVersion", ctypes.c_uint8),
("minorVersion", ctypes.c_uint8),
("bugfix", ctypes.c_uint8),
("build", ctypes.c_uint8),
]
class BOA_UuidVersion(ctypes.Structure):
_fields_ = [("uuid", BOA_UuidBin), ("version", BOA_Version)]
class BOA_ServiceId(ctypes.Structure):
_fields_ = [("api", BOA_UuidVersion), ("access", BOA_UuidVersion)]
class BOA_ServiceIdParam(ctypes.Structure):
_fields_ = [
("id", BOA_ServiceId),
("count", ctypes.c_uint32),
("accessParam", ctypes.c_char * 128),
]
# Connection Service Interface (CSI)
# CSI - Search For Service (SFS)
CSI_NodeType = ctypes.c_uint32
CSI_NODE_MIN = CSI_NodeType(0)
CSI_NODE_MAX = CSI_NodeType(0x7FFF)
class CSI_NodeRange(ctypes.Structure):
_fields_ = [("min", CSI_NodeType), ("max", CSI_NodeType)]
class CSI_SubItem(ctypes.Structure):
_fields_ = [
("server", BOA_ServiceIdParam),
("nodeType", CSI_NodeType),
("uriName", ctypes.c_char * 128),
("visibleName", ctypes.c_char * 4),
("version", BOA_Version),
("reserved2", ctypes.c_char * 88),
("serverAffinity", BOA_UuidBin),
("requiredAffinity0", BOA_UuidBin),
("reserved", ctypes.c_int32 * 4),
("requiredAffinity1", BOA_UuidBin),
("count", ctypes.c_int32),
("requiredAPI", BOA_ServiceId * 4),
]
class CSI_Tree(ctypes.Structure):
pass
CSI_Tree._fields_ = [
("item", CSI_SubItem),
("sibling", ctypes.POINTER(CSI_Tree)),
("child", ctypes.POINTER(CSI_Tree)),
("childrenProbed", ctypes.c_int),
]
CSI_CreateProtocolTree = _csi.CSI_CreateProtocolTree
CSI_CreateProtocolTree.argtypes = [
ctypes.c_char_p,
CSI_NodeRange,
ctypes.POINTER(ctypes.POINTER(CSI_Tree)),
]
CSI_CreateProtocolTree.restype = BOA_ResultCode
CSI_CreateProtocolTree.errcheck = errcheck_init
CSI_DestroyProtocolTree = _csi.CSI_DestroyProtocolTree
CSI_DestroyProtocolTree.argtypes = [
ctypes.POINTER(CSI_Tree),
]
CSI_DestroyProtocolTree.restype = BOA_ResultCode
CSI_DestroyProtocolTree.errcheck = errcheck_oper
# Open Controller Interface (OCI)
# OCI Common - Global Types
OCI_NO_VALUE = BOA_NO_VALUE
OCI_NO_HANDLE = BOA_NO_HANDLE
OCI_Handle = BOA_Handle
OCI_Time = BOA_Time
# OCI Common - Controller Handling
OCI_ControllerHandle = OCI_Handle
OCI_ControllerPropertiesMode = ctypes.c_uint32
OCI_CONTROLLER_MODE_RUNNING = OCI_ControllerPropertiesMode(0)
OCI_CONTROLLER_MODE_SUSPENDED = OCI_ControllerPropertiesMode(1)
OCI_SelfReceptionMode = ctypes.c_uint32
OCI_SELF_RECEPTION_OFF = OCI_SelfReceptionMode(0)
OCI_SELF_RECEPTION_ON = OCI_SelfReceptionMode(1)
# OCI Common - Event Handling
# OCI Common - Error Management
OCI_ErrorCode = BOA_ResultCode
OCI_InternalErrorEvent = ctypes.c_uint32
OCI_INTERNAL_GENERAL_ERROR = OCI_InternalErrorEvent(0)
class OCI_InternalErrorEventMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("eventCode", OCI_InternalErrorEvent),
("errorCode", OCI_ErrorCode),
]
OCI_GetError = _oci.OCI_GetError
OCI_GetError.argtypes = [
OCI_Handle,
OCI_ErrorCode,
ctypes.c_char_p,
ctypes.c_uint32,
]
OCI_GetError.restype = OCI_ErrorCode
OCI_GetError.errcheck = errcheck_oper
# OCI Common - Queue Handling
OCI_QueueHandle = OCI_Handle
OCI_QueueEvent = ctypes.c_uint32
OCI_QUEUE_UNDERRUN = OCI_QueueEvent(0)
OCI_QUEUE_EMPTY = OCI_QueueEvent(1)
OCI_QUEUE_NOT_EMPTY = OCI_QueueEvent(2)
OCI_QUEUE_LOW_WATERMARK = OCI_QueueEvent(3)
OCI_QUEUE_HIGH_WATERMARK = OCI_QueueEvent(4)
OCI_QUEUE_FULL = OCI_QueueEvent(5)
OCI_QUEUE_OVERFLOW = OCI_QueueEvent(6)
class OCI_QueueEventMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("eventCode", OCI_QueueEvent),
("destination", ctypes.c_uint32),
]
OCI_ResetQueue = _oci.OCI_ResetQueue
OCI_ResetQueue.argtypes = [OCI_QueueHandle]
OCI_ResetQueue.restype = OCI_ErrorCode
OCI_ResetQueue.errcheck = errcheck_oper
# OCI Common - Timer Handling
OCI_NO_TIME = BOA_NO_TIME
OCI_TimeReferenceScale = ctypes.c_uint32
OCI_TimeReferenceScaleUnknown = OCI_TimeReferenceScale(0)
OCI_TimeReferenceScaleTAI = OCI_TimeReferenceScale(1)
OCI_TimeReferenceScaleUTC = OCI_TimeReferenceScale(2)
OCI_TimerEvent = ctypes.c_uint32
OCI_TIMER_EVENT_SYNC_LOCK = OCI_TimerEvent(0)
OCI_TIMER_EVENT_SYNC_LOSS = OCI_TimerEvent(1)
OCI_TIMER_EVENT_LEAP_SECOND = OCI_TimerEvent(2)
class OCI_TimerCapabilities(ctypes.Structure):
_fields_ = [
("localClockID", ctypes.c_char * 40),
("format", ctypes.c_uint32),
("tickFrequency", ctypes.c_uint32),
("ticksPerIncrement", ctypes.c_uint32),
("localStratumLevel", ctypes.c_uint32),
("localReferenceScale", OCI_TimeReferenceScale),
("localTimeOriginIso8601", ctypes.c_char * 40),
("syncSlave", ctypes.c_uint32),
("syncMaster", ctypes.c_uint32),
]
class OCI_TimerEventMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("eventCode", OCI_TimerEvent),
("destination", ctypes.c_uint32),
]
OCI_GetTimerCapabilities = _oci.OCI_GetTimerCapabilities
OCI_GetTimerCapabilities.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_TimerCapabilities),
]
OCI_GetTimerCapabilities.restype = OCI_ErrorCode
OCI_GetTimerCapabilities.errcheck = errcheck_init
OCI_GetTimerValue = _oci.OCI_GetTimerValue
OCI_GetTimerValue.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_Time),
]
OCI_GetTimerValue.restype = OCI_ErrorCode
OCI_GetTimerValue.errcheck = errcheck_oper
# OCI CAN
# OCI CAN - CAN-FD
OCI_CANFDRxMode = ctypes.c_uint32
OCI_CANFDRXMODE_CANFD_FRAMES_IGNORED = OCI_CANFDRxMode(1)
OCI_CANFDRXMODE_CANFD_FRAMES_USING_CAN_MESSAGE = OCI_CANFDRxMode(2)
OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE = OCI_CANFDRxMode(4)
OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE_PADDING = OCI_CANFDRxMode(8)
OCI_CANRxMode = ctypes.c_uint32
OCI_CAN_RXMODE_CAN_FRAMES_IGNORED = OCI_CANRxMode(1)
OCI_CAN_RXMODE_CAN_FRAMES_USING_CAN_MESSAGE = OCI_CANRxMode(2)
OCI_CANFDTxConfig = ctypes.c_uint32
OCI_CANFDTX_USE_CAN_FRAMES_ONLY = OCI_CANFDTxConfig(1)
OCI_CANFDTX_USE_CANFD_FRAMES_ONLY = OCI_CANFDTxConfig(2)
OCI_CANFDTX_USE_CAN_AND_CANFD_FRAMES = OCI_CANFDTxConfig(4)
class OCI_CANFDRxConfig(ctypes.Structure):
_fields_ = [
("canRxMode", OCI_CANRxMode),
("canFdRxMode", OCI_CANFDRxMode),
]
class OCI_CANFDConfiguration(ctypes.Structure):
_fields_ = [
("dataBitRate", ctypes.c_uint32),
("dataSamplePoint", ctypes.c_uint32),
("dataBTL_Cycles", ctypes.c_uint32),
("dataSJW", ctypes.c_uint32),
("flags", ctypes.c_uint32),
("txSecondarySamplePointOffset", ctypes.c_uint32),
("canFdRxConfig", OCI_CANFDRxConfig),
("canFdTxConfig", OCI_CANFDTxConfig),
("txSecondarySamplePointFilterWindow", ctypes.c_uint16),
("reserved", ctypes.c_uint16),
]
class OCI_CANFDRxMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("frameID", ctypes.c_uint32),
("flags", ctypes.c_uint16),
("res", ctypes.c_uint8),
("size", ctypes.c_uint8),
("res1", ctypes.c_uint8 * 4),
("data", ctypes.c_uint8 * 64),
]
class OCI_CANFDTxMessage(ctypes.Structure):
_fields_ = [
("frameID", ctypes.c_uint32),
("flags", ctypes.c_uint16),
("res", ctypes.c_uint8),
("size", ctypes.c_uint8),
("data", ctypes.c_uint8 * 64),
]
# OCI CAN - Initialization
OCI_CAN_THREE_SAMPLES_PER_BIT = 2
OCI_CAN_SINGLE_SYNC_EDGE = 1
OCI_CAN_MEDIA_HIGH_SPEED = 1
OCI_CAN_STATE_ACTIVE = 0x00000001
OCI_CAN_STATE_PASSIVE = 0x00000002
OCI_CAN_STATE_ERRLIMIT = 0x00000004
OCI_CAN_STATE_BUSOFF = 0x00000008
OCI_CANBusParticipationMode = ctypes.c_uint32
OCI_BUSMODE_PASSIVE = OCI_CANBusParticipationMode(1)
OCI_BUSMODE_ACTIVE = OCI_CANBusParticipationMode(2)
OCI_CANBusTransmissionPolicies = ctypes.c_uint32
OCI_CANTX_UNDEFINED = OCI_CANBusTransmissionPolicies(0)
OCI_CANTX_DONTCARE = OCI_CANBusTransmissionPolicies(0)
OCI_CANTX_FIFO = OCI_CANBusTransmissionPolicies(1)
OCI_CANTX_BESTEFFORT = OCI_CANBusTransmissionPolicies(2)
class OCI_CANConfiguration(ctypes.Structure):
_fields_ = [
("baudrate", ctypes.c_uint32),
("samplePoint", ctypes.c_uint32),
("samplesPerBit", ctypes.c_uint32),
("BTL_Cycles", ctypes.c_uint32),
("SJW", ctypes.c_uint32),
("syncEdge", ctypes.c_uint32),
("physicalMedia", ctypes.c_uint32),
("selfReceptionMode", OCI_SelfReceptionMode),
("busParticipationMode", OCI_CANBusParticipationMode),
("canFDEnabled", ctypes.c_uint32),
("canFDConfig", OCI_CANFDConfiguration),
("canTxPolicy", OCI_CANBusTransmissionPolicies),
]
class OCI_CANControllerProperties(ctypes.Structure):
_fields_ = [
("mode", OCI_ControllerPropertiesMode),
]
class OCI_CANControllerCapabilities(ctypes.Structure):
_fields_ = [
("samplesPerBit", ctypes.c_uint32),
("syncEdge", ctypes.c_uint32),
("physicalMedia", ctypes.c_uint32),
("reserved", ctypes.c_uint32),
("busEvents", ctypes.c_uint32),
("errorFrames", ctypes.c_uint32),
("messageFlags", ctypes.c_uint32),
("canFDSupport", ctypes.c_uint32),
("canFDMaxDataSize", ctypes.c_uint32),
("canFDMaxQualifiedDataRate", ctypes.c_uint32),
("canFDMaxDataRate", ctypes.c_uint32),
("canFDRxConfig_CANMode", ctypes.c_uint32),
("canFDRxConfig_CANFDMode", ctypes.c_uint32),
("canFDTxConfig_Mode", ctypes.c_uint32),
("canBusParticipationMode", ctypes.c_uint32),
("canTxPolicies", ctypes.c_uint32),
]
class OCI_CANControllerStatus(ctypes.Structure):
_fields_ = [
("reserved", ctypes.c_uint32),
("stateCode", ctypes.c_uint32),
]
OCI_CreateCANControllerNoSearch = _oci.OCI_CreateCANControllerNoSearch
OCI_CreateCANControllerNoSearch.argtypes = [
ctypes.c_char_p,
ctypes.POINTER(BOA_Version),
ctypes.POINTER(CSI_Tree),
ctypes.POINTER(OCI_ControllerHandle),
]
OCI_CreateCANControllerNoSearch.restype = OCI_ErrorCode
OCI_CreateCANControllerNoSearch.errcheck = errcheck_init
OCI_OpenCANController = _oci.OCI_OpenCANController
OCI_OpenCANController.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANConfiguration),
ctypes.POINTER(OCI_CANControllerProperties),
]
OCI_OpenCANController.restype = OCI_ErrorCode
# no .errcheck, since we tolerate OCI_WARN_PARAM_ADAPTED warning
# OCI_OpenCANController.errcheck = errcheck_init
OCI_CloseCANController = _oci.OCI_CloseCANController
OCI_CloseCANController.argtypes = [OCI_ControllerHandle]
OCI_CloseCANController.restype = OCI_ErrorCode
OCI_CloseCANController.errcheck = errcheck_oper
OCI_DestroyCANController = _oci.OCI_DestroyCANController
OCI_DestroyCANController.argtypes = [OCI_ControllerHandle]
OCI_DestroyCANController.restype = OCI_ErrorCode
OCI_DestroyCANController.errcheck = errcheck_oper
OCI_AdaptCANConfiguration = _oci.OCI_AdaptCANConfiguration
OCI_AdaptCANConfiguration.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANConfiguration),
]
OCI_AdaptCANConfiguration.restype = OCI_ErrorCode
OCI_AdaptCANConfiguration.errcheck = errcheck_oper
OCI_GetCANControllerCapabilities = _oci.OCI_GetCANControllerCapabilities
OCI_GetCANControllerCapabilities.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANControllerCapabilities),
]
OCI_GetCANControllerCapabilities.restype = OCI_ErrorCode
OCI_GetCANControllerCapabilities.errcheck = errcheck_init
OCI_GetCANControllerStatus = _oci.OCI_GetCANControllerStatus
OCI_GetCANControllerStatus.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANControllerStatus),
]
OCI_GetCANControllerStatus.restype = OCI_ErrorCode
OCI_GetCANControllerStatus.errcheck = errcheck_oper
# OCI CAN - Filter
class OCI_CANRxFilter(ctypes.Structure):
_fields_ = [
("frameIDValue", ctypes.c_uint32),
("frameIDMask", ctypes.c_uint32),
("tag", ctypes.c_uint32),
]
class OCI_CANRxFilterEx(ctypes.Structure):
_fields_ = [
("frameIDValue", ctypes.c_uint32),
("frameIDMask", ctypes.c_uint32),
("tag", ctypes.c_uint32),
("flagsValue", ctypes.c_uint16),
("flagsMask", ctypes.c_uint16),
]
OCI_AddCANFrameFilterEx = _oci.OCI_AddCANFrameFilterEx
OCI_AddCANFrameFilterEx.argtypes = [
OCI_QueueHandle,
ctypes.POINTER(ctypes.POINTER(OCI_CANRxFilterEx)),
ctypes.c_uint32,
]
OCI_AddCANFrameFilterEx.restype = OCI_ErrorCode
OCI_AddCANFrameFilterEx.errcheck = errcheck_oper
OCI_RemoveCANFrameFilterEx = _oci.OCI_RemoveCANFrameFilterEx
OCI_RemoveCANFrameFilterEx.argtypes = [
OCI_QueueHandle,
ctypes.POINTER(ctypes.POINTER(OCI_CANRxFilterEx)),
ctypes.c_uint32,
]
OCI_RemoveCANFrameFilterEx.restype = OCI_ErrorCode
OCI_RemoveCANFrameFilterEx.errcheck = errcheck_oper
# OCI CAN - Messages
OCI_CAN_MSG_FLAG_EXTENDED = 0x1
OCI_CAN_MSG_FLAG_REMOTE_FRAME = 0x2
OCI_CAN_MSG_FLAG_SELFRECEPTION = 0x4
OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE = 0x8
OCI_CAN_MSG_FLAG_FD_TRUNC_AND_PAD = 0x10
OCI_CAN_MSG_FLAG_FD_ERROR_PASSIVE = 0x20
OCI_CAN_MSG_FLAG_FD_DATA = 0x40
OCI_CANMessageDataType = ctypes.c_uint32
OCI_CAN_RX_MESSAGE = OCI_CANMessageDataType(1)
OCI_CAN_TX_MESSAGE = OCI_CANMessageDataType(2)
OCI_CAN_ERROR_FRAME = OCI_CANMessageDataType(3)
OCI_CAN_BUS_EVENT = OCI_CANMessageDataType(4)
OCI_CAN_INTERNAL_ERROR_EVENT = OCI_CANMessageDataType(5)
OCI_CAN_QUEUE_EVENT = OCI_CANMessageDataType(6)
OCI_CAN_TIMER_EVENT = OCI_CANMessageDataType(7)
OCI_CANFDRX_MESSAGE = OCI_CANMessageDataType(8)
OCI_CANFDTX_MESSAGE = OCI_CANMessageDataType(9)
class OCI_CANTxMessage(ctypes.Structure):
_fields_ = [
("frameID", ctypes.c_uint32),
("flags", ctypes.c_uint16),
("res", ctypes.c_uint8),
("dlc", ctypes.c_uint8),
("data", ctypes.c_uint8 * 8),
]
class OCI_CANRxMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("frameID", ctypes.c_uint32),
("flags", ctypes.c_uint16),
("res", ctypes.c_uint8),
("dlc", ctypes.c_uint8),
("res1", ctypes.c_uint8 * 4),
("data", ctypes.c_uint8 * 8),
]
class OCI_CANErrorFrameMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("frameID", ctypes.c_uint32),
("flags", ctypes.c_uint16),
("res", ctypes.c_uint8),
("dlc", ctypes.c_uint8),
("type", ctypes.c_uint32),
("destination", ctypes.c_uint32),
]
class OCI_CANEventMessage(ctypes.Structure):
_fields_ = [
("timeStamp", OCI_Time),
("tag", ctypes.c_uint32),
("eventCode", ctypes.c_uint32),
("destination", ctypes.c_uint32),
]
class OCI_CANMessageData(ctypes.Union):
_fields_ = [
("rxMessage", OCI_CANRxMessage),
("txMessage", OCI_CANTxMessage),
("errorFrameMessage", OCI_CANErrorFrameMessage),
("canEventMessage", OCI_CANEventMessage),
("internalErrorEventMessage", OCI_InternalErrorEventMessage),
("timerEventMessage", OCI_TimerEventMessage),
("queueEventMessage", OCI_QueueEventMessage),
]
class OCI_CANMessageDataEx(ctypes.Union):
_fields_ = [
("rxMessage", OCI_CANRxMessage),
("txMessage", OCI_CANTxMessage),
("errorFrameMessage", OCI_CANErrorFrameMessage),
("canEventMessage", OCI_CANEventMessage),
("internalErrorEventMessage", OCI_InternalErrorEventMessage),
("timerEventMessage", OCI_TimerEventMessage),
("queueEventMessage", OCI_QueueEventMessage),
("canFDRxMessage", OCI_CANFDRxMessage),
("canFDTxMessage", OCI_CANFDTxMessage),
]
class OCI_CANMessage(ctypes.Structure):
_fields_ = [
("type", OCI_CANMessageDataType),
("reserved", ctypes.c_uint32),
("data", OCI_CANMessageData),
]
class OCI_CANMessageEx(ctypes.Structure):
_fields_ = [
("type", OCI_CANMessageDataType),
("reserved", ctypes.c_uint32),
("data", OCI_CANMessageDataEx),
]
# OCI CAN - Queues
OCI_CANRxCallbackFunctionSingleMsg = ctypes.CFUNCTYPE(
None, ctypes.c_void_p, ctypes.POINTER(OCI_CANMessage)
)
OCI_CANRxCallbackFunctionSingleMsgEx = ctypes.CFUNCTYPE(
None, ctypes.c_void_p, ctypes.POINTER(OCI_CANMessageEx)
)
class OCI_CANRxCallbackSingleMsg(ctypes.Structure):
class _U(ctypes.Union):
_fields_ = [
("function", OCI_CANRxCallbackFunctionSingleMsg),
("functionEx", OCI_CANRxCallbackFunctionSingleMsgEx),
]
_anonymous_ = ("u",)
_fields_ = [
("u", _U),
("userData", ctypes.c_void_p),
]
class OCI_CANRxQueueConfiguration(ctypes.Structure):
_fields_ = [
("onFrame", OCI_CANRxCallbackSingleMsg),
("onEvent", OCI_CANRxCallbackSingleMsg),
("selfReceptionMode", OCI_SelfReceptionMode),
]
class OCI_CANTxQueueConfiguration(ctypes.Structure):
_fields_ = [
("reserved", ctypes.c_uint32),
]
OCI_CreateCANRxQueue = _oci.OCI_CreateCANRxQueue
OCI_CreateCANRxQueue.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANRxQueueConfiguration),
ctypes.POINTER(OCI_QueueHandle),
]
OCI_CreateCANRxQueue.restype = OCI_ErrorCode
OCI_CreateCANRxQueue.errcheck = errcheck_init
OCI_DestroyCANRxQueue = _oci.OCI_DestroyCANRxQueue
OCI_DestroyCANRxQueue.argtypes = [OCI_QueueHandle]
OCI_DestroyCANRxQueue.restype = OCI_ErrorCode
OCI_DestroyCANRxQueue.errcheck = errcheck_oper
OCI_CreateCANTxQueue = _oci.OCI_CreateCANTxQueue
OCI_CreateCANTxQueue.argtypes = [
OCI_ControllerHandle,
ctypes.POINTER(OCI_CANTxQueueConfiguration),
ctypes.POINTER(OCI_QueueHandle),
]
OCI_CreateCANTxQueue.restype = OCI_ErrorCode
OCI_CreateCANTxQueue.errcheck = errcheck_init
OCI_DestroyCANTxQueue = _oci.OCI_DestroyCANTxQueue
OCI_DestroyCANTxQueue.argtypes = [OCI_QueueHandle]
OCI_DestroyCANTxQueue.restype = OCI_ErrorCode
OCI_DestroyCANTxQueue.errcheck = errcheck_oper
OCI_WriteCANDataEx = _oci.OCI_WriteCANDataEx
OCI_WriteCANDataEx.argtypes = [
OCI_QueueHandle,
OCI_Time,
ctypes.POINTER(ctypes.POINTER(OCI_CANMessageEx)),
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
]
OCI_WriteCANDataEx.restype = OCI_ErrorCode
OCI_WriteCANDataEx.errcheck = errcheck_oper
OCI_ReadCANDataEx = _oci.OCI_ReadCANDataEx
OCI_ReadCANDataEx.argtypes = [
OCI_QueueHandle,
OCI_Time,
ctypes.POINTER(ctypes.POINTER(OCI_CANMessageEx)),
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_uint32),
]
OCI_ReadCANDataEx.restype = OCI_ErrorCode
OCI_ReadCANDataEx.errcheck = errcheck_oper
python-can-4.5.0/can/interfaces/gs_usb.py 0000664 0000000 0000000 00000013740 14722003266 0020327 0 ustar 00root root 0000000 0000000 import logging
from typing import Optional, Tuple
import usb
from gs_usb.constants import CAN_EFF_FLAG, CAN_ERR_FLAG, CAN_MAX_DLC, CAN_RTR_FLAG
from gs_usb.gs_usb import GsUsb
from gs_usb.gs_usb_frame import GS_USB_NONE_ECHO_ID, GsUsbFrame
import can
from ..exceptions import CanInitializationError, CanOperationError
logger = logging.getLogger(__name__)
class GsUsbBus(can.BusABC):
def __init__(
self,
channel,
bitrate: int = 500_000,
index=None,
bus=None,
address=None,
can_filters=None,
**kwargs,
):
"""
:param channel: usb device name
:param index: device number if using automatic scan, starting from 0.
If specified, bus/address shall not be provided.
:param bus: number of the bus that the device is connected to
:param address: address of the device on the bus it is connected to
:param can_filters: not supported
:param bitrate: CAN network bandwidth (bits/s)
"""
self._is_shutdown = False
if (index is not None) and ((bus or address) is not None):
raise CanInitializationError(
"index and bus/address cannot be used simultaneously"
)
if index is None and address is None and bus is None:
index = channel
self._index = None
if index is not None:
devs = GsUsb.scan()
if len(devs) <= index:
raise CanInitializationError(
f"Cannot find device {index}. Devices found: {len(devs)}"
)
gs_usb = devs[index]
self._index = index
else:
gs_usb = GsUsb.find(bus=bus, address=address)
if not gs_usb:
raise CanInitializationError(f"Cannot find device {channel}")
self.gs_usb = gs_usb
self.channel_info = channel
self._can_protocol = can.CanProtocol.CAN_20
bit_timing = can.BitTiming.from_sample_point(
f_clock=self.gs_usb.device_capability.fclk_can,
bitrate=bitrate,
sample_point=87.5,
)
props_seg = 1
self.gs_usb.set_timing(
prop_seg=props_seg,
phase_seg1=bit_timing.tseg1 - props_seg,
phase_seg2=bit_timing.tseg2,
sjw=bit_timing.sjw,
brp=bit_timing.brp,
)
self.gs_usb.start()
self._bitrate = bitrate
super().__init__(
channel=channel,
can_filters=can_filters,
**kwargs,
)
def send(self, msg: can.Message, timeout: Optional[float] = None):
"""Transmit a message to the CAN bus.
:param Message msg: A message object.
:param timeout: timeout is not supported.
The function won't return until message is sent or exception is raised.
:raises CanOperationError:
if the message could not be sent
"""
can_id = msg.arbitration_id
if msg.is_extended_id:
can_id = can_id | CAN_EFF_FLAG
if msg.is_remote_frame:
can_id = can_id | CAN_RTR_FLAG
if msg.is_error_frame:
can_id = can_id | CAN_ERR_FLAG
# Pad message data
msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data)))
frame = GsUsbFrame()
frame.can_id = can_id
frame.can_dlc = msg.dlc
frame.timestamp_us = 0 # timestamp frame field is only useful on receive
frame.data = list(msg.data)
try:
self.gs_usb.send(frame)
except usb.core.USBError as exc:
raise CanOperationError("The message could not be sent") from exc
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[can.Message], bool]:
"""
Read a message from the bus and tell whether it was filtered.
This methods may be called by :meth:`~can.BusABC.recv`
to read a message multiple times if the filters set by
:meth:`~can.BusABC.set_filters` do not match and the call has
not yet timed out.
Never raises an error/exception.
:param float timeout: seconds to wait for a message,
see :meth:`~can.BusABC.send`
0 and None will be converted to minimum value 1ms.
:return:
1. a message that was read or None on timeout
2. a bool that is True if message filtering has already
been done and else False. In this interface it is always False
since filtering is not available
"""
frame = GsUsbFrame()
# Do not set timeout as None or zero here to avoid blocking
timeout_ms = round(timeout * 1000) if timeout else 1
if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms):
return None, False
msg = can.Message(
timestamp=frame.timestamp,
arbitration_id=frame.arbitration_id,
is_extended_id=frame.is_extended_id,
is_remote_frame=frame.is_remote_frame,
is_error_frame=frame.is_error_frame,
channel=self.channel_info,
dlc=frame.can_dlc,
data=bytearray(frame.data)[0 : frame.can_dlc],
is_rx=frame.echo_id == GS_USB_NONE_ECHO_ID,
)
return msg, False
def shutdown(self):
if self._is_shutdown:
return
super().shutdown()
self.gs_usb.stop()
if self._index is not None:
# Avoid errors on subsequent __init() by repeating the .scan() and .start() that would otherwise fail
# the next time the device is opened in __init__()
devs = GsUsb.scan()
if self._index < len(devs):
gs_usb = devs[self._index]
try:
gs_usb.set_bitrate(self._bitrate)
gs_usb.start()
gs_usb.stop()
except usb.core.USBError:
pass
self._is_shutdown = True
python-can-4.5.0/can/interfaces/ics_neovi/ 0000775 0000000 0000000 00000000000 14722003266 0020444 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/ics_neovi/__init__.py 0000664 0000000 0000000 00000000333 14722003266 0022554 0 ustar 00root root 0000000 0000000 """
"""
__all__ = [
"ICSApiError",
"ICSInitializationError",
"ICSOperationError",
"NeoViBus",
"neovi_bus",
]
from .neovi_bus import ICSApiError, ICSInitializationError, ICSOperationError, NeoViBus
python-can-4.5.0/can/interfaces/ics_neovi/neovi_bus.py 0000664 0000000 0000000 00000042721 14722003266 0023015 0 ustar 00root root 0000000 0000000 """
Intrepid Control Systems (ICS) neoVI interface module.
python-ics is a Python wrapper around the API provided by Intrepid Control
Systems for communicating with their neoVI range of devices.
Implementation references:
* https://github.com/intrepidcs/python_ics
"""
import functools
import logging
import os
import tempfile
from collections import Counter, defaultdict, deque
from datetime import datetime
from functools import partial
from itertools import cycle
from threading import Event
from warnings import warn
from can import BusABC, CanProtocol, Message
from ...exceptions import (
CanError,
CanInitializationError,
CanOperationError,
CanTimeoutError,
)
logger = logging.getLogger(__name__)
try:
import ics
except ImportError as ie:
logger.warning(
"You won't be able to use the ICS neoVI can backend without the "
"python-ics module installed!: %s",
ie,
)
ics = None
try:
from filelock import FileLock
except ImportError as ie:
logger.warning(
"Using ICS neoVI can backend without the "
"filelock module installed may cause some issues!: %s",
ie,
)
class FileLock:
"""Dummy file lock that does not actually do anything"""
def __init__(self, lock_file, timeout=-1):
self._lock_file = lock_file
self.timeout = timeout
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return None
# Use inter-process mutex to prevent concurrent device open.
# When neoVI server is enabled, there is an issue with concurrent device open.
open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock"))
description_id = cycle(range(1, 0x8000))
ICS_EPOCH = datetime.fromisoformat("2007-01-01")
ICS_EPOCH_DELTA = (ICS_EPOCH - datetime.fromisoformat("1970-01-01")).total_seconds()
class ICSApiError(CanError):
"""
Indicates an error with the ICS API.
"""
# A critical error which affects operation or accuracy.
ICS_SPY_ERR_CRITICAL = 0x10
# An error which is not understood.
ICS_SPY_ERR_QUESTION = 0x20
# An important error which may be critical depending on the application
ICS_SPY_ERR_EXCLAMATION = 0x30
# An error which probably does not need attention.
ICS_SPY_ERR_INFORMATION = 0x40
def __init__(
self,
error_code: int,
description_short: str,
description_long: str,
severity: int,
restart_needed: int,
):
super().__init__(f"{description_short}. {description_long}", error_code)
self.description_short = description_short
self.description_long = description_long
self.severity = severity
self.restart_needed = restart_needed == 1
def __reduce__(self):
return type(self), (
self.error_code,
self.description_short,
self.description_long,
self.severity,
self.restart_needed,
)
@property
def error_number(self) -> int:
"""Deprecated. Renamed to :attr:`can.CanError.error_code`."""
warn(
"ICSApiError::error_number has been replaced by ICSApiError.error_code in python-can 4.0"
"and will be remove in version 5.0.",
DeprecationWarning,
stacklevel=2,
)
return self.error_code
@property
def is_critical(self) -> bool:
return self.severity == self.ICS_SPY_ERR_CRITICAL
class ICSInitializationError(ICSApiError, CanInitializationError):
pass
class ICSOperationError(ICSApiError, CanOperationError):
pass
def check_if_bus_open(func):
"""
Decorator that checks if the bus is open before executing the function.
If the bus is not open, it raises a CanOperationError.
"""
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
"""
Wrapper function that checks if the bus is open before executing the function.
:raises CanOperationError: If the bus is not open.
"""
if self._is_shutdown:
raise CanOperationError("Cannot operate on a closed bus")
return func(self, *args, **kwargs)
return wrapper
class NeoViBus(BusABC):
"""
The CAN Bus implemented for the python_ics interface
https://github.com/intrepidcs/python_ics
"""
def __init__(self, channel, can_filters=None, **kwargs):
"""
:param channel:
The channel ids to create this bus with.
Can also be a single integer, netid name or a comma separated
string.
:type channel: int or str or list(int) or list(str)
:param list can_filters:
See :meth:`can.BusABC.set_filters` for details.
:param bool receive_own_messages:
If transmitted messages should also be received by this bus.
:param bool use_system_timestamp:
Use system timestamp for can messages instead of the hardware time
stamp
:param str serial:
Serial to connect (optional, will use the first found if not
supplied)
:param int bitrate:
Channel bitrate in bit/s. (optional, will enable the auto bitrate
feature if not supplied)
:param bool fd:
If CAN-FD frames should be supported.
:param int data_bitrate:
Which bitrate to use for data phase in CAN FD.
Defaults to arbitration bitrate.
:param override_library_name:
Absolute path or relative path to the library including filename.
:raise ImportError:
If *python-ics* is not available
:raise CanInitializationError:
If the bus could not be set up.
May or may not be a :class:`~ICSInitializationError`.
"""
if ics is None:
raise ImportError("Please install python-ics")
super().__init__(
channel=channel,
can_filters=can_filters,
**kwargs,
)
logger.info(f"CAN Filters: {can_filters}")
logger.info(f"Got configuration of: {kwargs}")
if "override_library_name" in kwargs:
ics.override_library_name(kwargs.get("override_library_name"))
if isinstance(channel, (list, tuple)):
self.channels = channel
elif isinstance(channel, int):
self.channels = [channel]
else:
# Assume comma separated string of channels
self.channels = [ch.strip() for ch in channel.split(",")]
self.channels = [NeoViBus.channel_to_netid(ch) for ch in self.channels]
type_filter = kwargs.get("type_filter")
serial = kwargs.get("serial")
self.dev = self._find_device(type_filter, serial)
is_fd = kwargs.get("fd", False)
self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20
with open_lock:
ics.open_device(self.dev)
try:
if "bitrate" in kwargs:
for channel in self.channels:
ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel)
if is_fd:
if "data_bitrate" in kwargs:
for channel in self.channels:
ics.set_fd_bit_rate(
self.dev, kwargs.get("data_bitrate"), channel
)
except ics.RuntimeError as re:
logger.error(re)
err = ICSInitializationError(*ics.get_last_api_error(self.dev))
try:
self.shutdown()
finally:
raise err
self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False))
self._receive_own_messages = kwargs.get("receive_own_messages", True)
self.channel_info = (
f"{self.dev.Name} {self.get_serial_number(self.dev)} CH:{self.channels}"
)
logger.info(f"Using device: {self.channel_info}")
self.rx_buffer = deque()
self.message_receipts = defaultdict(Event)
@staticmethod
def channel_to_netid(channel_name_or_id):
try:
channel = int(channel_name_or_id)
except ValueError:
netid = f"NETID_{channel_name_or_id.upper()}"
if hasattr(ics, netid):
channel = getattr(ics, netid)
else:
raise ValueError(
"channel must be an integer or a valid ICS channel name"
) from None
return channel
@staticmethod
def get_serial_number(device):
"""Decode (if needed) and return the ICS device serial string
:param device: ics device
:return: ics device serial string
:rtype: str
"""
if int("0A0000", 36) < device.SerialNumber < int("ZZZZZZ", 36):
return ics.base36enc(device.SerialNumber)
else:
return str(device.SerialNumber)
def shutdown(self):
super().shutdown()
ics.close_device(self.dev)
@staticmethod
def _detect_available_configs():
"""Detect all configurations/channels that this interface could
currently connect with.
:rtype: Iterator[dict]
:return: an iterable of dicts, each being a configuration suitable
for usage in the interface's bus constructor.
"""
if ics is None:
return []
try:
devices = ics.find_devices()
except Exception as e:
logger.debug("Failed to detect configs: %s", e)
return []
# TODO: add the channel(s)
return [
{"interface": "neovi", "serial": NeoViBus.get_serial_number(device)}
for device in devices
]
def _find_device(self, type_filter=None, serial=None):
"""Returns the first matching device or raises an error.
:raise CanInitializationError:
If not matching device could be found
"""
if type_filter is not None:
devices = ics.find_devices(type_filter)
else:
devices = ics.find_devices()
for device in devices:
if serial is None or self.get_serial_number(device) == str(serial):
return device
msg = ["No device"]
if type_filter is not None:
msg.append(f"with type {type_filter}")
if serial is not None:
msg.append(f"with serial {serial}")
msg.append("found.")
raise CanInitializationError(" ".join(msg))
@check_if_bus_open
def _process_msg_queue(self, timeout=0.1):
try:
messages, errors = ics.get_messages(self.dev, False, timeout)
except ics.RuntimeError:
return
for ics_msg in messages:
channel = ics_msg.NetworkID | (ics_msg.NetworkID2 << 8)
if channel not in self.channels:
continue
is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG)
if is_tx:
if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR):
continue
receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID)
if ics_msg.DescriptionID and receipt_key in self.message_receipts:
self.message_receipts[receipt_key].set()
if not self._receive_own_messages:
continue
self.rx_buffer.append(ics_msg)
if errors:
logger.warning("%d error(s) found", errors)
for msg, count in Counter(ics.get_error_messages(self.dev)).items():
error = ICSApiError(*msg)
if count > 1:
logger.warning(f"{error} (Repeated {count} times)")
else:
logger.warning(error)
def _get_timestamp_for_msg(self, ics_msg):
if self._use_system_timestamp:
# This is the system time stamp.
# TimeSystem is loaded with the value received from the timeGetTime
# call in the WIN32 multimedia API.
#
# The timeGetTime accuracy is up to 1 millisecond. See the WIN32
# API documentation for more information.
#
# This timestamp is useful for time comparing with other system
# events or data which is not synced with the neoVI timestamp.
#
# Currently, TimeSystem2 is not used.
return ics_msg.TimeSystem
else:
# This is the hardware time stamp.
return ics.get_timestamp_for_msg(self.dev, ics_msg) + ICS_EPOCH_DELTA
def _ics_msg_to_message(self, ics_msg):
is_fd = ics_msg.Protocol == ics.SPY_PROTOCOL_CANFD
message_from_ics = partial(
Message,
timestamp=self._get_timestamp_for_msg(ics_msg),
arbitration_id=ics_msg.ArbIDOrHeader,
is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME),
is_remote_frame=bool(ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME),
is_error_frame=bool(ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME),
channel=ics_msg.NetworkID | (ics_msg.NetworkID2 << 8),
dlc=ics_msg.NumberBytesData,
is_fd=is_fd,
is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG),
)
if is_fd:
if ics_msg.ExtraDataPtrEnabled:
data = ics_msg.ExtraDataPtr[: ics_msg.NumberBytesData]
else:
data = ics_msg.Data[: ics_msg.NumberBytesData]
return message_from_ics(
data=data,
error_state_indicator=bool(
ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_ESI
),
bitrate_switch=bool(
ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_BRS
),
)
else:
return message_from_ics(
data=ics_msg.Data[: ics_msg.NumberBytesData],
)
def _recv_internal(self, timeout=0.1):
if not self.rx_buffer:
self._process_msg_queue(timeout=timeout)
try:
ics_msg = self.rx_buffer.popleft()
msg = self._ics_msg_to_message(ics_msg)
except IndexError:
return None, False
return msg, False
@check_if_bus_open
def send(self, msg, timeout=0):
"""Transmit a message to the CAN bus.
:param Message msg: A message object.
:param float timeout:
If > 0, wait up to this many seconds for message to be ACK'ed.
If timeout is exceeded, an exception will be raised.
None blocks indefinitely.
:raises ValueError:
if the message is invalid
:raises can.CanTimeoutError:
if sending timed out
:raises CanOperationError:
If the bus is closed or the message could otherwise not be sent.
May or may not be a :class:`~ICSOperationError`.
"""
if not ics.validate_hobject(self.dev):
raise CanOperationError("bus not open")
# Check for valid DLC to avoid passing extra long data to the driver
if msg.is_fd:
if msg.dlc > 64:
raise ValueError(
f"DLC was {msg.dlc} but it should be <= 64 for CAN FD frames"
)
elif msg.dlc > 8:
raise ValueError(
f"DLC was {msg.dlc} but it should be <= 8 for normal CAN frames"
)
message = ics.SpyMessage()
flag0 = 0
if msg.is_extended_id:
flag0 |= ics.SPY_STATUS_XTD_FRAME
if msg.is_remote_frame:
flag0 |= ics.SPY_STATUS_REMOTE_FRAME
flag3 = 0
if msg.is_fd:
message.Protocol = ics.SPY_PROTOCOL_CANFD
if msg.bitrate_switch:
flag3 |= ics.SPY_STATUS3_CANFD_BRS
if msg.error_state_indicator:
flag3 |= ics.SPY_STATUS3_CANFD_ESI
message.ArbIDOrHeader = msg.arbitration_id
msg_data = msg.data[: msg.dlc]
message.NumberBytesData = msg.dlc
message.Data = tuple(msg_data[:8])
if msg.is_fd and len(msg_data) > 8:
message.ExtraDataPtrEnabled = 1
message.ExtraDataPtr = tuple(msg_data)
message.StatusBitField = flag0
message.StatusBitField2 = 0
message.StatusBitField3 = flag3
if msg.channel is not None:
network_id = msg.channel
elif len(self.channels) == 1:
network_id = self.channels[0]
else:
raise ValueError("msg.channel must be set when using multiple channels.")
message.NetworkID, message.NetworkID2 = int(network_id & 0xFF), int(
(network_id >> 8) & 0xFF
)
if timeout != 0:
msg_desc_id = next(description_id)
message.DescriptionID = msg_desc_id
receipt_key = (msg.arbitration_id, msg_desc_id)
self.message_receipts[receipt_key].clear()
try:
ics.transmit_messages(self.dev, message)
except ics.RuntimeError:
raise ICSOperationError(*ics.get_last_api_error(self.dev)) from None
# If timeout is set, wait for ACK
# This requires a notifier for the bus or
# some other thread calling recv periodically
if timeout != 0:
got_receipt = self.message_receipts[receipt_key].wait(timeout)
# We no longer need this receipt, so no point keeping it in memory
del self.message_receipts[receipt_key]
if not got_receipt:
raise CanTimeoutError("Transmit timeout")
python-can-4.5.0/can/interfaces/iscan.py 0000664 0000000 0000000 00000014314 14722003266 0020140 0 ustar 00root root 0000000 0000000 """
Interface for isCAN from *Thorsis Technologies GmbH*, former *ifak system GmbH*.
"""
import ctypes
import logging
import time
from typing import Optional, Tuple, Union
from can import (
BusABC,
CanError,
CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
CanProtocol,
Message,
)
logger = logging.getLogger(__name__)
CanData = ctypes.c_ubyte * 8
class MessageExStruct(ctypes.Structure):
_fields_ = [
("message_id", ctypes.c_ulong),
("is_extended", ctypes.c_ubyte),
("remote_req", ctypes.c_ubyte),
("data_len", ctypes.c_ubyte),
("data", CanData),
]
def check_status_initialization(result: int, function, arguments) -> int:
if result > 0:
raise IscanInitializationError(function, result, arguments)
return result
def check_status(result: int, function, arguments) -> int:
if result > 0:
raise IscanOperationError(function, result, arguments)
return result
try:
iscan = ctypes.cdll.LoadLibrary("iscandrv")
except OSError as e:
iscan = None
logger.warning("Failed to load IS-CAN driver: %s", e)
else:
iscan.isCAN_DeviceInitEx.argtypes = [ctypes.c_ubyte, ctypes.c_ubyte]
iscan.isCAN_DeviceInitEx.errcheck = check_status_initialization
iscan.isCAN_DeviceInitEx.restype = ctypes.c_ubyte
iscan.isCAN_ReceiveMessageEx.errcheck = check_status
iscan.isCAN_ReceiveMessageEx.restype = ctypes.c_ubyte
iscan.isCAN_TransmitMessageEx.errcheck = check_status
iscan.isCAN_TransmitMessageEx.restype = ctypes.c_ubyte
iscan.isCAN_CloseDevice.errcheck = check_status
iscan.isCAN_CloseDevice.restype = ctypes.c_ubyte
class IscanBus(BusABC):
"""isCAN interface"""
BAUDRATES = {
5000: 0,
10000: 1,
20000: 2,
50000: 3,
100000: 4,
125000: 5,
250000: 6,
500000: 7,
800000: 8,
1000000: 9,
}
def __init__(
self,
channel: Union[str, int],
bitrate: int = 500000,
poll_interval: float = 0.01,
**kwargs,
) -> None:
"""
:param channel:
Device number
:param bitrate:
Bitrate in bits/s
:param poll_interval:
Poll interval in seconds when reading messages
"""
if iscan is None:
raise CanInterfaceNotImplementedError("Could not load isCAN driver")
self.channel = ctypes.c_ubyte(int(channel))
self.channel_info = f"IS-CAN: {self.channel}"
self._can_protocol = CanProtocol.CAN_20
if bitrate not in self.BAUDRATES:
raise ValueError(f"Invalid bitrate, choose one of {set(self.BAUDRATES)}")
self.poll_interval = poll_interval
iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate])
super().__init__(
channel=channel,
bitrate=bitrate,
poll_interval=poll_interval,
**kwargs,
)
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
raw_msg = MessageExStruct()
end_time = time.time() + timeout if timeout is not None else None
while True:
try:
iscan.isCAN_ReceiveMessageEx(self.channel, ctypes.byref(raw_msg))
except IscanError as e:
if e.error_code != 8: # "No message received"
# An error occurred
raise
if end_time is not None and time.time() > end_time:
# No message within timeout
return None, False
# Sleep a short time to avoid hammering
time.sleep(self.poll_interval)
else:
# A message was received
break
msg = Message(
arbitration_id=raw_msg.message_id,
is_extended_id=bool(raw_msg.is_extended),
timestamp=time.time(), # Better than nothing...
is_remote_frame=bool(raw_msg.remote_req),
dlc=raw_msg.data_len,
data=raw_msg.data[: raw_msg.data_len],
channel=self.channel.value,
)
return msg, False
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
raw_msg = MessageExStruct(
msg.arbitration_id,
bool(msg.is_extended_id),
bool(msg.is_remote_frame),
msg.dlc,
CanData(*msg.data),
)
iscan.isCAN_TransmitMessageEx(self.channel, ctypes.byref(raw_msg))
def shutdown(self) -> None:
super().shutdown()
iscan.isCAN_CloseDevice(self.channel)
class IscanError(CanError):
ERROR_CODES = {
0: "Success",
1: "No access to device",
2: "Device with ID not found",
3: "Driver operation failed",
4: "Invalid parameter",
5: "Operation allowed only in online state",
6: "Device timeout",
7: "Device is transmitting a message",
8: "No message received",
9: "Thread not started",
10: "Thread already started",
11: "Buffer overrun",
12: "Device not initialized",
15: "Found the device, but it is being used by another process",
16: "Bus error",
17: "Bus off",
18: "Error passive",
19: "Data overrun",
20: "Error warning",
30: "Send error",
31: "Transmission not acknowledged on bus",
32: "Error critical bus",
35: "Callbackthread is blocked, stopping thread failed",
40: "Need a licence number under NT4",
}
def __init__(self, function, error_code: int, arguments) -> None:
try:
description = ": " + self.ERROR_CODES[error_code]
except KeyError:
description = ""
super().__init__(
f"Function {function.__name__} failed{description}",
error_code=error_code,
)
#: Status code
self.error_code = error_code
#: Function that failed
self.function = function
#: Arguments passed to function
self.arguments = arguments
class IscanOperationError(IscanError, CanOperationError):
pass
class IscanInitializationError(IscanError, CanInitializationError):
pass
python-can-4.5.0/can/interfaces/ixxat/ 0000775 0000000 0000000 00000000000 14722003266 0017623 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/ixxat/__init__.py 0000664 0000000 0000000 00000000774 14722003266 0021744 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016-2021 Giuseppe Corbelli
"""
__all__ = [
"IXXATBus",
"canlib",
"canlib_vcinpl",
"canlib_vcinpl2",
"constants",
"exceptions",
"get_ixxat_hwids",
"structures",
]
from can.interfaces.ixxat.canlib import IXXATBus
# import this and not the one from vcinpl2 for backward compatibility
from can.interfaces.ixxat.canlib_vcinpl import get_ixxat_hwids
python-can-4.5.0/can/interfaces/ixxat/canlib.py 0000664 0000000 0000000 00000013324 14722003266 0021430 0 ustar 00root root 0000000 0000000 from typing import Callable, List, Optional, Sequence, Union
import can.interfaces.ixxat.canlib_vcinpl as vcinpl
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
from can import (
BusABC,
BusState,
CyclicSendTaskABC,
Message,
)
from can.typechecking import AutoDetectedConfig
class IXXATBus(BusABC):
"""The CAN Bus implemented for the IXXAT interface.
Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN,
the other with CAN-FD.
This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2)
class depending on fd user option.
"""
def __init__(
self,
channel: int,
can_filters=None,
receive_own_messages: bool = False,
unique_hardware_id: Optional[int] = None,
extended: bool = True,
fd: bool = False,
rx_fifo_size: Optional[int] = None,
tx_fifo_size: Optional[int] = None,
bitrate: int = 500000,
data_bitrate: int = 2000000,
sjw_abr: Optional[int] = None,
tseg1_abr: Optional[int] = None,
tseg2_abr: Optional[int] = None,
sjw_dbr: Optional[int] = None,
tseg1_dbr: Optional[int] = None,
tseg2_dbr: Optional[int] = None,
ssp_dbr: Optional[int] = None,
**kwargs,
):
"""
:param channel:
The Channel id to create this bus with.
:param can_filters:
See :meth:`can.BusABC.set_filters`.
:param receive_own_messages:
Enable self-reception of sent messages.
:param unique_hardware_id:
UniqueHardwareId to connect (optional, will use the first found if not supplied)
:param extended:
Default True, enables the capability to use extended IDs.
:param fd:
Default False, enables CAN-FD usage.
:param rx_fifo_size:
Receive fifo size (default 1024 for fd, else 16)
:param tx_fifo_size:
Transmit fifo size (default 128 for fd, else 16)
:param bitrate:
Channel bitrate in bit/s
:param data_bitrate:
Channel bitrate in bit/s (only in CAN-Fd if baudrate switch enabled).
:param sjw_abr:
Bus timing value sample jump width (arbitration). Only takes effect with fd enabled.
:param tseg1_abr:
Bus timing value tseg1 (arbitration). Only takes effect with fd enabled.
:param tseg2_abr:
Bus timing value tseg2 (arbitration). Only takes effect with fd enabled.
:param sjw_dbr:
Bus timing value sample jump width (data). Only takes effect with fd and baudrate switch enabled.
:param tseg1_dbr:
Bus timing value tseg1 (data). Only takes effect with fd and bitrate switch enabled.
:param tseg2_dbr:
Bus timing value tseg2 (data). Only takes effect with fd and bitrate switch enabled.
:param ssp_dbr:
Secondary sample point (data). Only takes effect with fd and bitrate switch enabled.
"""
if fd:
if rx_fifo_size is None:
rx_fifo_size = 1024
if tx_fifo_size is None:
tx_fifo_size = 128
self.bus = vcinpl2.IXXATBus(
channel=channel,
can_filters=can_filters,
receive_own_messages=receive_own_messages,
unique_hardware_id=unique_hardware_id,
extended=extended,
rx_fifo_size=rx_fifo_size,
tx_fifo_size=tx_fifo_size,
bitrate=bitrate,
data_bitrate=data_bitrate,
sjw_abr=sjw_abr,
tseg1_abr=tseg1_abr,
tseg2_abr=tseg2_abr,
sjw_dbr=sjw_dbr,
tseg1_dbr=tseg1_dbr,
tseg2_dbr=tseg2_dbr,
ssp_dbr=ssp_dbr,
**kwargs,
)
else:
if rx_fifo_size is None:
rx_fifo_size = 16
if tx_fifo_size is None:
tx_fifo_size = 16
self.bus = vcinpl.IXXATBus(
channel=channel,
can_filters=can_filters,
receive_own_messages=receive_own_messages,
unique_hardware_id=unique_hardware_id,
extended=extended,
rx_fifo_size=rx_fifo_size,
tx_fifo_size=tx_fifo_size,
bitrate=bitrate,
**kwargs,
)
super().__init__(channel=channel, **kwargs)
self._can_protocol = self.bus.protocol
def flush_tx_buffer(self):
"""Flushes the transmit buffer on the IXXAT"""
return self.bus.flush_tx_buffer()
def _recv_internal(self, timeout):
"""Read a message from IXXAT device."""
return self.bus._recv_internal(timeout)
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
return self.bus.send(msg, timeout)
def _send_periodic_internal(
self,
msgs: Union[Sequence[Message], Message],
period: float,
duration: Optional[float] = None,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> CyclicSendTaskABC:
return self.bus._send_periodic_internal(
msgs, period, duration, autostart, modifier_callback
)
def shutdown(self) -> None:
super().shutdown()
self.bus.shutdown()
@property
def state(self) -> BusState:
"""
Return the current state of the hardware
"""
return self.bus.state
@staticmethod
def _detect_available_configs() -> List[AutoDetectedConfig]:
return vcinpl._detect_available_configs()
python-can-4.5.0/can/interfaces/ixxat/canlib_vcinpl.py 0000664 0000000 0000000 00000112305 14722003266 0023002 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
TODO: We could implement this interface such that setting other filters
could work when the initial filters were set to zero using the
software fallback. Or could the software filters even be changed
after the connection was opened? We need to document that bahaviour!
See also the NICAN interface.
"""
import ctypes
import functools
import logging
import sys
import warnings
from typing import Callable, List, Optional, Sequence, Tuple, Union
from can import (
BusABC,
BusState,
CanProtocol,
CyclicSendTaskABC,
LimitedDurationCyclicSendTaskABC,
Message,
RestartableCyclicTaskABC,
)
from can.ctypesutil import HANDLE, PHANDLE, CLibrary
from can.ctypesutil import HRESULT as ctypes_HRESULT
from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
from can.typechecking import AutoDetectedConfig
from can.util import deprecated_args_alias
from . import constants, structures
from .exceptions import *
__all__ = [
"VCITimeout",
"VCIError",
"VCIBusOffError",
"VCIDeviceNotFoundError",
"IXXATBus",
"vciFormatError",
]
log = logging.getLogger("can.ixxat")
# Hack to have vciFormatError as a free function, see below
vciFormatError = None
# main ctypes instance
_canlib = None
# TODO: Use ECI driver for linux
if sys.platform == "win32" or sys.platform == "cygwin":
try:
_canlib = CLibrary("vcinpl.dll")
except Exception as e:
log.warning("Cannot load IXXAT vcinpl library: %s", e)
else:
# Will not work on other systems, but have it importable anyway for
# tests/sphinx
log.warning("IXXAT VCI library does not work on %s platform", sys.platform)
def __vciFormatErrorExtended(
library_instance: CLibrary, function: Callable, vret: int, args: Tuple
):
"""Format a VCI error and attach failed function, decoded HRESULT and arguments
:param CLibrary library_instance:
Mapped instance of IXXAT vcinpl library
:param callable function:
Failed function
:param HRESULT vret:
HRESULT returned by vcinpl call
:param args:
Arbitrary arguments tuple
:return:
Formatted string
"""
# TODO: make sure we don't generate another exception
return (
f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}"
)
def __vciFormatError(library_instance: CLibrary, function: Callable, vret: int):
"""Format a VCI error and attach failed function and decoded HRESULT
:param CLibrary library_instance:
Mapped instance of IXXAT vcinpl library
:param callable function:
Failed function
:param HRESULT vret:
HRESULT returned by vcinpl call
:return:
Formatted string
"""
buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN)
ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN)
library_instance.vciFormatError(vret, buf, constants.VCI_MAX_ERRSTRLEN)
return "function {} failed ({})".format(
function._name, buf.value.decode("utf-8", "replace")
)
def __check_status(result, function, args):
"""
Check the result of a vcinpl function call and raise appropriate exception
in case of an error. Used as errcheck function when mapping C functions
with ctypes.
:param result:
Function call numeric result
:param callable function:
Called function
:param args:
Arbitrary arguments tuple
:raise:
:class:VCITimeout
:class:VCIRxQueueEmptyError
:class:StopIteration
:class:VCIError
"""
if isinstance(result, int):
# Real return value is an unsigned long, the following line converts the number to unsigned
result = ctypes.c_ulong(result).value
if result == constants.VCI_E_TIMEOUT:
raise VCITimeout(f"Function {function._name} timed out")
elif result == constants.VCI_E_RXQUEUE_EMPTY:
raise VCIRxQueueEmptyError()
elif result == constants.VCI_E_NO_MORE_ITEMS:
raise StopIteration()
elif result == constants.VCI_E_ACCESSDENIED:
pass # not a real error, might happen if another program has initialized the bus
elif result != constants.VCI_OK:
raise VCIError(vciFormatError(function, result))
return result
try:
# Map all required symbols and initialize library ---------------------------
# HRESULT VCIAPI vciInitialize ( void );
_canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status)
# void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize);
_canlib.map_symbol(
"vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)
)
# Hack to have vciFormatError as a free function
vciFormatError = functools.partial(__vciFormatError, _canlib)
# HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum );
_canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status)
# HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum );
_canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status)
# HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo );
_canlib.map_symbol(
"vciEnumDeviceNext",
ctypes.c_long,
(HANDLE, structures.PVCIDEVICEINFO),
__check_status,
)
# HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice );
_canlib.map_symbol(
"vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status
)
# HRESULT vciDeviceClose( HANDLE hDevice )
_canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status)
# HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn );
_canlib.map_symbol(
"canChannelOpen",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold );
_canlib.map_symbol(
"canChannelInitialize",
ctypes.c_long,
(HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable );
_canlib.map_symbol(
"canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status
)
# HRESULT canChannelClose( HANDLE hChannel )
_canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg );
_canlib.map_symbol(
"canChannelReadMessage",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, structures.PCANMSG),
__check_status,
)
# HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg );
_canlib.map_symbol(
"canChannelPeekMessage",
ctypes.c_long,
(HANDLE, structures.PCANMSG),
__check_status,
)
# HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout );
_canlib.map_symbol(
"canChannelWaitTxEvent",
ctypes.c_long,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout );
_canlib.map_symbol(
"canChannelWaitRxEvent",
ctypes.c_long,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg );
_canlib.map_symbol(
"canChannelPostMessage",
ctypes.c_long,
(HANDLE, structures.PCANMSG),
__check_status,
)
# HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg );
_canlib.map_symbol(
"canChannelSendMessage",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, structures.PCANMSG),
__check_status,
)
# HRESULT canChannelGetStatus (HANDLE hCanChn, PCANCHANSTATUS pStatus );
_canlib.map_symbol(
"canChannelGetStatus",
ctypes.c_long,
(HANDLE, structures.PCANCHANSTATUS),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl );
_canlib.map_symbol(
"canControlOpen",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 );
_canlib.map_symbol(
"canControlInitialize",
ctypes.c_long,
(HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl );
_canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl );
_canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart );
_canlib.map_symbol(
"canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status
)
# EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus );
_canlib.map_symbol(
"canControlGetStatus",
ctypes.c_long,
(HANDLE, structures.PCANLINESTATUS),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps );
_canlib.map_symbol(
"canControlGetCaps",
ctypes.c_long,
(HANDLE, structures.PCANCAPABILITIES),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask );
_canlib.map_symbol(
"canControlSetAccFilter",
ctypes.c_long,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask);
_canlib.map_symbol(
"canControlAddFilterIds",
ctypes.c_long,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask );
_canlib.map_symbol(
"canControlRemFilterIds",
ctypes.c_long,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler );
_canlib.map_symbol(
"canSchedulerOpen",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler );
_canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE,), __check_status)
# EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps );
_canlib.map_symbol(
"canSchedulerGetCaps",
ctypes.c_long,
(HANDLE, structures.PCANCAPABILITIES),
__check_status,
)
# EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable );
_canlib.map_symbol(
"canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status
)
# EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex );
_canlib.map_symbol(
"canSchedulerAddMessage",
ctypes.c_long,
(HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)),
__check_status,
)
# EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex );
_canlib.map_symbol(
"canSchedulerRemMessage",
ctypes.c_long,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount );
_canlib.map_symbol(
"canSchedulerStartMessage",
ctypes.c_long,
(HANDLE, ctypes.c_uint32, ctypes.c_uint16),
__check_status,
)
# EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex );
_canlib.map_symbol(
"canSchedulerStopMessage",
ctypes.c_long,
(HANDLE, ctypes.c_uint32),
__check_status,
)
_canlib.vciInitialize()
except AttributeError:
# In case _canlib == None meaning we're not on win32/no lib found
pass
except Exception as e:
log.warning("Could not initialize IXXAT VCI library: %s", e)
# ---------------------------------------------------------------------------
CAN_INFO_MESSAGES = {
constants.CAN_INFO_START: "CAN started",
constants.CAN_INFO_STOP: "CAN stopped",
constants.CAN_INFO_RESET: "CAN reset",
}
CAN_ERROR_MESSAGES = {
constants.CAN_ERROR_STUFF: "CAN bit stuff error",
constants.CAN_ERROR_FORM: "CAN form error",
constants.CAN_ERROR_ACK: "CAN acknowledgment error",
constants.CAN_ERROR_BIT: "CAN bit error",
constants.CAN_ERROR_CRC: "CAN CRC error",
constants.CAN_ERROR_OTHER: "Other (unknown) CAN error",
}
CAN_STATUS_FLAGS = {
constants.CAN_STATUS_TXPEND: "transmission pending",
constants.CAN_STATUS_OVRRUN: "data overrun occurred",
constants.CAN_STATUS_ERRLIM: "error warning limit exceeded",
constants.CAN_STATUS_BUSOFF: "bus off",
constants.CAN_STATUS_ININIT: "init mode active",
constants.CAN_STATUS_BUSCERR: "bus coupling error",
}
# ----------------------------------------------------------------------------
class IXXATBus(BusABC):
"""The CAN Bus implemented for the IXXAT interface.
.. warning::
This interface does implement efficient filtering of messages, but
the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
Using :meth:`~can.BusABC.set_filters` does not work.
"""
CHANNEL_BITRATES = {
0: {
10000: constants.CAN_BT0_10KB,
20000: constants.CAN_BT0_20KB,
50000: constants.CAN_BT0_50KB,
100000: constants.CAN_BT0_100KB,
125000: constants.CAN_BT0_125KB,
250000: constants.CAN_BT0_250KB,
500000: constants.CAN_BT0_500KB,
666000: constants.CAN_BT0_667KB,
666666: constants.CAN_BT0_667KB,
666667: constants.CAN_BT0_667KB,
667000: constants.CAN_BT0_667KB,
800000: constants.CAN_BT0_800KB,
1000000: constants.CAN_BT0_1000KB,
},
1: {
10000: constants.CAN_BT1_10KB,
20000: constants.CAN_BT1_20KB,
50000: constants.CAN_BT1_50KB,
100000: constants.CAN_BT1_100KB,
125000: constants.CAN_BT1_125KB,
250000: constants.CAN_BT1_250KB,
500000: constants.CAN_BT1_500KB,
666000: constants.CAN_BT1_667KB,
666666: constants.CAN_BT1_667KB,
666667: constants.CAN_BT1_667KB,
667000: constants.CAN_BT1_667KB,
800000: constants.CAN_BT1_800KB,
1000000: constants.CAN_BT1_1000KB,
},
}
@deprecated_args_alias(
deprecation_start="4.0.0",
deprecation_end="5.0.0",
UniqueHardwareId="unique_hardware_id",
rxFifoSize="rx_fifo_size",
txFifoSize="tx_fifo_size",
)
def __init__(
self,
channel: int,
can_filters=None,
receive_own_messages: bool = False,
unique_hardware_id: Optional[int] = None,
extended: bool = True,
rx_fifo_size: int = 16,
tx_fifo_size: int = 16,
bitrate: int = 500000,
**kwargs,
):
"""
:param channel:
The Channel id to create this bus with.
:param can_filters:
See :meth:`can.BusABC.set_filters`.
:param receive_own_messages:
Enable self-reception of sent messages.
:param unique_hardware_id:
unique_hardware_id to connect (optional, will use the first found if not supplied)
:param extended:
Default True, enables the capability to use extended IDs.
:param rx_fifo_size:
Receive fifo size (default 16)
:param tx_fifo_size:
Transmit fifo size (default 16)
:param bitrate:
Channel bitrate in bit/s
"""
if _canlib is None:
raise CanInterfaceNotImplementedError(
"The IXXAT VCI library has not been initialized. Check the logs for more details."
)
log.info("CAN Filters: %s", can_filters)
# Configuration options
self._receive_own_messages = receive_own_messages
# Usually comes as a string from the config file
channel = int(channel)
if bitrate not in self.CHANNEL_BITRATES[0]:
raise ValueError(f"Invalid bitrate {bitrate}")
if rx_fifo_size <= 0:
raise ValueError("rx_fifo_size must be > 0")
if tx_fifo_size <= 0:
raise ValueError("tx_fifo_size must be > 0")
if channel < 0:
raise ValueError("channel number must be >= 0")
self._device_handle = HANDLE()
self._device_info = structures.VCIDEVICEINFO()
self._control_handle = HANDLE()
self._channel_handle = HANDLE()
self._channel_capabilities = structures.CANCAPABILITIES()
self._message = structures.CANMSG()
self._payload = (ctypes.c_byte * 8)()
self._can_protocol = CanProtocol.CAN_20
# Search for supplied device
if unique_hardware_id is None:
log.info("Searching for first available device")
else:
log.info("Searching for unique HW ID %s", unique_hardware_id)
_canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(
self._device_handle, ctypes.byref(self._device_info)
)
except StopIteration:
if unique_hardware_id is None:
raise VCIDeviceNotFoundError(
"No IXXAT device(s) connected or device(s) in use by other process(es)."
) from None
else:
raise VCIDeviceNotFoundError(
f"Unique HW ID {unique_hardware_id} not connected or not available."
) from None
else:
if (unique_hardware_id is None) or (
self._device_info.UniqueHardwareId.AsChar
== bytes(unique_hardware_id, "ascii")
):
break
log.debug(
"Ignoring IXXAT with hardware id '%s'.",
self._device_info.UniqueHardwareId.AsChar.decode("ascii"),
)
_canlib.vciEnumDeviceClose(self._device_handle)
try:
_canlib.vciDeviceOpen(
ctypes.byref(self._device_info.VciObjectId),
ctypes.byref(self._device_handle),
)
except Exception as exception:
raise CanInitializationError(
f"Could not open device: {exception}"
) from exception
log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar)
log.info(
"Initializing channel %d in shared mode, %d rx buffers, %d tx buffers",
channel,
rx_fifo_size,
tx_fifo_size,
)
try:
_canlib.canChannelOpen(
self._device_handle,
channel,
constants.FALSE,
ctypes.byref(self._channel_handle),
)
except Exception as exception:
raise CanInitializationError(
f"Could not open and initialize channel: {exception}"
) from exception
# Signal TX/RX events when at least one frame has been handled
_canlib.canChannelInitialize(
self._channel_handle, rx_fifo_size, 1, tx_fifo_size, 1
)
_canlib.canChannelActivate(self._channel_handle, constants.TRUE)
log.info("Initializing control %d bitrate %d", channel, bitrate)
_canlib.canControlOpen(
self._device_handle, channel, ctypes.byref(self._control_handle)
)
# compute opmode before control initialize
opmode = constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_ERRFRAME
if extended:
opmode |= constants.CAN_OPMODE_EXTENDED
# control initialize
_canlib.canControlInitialize(
self._control_handle,
opmode,
self.CHANNEL_BITRATES[0][bitrate],
self.CHANNEL_BITRATES[1][bitrate],
)
_canlib.canControlGetCaps(
self._control_handle, ctypes.byref(self._channel_capabilities)
)
# With receive messages, this field contains the relative reception time of
# the message in ticks. The resolution of a tick can be calculated from the fields
# dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula:
# frequency [1/s] = dwClockFreq / dwTscDivisor
self._tick_resolution = (
self._channel_capabilities.dwClockFreq
/ self._channel_capabilities.dwTscDivisor
)
# Setup filters before starting the channel
if can_filters:
log.info("The IXXAT VCI backend is filtering messages")
# Disable every message coming in
for extended in (0, 1):
_canlib.canControlSetAccFilter(
self._control_handle,
extended,
constants.CAN_ACC_CODE_NONE,
constants.CAN_ACC_MASK_NONE,
)
for can_filter in can_filters:
# Filters define what messages are accepted
code = int(can_filter["can_id"])
mask = int(can_filter["can_mask"])
extended = can_filter.get("extended", False)
_canlib.canControlAddFilterIds(
self._control_handle, 1 if extended else 0, code << 1, mask << 1
)
log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask)
# Start the CAN controller. Messages will be forwarded to the channel
_canlib.canControlStart(self._control_handle, constants.TRUE)
# For cyclic transmit list. Set when .send_periodic() is first called
self._scheduler = None
self._scheduler_resolution = None
self.channel = channel
# Usually you get back 3 messages like "CAN initialized" ecc...
# Clear the FIFO by filter them out with low timeout
for _ in range(rx_fifo_size):
try:
_canlib.canChannelReadMessage(
self._channel_handle, 0, ctypes.byref(self._message)
)
except (VCITimeout, VCIRxQueueEmptyError):
break
super().__init__(channel=channel, can_filters=None, **kwargs)
def _inWaiting(self):
try:
_canlib.canChannelWaitRxEvent(self._channel_handle, 0)
except VCITimeout:
return 0
else:
return 1
def flush_tx_buffer(self):
"""Flushes the transmit buffer on the IXXAT"""
# TODO #64: no timeout?
_canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE)
def _recv_internal(self, timeout):
"""Read a message from IXXAT device."""
data_received = False
if self._inWaiting() or timeout == 0:
# Peek without waiting
recv_function = functools.partial(
_canlib.canChannelPeekMessage,
self._channel_handle,
ctypes.byref(self._message),
)
else:
# Wait if no message available
timeout = (
constants.INFINITE
if (timeout is None or timeout < 0)
else int(timeout * 1000)
)
recv_function = functools.partial(
_canlib.canChannelReadMessage,
self._channel_handle,
timeout,
ctypes.byref(self._message),
)
try:
recv_function()
except (VCITimeout, VCIRxQueueEmptyError):
# Ignore the 2 errors, overall timeout is handled by BusABC.recv
pass
else:
# See if we got a data or info/error messages
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
data_received = True
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
log.info(
CAN_INFO_MESSAGES.get(
self._message.abData[0],
f"Unknown CAN info message code {self._message.abData[0]}",
)
)
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR:
if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR:
log.warning("CAN error: data overrun")
else:
log.warning(
CAN_ERROR_MESSAGES.get(
self._message.abData[0],
f"Unknown CAN error message code {self._message.abData[0]}",
)
)
log.warning(
"CAN message flags bAddFlags/bFlags2 0x%02X bflags 0x%02X",
self._message.uMsgInfo.Bytes.bAddFlags,
self._message.uMsgInfo.Bytes.bFlags,
)
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR:
pass
else:
log.warning(
"Unexpected message info type 0x%X",
self._message.uMsgInfo.Bits.type,
)
finally:
if not data_received:
# Check hard errors
status = structures.CANLINESTATUS()
_canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
error_byte_1 = status.dwStatus & 0x0F
error_byte_2 = status.dwStatus & 0xF0
if error_byte_1 > constants.CAN_STATUS_TXPEND:
# CAN_STATUS_OVRRUN = 0x02 # data overrun occurred
# CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded
# CAN_STATUS_BUSOFF = 0x08 # bus off status
if error_byte_1 & constants.CAN_STATUS_OVRRUN:
raise VCIError("Data overrun occurred")
elif error_byte_1 & constants.CAN_STATUS_ERRLIM:
raise VCIError("Error warning limit exceeded")
elif error_byte_1 & constants.CAN_STATUS_BUSOFF:
raise VCIError("Bus off status")
elif error_byte_2 > constants.CAN_STATUS_ININIT:
# CAN_STATUS_BUSCERR = 0x20 # bus coupling error
if error_byte_2 & constants.CAN_STATUS_BUSCERR:
raise VCIError("Bus coupling error")
if not data_received:
# Timed out / can message type is not DATA
return None, True
# The _message.dwTime is a 32bit tick value and will overrun,
# so expect to see the value restarting from 0
rx_msg = Message(
timestamp=self._message.dwTime
/ self._tick_resolution, # Relative time in s
is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr),
is_extended_id=bool(self._message.uMsgInfo.Bits.ext),
arbitration_id=self._message.dwMsgId,
dlc=self._message.uMsgInfo.Bits.dlc,
data=self._message.abData[: self._message.uMsgInfo.Bits.dlc],
channel=self.channel,
)
return rx_msg, True
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""
Sends a message on the bus. The interface may buffer the message.
:param msg:
The message to send.
:param timeout:
Timeout after some time.
:raise:
:class:CanTimeoutError
:class:CanOperationError
"""
# This system is not designed to be very efficient
message = structures.CANMSG()
message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA
message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0
message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0
message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0
message.dwMsgId = msg.arbitration_id
if msg.dlc:
message.uMsgInfo.Bits.dlc = msg.dlc
adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data)
ctypes.memmove(message.abData, adapter, len(msg.data))
if timeout:
_canlib.canChannelSendMessage(
self._channel_handle, int(timeout * 1000), message
)
else:
_canlib.canChannelPostMessage(self._channel_handle, message)
# Want to log outgoing messages?
# log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message)
def _send_periodic_internal(
self,
msgs: Union[Sequence[Message], Message],
period: float,
duration: Optional[float] = None,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> CyclicSendTaskABC:
"""Send a message using built-in cyclic transmit list functionality."""
if modifier_callback is None:
if self._scheduler is None:
self._scheduler = HANDLE()
_canlib.canSchedulerOpen(
self._device_handle, self.channel, self._scheduler
)
caps = structures.CANCAPABILITIES()
_canlib.canSchedulerGetCaps(self._scheduler, caps)
self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
return CyclicSendTask(
self._scheduler,
msgs,
period,
duration,
self._scheduler_resolution,
autostart=autostart,
)
# fallback to thread based cyclic task
warnings.warn(
f"{self.__class__.__name__} falls back to a thread-based cyclic task, "
"when the `modifier_callback` argument is given.",
stacklevel=3,
)
return BusABC._send_periodic_internal(
self,
msgs=msgs,
period=period,
duration=duration,
autostart=autostart,
modifier_callback=modifier_callback,
)
def shutdown(self):
super().shutdown()
if self._scheduler is not None:
_canlib.canSchedulerClose(self._scheduler)
_canlib.canChannelClose(self._channel_handle)
_canlib.canControlStart(self._control_handle, constants.FALSE)
_canlib.canControlReset(self._control_handle)
_canlib.canControlClose(self._control_handle)
_canlib.vciDeviceClose(self._device_handle)
@property
def state(self) -> BusState:
"""
Return the current state of the hardware
"""
status = structures.CANLINESTATUS()
_canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
if status.bOpMode == constants.CAN_OPMODE_LISTONLY:
return BusState.PASSIVE
error_byte_1 = status.dwStatus & 0x0F
# CAN_STATUS_BUSOFF = 0x08 # bus off status
if error_byte_1 & constants.CAN_STATUS_BUSOFF:
return BusState.ERROR
error_byte_2 = status.dwStatus & 0xF0
# CAN_STATUS_BUSCERR = 0x20 # bus coupling error
if error_byte_2 & constants.CAN_STATUS_BUSCERR:
return BusState.ERROR
return BusState.ACTIVE
# ~class IXXATBus(BusABC): ---------------------------------------------------
class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC):
"""A message in the cyclic transmit list."""
def __init__(
self,
scheduler,
msgs,
period,
duration,
resolution,
autostart: bool = True,
):
super().__init__(msgs, period, duration)
if len(self.messages) != 1:
raise ValueError(
"IXXAT Interface only supports periodic transmission of 1 element"
)
self._scheduler = scheduler
self._index = None
self._count = int(duration / period) if duration else 0
self._msg = structures.CANCYCLICTXMSG()
self._msg.wCycleTime = int(round(period * resolution))
self._msg.dwMsgId = self.messages[0].arbitration_id
self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA
self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0
self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0
self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc
for i, b in enumerate(self.messages[0].data):
self._msg.abData[i] = b
if autostart:
self.start()
def start(self):
"""Start transmitting message (add to list if needed)."""
if self._index is None:
self._index = ctypes.c_uint32()
_canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index)
_canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count)
def pause(self):
"""Pause transmitting message (keep it in the list)."""
_canlib.canSchedulerStopMessage(self._scheduler, self._index)
def stop(self):
"""Stop transmitting message (remove from list)."""
# Remove it completely instead of just stopping it to avoid filling up
# the list with permanently stopped messages
_canlib.canSchedulerRemMessage(self._scheduler, self._index)
self._index = None
def _format_can_status(status_flags: int):
"""
Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus
field in CANLINESTATUS.
Valid states are defined in the CAN_STATUS_* constants in cantype.h
"""
states = []
for flag, description in CAN_STATUS_FLAGS.items():
if status_flags & flag:
states.append(description)
status_flags &= ~flag
if status_flags:
states.append(f"unknown state 0x{status_flags:02x}")
if states:
return "CAN status message: {}".format(", ".join(states))
else:
return "Empty CAN status message"
def get_ixxat_hwids():
"""Get a list of hardware ids of all available IXXAT devices."""
hwids = []
device_handle = HANDLE()
device_info = structures.VCIDEVICEINFO()
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
except StopIteration:
break
else:
hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii"))
_canlib.vciEnumDeviceClose(device_handle)
return hwids
def _detect_available_configs() -> List[AutoDetectedConfig]:
config_list = [] # list in wich to store the resulting bus kwargs
# used to detect HWID
device_handle = HANDLE()
device_info = structures.VCIDEVICEINFO()
# used to attempt to open channels
channel_handle = HANDLE()
device_handle2 = HANDLE()
try:
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
except StopIteration:
break
else:
hwid = device_info.UniqueHardwareId.AsChar.decode("ascii")
_canlib.vciDeviceOpen(
ctypes.byref(device_info.VciObjectId),
ctypes.byref(device_handle2),
)
for channel in range(4):
try:
_canlib.canChannelOpen(
device_handle2,
channel,
constants.FALSE,
ctypes.byref(channel_handle),
)
except Exception:
# Array outside of bounds error == accessing a channel not in the hardware
break
else:
_canlib.canChannelClose(channel_handle)
config_list.append(
{
"interface": "ixxat",
"channel": channel,
"unique_hardware_id": hwid,
}
)
_canlib.vciDeviceClose(device_handle2)
_canlib.vciEnumDeviceClose(device_handle)
except AttributeError:
pass # _canlib is None in the CI tests -> return a blank list
return config_list
python-can-4.5.0/can/interfaces/ixxat/canlib_vcinpl2.py 0000664 0000000 0000000 00000115344 14722003266 0023072 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
TODO: We could implement this interface such that setting other filters
could work when the initial filters were set to zero using the
software fallback. Or could the software filters even be changed
after the connection was opened? We need to document that bahaviour!
See also the NICAN interface.
"""
import ctypes
import functools
import logging
import sys
import time
import warnings
from typing import Callable, Optional, Sequence, Tuple, Union
from can import (
BusABC,
CanProtocol,
CyclicSendTaskABC,
LimitedDurationCyclicSendTaskABC,
Message,
RestartableCyclicTaskABC,
)
from can.ctypesutil import HANDLE, PHANDLE, CLibrary
from can.ctypesutil import HRESULT as ctypes_HRESULT
from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError
from can.util import deprecated_args_alias, dlc2len, len2dlc
from . import constants, structures
from .exceptions import *
__all__ = [
"VCITimeout",
"VCIError",
"VCIBusOffError",
"VCIDeviceNotFoundError",
"IXXATBus",
"vciFormatError",
]
log = logging.getLogger("can.ixxat")
# Hack to have vciFormatError as a free function, see below
vciFormatError = None
# main ctypes instance
_canlib = None
# TODO: Use ECI driver for linux
if sys.platform == "win32" or sys.platform == "cygwin":
try:
_canlib = CLibrary("vcinpl2.dll")
except Exception as e:
log.warning("Cannot load IXXAT vcinpl library: %s", e)
else:
# Will not work on other systems, but have it importable anyway for
# tests/sphinx
log.warning("IXXAT VCI library does not work on %s platform", sys.platform)
def __vciFormatErrorExtended(
library_instance: CLibrary, function: Callable, vret: int, args: Tuple
):
"""Format a VCI error and attach failed function, decoded HRESULT and arguments
:param CLibrary library_instance:
Mapped instance of IXXAT vcinpl library
:param callable function:
Failed function
:param HRESULT vret:
HRESULT returned by vcinpl call
:param args:
Arbitrary arguments tuple
:return:
Formatted string
"""
# TODO: make sure we don't generate another exception
return (
f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}"
)
def __vciFormatError(library_instance: CLibrary, function: Callable, vret: int):
"""Format a VCI error and attach failed function and decoded HRESULT
:param CLibrary library_instance:
Mapped instance of IXXAT vcinpl library
:param callable function:
Failed function
:param HRESULT vret:
HRESULT returned by vcinpl call
:return:
Formatted string
"""
buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN)
ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN)
library_instance.vciFormatError(vret, buf, constants.VCI_MAX_ERRSTRLEN)
return "function {} failed ({})".format(
function._name, buf.value.decode("utf-8", "replace")
)
def __check_status(result, function, args):
"""
Check the result of a vcinpl function call and raise appropriate exception
in case of an error. Used as errcheck function when mapping C functions
with ctypes.
:param result:
Function call numeric result
:param callable function:
Called function
:param args:
Arbitrary arguments tuple
:raise:
:class:VCITimeout
:class:VCIRxQueueEmptyError
:class:StopIteration
:class:VCIError
"""
if result == constants.VCI_E_TIMEOUT:
raise VCITimeout(f"Function {function._name} timed out")
elif result == constants.VCI_E_RXQUEUE_EMPTY:
raise VCIRxQueueEmptyError()
elif result == constants.VCI_E_NO_MORE_ITEMS:
raise StopIteration()
elif result == constants.VCI_E_ACCESSDENIED:
pass # not a real error, might happen if another program has initialized the bus
elif result != constants.VCI_OK:
raise VCIError(vciFormatError(function, result))
return result
try:
hresult_type = ctypes.c_ulong
# Map all required symbols and initialize library ---------------------------
# HRESULT VCIAPI vciInitialize ( void );
_canlib.map_symbol("vciInitialize", hresult_type, (), __check_status)
# void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize);
try:
_canlib.map_symbol(
"vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)
)
except ImportError:
_canlib.map_symbol(
"vciFormatErrorA", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)
)
_canlib.vciFormatError = _canlib.vciFormatErrorA
# Hack to have vciFormatError as a free function
vciFormatError = functools.partial(__vciFormatError, _canlib)
# HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum );
_canlib.map_symbol("vciEnumDeviceOpen", hresult_type, (PHANDLE,), __check_status)
# HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum );
_canlib.map_symbol("vciEnumDeviceClose", hresult_type, (HANDLE,), __check_status)
# HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo );
_canlib.map_symbol(
"vciEnumDeviceNext",
hresult_type,
(HANDLE, structures.PVCIDEVICEINFO),
__check_status,
)
# HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice );
_canlib.map_symbol(
"vciDeviceOpen", hresult_type, (structures.PVCIID, PHANDLE), __check_status
)
# HRESULT vciDeviceClose( HANDLE hDevice )
_canlib.map_symbol("vciDeviceClose", hresult_type, (HANDLE,), __check_status)
# HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn );
_canlib.map_symbol(
"canChannelOpen",
hresult_type,
(HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT VCIAPI
# canChannelInitialize( IN HANDLE hCanChn,
# IN UINT16 wRxFifoSize,
# IN UINT16 wRxThreshold,
# IN UINT16 wTxFifoSize,
# IN UINT16 wTxThreshold,
# IN UINT32 dwFilterSize,
# IN UINT8 bFilterMode );
_canlib.map_symbol(
"canChannelInitialize",
hresult_type,
(
HANDLE,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint32,
ctypes.c_uint8,
),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable );
_canlib.map_symbol(
"canChannelActivate", hresult_type, (HANDLE, ctypes.c_long), __check_status
)
# HRESULT canChannelClose( HANDLE hChannel )
_canlib.map_symbol("canChannelClose", hresult_type, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG2 pCanMsg );
_canlib.map_symbol(
"canChannelReadMessage",
hresult_type,
(HANDLE, ctypes.c_uint32, structures.PCANMSG2),
__check_status,
)
# HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG2 pCanMsg );
_canlib.map_symbol(
"canChannelPeekMessage",
hresult_type,
(HANDLE, structures.PCANMSG2),
__check_status,
)
# HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout );
_canlib.map_symbol(
"canChannelWaitTxEvent",
hresult_type,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout );
_canlib.map_symbol(
"canChannelWaitRxEvent",
hresult_type,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG2 pCanMsg );
_canlib.map_symbol(
"canChannelPostMessage",
hresult_type,
(HANDLE, structures.PCANMSG2),
__check_status,
)
# HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG2 pCanMsg );
_canlib.map_symbol(
"canChannelSendMessage",
hresult_type,
(HANDLE, ctypes.c_uint32, structures.PCANMSG2),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl );
_canlib.map_symbol(
"canControlOpen",
hresult_type,
(HANDLE, ctypes.c_uint32, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT VCIAPI
# canControlInitialize( IN HANDLE hCanCtl,
# IN UINT8 bOpMode,
# IN UINT8 bExMode,
# IN UINT8 bSFMode,
# IN UINT8 bEFMode,
# IN UINT32 dwSFIds,
# IN UINT32 dwEFIds,
# IN PCANBTP pBtpSDR,
# IN PCANBTP pBtpFDR );
_canlib.map_symbol(
"canControlInitialize",
hresult_type,
(
HANDLE,
ctypes.c_uint8,
ctypes.c_uint8,
ctypes.c_uint8,
ctypes.c_uint8,
ctypes.c_uint32,
ctypes.c_uint32,
structures.PCANBTP,
structures.PCANBTP,
),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl );
_canlib.map_symbol("canControlClose", hresult_type, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl );
_canlib.map_symbol("canControlReset", hresult_type, (HANDLE,), __check_status)
# EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart );
_canlib.map_symbol(
"canControlStart", hresult_type, (HANDLE, ctypes.c_long), __check_status
)
# EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS2 pStatus );
_canlib.map_symbol(
"canControlGetStatus",
hresult_type,
(HANDLE, structures.PCANLINESTATUS2),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES2 pCanCaps );
_canlib.map_symbol(
"canControlGetCaps",
hresult_type,
(HANDLE, structures.PCANCAPABILITIES2),
__check_status,
)
# EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask );
_canlib.map_symbol(
"canControlSetAccFilter",
hresult_type,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask);
_canlib.map_symbol(
"canControlAddFilterIds",
hresult_type,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask );
_canlib.map_symbol(
"canControlRemFilterIds",
hresult_type,
(HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler );
_canlib.map_symbol(
"canSchedulerOpen",
hresult_type,
(HANDLE, ctypes.c_uint32, PHANDLE),
__check_status,
)
# EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler );
_canlib.map_symbol("canSchedulerClose", hresult_type, (HANDLE,), __check_status)
# EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES2 pCaps );
_canlib.map_symbol(
"canSchedulerGetCaps",
hresult_type,
(HANDLE, structures.PCANCAPABILITIES2),
__check_status,
)
# EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable );
_canlib.map_symbol(
"canSchedulerActivate", hresult_type, (HANDLE, ctypes.c_int), __check_status
)
# EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG2 pMessage, PUINT32 pdwIndex );
_canlib.map_symbol(
"canSchedulerAddMessage",
hresult_type,
(HANDLE, structures.PCANCYCLICTXMSG2, ctypes.POINTER(ctypes.c_uint32)),
__check_status,
)
# EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex );
_canlib.map_symbol(
"canSchedulerRemMessage",
hresult_type,
(HANDLE, ctypes.c_uint32),
__check_status,
)
# EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount );
_canlib.map_symbol(
"canSchedulerStartMessage",
hresult_type,
(HANDLE, ctypes.c_uint32, ctypes.c_uint16),
__check_status,
)
# EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex );
_canlib.map_symbol(
"canSchedulerStopMessage",
hresult_type,
(HANDLE, ctypes.c_uint32),
__check_status,
)
_canlib.vciInitialize()
except AttributeError:
# In case _canlib == None meaning we're not on win32/no lib found
pass
except Exception as e:
log.warning("Could not initialize IXXAT VCI library: %s", e)
# ---------------------------------------------------------------------------
CAN_INFO_MESSAGES = {
constants.CAN_INFO_START: "CAN started",
constants.CAN_INFO_STOP: "CAN stopped",
constants.CAN_INFO_RESET: "CAN reset",
}
CAN_ERROR_MESSAGES = {
constants.CAN_ERROR_STUFF: "CAN bit stuff error",
constants.CAN_ERROR_FORM: "CAN form error",
constants.CAN_ERROR_ACK: "CAN acknowledgment error",
constants.CAN_ERROR_BIT: "CAN bit error",
constants.CAN_ERROR_CRC: "CAN CRC error",
constants.CAN_ERROR_OTHER: "Other (unknown) CAN error",
}
CAN_STATUS_FLAGS = {
constants.CAN_STATUS_TXPEND: "transmission pending",
constants.CAN_STATUS_OVRRUN: "data overrun occurred",
constants.CAN_STATUS_ERRLIM: "error warning limit exceeded",
constants.CAN_STATUS_BUSOFF: "bus off",
constants.CAN_STATUS_ININIT: "init mode active",
constants.CAN_STATUS_BUSCERR: "bus coupling error",
}
# ----------------------------------------------------------------------------
class IXXATBus(BusABC):
"""The CAN Bus implemented for the IXXAT interface.
.. warning::
This interface does implement efficient filtering of messages, but
the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
Using :meth:`~can.BusABC.set_filters` does not work.
"""
@deprecated_args_alias(
deprecation_start="4.0.0",
deprecation_end="5.0.0",
UniqueHardwareId="unique_hardware_id",
rxFifoSize="rx_fifo_size",
txFifoSize="tx_fifo_size",
)
def __init__(
self,
channel: int,
can_filters=None,
receive_own_messages: int = False,
unique_hardware_id: Optional[int] = None,
extended: bool = True,
rx_fifo_size: int = 1024,
tx_fifo_size: int = 128,
bitrate: int = 500000,
data_bitrate: int = 2000000,
sjw_abr: Optional[int] = None,
tseg1_abr: Optional[int] = None,
tseg2_abr: Optional[int] = None,
sjw_dbr: Optional[int] = None,
tseg1_dbr: Optional[int] = None,
tseg2_dbr: Optional[int] = None,
ssp_dbr: Optional[int] = None,
**kwargs,
):
"""
:param channel:
The Channel id to create this bus with.
:param can_filters:
See :meth:`can.BusABC.set_filters`.
:param receive_own_messages:
Enable self-reception of sent messages.
:param unique_hardware_id:
unique_hardware_id to connect (optional, will use the first found if not supplied)
:param extended:
Default True, enables the capability to use extended IDs.
:param rx_fifo_size:
Receive fifo size (default 1024)
:param tx_fifo_size:
Transmit fifo size (default 128)
:param bitrate:
Channel bitrate in bit/s
:param data_bitrate:
Channel bitrate in bit/s (only in CAN-Fd if baudrate switch enabled).
:param sjw_abr:
Bus timing value sample jump width (arbitration).
:param tseg1_abr:
Bus timing value tseg1 (arbitration)
:param tseg2_abr:
Bus timing value tseg2 (arbitration)
:param sjw_dbr:
Bus timing value sample jump width (data)
:param tseg1_dbr:
Bus timing value tseg1 (data). Only takes effect with fd and bitrate switch enabled.
:param tseg2_dbr:
Bus timing value tseg2 (data). Only takes effect with fd and bitrate switch enabled.
:param ssp_dbr:
Secondary sample point (data). Only takes effect with fd and bitrate switch enabled.
"""
if _canlib is None:
raise CanInterfaceNotImplementedError(
"The IXXAT VCI library has not been initialized. Check the logs for more details."
)
log.info("CAN Filters: %s", can_filters)
# Configuration options
self._receive_own_messages = receive_own_messages
# Usually comes as a string from the config file
channel = int(channel)
if bitrate not in constants.CAN_BITRATE_PRESETS and (
tseg1_abr is None or tseg2_abr is None or sjw_abr is None
):
raise ValueError(
f"To use bitrate {bitrate} (that has not predefined preset) is mandatory "
f"to use also parameters tseg1_abr, tseg2_abr and swj_abr"
)
if data_bitrate not in constants.CAN_DATABITRATE_PRESETS and (
tseg1_dbr is None or tseg2_dbr is None or sjw_dbr is None
):
raise ValueError(
f"To use data_bitrate {data_bitrate} (that has not predefined preset) is mandatory "
f"to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr"
)
if rx_fifo_size <= 0:
raise ValueError("rx_fifo_size must be > 0")
if tx_fifo_size <= 0:
raise ValueError("tx_fifo_size must be > 0")
if channel < 0:
raise ValueError("channel number must be >= 0")
self._device_handle = HANDLE()
self._device_info = structures.VCIDEVICEINFO()
self._control_handle = HANDLE()
self._channel_handle = HANDLE()
self._channel_capabilities = structures.CANCAPABILITIES2()
self._message = structures.CANMSG2()
self._payload = (ctypes.c_byte * 64)()
self._can_protocol = CanProtocol.CAN_FD
# Search for supplied device
if unique_hardware_id is None:
log.info("Searching for first available device")
else:
log.info("Searching for unique HW ID %s", unique_hardware_id)
_canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(
self._device_handle, ctypes.byref(self._device_info)
)
except StopIteration:
if unique_hardware_id is None:
raise VCIDeviceNotFoundError(
"No IXXAT device(s) connected or device(s) in use by other process(es)."
) from None
else:
raise VCIDeviceNotFoundError(
f"Unique HW ID {unique_hardware_id} not connected or not available."
) from None
else:
if (unique_hardware_id is None) or (
self._device_info.UniqueHardwareId.AsChar
== bytes(unique_hardware_id, "ascii")
):
break
else:
log.debug(
"Ignoring IXXAT with hardware id '%s'.",
self._device_info.UniqueHardwareId.AsChar.decode("ascii"),
)
_canlib.vciEnumDeviceClose(self._device_handle)
try:
_canlib.vciDeviceOpen(
ctypes.byref(self._device_info.VciObjectId),
ctypes.byref(self._device_handle),
)
except Exception as exception:
raise CanInitializationError(
f"Could not open device: {exception}"
) from exception
log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar)
log.info(
"Initializing channel %d in shared mode, %d rx buffers, %d tx buffers",
channel,
rx_fifo_size,
tx_fifo_size,
)
try:
_canlib.canChannelOpen(
self._device_handle,
channel,
constants.FALSE,
ctypes.byref(self._channel_handle),
)
except Exception as exception:
raise CanInitializationError(
f"Could not open and initialize channel: {exception}"
) from exception
# Signal TX/RX events when at least one frame has been handled
_canlib.canChannelInitialize(
self._channel_handle,
rx_fifo_size,
1,
tx_fifo_size,
1,
0,
constants.CAN_FILTER_PASS,
)
_canlib.canChannelActivate(self._channel_handle, constants.TRUE)
pBtpSDR = IXXATBus._canptb_build(
defaults=constants.CAN_BITRATE_PRESETS,
bitrate=bitrate,
tseg1=tseg1_abr,
tseg2=tseg2_abr,
sjw=sjw_abr,
ssp=0,
)
pBtpFDR = IXXATBus._canptb_build(
defaults=constants.CAN_DATABITRATE_PRESETS,
bitrate=data_bitrate,
tseg1=tseg1_dbr,
tseg2=tseg2_dbr,
sjw=sjw_dbr,
ssp=ssp_dbr if ssp_dbr is not None else tseg1_dbr,
)
log.info(
"Initializing control %d with SDR={%s}, FDR={%s}",
channel,
pBtpSDR,
pBtpFDR,
)
_canlib.canControlOpen(
self._device_handle, channel, ctypes.byref(self._control_handle)
)
_canlib.canControlGetCaps(
self._control_handle, ctypes.byref(self._channel_capabilities)
)
# check capabilities
bOpMode = constants.CAN_OPMODE_UNDEFINED
if (
self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_STDANDEXT
) != 0:
# controller supportes CAN_OPMODE_STANDARD and CAN_OPMODE_EXTENDED at the same time
bOpMode |= constants.CAN_OPMODE_STANDARD # enable both 11 bits reception
if extended: # parameter from configuration
bOpMode |= constants.CAN_OPMODE_EXTENDED # enable 29 bits reception
elif (
self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_STDANDEXT
) != 0:
log.warning(
"Channel %d capabilities allow either basic or extended IDs, but not both. using %s according to parameter [extended=%s]",
channel,
"extended" if extended else "basic",
"True" if extended else "False",
)
bOpMode |= (
constants.CAN_OPMODE_EXTENDED
if extended
else constants.CAN_OPMODE_STANDARD
)
if (
self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_ERRFRAME
) != 0:
bOpMode |= constants.CAN_OPMODE_ERRFRAME
bExMode = constants.CAN_EXMODE_DISABLED
if (self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_EXTDATA) != 0:
bExMode |= constants.CAN_EXMODE_EXTDATALEN
if (
self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_FASTDATA
) != 0:
bExMode |= constants.CAN_EXMODE_FASTDATA
_canlib.canControlInitialize(
self._control_handle,
bOpMode,
bExMode,
constants.CAN_FILTER_PASS,
constants.CAN_FILTER_PASS,
0,
0,
ctypes.byref(pBtpSDR),
ctypes.byref(pBtpFDR),
)
# With receive messages, this field contains the relative reception time of
# the message in ticks. The resolution of a tick can be calculated from the fields
# dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula:
# frequency [1/s] = dwClockFreq / dwTscDivisor
self._tick_resolution = (
self._channel_capabilities.dwTscClkFreq
/ self._channel_capabilities.dwTscDivisor
)
# Setup filters before starting the channel
if can_filters:
log.info("The IXXAT VCI backend is filtering messages")
# Disable every message coming in
for extended in (0, 1):
_canlib.canControlSetAccFilter(
self._control_handle,
extended,
constants.CAN_ACC_CODE_NONE,
constants.CAN_ACC_MASK_NONE,
)
for can_filter in can_filters:
# Filters define what messages are accepted
code = int(can_filter["can_id"])
mask = int(can_filter["can_mask"])
extended = can_filter.get("extended", False)
_canlib.canControlAddFilterIds(
self._control_handle, 1 if extended else 0, code << 1, mask << 1
)
log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask)
# Start the CAN controller. Messages will be forwarded to the channel
_canlib.canControlStart(self._control_handle, constants.TRUE)
# For cyclic transmit list. Set when .send_periodic() is first called
self._scheduler = None
self._scheduler_resolution = None
self.channel = channel
# Usually you get back 3 messages like "CAN initialized" ecc...
# Clear the FIFO by filter them out with low timeout
for _ in range(rx_fifo_size):
try:
_canlib.canChannelReadMessage(
self._channel_handle, 0, ctypes.byref(self._message)
)
except (VCITimeout, VCIRxQueueEmptyError):
break
super().__init__(channel=channel, can_filters=None, **kwargs)
@staticmethod
def _canptb_build(defaults, bitrate, tseg1, tseg2, sjw, ssp):
if bitrate in defaults:
d = defaults[bitrate]
if tseg1 is None:
tseg1 = d.wTS1
if tseg2 is None:
tseg2 = d.wTS2
if sjw is None:
sjw = d.wSJW
if ssp is None:
ssp = d.wTDO
dw_mode = d.dwMode
else:
dw_mode = 0
return structures.CANBTP(
dwMode=dw_mode,
dwBPS=bitrate,
wTS1=tseg1,
wTS2=tseg2,
wSJW=sjw,
wTDO=ssp,
)
def _inWaiting(self):
try:
_canlib.canChannelWaitRxEvent(self._channel_handle, 0)
except VCITimeout:
return 0
else:
return 1
def flush_tx_buffer(self):
"""Flushes the transmit buffer on the IXXAT"""
# TODO #64: no timeout?
_canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE)
def _recv_internal(self, timeout):
"""Read a message from IXXAT device."""
# TODO: handling CAN error messages?
data_received = False
if timeout == 0:
# Peek without waiting
try:
_canlib.canChannelPeekMessage(
self._channel_handle, ctypes.byref(self._message)
)
except (VCITimeout, VCIRxQueueEmptyError, VCIError):
# VCIError means no frame available (canChannelPeekMessage returned different from zero)
return None, True
else:
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
data_received = True
else:
# Wait if no message available
if timeout is None or timeout < 0:
remaining_ms = constants.INFINITE
t0 = None
else:
timeout_ms = int(timeout * 1000)
remaining_ms = timeout_ms
t0 = time.perf_counter()
while True:
try:
_canlib.canChannelReadMessage(
self._channel_handle, remaining_ms, ctypes.byref(self._message)
)
except (VCITimeout, VCIRxQueueEmptyError):
# Ignore the 2 errors, the timeout is handled manually with the perf_counter()
pass
else:
# See if we got a data or info/error messages
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
data_received = True
break
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
log.info(
CAN_INFO_MESSAGES.get(
self._message.abData[0],
f"Unknown CAN info message code {self._message.abData[0]}",
)
)
elif (
self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR
):
log.warning(
CAN_ERROR_MESSAGES.get(
self._message.abData[0],
f"Unknown CAN error message code {self._message.abData[0]}",
)
)
elif (
self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS
):
log.info(_format_can_status(self._message.abData[0]))
if self._message.abData[0] & constants.CAN_STATUS_BUSOFF:
raise VCIBusOffError()
elif (
self._message.uMsgInfo.Bits.type
== constants.CAN_MSGTYPE_TIMEOVR
):
pass
else:
log.warning("Unexpected message info type")
if t0 is not None:
remaining_ms = timeout_ms - int((time.perf_counter() - t0) * 1000)
if remaining_ms < 0:
break
if not data_received:
# Timed out / can message type is not DATA
return None, True
data_len = dlc2len(self._message.uMsgInfo.Bits.dlc)
# The _message.dwTime is a 32bit tick value and will overrun,
# so expect to see the value restarting from 0
rx_msg = Message(
timestamp=self._message.dwTime
/ self._tick_resolution, # Relative time in s
is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr),
is_fd=bool(self._message.uMsgInfo.Bits.edl),
is_rx=True,
is_error_frame=bool(
self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR
),
bitrate_switch=bool(self._message.uMsgInfo.Bits.fdr),
error_state_indicator=bool(self._message.uMsgInfo.Bits.esi),
is_extended_id=bool(self._message.uMsgInfo.Bits.ext),
arbitration_id=self._message.dwMsgId,
dlc=data_len,
data=self._message.abData[:data_len],
channel=self.channel,
)
return rx_msg, True
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""
Sends a message on the bus. The interface may buffer the message.
:param msg:
The message to send.
:param timeout:
Timeout after some time.
:raise:
:class:CanTimeoutError
:class:CanOperationError
"""
# This system is not designed to be very efficient
message = structures.CANMSG2()
message.uMsgInfo.Bits.type = (
constants.CAN_MSGTYPE_ERROR
if msg.is_error_frame
else constants.CAN_MSGTYPE_DATA
)
message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0
message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0
message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0
message.uMsgInfo.Bits.fdr = 1 if msg.bitrate_switch else 0
message.uMsgInfo.Bits.esi = 1 if msg.error_state_indicator else 0
message.uMsgInfo.Bits.edl = 1 if msg.is_fd else 0
message.dwMsgId = msg.arbitration_id
if msg.dlc: # this dlc means number of bytes of payload
message.uMsgInfo.Bits.dlc = len2dlc(msg.dlc)
data_len_dif = msg.dlc - len(msg.data)
data = msg.data + bytearray(
[0] * data_len_dif
) # pad with zeros until required length
adapter = (ctypes.c_uint8 * msg.dlc).from_buffer(data)
ctypes.memmove(message.abData, adapter, msg.dlc)
if timeout:
_canlib.canChannelSendMessage(
self._channel_handle, int(timeout * 1000), message
)
else:
_canlib.canChannelPostMessage(self._channel_handle, message)
def _send_periodic_internal(
self,
msgs: Union[Sequence[Message], Message],
period: float,
duration: Optional[float] = None,
autostart: bool = True,
modifier_callback: Optional[Callable[[Message], None]] = None,
) -> CyclicSendTaskABC:
"""Send a message using built-in cyclic transmit list functionality."""
if modifier_callback is None:
if self._scheduler is None:
self._scheduler = HANDLE()
_canlib.canSchedulerOpen(
self._device_handle, self.channel, self._scheduler
)
caps = structures.CANCAPABILITIES2()
_canlib.canSchedulerGetCaps(self._scheduler, caps)
self._scheduler_resolution = (
caps.dwCmsClkFreq / caps.dwCmsDivisor
) # TODO: confirm
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
return CyclicSendTask(
self._scheduler,
msgs,
period,
duration,
self._scheduler_resolution,
autostart=autostart,
)
# fallback to thread based cyclic task
warnings.warn(
f"{self.__class__.__name__} falls back to a thread-based cyclic task, "
"when the `modifier_callback` argument is given.",
stacklevel=3,
)
return BusABC._send_periodic_internal(
self,
msgs=msgs,
period=period,
duration=duration,
autostart=autostart,
modifier_callback=modifier_callback,
)
def shutdown(self):
super().shutdown()
if self._scheduler is not None:
_canlib.canSchedulerClose(self._scheduler)
_canlib.canChannelClose(self._channel_handle)
_canlib.canControlStart(self._control_handle, constants.FALSE)
_canlib.canControlClose(self._control_handle)
_canlib.vciDeviceClose(self._device_handle)
class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC):
"""A message in the cyclic transmit list."""
def __init__(
self,
scheduler,
msgs,
period,
duration,
resolution,
autostart: bool = True,
):
super().__init__(msgs, period, duration)
if len(self.messages) != 1:
raise ValueError(
"IXXAT Interface only supports periodic transmission of 1 element"
)
self._scheduler = scheduler
self._index = None
self._count = int(duration / period) if duration else 0
self._msg = structures.CANCYCLICTXMSG2()
self._msg.wCycleTime = int(round(period * resolution))
self._msg.dwMsgId = self.messages[0].arbitration_id
self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA
self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0
self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0
self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc
for i, b in enumerate(self.messages[0].data):
self._msg.abData[i] = b
if autostart:
self.start()
def start(self):
"""Start transmitting message (add to list if needed)."""
if self._index is None:
self._index = ctypes.c_uint32()
_canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index)
_canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count)
def pause(self):
"""Pause transmitting message (keep it in the list)."""
_canlib.canSchedulerStopMessage(self._scheduler, self._index)
def stop(self):
"""Stop transmitting message (remove from list)."""
# Remove it completely instead of just stopping it to avoid filling up
# the list with permanently stopped messages
_canlib.canSchedulerRemMessage(self._scheduler, self._index)
self._index = None
def _format_can_status(status_flags: int):
"""
Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus
field in CANLINESTATUS.
Valid states are defined in the CAN_STATUS_* constants in cantype.h
"""
states = []
for flag, description in CAN_STATUS_FLAGS.items():
if status_flags & flag:
states.append(description)
status_flags &= ~flag
if status_flags:
states.append(f"unknown state 0x{status_flags:02x}")
if states:
return "CAN status message: {}".format(", ".join(states))
else:
return "Empty CAN status message"
def get_ixxat_hwids():
"""Get a list of hardware ids of all available IXXAT devices."""
hwids = []
device_handle = HANDLE()
device_info = structures.VCIDEVICEINFO()
_canlib.vciEnumDeviceOpen(ctypes.byref(device_handle))
while True:
try:
_canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info))
except StopIteration:
break
else:
hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii"))
_canlib.vciEnumDeviceClose(device_handle)
return hwids
python-can-4.5.0/can/interfaces/ixxat/constants.py 0000664 0000000 0000000 00000021616 14722003266 0022217 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
"""
from . import structures
FALSE = 0
TRUE = 1
INFINITE = 0xFFFFFFFF
VCI_MAX_ERRSTRLEN = 256
# Bitrates
CAN_BT0_10KB = 0x31
CAN_BT1_10KB = 0x1C
CAN_BT0_20KB = 0x18
CAN_BT1_20KB = 0x1C
CAN_BT0_50KB = 0x09
CAN_BT1_50KB = 0x1C
CAN_BT0_100KB = 0x04
CAN_BT1_100KB = 0x1C
CAN_BT0_125KB = 0x03
CAN_BT1_125KB = 0x1C
CAN_BT0_250KB = 0x01
CAN_BT1_250KB = 0x1C
CAN_BT0_500KB = 0x00
CAN_BT1_500KB = 0x1C
CAN_BT0_667KB = 0x00
CAN_BT1_667KB = 0x18
CAN_BT0_800KB = 0x00
CAN_BT1_800KB = 0x16
CAN_BT0_1000KB = 0x00
CAN_BT1_1000KB = 0x14
# Facilities/severities
SEV_INFO = 0x40000000
SEV_WARN = 0x80000000
SEV_ERROR = 0xC0000000
SEV_MASK = 0xC0000000
SEV_SUCCESS = 0x00000000
RESERVED_FLAG = 0x10000000
CUSTOMER_FLAG = 0x20000000
STATUS_MASK = 0x0000FFFF
FACILITY_MASK = 0x0FFF0000
# Or so I hope
FACILITY_STD = 0
SEV_STD_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_STD
SEV_STD_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_STD
SEV_STD_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_STD
FACILITY_VCI = 0x00010000
SEV_VCI_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_VCI
SEV_VCI_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_VCI
SEV_VCI_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_VCI
FACILITY_DAL = 0x00020000
SEV_DAL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_DAL
SEV_DAL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_DAL
SEV_DAL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_DAL
FACILITY_CCL = 0x00030000
SEV_CCL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_CCL
SEV_CCL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_CCL
SEV_CCL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_CCL
FACILITY_BAL = 0x00040000
SEV_BAL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_BAL
SEV_BAL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_BAL
SEV_BAL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_BAL
# Errors
VCI_SUCCESS = 0x00
VCI_OK = 0x00
VCI_E_UNEXPECTED = SEV_VCI_ERROR | 0x0001
VCI_E_NOT_IMPLEMENTED = SEV_VCI_ERROR | 0x0002
VCI_E_OUTOFMEMORY = SEV_VCI_ERROR | 0x0003
VCI_E_INVALIDARG = SEV_VCI_ERROR | 0x0004
VCI_E_NOINTERFACE = SEV_VCI_ERROR | 0x0005
VCI_E_INVPOINTER = SEV_VCI_ERROR | 0x0006
VCI_E_INVHANDLE = SEV_VCI_ERROR | 0x0007
VCI_E_ABORT = SEV_VCI_ERROR | 0x0008
VCI_E_FAIL = SEV_VCI_ERROR | 0x0009
VCI_E_ACCESSDENIED = SEV_VCI_ERROR | 0x000A
VCI_E_TIMEOUT = SEV_VCI_ERROR | 0x000B
VCI_E_BUSY = SEV_VCI_ERROR | 0x000C
VCI_E_PENDING = SEV_VCI_ERROR | 0x000D
VCI_E_NO_DATA = SEV_VCI_ERROR | 0x000E
VCI_E_NO_MORE_ITEMS = SEV_VCI_ERROR | 0x000F
VCI_E_NOT_INITIALIZED = SEV_VCI_ERROR | 0x0010
VCI_E_ALREADY_INITIALIZED = SEV_VCI_ERROR | 0x00011
VCI_E_RXQUEUE_EMPTY = SEV_VCI_ERROR | 0x00012
VCI_E_TXQUEUE_FULL = SEV_VCI_ERROR | 0x0013
VCI_E_BUFFER_OVERFLOW = SEV_VCI_ERROR | 0x0014
VCI_E_INVALID_STATE = SEV_VCI_ERROR | 0x0015
VCI_E_OBJECT_ALREADY_EXISTS = SEV_VCI_ERROR | 0x0016
VCI_E_INVALID_INDEX = SEV_VCI_ERROR | 0x0017
VCI_E_END_OF_FILE = SEV_VCI_ERROR | 0x0018
VCI_E_DISCONNECTED = SEV_VCI_ERROR | 0x0019
VCI_E_WRONG_FLASHFWVERSION = SEV_VCI_ERROR | 0x001A
# Controller status
CAN_STATUS_TXPEND = 0x01 # transmission pending
CAN_STATUS_OVRRUN = 0x02 # data overrun occurred
CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded
CAN_STATUS_BUSOFF = 0x08 # bus off status
CAN_STATUS_ININIT = 0x10 # init mode active
CAN_STATUS_BUSCERR = 0x20 # bus coupling error
# Controller operating modes
CAN_OPMODE_UNDEFINED = 0x00 # undefined
CAN_OPMODE_STANDARD = 0x01 # reception of 11-bit id messages
CAN_OPMODE_EXTENDED = 0x02 # reception of 29-bit id messages
CAN_OPMODE_ERRFRAME = 0x04 # reception of error frames
CAN_OPMODE_LISTONLY = 0x08 # listen only mode (TX passive)
CAN_OPMODE_LOWSPEED = 0x10 # use low speed bus interface
CAN_OPMODE_AUTOBAUD = 0x20 # automatic bit rate detection
# Extended operating modes
CAN_EXMODE_DISABLED = 0x00
CAN_EXMODE_EXTDATALEN = 0x01
CAN_EXMODE_FASTDATA = 0x02
CAN_EXMODE_NONISOCANFD = 0x04
# Message types
CAN_MSGTYPE_DATA = 0
CAN_MSGTYPE_INFO = 1
CAN_MSGTYPE_ERROR = 2
CAN_MSGTYPE_STATUS = 3
CAN_MSGTYPE_WAKEUP = 4
CAN_MSGTYPE_TIMEOVR = 5
CAN_MSGTYPE_TIMERST = 6
# Information supplied in the abData[0] field of info frames
# (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_INFO).
CAN_INFO_START = 1
CAN_INFO_STOP = 2
CAN_INFO_RESET = 3
# Information supplied in the abData[0] field of info frames
# (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_ERROR).
CAN_ERROR_STUFF = 1 # stuff error
CAN_ERROR_FORM = 2 # form error
CAN_ERROR_ACK = 3 # acknowledgment error
CAN_ERROR_BIT = 4 # bit error
CAN_ERROR_CRC = 6 # CRC error
CAN_ERROR_OTHER = 7 # other (unspecified) error
# acceptance code and mask to reject all CAN IDs
CAN_ACC_MASK_NONE = 0xFFFFFFFF
CAN_ACC_CODE_NONE = 0x80000000
# BTMODEs
CAN_BTMODE_RAW = 0x00000001 # raw mode
CAN_BTMODE_TSM = 0x00000002 # triple sampling mode
CAN_FILTER_VOID = 0x00 # invalid or unknown filter mode (do not use for initialization)
CAN_FILTER_LOCK = 0x01 # lock filter (inhibit all IDs)
CAN_FILTER_PASS = 0x02 # bypass filter (pass all IDs)
CAN_FILTER_INCL = 0x03 # inclusive filtering (pass registered IDs)
CAN_FILTER_EXCL = 0x04 # exclusive filtering (inhibit registered IDs)
# message information flags (used by )
CAN_MSGFLAGS_DLC = 0x0F # [bit 0] data length code
CAN_MSGFLAGS_OVR = 0x10 # [bit 4] data overrun flag
CAN_MSGFLAGS_SRR = 0x20 # [bit 5] self reception request
CAN_MSGFLAGS_RTR = 0x40 # [bit 6] remote transmission request
CAN_MSGFLAGS_EXT = 0x80 # [bit 7] frame format (0=11-bit, 1=29-bit)
# extended message information flags (used by )
CAN_MSGFLAGS2_SSM = 0x01 # [bit 0] single shot mode
CAN_MSGFLAGS2_HPM = 0x02 # [bit 1] high priority message
CAN_MSGFLAGS2_EDL = 0x04 # [bit 2] extended data length
CAN_MSGFLAGS2_FDR = 0x08 # [bit 3] fast data bit rate
CAN_MSGFLAGS2_ESI = 0x10 # [bit 4] error state indicator
CAN_MSGFLAGS2_RES = 0xE0 # [bit 5..7] reserved bits
CAN_ACCEPT_REJECT = 0x00 # message not accepted
CAN_ACCEPT_ALWAYS = 0xFF # message always accepted
CAN_ACCEPT_FILTER_1 = 0x01 # message accepted by filter 1
CAN_ACCEPT_FILTER_2 = 0x02 # message accepted by filter 2
CAN_ACCEPT_PASSEXCL = 0x03 # message passes exclusion filter
CAN_FEATURE_STDOREXT = 0x00000001 # 11 OR 29 bit (exclusive)
CAN_FEATURE_STDANDEXT = 0x00000002 # 11 AND 29 bit (simultaneous)
CAN_FEATURE_RMTFRAME = 0x00000004 # reception of remote frames
CAN_FEATURE_ERRFRAME = 0x00000008 # reception of error frames
CAN_FEATURE_BUSLOAD = 0x00000010 # bus load measurement
CAN_FEATURE_IDFILTER = 0x00000020 # exact message filter
CAN_FEATURE_LISTONLY = 0x00000040 # listen only mode
CAN_FEATURE_SCHEDULER = 0x00000080 # cyclic message scheduler
CAN_FEATURE_GENERRFRM = 0x00000100 # error frame generation
CAN_FEATURE_DELAYEDTX = 0x00000200 # delayed message transmitter
CAN_FEATURE_SINGLESHOT = 0x00000400 # single shot mode
CAN_FEATURE_HIGHPRIOR = 0x00000800 # high priority message
CAN_FEATURE_AUTOBAUD = 0x00001000 # automatic bit rate detection
CAN_FEATURE_EXTDATA = 0x00002000 # extended data length (CANFD)
CAN_FEATURE_FASTDATA = 0x00004000 # fast data bit rate (CANFD)
CAN_FEATURE_ISOFRAME = 0x00008000 # ISO conform frame (CANFD)
CAN_FEATURE_NONISOFRM = (
0x00010000 # non ISO conform frame (CANFD) (different CRC computation)
)
CAN_FEATURE_64BITTSC = 0x00020000 # 64-bit time stamp counter
CAN_BITRATE_PRESETS = {
250000: structures.CANBTP(
dwMode=0, dwBPS=250000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0
), # SP = 80,0%
500000: structures.CANBTP(
dwMode=0, dwBPS=500000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0
), # SP = 80,0%
1000000: structures.CANBTP(
dwMode=0, dwBPS=1000000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0
), # SP = 80,0%
}
CAN_DATABITRATE_PRESETS = {
500000: structures.CANBTP(
dwMode=0, dwBPS=500000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=6400
), # SP = 80,0%
833333: structures.CANBTP(
dwMode=0, dwBPS=833333, wTS1=1600, wTS2=400, wSJW=400, wTDO=1620
), # SP = 80,0%
1000000: structures.CANBTP(
dwMode=0, dwBPS=1000000, wTS1=1600, wTS2=400, wSJW=400, wTDO=1600
), # SP = 80,0%
1538461: structures.CANBTP(
dwMode=0, dwBPS=1538461, wTS1=1000, wTS2=300, wSJW=300, wTDO=1040
), # SP = 76,9%
2000000: structures.CANBTP(
dwMode=0, dwBPS=2000000, wTS1=1600, wTS2=400, wSJW=400, wTDO=1600
), # SP = 80,0%
4000000: structures.CANBTP(
dwMode=0, dwBPS=4000000, wTS1=800, wTS2=200, wSJW=200, wTDO=800
), # SP = 80,0%
5000000: structures.CANBTP(
dwMode=0, dwBPS=5000000, wTS1=600, wTS2=200, wSJW=200, wTDO=600
), # SP = 75,0%
6666666: structures.CANBTP(
dwMode=0, dwBPS=6666666, wTS1=400, wTS2=200, wSJW=200, wTDO=402
), # SP = 66,7%
8000000: structures.CANBTP(
dwMode=0, dwBPS=8000000, wTS1=400, wTS2=100, wSJW=100, wTDO=250
), # SP = 80,0%
10000000: structures.CANBTP(
dwMode=0, dwBPS=10000000, wTS1=300, wTS2=100, wSJW=100, wTDO=200
), # SP = 75,0%
}
python-can-4.5.0/can/interfaces/ixxat/exceptions.py 0000664 0000000 0000000 00000001722 14722003266 0022360 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
Copyright (C) 2019 Marcel Kanter
"""
from can import (
CanInitializationError,
CanOperationError,
CanTimeoutError,
)
__all__ = [
"VCITimeout",
"VCIError",
"VCIRxQueueEmptyError",
"VCIBusOffError",
"VCIDeviceNotFoundError",
]
class VCITimeout(CanTimeoutError):
"""Wraps the VCI_E_TIMEOUT error"""
class VCIError(CanOperationError):
"""Try to display errors that occur within the wrapped C library nicely."""
class VCIRxQueueEmptyError(VCIError):
"""Wraps the VCI_E_RXQUEUE_EMPTY error"""
def __init__(self):
super().__init__("Receive queue is empty")
class VCIBusOffError(VCIError):
def __init__(self):
super().__init__("Controller is in BUSOFF state")
class VCIDeviceNotFoundError(CanInitializationError):
pass
python-can-4.5.0/can/interfaces/ixxat/structures.py 0000664 0000000 0000000 00000024454 14722003266 0022431 0 ustar 00root root 0000000 0000000 """
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
Copyright (C) 2016 Giuseppe Corbelli
"""
import ctypes
class LUID(ctypes.Structure):
_fields_ = [("LowPart", ctypes.c_uint32), ("HighPart", ctypes.c_int32)]
PLUID = ctypes.POINTER(LUID)
class VCIID(ctypes.Union):
_fields_ = [("AsLuid", LUID), ("AsInt64", ctypes.c_int64)]
PVCIID = ctypes.POINTER(VCIID)
class GUID(ctypes.Structure):
_fields_ = [
("Data1", ctypes.c_uint32),
("Data2", ctypes.c_uint16),
("Data3", ctypes.c_uint16),
("Data4", ctypes.c_char * 8),
]
class VCIDEVICEINFO(ctypes.Structure):
class UniqueHardwareId(ctypes.Union):
_fields_ = [("AsChar", ctypes.c_char * 16), ("AsGuid", GUID)]
_fields_ = [
("VciObjectId", VCIID),
("DeviceClass", GUID),
("DriverMajorVersion", ctypes.c_uint8),
("DriverMinorVersion", ctypes.c_uint8),
("DriverBuildVersion", ctypes.c_uint16),
("HardwareBranchVersion", ctypes.c_uint8),
("HardwareMajorVersion", ctypes.c_uint8),
("HardwareMinorVersion", ctypes.c_uint8),
("HardwareBuildVersion", ctypes.c_uint8),
("UniqueHardwareId", UniqueHardwareId),
("Description", ctypes.c_char * 128),
("Manufacturer", ctypes.c_char * 126),
("DriverReleaseVersion", ctypes.c_uint16),
]
def __str__(self):
return (
f"Mfg: {self.Manufacturer}, "
f"Dev: {self.Description} "
f"HW: {self.HardwareBranchVersion}"
f".{self.HardwareMajorVersion}"
f".{self.HardwareMinorVersion}"
f".{self.HardwareBuildVersion} "
f"Drv: {self.DriverReleaseVersion}"
f".{self.DriverMajorVersion}"
f".{self.DriverMinorVersion}"
f".{self.DriverBuildVersion}"
)
PVCIDEVICEINFO = ctypes.POINTER(VCIDEVICEINFO)
class CANLINESTATUS(ctypes.Structure):
_fields_ = [
# current CAN operating mode. Value is a logical combination of
# one or more CAN_OPMODE_xxx constants
("bOpMode", ctypes.c_uint8),
("bBtReg0", ctypes.c_uint8), # current bus timing register 0 value
("bBtReg1", ctypes.c_uint8), # current bus timing register 1 value
("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100)
("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_)
]
PCANLINESTATUS = ctypes.POINTER(CANLINESTATUS)
class CANCHANSTATUS(ctypes.Structure):
_fields_ = [
("sLineStatus", CANLINESTATUS), # current CAN line status
("fActivated", ctypes.c_uint32), # TRUE if the channel is activated
("fRxOverrun", ctypes.c_uint32), # TRUE if receive FIFO overrun occurred
("bRxFifoLoad", ctypes.c_uint8), # receive FIFO load in percent (0..100)
("bTxFifoLoad", ctypes.c_uint8), # transmit FIFO load in percent (0..100)
]
PCANCHANSTATUS = ctypes.POINTER(CANCHANSTATUS)
class CANCAPABILITIES(ctypes.Structure):
_fields_ = [
("wCtrlType", ctypes.c_uint16),
("wBusCoupling", ctypes.c_uint16),
("dwFeatures", ctypes.c_uint32),
("dwClockFreq", ctypes.c_uint32),
("dwTscDivisor", ctypes.c_uint32),
("dwCmsDivisor", ctypes.c_uint32),
("dwCmsMaxTicks", ctypes.c_uint32),
("dwDtxDivisor", ctypes.c_uint32),
("dwDtxMaxTicks", ctypes.c_uint32),
]
PCANCAPABILITIES = ctypes.POINTER(CANCAPABILITIES)
class CANMSGINFO(ctypes.Union):
class Bytes(ctypes.Structure):
_fields_ = [
("bType", ctypes.c_uint8), # type (see CAN_MSGTYPE_ constants)
(
"bAddFlags",
ctypes.c_uint8,
), # extended flags (see CAN_MSGFLAGS2_ constants). AKA bFlags2 in VCI v4
("bFlags", ctypes.c_uint8), # flags (see CAN_MSGFLAGS_ constants)
("bAccept", ctypes.c_uint8), # accept code (see CAN_ACCEPT_ constants)
]
class Bits(ctypes.Structure):
_fields_ = [
("type", ctypes.c_uint32, 8), # type (see CAN_MSGTYPE_ constants)
("ssm", ctypes.c_uint32, 1), # single shot mode
("hpm", ctypes.c_uint32, 1), # high priority message
("edl", ctypes.c_uint32, 1), # extended data length
("fdr", ctypes.c_uint32, 1), # fast data bit rate
("esi", ctypes.c_uint32, 1), # error state indicator
("res", ctypes.c_uint32, 3), # reserved set to 0
("dlc", ctypes.c_uint32, 4), # data length code
("ovr", ctypes.c_uint32, 1), # data overrun
("srr", ctypes.c_uint32, 1), # self reception request
("rtr", ctypes.c_uint32, 1), # remote transmission request
(
"ext",
ctypes.c_uint32,
1,
), # extended frame format (0=standard, 1=extended)
("afc", ctypes.c_uint32, 8), # accept code (see CAN_ACCEPT_ constants)
]
_fields_ = [("Bytes", Bytes), ("Bits", Bits)]
PCANMSGINFO = ctypes.POINTER(CANMSGINFO)
class CANMSG(ctypes.Structure):
_fields_ = [
("dwTime", ctypes.c_uint32),
# CAN ID of the message in Intel format (aligned right) without RTR bit.
("dwMsgId", ctypes.c_uint32),
("uMsgInfo", CANMSGINFO),
("abData", ctypes.c_uint8 * 8),
]
def __str__(self) -> str:
return """ID: 0x{:04x}{} DLC: {:02d} DATA: {}""".format(
self.dwMsgId,
"[RTR]" if self.uMsgInfo.Bits.rtr else "",
self.uMsgInfo.Bits.dlc,
memoryview(self.abData)[: self.uMsgInfo.Bits.dlc].hex(sep=" "),
)
PCANMSG = ctypes.POINTER(CANMSG)
class CANCYCLICTXMSG(ctypes.Structure):
_fields_ = [
("wCycleTime", ctypes.c_uint16),
("bIncrMode", ctypes.c_uint8),
("bByteIndex", ctypes.c_uint8),
("dwMsgId", ctypes.c_uint32),
("uMsgInfo", CANMSGINFO),
("abData", ctypes.c_uint8 * 8),
]
PCANCYCLICTXMSG = ctypes.POINTER(CANCYCLICTXMSG)
class CANBTP(ctypes.Structure):
_fields_ = [
("dwMode", ctypes.c_uint32), # timing mode (see CAN_BTMODE_ const)
("dwBPS", ctypes.c_uint32), # bits per second or prescaler (see CAN_BTMODE_RAW)
("wTS1", ctypes.c_uint16), # length of time segment 1 in quanta
("wTS2", ctypes.c_uint16), # length of time segment 2 in quanta
("wSJW", ctypes.c_uint16), # re-synchronization jump width im quanta
(
"wTDO",
ctypes.c_uint16,
), # transceiver delay offset (SSP offset) in quanta (0 = disabled, 0xFFFF = simplified SSP positioning)
]
def __str__(self):
return "dwMode=%d, dwBPS=%d, wTS1=%d, wTS2=%d, wSJW=%d, wTDO=%d" % (
self.dwMode,
self.dwBPS,
self.wTS1,
self.wTS2,
self.wSJW,
self.wTDO,
)
PCANBTP = ctypes.POINTER(CANBTP)
class CANCAPABILITIES2(ctypes.Structure):
_fields_ = [
("wCtrlType", ctypes.c_uint16), # Type of CAN controller (see CAN_CTRL_ const)
("wBusCoupling", ctypes.c_uint16), # Type of Bus coupling (see CAN_BUSC_ const)
(
"dwFeatures",
ctypes.c_uint32,
), # supported features (see CAN_FEATURE_ constants)
("dwCanClkFreq", ctypes.c_uint32), # CAN clock frequency [Hz]
("sSdrRangeMin", CANBTP), # minimum bit timing values for standard bit rate
("sSdrRangeMax", CANBTP), # maximum bit timing values for standard bit rate
("sFdrRangeMin", CANBTP), # minimum bit timing values for fast data bit rate
("sFdrRangeMax", CANBTP), # maximum bit timing values for fast data bit rate
(
"dwTscClkFreq",
ctypes.c_uint32,
), # clock frequency of the time stamp counter [Hz]
("dwTscDivisor", ctypes.c_uint32), # divisor for the message time stamp counter
(
"dwCmsClkFreq",
ctypes.c_uint32,
), # clock frequency of cyclic message scheduler [Hz]
("dwCmsDivisor", ctypes.c_uint32), # divisor for the cyclic message scheduler
(
"dwCmsMaxTicks",
ctypes.c_uint32,
), # maximum tick count value of the cyclic message
(
"dwDtxClkFreq",
ctypes.c_uint32,
), # clock frequency of the delayed message transmitter [Hz]
(
"dwDtxDivisor",
ctypes.c_uint32,
), # divisor for the delayed message transmitter
(
"dwDtxMaxTicks",
ctypes.c_uint32,
), # maximum tick count value of the delayed message transmitter
]
PCANCAPABILITIES2 = ctypes.POINTER(CANCAPABILITIES2)
class CANLINESTATUS2(ctypes.Structure):
_fields_ = [
("bOpMode", ctypes.c_uint8), # current CAN operating mode
("bExMode", ctypes.c_uint8), # current CAN extended operating mode
("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100)
("bReserved", ctypes.c_uint8), # reserved set to 0
("sBtpSdr", ctypes.c_uint8), # standard bit rate timing
("sBtpFdr", ctypes.c_uint8), # fast data bit rate timing
("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_)
]
PCANLINESTATUS2 = ctypes.POINTER(CANLINESTATUS2)
class CANMSG2(ctypes.Structure):
_fields_ = [
("dwTime", ctypes.c_uint32), # time stamp for receive message
("rsvd", ctypes.c_uint32), # reserved (set to 0)
("dwMsgId", ctypes.c_uint32), # CAN message identifier (INTEL format)
("uMsgInfo", CANMSGINFO), # message information (bit field)
("abData", ctypes.c_uint8 * 64), # message data
]
PCANMSG2 = ctypes.POINTER(CANMSG2)
class CANCYCLICTXMSG2(ctypes.Structure):
_fields_ = [
("wCycleTime", ctypes.c_uint16), # cycle time for the message in ticks
(
"bIncrMode",
ctypes.c_uint8,
), # auto increment mode (see CAN_CTXMSG_INC_ const)
(
"bByteIndex",
ctypes.c_uint8,
), # index of the byte within abData[] to increment
("dwMsgId", ctypes.c_uint32), # message identifier (INTEL format)
("uMsgInfo", CANMSGINFO), # message information (bit field)
("abData", ctypes.c_uint8 * 64), # message data
]
PCANCYCLICTXMSG2 = ctypes.POINTER(CANCYCLICTXMSG2)
python-can-4.5.0/can/interfaces/kvaser/ 0000775 0000000 0000000 00000000000 14722003266 0017761 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/kvaser/__init__.py 0000664 0000000 0000000 00000000332 14722003266 0022070 0 ustar 00root root 0000000 0000000 """
"""
__all__ = [
"CANLIBInitializationError",
"CANLIBOperationError",
"KvaserBus",
"canlib",
"constants",
"get_channel_info",
"structures",
]
from can.interfaces.kvaser.canlib import *
python-can-4.5.0/can/interfaces/kvaser/canlib.py 0000664 0000000 0000000 00000066667 14722003266 0021610 0 ustar 00root root 0000000 0000000 """
Contains Python equivalents of the function and constant
definitions in CANLIB's canlib.h, with some supporting functionality
specific to Python.
Copyright (C) 2010 Dynamic Controls
"""
import ctypes
import logging
import sys
import time
from typing import Optional, Union
from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message
from can.exceptions import CanError, CanInitializationError, CanOperationError
from can.typechecking import CanFilters
from can.util import check_or_adjust_timing_clock, time_perfcounter_correlation
from . import constants as canstat
from . import structures
log = logging.getLogger("can.kvaser")
# Resolution in us
TIMESTAMP_RESOLUTION = 10
TIMESTAMP_FACTOR = TIMESTAMP_RESOLUTION / 1000000.0
try:
if sys.platform == "win32":
__canlib = ctypes.windll.LoadLibrary("canlib32")
else:
__canlib = ctypes.cdll.LoadLibrary("libcanlib.so")
log.info("loaded kvaser's CAN library")
except OSError:
log.warning("Kvaser canlib is unavailable.")
__canlib = None
def _unimplemented_function(*args):
raise NotImplementedError("This function is not implemented in canlib")
def __get_canlib_function(func_name, argtypes=None, restype=None, errcheck=None):
argtypes = [] if argtypes is None else argtypes
# log.debug('Wrapping function "%s"' % func_name)
try:
# e.g. canlib.canBusOn
retval = getattr(__canlib, func_name)
# log.debug('"%s" found in library', func_name)
except AttributeError:
log.warning('"%s" was not found in library', func_name)
return _unimplemented_function
else:
# log.debug('Result type is: %s' % type(restype))
# log.debug('Error check function is: %s' % errcheck)
retval.argtypes = argtypes
retval.restype = restype
if errcheck:
retval.errcheck = errcheck
return retval
class CANLIBError(CanError):
"""
Try to display errors that occur within the wrapped C library nicely.
"""
def __init__(self, function, error_code, arguments):
message = CANLIBError._get_error_message(error_code)
super().__init__(f"Function {function.__name__} failed - {message}", error_code)
self.function = function
self.arguments = arguments
@staticmethod
def _get_error_message(error_code: int) -> str:
errmsg = ctypes.create_string_buffer(128)
canGetErrorText(error_code, errmsg, len(errmsg))
return errmsg.value.decode("ascii")
class CANLIBInitializationError(CANLIBError, CanInitializationError):
pass
class CANLIBOperationError(CANLIBError, CanOperationError):
pass
def __convert_can_status_to_int(result):
if isinstance(result, int):
return result
else:
return result.value
def __check_status_operation(result, function, arguments):
result = __convert_can_status_to_int(result)
if not canstat.CANSTATUS_SUCCESS(result):
raise CANLIBOperationError(function, result, arguments)
return result
def __check_status_initialization(result, function, arguments):
result = __convert_can_status_to_int(result)
if not canstat.CANSTATUS_SUCCESS(result):
raise CANLIBInitializationError(function, result, arguments)
return result
def __check_status_read(result, function, arguments):
result = __convert_can_status_to_int(result)
if not canstat.CANSTATUS_SUCCESS(result) and result != canstat.canERR_NOMSG:
raise CANLIBOperationError(function, result, arguments)
return result
class c_canHandle(ctypes.c_int):
pass
canINVALID_HANDLE = -1
def __check_bus_handle_validity(handle, function, arguments):
if handle.value > canINVALID_HANDLE:
return handle # is valid
result = __convert_can_status_to_int(handle)
raise CANLIBInitializationError(function, result, arguments)
if __canlib is not None:
canInitializeLibrary = __get_canlib_function("canInitializeLibrary")
canGetErrorText = __get_canlib_function(
"canGetErrorText",
argtypes=[canstat.c_canStatus, ctypes.c_char_p, ctypes.c_uint],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
# TODO wrap this type of function to provide a more Pythonic API
canGetNumberOfChannels = __get_canlib_function(
"canGetNumberOfChannels",
argtypes=[ctypes.c_void_p],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
kvReadTimer = __get_canlib_function(
"kvReadTimer",
argtypes=[c_canHandle, ctypes.POINTER(ctypes.c_uint)],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canBusOff = __get_canlib_function(
"canBusOff",
argtypes=[c_canHandle],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canBusOn = __get_canlib_function(
"canBusOn",
argtypes=[c_canHandle],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canClose = __get_canlib_function(
"canClose",
argtypes=[c_canHandle],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canOpenChannel = __get_canlib_function(
"canOpenChannel",
argtypes=[ctypes.c_int, ctypes.c_int],
restype=c_canHandle,
errcheck=__check_bus_handle_validity,
)
canSetBusParams = __get_canlib_function(
"canSetBusParams",
argtypes=[
c_canHandle,
ctypes.c_long,
ctypes.c_uint,
ctypes.c_uint,
ctypes.c_uint,
ctypes.c_uint,
ctypes.c_uint,
],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canSetBusParamsC200 = __get_canlib_function(
"canSetBusParamsC200",
argtypes=[
c_canHandle,
ctypes.c_byte,
ctypes.c_byte,
],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canSetBusParamsFd = __get_canlib_function(
"canSetBusParamsFd",
argtypes=[
c_canHandle,
ctypes.c_long,
ctypes.c_uint,
ctypes.c_uint,
ctypes.c_uint,
],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canSetBusOutputControl = __get_canlib_function(
"canSetBusOutputControl",
argtypes=[c_canHandle, ctypes.c_uint],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canSetAcceptanceFilter = __get_canlib_function(
"canSetAcceptanceFilter",
argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_uint, ctypes.c_int],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canReadWait = __get_canlib_function(
"canReadWait",
argtypes=[
c_canHandle,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_long,
],
restype=canstat.c_canStatus,
errcheck=__check_status_read,
)
canWrite = __get_canlib_function(
"canWrite",
argtypes=[
c_canHandle,
ctypes.c_long,
ctypes.c_void_p,
ctypes.c_uint,
ctypes.c_uint,
],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canWriteSync = __get_canlib_function(
"canWriteSync",
argtypes=[c_canHandle, ctypes.c_ulong],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canIoCtlInit = __get_canlib_function(
"canIoCtl",
argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canIoCtl = __get_canlib_function(
"canIoCtl",
argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canGetVersion = __get_canlib_function(
"canGetVersion", restype=ctypes.c_short, errcheck=__check_status_operation
)
kvFlashLeds = __get_canlib_function(
"kvFlashLeds",
argtypes=[c_canHandle, ctypes.c_int, ctypes.c_int],
restype=ctypes.c_short,
errcheck=__check_status_operation,
)
if sys.platform == "win32":
canGetVersionEx = __get_canlib_function(
"canGetVersionEx",
argtypes=[ctypes.c_uint],
restype=ctypes.c_uint,
errcheck=__check_status_operation,
)
canGetChannelData = __get_canlib_function(
"canGetChannelData",
argtypes=[ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t],
restype=canstat.c_canStatus,
errcheck=__check_status_initialization,
)
canRequestBusStatistics = __get_canlib_function(
"canRequestBusStatistics",
argtypes=[c_canHandle],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
canGetBusStatistics = __get_canlib_function(
"canGetBusStatistics",
argtypes=[
c_canHandle,
ctypes.POINTER(structures.BusStatistics),
ctypes.c_size_t,
],
restype=canstat.c_canStatus,
errcheck=__check_status_operation,
)
def init_kvaser_library():
if __canlib is not None:
try:
log.debug("Initializing Kvaser CAN library")
canInitializeLibrary()
log.debug("CAN library initialized")
except Exception:
log.warning("Kvaser canlib could not be initialized.")
DRIVER_MODE_SILENT = False
DRIVER_MODE_NORMAL = True
BITRATE_OBJS = {
1000000: canstat.canBITRATE_1M,
500000: canstat.canBITRATE_500K,
250000: canstat.canBITRATE_250K,
125000: canstat.canBITRATE_125K,
100000: canstat.canBITRATE_100K,
83000: canstat.canBITRATE_83K,
62000: canstat.canBITRATE_62K,
50000: canstat.canBITRATE_50K,
10000: canstat.canBITRATE_10K,
}
BITRATE_FD = {
500000: canstat.canFD_BITRATE_500K_80P,
1000000: canstat.canFD_BITRATE_1M_80P,
2000000: canstat.canFD_BITRATE_2M_80P,
4000000: canstat.canFD_BITRATE_4M_80P,
8000000: canstat.canFD_BITRATE_8M_60P,
}
class KvaserBus(BusABC):
"""
The CAN Bus implemented for the Kvaser interface.
"""
def __init__(
self,
channel: int,
can_filters: Optional[CanFilters] = None,
timing: Optional[Union[BitTiming, BitTimingFd]] = None,
**kwargs,
):
"""
:param int channel:
The Channel id to create this bus with.
:param list can_filters:
See :meth:`can.BusABC.set_filters`.
Backend Configuration
:param timing:
An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd`
to specify the bit timing parameters for the Kvaser interface. If provided, it
takes precedence over the all other timing-related parameters.
Note that the `f_clock` property of the `timing` instance must be 16_000_000 (16MHz)
for standard CAN or 80_000_000 (80MHz) for CAN FD.
:param int bitrate:
Bitrate of channel in bit/s
:param bool accept_virtual:
If virtual channels should be accepted.
:param int tseg1:
Time segment 1, that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
If this parameter is not given, the Kvaser driver will try to choose
all bit timing parameters from a set of defaults.
:param int tseg2:
Time segment 2, that is, the number of quanta from the sampling
point to the end of the bit.
:param int sjw:
The Synchronization Jump Width. Decides the maximum number of time quanta
that the controller can resynchronize every bit.
:param int no_samp:
Either 1 or 3. Some CAN controllers can also sample each bit three times.
In this case, the bit will be sampled three quanta in a row,
with the last sample being taken in the edge between TSEG1 and TSEG2.
Three samples should only be used for relatively slow baudrates.
:param bool driver_mode:
Silent or normal.
:param bool single_handle:
Use one Kvaser CANLIB bus handle for both reading and writing.
This can be set if reading and/or writing is done from one thread.
:param bool receive_own_messages:
If messages transmitted should also be received back.
Only works if single_handle is also False.
If you want to receive messages from other applications on the same
computer, set this to True or set single_handle to True.
:param bool fd:
If CAN-FD frames should be supported.
:param bool fd_non_iso:
Open the channel in Non-ISO (Bosch) FD mode. Only applies for FD buses.
This changes the handling of the stuff-bit counter and the CRC. Defaults
to False (ISO mode)
:param bool exclusive:
Don't allow sharing of this CANlib channel.
:param bool override_exclusive:
Open the channel even if it is opened for exclusive access already.
:param int data_bitrate:
Which bitrate to use for data phase in CAN FD.
Defaults to arbitration bitrate.
"""
log.info(f"CAN Filters: {can_filters}")
log.info(f"Got configuration of: {kwargs}")
bitrate = kwargs.get("bitrate", 500000)
tseg1 = kwargs.get("tseg1", 0)
tseg2 = kwargs.get("tseg2", 0)
sjw = kwargs.get("sjw", 0)
no_samp = kwargs.get("no_samp", 0)
driver_mode = kwargs.get("driver_mode", DRIVER_MODE_NORMAL)
single_handle = kwargs.get("single_handle", False)
receive_own_messages = kwargs.get("receive_own_messages", False)
exclusive = kwargs.get("exclusive", False)
override_exclusive = kwargs.get("override_exclusive", False)
accept_virtual = kwargs.get("accept_virtual", True)
fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False)
data_bitrate = kwargs.get("data_bitrate", None)
fd_non_iso = kwargs.get("fd_non_iso", False)
try:
channel = int(channel)
except ValueError:
raise ValueError("channel must be an integer") from None
self.channel = channel
self.single_handle = single_handle
self._can_protocol = CanProtocol.CAN_20
if fd_non_iso:
self._can_protocol = CanProtocol.CAN_FD_NON_ISO
elif fd:
self._can_protocol = CanProtocol.CAN_FD
log.debug("Initialising bus instance")
num_channels = ctypes.c_int(0)
canGetNumberOfChannels(ctypes.byref(num_channels))
num_channels = int(num_channels.value)
log.info("Found %d available channels", num_channels)
for idx in range(num_channels):
channel_info = get_channel_info(idx)
channel_info = f'{channel_info["device_name"]}, S/N {channel_info["serial"]} (#{channel_info["dongle_channel"]})'
log.info("%d: %s", idx, channel_info)
if idx == channel:
self.channel_info = channel_info
flags = 0
if exclusive:
flags |= canstat.canOPEN_EXCLUSIVE
if override_exclusive:
flags |= canstat.canOPEN_OVERRIDE_EXCLUSIVE
if accept_virtual:
flags |= canstat.canOPEN_ACCEPT_VIRTUAL
if fd:
if fd_non_iso:
flags |= canstat.canOPEN_CAN_FD_NONISO
else:
flags |= canstat.canOPEN_CAN_FD
log.debug("Creating read handle to bus channel: %s", channel)
self._read_handle = canOpenChannel(channel, flags)
canIoCtlInit(
self._read_handle,
canstat.canIOCTL_SET_TIMER_SCALE,
ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)),
4,
)
if isinstance(timing, BitTimingFd):
timing = check_or_adjust_timing_clock(timing, [80_000_000])
canSetBusParams(
self._read_handle,
timing.nom_bitrate,
timing.nom_tseg1,
timing.nom_tseg2,
timing.nom_sjw,
1,
0,
)
canSetBusParamsFd(
self._read_handle,
timing.data_bitrate,
timing.data_tseg1,
timing.data_tseg2,
timing.data_sjw,
)
elif isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, [16_000_000])
canSetBusParamsC200(self._read_handle, timing.btr0, timing.btr1)
else:
if fd:
if "tseg1" not in kwargs and bitrate in BITRATE_FD:
# Use predefined bitrate for arbitration
bitrate = BITRATE_FD[bitrate]
if data_bitrate in BITRATE_FD:
# Use predefined bitrate for data
data_bitrate = BITRATE_FD[data_bitrate]
elif not data_bitrate:
# Use same bitrate for arbitration and data phase
data_bitrate = bitrate
canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw)
else:
if "tseg1" not in kwargs and bitrate in BITRATE_OBJS:
bitrate = BITRATE_OBJS[bitrate]
canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0)
# By default, use local echo if single handle is used (see #160)
local_echo = single_handle or receive_own_messages
if receive_own_messages and single_handle:
log.warning("receive_own_messages only works if single_handle is False")
canIoCtlInit(
self._read_handle,
canstat.canIOCTL_SET_LOCAL_TXECHO,
ctypes.byref(ctypes.c_byte(local_echo)),
1,
)
if self.single_handle:
log.debug("We don't require separate handles to the bus")
self._write_handle = self._read_handle
else:
log.debug("Creating separate handle for TX on channel: %s", channel)
if exclusive:
flags_ = flags & ~canstat.canOPEN_EXCLUSIVE
flags_ |= canstat.canOPEN_OVERRIDE_EXCLUSIVE
else:
flags_ = flags
self._write_handle = canOpenChannel(channel, flags_)
can_driver_mode = (
canstat.canDRIVER_SILENT
if driver_mode == DRIVER_MODE_SILENT
else canstat.canDRIVER_NORMAL
)
canSetBusOutputControl(self._write_handle, can_driver_mode)
self._is_filtered = False
super().__init__(
channel=channel,
can_filters=can_filters,
**kwargs,
)
# activate channel after CAN filters were applied
log.debug("Go on bus")
if not self.single_handle:
canBusOn(self._read_handle)
canBusOn(self._write_handle)
# timestamp must be set after bus is online, otherwise kvReadTimer may return erroneous values
self._timestamp_offset = self._update_timestamp_offset()
def _update_timestamp_offset(self) -> float:
timer = ctypes.c_uint(0)
try:
if time.get_clock_info("time").resolution > 1e-5:
ts, perfcounter = time_perfcounter_correlation()
kvReadTimer(self._read_handle, ctypes.byref(timer))
current_perfcounter = time.perf_counter()
now = ts + (current_perfcounter - perfcounter)
return now - (timer.value * TIMESTAMP_FACTOR)
else:
kvReadTimer(self._read_handle, ctypes.byref(timer))
return time.time() - (timer.value * TIMESTAMP_FACTOR)
except Exception as exc:
# timer is usually close to 0
log.info(str(exc))
return time.time() - (timer.value * TIMESTAMP_FACTOR)
def _apply_filters(self, filters):
if filters and len(filters) == 1:
can_id = filters[0]["can_id"]
can_mask = filters[0]["can_mask"]
extended = 1 if filters[0].get("extended") else 0
try:
for handle in (self._read_handle, self._write_handle):
canSetAcceptanceFilter(handle, can_id, can_mask, extended)
except (NotImplementedError, CANLIBError) as e:
self._is_filtered = False
log.error("Filtering is not supported - %s", e)
else:
self._is_filtered = True
log.info("canlib is filtering on ID 0x%X, mask 0x%X", can_id, can_mask)
else:
self._is_filtered = False
log.info("Hardware filtering has been disabled")
try:
for handle in (self._read_handle, self._write_handle):
for extended in (0, 1):
canSetAcceptanceFilter(handle, 0, 0, extended)
except (NotImplementedError, CANLIBError) as e:
log.error("An error occurred while disabling filtering: %s", e)
def flush_tx_buffer(self):
"""Wipeout the transmit buffer on the Kvaser."""
canIoCtl(self._write_handle, canstat.canIOCTL_FLUSH_TX_BUFFER, 0, 0)
def _recv_internal(self, timeout=None):
"""
Read a message from kvaser device and return whether filtering has taken place.
"""
arb_id = ctypes.c_long(0)
data = ctypes.create_string_buffer(64)
dlc = ctypes.c_uint(0)
flags = ctypes.c_uint(0)
timestamp = ctypes.c_ulong(0)
if timeout is None:
# Set infinite timeout
# http://www.kvaser.com/canlib-webhelp/group___c_a_n.html#ga2edd785a87cc16b49ece8969cad71e5b
timeout = 0xFFFFFFFF
else:
timeout = int(timeout * 1000)
# log.log(9, 'Reading for %d ms on handle: %s' % (timeout, self._read_handle))
status = canReadWait(
self._read_handle,
ctypes.byref(arb_id),
ctypes.byref(data),
ctypes.byref(dlc),
ctypes.byref(flags),
ctypes.byref(timestamp),
timeout, # This is an X ms blocking read
)
if status == canstat.canOK:
data_array = data.raw
flags = flags.value
is_extended = bool(flags & canstat.canMSG_EXT)
is_remote_frame = bool(flags & canstat.canMSG_RTR)
is_error_frame = bool(flags & canstat.canMSG_ERROR_FRAME)
is_fd = bool(flags & canstat.canFDMSG_FDF)
bitrate_switch = bool(flags & canstat.canFDMSG_BRS)
error_state_indicator = bool(flags & canstat.canFDMSG_ESI)
msg_timestamp = timestamp.value * TIMESTAMP_FACTOR
rx_msg = Message(
arbitration_id=arb_id.value,
data=data_array[: dlc.value],
dlc=dlc.value,
is_extended_id=is_extended,
is_error_frame=is_error_frame,
is_remote_frame=is_remote_frame,
is_fd=is_fd,
bitrate_switch=bitrate_switch,
error_state_indicator=error_state_indicator,
channel=self.channel,
timestamp=msg_timestamp + self._timestamp_offset,
)
# log.debug('Got message: %s' % rx_msg)
return rx_msg, self._is_filtered
else:
# log.debug('read complete -> status not okay')
return None, self._is_filtered
def send(self, msg, timeout=None):
# log.debug("Writing a message: {}".format(msg))
flags = canstat.canMSG_EXT if msg.is_extended_id else canstat.canMSG_STD
if msg.is_remote_frame:
flags |= canstat.canMSG_RTR
if msg.is_error_frame:
flags |= canstat.canMSG_ERROR_FRAME
if msg.is_fd:
flags |= canstat.canFDMSG_FDF
if msg.bitrate_switch:
flags |= canstat.canFDMSG_BRS
ArrayConstructor = ctypes.c_byte * msg.dlc
buf = ArrayConstructor(*msg.data)
canWrite(
self._write_handle, msg.arbitration_id, ctypes.byref(buf), msg.dlc, flags
)
if timeout:
canWriteSync(self._write_handle, int(timeout * 1000))
def flash(self, flash=True):
"""
Turn on or off flashing of the device's LED for physical
identification purposes.
"""
if flash:
action = canstat.kvLED_ACTION_ALL_LEDS_ON
else:
action = canstat.kvLED_ACTION_ALL_LEDS_OFF
try:
kvFlashLeds(self._read_handle, action, 30000)
except (CANLIBError, NotImplementedError) as e:
log.error("Could not flash LEDs (%s)", e)
def shutdown(self):
super().shutdown()
# Wait for transmit queue to be cleared
try:
canWriteSync(self._write_handle, 100)
except CANLIBError:
# Not a huge deal and it seems that we get timeout if no messages
# exists in the buffer at all
pass
if not self.single_handle:
canBusOff(self._read_handle)
canClose(self._read_handle)
canBusOff(self._write_handle)
canClose(self._write_handle)
def get_stats(self) -> structures.BusStatistics:
"""Retrieves the bus statistics.
Use like so:
.. testsetup:: kvaser
from unittest.mock import Mock
from can.interfaces.kvaser.structures import BusStatistics
bus = Mock()
bus.get_stats = Mock(side_effect=lambda: BusStatistics())
.. doctest:: kvaser
>>> stats = bus.get_stats()
>>> print(stats)
std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0
:returns: bus statistics.
"""
canRequestBusStatistics(self._write_handle)
stats = structures.BusStatistics()
canGetBusStatistics(
self._write_handle, ctypes.pointer(stats), ctypes.sizeof(stats)
)
return stats
@staticmethod
def _detect_available_configs():
config_list = []
try:
num_channels = ctypes.c_int(0)
canGetNumberOfChannels(ctypes.byref(num_channels))
for channel in range(0, int(num_channels.value)):
info = get_channel_info(channel)
config_list.append({"interface": "kvaser", "channel": channel, **info})
except (CANLIBError, NameError):
pass
return config_list
def get_channel_info(channel):
name = ctypes.create_string_buffer(80)
serial = ctypes.c_uint64()
number = ctypes.c_uint()
canGetChannelData(
channel,
canstat.canCHANNELDATA_DEVDESCR_ASCII,
ctypes.byref(name),
ctypes.sizeof(name),
)
canGetChannelData(
channel,
canstat.canCHANNELDATA_CARD_SERIAL_NO,
ctypes.byref(serial),
ctypes.sizeof(serial),
)
canGetChannelData(
channel,
canstat.canCHANNELDATA_CHAN_NO_ON_CARD,
ctypes.byref(number),
ctypes.sizeof(number),
)
return {
"device_name": name.value.decode("ascii", errors="replace"),
"serial": serial.value,
"dongle_channel": number.value + 1,
}
init_kvaser_library()
python-can-4.5.0/can/interfaces/kvaser/constants.py 0000664 0000000 0000000 00000015465 14722003266 0022362 0 ustar 00root root 0000000 0000000 """
Contains Python equivalents of the function and constant
definitions in CANLIB's canstat.h, with some supporting functionality
specific to Python.
Copyright (C) 2010 Dynamic Controls
"""
import ctypes
class c_canStatus(ctypes.c_int):
pass
# TODO better formatting
canOK = 0
canERR_PARAM = -1
canERR_NOMSG = -2
canERR_NOTFOUND = -3
canERR_NOMEM = -4
canERR_NOCHANNELS = -5
canERR_RESERVED_3 = -6
canERR_TIMEOUT = -7
canERR_NOTINITIALIZED = -8
canERR_NOHANDLES = -9
canERR_INVHANDLE = -10
canERR_INIFILE = -11
canERR_DRIVER = -12
canERR_TXBUFOFL = -13
canERR_RESERVED_1 = -14
canERR_HARDWARE = -15
canERR_DYNALOAD = -16
canERR_DYNALIB = -17
canERR_DYNAINIT = -18
canERR_NOT_SUPPORTED = -19
canERR_RESERVED_5 = -20
canERR_RESERVED_6 = -21
canERR_RESERVED_2 = -22
canERR_DRIVERLOAD = -23
canERR_DRIVERFAILED = -24
canERR_NOCONFIGMGR = -25
canERR_NOCARD = -26
canERR_RESERVED_7 = -27
canERR_REGISTRY = -28
canERR_LICENSE = -29
canERR_INTERNAL = -30
canERR_NO_ACCESS = -31
canERR_NOT_IMPLEMENTED = -32
canERR__RESERVED = -33
def CANSTATUS_SUCCESS(status):
return status >= canOK
canMSG_MASK = 0x00FF
canMSG_RTR = 0x0001
canMSG_STD = 0x0002
canMSG_EXT = 0x0004
canMSG_WAKEUP = 0x0008
canMSG_NERR = 0x0010
canMSG_ERROR_FRAME = 0x0020
canMSG_TXACK = 0x0040
canMSG_TXRQ = 0x0080
canFDMSG_FDF = 0x010000
canFDMSG_BRS = 0x020000
canFDMSG_ESI = 0x040000
canMSGERR_MASK = 0xFF00
canMSGERR_HW_OVERRUN = 0x0200
canMSGERR_SW_OVERRUN = 0x0400
canMSGERR_STUFF = 0x0800
canMSGERR_FORM = 0x1000
canMSGERR_CRC = 0x2000
canMSGERR_BIT0 = 0x4000
canMSGERR_BIT1 = 0x8000
canMSGERR_OVERRUN = 0x0600
canMSGERR_BIT = 0xC000
canMSGERR_BUSERR = 0xF800
canTRANSCEIVER_LINEMODE_NA = 0
canTRANSCEIVER_LINEMODE_SWC_SLEEP = 4
canTRANSCEIVER_LINEMODE_SWC_NORMAL = 5
canTRANSCEIVER_LINEMODE_SWC_FAST = 6
canTRANSCEIVER_LINEMODE_SWC_WAKEUP = 7
canTRANSCEIVER_LINEMODE_SLEEP = 8
canTRANSCEIVER_LINEMODE_NORMAL = 9
canTRANSCEIVER_LINEMODE_STDBY = 10
canTRANSCEIVER_LINEMODE_TT_CAN_H = 11
canTRANSCEIVER_LINEMODE_TT_CAN_L = 12
canTRANSCEIVER_LINEMODE_OEM1 = 13
canTRANSCEIVER_LINEMODE_OEM2 = 14
canTRANSCEIVER_LINEMODE_OEM3 = 15
canTRANSCEIVER_LINEMODE_OEM4 = 16
canTRANSCEIVER_RESNET_NA = 0
canTRANSCEIVER_RESNET_MASTER = 1
canTRANSCEIVER_RESNET_MASTER_STBY = 2
canTRANSCEIVER_RESNET_SLAVE = 3
canTRANSCEIVER_TYPE_UNKNOWN = 0
canTRANSCEIVER_TYPE_251 = 1
canTRANSCEIVER_TYPE_252 = 2
canTRANSCEIVER_TYPE_DNOPTO = 3
canTRANSCEIVER_TYPE_W210 = 4
canTRANSCEIVER_TYPE_SWC_PROTO = 5
canTRANSCEIVER_TYPE_SWC = 6
canTRANSCEIVER_TYPE_EVA = 7
canTRANSCEIVER_TYPE_FIBER = 8
canTRANSCEIVER_TYPE_K251 = 9
canTRANSCEIVER_TYPE_K = 10
canTRANSCEIVER_TYPE_1054_OPTO = 11
canTRANSCEIVER_TYPE_SWC_OPTO = 12
canTRANSCEIVER_TYPE_TT = 13
canTRANSCEIVER_TYPE_1050 = 14
canTRANSCEIVER_TYPE_1050_OPTO = 15
canTRANSCEIVER_TYPE_1041 = 16
canTRANSCEIVER_TYPE_1041_OPTO = 17
canTRANSCEIVER_TYPE_RS485 = 18
canTRANSCEIVER_TYPE_LIN = 19
canTRANSCEIVER_TYPE_KONE = 20
canTRANSCEIVER_TYPE_LINX_LIN = 64
canTRANSCEIVER_TYPE_LINX_J1708 = 66
canTRANSCEIVER_TYPE_LINX_K = 68
canTRANSCEIVER_TYPE_LINX_SWC = 70
canTRANSCEIVER_TYPE_LINX_LS = 72
canTransceiverTypeStrings = {
canTRANSCEIVER_TYPE_UNKNOWN: "unknown",
canTRANSCEIVER_TYPE_251: "82C251",
canTRANSCEIVER_TYPE_252: "82C252/TJA1053/TJA1054",
canTRANSCEIVER_TYPE_DNOPTO: "Optoisolated 82C251",
canTRANSCEIVER_TYPE_W210: "W210",
canTRANSCEIVER_TYPE_SWC_PROTO: "AU5790 prototype",
canTRANSCEIVER_TYPE_SWC: "AU5790",
canTRANSCEIVER_TYPE_EVA: "EVA",
canTRANSCEIVER_TYPE_FIBER: "82C251 with fibre extension",
canTRANSCEIVER_TYPE_K251: "K251",
canTRANSCEIVER_TYPE_K: "K",
canTRANSCEIVER_TYPE_1054_OPTO: "TJA1054 optical isolation",
canTRANSCEIVER_TYPE_SWC_OPTO: "AU5790 optical isolation",
canTRANSCEIVER_TYPE_TT: "B10011S Truck-And-Trailer",
canTRANSCEIVER_TYPE_1050: "TJA1050",
canTRANSCEIVER_TYPE_1050_OPTO: "TJA1050 optical isolation",
canTRANSCEIVER_TYPE_1041: "TJA1041",
canTRANSCEIVER_TYPE_1041_OPTO: "TJA1041 optical isolation",
canTRANSCEIVER_TYPE_RS485: "RS485",
canTRANSCEIVER_TYPE_LIN: "LIN",
canTRANSCEIVER_TYPE_KONE: "KONE",
canTRANSCEIVER_TYPE_LINX_LIN: "LINX_LIN",
canTRANSCEIVER_TYPE_LINX_J1708: "LINX_J1708",
canTRANSCEIVER_TYPE_LINX_K: "LINX_K",
canTRANSCEIVER_TYPE_LINX_SWC: "LINX_SWC",
canTRANSCEIVER_TYPE_LINX_LS: "LINX_LS",
}
canDRIVER_NORMAL = 4
canDRIVER_SILENT = 1
canDRIVER_SELFRECEPTION = 8
canDRIVER_OFF = 0
canOPEN_EXCLUSIVE = 0x0008
canOPEN_REQUIRE_EXTENDED = 0x0010
canOPEN_ACCEPT_VIRTUAL = 0x0020
canOPEN_OVERRIDE_EXCLUSIVE = 0x0040
canOPEN_REQUIRE_INIT_ACCESS = 0x0080
canOPEN_NO_INIT_ACCESS = 0x0100
canOPEN_ACCEPT_LARGE_DLC = 0x0200
canOPEN_CAN_FD = 0x0400
canOPEN_CAN_FD_NONISO = 0x0800
canIOCTL_GET_RX_BUFFER_LEVEL = 8
canIOCTL_GET_TX_BUFFER_LEVEL = 9
canIOCTL_FLUSH_RX_BUFFER = 10
canIOCTL_FLUSH_TX_BUFFER = 11
canIOCTL_GET_TIMER_SCALE = 12
canIOCTL_SET_TXRQ = 13
canIOCTL_GET_EVENTHANDLE = 14
canIOCTL_SET_BYPASS_MODE = 15
canIOCTL_SET_WAKEUP = 16
canIOCTL_GET_DRIVERHANDLE = 17
canIOCTL_MAP_RXQUEUE = 18
canIOCTL_GET_WAKEUP = 19
canIOCTL_SET_REPORT_ACCESS_ERRORS = 20
canIOCTL_GET_REPORT_ACCESS_ERRORS = 21
canIOCTL_CONNECT_TO_VIRTUAL_BUS = 22
canIOCTL_DISCONNECT_FROM_VIRTUAL_BUS = 23
canIOCTL_SET_USER_IOPORT = 24
canIOCTL_GET_USER_IOPORT = 25
canIOCTL_SET_BUFFER_WRAPAROUND_MODE = 26
canIOCTL_SET_RX_QUEUE_SIZE = 27
canIOCTL_SET_USB_THROTTLE = 28
canIOCTL_GET_USB_THROTTLE = 29
canIOCTL_SET_BUSON_TIME_AUTO_RESET = 30
canIOCTL_SET_LOCAL_TXECHO = 32
canIOCTL_PREFER_EXT = 1
canIOCTL_PREFER_STD = 2
canIOCTL_CLEAR_ERROR_COUNTERS = 5
canIOCTL_SET_TIMER_SCALE = 6
canIOCTL_SET_TXACK = 7
canCHANNELDATA_CHANNEL_CAP = 1
canCHANNELDATA_TRANS_CAP = 2
canCHANNELDATA_CHANNEL_FLAGS = 3
canCHANNELDATA_CARD_TYPE = 4
canCHANNELDATA_CARD_NUMBER = 5
canCHANNELDATA_CHAN_NO_ON_CARD = 6
canCHANNELDATA_CARD_SERIAL_NO = 7
canCHANNELDATA_TRANS_SERIAL_NO = 8
canCHANNELDATA_CARD_FIRMWARE_REV = 9
canCHANNELDATA_CARD_HARDWARE_REV = 10
canCHANNELDATA_CARD_UPC_NO = 11
canCHANNELDATA_TRANS_UPC_NO = 12
canCHANNELDATA_CHANNEL_NAME = 13
canCHANNELDATA_DLL_FILE_VERSION = 14
canCHANNELDATA_DLL_PRODUCT_VERSION = 15
canCHANNELDATA_DLL_FILETYPE = 16
canCHANNELDATA_TRANS_TYPE = 17
canCHANNELDATA_DEVICE_PHYSICAL_POSITION = 18
canCHANNELDATA_UI_NUMBER = 19
canCHANNELDATA_TIMESYNC_ENABLED = 20
canCHANNELDATA_DRIVER_FILE_VERSION = 21
canCHANNELDATA_DRIVER_PRODUCT_VERSION = 22
canCHANNELDATA_MFGNAME_UNICODE = 23
canCHANNELDATA_MFGNAME_ASCII = 24
canCHANNELDATA_DEVDESCR_UNICODE = 25
canCHANNELDATA_DEVDESCR_ASCII = 26
canCHANNELDATA_DRIVER_NAME = 27
kvLED_ACTION_ALL_LEDS_ON = 0
kvLED_ACTION_ALL_LEDS_OFF = 1
canBITRATE_1M = -1
canBITRATE_500K = -2
canBITRATE_250K = -3
canBITRATE_125K = -4
canBITRATE_100K = -5
canBITRATE_62K = -6
canBITRATE_50K = -7
canBITRATE_83K = -8
canBITRATE_10K = -9
canFD_BITRATE_500K_80P = -1000
canFD_BITRATE_1M_80P = -1001
canFD_BITRATE_2M_80P = -1002
canFD_BITRATE_4M_80P = -1003
canFD_BITRATE_8M_60P = -1004
python-can-4.5.0/can/interfaces/kvaser/structures.py 0000664 0000000 0000000 00000003663 14722003266 0022566 0 ustar 00root root 0000000 0000000 """
Contains Python equivalents of the structures in CANLIB's canlib.h,
with some supporting functionality specific to Python.
"""
import ctypes
class BusStatistics(ctypes.Structure):
"""This structure is used with the method
:meth:`~can.interfaces.kvaser.canlib.KvaserBus.get_stats`.
"""
_fields_ = [
("m_stdData", ctypes.c_ulong),
("m_stdRemote", ctypes.c_ulong),
("m_extData", ctypes.c_ulong),
("m_extRemote", ctypes.c_ulong),
("m_errFrame", ctypes.c_ulong),
("m_busLoad", ctypes.c_ulong),
("m_overruns", ctypes.c_ulong),
]
def __str__(self):
return (
f"std_data: {self.std_data}, "
f"std_remote: {self.std_remote}, "
f"ext_data: {self.ext_data}, "
f"ext_remote: {self.ext_remote}, "
f"err_frame: {self.err_frame}, "
f"bus_load: {self.bus_load / 100.0:.1f}%, "
f"overruns: {self.overruns}"
)
@property
def std_data(self):
"""Number of received standard (11-bit identifiers) data frames."""
return self.m_stdData
@property
def std_remote(self):
"""Number of received standard (11-bit identifiers) remote frames."""
return self.m_stdRemote
@property
def ext_data(self):
"""Number of received extended (29-bit identifiers) data frames."""
return self.m_extData
@property
def ext_remote(self):
"""Number of received extended (29-bit identifiers) remote frames."""
return self.m_extRemote
@property
def err_frame(self):
"""Number of error frames."""
return self.m_errFrame
@property
def bus_load(self):
"""The bus load, expressed as an integer in the interval 0 - 10000 representing 0.00% - 100.00% bus load."""
return self.m_busLoad
@property
def overruns(self):
"""Number of overruns."""
return self.m_overruns
python-can-4.5.0/can/interfaces/neousys/ 0000775 0000000 0000000 00000000000 14722003266 0020173 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/neousys/__init__.py 0000664 0000000 0000000 00000000206 14722003266 0022302 0 ustar 00root root 0000000 0000000 """ Neousys CAN bus driver """
__all__ = [
"NeousysBus",
"neousys",
]
from can.interfaces.neousys.neousys import NeousysBus
python-can-4.5.0/can/interfaces/neousys/neousys.py 0000664 0000000 0000000 00000017015 14722003266 0022256 0 ustar 00root root 0000000 0000000 """ Neousys CAN bus driver """
#
# This kind of interface can be found for example on Neousys POC-551VTC
# One needs to have correct drivers and DLL (Share object for Linux) from Neousys
#
# https://www.neousys-tech.com/en/support-service/resources/category/299-poc-551vtc-driver
#
# Beware this is only tested on Linux kernel higher than v5.3. This should be drop in
# with Windows but you have to replace with correct named DLL
#
# pylint: disable=too-few-public-methods
# pylint: disable=too-many-instance-attributes
# pylint: disable=wrong-import-position
import logging
import platform
import queue
from ctypes import (
CFUNCTYPE,
POINTER,
Structure,
byref,
c_ubyte,
c_uint,
c_ushort,
sizeof,
)
from time import time
try:
from ctypes import WinDLL
except ImportError:
from ctypes import CDLL
from can import (
BusABC,
CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
CanProtocol,
Message,
)
logger = logging.getLogger(__name__)
class NeousysCanSetup(Structure):
"""C CAN Setup struct"""
_fields_ = [
("bitRate", c_uint),
("recvConfig", c_uint),
("recvId", c_uint),
("recvMask", c_uint),
]
class NeousysCanMsg(Structure):
"""C CAN Message struct"""
_fields_ = [
("id", c_uint),
("flags", c_ushort),
("extra", c_ubyte),
("len", c_ubyte),
("data", c_ubyte * 8),
]
# valid:2~16, sum of the Synchronization, Propagation, and
# Phase Buffer 1 segments, measured in time quanta.
# valid:1~8, the Phase Buffer 2 segment in time quanta.
# valid:1~4, Resynchronization Jump Width in time quanta
# valid:1~1023, CAN_CLK divider used to determine time quanta
class NeousysCanBitClk(Structure):
"""C CAN BIT Clock struct"""
_fields_ = [
("syncPropPhase1Seg", c_ushort),
("phase2Seg", c_ushort),
("jumpWidth", c_ushort),
("quantumPrescaler", c_ushort),
]
NEOUSYS_CAN_MSG_CALLBACK = CFUNCTYPE(None, POINTER(NeousysCanMsg), c_uint)
NEOUSYS_CAN_STATUS_CALLBACK = CFUNCTYPE(None, c_uint)
NEOUSYS_CAN_MSG_EXTENDED_ID = 0x0004
NEOUSYS_CAN_MSG_REMOTE_FRAME = 0x0040
NEOUSYS_CAN_MSG_DATA_NEW = 0x0080
NEOUSYS_CAN_MSG_DATA_LOST = 0x0100
NEOUSYS_CAN_MSG_USE_ID_FILTER = 0x00000008
NEOUSYS_CAN_MSG_USE_DIR_FILTER = (
0x00000010 | NEOUSYS_CAN_MSG_USE_ID_FILTER
) # only accept the direction specified in the message type
NEOUSYS_CAN_MSG_USE_EXT_FILTER = (
0x00000020 | NEOUSYS_CAN_MSG_USE_ID_FILTER
) # filters on only extended identifiers
NEOUSYS_CAN_STATUS_BUS_OFF = 0x00000080
NEOUSYS_CAN_STATUS_EWARN = (
0x00000040 # can controller error level has reached warning level.
)
NEOUSYS_CAN_STATUS_EPASS = (
0x00000020 # can controller error level has reached error passive level.
)
NEOUSYS_CAN_STATUS_LEC_STUFF = 0x00000001 # a bit stuffing error has occurred.
NEOUSYS_CAN_STATUS_LEC_FORM = 0x00000002 # a formatting error has occurred.
NEOUSYS_CAN_STATUS_LEC_ACK = 0x00000003 # an acknowledge error has occurred.
NEOUSYS_CAN_STATUS_LEC_BIT1 = (
0x00000004 # the bus remained a bit level of 1 for longer than is allowed.
)
NEOUSYS_CAN_STATUS_LEC_BIT0 = (
0x00000005 # the bus remained a bit level of 0 for longer than is allowed.
)
NEOUSYS_CAN_STATUS_LEC_CRC = 0x00000006 # a crc error has occurred.
NEOUSYS_CAN_STATUS_LEC_MASK = (
0x00000007 # this is the mask for the can last error code (lec).
)
NEOUSYS_CANLIB = None
try:
if platform.system() == "Windows":
NEOUSYS_CANLIB = WinDLL("./WDT_DIO.dll")
else:
NEOUSYS_CANLIB = CDLL("libwdt_dio.so")
logger.info("Loaded Neousys WDT_DIO Can driver")
except OSError as error:
logger.info("Cannot load Neousys CAN bus dll or shared object: %s", error)
class NeousysBus(BusABC):
"""Neousys CAN bus Class"""
def __init__(self, channel, device=0, bitrate=500000, **kwargs):
"""
:param channel: channel number
:param device: device number
:param bitrate: bit rate.
"""
super().__init__(channel, **kwargs)
if NEOUSYS_CANLIB is None:
raise CanInterfaceNotImplementedError("Neousys WDT_DIO Can driver missing")
self.channel = channel
self.device = device
self.channel_info = f"Neousys Can: device {self.device}, channel {self.channel}"
self._can_protocol = CanProtocol.CAN_20
self.queue = queue.Queue()
# Init with accept all and wanted bitrate
self.init_config = NeousysCanSetup(bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0)
self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb)
self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK(self._neousys_status_cb)
if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0:
raise CanInitializationError("Neousys CAN bus Setup receive callback")
if NEOUSYS_CANLIB.CAN_RegisterStatus(0, self._neousys_status_cb) == 0:
raise CanInitializationError("Neousys CAN bus Setup status callback")
if (
NEOUSYS_CANLIB.CAN_Setup(
channel, byref(self.init_config), sizeof(self.init_config)
)
== 0
):
raise CanInitializationError("Neousys CAN bus Setup Error")
if NEOUSYS_CANLIB.CAN_Start(channel) == 0:
raise CanInitializationError("Neousys CAN bus Start Error")
def send(self, msg, timeout=None) -> None:
"""
:param msg: message to send
:param timeout: timeout is not used here
"""
tx_msg = NeousysCanMsg(
msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data)
)
if NEOUSYS_CANLIB.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) == 0:
raise CanOperationError("Neousys Can can't send message")
def _recv_internal(self, timeout):
try:
return self.queue.get(block=True, timeout=timeout), False
except queue.Empty:
return None, False
def _neousys_recv_cb(self, msg, sizeof_msg) -> None:
"""
:param msg: struct CAN_MSG
:param sizeof_msg: message number
"""
msg_bytes = bytearray(msg.contents.data)
remote_frame = bool(msg.contents.flags & NEOUSYS_CAN_MSG_REMOTE_FRAME)
extended_frame = bool(msg.contents.flags & NEOUSYS_CAN_MSG_EXTENDED_ID)
if msg.contents.flags & NEOUSYS_CAN_MSG_DATA_LOST:
logger.error("_neousys_recv_cb flag CAN_MSG_DATA_LOST")
msg = Message(
timestamp=time(),
arbitration_id=msg.contents.id,
is_remote_frame=remote_frame,
is_extended_id=extended_frame,
channel=self.channel,
dlc=msg.contents.len,
data=msg_bytes[: msg.contents.len],
)
# Reading happens in Callback function and
# with Python-CAN it happens polling
# so cache stuff in array to for poll
try:
self.queue.put(msg)
except queue.Full:
raise CanOperationError("Neousys message Queue is full") from None
def _neousys_status_cb(self, status) -> None:
"""
:param status: BUS Status
"""
logger.info("%s _neousys_status_cb: %d", self.init_config, status)
def shutdown(self):
super().shutdown()
NEOUSYS_CANLIB.CAN_Stop(self.channel)
@staticmethod
def _detect_available_configs():
if NEOUSYS_CANLIB is None:
return []
else:
# There is only one channel
return [{"interface": "neousys", "channel": 0}]
python-can-4.5.0/can/interfaces/nican.py 0000664 0000000 0000000 00000026440 14722003266 0020136 0 ustar 00root root 0000000 0000000 """
NI-CAN interface module.
Implementation references:
* http://www.ni.com/pdf/manuals/370289c.pdf
* https://github.com/buendiya/NicanPython
TODO: We could implement this interface such that setting other filters
could work when the initial filters were set to zero using the
software fallback. Or could the software filters even be changed
after the connection was opened? We need to document that behaviour!
See also the IXXAT interface.
"""
import ctypes
import logging
import sys
from typing import Optional, Tuple, Type
import can.typechecking
from can import (
BusABC,
CanError,
CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
CanProtocol,
Message,
)
logger = logging.getLogger(__name__)
NC_SUCCESS = 0
NC_ERR_TIMEOUT = 1
TIMEOUT_ERROR_CODE = -1074388991
NC_DURATION_INFINITE = 0xFFFFFFFF
NC_OP_START = 0x80000001
NC_OP_STOP = 0x80000002
NC_OP_RESET = 0x80000003
NC_FRMTYPE_REMOTE = 1
NC_FRMTYPE_COMM_ERR = 2
NC_ST_READ_AVAIL = 0x00000001
NC_ST_WRITE_SUCCESS = 0x00000002
NC_ST_ERROR = 0x00000010
NC_ST_WARNING = 0x00000020
NC_ATTR_BAUD_RATE = 0x80000007
NC_ATTR_START_ON_OPEN = 0x80000006
NC_ATTR_READ_Q_LEN = 0x80000013
NC_ATTR_WRITE_Q_LEN = 0x80000014
NC_ATTR_CAN_COMP_STD = 0x80010001
NC_ATTR_CAN_MASK_STD = 0x80010002
NC_ATTR_CAN_COMP_XTD = 0x80010003
NC_ATTR_CAN_MASK_XTD = 0x80010004
NC_ATTR_LOG_COMM_ERRS = 0x8001000A
NC_FL_CAN_ARBID_XTD = 0x20000000
CanData = ctypes.c_ubyte * 8
class RxMessageStruct(ctypes.Structure):
_pack_ = 1
_fields_ = [
("timestamp", ctypes.c_ulonglong),
("arb_id", ctypes.c_ulong),
("frame_type", ctypes.c_ubyte),
("dlc", ctypes.c_ubyte),
("data", CanData),
]
class TxMessageStruct(ctypes.Structure):
_fields_ = [
("arb_id", ctypes.c_ulong),
("is_remote", ctypes.c_ubyte),
("dlc", ctypes.c_ubyte),
("data", CanData),
]
class NicanError(CanError):
"""Error from NI-CAN driver."""
def __init__(self, function, error_code: int, arguments) -> None:
super().__init__(
message=f"{function} failed: {get_error_message(error_code)}",
error_code=error_code,
)
#: Function that failed
self.function = function
#: Arguments passed to function
self.arguments = arguments
class NicanInitializationError(NicanError, CanInitializationError):
pass
class NicanOperationError(NicanError, CanOperationError):
pass
def check_status(
result: int,
function,
arguments,
error_class: Type[NicanError] = NicanOperationError,
) -> int:
if result > 0:
logger.warning(get_error_message(result))
elif result < 0:
raise error_class(function, result, arguments)
return result
def check_status_init(*args, **kwargs) -> int:
return check_status(*args, **kwargs, error_class=NicanInitializationError)
def get_error_message(status_code: int) -> str:
"""Convert status code to descriptive string."""
errmsg = ctypes.create_string_buffer(1024)
nican.ncStatusToString(status_code, len(errmsg), errmsg)
return errmsg.value.decode("ascii")
if sys.platform == "win32":
try:
nican = ctypes.windll.LoadLibrary("nican")
except Exception as e:
nican = None
logger.error("Failed to load NI-CAN driver: %s", e)
else:
nican.ncConfig.argtypes = [
ctypes.c_char_p,
ctypes.c_ulong,
ctypes.c_void_p,
ctypes.c_void_p,
]
nican.ncConfig.errcheck = check_status_init
nican.ncOpenObject.argtypes = [ctypes.c_char_p, ctypes.c_void_p]
nican.ncOpenObject.errcheck = check_status_init
nican.ncCloseObject.errcheck = check_status
nican.ncAction.argtypes = [ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong]
nican.ncAction.errcheck = check_status
nican.ncRead.errcheck = check_status
nican.ncWrite.errcheck = check_status
nican.ncWaitForState.argtypes = [
ctypes.c_ulong,
ctypes.c_ulong,
ctypes.c_ulong,
ctypes.c_void_p,
]
nican.ncWaitForState.errcheck = check_status
nican.ncStatusToString.argtypes = [ctypes.c_int, ctypes.c_uint, ctypes.c_char_p]
else:
nican = None
logger.warning("NI-CAN interface is only available on Windows systems")
class NicanBus(BusABC):
"""
The CAN Bus implemented for the NI-CAN interface.
.. warning::
This interface does implement efficient filtering of messages, but
the filters have to be set in ``__init__`` using the ``can_filters`` parameter.
Using :meth:`~can.BusABC.set_filters` does not work.
"""
def __init__(
self,
channel: str,
can_filters: Optional[can.typechecking.CanFilters] = None,
bitrate: Optional[int] = None,
log_errors: bool = True,
**kwargs,
) -> None:
"""
:param channel:
Name of the object to open (e.g. `"CAN0"`)
:param bitrate:
Bitrate in bit/s
:param can_filters:
See :meth:`can.BusABC.set_filters`.
:param log_errors:
If True, communication errors will appear as CAN messages with
``is_error_frame`` set to True and ``arbitration_id`` will identify
the error (default True)
:raise ~can.exceptions.CanInterfaceNotImplementedError:
If the current operating system is not supported or the driver could not be loaded.
:raise ~can.interfaces.nican.NicanInitializationError:
If the bus could not be set up.
"""
if nican is None:
raise CanInterfaceNotImplementedError(
"The NI-CAN driver could not be loaded. "
"Check that you are using 32-bit Python on Windows."
)
self.channel = channel
self.channel_info = f"NI-CAN: {channel}"
self._can_protocol = CanProtocol.CAN_20
channel_bytes = channel.encode("ascii")
config = [(NC_ATTR_START_ON_OPEN, True), (NC_ATTR_LOG_COMM_ERRS, log_errors)]
if not can_filters:
logger.info("Filtering has been disabled")
config.extend(
[
(NC_ATTR_CAN_COMP_STD, 0),
(NC_ATTR_CAN_MASK_STD, 0),
(NC_ATTR_CAN_COMP_XTD, 0),
(NC_ATTR_CAN_MASK_XTD, 0),
]
)
else:
for can_filter in can_filters:
can_id = can_filter["can_id"]
can_mask = can_filter["can_mask"]
logger.info("Filtering on ID 0x%X, mask 0x%X", can_id, can_mask)
if can_filter.get("extended"):
config.extend(
[
(NC_ATTR_CAN_COMP_XTD, can_id | NC_FL_CAN_ARBID_XTD),
(NC_ATTR_CAN_MASK_XTD, can_mask),
]
)
else:
config.extend(
[
(NC_ATTR_CAN_COMP_STD, can_id),
(NC_ATTR_CAN_MASK_STD, can_mask),
]
)
if bitrate:
config.append((NC_ATTR_BAUD_RATE, bitrate))
AttrList = ctypes.c_ulong * len(config)
attr_id_list = AttrList(*(row[0] for row in config))
attr_value_list = AttrList(*(row[1] for row in config))
nican.ncConfig(
channel_bytes,
len(config),
ctypes.byref(attr_id_list),
ctypes.byref(attr_value_list),
)
self.handle = ctypes.c_ulong()
nican.ncOpenObject(channel_bytes, ctypes.byref(self.handle))
super().__init__(
channel=channel,
can_filters=can_filters,
bitrate=bitrate,
log_errors=log_errors,
**kwargs,
)
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
"""
Read a message from a NI-CAN bus.
:param timeout:
Max time to wait in seconds or ``None`` if infinite
:raises can.interfaces.nican.NicanOperationError:
If reception fails
"""
if timeout is None:
timeout = NC_DURATION_INFINITE
else:
timeout = int(timeout * 1000)
state = ctypes.c_ulong()
try:
nican.ncWaitForState(
self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state)
)
except NicanError as e:
if e.error_code == TIMEOUT_ERROR_CODE:
return None, True
else:
raise
raw_msg = RxMessageStruct()
nican.ncRead(self.handle, ctypes.sizeof(raw_msg), ctypes.byref(raw_msg))
# http://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux
timestamp = raw_msg.timestamp / 10000000.0 - 11644473600
is_remote_frame = raw_msg.frame_type == NC_FRMTYPE_REMOTE
is_error_frame = raw_msg.frame_type == NC_FRMTYPE_COMM_ERR
is_extended = bool(raw_msg.arb_id & NC_FL_CAN_ARBID_XTD)
arb_id = raw_msg.arb_id
if not is_error_frame:
arb_id &= 0x1FFFFFFF
dlc = raw_msg.dlc
msg = Message(
timestamp=timestamp,
channel=self.channel,
is_remote_frame=is_remote_frame,
is_error_frame=is_error_frame,
is_extended_id=is_extended,
arbitration_id=arb_id,
dlc=dlc,
data=raw_msg.data[:dlc],
)
return msg, True
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""
Send a message to NI-CAN.
:param msg:
Message to send
:param timeout:
The timeout
.. warning:: This gets ignored.
:raises can.interfaces.nican.NicanOperationError:
If writing to transmit buffer fails.
It does not wait for message to be ACKed currently.
"""
arb_id = msg.arbitration_id
if msg.is_extended_id:
arb_id |= NC_FL_CAN_ARBID_XTD
raw_msg = TxMessageStruct(
arb_id, bool(msg.is_remote_frame), msg.dlc, CanData(*msg.data)
)
nican.ncWrite(self.handle, ctypes.sizeof(raw_msg), ctypes.byref(raw_msg))
# TODO:
# ncWaitForState can not be called here if the recv() method is called
# from a different thread, which is a very common use case.
# Maybe it is possible to use ncCreateNotification instead but seems a
# bit overkill at the moment.
# state = ctypes.c_ulong()
# nican.ncWaitForState(self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state))
def reset(self) -> None:
"""
Resets network interface. Stops network interface, then resets the CAN
chip to clear the CAN error counters (clear error passive state).
Resetting includes clearing all entries from read and write queues.
:raises can.interfaces.nican.NicanOperationError:
If resetting fails.
"""
nican.ncAction(self.handle, NC_OP_RESET, 0)
def shutdown(self) -> None:
"""Close object."""
super().shutdown()
nican.ncCloseObject(self.handle)
python-can-4.5.0/can/interfaces/nixnet.py 0000664 0000000 0000000 00000031573 14722003266 0020356 0 ustar 00root root 0000000 0000000 """
NI-XNET interface module.
Implementation references:
NI-XNET Hardware and Software Manual: https://www.ni.com/pdf/manuals/372840h.pdf
NI-XNET Python implementation: https://github.com/ni/nixnet-python
Authors: Javier Rubio Giménez , Jose A. Escobar
"""
import logging
import os
import time
import warnings
from queue import SimpleQueue
from types import ModuleType
from typing import Any, List, Optional, Tuple, Union
import can.typechecking
from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message
from can.exceptions import (
CanInitializationError,
CanInterfaceNotImplementedError,
CanOperationError,
)
from can.util import check_or_adjust_timing_clock, deprecated_args_alias
logger = logging.getLogger(__name__)
nixnet: Optional[ModuleType] = None
try:
import nixnet # type: ignore
import nixnet.constants # type: ignore
import nixnet.system # type: ignore
import nixnet.types # type: ignore
except Exception as exc:
logger.warning("Could not import nixnet: %s", exc)
class NiXNETcanBus(BusABC):
"""
The CAN Bus implemented for the NI-XNET interface.
"""
@deprecated_args_alias(
deprecation_start="4.2.0",
deprecation_end="5.0.0",
brs=None,
log_errors=None,
)
def __init__(
self,
channel: str = "CAN1",
bitrate: int = 500_000,
timing: Optional[Union[BitTiming, BitTimingFd]] = None,
can_filters: Optional[can.typechecking.CanFilters] = None,
receive_own_messages: bool = False,
can_termination: bool = False,
fd: bool = False,
fd_bitrate: Optional[int] = None,
poll_interval: float = 0.001,
**kwargs: Any,
) -> None:
"""
:param str channel:
Name of the object to open (e.g. 'CAN0')
:param int bitrate:
Bitrate in bits/s
:param timing:
Optional :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance
to use for custom bit timing setting. The `f_clock` value of the timing
instance must be set to 40_000_000 (40MHz).
If this parameter is provided, it takes precedence over all other
timing-related parameters like `bitrate`, `fd_bitrate` and `fd`.
:param list can_filters:
See :meth:`can.BusABC.set_filters`.
:param receive_own_messages:
Enable self-reception of sent messages.
:param poll_interval:
Poll interval in seconds.
:raises ~can.exceptions.CanInitializationError:
If starting communication fails
"""
if os.name != "nt" and not kwargs.get("_testing", False):
raise CanInterfaceNotImplementedError(
f"The NI-XNET interface is only supported on Windows, "
f'but you are running "{os.name}"'
)
if nixnet is None:
raise CanInterfaceNotImplementedError("The NI-XNET API has not been loaded")
self.nixnet = nixnet
self._rx_queue = SimpleQueue() # type: ignore[var-annotated]
self.channel = channel
self.channel_info = "NI-XNET: " + channel
self.poll_interval = poll_interval
is_fd = isinstance(timing, BitTimingFd) if timing else fd
self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20
# Set database for the initialization
database_name = ":can_fd_brs:" if is_fd else ":memory:"
try:
# We need two sessions for this application,
# one to send frames and another to receive them
self._session_send = nixnet.session.FrameOutStreamSession(
channel, database_name=database_name
)
self._session_receive = nixnet.session.FrameInStreamSession(
channel, database_name=database_name
)
self._interface = self._session_send.intf
# set interface properties
self._interface.can_lstn_only = kwargs.get("listen_only", False)
self._interface.echo_tx = receive_own_messages
self._interface.bus_err_to_in_strm = True
if isinstance(timing, BitTimingFd):
timing = check_or_adjust_timing_clock(timing, [40_000_000])
custom_nom_baud_rate = ( # nxPropSession_IntfBaudRate64
0xA0000000
+ (timing.nom_tq << 32)
+ (timing.nom_sjw - 1 << 16)
+ (timing.nom_tseg1 - 1 << 8)
+ (timing.nom_tseg2 - 1)
)
custom_data_baud_rate = ( # nxPropSession_IntfCanFdBaudRate64
0xA0000000
+ (timing.data_tq << 13)
+ (timing.data_tseg1 - 1 << 8)
+ (timing.data_tseg2 - 1 << 4)
+ (timing.data_sjw - 1)
)
self._interface.baud_rate = custom_nom_baud_rate
self._interface.can_fd_baud_rate = custom_data_baud_rate
elif isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, [40_000_000])
custom_baud_rate = ( # nxPropSession_IntfBaudRate64
0xA0000000
+ (timing.tq << 32)
+ (timing.sjw - 1 << 16)
+ (timing.tseg1 - 1 << 8)
+ (timing.tseg2 - 1)
)
self._interface.baud_rate = custom_baud_rate
else:
# See page 1017 of NI-XNET Hardware and Software Manual
# to set custom can configuration
if bitrate:
self._interface.baud_rate = bitrate
if is_fd:
# See page 951 of NI-XNET Hardware and Software Manual
# to set custom can configuration
self._interface.can_fd_baud_rate = fd_bitrate or bitrate
_can_termination = (
nixnet.constants.CanTerm.ON
if can_termination
else nixnet.constants.CanTerm.OFF
)
self._interface.can_term = _can_termination
# self._session_receive.queue_size = 512
# Once that all the parameters have been set, we start the sessions
self._session_send.start()
self._session_receive.start()
except nixnet.errors.XnetError as error:
raise CanInitializationError(
f"{error.args[0]} ({error.error_type})", error.error_code
) from None
self._is_filtered = False
super().__init__(
channel=channel,
can_filters=can_filters,
bitrate=bitrate,
**kwargs,
)
@property
def fd(self) -> bool:
class_name = self.__class__.__name__
warnings.warn(
f"The {class_name}.fd property is deprecated and superseded by "
f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.",
DeprecationWarning,
stacklevel=2,
)
return self._can_protocol is CanProtocol.CAN_FD
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
end_time = time.perf_counter() + timeout if timeout is not None else None
while True:
# try to read all available frames
for frame in self._session_receive.frames.read(1024, timeout=0):
self._rx_queue.put_nowait(frame)
if self._rx_queue.qsize():
break
# check for timeout
if end_time is not None and time.perf_counter() > end_time:
return None, False
# Wait a short time until we try to read again
time.sleep(self.poll_interval)
can_frame = self._rx_queue.get_nowait()
# Timestamp should be converted from raw frame format(100ns increment
# from(12:00 a.m. January 1 1601 Coordinated Universal Time (UTC))
# to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT))
timestamp = can_frame.timestamp * 1e-7 - 11_644_473_600
if can_frame.type is self.nixnet.constants.FrameType.CAN_BUS_ERROR:
msg = Message(
timestamp=timestamp,
channel=self.channel,
is_error_frame=True,
)
else:
msg = Message(
timestamp=timestamp,
channel=self.channel,
is_remote_frame=can_frame.type
is self.nixnet.constants.FrameType.CAN_REMOTE,
is_error_frame=False,
is_fd=(
can_frame.type is self.nixnet.constants.FrameType.CANFD_DATA
or can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA
),
bitrate_switch=(
can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA
),
is_extended_id=can_frame.identifier.extended,
# Get identifier from CanIdentifier structure
arbitration_id=can_frame.identifier.identifier,
dlc=len(can_frame.payload),
data=can_frame.payload,
is_rx=not can_frame.echo,
)
return msg, False
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
"""
Send a message using NI-XNET.
:param can.Message msg:
Message to send
:param float timeout:
Max time to wait for the device to be ready in seconds, None if time is infinite
:raises can.exceptions.CanOperationError:
If writing to transmit buffer fails.
It does not wait for message to be ACKed currently.
"""
if timeout is None:
timeout = self.nixnet.constants.TIMEOUT_INFINITE
if msg.is_remote_frame:
type_message = self.nixnet.constants.FrameType.CAN_REMOTE
elif msg.is_error_frame:
type_message = self.nixnet.constants.FrameType.CAN_BUS_ERROR
elif msg.is_fd:
if msg.bitrate_switch:
type_message = self.nixnet.constants.FrameType.CANFDBRS_DATA
else:
type_message = self.nixnet.constants.FrameType.CANFD_DATA
else:
type_message = self.nixnet.constants.FrameType.CAN_DATA
can_frame = self.nixnet.types.CanFrame(
self.nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id),
type=type_message,
payload=msg.data,
)
try:
self._session_send.frames.write([can_frame], timeout)
except self.nixnet.errors.XnetError as error:
raise CanOperationError(
f"{error.args[0]} ({error.error_type})", error.error_code
) from None
def reset(self) -> None:
"""
Resets network interface. Stops network interface, then resets the CAN
chip to clear the CAN error counters (clear error passive state).
Resetting includes clearing all entries from read and write queues.
"""
self._session_send.flush()
self._session_receive.flush()
self._session_send.stop()
self._session_receive.stop()
self._session_send.start()
self._session_receive.start()
def shutdown(self) -> None:
"""Close object."""
super().shutdown()
if hasattr(self, "_session_send"):
self._session_send.flush()
self._session_send.stop()
self._session_send.close()
if hasattr(self, "_session_receive"):
self._session_receive.flush()
self._session_receive.stop()
self._session_receive.close()
@staticmethod
def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]:
configs = []
try:
with nixnet.system.System() as nixnet_system: # type: ignore[union-attr]
for interface in nixnet_system.intf_refs_can:
channel = str(interface)
logger.debug(
"Found channel index %d: %s", interface.port_num, channel
)
configs.append(
{
"interface": "nixnet",
"channel": channel,
"can_term_available": interface.can_term_cap
is nixnet.constants.CanTermCap.YES, # type: ignore[union-attr]
"supports_fd": interface.can_tcvr_cap
is nixnet.constants.CanTcvrCap.HS, # type: ignore[union-attr]
}
)
except Exception as error:
logger.debug("An error occured while searching for configs: %s", str(error))
return configs # type: ignore
python-can-4.5.0/can/interfaces/pcan/ 0000775 0000000 0000000 00000000000 14722003266 0017407 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/pcan/__init__.py 0000664 0000000 0000000 00000000211 14722003266 0021512 0 ustar 00root root 0000000 0000000 """
"""
__all__ = [
"PcanBus",
"PcanError",
"basic",
"pcan",
]
from can.interfaces.pcan.pcan import PcanBus, PcanError
python-can-4.5.0/can/interfaces/pcan/basic.py 0000664 0000000 0000000 00000120506 14722003266 0021046 0 ustar 00root root 0000000 0000000 # PCANBasic.py
#
# ~~~~~~~~~~~~
#
# PCAN-Basic API
#
# ~~~~~~~~~~~~
#
# ------------------------------------------------------------------
# Author : Keneth Wagner
# Last change: 2022-07-06
# ------------------------------------------------------------------
#
# Copyright (C) 1999-2022 PEAK-System Technik GmbH, Darmstadt
# more Info at http://www.peak-system.com
# Module Imports
import logging
import platform
from ctypes import *
from ctypes.util import find_library
PLATFORM = platform.system()
IS_WINDOWS = PLATFORM == "Windows"
IS_LINUX = PLATFORM == "Linux"
logger = logging.getLogger("can.pcan")
# ///////////////////////////////////////////////////////////
# Type definitions
# ///////////////////////////////////////////////////////////
TPCANHandle = c_ushort # Represents a PCAN hardware channel handle
TPCANStatus = int # Represents a PCAN status/error code
TPCANParameter = c_ubyte # Represents a PCAN parameter to be read or set
TPCANDevice = c_ubyte # Represents a PCAN device
TPCANMessageType = c_ubyte # Represents the type of a PCAN message
TPCANType = c_ubyte # Represents the type of PCAN hardware to be initialized
TPCANMode = c_ubyte # Represents a PCAN filter mode
TPCANBaudrate = c_ushort # Represents a PCAN Baud rate register value
TPCANBitrateFD = c_char_p # Represents a PCAN-FD bit rate string
TPCANTimestampFD = c_ulonglong # Represents a timestamp of a received PCAN FD message
# ///////////////////////////////////////////////////////////
# Value definitions
# ///////////////////////////////////////////////////////////
# Currently defined and supported PCAN channels
#
PCAN_NONEBUS = TPCANHandle(0x00) # Undefined/default value for a PCAN bus
PCAN_ISABUS1 = TPCANHandle(0x21) # PCAN-ISA interface, channel 1
PCAN_ISABUS2 = TPCANHandle(0x22) # PCAN-ISA interface, channel 2
PCAN_ISABUS3 = TPCANHandle(0x23) # PCAN-ISA interface, channel 3
PCAN_ISABUS4 = TPCANHandle(0x24) # PCAN-ISA interface, channel 4
PCAN_ISABUS5 = TPCANHandle(0x25) # PCAN-ISA interface, channel 5
PCAN_ISABUS6 = TPCANHandle(0x26) # PCAN-ISA interface, channel 6
PCAN_ISABUS7 = TPCANHandle(0x27) # PCAN-ISA interface, channel 7
PCAN_ISABUS8 = TPCANHandle(0x28) # PCAN-ISA interface, channel 8
PCAN_DNGBUS1 = TPCANHandle(0x31) # PCAN-Dongle/LPT interface, channel 1
PCAN_PCIBUS1 = TPCANHandle(0x41) # PCAN-PCI interface, channel 1
PCAN_PCIBUS2 = TPCANHandle(0x42) # PCAN-PCI interface, channel 2
PCAN_PCIBUS3 = TPCANHandle(0x43) # PCAN-PCI interface, channel 3
PCAN_PCIBUS4 = TPCANHandle(0x44) # PCAN-PCI interface, channel 4
PCAN_PCIBUS5 = TPCANHandle(0x45) # PCAN-PCI interface, channel 5
PCAN_PCIBUS6 = TPCANHandle(0x46) # PCAN-PCI interface, channel 6
PCAN_PCIBUS7 = TPCANHandle(0x47) # PCAN-PCI interface, channel 7
PCAN_PCIBUS8 = TPCANHandle(0x48) # PCAN-PCI interface, channel 8
PCAN_PCIBUS9 = TPCANHandle(0x409) # PCAN-PCI interface, channel 9
PCAN_PCIBUS10 = TPCANHandle(0x40A) # PCAN-PCI interface, channel 10
PCAN_PCIBUS11 = TPCANHandle(0x40B) # PCAN-PCI interface, channel 11
PCAN_PCIBUS12 = TPCANHandle(0x40C) # PCAN-PCI interface, channel 12
PCAN_PCIBUS13 = TPCANHandle(0x40D) # PCAN-PCI interface, channel 13
PCAN_PCIBUS14 = TPCANHandle(0x40E) # PCAN-PCI interface, channel 14
PCAN_PCIBUS15 = TPCANHandle(0x40F) # PCAN-PCI interface, channel 15
PCAN_PCIBUS16 = TPCANHandle(0x410) # PCAN-PCI interface, channel 16
PCAN_USBBUS1 = TPCANHandle(0x51) # PCAN-USB interface, channel 1
PCAN_USBBUS2 = TPCANHandle(0x52) # PCAN-USB interface, channel 2
PCAN_USBBUS3 = TPCANHandle(0x53) # PCAN-USB interface, channel 3
PCAN_USBBUS4 = TPCANHandle(0x54) # PCAN-USB interface, channel 4
PCAN_USBBUS5 = TPCANHandle(0x55) # PCAN-USB interface, channel 5
PCAN_USBBUS6 = TPCANHandle(0x56) # PCAN-USB interface, channel 6
PCAN_USBBUS7 = TPCANHandle(0x57) # PCAN-USB interface, channel 7
PCAN_USBBUS8 = TPCANHandle(0x58) # PCAN-USB interface, channel 8
PCAN_USBBUS9 = TPCANHandle(0x509) # PCAN-USB interface, channel 9
PCAN_USBBUS10 = TPCANHandle(0x50A) # PCAN-USB interface, channel 10
PCAN_USBBUS11 = TPCANHandle(0x50B) # PCAN-USB interface, channel 11
PCAN_USBBUS12 = TPCANHandle(0x50C) # PCAN-USB interface, channel 12
PCAN_USBBUS13 = TPCANHandle(0x50D) # PCAN-USB interface, channel 13
PCAN_USBBUS14 = TPCANHandle(0x50E) # PCAN-USB interface, channel 14
PCAN_USBBUS15 = TPCANHandle(0x50F) # PCAN-USB interface, channel 15
PCAN_USBBUS16 = TPCANHandle(0x510) # PCAN-USB interface, channel 16
PCAN_PCCBUS1 = TPCANHandle(0x61) # PCAN-PC Card interface, channel 1
PCAN_PCCBUS2 = TPCANHandle(0x62) # PCAN-PC Card interface, channel 2
PCAN_LANBUS1 = TPCANHandle(0x801) # PCAN-LAN interface, channel 1
PCAN_LANBUS2 = TPCANHandle(0x802) # PCAN-LAN interface, channel 2
PCAN_LANBUS3 = TPCANHandle(0x803) # PCAN-LAN interface, channel 3
PCAN_LANBUS4 = TPCANHandle(0x804) # PCAN-LAN interface, channel 4
PCAN_LANBUS5 = TPCANHandle(0x805) # PCAN-LAN interface, channel 5
PCAN_LANBUS6 = TPCANHandle(0x806) # PCAN-LAN interface, channel 6
PCAN_LANBUS7 = TPCANHandle(0x807) # PCAN-LAN interface, channel 7
PCAN_LANBUS8 = TPCANHandle(0x808) # PCAN-LAN interface, channel 8
PCAN_LANBUS9 = TPCANHandle(0x809) # PCAN-LAN interface, channel 9
PCAN_LANBUS10 = TPCANHandle(0x80A) # PCAN-LAN interface, channel 10
PCAN_LANBUS11 = TPCANHandle(0x80B) # PCAN-LAN interface, channel 11
PCAN_LANBUS12 = TPCANHandle(0x80C) # PCAN-LAN interface, channel 12
PCAN_LANBUS13 = TPCANHandle(0x80D) # PCAN-LAN interface, channel 13
PCAN_LANBUS14 = TPCANHandle(0x80E) # PCAN-LAN interface, channel 14
PCAN_LANBUS15 = TPCANHandle(0x80F) # PCAN-LAN interface, channel 15
PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16
# Represent the PCAN error and status codes
#
PCAN_ERROR_OK = TPCANStatus(0x00000) # No error
PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full
PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late
PCAN_ERROR_BUSLIGHT = TPCANStatus(
0x00004
) # Bus error: an error counter reached the 'light' limit
PCAN_ERROR_BUSHEAVY = TPCANStatus(
0x00008
) # Bus error: an error counter reached the 'heavy' limit
PCAN_ERROR_BUSWARNING = TPCANStatus(
PCAN_ERROR_BUSHEAVY
) # Bus error: an error counter reached the 'warning' limit
PCAN_ERROR_BUSPASSIVE = TPCANStatus(
0x40000
) # Bus error: the CAN controller is error passive
PCAN_ERROR_BUSOFF = TPCANStatus(
0x00010
) # Bus error: the CAN controller is in bus-off state
PCAN_ERROR_ANYBUSERR = TPCANStatus(
PCAN_ERROR_BUSWARNING
| PCAN_ERROR_BUSLIGHT
| PCAN_ERROR_BUSHEAVY
| PCAN_ERROR_BUSOFF
| PCAN_ERROR_BUSPASSIVE
) # Mask for all bus errors
PCAN_ERROR_QRCVEMPTY = TPCANStatus(0x00020) # Receive queue is empty
PCAN_ERROR_QOVERRUN = TPCANStatus(0x00040) # Receive queue was read too late
PCAN_ERROR_QXMTFULL = TPCANStatus(0x00080) # Transmit queue is full
PCAN_ERROR_REGTEST = TPCANStatus(
0x00100
) # Test of the CAN controller hardware registers failed (no hardware found)
PCAN_ERROR_NODRIVER = TPCANStatus(0x00200) # Driver not loaded
PCAN_ERROR_HWINUSE = TPCANStatus(0x00400) # Hardware already in use by a Net
PCAN_ERROR_NETINUSE = TPCANStatus(0x00800) # A Client is already connected to the Net
PCAN_ERROR_ILLHW = TPCANStatus(0x01400) # Hardware handle is invalid
PCAN_ERROR_ILLNET = TPCANStatus(0x01800) # Net handle is invalid
PCAN_ERROR_ILLCLIENT = TPCANStatus(0x01C00) # Client handle is invalid
PCAN_ERROR_ILLHANDLE = TPCANStatus(
PCAN_ERROR_ILLHW | PCAN_ERROR_ILLNET | PCAN_ERROR_ILLCLIENT
) # Mask for all handle errors
PCAN_ERROR_RESOURCE = TPCANStatus(
0x02000
) # Resource (FIFO, Client, timeout) cannot be created
PCAN_ERROR_ILLPARAMTYPE = TPCANStatus(0x04000) # Invalid parameter
PCAN_ERROR_ILLPARAMVAL = TPCANStatus(0x08000) # Invalid parameter value
PCAN_ERROR_UNKNOWN = TPCANStatus(0x10000) # Unknown error
PCAN_ERROR_ILLDATA = TPCANStatus(0x20000) # Invalid data, function, or action
PCAN_ERROR_ILLMODE = TPCANStatus(
0x80000
) # Driver object state is wrong for the attempted operation
PCAN_ERROR_CAUTION = TPCANStatus(
0x2000000
) # An operation was successfully carried out, however, irregularities were registered
PCAN_ERROR_INITIALIZE = TPCANStatus(
0x4000000
) # Channel is not initialized [Value was changed from 0x40000 to 0x4000000]
PCAN_ERROR_ILLOPERATION = TPCANStatus(
0x8000000
) # Invalid operation [Value was changed from 0x80000 to 0x8000000]
# PCAN devices
#
PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value
PCAN_PEAKCAN = TPCANDevice(0x01) # PCAN Non-PnP devices. NOT USED WITHIN PCAN-Basic API
PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus
PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle
PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express
PCAN_USB = TPCANDevice(0x05) # PCAN-USB and PCAN-USB Pro
PCAN_PCC = TPCANDevice(0x06) # PCAN-PC Card
PCAN_VIRTUAL = TPCANDevice(
0x07
) # PCAN Virtual hardware. NOT USED WITHIN PCAN-Basic API
PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices
# PCAN parameters
#
PCAN_DEVICE_ID = TPCANParameter(0x01) # Device identifier parameter
PCAN_5VOLTS_POWER = TPCANParameter(0x02) # 5-Volt power parameter
PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter
PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter
PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter
PCAN_CHANNEL_VERSION = TPCANParameter(0x06) # PCAN device channel version parameter
PCAN_BUSOFF_AUTORESET = TPCANParameter(0x07) # PCAN Reset-On-Busoff parameter
PCAN_LISTEN_ONLY = TPCANParameter(0x08) # PCAN Listen-Only parameter
PCAN_LOG_LOCATION = TPCANParameter(0x09) # Directory path for log files
PCAN_LOG_STATUS = TPCANParameter(0x0A) # Debug-Log activation status
PCAN_LOG_CONFIGURE = TPCANParameter(
0x0B
) # Configuration of the debugged information (LOG_FUNCTION_***)
PCAN_LOG_TEXT = TPCANParameter(0x0C) # Custom insertion of text into the log file
PCAN_CHANNEL_CONDITION = TPCANParameter(0x0D) # Availability status of a PCAN-Channel
PCAN_HARDWARE_NAME = TPCANParameter(0x0E) # PCAN hardware name parameter
PCAN_RECEIVE_STATUS = TPCANParameter(0x0F) # Message reception status of a PCAN-Channel
PCAN_CONTROLLER_NUMBER = TPCANParameter(0x10) # CAN-Controller number of a PCAN-Channel
PCAN_TRACE_LOCATION = TPCANParameter(0x11) # Directory path for PCAN trace files
PCAN_TRACE_STATUS = TPCANParameter(0x12) # CAN tracing activation status
PCAN_TRACE_SIZE = TPCANParameter(
0x13
) # Configuration of the maximum file size of a CAN trace
PCAN_TRACE_CONFIGURE = TPCANParameter(
0x14
) # Configuration of the trace file storing mode (TRACE_FILE_***)
PCAN_CHANNEL_IDENTIFYING = TPCANParameter(
0x15
) # Physical identification of a USB based PCAN-Channel by blinking its associated LED
PCAN_CHANNEL_FEATURES = TPCANParameter(
0x16
) # Capabilities of a PCAN device (FEATURE_***)
PCAN_BITRATE_ADAPTING = TPCANParameter(
0x17
) # Using of an existing bit rate (PCAN-View connected to a channel)
PCAN_BITRATE_INFO = TPCANParameter(0x18) # Configured bit rate as Btr0Btr1 value
PCAN_BITRATE_INFO_FD = TPCANParameter(
0x19
) # Configured bit rate as TPCANBitrateFD string
PCAN_BUSSPEED_NOMINAL = TPCANParameter(
0x1A
) # Configured nominal CAN Bus speed as Bits per seconds
PCAN_BUSSPEED_DATA = TPCANParameter(
0x1B
) # Configured CAN data speed as Bits per seconds
PCAN_IP_ADDRESS = TPCANParameter(
0x1C
) # Remote address of a LAN channel as string in IPv4 format
PCAN_LAN_SERVICE_STATUS = TPCANParameter(
0x1D
) # Status of the Virtual PCAN-Gateway Service
PCAN_ALLOW_STATUS_FRAMES = TPCANParameter(
0x1E
) # Status messages reception status within a PCAN-Channel
PCAN_ALLOW_RTR_FRAMES = TPCANParameter(
0x1F
) # RTR messages reception status within a PCAN-Channel
PCAN_ALLOW_ERROR_FRAMES = TPCANParameter(
0x20
) # Error messages reception status within a PCAN-Channel
PCAN_INTERFRAME_DELAY = TPCANParameter(
0x21
) # Delay, in microseconds, between sending frames
PCAN_ACCEPTANCE_FILTER_11BIT = TPCANParameter(
0x22
) # Filter over code and mask patterns for 11-Bit messages
PCAN_ACCEPTANCE_FILTER_29BIT = TPCANParameter(
0x23
) # Filter over code and mask patterns for 29-Bit messages
PCAN_IO_DIGITAL_CONFIGURATION = TPCANParameter(
0x24
) # Output mode of 32 digital I/O pin of a PCAN-USB Chip. 1: Output-Active 0 : Output Inactive
PCAN_IO_DIGITAL_VALUE = TPCANParameter(
0x25
) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip
PCAN_IO_DIGITAL_SET = TPCANParameter(
0x26
) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High
PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0
PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin
PCAN_FIRMWARE_VERSION = TPCANParameter(
0x29
) # Get the version of the firmware used by the device associated with a PCAN-Channel
PCAN_ATTACHED_CHANNELS_COUNT = TPCANParameter(
0x2A
) # Get the amount of PCAN channels attached to a system
PCAN_ATTACHED_CHANNELS = TPCANParameter(
0x2B
) # Get information about PCAN channels attached to a system
PCAN_ALLOW_ECHO_FRAMES = TPCANParameter(
0x2C
) # Echo messages reception status within a PCAN-Channel
PCAN_DEVICE_PART_NUMBER = TPCANParameter(
0x2D
) # Get the part number associated to a device
# DEPRECATED parameters
#
PCAN_DEVICE_NUMBER = PCAN_DEVICE_ID # DEPRECATED. Use PCAN_DEVICE_ID instead
# PCAN parameter values
#
PCAN_PARAMETER_OFF = 0x00 # The PCAN parameter is not set (inactive)
PCAN_PARAMETER_ON = 0x01 # The PCAN parameter is set (active)
PCAN_FILTER_CLOSE = 0x00 # The PCAN filter is closed. No messages will be received
PCAN_FILTER_OPEN = (
0x01 # The PCAN filter is fully opened. All messages will be received
)
PCAN_FILTER_CUSTOM = 0x02 # The PCAN filter is custom configured. Only registered messages will be received
PCAN_CHANNEL_UNAVAILABLE = 0x00 # The PCAN-Channel handle is illegal, or its associated hardware is not available
PCAN_CHANNEL_AVAILABLE = 0x01 # The PCAN-Channel handle is available to be connected (PnP Hardware: it means furthermore that the hardware is plugged-in)
PCAN_CHANNEL_OCCUPIED = (
0x02 # The PCAN-Channel handle is valid, and is already being used
)
PCAN_CHANNEL_PCANVIEW = (
PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED
) # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect
LOG_FUNCTION_DEFAULT = 0x00 # Logs system exceptions / errors
LOG_FUNCTION_ENTRY = 0x01 # Logs the entries to the PCAN-Basic API functions
LOG_FUNCTION_PARAMETERS = (
0x02 # Logs the parameters passed to the PCAN-Basic API functions
)
LOG_FUNCTION_LEAVE = 0x04 # Logs the exits from the PCAN-Basic API functions
LOG_FUNCTION_WRITE = 0x08 # Logs the CAN messages passed to the CAN_Write function
LOG_FUNCTION_READ = 0x10 # Logs the CAN messages received within the CAN_Read function
LOG_FUNCTION_ALL = (
0xFFFF # Logs all possible information within the PCAN-Basic API functions
)
TRACE_FILE_SINGLE = (
0x00 # A single file is written until it size reaches PAN_TRACE_SIZE
)
TRACE_FILE_SEGMENTED = (
0x01 # Traced data is distributed in several files with size PAN_TRACE_SIZE
)
TRACE_FILE_DATE = 0x02 # Includes the date into the name of the trace file
TRACE_FILE_TIME = 0x04 # Includes the start time into the name of the trace file
TRACE_FILE_OVERWRITE = 0x80 # Causes the overwriting of available traces (same name)
FEATURE_FD_CAPABLE = 0x01 # Device supports flexible data-rate (CAN-FD)
FEATURE_DELAY_CAPABLE = (
0x02 # Device supports a delay between sending frames (FPGA based USB devices)
)
FEATURE_IO_CAPABLE = (
0x04 # Device supports I/O functionality for electronic circuits (USB-Chip devices)
)
SERVICE_STATUS_STOPPED = 0x01 # The service is not running
SERVICE_STATUS_RUNNING = 0x04 # The service is running
# Other constants
#
MAX_LENGTH_HARDWARE_NAME = (
33 # Maximum length of the name of a device: 32 characters + terminator
)
MAX_LENGTH_VERSION_STRING = (
256 # Maximum length of a version string: 255 characters + terminator
)
# PCAN message types
#
PCAN_MESSAGE_STANDARD = TPCANMessageType(
0x00
) # The PCAN message is a CAN Standard Frame (11-bit identifier)
PCAN_MESSAGE_RTR = TPCANMessageType(
0x01
) # The PCAN message is a CAN Remote-Transfer-Request Frame
PCAN_MESSAGE_EXTENDED = TPCANMessageType(
0x02
) # The PCAN message is a CAN Extended Frame (29-bit identifier)
PCAN_MESSAGE_FD = TPCANMessageType(
0x04
) # The PCAN message represents a FD frame in terms of CiA Specs
PCAN_MESSAGE_BRS = TPCANMessageType(
0x08
) # The PCAN message represents a FD bit rate switch (CAN data at a higher bit rate)
PCAN_MESSAGE_ESI = TPCANMessageType(
0x10
) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active)
PCAN_MESSAGE_ECHO = TPCANMessageType(
0x20
) # The PCAN message represents an echo CAN Frame
PCAN_MESSAGE_ERRFRAME = TPCANMessageType(
0x40
) # The PCAN message represents an error frame
PCAN_MESSAGE_STATUS = TPCANMessageType(
0x80
) # The PCAN message represents a PCAN status message
# LookUp Parameters
#
LOOKUP_DEVICE_TYPE = (
b"devicetype" # Lookup channel by Device type (see PCAN devices e.g. PCAN_USB)
)
LOOKUP_DEVICE_ID = b"deviceid" # Lookup channel by device id
LOOKUP_CONTROLLER_NUMBER = (
b"controllernumber" # Lookup channel by CAN controller 0-based index
)
LOOKUP_IP_ADDRESS = b"ipaddress" # Lookup channel by IP address (LAN channels only)
# Frame Type / Initialization Mode
#
PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD
PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED
# Baud rate codes = BTR0/BTR1 register values for the CAN controller.
# You can define your own Baud rate with the BTROBTR1 register.
# Take a look at www.peak-system.com for our free software "BAUDTOOL"
# to calculate the BTROBTR1 register for every bit rate and sample point.
#
PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s
PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s
PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s
PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s
PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s
PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s
PCAN_BAUD_95K = TPCANBaudrate(0xC34E) # 95,238 kBit/s
PCAN_BAUD_83K = TPCANBaudrate(0x852B) # 83,333 kBit/s
PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s
PCAN_BAUD_47K = TPCANBaudrate(0x1414) # 47,619 kBit/s
PCAN_BAUD_33K = TPCANBaudrate(0x8B2F) # 33,333 kBit/s
PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s
PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s
PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s
# Represents the configuration for a CAN bit rate
# Note:
# * Each parameter and its value must be separated with a '='.
# * Each pair of parameter/value must be separated using ','.
#
# Example:
# f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1
#
PCAN_BR_CLOCK = TPCANBitrateFD(b"f_clock")
PCAN_BR_CLOCK_MHZ = TPCANBitrateFD(b"f_clock_mhz")
PCAN_BR_NOM_BRP = TPCANBitrateFD(b"nom_brp")
PCAN_BR_NOM_TSEG1 = TPCANBitrateFD(b"nom_tseg1")
PCAN_BR_NOM_TSEG2 = TPCANBitrateFD(b"nom_tseg2")
PCAN_BR_NOM_SJW = TPCANBitrateFD(b"nom_sjw")
PCAN_BR_NOM_SAMPLE = TPCANBitrateFD(b"nom_sam")
PCAN_BR_DATA_BRP = TPCANBitrateFD(b"data_brp")
PCAN_BR_DATA_TSEG1 = TPCANBitrateFD(b"data_tseg1")
PCAN_BR_DATA_TSEG2 = TPCANBitrateFD(b"data_tseg2")
PCAN_BR_DATA_SJW = TPCANBitrateFD(b"data_sjw")
PCAN_BR_DATA_SAMPLE = TPCANBitrateFD(b"data_ssp_offset")
# Supported Non-PnP Hardware types
#
PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200
PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000
PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA
PCAN_TYPE_DNG = TPCANType(0x02) # PCAN-Dongle 82C200
PCAN_TYPE_DNG_EPP = TPCANType(0x03) # PCAN-Dongle EPP 82C200
PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000
PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000
# string description of the error codes
PCAN_DICT_STATUS = {
PCAN_ERROR_OK: "OK",
PCAN_ERROR_XMTFULL: "XMTFULL",
PCAN_ERROR_OVERRUN: "OVERRUN",
PCAN_ERROR_BUSLIGHT: "BUSLIGHT",
PCAN_ERROR_BUSHEAVY: "BUSHEAVY",
PCAN_ERROR_BUSWARNING: "BUSWARNING",
PCAN_ERROR_BUSPASSIVE: "BUSPASSIVE",
PCAN_ERROR_BUSOFF: "BUSOFF",
PCAN_ERROR_ANYBUSERR: "ANYBUSERR",
PCAN_ERROR_QRCVEMPTY: "QRCVEMPTY",
PCAN_ERROR_QOVERRUN: "QOVERRUN",
PCAN_ERROR_QXMTFULL: "QXMTFULL",
PCAN_ERROR_REGTEST: "ERR_REGTEST",
PCAN_ERROR_NODRIVER: "NODRIVER",
PCAN_ERROR_HWINUSE: "HWINUSE",
PCAN_ERROR_NETINUSE: "NETINUSE",
PCAN_ERROR_ILLHW: "ILLHW",
PCAN_ERROR_ILLNET: "ILLNET",
PCAN_ERROR_ILLCLIENT: "ILLCLIENT",
PCAN_ERROR_ILLHANDLE: "ILLHANDLE",
PCAN_ERROR_RESOURCE: "ERR_RESOURCE",
PCAN_ERROR_ILLPARAMTYPE: "ILLPARAMTYPE",
PCAN_ERROR_ILLPARAMVAL: "ILLPARAMVAL",
PCAN_ERROR_UNKNOWN: "UNKNOWN",
PCAN_ERROR_ILLDATA: "ILLDATA",
PCAN_ERROR_CAUTION: "CAUTION",
PCAN_ERROR_INITIALIZE: "ERR_INITIALIZE",
PCAN_ERROR_ILLOPERATION: "ILLOPERATION",
}
# Represents a PCAN message
#
class TPCANMsg(Structure):
"""
Represents a PCAN message
"""
_fields_ = [
("ID", c_uint), # 11/29-bit message identifier
("MSGTYPE", TPCANMessageType), # Type of the message
("LEN", c_ubyte), # Data Length Code of the message (0..8)
("DATA", c_ubyte * 8),
] # Data of the message (DATA[0]..DATA[7])
# Represents a timestamp of a received PCAN message
# Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow
#
class TPCANTimestamp(Structure):
"""
Represents a timestamp of a received PCAN message
Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow
"""
_fields_ = [
("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1
("millis_overflow", c_ushort), # Roll-arounds of millis
("micros", c_ushort),
] # Microseconds: 0..999
# Represents a PCAN message from a FD capable hardware
#
class TPCANMsgFD(Structure):
"""
Represents a PCAN message
"""
_fields_ = [
("ID", c_uint), # 11/29-bit message identifier
("MSGTYPE", TPCANMessageType), # Type of the message
("DLC", c_ubyte), # Data Length Code of the message (0..15)
("DATA", c_ubyte * 64),
] # Data of the message (DATA[0]..DATA[63])
# Describes an available PCAN channel
#
class TPCANChannelInformation(Structure):
"""
Describes an available PCAN channel
"""
_fields_ = [
("channel_handle", TPCANHandle), # PCAN channel handle
("device_type", TPCANDevice), # Kind of PCAN device
("controller_number", c_ubyte), # CAN-Controller number
("device_features", c_uint), # Device capabilities flag (see FEATURE_*)
("device_name", c_char * MAX_LENGTH_HARDWARE_NAME), # Device name
("device_id", c_uint), # Device number
("channel_condition", c_uint),
] # Availability status of a PCAN-Channel
# ///////////////////////////////////////////////////////////
# Additional objects
# ///////////////////////////////////////////////////////////
PCAN_BITRATES = {
1000000: PCAN_BAUD_1M,
800000: PCAN_BAUD_800K,
500000: PCAN_BAUD_500K,
250000: PCAN_BAUD_250K,
125000: PCAN_BAUD_125K,
100000: PCAN_BAUD_100K,
95000: PCAN_BAUD_95K,
83000: PCAN_BAUD_83K,
50000: PCAN_BAUD_50K,
47000: PCAN_BAUD_47K,
33000: PCAN_BAUD_33K,
20000: PCAN_BAUD_20K,
10000: PCAN_BAUD_10K,
5000: PCAN_BAUD_5K,
}
PCAN_FD_PARAMETER_LIST = (
"nom_brp",
"nom_tseg1",
"nom_tseg2",
"nom_sjw",
"data_brp",
"data_tseg1",
"data_tseg2",
"data_sjw",
)
PCAN_CHANNEL_NAMES = {
"PCAN_NONEBUS": PCAN_NONEBUS,
"PCAN_ISABUS1": PCAN_ISABUS1,
"PCAN_ISABUS2": PCAN_ISABUS2,
"PCAN_ISABUS3": PCAN_ISABUS3,
"PCAN_ISABUS4": PCAN_ISABUS4,
"PCAN_ISABUS5": PCAN_ISABUS5,
"PCAN_ISABUS6": PCAN_ISABUS6,
"PCAN_ISABUS7": PCAN_ISABUS7,
"PCAN_ISABUS8": PCAN_ISABUS8,
"PCAN_DNGBUS1": PCAN_DNGBUS1,
"PCAN_PCIBUS1": PCAN_PCIBUS1,
"PCAN_PCIBUS2": PCAN_PCIBUS2,
"PCAN_PCIBUS3": PCAN_PCIBUS3,
"PCAN_PCIBUS4": PCAN_PCIBUS4,
"PCAN_PCIBUS5": PCAN_PCIBUS5,
"PCAN_PCIBUS6": PCAN_PCIBUS6,
"PCAN_PCIBUS7": PCAN_PCIBUS7,
"PCAN_PCIBUS8": PCAN_PCIBUS8,
"PCAN_PCIBUS9": PCAN_PCIBUS9,
"PCAN_PCIBUS10": PCAN_PCIBUS10,
"PCAN_PCIBUS11": PCAN_PCIBUS11,
"PCAN_PCIBUS12": PCAN_PCIBUS12,
"PCAN_PCIBUS13": PCAN_PCIBUS13,
"PCAN_PCIBUS14": PCAN_PCIBUS14,
"PCAN_PCIBUS15": PCAN_PCIBUS15,
"PCAN_PCIBUS16": PCAN_PCIBUS16,
"PCAN_USBBUS1": PCAN_USBBUS1,
"PCAN_USBBUS2": PCAN_USBBUS2,
"PCAN_USBBUS3": PCAN_USBBUS3,
"PCAN_USBBUS4": PCAN_USBBUS4,
"PCAN_USBBUS5": PCAN_USBBUS5,
"PCAN_USBBUS6": PCAN_USBBUS6,
"PCAN_USBBUS7": PCAN_USBBUS7,
"PCAN_USBBUS8": PCAN_USBBUS8,
"PCAN_USBBUS9": PCAN_USBBUS9,
"PCAN_USBBUS10": PCAN_USBBUS10,
"PCAN_USBBUS11": PCAN_USBBUS11,
"PCAN_USBBUS12": PCAN_USBBUS12,
"PCAN_USBBUS13": PCAN_USBBUS13,
"PCAN_USBBUS14": PCAN_USBBUS14,
"PCAN_USBBUS15": PCAN_USBBUS15,
"PCAN_USBBUS16": PCAN_USBBUS16,
"PCAN_PCCBUS1": PCAN_PCCBUS1,
"PCAN_PCCBUS2": PCAN_PCCBUS2,
"PCAN_LANBUS1": PCAN_LANBUS1,
"PCAN_LANBUS2": PCAN_LANBUS2,
"PCAN_LANBUS3": PCAN_LANBUS3,
"PCAN_LANBUS4": PCAN_LANBUS4,
"PCAN_LANBUS5": PCAN_LANBUS5,
"PCAN_LANBUS6": PCAN_LANBUS6,
"PCAN_LANBUS7": PCAN_LANBUS7,
"PCAN_LANBUS8": PCAN_LANBUS8,
"PCAN_LANBUS9": PCAN_LANBUS9,
"PCAN_LANBUS10": PCAN_LANBUS10,
"PCAN_LANBUS11": PCAN_LANBUS11,
"PCAN_LANBUS12": PCAN_LANBUS12,
"PCAN_LANBUS13": PCAN_LANBUS13,
"PCAN_LANBUS14": PCAN_LANBUS14,
"PCAN_LANBUS15": PCAN_LANBUS15,
"PCAN_LANBUS16": PCAN_LANBUS16,
}
VALID_PCAN_CAN_CLOCKS = [8_000_000]
VALID_PCAN_FD_CLOCKS = [
20_000_000,
24_000_000,
30_000_000,
40_000_000,
60_000_000,
80_000_000,
]
# ///////////////////////////////////////////////////////////
# PCAN-Basic API function declarations
# ///////////////////////////////////////////////////////////
# PCAN-Basic API class implementation
#
class PCANBasic:
"""PCAN-Basic API class implementation"""
def __init__(self):
if platform.system() == "Windows":
load_library_func = windll.LoadLibrary
else:
load_library_func = cdll.LoadLibrary
if platform.system() == "Windows" or "CYGWIN" in platform.system():
lib_name = "PCANBasic"
elif platform.system() == "Darwin":
# PCBUSB library is a third-party software created
# and maintained by the MacCAN project
lib_name = "PCBUSB"
else:
lib_name = "pcanbasic"
lib_path = find_library(lib_name)
if not lib_path:
raise OSError(f"{lib_name} library not found.")
try:
self.__m_dllBasic = load_library_func(lib_path)
except OSError:
raise OSError(
f"The PCAN-Basic API could not be loaded. ({lib_path})"
) from None
# Initializes a PCAN Channel
#
def Initialize(
self,
Channel,
Btr0Btr1,
HwType=TPCANType(0), # noqa: B008
IOPort=c_uint(0), # noqa: B008
Interrupt=c_ushort(0), # noqa: B008
):
"""Initializes a PCAN Channel
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Btr0Btr1 : The speed for the communication (BTR0BTR1 code)
HwType : Non-PnP: The type of hardware and operation mode
IOPort : Non-PnP: The I/O address for the parallel port
Interrupt: Non-PnP: Interrupt number of the parallel port
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_Initialize(
Channel, Btr0Btr1, HwType, IOPort, Interrupt
)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.Initialize")
raise
# Initializes a FD capable PCAN Channel
#
def InitializeFD(self, Channel, BitrateFD):
"""Initializes a FD capable PCAN Channel
Parameters:
Channel : The handle of a FD capable PCAN Channel
BitrateFD : The speed for the communication (FD bit rate string)
Remarks:
* See PCAN_BR_* values.
* parameter and values must be separated by '='
* Couples of Parameter/value must be separated by ','
* Following Parameter must be filled out: f_clock, data_brp, data_sjw, data_tseg1, data_tseg2,
nom_brp, nom_sjw, nom_tseg1, nom_tseg2.
* Following Parameters are optional (not used yet): data_ssp_offset, nom_sam
Example:
f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_InitializeFD(Channel, BitrateFD)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.InitializeFD")
raise
# Uninitializes one or all PCAN Channels initialized by CAN_Initialize
#
def Uninitialize(self, Channel):
"""Uninitializes one or all PCAN Channels initialized by CAN_Initialize
Remarks:
Giving the TPCANHandle value "PCAN_NONEBUS", uninitialize all initialized channels
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_Uninitialize(Channel)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.Uninitialize")
raise
# Resets the receive and transmit queues of the PCAN Channel
#
def Reset(self, Channel):
"""Resets the receive and transmit queues of the PCAN Channel
Remarks:
A reset of the CAN controller is not performed
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_Reset(Channel)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.Reset")
raise
# Gets the current status of a PCAN Channel
#
def GetStatus(self, Channel):
"""Gets the current status of a PCAN Channel
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_GetStatus(Channel)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.GetStatus")
raise
# Reads a CAN message from the receive queue of a PCAN Channel
#
def Read(self, Channel):
"""Reads a CAN message from the receive queue of a PCAN Channel
Remarks:
The return value of this method is a 3-tuple, where
the first value is the result (TPCANStatus) of the method.
The order of the values are:
[0]: A TPCANStatus error code
[1]: A TPCANMsg structure with the CAN message read
[2]: A TPCANTimestamp structure with the time when a message was read
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Returns:
A tuple with three values
"""
try:
msg = TPCANMsg()
timestamp = TPCANTimestamp()
res = self.__m_dllBasic.CAN_Read(Channel, byref(msg), byref(timestamp))
return TPCANStatus(res), msg, timestamp
except:
logger.error("Exception on PCANBasic.Read")
raise
# Reads a CAN message from the receive queue of a FD capable PCAN Channel
#
def ReadFD(self, Channel):
"""Reads a CAN message from the receive queue of a FD capable PCAN Channel
Remarks:
The return value of this method is a 3-tuple, where
the first value is the result (TPCANStatus) of the method.
The order of the values are:
[0]: A TPCANStatus error code
[1]: A TPCANMsgFD structure with the CAN message read
[2]: A TPCANTimestampFD that is the time when a message was read
Parameters:
Channel : The handle of a FD capable PCAN Channel
Returns:
A tuple with three values
"""
try:
msg = TPCANMsgFD()
timestamp = TPCANTimestampFD()
res = self.__m_dllBasic.CAN_ReadFD(Channel, byref(msg), byref(timestamp))
return TPCANStatus(res), msg, timestamp
except:
logger.error("Exception on PCANBasic.ReadFD")
raise
# Transmits a CAN message
#
def Write(self, Channel, MessageBuffer):
"""Transmits a CAN message
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
MessageBuffer: A TPCANMsg representing the CAN message to be sent
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_Write(Channel, byref(MessageBuffer))
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.Write")
raise
# Transmits a CAN message over a FD capable PCAN Channel
#
def WriteFD(self, Channel, MessageBuffer):
"""Transmits a CAN message over a FD capable PCAN Channel
Parameters:
Channel : The handle of a FD capable PCAN Channel
MessageBuffer: A TPCANMsgFD buffer with the message to be sent
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_WriteFD(Channel, byref(MessageBuffer))
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.WriteFD")
raise
# Configures the reception filter
#
def FilterMessages(self, Channel, FromID, ToID, Mode):
"""Configures the reception filter
Remarks:
The message filter will be expanded with every call to this function.
If it is desired to reset the filter, please use the 'SetValue' function.
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
FromID : A c_uint value with the lowest CAN ID to be received
ToID : A c_uint value with the highest CAN ID to be received
Mode : A TPCANMode representing the message type (Standard, 11-bit
identifier, or Extended, 29-bit identifier)
Returns:
A TPCANStatus error code
"""
try:
res = self.__m_dllBasic.CAN_FilterMessages(Channel, FromID, ToID, Mode)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.FilterMessages")
raise
# Retrieves a PCAN Channel value
#
def GetValue(self, Channel, Parameter):
"""Retrieves a PCAN Channel value
Remarks:
Parameters can be present or not according with the kind
of Hardware (PCAN Channel) being used. If a parameter is not available,
a PCAN_ERROR_ILLPARAMTYPE error will be returned.
The return value of this method is a 2-tuple, where
the first value is the result (TPCANStatus) of the method and
the second one, the asked value
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Parameter : The TPCANParameter parameter to get
Returns:
A tuple with 2 values
"""
try:
if (
Parameter == PCAN_API_VERSION
or Parameter == PCAN_HARDWARE_NAME
or Parameter == PCAN_CHANNEL_VERSION
or Parameter == PCAN_LOG_LOCATION
or Parameter == PCAN_TRACE_LOCATION
or Parameter == PCAN_BITRATE_INFO_FD
or Parameter == PCAN_IP_ADDRESS
or Parameter == PCAN_FIRMWARE_VERSION
or Parameter == PCAN_DEVICE_PART_NUMBER
):
mybuffer = create_string_buffer(256)
elif Parameter == PCAN_ATTACHED_CHANNELS:
res = self.GetValue(Channel, PCAN_ATTACHED_CHANNELS_COUNT)
if TPCANStatus(res[0]) != PCAN_ERROR_OK:
return TPCANStatus(res[0]), ()
mybuffer = (TPCANChannelInformation * res[1])()
elif (
Parameter == PCAN_ACCEPTANCE_FILTER_11BIT
or PCAN_ACCEPTANCE_FILTER_29BIT
):
mybuffer = c_int64(0)
else:
mybuffer = c_int(0)
res = self.__m_dllBasic.CAN_GetValue(
Channel, Parameter, byref(mybuffer), sizeof(mybuffer)
)
if Parameter == PCAN_ATTACHED_CHANNELS:
return TPCANStatus(res), mybuffer
else:
return TPCANStatus(res), mybuffer.value
except:
logger.error("Exception on PCANBasic.GetValue")
raise
# Returns a descriptive text of a given TPCANStatus
# error code, in any desired language
#
def SetValue(self, Channel, Parameter, Buffer):
"""Returns a descriptive text of a given TPCANStatus error
code, in any desired language
Remarks:
Parameters can be present or not according with the kind
of Hardware (PCAN Channel) being used. If a parameter is not available,
a PCAN_ERROR_ILLPARAMTYPE error will be returned.
Parameters:
Channel : A TPCANHandle representing a PCAN Channel
Parameter : The TPCANParameter parameter to set
Buffer : Buffer with the value to be set
BufferLength : Size in bytes of the buffer
Returns:
A TPCANStatus error code
"""
try:
if (
Parameter == PCAN_LOG_LOCATION
or Parameter == PCAN_LOG_TEXT
or Parameter == PCAN_TRACE_LOCATION
):
mybuffer = create_string_buffer(256)
elif (
Parameter == PCAN_ACCEPTANCE_FILTER_11BIT
or PCAN_ACCEPTANCE_FILTER_29BIT
):
mybuffer = c_int64(0)
else:
mybuffer = c_int(0)
mybuffer.value = Buffer
res = self.__m_dllBasic.CAN_SetValue(
Channel, Parameter, byref(mybuffer), sizeof(mybuffer)
)
return TPCANStatus(res)
except:
logger.error("Exception on PCANBasic.SetValue")
raise
def GetErrorText(self, Error, Language=0):
"""Configures or sets a PCAN Channel value
Remarks:
The current languages available for translation are:
Neutral (0x00), German (0x07), English (0x09), Spanish (0x0A),
Italian (0x10) and French (0x0C)
The return value of this method is a 2-tuple, where
the first value is the result (TPCANStatus) of the method and
the second one, the error text
Parameters:
Error : A TPCANStatus error code
Language : Indicates a 'Primary language ID' (Default is Neutral(0))
Returns:
A tuple with 2 values
"""
try:
mybuffer = create_string_buffer(256)
res = self.__m_dllBasic.CAN_GetErrorText(Error, Language, byref(mybuffer))
return TPCANStatus(res), mybuffer.value
except:
logger.error("Exception on PCANBasic.GetErrorText")
raise
def LookUpChannel(self, Parameters):
"""Finds a PCAN-Basic channel that matches with the given parameters
Remarks:
The return value of this method is a 2-tuple, where
the first value is the result (TPCANStatus) of the method and
the second one a TPCANHandle value
Parameters:
Parameters : A comma separated string contained pairs of parameter-name/value
to be matched within a PCAN-Basic channel
Returns:
A tuple with 2 values
"""
try:
mybuffer = TPCANHandle(0)
res = self.__m_dllBasic.CAN_LookUpChannel(Parameters, byref(mybuffer))
return TPCANStatus(res), mybuffer
except:
logger.error("Exception on PCANBasic.LookUpChannel")
raise
python-can-4.5.0/can/interfaces/pcan/pcan.py 0000664 0000000 0000000 00000067435 14722003266 0020721 0 ustar 00root root 0000000 0000000 """
Enable basic CAN over a PCAN USB device.
"""
import logging
import platform
import time
import warnings
from typing import Any, List, Optional, Tuple, Union
from packaging import version
from can import (
BitTiming,
BitTimingFd,
BusABC,
BusState,
CanError,
CanInitializationError,
CanOperationError,
CanProtocol,
Message,
)
from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc
from .basic import (
FEATURE_FD_CAPABLE,
IS_LINUX,
IS_WINDOWS,
PCAN_ALLOW_ECHO_FRAMES,
PCAN_ALLOW_ERROR_FRAMES,
PCAN_API_VERSION,
PCAN_ATTACHED_CHANNELS,
PCAN_BAUD_500K,
PCAN_BITRATES,
PCAN_BUSOFF_AUTORESET,
PCAN_CHANNEL_AVAILABLE,
PCAN_CHANNEL_CONDITION,
PCAN_CHANNEL_FEATURES,
PCAN_CHANNEL_IDENTIFYING,
PCAN_CHANNEL_NAMES,
PCAN_DEVICE_NUMBER,
PCAN_DICT_STATUS,
PCAN_ERROR_BUSHEAVY,
PCAN_ERROR_BUSLIGHT,
PCAN_ERROR_ILLDATA,
PCAN_ERROR_OK,
PCAN_ERROR_QRCVEMPTY,
PCAN_FD_PARAMETER_LIST,
PCAN_LANBUS1,
PCAN_LISTEN_ONLY,
PCAN_MESSAGE_BRS,
PCAN_MESSAGE_ECHO,
PCAN_MESSAGE_ERRFRAME,
PCAN_MESSAGE_ESI,
PCAN_MESSAGE_EXTENDED,
PCAN_MESSAGE_FD,
PCAN_MESSAGE_RTR,
PCAN_MESSAGE_STANDARD,
PCAN_NONEBUS,
PCAN_PARAMETER_OFF,
PCAN_PARAMETER_ON,
PCAN_PCCBUS1,
PCAN_PCIBUS1,
PCAN_RECEIVE_EVENT,
PCAN_TYPE_ISA,
PCAN_USBBUS1,
VALID_PCAN_CAN_CLOCKS,
VALID_PCAN_FD_CLOCKS,
PCANBasic,
TPCANBaudrate,
TPCANChannelInformation,
TPCANHandle,
TPCANMsg,
TPCANMsgFD,
)
# Set up logging
log = logging.getLogger("can.pcan")
MIN_PCAN_API_VERSION = version.parse("4.2.0")
try:
# use the "uptime" library if available
import uptime
# boottime() and fromtimestamp() are timezone offset, so the difference is not.
if uptime.boottime() is None:
boottimeEpoch = 0
else:
boottimeEpoch = uptime.boottime().timestamp()
except ImportError:
log.warning(
"uptime library not available, timestamps are relative to boot time and not to Epoch UTC",
)
boottimeEpoch = 0
HAS_EVENTS = False
if IS_WINDOWS:
try:
# Try builtin Python 3 Windows API
from _overlapped import CreateEvent
from _winapi import INFINITE, WAIT_OBJECT_0, WaitForSingleObject
HAS_EVENTS = True
except ImportError:
pass
elif IS_LINUX:
try:
import select
HAS_EVENTS = True
except Exception:
pass
class PcanBus(BusABC):
def __init__(
self,
channel: str = "PCAN_USBBUS1",
device_id: Optional[int] = None,
state: BusState = BusState.ACTIVE,
timing: Optional[Union[BitTiming, BitTimingFd]] = None,
bitrate: int = 500000,
receive_own_messages: bool = False,
**kwargs: Any,
):
"""A PCAN USB interface to CAN.
On top of the usual :class:`~can.Bus` methods provided,
the PCAN interface includes the :meth:`flash`
and :meth:`status` methods.
:param str channel:
The can interface name. An example would be 'PCAN_USBBUS1'.
Alternatively the value can be an int with the numerical value.
Default is 'PCAN_USBBUS1'
:param int device_id:
Select the PCAN interface based on its ID. The device ID is a 8/32bit
value that can be configured for each PCAN device. If you set the
device_id parameter, it takes precedence over the channel parameter.
The constructor searches all connected interfaces and initializes the
first one that matches the parameter value. If no device is found,
an exception is raised.
:param can.bus.BusState state:
BusState of the channel.
Default is ACTIVE
:param timing:
An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd`
to specify the bit timing parameters for the PCAN interface. If this parameter
is provided, it takes precedence over all other timing-related parameters.
If this parameter is not provided, the bit timing parameters can be specified
using the `bitrate` parameter for standard CAN or the `fd`, `f_clock`,
`f_clock_mhz`, `nom_brp`, `nom_tseg1`, `nom_tseg2`, `nom_sjw`, `data_brp`,
`data_tseg1`, `data_tseg2`, and `data_sjw` parameters for CAN FD.
Note that the `f_clock` value of the `timing` instance must be 8_000_000
for standard CAN or any of the following values for CAN FD: 20_000_000,
24_000_000, 30_000_000, 40_000_000, 60_000_000, 80_000_000.
:param int bitrate:
Bitrate of channel in bit/s.
Default is 500 kbit/s.
Ignored if using CanFD.
:param receive_own_messages:
Enable self-reception of sent messages.
:param bool fd:
Should the Bus be initialized in CAN-FD mode.
:param int f_clock:
Clock rate in Hz.
Any of the following:
20000000, 24000000, 30000000, 40000000, 60000000, 80000000.
Ignored if not using CAN-FD.
Pass either f_clock or f_clock_mhz.
:param int f_clock_mhz:
Clock rate in MHz.
Any of the following:
20, 24, 30, 40, 60, 80.
Ignored if not using CAN-FD.
Pass either f_clock or f_clock_mhz.
:param int nom_brp:
Clock prescaler for nominal time quantum.
In the range (1..1024)
Ignored if not using CAN-FD.
:param int nom_tseg1:
Time segment 1 for nominal bit rate,
that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
In the range (1..256).
Ignored if not using CAN-FD.
:param int nom_tseg2:
Time segment 2 for nominal bit rate,
that is, the number of quanta from the sampling
point to the end of the bit.
In the range (1..128).
Ignored if not using CAN-FD.
:param int nom_sjw:
Synchronization Jump Width for nominal bit rate.
Decides the maximum number of time quanta
that the controller can resynchronize every bit.
In the range (1..128).
Ignored if not using CAN-FD.
:param int data_brp:
Clock prescaler for fast data time quantum.
In the range (1..1024)
Ignored if not using CAN-FD.
:param int data_tseg1:
Time segment 1 for fast data bit rate,
that is, the number of quanta from (but not including)
the Sync Segment to the sampling point.
In the range (1..32).
Ignored if not using CAN-FD.
:param int data_tseg2:
Time segment 2 for fast data bit rate,
that is, the number of quanta from the sampling
point to the end of the bit.
In the range (1..16).
Ignored if not using CAN-FD.
:param int data_sjw:
Synchronization Jump Width for fast data bit rate.
Decides the maximum number of time quanta
that the controller can resynchronize every bit.
In the range (1..16).
Ignored if not using CAN-FD.
:param bool auto_reset:
Enable automatic recovery in bus off scenario.
Resetting the driver takes ~500ms during which
it will not be responsive.
"""
self.m_objPCANBasic = PCANBasic()
if device_id is not None:
channel = self._find_channel_by_dev_id(device_id)
if channel is None:
err_msg = f"Cannot find a channel with ID {device_id:08x}"
raise ValueError(err_msg)
is_fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False)
self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20
self.channel_info = str(channel)
hwtype = PCAN_TYPE_ISA
ioport = 0x02A0
interrupt = 11
if not isinstance(channel, int):
channel = PCAN_CHANNEL_NAMES[channel]
self.m_PcanHandle = channel
self.check_api_version()
if state in [BusState.ACTIVE, BusState.PASSIVE]:
self.state = state
else:
raise ValueError("BusState must be Active or Passive")
if isinstance(timing, BitTiming):
timing = check_or_adjust_timing_clock(timing, VALID_PCAN_CAN_CLOCKS)
pcan_bitrate = TPCANBaudrate(timing.btr0 << 8 | timing.btr1)
result = self.m_objPCANBasic.Initialize(
self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt
)
elif is_fd:
if isinstance(timing, BitTimingFd):
timing = check_or_adjust_timing_clock(
timing, sorted(VALID_PCAN_FD_CLOCKS, reverse=True)
)
# We dump the timing parameters into the kwargs because they have equal names
# as the kwargs parameters and this saves us one additional code path
kwargs.update(timing)
clock_param = "f_clock" if "f_clock" in kwargs else "f_clock_mhz"
fd_parameters_values = [
f"{key}={kwargs[key]}"
for key in (clock_param, *PCAN_FD_PARAMETER_LIST)
if key in kwargs
]
self.fd_bitrate = ", ".join(fd_parameters_values).encode("ascii")
result = self.m_objPCANBasic.InitializeFD(
self.m_PcanHandle, self.fd_bitrate
)
else:
pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K)
result = self.m_objPCANBasic.Initialize(
self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt
)
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON
)
if result != PCAN_ERROR_OK:
if platform.system() != "Darwin":
raise PcanCanInitializationError(self._get_formatted_error(result))
else:
# TODO Remove Filter when MACCan actually supports it:
# https://github.com/mac-can/PCBUSB-Library/
log.debug(
"Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.11.2"
)
if receive_own_messages:
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_ALLOW_ECHO_FRAMES, PCAN_PARAMETER_ON
)
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
if kwargs.get("auto_reset", False):
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_BUSOFF_AUTORESET, PCAN_PARAMETER_ON
)
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
if HAS_EVENTS:
if IS_WINDOWS:
self._recv_event = CreateEvent(None, 0, 0, None)
result = self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event
)
elif IS_LINUX:
result, self._recv_event = self.m_objPCANBasic.GetValue(
self.m_PcanHandle, PCAN_RECEIVE_EVENT
)
if result != PCAN_ERROR_OK:
raise PcanCanInitializationError(self._get_formatted_error(result))
super().__init__(
channel=channel,
state=state,
bitrate=bitrate,
**kwargs,
)
def _find_channel_by_dev_id(self, device_id):
"""
Iterate over all possible channels to find a channel that matches the device
ID. This method is somewhat brute force, but the Basic API only offers a
suitable API call since V4.4.0.
:param device_id: The device_id for which to search for
:return: The name of a PCAN channel that matches the device ID, or None if
no channel can be found.
"""
for ch_name, ch_handle in PCAN_CHANNEL_NAMES.items():
err, cur_dev_id = self.m_objPCANBasic.GetValue(
ch_handle, PCAN_DEVICE_NUMBER
)
if err != PCAN_ERROR_OK:
continue
if cur_dev_id == device_id:
return ch_name
return None
def _get_formatted_error(self, error):
"""
Gets the text using the GetErrorText API function.
If the function call succeeds, the translated error is returned. If it fails,
a text describing the current error is returned. Multiple errors may
be present in which case their individual messages are included in the
return string, one line per error.
"""
def bits(n):
"""
Iterate over all the set bits in `n`, returning the masked bits at
the set indices
"""
while n:
# Create a mask to mask the lowest set bit in n
mask = ~n + 1
masked_value = n & mask
yield masked_value
# Toggle the lowest set bit
n ^= masked_value
stsReturn = self.m_objPCANBasic.GetErrorText(error, 0x9)
if stsReturn[0] != PCAN_ERROR_OK:
strings = []
for b in bits(error):
stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9)
if stsReturn[0] != PCAN_ERROR_OK:
text = f"An error occurred. Error-code's text ({error:X}h) couldn't be retrieved"
else:
text = stsReturn[1].decode("utf-8", errors="replace")
strings.append(text)
complete_text = "\n".join(strings)
else:
complete_text = stsReturn[1].decode("utf-8", errors="replace")
return complete_text
def get_api_version(self):
error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION)
if error != PCAN_ERROR_OK:
raise CanInitializationError("Failed to read pcan basic api version")
# fix https://github.com/hardbyte/python-can/issues/1642
version_string = value.decode("ascii").replace(",", ".").replace(" ", "")
return version.parse(version_string)
def check_api_version(self):
apv = self.get_api_version()
if apv < MIN_PCAN_API_VERSION:
log.warning(
f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}."
f" Installed version is {apv}. Consider upgrade of pcan basic package"
)
def status(self):
"""
Query the PCAN bus status.
:rtype: int
:return: The status code. See values in **basic.PCAN_ERROR_**
"""
return self.m_objPCANBasic.GetStatus(self.m_PcanHandle)
def status_is_ok(self):
"""
Convenience method to check that the bus status is OK
"""
status = self.status()
return status == PCAN_ERROR_OK
def reset(self):
"""
Command the PCAN driver to reset the bus after an error.
"""
status = self.m_objPCANBasic.Reset(self.m_PcanHandle)
return status == PCAN_ERROR_OK
def get_device_number(self):
"""
Return the PCAN device number.
:rtype: int
:return: PCAN device number
"""
error, value = self.m_objPCANBasic.GetValue(
self.m_PcanHandle, PCAN_DEVICE_NUMBER
)
if error != PCAN_ERROR_OK:
return None
return value
def set_device_number(self, device_number):
"""
Set the PCAN device number.
:param device_number: new PCAN device number
:rtype: bool
:return: True if device number set successfully
"""
try:
if (
self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_DEVICE_NUMBER, int(device_number)
)
!= PCAN_ERROR_OK
):
raise ValueError()
except ValueError:
log.error("Invalid value '%s' for device number.", device_number)
return False
return True
def _recv_internal(
self, timeout: Optional[float]
) -> Tuple[Optional[Message], bool]:
end_time = time.time() + timeout if timeout is not None else None
while True:
if self._can_protocol is CanProtocol.CAN_FD:
result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD(
self.m_PcanHandle
)
else:
result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.Read(
self.m_PcanHandle
)
if result == PCAN_ERROR_OK:
# message received
break
if result == PCAN_ERROR_QRCVEMPTY:
# receive queue is empty, wait or return on timeout
if end_time is None:
time_left: Optional[float] = None
timed_out = False
else:
time_left = max(0.0, end_time - time.time())
timed_out = time_left == 0.0
if timed_out:
return None, False
if not HAS_EVENTS:
# polling mode
time.sleep(0.001)
continue
if IS_WINDOWS:
# Windows with event
if time_left is None:
time_left_ms = INFINITE
else:
time_left_ms = int(time_left * 1000)
_ret = WaitForSingleObject(self._recv_event, time_left_ms)
if _ret == WAIT_OBJECT_0:
continue
elif IS_LINUX:
# Linux with event
recv, _, _ = select.select([self._recv_event], [], [], time_left)
if self._recv_event in recv:
continue
elif result & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY):
log.warning(self._get_formatted_error(result))
elif result == PCAN_ERROR_ILLDATA:
# When there is an invalid frame on CAN bus (in our case CAN FD), PCAN first reports result PCAN_ERROR_ILLDATA
# and then it sends the error frame. If the PCAN_ERROR_ILLDATA is not ignored, python-can throws an exception.
# So we ignore any PCAN_ERROR_ILLDATA results here.
pass
else:
raise PcanCanOperationError(self._get_formatted_error(result))
return None, False
is_extended_id = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value)
is_remote_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_RTR.value)
is_fd = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_FD.value)
is_rx = not bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ECHO.value)
bitrate_switch = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_BRS.value)
error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value)
is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value)
if self._can_protocol is CanProtocol.CAN_FD:
dlc = dlc2len(pcan_msg.DLC)
timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0))
else:
dlc = pcan_msg.LEN
timestamp = boottimeEpoch + (
(
pcan_timestamp.micros
+ 1000 * pcan_timestamp.millis
+ 0x100000000 * 1000 * pcan_timestamp.millis_overflow
)
/ (1000.0 * 1000.0)
)
rx_msg = Message(
timestamp=timestamp,
arbitration_id=pcan_msg.ID,
is_extended_id=is_extended_id,
is_remote_frame=is_remote_frame,
is_error_frame=is_error_frame,
dlc=dlc,
data=pcan_msg.DATA[:dlc],
is_fd=is_fd,
is_rx=is_rx,
bitrate_switch=bitrate_switch,
error_state_indicator=error_state_indicator,
)
return rx_msg, False
def send(self, msg, timeout=None):
msgType = (
PCAN_MESSAGE_EXTENDED.value
if msg.is_extended_id
else PCAN_MESSAGE_STANDARD.value
)
if msg.is_remote_frame:
msgType |= PCAN_MESSAGE_RTR.value
if msg.is_error_frame:
msgType |= PCAN_MESSAGE_ERRFRAME.value
if msg.is_fd:
msgType |= PCAN_MESSAGE_FD.value
if msg.bitrate_switch:
msgType |= PCAN_MESSAGE_BRS.value
if msg.error_state_indicator:
msgType |= PCAN_MESSAGE_ESI.value
if self._can_protocol is CanProtocol.CAN_FD:
# create a TPCANMsg message structure
CANMsg = TPCANMsgFD()
# configure the message. ID, Length of data, message type and data
CANMsg.ID = msg.arbitration_id
CANMsg.DLC = len2dlc(msg.dlc)
CANMsg.MSGTYPE = msgType
# copy data
CANMsg.DATA[: msg.dlc] = msg.data[: msg.dlc]
log.debug("Data: %s", msg.data)
log.debug("Type: %s", type(msg.data))
result = self.m_objPCANBasic.WriteFD(self.m_PcanHandle, CANMsg)
else:
# create a TPCANMsg message structure
CANMsg = TPCANMsg()
# configure the message. ID, Length of data, message type and data
CANMsg.ID = msg.arbitration_id
CANMsg.LEN = msg.dlc
CANMsg.MSGTYPE = msgType
# if a remote frame will be sent, data bytes are not important.
if not msg.is_remote_frame:
# copy data
CANMsg.DATA[: CANMsg.LEN] = msg.data[: CANMsg.LEN]
log.debug("Data: %s", msg.data)
log.debug("Type: %s", type(msg.data))
result = self.m_objPCANBasic.Write(self.m_PcanHandle, CANMsg)
if result != PCAN_ERROR_OK:
raise PcanCanOperationError(
"Failed to send: " + self._get_formatted_error(result)
)
def flash(self, flash):
"""
Turn on or off flashing of the device's LED for physical
identification purposes.
"""
self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash)
)
def shutdown(self):
super().shutdown()
if HAS_EVENTS and IS_LINUX:
self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_RECEIVE_EVENT, 0)
self.m_objPCANBasic.Uninitialize(self.m_PcanHandle)
@property
def fd(self) -> bool:
class_name = self.__class__.__name__
warnings.warn(
f"The {class_name}.fd property is deprecated and superseded by {class_name}.protocol. "
"It is scheduled for removal in python-can version 5.0.",
DeprecationWarning,
stacklevel=2,
)
return self._can_protocol is CanProtocol.CAN_FD
@property
def state(self):
return self._state
@state.setter
def state(self, new_state):
# declare here, which is called by __init__()
self._state = new_state # pylint: disable=attribute-defined-outside-init
if new_state is BusState.ACTIVE:
self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF
)
elif new_state is BusState.PASSIVE:
# When this mode is set, the CAN controller does not take part on active events (eg. transmit CAN messages)
# but stays in a passive mode (CAN monitor), in which it can analyse the traffic on the CAN bus used by a
# PCAN channel. See also the Philips Data Sheet "SJA1000 Stand-alone CAN controller".
self.m_objPCANBasic.SetValue(
self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_ON
)
@staticmethod
def _detect_available_configs():
channels = []
try:
library_handle = PCANBasic()
except OSError:
return channels
interfaces = []
if platform.system() != "Darwin":
res, value = library_handle.GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS)
if res != PCAN_ERROR_OK:
return interfaces
channel_information: List[TPCANChannelInformation] = list(value)
for channel in channel_information:
# find channel name in PCAN_CHANNEL_NAMES by value
channel_name = next(
_channel_name
for _channel_name, channel_id in PCAN_CHANNEL_NAMES.items()
if channel_id.value == channel.channel_handle
)
channel_config = {
"interface": "pcan",
"channel": channel_name,
"supports_fd": bool(channel.device_features & FEATURE_FD_CAPABLE),
"controller_number": channel.controller_number,
"device_features": channel.device_features,
"device_id": channel.device_id,
"device_name": channel.device_name.decode("latin-1"),
"device_type": channel.device_type,
"channel_condition": channel.channel_condition,
}
interfaces.append(channel_config)
return interfaces
for i in range(16):
interfaces.append(
{
"id": TPCANHandle(PCAN_PCIBUS1.value + i),
"name": "PCAN_PCIBUS" + str(i + 1),
}
)
for i in range(16):
interfaces.append(
{
"id": TPCANHandle(PCAN_USBBUS1.value + i),
"name": "PCAN_USBBUS" + str(i + 1),
}
)
for i in range(2):
interfaces.append(
{
"id": TPCANHandle(PCAN_PCCBUS1.value + i),
"name": "PCAN_PCCBUS" + str(i + 1),
}
)
for i in range(16):
interfaces.append(
{
"id": TPCANHandle(PCAN_LANBUS1.value + i),
"name": "PCAN_LANBUS" + str(i + 1),
}
)
for i in interfaces:
try:
error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_CONDITION)
if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE:
continue
has_fd = False
error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_FEATURES)
if error == PCAN_ERROR_OK:
has_fd = bool(value & FEATURE_FD_CAPABLE)
channels.append(
{"interface": "pcan", "channel": i["name"], "supports_fd": has_fd}
)
except AttributeError: # Ignore if this fails for some interfaces
pass
return channels
def status_string(self) -> Optional[str]:
"""
Query the PCAN bus status.
:return: The status description, if any was found.
"""
try:
return PCAN_DICT_STATUS[self.status()]
except KeyError:
return None
class PcanError(CanError):
"""A generic error on a PCAN bus."""
class PcanCanOperationError(CanOperationError, PcanError):
"""Like :class:`can.exceptions.CanOperationError`, but specific to Pcan."""
class PcanCanInitializationError(CanInitializationError, PcanError):
"""Like :class:`can.exceptions.CanInitializationError`, but specific to Pcan."""
python-can-4.5.0/can/interfaces/robotell.py 0000664 0000000 0000000 00000036614 14722003266 0020674 0 ustar 00root root 0000000 0000000 """
Interface for Chinese Robotell compatible interfaces (win32/linux).
"""
import io
import logging
import time
from typing import Optional
from can import BusABC, CanProtocol, Message
from ..exceptions import CanInterfaceNotImplementedError, CanOperationError
logger = logging.getLogger(__name__)
try:
import serial
except ImportError:
logger.warning(
"You won't be able to use the Robotell can backend without "
"the serial module installed!"
)
serial = None
class robotellBus(BusABC):
"""
robotell interface
"""
_PACKET_HEAD = 0xAA # Frame starts with 2x FRAME_HEAD bytes
_PACKET_TAIL = 0x55 # Frame ends with 2x FRAME_END bytes
_PACKET_ESC = (
0xA5 # Escape char before any HEAD, TAIL or ESC chat (including in checksum)
)
_CAN_CONFIG_CHANNEL = 0xFF # Configuration channel of CAN
_CAN_SERIALBPS_ID = 0x01FFFE90 # USB Serial port speed
_CAN_ART_ID = 0x01FFFEA0 # Automatic retransmission
_CAN_ABOM_ID = 0x01FFFEB0 # Automatic bus management
_CAN_RESET_ID = 0x01FFFEC0 # ID for initialization
_CAN_BAUD_ID = 0x01FFFED0 # CAN baud rate
_CAN_FILTER_BASE_ID = 0x01FFFEE0 # ID for first filter (filter0)
_CAN_FILTER_MAX_ID = 0x01FFFEE0 + 13 # ID for the last filter (filter13)
_CAN_INIT_FLASH_ID = 0x01FFFEFF # Restore factory settings
_CAN_READ_SERIAL1 = 0x01FFFFF0 # Read first part of device serial number
_CAN_READ_SERIAL2 = 0x01FFFFF1 # Read first part of device serial number
_MAX_CAN_BAUD = 1000000 # Maximum supported CAN baud rate
_FILTER_ID_MASK = 0x0000000F # Filter ID mask
_CAN_FILTER_EXTENDED = 0x40000000 # Enable mask
_CAN_FILTER_ENABLE = 0x80000000 # Enable filter
_CAN_STANDARD_FMT = 0 # Standard message ID
_CAN_EXTENDED_FMT = 1 # 29 Bit extended format ID
_CAN_DATA_FRAME = 0 # Send data frame
_CAN_REMOTE_FRAME = 1 # Request remote frame
def __init__(
self, channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs
):
"""
:param str channel:
port of underlying serial or usb device (e.g. ``/dev/ttyUSB0``, ``COM8``, ...)
Must not be empty. Can also end with ``@115200`` (or similarly) to specify the baudrate.
:param int ttyBaudrate:
baudrate of underlying serial or usb device (Ignored if set via the ``channel`` parameter)
:param int bitrate:
CAN Bitrate in bit/s. Value is stored in the adapter and will be used as default if no bitrate is specified
:param bool rtscts:
turn hardware handshake (RTS/CTS) on and off
"""
if serial is None:
raise CanInterfaceNotImplementedError("The serial module is not installed")
if not channel: # if None or empty
raise TypeError("Must specify a serial port.")
if "@" in channel:
(channel, ttyBaudrate) = channel.split("@")
self.serialPortOrig = serial.serial_for_url(
channel, baudrate=ttyBaudrate, rtscts=rtscts
)
# Disable flushing queued config ACKs on lookup channel (for unit tests)
self._loopback_test = channel == "loop://"
self._rxbuffer = bytearray() # raw bytes from the serial port
self._rxmsg = [] # extracted CAN messages waiting to be read
self._configmsg = [] # extracted config channel messages
self._writeconfig(self._CAN_RESET_ID, 0) # Not sure if this is really necessary
if bitrate is not None:
self.set_bitrate(bitrate)
self._can_protocol = CanProtocol.CAN_20
self.channel_info = (
f"Robotell USB-CAN s/n {self.get_serial_number(1)} on {channel}"
)
logger.info("Using device: %s", self.channel_info)
super().__init__(channel=channel, **kwargs)
def set_bitrate(self, bitrate):
"""
:raise ValueError: if *bitrate* is greater than 1000000
:param int bitrate:
Bitrate in bit/s
"""
if bitrate <= self._MAX_CAN_BAUD:
self._writeconfig(self._CAN_BAUD_ID, bitrate)
else:
raise ValueError(f"Invalid bitrate, must be less than {self._MAX_CAN_BAUD}")
def set_auto_retransmit(self, retrans_flag):
"""
:param bool retrans_flag:
Enable/disable automatic retransmission of unacknowledged CAN frames
"""
self._writeconfig(self._CAN_ART_ID, 1 if retrans_flag else 0)
def set_auto_bus_management(self, auto_man):
"""
:param bool auto_man:
Enable/disable automatic bus management
"""
# Not sure what "automatic bus management" does. Does not seem to control
# automatic ACK of CAN frames (listen only mode)
self._writeconfig(self._CAN_ABOM_ID, 1 if auto_man else 0)
def set_serial_rate(self, serial_bps):
"""
:param int serial_bps:
Set the baud rate of the serial port (not CAN) interface
"""
self._writeconfig(self._CAN_SERIALBPS_ID, serial_bps)
def set_hw_filter(self, filterid, enabled, msgid_value, msgid_mask, extended_msg):
"""
:raise ValueError: if *filterid* is not between 1 and 14
:param int filterid:
ID of filter (1-14)
:param bool enabled:
This filter is enabled
:param int msgid_value:
CAN message ID to filter on. The test unit does not accept an extented message ID unless bit 31 of the ID was set.
:param int msgid_mask:
Mask to apply to CAN messagge ID
:param bool extended_msg:
Filter operates on extended format messages
"""
if filterid < 1 or filterid > 14:
raise ValueError("Invalid filter ID. ID must be between 0 and 13")
else:
configid = self._CAN_FILTER_BASE_ID + (filterid - 1)
msgid_value += self._CAN_FILTER_ENABLE if enabled else 0
msgid_value += self._CAN_FILTER_EXTENDED if extended_msg else 0
self._writeconfig(configid, msgid_value, msgid_mask)
def _getconfigsize(self, configid):
if configid == self._CAN_ART_ID or configid == self._CAN_ABOM_ID:
return 1
if configid == self._CAN_BAUD_ID or configid == self._CAN_INIT_FLASH_ID:
return 4
if configid == self._CAN_SERIALBPS_ID:
return 4
if configid == self._CAN_READ_SERIAL1 or configid <= self._CAN_READ_SERIAL2:
return 8
if self._CAN_FILTER_BASE_ID <= configid <= self._CAN_FILTER_MAX_ID:
return 8
return 0
def _readconfig(self, configid, timeout):
self._writemessage(
msgid=configid,
msgdata=bytearray(8),
datalen=self._getconfigsize(configid),
msgchan=self._CAN_CONFIG_CHANNEL,
msgformat=self._CAN_EXTENDED_FMT,
msgtype=self._CAN_REMOTE_FRAME,
)
# Read message from config channel with result. Flush any previously pending config messages
newmsg = self._readmessage(not self._loopback_test, True, timeout)
if newmsg is None:
logger.warning(
f"Timeout waiting for response when reading config value {configid:04X}."
)
return None
return newmsg[4:12]
def _writeconfig(self, configid, value, value2=0):
configsize = self._getconfigsize(configid)
configdata = bytearray(configsize)
if configsize >= 1:
configdata[0] = value & 0xFF
if configsize >= 4:
configdata[1] = (value >> 8) & 0xFF
configdata[2] = (value >> 16) & 0xFF
configdata[3] = (value >> 24) & 0xFF
if configsize >= 8:
configdata[4] = value2 & 0xFF
configdata[5] = (value2 >> 8) & 0xFF
configdata[6] = (value2 >> 16) & 0xFF
configdata[7] = (value2 >> 24) & 0xFF
self._writemessage(
msgid=configid,
msgdata=configdata,
datalen=configsize,
msgchan=self._CAN_CONFIG_CHANNEL,
msgformat=self._CAN_EXTENDED_FMT,
msgtype=self._CAN_DATA_FRAME,
)
# Read message from config channel to verify. Flush any previously pending config messages
newmsg = self._readmessage(not self._loopback_test, True, 1)
if newmsg is None:
logger.warning(
"Timeout waiting for response when writing config value %d", configid
)
def _readmessage(self, flushold, cfgchannel, timeout):
header = bytearray([self._PACKET_HEAD, self._PACKET_HEAD])
terminator = bytearray([self._PACKET_TAIL, self._PACKET_TAIL])
msgqueue = self._configmsg if cfgchannel else self._rxmsg
if flushold:
del msgqueue[:]
# read what is already in serial port receive buffer - unless we are doing loopback testing
if not self._loopback_test:
while self.serialPortOrig.in_waiting:
self._rxbuffer += self.serialPortOrig.read()
# loop until we have read an appropriate message
start = time.time()
time_left = timeout
while True:
# make sure first bytes in RX buffer is a new packet header
headpos = self._rxbuffer.find(header)
if headpos > 0:
# data does not start with expected header bytes. Log error and ignore garbage
logger.warning("Ignoring extra " + str(headpos) + " garbage bytes")
del self._rxbuffer[:headpos]
headpos = self._rxbuffer.find(header) # should now be at index 0!
# check to see if we have a complete packet in the RX buffer
termpos = self._rxbuffer.find(terminator)
if headpos == 0 and termpos > headpos:
# copy packet into message structure and un-escape bytes
newmsg = bytearray()
idx = headpos + len(header)
while idx < termpos:
if self._rxbuffer[idx] == self._PACKET_ESC:
idx += 1
newmsg.append(self._rxbuffer[idx])
idx += 1
del self._rxbuffer[: termpos + len(terminator)]
# Check one - make sure message structure is the correct length
if len(newmsg) == 17:
# Check two - verify the checksum
cs = 0
for idx in range(16):
cs = (cs + newmsg[idx]) & 0xFF
if newmsg[16] == cs:
# OK, valid message - place it in the correct queue
if newmsg[13] == 0xFF: # Check for config channel
self._configmsg.append(newmsg)
else:
self._rxmsg.append(newmsg)
else:
logger.warning("Incorrect message checksum, discarded message")
else:
logger.warning(
"Invalid message structure length %d, ignoring message",
len(newmsg),
)
# Check if we have a message in the desired queue - if so copy and return
if len(msgqueue) > 0:
newmsg = msgqueue[0]
del msgqueue[:1]
return newmsg
# if we still don't have a complete message, do a blocking read
self.serialPortOrig.timeout = time_left
byte = self.serialPortOrig.read()
if byte:
self._rxbuffer += byte
# If there is time left, try next one with reduced timeout
if timeout is not None:
time_left = timeout - (time.time() - start)
if time_left <= 0:
return None
def _writemessage(self, msgid, msgdata, datalen, msgchan, msgformat, msgtype):
msgbuf = bytearray(17) # Message structure plus checksum byte
msgbuf[0] = msgid & 0xFF
msgbuf[1] = (msgid >> 8) & 0xFF
msgbuf[2] = (msgid >> 16) & 0xFF
msgbuf[3] = (msgid >> 24) & 0xFF
if msgtype == self._CAN_DATA_FRAME:
for idx in range(datalen):
msgbuf[idx + 4] = msgdata[idx]
msgbuf[12] = datalen
msgbuf[13] = msgchan
msgbuf[14] = msgformat
msgbuf[15] = msgtype
cs = 0
for idx in range(16):
cs = (cs + msgbuf[idx]) & 0xFF
msgbuf[16] = cs
packet = bytearray()
packet.append(self._PACKET_HEAD)
packet.append(self._PACKET_HEAD)
for msgbyte in msgbuf:
if (
msgbyte == self._PACKET_ESC
or msgbyte == self._PACKET_HEAD
or msgbyte == self._PACKET_TAIL
):
packet.append(self._PACKET_ESC)
packet.append(msgbyte)
packet.append(self._PACKET_TAIL)
packet.append(self._PACKET_TAIL)
self.serialPortOrig.write(packet)
self.serialPortOrig.flush()
def flush(self):
del self._rxbuffer[:]
del self._rxmsg[:]
del self._configmsg[:]
while self.serialPortOrig.in_waiting:
self.serialPortOrig.read()
def _recv_internal(self, timeout):
msgbuf = self._readmessage(False, False, timeout)
if msgbuf is not None:
msg = Message(
arbitration_id=msgbuf[0]
+ (msgbuf[1] << 8)
+ (msgbuf[2] << 16)
+ (msgbuf[3] << 24),
is_extended_id=(msgbuf[14] == self._CAN_EXTENDED_FMT),
timestamp=time.time(), # Better than nothing...
is_remote_frame=(msgbuf[15] == self._CAN_REMOTE_FRAME),
dlc=msgbuf[12],
data=msgbuf[4 : 4 + msgbuf[12]],
)
return msg, False
return None, False
def send(self, msg, timeout=None):
if timeout != self.serialPortOrig.write_timeout:
self.serialPortOrig.write_timeout = timeout
self._writemessage(
msg.arbitration_id,
msg.data,
msg.dlc,
0,
self._CAN_EXTENDED_FMT if msg.is_extended_id else self._CAN_STANDARD_FMT,
self._CAN_REMOTE_FRAME if msg.is_remote_frame else self._CAN_DATA_FRAME,
)
def shutdown(self):
super().shutdown()
self.serialPortOrig.close()
def fileno(self):
try:
return self.serialPortOrig.fileno()
except io.UnsupportedOperation:
raise NotImplementedError(
"fileno is not implemented using current CAN bus on this platform"
) from None
except Exception as exception:
raise CanOperationError("Cannot fetch fileno") from exception
def get_serial_number(self, timeout: Optional[int]) -> Optional[str]:
"""Get serial number of the slcan interface.
:param timeout:
seconds to wait for serial number or None to wait indefinitely
:return:
None on timeout or a str object.
"""
sn1 = self._readconfig(self._CAN_READ_SERIAL1, timeout)
if sn1 is None:
return None
sn2 = self._readconfig(self._CAN_READ_SERIAL2, timeout)
if sn2 is None:
return None
serial = ""
for idx in range(0, 8, 2):
serial += f"{sn1[idx]:02X}{sn1[idx + 1]:02X}-"
for idx in range(0, 4, 2):
serial += f"{sn2[idx]:02X}{sn2[idx + 1]:02X}-"
return serial[:-1]
python-can-4.5.0/can/interfaces/seeedstudio/ 0000775 0000000 0000000 00000000000 14722003266 0021003 5 ustar 00root root 0000000 0000000 python-can-4.5.0/can/interfaces/seeedstudio/__init__.py 0000664 0000000 0000000 00000000167 14722003266 0023120 0 ustar 00root root 0000000 0000000 """
"""
__all__ = [
"SeeedBus",
"seeedstudio",
]
from can.interfaces.seeedstudio.seeedstudio import SeeedBus
python-can-4.5.0/can/interfaces/seeedstudio/seeedstudio.py 0000664 0000000 0000000 00000022612 14722003266 0023675 0 ustar 00root root 0000000 0000000 """
To Support the Seeed USB-Can analyzer interface. The device will appear
as a serial port, for example "/dev/ttyUSB0" on Linux machines
or "COM1" on Windows.
https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html
SKU 114991193
"""
import io
import logging
import struct
from time import time
import can
from can import BusABC, CanProtocol, Message
logger = logging.getLogger("seeedbus")
try:
import serial
except ImportError:
logger.warning(
"You won't be able to use the serial can backend without "
"the serial module installed!"
)
serial = None
class SeeedBus(BusABC):
"""
Enable basic can communication over a USB-CAN-Analyzer device.
"""
BITRATE = {
1000000: 0x01,
800000: 0x02,
500000: 0x03,
400000: 0x04,
250000: 0x05,
200000: 0x06,
125000: 0x07,
100000: 0x08,
50000: 0x09,
20000: 0x0A,
10000: 0x0B,
5000: 0x0C,
}
FRAMETYPE = {"STD": 0x01, "EXT": 0x02}
OPERATIONMODE = {
"normal": 0x00,
"loopback": 0x01,
"silent": 0x02,
"loopback_and_silent": 0x03,
}
def __init__(
self,
channel,
baudrate=2000000,
timeout=0.1,
frame_type="STD",
operation_mode="normal",
bitrate=500000,
**kwargs,
):
"""
:param str channel:
The serial device to open. For example "/dev/ttyS1" or
"/dev/ttyUSB0" on Linux or "COM1" on Windows systems.
:param baudrate:
The default matches required baudrate
:param float timeout:
Timeout for the serial device in seconds (default 0.1).
:param str frame_type:
STD or EXT, to select standard or extended messages
:param operation_mode
normal, loopback, silent or loopback_and_silent.
:param bitrate
CAN bus bit rate, selected from available list.
:raises can.CanInitializationError: If the given parameters are invalid.
:raises can.CanInterfaceNotImplementedError: If the serial module is not installed.
"""
if serial is None:
raise can.CanInterfaceNotImplementedError(
"the serial module is not installed"
)
self.bit_rate = bitrate
self.frame_type = frame_type
self.op_mode = operation_mode
self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00])
self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00])
self._can_protocol = CanProtocol.CAN_20
if not channel:
raise can.CanInitializationError("Must specify a serial port.")
self.channel_info = "Serial interface: " + channel
try:
self.ser = serial.Serial(
channel, baudrate=baudrate, timeout=timeout, rtscts=False
)
except ValueError as error:
raise can.CanInitializationError(
"could not create the serial device"
) from error
super().__init__(channel=channel, **kwargs)
self.init_frame()
def shutdown(self):
"""
Close the serial interface.
"""
super().shutdown()
self.ser.close()
def init_frame(self, timeout=None):
"""
Send init message to setup the device for comms. this is called during
interface creation.
:param timeout:
This parameter will be ignored. The timeout value of the channel is
used instead.
"""
byte_msg = bytearray()
byte_msg.append(0xAA) # Frame Start Byte 1
byte_msg.append(0x55) # Frame Start Byte 2
byte_msg.append(0x12) # Initialization Message ID
byte_msg.append(SeeedBus.BITRATE[self.bit_rate]) # CAN Baud Rate
byte_msg.append(SeeedBus.FRAMETYPE[self.frame_type])
byte_msg.extend(self.filter_id)
byte_msg.extend(self.mask_id)
byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode])
byte_msg.append(0x01) # Follows 'Send once' in windows app.
byte_msg.extend([0x00] * 4) # Manual bitrate config, details unknown.
crc = sum(byte_msg[2:]) & 0xFF
byte_msg.append(crc)
logger.debug("init_frm:\t%s", byte_msg.hex())
try:
self.ser.write(byte_msg)
except Exception as error:
raise can.CanInitializationError("could send init frame") from error
def flush_buffer(self):
self.ser.flushInput()
def status_frame(self, timeout=None):
"""
Send status request message over the serial device. The device will
respond but details of error codes are unknown but are logged - DEBUG.
:param timeout:
This parameter will be ignored. The timeout value of the channel is
used instead.
"""
byte_msg = bytearray()
byte_msg.append(0xAA) # Frame Start Byte 1
byte_msg.append(0x55) # Frame Start Byte 2
byte_msg.append(0x04) # Status Message ID
byte_msg.append(0x00) # In response packet - Rx error count
byte_msg.append(0x00) # In response packet - Tx error count
byte_msg.extend([0x00] * 14)
crc = sum(byte_msg[2:]) & 0xFF
byte_msg.append(crc)
logger.debug("status_frm:\t%s", byte_msg.hex())
self._write(byte_msg)
def send(self, msg, timeout=None):
"""
Send a message over the serial device.
:param can.Message msg:
Message to send.
:param timeout:
This parameter will be ignored. The timeout value of the channel is
used instead.
"""
byte_msg = bytearray()
byte_msg.append(0xAA)
m_type = 0xC0
if msg.is_extended_id:
m_type += 1 << 5
if msg.is_remote_frame:
m_type += 1 << 4
m_type += msg.dlc
byte_msg.append(m_type)
if msg.is_extended_id:
a_id = struct.pack(" None:
try:
self.ser.write(byte_msg)
except serial.PortNotOpenError as error:
raise can.CanOperationError("writing to closed port") from error
except serial.SerialTimeoutException as error:
raise can.CanTimeoutError() from error
def _recv_internal(self, timeout):
"""
Read a message from the serial device.
:param timeout:
.. warning::
This parameter will be ignored. The timeout value of the
channel is used.
:returns:
Received message and False (because not filtering as taken place).
:rtype:
can.Message, bool
"""
try:
# ser.read can return an empty string
# or raise a SerialException
rx_byte_1 = self.ser.read()
except serial.PortNotOpenError as error:
raise can.CanOperationError("reading from closed port") from error
except serial.SerialException:
return None, False
if rx_byte_1 and ord(rx_byte_1) == 0xAA:
try:
rx_byte_2 = ord(self.ser.read())
time_stamp = time()
if rx_byte_2 == 0x55:
status = bytearray([0xAA, 0x55])
status += bytearray(self.ser.read(18))
logger.debug("status resp:\t%s", status.hex())
else:
length = int(rx_byte_2 & 0x0F)
is_extended = bool(rx_byte_2 & 0x20)
is_remote = bool(rx_byte_2 & 0x10)
if is_extended:
s_3_4_5_6 = bytearray(self.ser.read(4))
arb_id = (struct.unpack("