diff --git a/.appveyor.yml b/.appveyor.yml index f65386f3e..500c71320 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,14 +3,10 @@ environment: # For Python versions available on Appveyor, see # https://www.appveyor.com/docs/windows-images-software/#python - # Python pre-2.7 and 3.0-3.4 have reached EOL + # Only Python 3.6+ is supported - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python27-x64" - - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37-x64" diff --git a/.gitignore b/.gitignore index 6b813427e..258ca73ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ test/__tempdir__/ .pytest_cache/ +.mypy_cache/ +.dmypy.json +dmypy.json # ------------------------- # below: https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..5a2613994 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,570 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.pylintrc-wip b/.pylintrc-wip new file mode 100644 index 000000000..c028f9f3d --- /dev/null +++ b/.pylintrc-wip @@ -0,0 +1,534 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=invalid-name, + missing-docstring, + empty-docstring, + line-too-long, + too-many-lines, + bad-whitespace, + bad-continuation, + wrong-import-order, + ungrouped-imports, + wrong-import-position, + too-many-function-args, + locally-disabled, + too-many-instance-attributes, + too-few-public-methods, + too-many-public-methods, + too-many-branches, + too-many-arguments, + too-many-locals, + too-many-statements, + too-many-nested-blocks, + no-else-raise, + bad-indentation, + wildcard-import, + fixme, + broad-except, + redefined-builtin, + no-else-return, + redefined-argument-from-local, + abstract-class-instantiated, + multiple-statements, + cyclic-import, + useless-else-on-loop, + duplicate-code, + inconsistent-return-statements, + arguments-differ, + unused-wildcard-import, + logging-fstring-interpolation, + logging-format-interpolation, + unused-argument, + abstract-method, + attribute-defined-outside-init, + protected-access, + keyword-arg-before-vararg + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.travis.yml b/.travis.yml index b45e17d95..76195ce51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,12 @@ cache: - "$HOME/.cache/pip" python: - # CPython; versions pre-2.7 and 3.0-3.5 have reached EOL - - "2.7" + # CPython; only 3.6 is supported - "3.6" - "3.7" - - 3.8-dev + - "3.8" - nightly # PyPy: - - pypy # Python 2.7 - - pypy3.5 # Python 3.5 - pypy3 env: @@ -42,7 +39,6 @@ jobs: allow_failures: # we allow all dev & nightly builds to fail, since these python versions might # still be very unstable - - python: 3.8-dev - python: nightly include: @@ -66,14 +62,6 @@ jobs: os: osx osx_image: xcode8.3 python: 3.6-dev - - stage: test - os: osx - osx_image: xcode8.3 - python: 3.7-dev - - stage: test - os: osx - osx_image: xcode8.3 - python: nightly - stage: documentation name: "Sphinx Build" @@ -85,6 +73,57 @@ jobs: # -a Write all files # -n nitpicky - python -m sphinx -an doc build + - stage: linter + name: "Linter Checks" + python: "3.7" + before_install: + - travis_retry pip install -r requirements-lint.txt + script: + # ------------- + # pylint checking: + # Slowly enable all pylint warnings by adding addressed classes of + # warnings to the .pylintrc-wip file to prevent them from being + # re-introduced + # check the entire main codebase + - pylint --rcfile=.pylintrc-wip can/**.py + # check setup.py + - pylint --rcfile=.pylintrc *.py + # check doc/conf.py and possible other scripts in there + - pylint --rcfile=.pylintrc doc/**.py + # check the scripts folder + - pylint --rcfile=.pylintrc scripts/**.py + # check the examples + - pylint --rcfile=.pylintrc-wip examples/**.py + # ------------- + # mypy checking: + - mypy + --python-version=3.7 + --ignore-missing-imports + --no-implicit-optional + can/bit_timing.py + can/broadcastmanager.py + can/bus.py + can/interface.py + can/interfaces/socketcan/**.py + can/interfaces/virtual.py + can/listener.py + can/logger.py + can/message.py + can/notifier.py + can/player.py + can/thread_safe_bus.py + can/typechecking.py + can/util.py + can/io/**.py + scripts/**.py + examples/**.py + - stage: linter + name: "Formatting Checks" + python: "3.7" + before_install: + - travis_retry pip install -r requirements-lint.txt + script: + - black --check --verbose . - stage: deploy name: "PyPi Deployment" python: "3.7" diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9a37da3b5..b7ac9fbd4 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -27,3 +27,4 @@ Alexander Mueller Jan Goeteyn "ykzheng" Lear Corporation +Nick Black diff --git a/README.rst b/README.rst index affcde831..9fce86c02 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,26 @@ python-can ========== -|release| |docs| |build_travis| |build_appveyor| |coverage| |downloads| +|release| |downloads| |downloads_monthly| |formatter| + +|docs| |build_travis| |build_appveyor| |coverage| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ :alt: Latest Version on PyPi +.. |downloads| image:: https://pepy.tech/badge/python-can + :target: https://pepy.tech/project/python-can + :alt: Downloads on PePy + +.. |downloads_monthly| image:: https://pepy.tech/badge/python-can/month + :target: https://pepy.tech/project/python-can/month + :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 @@ -23,10 +37,6 @@ python-can :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io -.. |downloads| image:: https://pepy.tech/badge/python-can - :target: https://pepy.tech/project/python-can - :alt: Downloads on PePy - 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 @@ -37,16 +47,16 @@ 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 supports Python 2.7, Python 3.5+ as well as PyPy 2 & 3 and runs +The library currently supports Python 3.6+ as well as PyPy 3 and runs on Mac, Linux and Windows. -================== =========== -Library Version Python ------------------- ----------- - 2.x 2.6+, 3.4+ - 3.x 2.7+, 3.5+ - 4.x (expected) 3.6+ -================== =========== +============================== =========== +Library Version Python +------------------------------ ----------- + 2.x 2.6+, 3.4+ + 3.x 2.7+, 3.5+ + 4.x *(currently on develop)* 3.6+ +============================== =========== Features @@ -85,7 +95,7 @@ Example usage # iterate over received messages for msg in bus: - print("{X}: {}".format(msg.arbitration_id, msg.data)) + print("{:X}: {}".format(msg.arbitration_id, msg.data)) # or use an asynchronous notifier notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) diff --git a/can/CAN.py b/can/CAN.py deleted file mode 100644 index 0ed96dfb1..000000000 --- a/can/CAN.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding: utf-8 - -""" -This module was once the core of python-can, containing -implementations of all the major classes in the library, now -however all functionality has been refactored out. This API -is left intact for version 2.x to aide with migration. - -WARNING: -This module is deprecated an will get removed in version 3.x. -Please use ``import can`` instead. -""" - -from __future__ import absolute_import - -from can.message import Message -from can.listener import Listener, BufferedReader, RedirectReader -from can.util import set_logging_level -from can.io import * - -import warnings - -# See #267. -# Version 2.0 - 2.1: Log a Debug message -# Version 2.2: Log a Warning -# Version 3.x: DeprecationWarning -# Version 4.0: Remove the module -warnings.warn('Loading python-can via the old "CAN" API is deprecated since v3.0 an will get removed in v4.0 ' - 'Please use `import can` instead.', DeprecationWarning) diff --git a/can/__init__.py b/can/__init__.py index a612363ae..457546307 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,32 +1,25 @@ -# coding: utf-8 - """ ``can`` is an object-orient Controller Area Network (CAN) interface module. """ -from __future__ import absolute_import - import logging +from typing import Dict, Any + __version__ = "3.2.0" -log = logging.getLogger('can') +log = logging.getLogger("can") -rc = dict() +rc: Dict[str, Any] = dict() class CanError(IOError): """Indicates an error with the CAN network. """ - pass -from .listener import Listener, BufferedReader, RedirectReader -try: - from .listener import AsyncBufferedReader -except ImportError: - pass +from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader from .io import Logger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader @@ -44,10 +37,12 @@ class CanError(IOError): from .interfaces import VALID_INTERFACES from . import interface from .interface import Bus, detect_available_configs - -from .broadcastmanager import send_periodic, \ - CyclicSendTaskABC, \ - LimitedDurationCyclicSendTaskABC, \ - ModifiableCyclicTaskABC, \ - MultiRateCyclicSendTaskABC, \ - RestartableCyclicTaskABC +from .bit_timing import BitTiming + +from .broadcastmanager import ( + CyclicSendTaskABC, + LimitedDurationCyclicSendTaskABC, + ModifiableCyclicTaskABC, + MultiRateCyclicSendTaskABC, + RestartableCyclicTaskABC, +) diff --git a/can/bit_timing.py b/can/bit_timing.py new file mode 100644 index 000000000..b0ad762fb --- /dev/null +++ b/can/bit_timing.py @@ -0,0 +1,232 @@ +from typing import Optional, Union + + +class BitTiming: + """Representation of a bit timing configuration. + + The class can be constructed in various ways, depending on the information + available or the capabilities of the interfaces that need to be supported. + + The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW:: + + can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1) + + If the clock frequency is unknown it may be omitted but some interfaces may + require it. + + Alternatively the BRP can be given instead of bitrate and clock frequency but this + will limit the number of supported interfaces. + + It is also possible specify BTR registers directly, + but will not work for all interfaces:: + + can.BitTiming(btr0=0x00, btr1=0x14) + """ + + sync_seg = 1 + + def __init__( + self, + bitrate: Optional[int] = None, + f_clock: Optional[int] = None, + brp: Optional[int] = None, + tseg1: Optional[int] = None, + tseg2: Optional[int] = None, + sjw: Optional[int] = None, + nof_samples: int = 1, + btr0: Optional[int] = None, + btr1: Optional[int] = None, + ): + """ + :param int bitrate: + Bitrate in bits/s. + :param int f_clock: + The CAN system clock frequency in Hz. + Usually the oscillator frequency divided by 2. + :param int brp: + Bit Rate Prescaler. Prefer to use bitrate and f_clock instead. + :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 int btr0: + The BTR0 register value used by many CAN controllers. + :param int btr1: + The BTR1 register value used by many CAN controllers. + """ + self._bitrate = bitrate + self._brp = brp + self._sjw = sjw + self._tseg1 = tseg1 + self._tseg2 = tseg2 + self._nof_samples = nof_samples + self._f_clock = f_clock + + if btr0 is not None: + self._brp = (btr0 & 0x3F) + 1 + self._sjw = (btr0 >> 6) + 1 + if btr1 is not None: + self._tseg1 = (btr1 & 0xF) + 1 + self._tseg2 = ((btr1 >> 4) & 0x7) + 1 + self._nof_samples = 3 if btr1 & 0x80 else 1 + + if nof_samples not in (1, 3): + raise ValueError("nof_samples must be 1 or 3") + + @property + def nbt(self) -> int: + """Nominal Bit Time.""" + return self.sync_seg + self.tseg1 + self.tseg2 + + @property + def bitrate(self) -> Union[int, float]: + """Bitrate in bits/s.""" + if self._bitrate: + return self._bitrate + if self._f_clock and self._brp: + return self._f_clock / (self._brp * self.nbt) + raise ValueError("bitrate must be specified") + + @property + def brp(self) -> int: + """Bit Rate Prescaler.""" + if self._brp: + return self._brp + if self._f_clock and self._bitrate: + return round(self._f_clock / (self._bitrate * self.nbt)) + raise ValueError("Either bitrate and f_clock or brp must be specified") + + @property + def sjw(self) -> int: + """Synchronization Jump Width.""" + if not self._sjw: + raise ValueError("sjw must be specified") + return self._sjw + + @property + def tseg1(self) -> int: + """Time segment 1. + + The number of quanta from (but not including) the Sync Segment to the sampling point. + """ + if not self._tseg1: + raise ValueError("tseg1 must be specified") + return self._tseg1 + + @property + def tseg2(self) -> int: + """Time segment 2. + + The number of quanta from the sampling point to the end of the bit. + """ + if not self._tseg2: + raise ValueError("tseg2 must be specified") + return self._tseg2 + + @property + def nof_samples(self) -> int: + """Number of samples (1 or 3).""" + if not self._nof_samples: + raise ValueError("nof_samples must be specified") + return self._nof_samples + + @property + def f_clock(self) -> int: + """The CAN system clock frequency in Hz. + + Usually the oscillator frequency divided by 2. + """ + if not self._f_clock: + raise ValueError("f_clock must be specified") + return self._f_clock + + @property + def sample_point(self) -> float: + """Sample point in percent.""" + return 100.0 * (self.nbt - self.tseg2) / self.nbt + + @property + def btr0(self) -> int: + sjw = self.sjw + brp = self.brp + + if brp < 1 or brp > 64: + raise ValueError("brp must be 1 - 64") + if sjw < 1 or sjw > 4: + raise ValueError("sjw must be 1 - 4") + + return (sjw - 1) << 6 | brp - 1 + + @property + def btr1(self) -> int: + sam = 1 if self.nof_samples == 3 else 0 + tseg1 = self.tseg1 + tseg2 = self.tseg2 + + if tseg1 < 1 or tseg1 > 16: + raise ValueError("tseg1 must be 1 - 16") + if tseg2 < 1 or tseg2 > 8: + raise ValueError("tseg2 must be 1 - 8") + + return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1 + + def __str__(self) -> str: + segments = [] + try: + segments.append(f"{self.bitrate} bits/s") + except ValueError: + pass + try: + segments.append(f"sample point: {self.sample_point:.2f}%") + except ValueError: + pass + try: + segments.append(f"BRP: {self.brp}") + except ValueError: + pass + try: + segments.append(f"TSEG1: {self.tseg1}") + except ValueError: + pass + try: + segments.append(f"TSEG2: {self.tseg2}") + except ValueError: + pass + try: + segments.append(f"SJW: {self.sjw}") + except ValueError: + pass + try: + segments.append(f"BTR: {self.btr0:02X}{self.btr1:02X}h") + except ValueError: + pass + return ", ".join(segments) + + def __repr__(self) -> str: + kwargs = {} + if self._f_clock: + kwargs["f_clock"] = self._f_clock + if self._bitrate: + kwargs["bitrate"] = self._bitrate + if self._brp: + kwargs["brp"] = self._brp + if self._tseg1: + kwargs["tseg1"] = self._tseg1 + if self._tseg2: + kwargs["tseg2"] = self._tseg2 + if self._sjw: + kwargs["sjw"] = self._sjw + if self._nof_samples != 1: + kwargs["nof_samples"] = self._nof_samples + args = ", ".join(f"{key}={value}" for key, value in kwargs.items()) + return f"can.BitTiming({args})" diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 79d586744..173727789 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Exposes several methods for transmitting cyclic messages. @@ -7,16 +5,32 @@ :meth:`can.BusABC.send_periodic`. """ +from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING + +from can import typechecking + +if TYPE_CHECKING: + from can.bus import BusABC + +from can.message import Message + import abc import logging import threading import time -import warnings -log = logging.getLogger('can.bcm') +# try to import win32event for event-based cyclic send task(needs pywin32 package) +try: + import win32event + + HAS_EVENTS = True +except ImportError: + HAS_EVENTS = False +log = logging.getLogger("can.bcm") -class CyclicTask(object): + +class CyclicTask: """ Abstract Base for all cyclic tasks. """ @@ -35,29 +49,72 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__(self, message, period): + def __init__(self, messages: Union[Sequence[Message], Message], period: float): """ - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. + :param messages: + The messages to be sent periodically. + :param period: The rate in seconds at which to send the messages. """ - self.message = message - self.can_id = message.arbitration_id - self.arbitration_id = message.arbitration_id + 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 - super(CyclicSendTaskABC, self).__init__() + 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 + """ + 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): - def __init__(self, message, period, duration): +class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): + def __init__( + self, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float], + ): """Message send task with a defined duration and period. - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. - :param float duration: - The duration to keep sending this message at given rate. + :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. """ - super(LimitedDurationCyclicSendTaskABC, self).__init__(message, period) + super().__init__(messages, period) self.duration = duration @@ -73,86 +130,153 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def modify_data(self, message): - """Update the contents of this periodically sent message without altering - the timing. + def _check_modified_messages(self, messages: Tuple[Message, ...]): + """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. - :param can.Message message: - The message with the new :attr:`can.Message.data`. - Note: The arbitration ID cannot be changed. + Should be called when modify_data is called in the cyclic task. + """ + 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]): + """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. """ - self.message = message + messages = self._check_and_convert_messages(messages) + self._check_modified_messages(messages) + + self.messages = messages class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): """A Cyclic send task that supports switches send frequency after a set time. """ - def __init__(self, channel, message, count, initial_period, subsequent_period): + def __init__( + self, + channel: typechecking.Channel, + messages: Union[Sequence[Message], Message], + count: int, + initial_period: float, + subsequent_period: float, + ): """ Transmits a message `count` times at `initial_period` then continues to - transmit message at `subsequent_period`. + transmit messages at `subsequent_period`. :param channel: See interface specific documentation. - :param can.Message message: - :param int count: - :param float initial_period: - :param float subsequent_period: + :param messages: + :param count: + :param initial_period: + :param subsequent_period: """ - super(MultiRateCyclicSendTaskABC, self).__init__(channel, message, subsequent_period) - - -class ThreadBasedCyclicSendTask(ModifiableCyclicTaskABC, - LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): - """Fallback cyclic send task using thread.""" - - def __init__(self, bus, lock, message, period, duration=None): - super(ThreadBasedCyclicSendTask, self).__init__(message, period, duration) + super().__init__(messages, subsequent_period) + self._channel = channel + + +class ThreadBasedCyclicSendTask( + ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, 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, + ): + """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 continuiously 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`. + """ + super().__init__(messages, period, duration) self.bus = bus - self.lock = lock + self.send_lock = lock self.stopped = True self.thread = None - self.end_time = time.time() + duration if duration else None + self.end_time = time.perf_counter() + duration if duration else None + self.on_error = on_error + + if HAS_EVENTS: + self.period_ms: int = int(round(period * 1000, 0)) + self.event = win32event.CreateWaitableTimer(None, False, None) + self.start() def stop(self): + if HAS_EVENTS: + win32event.CancelWaitableTimer(self.event.handle) self.stopped = True def start(self): self.stopped = False if self.thread is None or not self.thread.is_alive(): - name = "Cyclic send task for 0x%X" % (self.message.arbitration_id) + name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id) self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True + + if HAS_EVENTS: + win32event.SetWaitableTimer( + self.event.handle, 0, self.period_ms, None, None, False + ) + self.thread.start() def _run(self): + msg_index = 0 while not self.stopped: # Prevent calling bus.send from multiple threads - with self.lock: - started = time.time() + with self.send_lock: + started = time.perf_counter() try: - self.bus.send(self.message) + self.bus.send(self.messages[msg_index]) except Exception as exc: log.exception(exc) - break - if self.end_time is not None and time.time() >= self.end_time: + if self.on_error: + if not self.on_error(exc): + break + else: + break + if self.end_time is not None and time.perf_counter() >= self.end_time: break - # Compensate for the time it takes to send the message - delay = self.period - (time.time() - started) - time.sleep(max(0.0, delay)) - - -def send_periodic(bus, message, period, *args, **kwargs): - """ - Send a :class:`~can.Message` every `period` seconds on the given bus. - - :param can.BusABC bus: A CAN bus which supports sending. - :param can.Message message: Message to send periodically. - :param float period: The minimum time between sending messages. - :return: A started task instance - """ - warnings.warn("The function `can.send_periodic` is deprecated and will " + - "be removed in an upcoming version. Please use `can.Bus.send_periodic` instead.", DeprecationWarning) - return bus.send_periodic(message, period, *args, **kwargs) + msg_index = (msg_index + 1) % len(self.messages) + + if HAS_EVENTS: + win32event.WaitForSingleObject(self.event.handle, self.period_ms) + else: + # Compensate for the time it takes to send the message + delay = self.period - (time.perf_counter() - started) + time.sleep(max(0.0, delay)) diff --git a/can/bus.py b/can/bus.py index 2b36b3c57..e315ee9d5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,19 +1,20 @@ -# coding: utf-8 - """ Contains the ABC bus implementation and its documentation. """ -from __future__ import print_function, absolute_import +from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union + +import can.typechecking from abc import ABCMeta, abstractmethod +import can import logging import threading from time import time -from collections import namedtuple from aenum import Enum, auto -from .broadcastmanager import ThreadBasedCyclicSendTask +from can.broadcastmanager import ThreadBasedCyclicSendTask +from can.message import Message LOG = logging.getLogger(__name__) @@ -26,7 +27,7 @@ class BusState(Enum): ERROR = auto() -class BusABC(object): +class BusABC(metaclass=ABCMeta): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. @@ -34,13 +35,18 @@ class BusABC(object): """ #: a string describing the underlying bus and/or channel - channel_info = 'unknown' + channel_info = "unknown" #: Log level for received messages RECV_LOGGING_LEVEL = 9 @abstractmethod - def __init__(self, channel, can_filters=None, **kwargs): + 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 @@ -49,28 +55,26 @@ def __init__(self, channel, can_filters=None, **kwargs): :param channel: The can interface identifier. Expected type is backend dependent. - :param list can_filters: + :param can_filters: See :meth:`~can.BusABC.set_filters` for details. :param dict kwargs: Any backend dependent configurations are passed in this dictionary """ - self._periodic_tasks = [] + self._periodic_tasks: List[can.broadcastmanager.CyclicSendTaskABC] = [] self.set_filters(can_filters) - def __str__(self): + def __str__(self) -> str: return self.channel_info - def recv(self, timeout=None): + def recv(self, timeout: Optional[float] = None) -> Optional[Message]: """Block waiting for a message from the Bus. - :type timeout: float or None :param timeout: seconds to wait for a message or None to wait indefinitely - :rtype: can.Message or None :return: - None on timeout or a :class:`can.Message` object. + None on timeout or a :class:`Message` object. :raises can.CanError: if an error occurred while reading """ @@ -84,7 +88,7 @@ def recv(self, timeout=None): # return it, if it matches if msg and (already_filtered or self._matches_filters(msg)): - LOG.log(self.RECV_LOGGING_LEVEL, 'Received: %s', msg) + LOG.log(self.RECV_LOGGING_LEVEL, "Received: %s", msg) return msg # if not, and timeout is None, try indefinitely @@ -102,7 +106,9 @@ def recv(self, timeout=None): else: return None - def _recv_internal(self, timeout): + 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` @@ -130,7 +136,6 @@ def _recv_internal(self, timeout): :param float timeout: seconds to wait for a message, see :meth:`~can.BusABC.send` - :rtype: tuple[can.Message, bool] or tuple[None, bool] :return: 1. a message that was read or None on timeout 2. a bool that is True if message filtering has already @@ -146,14 +151,13 @@ def _recv_internal(self, timeout): raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None): """Transmit a message to the CAN bus. Override this method to enable the transmit path. - :param can.Message msg: A message object. + :param Message msg: A message object. - :type timeout: float or None :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. @@ -166,8 +170,14 @@ def send(self, msg, timeout=None): """ raise NotImplementedError("Trying to write to a readonly bus?") - def send_periodic(self, msg, period, duration=None, store_task=True): - """Start sending a message at a given period on this bus. + def send_periodic( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + store_task: bool = True, + ) -> 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: @@ -177,35 +187,42 @@ def send_periodic(self, msg, period, duration=None, store_task=True): - :meth:`BusABC.stop_all_periodic_tasks()` is called - the task's :meth:`CyclicTask.stop()` method is called. - :param can.Message msg: - Message to transmit - :param float period: + :param msgs: + Messages to transmit + :param period: Period in seconds between each message - :param float duration: - The duration to keep sending this message at given rate. If + :param duration: + Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. - :param bool store_task: + :param store_task: If True (the default) the task will be attached to this Bus instance. Disable to instead manage tasks manually. :return: A started task instance. Note the task can be stopped (and depending on the backend modified) by calling the :meth:`stop` method. - :rtype: can.broadcastmanager.CyclicSendTaskABC .. note:: - Note the duration before the message stops being sent may not + 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. + 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. """ - task = self._send_periodic_internal(msg, period, duration) + if not isinstance(msgs, (list, tuple)): + if isinstance(msgs, Message): + msgs = [msgs] + else: + raise ValueError("Must be either a list, tuple, or a Message") + if not msgs: + raise ValueError("Must be at least a list or tuple of length 1") + task = self._send_periodic_internal(msgs, period, duration) # we wrap the task's stop method to also remove it from the Bus's list of tasks original_stop_method = task.stop @@ -216,46 +233,64 @@ def wrapped_stop_method(remove_task=True): except ValueError: pass original_stop_method() - task.stop = wrapped_stop_method + + setattr(task, "stop", wrapped_stop_method) if store_task: self._periodic_tasks.append(task) return task - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. Override this method to enable a more efficient backend specific approach. - :param can.Message msg: - Message to transmit - :param float period: + :param msgs: + Messages to transmit + :param period: Period in seconds between each message - :param float duration: - The duration to keep sending this message at given rate. If + :param duration: + The duration between sending each message at the given rate. If no duration is provided, the task will continue indefinitely. :return: - A started task instance. Note the task can be stopped (and depending on - the backend modified) by calling the :meth:`stop` method. - :rtype: can.broadcastmanager.CyclicSendTaskABC + A started task instance. Note the task can be stopped (and + depending on the backend modified) by calling the :meth:`stop` + method. """ if not hasattr(self, "_lock_send_periodic"): - # Create a send lock for this bus - self._lock_send_periodic = threading.Lock() - task = ThreadBasedCyclicSendTask(self, self._lock_send_periodic, msg, period, duration) + # Create a send lock for this bus, but not for buses which override this method + self._lock_send_periodic = ( + threading.Lock() + ) # pylint: disable=attribute-defined-outside-init + task = ThreadBasedCyclicSendTask( + self, self._lock_send_periodic, msgs, period, duration + ) return task def stop_all_periodic_tasks(self, remove_tasks=True): - """Stop sending any messages that were started using bus.send_periodic + """Stop sending any messages that were started using **bus.send_periodic**. + + .. note:: + The result is undefined if a single task throws an exception while being stopped. :param bool remove_tasks: Stop tracking the stopped tasks. """ for task in self._periodic_tasks: - task.stop(remove_task=remove_tasks) + # we cannot let `task.stop()` modify `self._periodic_tasks` while we are + # iterating over it (#634) + task.stop(remove_task=False) - def __iter__(self): + if remove_tasks: + self._periodic_tasks = [] + + def __iter__(self) -> Iterator[Message]: """Allow iteration on messages as they are received. >>> for msg in bus: @@ -263,7 +298,7 @@ def __iter__(self): :yields: - :class:`can.Message` msg objects. + :class:`Message` msg objects. """ while True: msg = self.recv(timeout=1.0) @@ -271,7 +306,7 @@ def __iter__(self): yield msg @property - def filters(self): + def filters(self) -> Optional[can.typechecking.CanFilters]: """ Modify the filters of this bus. See :meth:`~can.BusABC.set_filters` for details. @@ -279,10 +314,10 @@ def filters(self): return self._filters @filters.setter - def filters(self, filters): + def filters(self, filters: Optional[can.typechecking.CanFilters]): self.set_filters(filters) - def set_filters(self, filters=None): + def set_filters(self, filters: Optional[can.typechecking.CanFilters] = None): """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. @@ -307,26 +342,24 @@ def set_filters(self, filters=None): self._filters = filters or None self._apply_filters(self._filters) - def _apply_filters(self, filters): + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]): """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. - :param Iterator[dict] filters: + :param filters: See :meth:`~can.BusABC.set_filters` for details. """ - pass - def _matches_filters(self, msg): + 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 can.Message msg: + :param msg: the message to check if matching - :rtype: bool :return: whether the given message matches at least one filter """ @@ -336,13 +369,14 @@ def _matches_filters(self, msg): for _filter in self._filters: # check if this filter even applies to the message - if 'extended' in _filter and \ - _filter['extended'] != msg.is_extended_id: - continue + 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'] + can_id = _filter["can_id"] + can_mask = _filter["can_mask"] # basically, we compute # `msg.arbitration_id & can_mask == can_id & can_mask` @@ -356,14 +390,12 @@ def _matches_filters(self, msg): def flush_tx_buffer(self): """Discard every message that may be queued in the output buffer(s). """ - pass def shutdown(self): """ Called to carry out any interface specific cleanup required in shutting down a bus. """ - pass def __enter__(self): return self @@ -372,25 +404,21 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() @property - def state(self): + def state(self) -> BusState: """ Return the current state of the hardware - - :type: can.BusState """ return BusState.ACTIVE @state.setter - def state(self, new_state): + def state(self, new_state: BusState): """ Set the new state of the hardware - - :type: can.BusState """ raise NotImplementedError("Property is not implemented.") @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: """Detect all configurations/channels that this interface could currently connect with. @@ -398,10 +426,7 @@ def _detect_available_configs(): May not to be implemented by every interface on every platform. - :rtype: Iterator[dict] :return: an iterable of dicts, each being a configuration suitable for usage in the interface's bus constructor. """ raise NotImplementedError() - - __metaclass__ = ABCMeta diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 8a69b8df9..cba8e797b 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,17 +1,14 @@ -# coding: utf-8 - """ This module contains common `ctypes` utils. """ -import binascii import ctypes import logging import sys -log = logging.getLogger('can.ctypesutil') +log = logging.getLogger("can.ctypesutil") -__all__ = ['CLibrary', 'HANDLE', 'PHANDLE', 'HRESULT'] +__all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] try: _LibBase = ctypes.WinDLL @@ -34,19 +31,25 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): :param callable errcheck: optional error checking function, see ctypes docs for _FuncPtr """ - if (argtypes): + if argtypes: prototype = self.function_type(restype, *argtypes) else: prototype = self.function_type(restype) try: symbol = prototype((func_name, self)) except AttributeError: - raise ImportError("Could not map function '{}' from library {}".format(func_name, self._name)) + raise ImportError( + "Could not map function '{}' from library {}".format( + func_name, self._name + ) + ) setattr(symbol, "_name", func_name) - log.debug('Wrapped function "{}", result type: {}, error_check {}'.format(func_name, type(restype), errcheck)) + log.debug( + f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}' + ) - if (errcheck): + if errcheck: symbol.errcheck = errcheck setattr(self, func_name, symbol) @@ -57,10 +60,10 @@ class CLibrary_Win32(_LibBase, LibraryMixin): " Basic ctypes.WinDLL derived class + LibraryMixin " def __init__(self, library_or_path): - if (isinstance(library_or_path, str)): - super(CLibrary_Win32, self).__init__(library_or_path) + if isinstance(library_or_path, str): + super().__init__(library_or_path) else: - super(CLibrary_Win32, self).__init__(library_or_path._name, library_or_path._handle) + super().__init__(library_or_path._name, library_or_path._handle) @property def function_type(self): @@ -71,10 +74,10 @@ class CLibrary_Unix(ctypes.CDLL, LibraryMixin): " Basic ctypes.CDLL derived class + LibraryMixin " def __init__(self, library_or_path): - if (isinstance(library_or_path, str)): - super(CLibrary_Unix, self).__init__(library_or_path) + if isinstance(library_or_path, str): + super().__init__(library_or_path) else: - super(CLibrary_Unix, self).__init__(library_or_path._name, library_or_path._handle) + super().__init__(library_or_path._name, library_or_path._handle) @property def function_type(self): @@ -96,4 +99,5 @@ class HRESULT(ctypes.c_long): class HANDLE(ctypes.c_void_p): pass + PHANDLE = ctypes.POINTER(HANDLE) diff --git a/can/interface.py b/can/interface.py index 6c830d0c4..8d96f651d 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,34 +1,18 @@ -# coding: utf-8 - """ This module contains the base implementation of :class:`can.BusABC` as well as a list of all available backends and some implemented CyclicSendTasks. """ -from __future__ import absolute_import, print_function - -import sys import importlib import logging -import can from .bus import BusABC -from .broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC from .util import load_config from .interfaces import BACKENDS -if 'linux' in sys.platform: - # Deprecated and undocumented access to SocketCAN cyclic tasks - # Will be removed in version 4.0 - from can.interfaces.socketcan import CyclicSendTask, MultiRateCyclicSendTask - -# Required by "detect_available_configs" for argument interpretation -if sys.version_info.major > 2: - basestring = str - -log = logging.getLogger('can.interface') -log_autodetect = log.getChild('detect_available_configs') +log = logging.getLogger("can.interface") +log_autodetect = log.getChild("detect_available_configs") def _get_class_for_interface(interface): @@ -52,7 +36,9 @@ def _get_class_for_interface(interface): module = importlib.import_module(module_name) except Exception as e: raise ImportError( - "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) + "Cannot import module {} for CAN interface '{}': {}".format( + module_name, interface, e + ) ) # Get the correct class @@ -60,14 +46,15 @@ def _get_class_for_interface(interface): bus_class = getattr(module, class_name) except Exception as e: raise ImportError( - "Cannot import class {} from module {} for CAN interface '{}': {}" - .format(class_name, module_name, interface, e) + "Cannot import class {} from module {} for CAN interface '{}': {}".format( + class_name, module_name, interface, e + ) ) return bus_class -class Bus(BusABC): +class Bus(BusABC): # pylint disable=abstract-method """Bus wrapper with configuration loading. Instantiates a CAN Bus of the given ``interface``, falls back to reading a @@ -81,7 +68,7 @@ def __new__(cls, channel=None, *args, **kwargs): Some might have a special meaning, see below. :param channel: - Set to ``None`` to let it be reloved automatically from the default + Set to ``None`` to let it be resloved automatically from the default configuration. That might fail, see below. Expected type is backend dependent. @@ -99,26 +86,26 @@ def __new__(cls, channel=None, *args, **kwargs): # figure out the rest of the configuration; this might raise an error if channel is not None: - kwargs['channel'] = channel - if 'context' in kwargs: - context = kwargs['context'] - del kwargs['context'] + kwargs["channel"] = channel + if "context" in kwargs: + context = kwargs["context"] + del kwargs["context"] else: context = None kwargs = load_config(config=kwargs, context=context) # resolve the bus class to use for that interface - cls = _get_class_for_interface(kwargs['interface']) + cls = _get_class_for_interface(kwargs["interface"]) # remove the 'interface' key so it doesn't get passed to the backend - del kwargs['interface'] + del kwargs["interface"] # make sure the bus can handle this config format - if 'channel' not in kwargs: + if "channel" not in kwargs: raise ValueError("'channel' argument missing") else: - channel = kwargs['channel'] - del kwargs['channel'] + channel = kwargs["channel"] + del kwargs["channel"] if channel is None: # Use the default channel for the backend @@ -149,10 +136,9 @@ def detect_available_configs(interfaces=None): # Figure out where to search if interfaces is None: - # use an iterator over the keys so we do not have to copy it - interfaces = BACKENDS.keys() - elif isinstance(interfaces, basestring): - interfaces = [interfaces, ] + interfaces = BACKENDS + elif isinstance(interfaces, str): + interfaces = (interfaces,) # else it is supposed to be an iterable of strings result = [] @@ -161,21 +147,33 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) except ImportError: - log_autodetect.debug('interface "%s" can not be loaded for detection of available configurations', interface) + log_autodetect.debug( + 'interface "%s" can not be loaded for detection of available configurations', + interface, + ) continue # get available channels try: - available = list(bus_class._detect_available_configs()) + 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) + 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)) + 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 + if "interface" not in config: + config["interface"] = interface # append to result result += available diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index ec79e51d6..2f00d0309 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Interfaces contain low level implementations that interact with CAN hardware. """ @@ -10,31 +8,29 @@ # interface_name => (module, classname) BACKENDS = { - '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'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus'), - 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), - 'systec': ('can.interfaces.systec', 'UcanBus') + "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"), + "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"), } -BACKENDS.update({ - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points('can.interface') -}) - -# Old entry point name. May be removed >3.0. -for interface in iter_entry_points('python_can.interface'): - BACKENDS[interface.name] = (interface.module_name, interface.attrs[0]) - warnings.warn('{} is using the deprecated python_can.interface entry point. '.format(interface.name) + - 'Please change to can.interface instead.', DeprecationWarning) +BACKENDS.update( + { + interface.name: (interface.module_name, interface.attrs[0]) + for interface in iter_entry_points("can.interface") + } +) -VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) +VALID_INTERFACES = frozenset(list(BACKENDS.keys())) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 35f240a66..a57260490 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -7,25 +7,29 @@ class VCI_INIT_CONFIG(Structure): - _fields_ = [("AccCode", c_int32), - ("AccMask", c_int32), - ("Reserved", c_int32), - ("Filter", c_ubyte), - ("Timing0", c_ubyte), - ("Timing1", c_ubyte), - ("Mode", c_ubyte)] + _fields_ = [ + ("AccCode", c_int32), + ("AccMask", c_int32), + ("Reserved", c_int32), + ("Filter", c_ubyte), + ("Timing0", c_ubyte), + ("Timing1", c_ubyte), + ("Mode", c_ubyte), + ] class VCI_CAN_OBJ(Structure): - _fields_ = [("ID", c_uint), - ("TimeStamp", c_int), - ("TimeFlag", c_byte), - ("SendType", c_byte), - ("RemoteFlag", c_byte), - ("ExternFlag", c_byte), - ("DataLen", c_byte), - ("Data", c_ubyte * 8), - ("Reserved", c_byte * 3)] + _fields_ = [ + ("ID", c_uint), + ("TimeStamp", c_int), + ("TimeFlag", c_byte), + ("SendType", c_byte), + ("RemoteFlag", c_byte), + ("ExternFlag", c_byte), + ("DataLen", c_byte), + ("Data", c_ubyte * 8), + ("Reserved", c_byte * 3), + ] VCI_USBCAN2 = 4 @@ -66,17 +70,26 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): - def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None): + def __init__( + self, + channel, + device=0, + bitrate=None, + Timing0=None, + Timing1=None, + can_filters=None, + **kwargs, + ): """ :param channel: channel number :param device: device number - :param baud: baud rate - :param Timing0: customize the timing register if baudrate is not specified + :param bitrate: CAN network bandwidth (bits/s) + :param Timing0: customize the timing register if bitrate is not specified :param Timing1: :param can_filters: filters for packet """ - super(CANalystIIBus, self).__init__(channel, can_filters) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) if isinstance(channel, (list, tuple)): self.channels = channel @@ -84,17 +97,19 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [int(ch.strip()) for ch in channel.split(',')] + self.channels = [int(ch.strip()) for ch in channel.split(",")] self.device = device - self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) + self.channel_info = "CANalyst-II: device {}, channels {}".format( + self.device, self.channels + ) - if baud is not None: + if bitrate is not None: try: - Timing0, Timing1 = TIMING_DICT[baud] + Timing0, Timing1 = TIMING_DICT[bitrate] except KeyError: - raise ValueError("Baudrate is not supported") + raise ValueError("Bitrate is not supported") if Timing0 is None or Timing1 is None: raise ValueError("Timing registers are not set") @@ -105,7 +120,10 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can logger.error("VCI_OpenDevice Error") for channel in self.channels: - if CANalystII.VCI_InitCAN(VCI_USBCAN2, self.device, channel, byref(self.init_config)) == STATUS_ERR: + status = CANalystII.VCI_InitCAN( + VCI_USBCAN2, self.device, channel, byref(self.init_config) + ) + if status == STATUS_ERR: logger.error("VCI_InitCAN Error") self.shutdown() return @@ -123,17 +141,28 @@ def send(self, msg, timeout=None): :return: """ extern_flag = 1 if msg.is_extended_id else 0 - raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 1, msg.is_remote_frame, extern_flag, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + raw_message = VCI_CAN_OBJ( + msg.arbitration_id, + 0, + 0, + 1, + msg.is_remote_frame, + extern_flag, + msg.dlc, + (c_ubyte * 8)(*msg.data), + (c_byte * 3)(*[0, 0, 0]), + ) if msg.channel is not None: channel = msg.channel elif len(self.channels) == 1: channel = self.channels[0] else: - raise ValueError( - "msg.channel must be set when using multiple channels.") + raise ValueError("msg.channel must be set when using multiple channels.") - CANalystII.VCI_Transmit(VCI_USBCAN2, self.device, channel, byref(raw_message), 1) + CANalystII.VCI_Transmit( + VCI_USBCAN2, self.device, channel, byref(raw_message), 1 + ) def _recv_internal(self, timeout=None): """ @@ -145,7 +174,10 @@ def _recv_internal(self, timeout=None): timeout = -1 if timeout is None else int(timeout * 1000) - if CANalystII.VCI_Receive(VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: + status = CANalystII.VCI_Receive( + VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout + ) + if status <= STATUS_ERR: return None, False else: return ( diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 4426b1585..1ac666b6c 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 4baee6177..638460a4c 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ ICS NeoVi interface module. @@ -11,6 +9,8 @@ """ import logging +import os +import tempfile from collections import deque from can import Message, CanError, BusABC @@ -22,11 +22,41 @@ 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 + "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")) + + class ICSApiError(CanError): """ Indicates an error with the ICS API. @@ -42,10 +72,14 @@ class ICSApiError(CanError): ICS_SPY_ERR_INFORMATION = 0x40 def __init__( - self, error_number, description_short, description_long, - severity, restart_needed + self, + error_number, + description_short, + description_long, + severity, + restart_needed, ): - super(ICSApiError, self).__init__(description_short) + super().__init__(description_short) self.error_number = error_number self.description_short = description_short self.description_long = description_long @@ -95,16 +129,15 @@ def __init__(self, channel, can_filters=None, **kwargs): Absolute path or relative path to the library including filename. """ if ics is None: - raise ImportError('Please install python-ics') + raise ImportError("Please install python-ics") - super(NeoViBus, self).__init__( - channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(kwargs)) - if 'override_library_name' in kwargs: - ics.override_library_name(kwargs.get('override_library_name')) + if "override_library_name" in kwargs: + ics.override_library_name(kwargs.get("override_library_name")) if isinstance(channel, (list, tuple)): self.channels = channel @@ -112,34 +145,33 @@ def __init__(self, channel, can_filters=None, **kwargs): self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [ch.strip() for ch in channel.split(',')] + 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') + type_filter = kwargs.get("type_filter") + serial = kwargs.get("serial") self.dev = self._find_device(type_filter, serial) - ics.open_device(self.dev) - if 'bitrate' in kwargs: + with open_lock: + ics.open_device(self.dev) + + if "bitrate" in kwargs: for channel in self.channels: - ics.set_bit_rate(self.dev, kwargs.get('bitrate'), channel) + ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) - fd = kwargs.get('fd', False) + fd = kwargs.get("fd", False) if fd: - if 'data_bitrate' in kwargs: + if "data_bitrate" in kwargs: for channel in self.channels: - ics.set_fd_bit_rate( - self.dev, kwargs.get('data_bitrate'), channel) + ics.set_fd_bit_rate(self.dev, kwargs.get("data_bitrate"), channel) - self._use_system_timestamp = bool( - kwargs.get('use_system_timestamp', False) - ) - self._receive_own_messages = kwargs.get('receive_own_messages', True) + self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) + self._receive_own_messages = kwargs.get("receive_own_messages", True) - self.channel_info = '%s %s CH:%s' % ( + self.channel_info = "%s %s CH:%s" % ( self.dev.Name, self.get_serial_number(self.dev), - self.channels + self.channels, ) logger.info("Using device: {}".format(self.channel_info)) @@ -155,8 +187,7 @@ def channel_to_netid(channel_name_or_id): channel = getattr(ics, netid) else: raise ValueError( - 'channel must be an integer or ' - 'a valid ICS channel name' + "channel must be an integer or " "a valid ICS channel name" ) return channel @@ -174,7 +205,7 @@ def get_serial_number(device): return str(device.SerialNumber) def shutdown(self): - super(NeoViBus, self).shutdown() + super().shutdown() ics.close_device(self.dev) @staticmethod @@ -196,10 +227,10 @@ def _detect_available_configs(): return [] # TODO: add the channel(s) - return [{ - 'interface': 'neovi', - 'serial': NeoViBus.get_serial_number(device) - } for device in devices] + return [ + {"interface": "neovi", "serial": NeoViBus.get_serial_number(device)} + for device in devices + ] def _find_device(self, type_filter=None, serial=None): if type_filter is not None: @@ -212,14 +243,14 @@ def _find_device(self, type_filter=None, serial=None): dev = device break else: - msg = ['No device'] + msg = ["No device"] if type_filter is not None: - msg.append('with type {}'.format(type_filter)) + msg.append("with type {}".format(type_filter)) if serial is not None: - msg.append('with serial {}'.format(serial)) - msg.append('found.') - raise Exception(' '.join(msg)) + msg.append("with serial {}".format(serial)) + msg.append("found.") + raise Exception(" ".join(msg)) return dev def _process_msg_queue(self, timeout=0.1): @@ -235,7 +266,7 @@ def _process_msg_queue(self, timeout=0.1): continue self.rx_buffer.append(ics_msg) if errors: - logger.warning("%d error(s) found" % errors) + logger.warning("%d error(s) found", errors) for msg in ics.get_error_messages(self.dev): error = ICSApiError(*msg) @@ -264,19 +295,18 @@ def _ics_msg_to_message(self, ics_msg): if is_fd: if ics_msg.ExtraDataPtrEnabled: - data = ics_msg.ExtraDataPtr[:ics_msg.NumberBytesData] + data = ics_msg.ExtraDataPtr[: ics_msg.NumberBytesData] else: - data = ics_msg.Data[:ics_msg.NumberBytesData] + data = ics_msg.Data[: ics_msg.NumberBytesData] return Message( timestamp=self._get_timestamp_for_msg(ics_msg), arbitration_id=ics_msg.ArbIDOrHeader, data=data, dlc=ics_msg.NumberBytesData, - is_extended_id=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME - ), + is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, + is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), @@ -286,22 +316,21 @@ def _ics_msg_to_message(self, ics_msg): bitrate_switch=bool( ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_BRS ), - channel=ics_msg.NetworkID + channel=ics_msg.NetworkID, ) else: return Message( timestamp=self._get_timestamp_for_msg(ics_msg), arbitration_id=ics_msg.ArbIDOrHeader, - data=ics_msg.Data[:ics_msg.NumberBytesData], + data=ics_msg.Data[: ics_msg.NumberBytesData], dlc=ics_msg.NumberBytesData, - is_extended_id=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME - ), + is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, + is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), - channel=ics_msg.NetworkID + channel=ics_msg.NetworkID, ) def _recv_internal(self, timeout=0.1): @@ -334,11 +363,12 @@ def send(self, msg, timeout=None): flag3 |= ics.SPY_STATUS3_CANFD_ESI message.ArbIDOrHeader = msg.arbitration_id - message.NumberBytesData = len(msg.data) - message.Data = tuple(msg.data[:8]) - if msg.is_fd and len(msg.data) > 8: + msg_data = msg.data + message.NumberBytesData = len(msg_data) + message.Data = tuple(msg_data[:8]) + if msg.is_fd and len(msg_data) > 8: message.ExtraDataPtrEnabled = 1 - message.ExtraDataPtr = tuple(msg.data) + message.ExtraDataPtr = tuple(msg_data) message.StatusBitField = flag0 message.StatusBitField2 = 0 message.StatusBitField3 = flag3 @@ -347,8 +377,7 @@ def send(self, msg, timeout=None): elif len(self.channels) == 1: message.NetworkID = self.channels[0] else: - raise ValueError( - "msg.channel must be set when using multiple channels.") + raise ValueError("msg.channel must be set when using multiple channels.") try: ics.transmit_messages(self.dev, message) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index a646bd96e..a0bb413f2 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -1,11 +1,7 @@ -# coding: utf-8 - """ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. """ -from __future__ import absolute_import, division - import ctypes import time import logging @@ -63,7 +59,7 @@ class IscanBus(BusABC): 250000: 6, 500000: 7, 800000: 8, - 1000000: 9 + 1000000: 9, } def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): @@ -88,8 +84,9 @@ def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): self.poll_interval = poll_interval iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) - super(IscanBus, self).__init__(channel=channel, bitrate=bitrate, - poll_interval=poll_interval, **kwargs) + super().__init__( + channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + ) def _recv_internal(self, timeout): raw_msg = MessageExStruct() @@ -110,21 +107,25 @@ def _recv_internal(self, timeout): # 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) + 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, timeout=None): - raw_msg = MessageExStruct(msg.arbitration_id, - bool(msg.is_extended_id), - bool(msg.is_remote_frame), - msg.dlc, - CanData(*msg.data)) + 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): @@ -157,11 +158,11 @@ class IscanError(CanError): 31: "Transmission not acknowledged on bus", 32: "Error critical bus", 35: "Callbackthread is blocked, stopping thread failed", - 40: "Need a licence number under NT4" + 40: "Need a licence number under NT4", } def __init__(self, function, error_code, arguments): - super(IscanError, self).__init__() + super().__init__() # :Status code self.error_code = error_code # :Function that failed @@ -170,6 +171,7 @@ def __init__(self, function, error_code, arguments): self.arguments = arguments def __str__(self): - description = self.ERROR_CODES.get(self.error_code, - "Error code %d" % self.error_code) + description = self.ERROR_CODES.get( + self.error_code, "Error code %d" % self.error_code + ) return "Function %s failed: %s" % (self.function.__name__, description) diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index aef26b729..a4613880b 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..aa90ffafe 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems @@ -13,30 +11,32 @@ """ -from __future__ import absolute_import, division - import ctypes import functools import logging import sys -from can import CanError, BusABC, Message -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC) +from can import BusABC, Message +from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, + RestartableCyclicTaskABC, +) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures from .exceptions import * -__all__ = ["VCITimeout", "VCIError", "VCIDeviceNotFoundError", "IXXATBus", "vciFormatError"] +__all__ = [ + "VCITimeout", + "VCIError", + "VCIDeviceNotFoundError", + "IXXATBus", + "vciFormatError", +] -log = logging.getLogger('can.ixxat') +log = logging.getLogger("can.ixxat") -try: - # since Python 3.3 - from time import perf_counter as _timer_function -except ImportError: - from time import clock as _timer_function +from time import perf_counter as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -72,10 +72,9 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): :return: Formatted string """ - #TODO: make sure we don't generate another exception + # TODO: make sure we don't generate another exception return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT), - arguments + __vciFormatError(library_instance, function, HRESULT), arguments ) @@ -93,7 +92,9 @@ def __vciFormatError(library_instance, function, HRESULT): buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + return "function {} failed ({})".format( + function._name, buf.value.decode("utf-8", "replace") + ) def __check_status(result, function, arguments): @@ -124,19 +125,22 @@ def __check_status(result, function, arguments): 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 + 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 ); + # 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)) + # 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) @@ -145,70 +149,188 @@ def __check_status(result, function, arguments): # 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) + _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) + _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) + _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) + _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) + _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) - - #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("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, + ) + + # 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 ); + # 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) + # 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 @@ -219,20 +341,20 @@ def __check_status(result, function, arguments): CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + 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", + 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", } -#---------------------------------------------------------------------------- +# ---------------------------------------------------------------------------- class IXXATBus(BusABC): @@ -257,7 +379,7 @@ class IXXATBus(BusABC): 250000: constants.CAN_BT0_250KB, 500000: constants.CAN_BT0_500KB, 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB + 1000000: constants.CAN_BT0_1000KB, }, 1: { 10000: constants.CAN_BT1_10KB, @@ -268,8 +390,8 @@ class IXXATBus(BusABC): 250000: constants.CAN_BT1_250KB, 500000: constants.CAN_BT1_500KB, 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } + 1000000: constants.CAN_BT1_1000KB, + }, } def __init__(self, channel, can_filters=None, **kwargs): @@ -290,19 +412,21 @@ def __init__(self, channel, can_filters=None, **kwargs): Channel bitrate in bit/s """ if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + raise ImportError( + "The IXXAT VCI library has not been initialized. Check the logs for more details." + ) log.info("CAN Filters: %s", can_filters) log.info("Got configuration of: %s", kwargs) # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) + bitrate = kwargs.get("bitrate", 500000) + UniqueHardwareId = kwargs.get("UniqueHardwareId", None) + rxFifoSize = kwargs.get("rxFifoSize", 16) + txFifoSize = kwargs.get("txFifoSize", 16) + self._receive_own_messages = kwargs.get("receive_own_messages", False) # Usually comes as a string from the config file channel = int(channel) - if (bitrate not in self.CHANNEL_BITRATES[0]): + if bitrate not in self.CHANNEL_BITRATES[0]: raise ValueError("Invalid bitrate {}".format(bitrate)) self._device_handle = HANDLE() @@ -321,62 +445,99 @@ def __init__(self, channel, can_filters=None, **kwargs): _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) while True: try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + _canlib.vciEnumDeviceNext( + self._device_handle, ctypes.byref(self._device_info) + ) except StopIteration: - if (UniqueHardwareId is None): - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + if UniqueHardwareId is None: + raise VCIDeviceNotFoundError( + "No IXXAT device(s) connected or device(s) in use by other process(es)." + ) else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + raise VCIDeviceNotFoundError( + "Unique HW ID {} not connected or not available.".format( + UniqueHardwareId + ) + ) else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + if (UniqueHardwareId is None) or ( + self._device_info.UniqueHardwareId.AsChar + == bytes(UniqueHardwareId, "ascii") + ): break else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) _canlib.vciEnumDeviceClose(self._device_handle) - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + _canlib.vciDeviceOpen( + ctypes.byref(self._device_info.VciObjectId), + ctypes.byref(self._device_handle), + ) 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, rxFifoSize, txFifoSize) - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + log.info( + "Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", + channel, + rxFifoSize, + txFifoSize, + ) + _canlib.canChannelOpen( + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 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)) + _canlib.canControlOpen( + self._device_handle, channel, ctypes.byref(self._control_handle) + ) _canlib.canControlInitialize( self._control_handle, - constants.CAN_OPMODE_STANDARD|constants.CAN_OPMODE_EXTENDED|constants.CAN_OPMODE_ERRFRAME, + constants.CAN_OPMODE_STANDARD + | constants.CAN_OPMODE_EXTENDED + | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], - self.CHANNEL_BITRATES[1][bitrate] + self.CHANNEL_BITRATES[1][bitrate], + ) + _canlib.canControlGetCaps( + self._control_handle, ctypes.byref(self._channel_capabilities) ) - _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 # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + self._tick_resolution = float( + 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) + _canlib.canControlSetAccFilter( + self._control_handle, + extended, + constants.CAN_ACC_CODE_NONE, + constants.CAN_ACC_MASK_NONE, + ) for can_filter in can_filters: # Whitelist - 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) + 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 @@ -389,13 +550,15 @@ def __init__(self, channel, can_filters=None, **kwargs): # Usually you get back 3 messages like "CAN initialized" ecc... # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): + for _ in range(rxFifoSize): try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + _canlib.canChannelReadMessage( + self._channel_handle, 0, ctypes.byref(self._message) + ) except (VCITimeout, VCIRxQueueEmptyError): break - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + super().__init__(channel=channel, can_filters=None, **kwargs) def _inWaiting(self): try: @@ -419,7 +582,9 @@ def _recv_internal(self, timeout): if timeout == 0: # Peek without waiting try: - _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) + _canlib.canChannelPeekMessage( + self._channel_handle, ctypes.byref(self._message) + ) except (VCITimeout, VCIRxQueueEmptyError): return None, True else: @@ -437,7 +602,9 @@ def _recv_internal(self, timeout): while True: try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + _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 _timer_function() pass @@ -448,15 +615,34 @@ def _recv_internal(self, timeout): break elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: - log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + "Unknown CAN info message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR + ): + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + "Unknown CAN error message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type + == constants.CAN_MSGTYPE_TIMEOVR + ): pass else: - log.warn("Unexpected message info type") + log.warning("Unexpected message info type") if t0 is not None: remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) @@ -470,13 +656,14 @@ def _recv_internal(self, timeout): # 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=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + 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 + data=self._message.abData[: self._message.uMsgInfo.Bits.dlc], + channel=self.channel, ) return rx_msg, True @@ -497,7 +684,8 @@ def send(self, msg, timeout=None): if timeout: _canlib.canChannelSendMessage( - self._channel_handle, int(timeout * 1000), message) + self._channel_handle, int(timeout * 1000), message + ) else: _canlib.canChannelPostMessage(self._channel_handle, message) @@ -505,14 +693,14 @@ def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, - self._scheduler) + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) caps = structures.CANCAPABILITIES() _canlib.canSchedulerGetCaps(self._scheduler, caps) self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, - self._scheduler_resolution) + return CyclicSendTask( + self._scheduler, msg, period, duration, self._scheduler_resolution + ) def shutdown(self): if self._scheduler is not None: @@ -522,35 +710,29 @@ def shutdown(self): _canlib.canControlClose(self._control_handle) _canlib.vciDeviceClose(self._device_handle) - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True - -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" - def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) + def __init__(self, scheduler, msgs, period, duration, resolution): + 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 = msg.arbitration_id + self._msg.dwMsgId = self.messages[0].arbitration_id self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.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 self.start() @@ -558,12 +740,8 @@ 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) + _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).""" diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index d466e096d..a24a3f291 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,148 +1,146 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli """ -FALSE = 0 -TRUE = 1 +FALSE = 0 +TRUE = 1 -INFINITE = 0xFFFFFFFF +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_800KB = 0x00 -CAN_BT1_800KB = 0x16 -CAN_BT0_1000KB = 0x00 -CAN_BT1_1000KB = 0x14 +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_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 +SEV_INFO = 0x40000000 +SEV_WARN = 0x80000000 +SEV_ERROR = 0xC0000000 +SEV_MASK = 0xC0000000 +SEV_SUCCESS = 0x00000000 -RESERVED_FLAG = 0x10000000 -CUSTOMER_FLAG = 0x20000000 +RESERVED_FLAG = 0x10000000 +CUSTOMER_FLAG = 0x20000000 -STATUS_MASK = 0x0000FFFF -FACILITY_MASK = 0x0FFF0000 +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 +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_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_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_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 +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_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_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_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 -CAN_STATUS_OVRRUN = 0x02 -CAN_STATUS_ERRLIM = 0x04 -CAN_STATUS_BUSOFF = 0x08 -CAN_STATUS_ININIT = 0x10 -CAN_STATUS_BUSCERR = 0x20 +CAN_STATUS_TXPEND = 0x01 +CAN_STATUS_OVRRUN = 0x02 +CAN_STATUS_ERRLIM = 0x04 +CAN_STATUS_BUSOFF = 0x08 +CAN_STATUS_ININIT = 0x10 +CAN_STATUS_BUSCERR = 0x20 # Controller operating modes CAN_OPMODE_UNDEFINED = 0x00 -CAN_OPMODE_STANDARD = 0x01 -CAN_OPMODE_EXTENDED = 0x02 -CAN_OPMODE_ERRFRAME = 0x04 -CAN_OPMODE_LISTONLY = 0x08 -CAN_OPMODE_LOWSPEED = 0x10 +CAN_OPMODE_STANDARD = 0x01 +CAN_OPMODE_EXTENDED = 0x02 +CAN_OPMODE_ERRFRAME = 0x04 +CAN_OPMODE_LISTONLY = 0x08 +CAN_OPMODE_LOWSPEED = 0x10 # Message types -CAN_MSGTYPE_DATA = 0 -CAN_MSGTYPE_INFO = 1 -CAN_MSGTYPE_ERROR = 2 -CAN_MSGTYPE_STATUS = 3 -CAN_MSGTYPE_WAKEUP = 4 +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 +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 +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 +CAN_ACC_MASK_NONE = 0xFFFFFFFF +CAN_ACC_CODE_NONE = 0x80000000 diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index ac1700dca..d548510b1 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems @@ -8,23 +6,22 @@ from can import CanError -__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] +__all__ = ["VCITimeout", "VCIError", "VCIRxQueueEmptyError", "VCIDeviceNotFoundError"] class VCITimeout(CanError): """ Wraps the VCI_E_TIMEOUT error """ - pass class VCIError(CanError): """ Try to display errors that occur within the wrapped C library nicely. """ - pass class VCIRxQueueEmptyError(VCIError): """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + super().__init__("Receive queue is empty") class VCIDeviceNotFoundError(CanError): diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 65b177d94..73c01823d 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems @@ -8,19 +6,18 @@ import ctypes + class LUID(ctypes.Structure): - _fields_ = [ - ("LowPart", ctypes.c_uint32), - ("HighPart", ctypes.c_int32), - ] + _fields_ = [("LowPart", ctypes.c_uint32), ("HighPart", ctypes.c_int32)] + + PLUID = ctypes.POINTER(LUID) class VCIID(ctypes.Union): - _fields_ = [ - ("AsLuid", LUID), - ("AsInt64", ctypes.c_int64), - ] + _fields_ = [("AsLuid", LUID), ("AsInt64", ctypes.c_int64)] + + PVCIID = ctypes.POINTER(VCIID) @@ -35,10 +32,8 @@ class GUID(ctypes.Structure): class VCIDEVICEINFO(ctypes.Structure): class UniqueHardwareId(ctypes.Union): - _fields_ = [ - ("AsChar", ctypes.c_char * 16), - ("AsGuid", GUID), - ] + _fields_ = [("AsChar", ctypes.c_char * 16), ("AsGuid", GUID)] + _fields_ = [ ("VciObjectId", VCIID), ("DeviceClass", GUID), @@ -66,8 +61,10 @@ def __str__(self): self.DriverReleaseVersion, self.DriverMajorVersion, self.DriverMinorVersion, - self.DriverBuildVersion + self.DriverBuildVersion, ) + + PVCIDEVICEINFO = ctypes.POINTER(VCIDEVICEINFO) @@ -77,8 +74,10 @@ class CANLINESTATUS(ctypes.Structure): ("bBtReg0", ctypes.c_uint8), ("bBtReg1", ctypes.c_uint8), ("bBusLoad", ctypes.c_uint8), - ("dwStatus", ctypes.c_uint32) + ("dwStatus", ctypes.c_uint32), ] + + PCANLINESTATUS = ctypes.POINTER(CANLINESTATUS) @@ -88,8 +87,10 @@ class CANCHANSTATUS(ctypes.Structure): ("fActivated", ctypes.c_uint32), ("fRxOverrun", ctypes.c_uint32), ("bRxFifoLoad", ctypes.c_uint8), - ("bTxFifoLoad", ctypes.c_uint8) + ("bTxFifoLoad", ctypes.c_uint8), ] + + PCANCHANSTATUS = ctypes.POINTER(CANCHANSTATUS) @@ -103,8 +104,10 @@ class CANCAPABILITIES(ctypes.Structure): ("dwCmsDivisor", ctypes.c_uint32), ("dwCmsMaxTicks", ctypes.c_uint32), ("dwDtxDivisor", ctypes.c_uint32), - ("dwDtxMaxTicks", ctypes.c_uint32) + ("dwDtxMaxTicks", ctypes.c_uint32), ] + + PCANCAPABILITIES = ctypes.POINTER(CANCAPABILITIES) @@ -128,13 +131,12 @@ class Bits(ctypes.Structure): ("srr", ctypes.c_uint32, 1), ("rtr", ctypes.c_uint32, 1), ("ext", ctypes.c_uint32, 1), - ("afc", ctypes.c_uint32, 8) + ("afc", ctypes.c_uint32, 8), ] - _fields_ = [ - ("Bytes", Bytes), - ("Bits", Bits) - ] + _fields_ = [("Bytes", Bytes), ("Bits", Bits)] + + PCANMSGINFO = ctypes.POINTER(CANMSGINFO) @@ -143,10 +145,13 @@ class CANMSG(ctypes.Structure): ("dwTime", ctypes.c_uint32), ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), - ("abData", ctypes.c_uint8 * 8) + ("abData", ctypes.c_uint8 * 8), ] + + PCANMSG = ctypes.POINTER(CANMSG) + class CANCYCLICTXMSG(ctypes.Structure): _fields_ = [ ("wCycleTime", ctypes.c_uint16), @@ -154,6 +159,8 @@ class CANCYCLICTXMSG(ctypes.Structure): ("bByteIndex", ctypes.c_uint8), ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), - ("abData", ctypes.c_uint8 * 8) + ("abData", ctypes.c_uint8 * 8), ] + + PCANCYCLICTXMSG = ctypes.POINTER(CANCYCLICTXMSG) diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index 5cbe63386..36a21db9f 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index fa3a70221..d0610019b 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Contains Python equivalents of the function and constant definitions in CANLIB's canlib.h, with some supporting functionality @@ -8,8 +6,6 @@ Copyright (C) 2010 Dynamic Controls """ -from __future__ import absolute_import - import sys import time import logging @@ -20,7 +16,7 @@ from . import constants as canstat from . import structures -log = logging.getLogger('can.kvaser') +log = logging.getLogger("can.kvaser") # Resolution in us TIMESTAMP_RESOLUTION = 10 @@ -40,21 +36,22 @@ def _unimplemented_function(*args): - raise NotImplementedError('This function is not implemented in canlib') + raise NotImplementedError("This function is not implemented in canlib") -def __get_canlib_function(func_name, argtypes=[], restype=None, errcheck=None): - #log.debug('Wrapping function "%s"' % func_name) +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) + # 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) + # log.debug('Result type is: %s' % type(restype)) + # log.debug('Error check function is: %s' % errcheck) retval.argtypes = argtypes retval.restype = restype if errcheck: @@ -69,14 +66,16 @@ class CANLIBError(CanError): """ def __init__(self, function, error_code, arguments): - super(CANLIBError, self).__init__() + super().__init__() self.error_code = error_code self.function = function self.arguments = arguments def __str__(self): - return "Function %s failed - %s" % (self.function.__name__, - self.__get_error_message()) + return "Function %s failed - %s" % ( + self.function.__name__, + self.__get_error_message(), + ) def __get_error_message(self): errmsg = ctypes.create_string_buffer(128) @@ -85,7 +84,7 @@ def __get_error_message(self): def __convert_can_status_to_int(result): - #log.debug("converting can status to int {} ({})".format(result, type(result))) + # log.debug("converting can status to int {} ({})".format(result, type(result))) if isinstance(result, int): return result else: @@ -95,7 +94,7 @@ def __convert_can_status_to_int(result): def __check_status(result, function, arguments): result = __convert_can_status_to_int(result) if not canstat.CANSTATUS_SUCCESS(result): - #log.debug('Detected error while checking CAN status') + # log.debug('Detected error while checking CAN status') raise CANLIBError(function, result, arguments) return result @@ -103,7 +102,7 @@ def __check_status(result, function, arguments): 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: - #log.debug('Detected error in which checking status read') + # log.debug('Detected error in which checking status read') raise CANLIBError(function, result, arguments) return result @@ -111,11 +110,12 @@ def __check_status_read(result, function, arguments): class c_canHandle(ctypes.c_int): pass + canINVALID_HANDLE = -1 def __handle_is_valid(handle): - return (handle.value > canINVALID_HANDLE) + return handle.value > canINVALID_HANDLE def __check_bus_handle_validity(handle, function, arguments): @@ -125,141 +125,187 @@ def __check_bus_handle_validity(handle, function, arguments): else: return handle + 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) + canGetErrorText = __get_canlib_function( + "canGetErrorText", + argtypes=[canstat.c_canStatus, ctypes.c_char_p, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) # 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) - - kvReadTimer = __get_canlib_function("kvReadTimer", - argtypes=[c_canHandle, - ctypes.POINTER(ctypes.c_uint)], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canBusOff = __get_canlib_function("canBusOff", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canBusOn = __get_canlib_function("canBusOn", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canClose = __get_canlib_function("canClose", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - 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) - - 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) - - canSetBusOutputControl = __get_canlib_function("canSetBusOutputControl", - argtypes=[c_canHandle, - ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canSetAcceptanceFilter = __get_canlib_function("canSetAcceptanceFilter", - argtypes=[ - c_canHandle, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_int - ], - restype=canstat.c_canStatus, - errcheck=__check_status) - - 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) - - canWriteSync = __get_canlib_function("canWriteSync", - argtypes=[c_canHandle, ctypes.c_ulong], - restype=canstat.c_canStatus, - errcheck=__check_status) - - 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) - - canGetVersion = __get_canlib_function("canGetVersion", - restype=ctypes.c_short, - errcheck=__check_status) - - kvFlashLeds = __get_canlib_function("kvFlashLeds", - argtypes=[c_canHandle, ctypes.c_int, - ctypes.c_int], - restype=ctypes.c_short, - errcheck=__check_status) + canGetNumberOfChannels = __get_canlib_function( + "canGetNumberOfChannels", + argtypes=[ctypes.c_void_p], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + kvReadTimer = __get_canlib_function( + "kvReadTimer", + argtypes=[c_canHandle, ctypes.POINTER(ctypes.c_uint)], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canBusOff = __get_canlib_function( + "canBusOff", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canBusOn = __get_canlib_function( + "canBusOn", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canClose = __get_canlib_function( + "canClose", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + 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, + ) + + 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, + ) + + canSetBusOutputControl = __get_canlib_function( + "canSetBusOutputControl", + argtypes=[c_canHandle, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canSetAcceptanceFilter = __get_canlib_function( + "canSetAcceptanceFilter", + argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_uint, ctypes.c_int], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + 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, + ) + + canWriteSync = __get_canlib_function( + "canWriteSync", + argtypes=[c_canHandle, ctypes.c_ulong], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + 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, + ) + + canGetVersion = __get_canlib_function( + "canGetVersion", restype=ctypes.c_short, errcheck=__check_status + ) + + kvFlashLeds = __get_canlib_function( + "kvFlashLeds", + argtypes=[c_canHandle, ctypes.c_int, ctypes.c_int], + restype=ctypes.c_short, + errcheck=__check_status, + ) if sys.platform == "win32": - canGetVersionEx = __get_canlib_function("canGetVersionEx", - argtypes=[ctypes.c_uint], - restype=ctypes.c_uint, - errcheck=__check_status) - - 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) - - canRequestBusStatistics = __get_canlib_function("canRequestBusStatistics", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canGetBusStatistics = __get_canlib_function("canGetBusStatistics", - argtypes=[c_canHandle, - ctypes.POINTER(structures.BusStatistics), - ctypes.c_size_t], - restype=canstat.c_canStatus, - errcheck=__check_status) + canGetVersionEx = __get_canlib_function( + "canGetVersionEx", + argtypes=[ctypes.c_uint], + restype=ctypes.c_uint, + errcheck=__check_status, + ) + + 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, + ) + + canRequestBusStatistics = __get_canlib_function( + "canRequestBusStatistics", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canGetBusStatistics = __get_canlib_function( + "canGetBusStatistics", + argtypes=[ + c_canHandle, + ctypes.POINTER(structures.BusStatistics), + ctypes.c_size_t, + ], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) def init_kvaser_library(): @@ -268,7 +314,7 @@ def init_kvaser_library(): log.debug("Initializing Kvaser CAN library") canInitializeLibrary() log.debug("CAN library initialized") - except: + except Exception: log.warning("Kvaser canlib could not be initialized.") @@ -285,7 +331,7 @@ def init_kvaser_library(): 83000: canstat.canBITRATE_83K, 62000: canstat.canBITRATE_62K, 50000: canstat.canBITRATE_50K, - 10000: canstat.canBITRATE_10K + 10000: canstat.canBITRATE_10K, } BITRATE_FD = { @@ -293,7 +339,7 @@ def init_kvaser_library(): 1000000: canstat.canFD_BITRATE_1M_80P, 2000000: canstat.canFD_BITRATE_2M_80P, 4000000: canstat.canFD_BITRATE_4M_80P, - 8000000: canstat.canFD_BITRATE_8M_60P + 8000000: canstat.canFD_BITRATE_8M_60P, } @@ -354,35 +400,34 @@ def __init__(self, channel, can_filters=None, **kwargs): log.info("CAN Filters: {}".format(can_filters)) log.info("Got configuration of: {}".format(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) - accept_virtual = kwargs.get('accept_virtual', True) - fd = kwargs.get('fd', False) - data_bitrate = kwargs.get('data_bitrate', None) + 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) + accept_virtual = kwargs.get("accept_virtual", True) + fd = kwargs.get("fd", False) + data_bitrate = kwargs.get("data_bitrate", None) try: channel = int(channel) except ValueError: - raise ValueError('channel must be an integer') + raise ValueError("channel must be an integer") self.channel = channel - log.debug('Initialising bus instance') + log.debug("Initialising bus instance") self.single_handle = single_handle num_channels = ctypes.c_int(0) - res = canGetNumberOfChannels(ctypes.byref(num_channels)) - #log.debug("Res: {}".format(res)) + canGetNumberOfChannels(ctypes.byref(num_channels)) num_channels = int(num_channels.value) - log.info('Found %d available channels' % num_channels) + log.info("Found %d available channels", num_channels) for idx in range(num_channels): channel_info = get_channel_info(idx) - log.info('%d: %s', idx, channel_info) + log.info("%d: %s", idx, channel_info) if idx == channel: self.channel_info = channel_info @@ -392,15 +437,17 @@ def __init__(self, channel, can_filters=None, **kwargs): if fd: flags |= canstat.canOPEN_CAN_FD - log.debug('Creating read handle to bus channel: %s' % channel) + log.debug("Creating read handle to bus channel: %s", channel) self._read_handle = canOpenChannel(channel, flags) - canIoCtl(self._read_handle, - canstat.canIOCTL_SET_TIMER_SCALE, - ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), - 4) - + canIoCtl( + self._read_handle, + canstat.canIOCTL_SET_TIMER_SCALE, + ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), + 4, + ) + if fd: - if 'tseg1' not in kwargs and bitrate in BITRATE_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: @@ -411,7 +458,7 @@ def __init__(self, channel, can_filters=None, **kwargs): data_bitrate = bitrate canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) else: - if 'tseg1' not in kwargs and bitrate in BITRATE_OBJS: + 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) @@ -419,22 +466,28 @@ def __init__(self, channel, can_filters=None, **kwargs): 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") - canIoCtl(self._read_handle, - canstat.canIOCTL_SET_LOCAL_TXECHO, - ctypes.byref(ctypes.c_byte(local_echo)), - 1) + canIoCtl( + 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) + log.debug("Creating separate handle for TX on channel: %s", channel) self._write_handle = canOpenChannel(channel, flags) canBusOn(self._read_handle) - can_driver_mode = canstat.canDRIVER_SILENT if driver_mode == DRIVER_MODE_SILENT else canstat.canDRIVER_NORMAL + can_driver_mode = ( + canstat.canDRIVER_SILENT + if driver_mode == DRIVER_MODE_SILENT + else canstat.canDRIVER_NORMAL + ) canSetBusOutputControl(self._write_handle, can_driver_mode) - log.debug('Going bus on TX handle') + log.debug("Going bus on TX handle") canBusOn(self._write_handle) timer = ctypes.c_uint(0) @@ -446,26 +499,26 @@ def __init__(self, channel, can_filters=None, **kwargs): self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) self._is_filtered = False - super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) 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 + 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) + 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) + 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') + log.info("Hardware filtering has been disabled") try: for handle in (self._read_handle, self._write_handle): for extended in (0, 1): @@ -496,7 +549,7 @@ def _recv_internal(self, timeout=None): else: timeout = int(timeout * 1000) - #log.log(9, 'Reading for %d ms on handle: %s' % (timeout, self._read_handle)) + # log.log(9, 'Reading for %d ms on handle: %s' % (timeout, self._read_handle)) status = canReadWait( self._read_handle, ctypes.byref(arb_id), @@ -504,11 +557,11 @@ def _recv_internal(self, timeout=None): ctypes.byref(dlc), ctypes.byref(flags), ctypes.byref(timestamp), - timeout # This is an X ms blocking read + timeout, # This is an X ms blocking read ) if status == canstat.canOK: - #log.debug('read complete -> status OK') + # log.debug('read complete -> status OK') data_array = data.raw flags = flags.value is_extended = bool(flags & canstat.canMSG_EXT) @@ -518,25 +571,27 @@ def _recv_internal(self, timeout=None): 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) + 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') + # 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)) + # 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 @@ -548,11 +603,9 @@ def send(self, msg, timeout=None): 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) + canWrite( + self._write_handle, msg.arbitration_id, ctypes.byref(buf), msg.dlc, flags + ) if timeout: canWriteSync(self._write_handle, int(timeout * 1000)) @@ -569,7 +622,7 @@ def flash(self, flash=True): try: kvFlashLeds(self._read_handle, action, 30000) except (CANLIBError, NotImplementedError) as e: - log.error('Could not flash LEDs (%s)', e) + log.error("Could not flash LEDs (%s)", e) def shutdown(self): # Wait for transmit queue to be cleared @@ -599,9 +652,9 @@ def get_stats(self): """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() - canGetBusStatistics(self._write_handle, - ctypes.pointer(stats), - ctypes.sizeof(stats)) + canGetBusStatistics( + self._write_handle, ctypes.pointer(stats), ctypes.sizeof(stats) + ) return stats @staticmethod @@ -612,7 +665,7 @@ def _detect_available_configs(): except Exception: pass return [ - {'interface': 'kvaser', 'channel': channel} + {"interface": "kvaser", "channel": channel} for channel in range(num_channels.value) ] @@ -622,18 +675,30 @@ def get_channel_info(channel): 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 '%s, S/N %d (#%d)' % ( - name.value.decode("ascii"), serial.value, number.value + 1) + 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 "%s, S/N %d (#%d)" % ( + name.value.decode("ascii"), + serial.value, + number.value + 1, + ) init_kvaser_library() diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index 0188235be..9dd3a9163 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Contains Python equivalents of the function and constant definitions in CANLIB's canstat.h, with some supporting functionality @@ -14,6 +12,7 @@ class c_canStatus(ctypes.c_int): pass + # TODO better formatting canOK = 0 canERR_PARAM = -1 @@ -54,7 +53,8 @@ class c_canStatus(ctypes.c_int): def CANSTATUS_SUCCESS(status): return status >= canOK -canMSG_MASK = 0x00ff + +canMSG_MASK = 0x00FF canMSG_RTR = 0x0001 canMSG_STD = 0x0002 canMSG_EXT = 0x0004 @@ -68,7 +68,7 @@ def CANSTATUS_SUCCESS(status): canFDMSG_BRS = 0x020000 canFDMSG_ESI = 0x040000 -canMSGERR_MASK = 0xff00 +canMSGERR_MASK = 0xFF00 canMSGERR_HW_OVERRUN = 0x0200 canMSGERR_SW_OVERRUN = 0x0400 canMSGERR_STUFF = 0x0800 @@ -153,7 +153,7 @@ def CANSTATUS_SUCCESS(status): canTRANSCEIVER_TYPE_LINX_J1708: "LINX_J1708", canTRANSCEIVER_TYPE_LINX_K: "LINX_K", canTRANSCEIVER_TYPE_LINX_SWC: "LINX_SWC", - canTRANSCEIVER_TYPE_LINX_LS: "LINX_LS" + canTRANSCEIVER_TYPE_LINX_LS: "LINX_LS", } canDRIVER_NORMAL = 4 diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 21eefe86f..c7d363dd4 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Contains Python equivalents of the structures in CANLIB's canlib.h, with some supporting functionality specific to Python. @@ -15,6 +13,7 @@ class BusStatistics(ctypes.Structure): .. seealso:: :meth:`KvaserBus.get_stats` """ + _fields_ = [ ("m_stdData", ctypes.c_ulong), ("m_stdRemote", ctypes.c_ulong), @@ -22,12 +21,14 @@ class BusStatistics(ctypes.Structure): ("m_extRemote", ctypes.c_ulong), ("m_errFrame", ctypes.c_ulong), ("m_busLoad", ctypes.c_ulong), - ("m_overruns", ctypes.c_ulong) + ("m_overruns", ctypes.c_ulong), ] def __str__(self): - return ("std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " - "err_frame: {}, bus_load: {:.1f}%, overruns: {}").format( + return ( + "std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " + "err_frame: {}, bus_load: {:.1f}%, overruns: {}" + ).format( self.std_data, self.std_remote, self.ext_data, diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 0e962cd2f..cc22c4f12 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ NI-CAN interface module. @@ -23,35 +21,35 @@ logger = logging.getLogger(__name__) -NC_SUCCESS = 0 -NC_ERR_TIMEOUT = 1 -TIMEOUT_ERROR_CODE = -1074388991 +NC_SUCCESS = 0 +NC_ERR_TIMEOUT = 1 +TIMEOUT_ERROR_CODE = -1074388991 -NC_DURATION_INFINITE = 0xFFFFFFFF +NC_DURATION_INFINITE = 0xFFFFFFFF -NC_OP_START = 0x80000001 -NC_OP_STOP = 0x80000002 -NC_OP_RESET = 0x80000003 +NC_OP_START = 0x80000001 +NC_OP_STOP = 0x80000002 +NC_OP_RESET = 0x80000003 -NC_FRMTYPE_REMOTE = 1 -NC_FRMTYPE_COMM_ERR = 2 +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_ST_READ_AVAIL = 0x00000001 +NC_ST_WRITE_SUCCESS = 0x00000002 +NC_ST_ERROR = 0x00000010 +NC_ST_WARNING = 0x00000020 -NC_ATTR_BAUD_RATE = 0x80000007 +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_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 +NC_FL_CAN_ARBID_XTD = 0x20000000 CanData = ctypes.c_ubyte * 8 @@ -66,6 +64,7 @@ class RxMessageStruct(ctypes.Structure): ("data", CanData), ] + class TxMessageStruct(ctypes.Structure): _fields_ = [ ("arb_id", ctypes.c_ulong), @@ -98,7 +97,11 @@ def get_error_message(status_code): 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] + ctypes.c_char_p, + ctypes.c_ulong, + ctypes.c_void_p, + ctypes.c_void_p, + ] nican.ncConfig.errcheck = check_status nican.ncOpenObject.argtypes = [ctypes.c_char_p, ctypes.c_void_p] nican.ncOpenObject.errcheck = check_status @@ -108,14 +111,18 @@ def get_error_message(status_code): 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] + 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] + 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. @@ -129,7 +136,9 @@ class NicanBus(BusABC): """ - def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs): + def __init__( + self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs + ): """ :param str channel: Name of the object to open (e.g. 'CAN0') @@ -150,42 +159,47 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k """ if nican is None: - raise ImportError("The NI-CAN driver could not be loaded. " - "Check that you are using 32-bit Python on Windows.") + raise ImportError( + "The NI-CAN driver could not be loaded. " + "Check that you are using 32-bit Python on Windows." + ) self.channel = channel self.channel_info = "NI-CAN: " + channel if not isinstance(channel, bytes): channel = channel.encode() - config = [ - (NC_ATTR_START_ON_OPEN, True), - (NC_ATTR_LOG_COMM_ERRS, log_errors) - ] + 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) - ]) + 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) - ]) + 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), - ]) + 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)) @@ -193,17 +207,23 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k 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, - len(config), - ctypes.byref(attr_id_list), - ctypes.byref(attr_value_list)) + nican.ncConfig( + channel, + len(config), + ctypes.byref(attr_id_list), + ctypes.byref(attr_value_list), + ) self.handle = ctypes.c_ulong() nican.ncOpenObject(channel, ctypes.byref(self.handle)) - super(NicanBus, self).__init__(channel=channel, - can_filters=can_filters, bitrate=bitrate, - log_errors=log_errors, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + bitrate=bitrate, + log_errors=log_errors, + **kwargs + ) def _recv_internal(self, timeout): """ @@ -223,7 +243,8 @@ def _recv_internal(self, timeout): state = ctypes.c_ulong() try: nican.ncWaitForState( - self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state)) + self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state) + ) except NicanError as e: if e.error_code == TIMEOUT_ERROR_CODE: return None, True @@ -241,14 +262,16 @@ def _recv_internal(self, timeout): 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]) + 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, timeout=None): @@ -265,20 +288,18 @@ def send(self, msg, timeout=None): 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)) + 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( + # state = ctypes.c_ulong() + # nican.ncWaitForState( # self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state)) def reset(self): @@ -293,22 +314,12 @@ def shutdown(self): """Close object.""" nican.ncCloseObject(self.handle) - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.nican.NicanBus`. - """ - if self.__set_filters_has_been_called: - logger.warn("using filters is not supported like this, see note on NicanBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True - class NicanError(CanError): """Error from NI-CAN driver.""" def __init__(self, function, error_code, arguments): - super(NicanError, self).__init__() + super().__init__() #: Status code self.error_code = error_code #: Function that failed @@ -318,4 +329,6 @@ def __init__(self, function, error_code, arguments): def __str__(self): return "Function %s failed:\n%s" % ( - self.function.__name__, get_error_message(self.error_code)) + self.function.__name__, + get_error_message(self.error_code), + ) diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index ceba250b5..3627f0a36 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 053197119..94a9ec950 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ PCAN-Basic API @@ -17,244 +15,364 @@ import platform import logging -logger = logging.getLogger('can.pcan') +if platform.system() == "Windows": + import winreg + -#/////////////////////////////////////////////////////////// +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 - -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// + +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 +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_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_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_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-Plug&Play 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_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value +PCAN_PEAKCAN = TPCANDevice( + 0x01 +) # PCAN Non-Plug&Play 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_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter -PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 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_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter +PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 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 parameter values -PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) -PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) -PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received -PCAN_FILTER_OPEN = int(0x01) # The PCAN filter is fully opened. All messages will be received -PCAN_FILTER_CUSTOM = int(0x02) # The PCAN filter is custom configured. Only registered messages will be received -PCAN_CHANNEL_UNAVAILABLE = int(0x00) # The PCAN-Channel handle is illegal, or its associated hardware is not available -PCAN_CHANNEL_AVAILABLE = int(0x01) # The PCAN-Channel handle is available to be connected (Plug&Play Hardware: it means furthermore that the hardware is plugged-in) -PCAN_CHANNEL_OCCUPIED = int(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 = int(0x00) # Logs system exceptions / errors -LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions -LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions -LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions -LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function -LOG_FUNCTION_READ = int(0x10) # Logs the CAN messages received within the CAN_Read function -LOG_FUNCTION_ALL = int(0xFFFF)# Logs all possible information within the PCAN-Basic API functions - -TRACE_FILE_SINGLE = int(0x00) # A single file is written until it size reaches PAN_TRACE_SIZE -TRACE_FILE_SEGMENTED = int(0x01) # Traced data is distributed in several files with size PAN_TRACE_SIZE -TRACE_FILE_DATE = int(0x02) # Includes the date into the name of the trace file -TRACE_FILE_TIME = int(0x04) # Includes the start time into the name of the trace file -TRACE_FILE_OVERWRITE = int(0x80) # Causes the overwriting of available traces (same name) - -FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) -FEATURE_DELAY_CAPABLE = int(0x02) # Device supports a delay between sending frames (FPGA based USB devices) -FEATURE_IO_CAPABLE = int(0x04) # Device supports I/O functionality for electronic circuits (USB-Chip devices) - -SERVICE_STATUS_STOPPED = int(0x01) # The service is not running -SERVICE_STATUS_RUNNING = int(0x04) # The service is running +PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) +PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) +PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received +PCAN_FILTER_OPEN = int( + 0x01 +) # The PCAN filter is fully opened. All messages will be received +PCAN_FILTER_CUSTOM = int( + 0x02 +) # The PCAN filter is custom configured. Only registered messages will be received +PCAN_CHANNEL_UNAVAILABLE = int( + 0x00 +) # The PCAN-Channel handle is illegal, or its associated hardware is not available +PCAN_CHANNEL_AVAILABLE = int( + 0x01 +) # The PCAN-Channel handle is available to be connected (Plug&Play Hardware: it means furthermore that the hardware is plugged-in) +PCAN_CHANNEL_OCCUPIED = int( + 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 = int(0x00) # Logs system exceptions / errors +LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions +LOG_FUNCTION_PARAMETERS = int( + 0x02 +) # Logs the parameters passed to the PCAN-Basic API functions +LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions +LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function +LOG_FUNCTION_READ = int( + 0x10 +) # Logs the CAN messages received within the CAN_Read function +LOG_FUNCTION_ALL = int( + 0xFFFF +) # Logs all possible information within the PCAN-Basic API functions + +TRACE_FILE_SINGLE = int( + 0x00 +) # A single file is written until it size reaches PAN_TRACE_SIZE +TRACE_FILE_SEGMENTED = int( + 0x01 +) # Traced data is distributed in several files with size PAN_TRACE_SIZE +TRACE_FILE_DATE = int(0x02) # Includes the date into the name of the trace file +TRACE_FILE_TIME = int(0x04) # Includes the start time into the name of the trace file +TRACE_FILE_OVERWRITE = int( + 0x80 +) # Causes the overwriting of available traces (same name) + +FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) +FEATURE_DELAY_CAPABLE = int( + 0x02 +) # Device supports a delay between sending frames (FPGA based USB devices) +FEATURE_IO_CAPABLE = int( + 0x04 +) # Device supports I/O functionality for electronic circuits (USB-Chip devices) + +SERVICE_STATUS_STOPPED = int(0x01) # The service is not running +SERVICE_STATUS_RUNNING = int(0x04) # The service is running # 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_ERRFRAME = TPCANMessageType(0x40) # The PCAN message represents an error frame -PCAN_MESSAGE_STATUS = TPCANMessageType(0x80) # The PCAN message represents a PCAN status message +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_ERRFRAME = TPCANMessageType( + 0x40 +) # The PCAN message represents an error frame +PCAN_MESSAGE_STATUS = TPCANMessageType( + 0x80 +) # The PCAN message represents a PCAN status message # Frame Type / Initialization Mode -PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD -PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED +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 +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: @@ -264,113 +382,184 @@ # 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") +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 No-Plug-And-Play 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 - - -class TPCANMsg (Structure): +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", +} + + +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]) + + _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]) -class TPCANMsgMac (Structure): +class TPCANMsgMac(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_ulong), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("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]) + _fields_ = [ + ( + "ID", + c_ulong, + ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("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]) -class TPCANTimestamp (Structure): + +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 + + _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 -class TPCANTimestampMac (Structure): +class TPCANTimestampMac(Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow """ - _fields_ = [ ("millis", c_ulong), # Base-value: milliseconds: 0.. 2^32-1 - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("millis_overflow", c_ushort), # Roll-arounds of millis - ("micros", c_ushort) ] # Microseconds: 0..999 + _fields_ = [ + ( + "millis", + c_ulong, + ), # Base-value: milliseconds: 0.. 2^32-1 - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("millis_overflow", c_ushort), # Roll-arounds of millis + ("micros", c_ushort), + ] # Microseconds: 0..999 -class TPCANMsgFD (Structure): + +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]) -class TPCANMsgFDMac (Structure): + _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]) + + +class TPCANMsgFDMac(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_ulong), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("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]) -#/////////////////////////////////////////////////////////// + _fields_ = [ + ( + "ID", + c_ulong, + ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("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]) + + +# /////////////////////////////////////////////////////////// # PCAN-Basic API function declarations -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// + class PCANBasic: """PCAN-Basic API class implementation """ def __init__(self): - # Loads the PCANBasic.dll - if platform.system() == 'Windows': + # Loads the PCANBasic.dll and checks if driver is available + if platform.system() == "Windows": self.__m_dllBasic = windll.LoadLibrary("PCANBasic") - elif platform.system() == 'Darwin': - self.__m_dllBasic = cdll.LoadLibrary('libPCBUSB.dylib') + aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") + winreg.CloseKey(aKey) + except WindowsError: + logger.error("Exception: The PEAK-driver couldn't be found!") + finally: + winreg.CloseKey(aReg) + elif platform.system() == "Darwin": + self.__m_dllBasic = cdll.LoadLibrary("libPCBUSB.dylib") else: self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") - if self.__m_dllBasic == None: + if self.__m_dllBasic is None: logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!") def Initialize( self, Channel, Btr0Btr1, - HwType = TPCANType(0), - IOPort = c_uint(0), - Interrupt = c_ushort(0)): + HwType=TPCANType(0), + IOPort=c_uint(0), + Interrupt=c_ushort(0), + ): """ Initializes a PCAN Channel @@ -386,16 +575,15 @@ def Initialize( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_Initialize(Channel,Btr0Btr1,HwType,IOPort,Interrupt) + res = self.__m_dllBasic.CAN_Initialize( + Channel, Btr0Btr1, HwType, IOPort, Interrupt + ) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.Initialize") raise - def InitializeFD( - self, - Channel, - BitrateFD): + def InitializeFD(self, Channel, BitrateFD): """ Initializes a FD capable PCAN Channel @@ -419,15 +607,13 @@ def InitializeFD( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_InitializeFD(Channel,BitrateFD) + res = self.__m_dllBasic.CAN_InitializeFD(Channel, BitrateFD) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.InitializeFD") raise - def Uninitialize( - self, - Channel): + def Uninitialize(self, Channel): """ Uninitializes one or all PCAN Channels initialized by CAN_Initialize @@ -448,9 +634,7 @@ def Uninitialize( logger.error("Exception on PCANBasic.Uninitialize") raise - def Reset( - self, - Channel): + def Reset(self, Channel): """ Resets the receive and transmit queues of the PCAN Channel @@ -471,9 +655,7 @@ def Reset( logger.error("Exception on PCANBasic.Reset") raise - def GetStatus( - self, - Channel): + def GetStatus(self, Channel): """ Gets the current status of a PCAN Channel @@ -491,9 +673,7 @@ def GetStatus( logger.error("Exception on PCANBasic.GetStatus") raise - def Read( - self, - Channel): + def Read(self, Channel): """ Reads a CAN message from the receive queue of a PCAN Channel @@ -513,21 +693,19 @@ def Read( A touple with three values """ try: - if platform.system() == 'Darwin': + if platform.system() == "Darwin": msg = TPCANMsgMac() timestamp = TPCANTimestampMac() else: msg = TPCANMsg() timestamp = TPCANTimestamp() - res = self.__m_dllBasic.CAN_Read(Channel,byref(msg),byref(timestamp)) - return TPCANStatus(res),msg,timestamp + res = self.__m_dllBasic.CAN_Read(Channel, byref(msg), byref(timestamp)) + return TPCANStatus(res), msg, timestamp except: logger.error("Exception on PCANBasic.Read") raise - def ReadFD( - self, - Channel): + def ReadFD(self, Channel): """ Reads a CAN message from the receive queue of a FD capable PCAN Channel @@ -547,21 +725,18 @@ def ReadFD( A touple with three values """ try: - if platform.system() == 'Darwin': + if platform.system() == "Darwin": msg = TPCANMsgFDMac() else: msg = TPCANMsgFD() timestamp = TPCANTimestampFD() - res = self.__m_dllBasic.CAN_ReadFD(Channel,byref(msg),byref(timestamp)) - return TPCANStatus(res),msg,timestamp + res = self.__m_dllBasic.CAN_ReadFD(Channel, byref(msg), byref(timestamp)) + return TPCANStatus(res), msg, timestamp except: logger.error("Exception on PCANBasic.ReadFD") raise - def Write( - self, - Channel, - MessageBuffer): + def Write(self, Channel, MessageBuffer): """ Transmits a CAN message @@ -574,16 +749,13 @@ def Write( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_Write(Channel,byref(MessageBuffer)) + res = self.__m_dllBasic.CAN_Write(Channel, byref(MessageBuffer)) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.Write") raise - def WriteFD( - self, - Channel, - MessageBuffer): + def WriteFD(self, Channel, MessageBuffer): """ Transmits a CAN message over a FD capable PCAN Channel @@ -596,18 +768,13 @@ def WriteFD( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_WriteFD(Channel,byref(MessageBuffer)) + res = self.__m_dllBasic.CAN_WriteFD(Channel, byref(MessageBuffer)) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.WriteFD") raise - def FilterMessages( - self, - Channel, - FromID, - ToID, - Mode): + def FilterMessages(self, Channel, FromID, ToID, Mode): """ Configures the reception filter @@ -627,16 +794,13 @@ def FilterMessages( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_FilterMessages(Channel,FromID,ToID,Mode) + res = self.__m_dllBasic.CAN_FilterMessages(Channel, FromID, ToID, Mode) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.FilterMessages") raise - def GetValue( - self, - Channel, - Parameter): + def GetValue(self, Channel, Parameter): """ Retrieves a PCAN Channel value @@ -658,22 +822,28 @@ def GetValue( A touple 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: + if Parameter in ( + PCAN_API_VERSION, + PCAN_HARDWARE_NAME, + PCAN_CHANNEL_VERSION, + PCAN_LOG_LOCATION, + PCAN_TRACE_LOCATION, + PCAN_BITRATE_INFO_FD, + PCAN_IP_ADDRESS, + ): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) - res = self.__m_dllBasic.CAN_GetValue(Channel,Parameter,byref(mybuffer),sizeof(mybuffer)) - return TPCANStatus(res),mybuffer.value + res = self.__m_dllBasic.CAN_GetValue( + Channel, Parameter, byref(mybuffer), sizeof(mybuffer) + ) + return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetValue") raise - def SetValue( - self, - Channel, - Parameter, - Buffer): + def SetValue(self, Channel, Parameter, Buffer): """ Returns a descriptive text of a given TPCANStatus error @@ -694,22 +864,21 @@ def SetValue( A TPCANStatus error code """ try: - if Parameter == PCAN_LOG_LOCATION or Parameter == PCAN_LOG_TEXT or Parameter == PCAN_TRACE_LOCATION: + if Parameter in (PCAN_LOG_LOCATION, PCAN_LOG_TEXT, PCAN_TRACE_LOCATION): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) mybuffer.value = Buffer - res = self.__m_dllBasic.CAN_SetValue(Channel,Parameter,byref(mybuffer),sizeof(mybuffer)) + 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): + def GetErrorText(self, Error, Language=0): """ Configures or sets a PCAN Channel value @@ -733,8 +902,8 @@ def GetErrorText( """ try: mybuffer = create_string_buffer(256) - res = self.__m_dllBasic.CAN_GetErrorText(Error,Language,byref(mybuffer)) - return TPCANStatus(res),mybuffer.value + res = self.__m_dllBasic.CAN_GetErrorText(Error, Language, byref(mybuffer)) + return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetErrorText") raise diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 864308bab..14d8dc774 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,77 +1,87 @@ -# coding: utf-8 - """ Enable basic CAN over a PCAN USB device. """ -from __future__ import absolute_import, print_function, division - import logging -import sys import time -import can +from typing import Optional from can import CanError, Message, BusABC from can.bus import BusState from can.util import len2dlc, dlc2len from .basic import * -boottimeEpoch = 0 try: + # use the "uptime" library if available import uptime import datetime - boottimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() -except: + + boottimeEpoch = ( + uptime.boottime() - datetime.datetime.utcfromtimestamp(0) + ).total_seconds() +except ImportError: boottimeEpoch = 0 try: # Try builtin Python 3 Windows API from _overlapped import CreateEvent from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + HAS_EVENTS = True except ImportError: try: # Try pywin32 package from win32event import CreateEvent from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + HAS_EVENTS = True except ImportError: # Use polling instead HAS_EVENTS = False -try: - # new in 3.3 - timeout_clock = time.perf_counter -except AttributeError: - # deprecated in 3.3 - timeout_clock = time.clock - # Set up logging -log = logging.getLogger('can.pcan') - - -pcan_bitrate_objs = {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'] +log = logging.getLogger("can.pcan") + + +pcan_bitrate_objs = { + 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", +] class PcanBus(BusABC): - - def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, *args, **kwargs): + def __init__( + self, + channel="PCAN_USBBUS1", + state=BusState.ACTIVE, + bitrate=500000, + *args, + **kwargs + ): """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, @@ -162,7 +172,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 """ self.channel_info = channel - self.fd = kwargs.get('fd', False) + self.fd = kwargs.get("fd", False) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA @@ -177,22 +187,35 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 else: raise ArgumentError("BusState must be Active or Passive") - if self.fd: - f_clock_val = kwargs.get('f_clock', None) + f_clock_val = kwargs.get("f_clock", None) if f_clock_val is None: - f_clock = "{}={}".format('f_clock_mhz', kwargs.get('f_clock_mhz', None)) + f_clock = "{}={}".format("f_clock_mhz", kwargs.get("f_clock_mhz", None)) else: - f_clock = "{}={}".format('f_clock', kwargs.get('f_clock', None)) - - fd_parameters_values = [f_clock] + ["{}={}".format(key, kwargs.get(key, None)) for key in pcan_fd_parameter_list if kwargs.get(key, None) is not None] + f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None)) - self.fd_bitrate = ' ,'.join(fd_parameters_values).encode("ascii") + fd_parameters_values = [f_clock] + [ + "{}={}".format(key, kwargs.get(key, None)) + for key in pcan_fd_parameter_list + if kwargs.get(key, None) is not None + ] + self.fd_bitrate = " ,".join(fd_parameters_values).encode("ascii") - result = self.m_objPCANBasic.InitializeFD(self.m_PcanHandle, self.fd_bitrate) + result = self.m_objPCANBasic.InitializeFD( + self.m_PcanHandle, self.fd_bitrate + ) else: - result = self.m_objPCANBasic.Initialize(self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt) + result = self.m_objPCANBasic.Initialize( + self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt + ) + + if result != PCAN_ERROR_OK: + raise PcanError(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: raise PcanError(self._get_formatted_error(result)) @@ -200,11 +223,12 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 if HAS_EVENTS: self._recv_event = CreateEvent(None, 0, 0, None) result = self.m_objPCANBasic.SetValue( - self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event) + self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event + ) if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) - super(PcanBus, self).__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) + super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) def _get_formatted_error(self, error): """ @@ -222,7 +246,7 @@ def bits(n): """ while n: # Create a mask to mask the lowest set bit in n - mask = (~n + 1) + mask = ~n + 1 masked_value = n & mask yield masked_value # Toggle the lowest set bit @@ -235,15 +259,17 @@ def bits(n): for b in bits(error): stsReturn = self.m_objPCANBasic.GetErrorText(b, 0) if stsReturn[0] != PCAN_ERROR_OK: - text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format(error) + text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format( + error + ) else: - text = stsReturn[1].decode('utf-8', errors='replace') + text = stsReturn[1].decode("utf-8", errors="replace") strings.append(text) - complete_text = '\n'.join(strings) + complete_text = "\n".join(strings) else: - complete_text = stsReturn[1].decode('utf-8', errors='replace') + complete_text = stsReturn[1].decode("utf-8", errors="replace") return complete_text @@ -277,9 +303,9 @@ def _recv_internal(self, timeout): timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE elif timeout is not None: # Calculate max time - end_time = timeout_clock() + timeout + end_time = time.perf_counter() + timeout - #log.debug("Trying to read a msg") + # log.debug("Trying to read a msg") result = None while result is None: @@ -293,7 +319,7 @@ def _recv_internal(self, timeout): val = WaitForSingleObject(self._recv_event, timeout_ms) if val != WAIT_OBJECT_0: return None, False - elif timeout is not None and timeout_clock() >= end_time: + elif timeout is not None and time.perf_counter() >= end_time: return None, False else: result = None @@ -307,39 +333,60 @@ def _recv_internal(self, timeout): theMsg = result[1] itsTimeStamp = result[2] - #log.debug("Received a message") + # log.debug("Received a message") - is_extended_id = (theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) == PCAN_MESSAGE_EXTENDED.value - is_remote_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value) == PCAN_MESSAGE_RTR.value + is_extended_id = ( + theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value + ) == PCAN_MESSAGE_EXTENDED.value + is_remote_frame = ( + theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value + ) == PCAN_MESSAGE_RTR.value is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value - bitrate_switch = (theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value) == PCAN_MESSAGE_BRS.value - error_state_indicator = (theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value) == PCAN_MESSAGE_ESI.value - is_error_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) == PCAN_MESSAGE_ERRFRAME.value - + bitrate_switch = ( + theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value + ) == PCAN_MESSAGE_BRS.value + error_state_indicator = ( + theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value + ) == PCAN_MESSAGE_ESI.value + is_error_frame = ( + theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value + ) == PCAN_MESSAGE_ERRFRAME.value if self.fd: dlc = dlc2len(theMsg.DLC) timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0)) else: dlc = theMsg.LEN - timestamp = boottimeEpoch + ((itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow) / (1000.0 * 1000.0)) - - - rx_msg = Message(timestamp=timestamp, - arbitration_id=theMsg.ID, - is_extended_id=is_extended_id, - is_remote_frame=is_remote_frame, - is_error_frame=is_error_frame, - dlc=dlc, - data=theMsg.DATA[:dlc], - is_fd=is_fd, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator) + timestamp = boottimeEpoch + ( + ( + itsTimeStamp.micros + + 1000 * itsTimeStamp.millis + + 0x100000000 * 1000 * itsTimeStamp.millis_overflow + ) + / (1000.0 * 1000.0) + ) + + rx_msg = Message( + timestamp=timestamp, + arbitration_id=theMsg.ID, + is_extended_id=is_extended_id, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, + dlc=dlc, + data=theMsg.DATA[:dlc], + is_fd=is_fd, + 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 + 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: @@ -353,11 +400,11 @@ def send(self, msg, timeout=None): if self.fd: # create a TPCANMsg message structure - if platform.system() == 'Darwin': + if platform.system() == "Darwin": CANMsg = TPCANMsgFDMac() else: CANMsg = TPCANMsgFD() - + # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id CANMsg.DLC = len2dlc(msg.dlc) @@ -374,7 +421,7 @@ def send(self, msg, timeout=None): else: # create a TPCANMsg message structure - if platform.system() == 'Darwin': + if platform.system() == "Darwin": CANMsg = TPCANMsgMac() else: CANMsg = TPCANMsg() @@ -385,9 +432,7 @@ def send(self, msg, timeout=None): CANMsg.MSGTYPE = msgType # if a remote frame will be sent, data bytes are not important. - if msg.is_remote_frame: - CANMsg.MSGTYPE = msgType.value | PCAN_MESSAGE_RTR.value - else: + if not msg.is_remote_frame: # copy data for i in range(CANMsg.LEN): CANMsg.DATA[i] = msg.data[i] @@ -405,10 +450,12 @@ 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)) + self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash) + ) def shutdown(self): - super(PcanBus, self).shutdown() + super().shutdown() self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) @property @@ -417,21 +464,83 @@ def state(self): @state.setter def state(self, new_state): - - self._state = 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) + 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) + 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 = [] + 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: + 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} + ) + return channels + + def status_string(self) -> Optional[str]: + """ + Query the PCAN bus status. + :return: The status in string. + """ + if self.status() in PCAN_DICT_STATUS: + return PCAN_DICT_STATUS[self.status()] + else: + return None class PcanError(CanError): """ A generic error on a PCAN bus. """ - pass diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py new file mode 100644 index 000000000..cb35aa774 --- /dev/null +++ b/can/interfaces/robotell.py @@ -0,0 +1,400 @@ +""" +Interface for Chinese Robotell compatible interfaces (win32/linux). +""" + +import time +import logging + +from can import BusABC, Message + +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. + :param int ttyBaudrate: + baudrate of underlying serial or usb device + :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 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.channel_info = "Robotell USB-CAN s/n %s on %s" % ( + self.get_serial_number(1), + channel, + ) + logger.info("Using device: {}".format(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( + "Invalid bitrate, must be less than " + str(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 managemenet" 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 configid >= self._CAN_FILTER_BASE_ID and 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( + "Timeout waiting for response when reading config value {:04X}.".format( + configid + ) + ) + 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 " + + str(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 " + + str(len(newmsg)) + + ", ignoring message" + ) + + # 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): + self.serialPortOrig.close() + + def fileno(self): + if hasattr(self.serialPortOrig, "fileno"): + return self.serialPortOrig.fileno() + # Return an invalid file descriptor on Windows + return -1 + + def get_serial_number(self, timeout): + """Get serial number of the slcan interface. + :type timeout: int or None + :param timeout: + seconds to wait for serial number or None to wait indefinitely + :rtype str or None + :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 += "{:02X}{:02X}-".format(sn1[idx], sn1[idx + 1]) + for idx in range(0, 4, 2): + serial += "{:02X}{:02X}-".format(sn2[idx], sn2[idx + 1]) + return serial[:-1] diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py new file mode 100644 index 000000000..cb1c17f1d --- /dev/null +++ b/can/interfaces/seeedstudio/__init__.py @@ -0,0 +1,4 @@ +""" +""" + +from can.interfaces.seeedstudio.seeedstudio import SeeedBus diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py new file mode 100644 index 000000000..aecc3c15f --- /dev/null +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -0,0 +1,268 @@ +""" +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 logging +import struct +from time import time +from can import BusABC, 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, + *args, + **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. + + """ + 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]) + if not channel: + raise ValueError("Must specify a serial port.") + + self.channel_info = "Serial interface: " + channel + self.ser = serial.Serial( + channel, baudrate=baudrate, timeout=timeout, rtscts=False + ) + + super(SeeedBus, self).__init__(channel=channel, *args, **kwargs) + self.init_frame() + + def shutdown(self): + """ + Close the serial interface. + """ + 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()) + self.ser.write(byte_msg) + + 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.ser.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(" 0: + continue + else: + return None + # return first message + for i in range(len(self._buffer)): + if self._buffer[i] == ord(self._OK) or self._buffer[i] == ord(self._ERROR): + string = self._buffer[: i + 1].decode() + del self._buffer[: i + 1] + break + return string + + def flush(self): + del self._buffer[:] + while self.serialPortOrig.in_waiting: + self.serialPortOrig.read() + def open(self): - self.write('O') + self._write("O") def close(self): - self.write('C') + self._write("C") def _recv_internal(self, timeout): - if timeout != self.serialPortOrig.timeout: - self.serialPortOrig.timeout = timeout canId = None remote = False extended = False frame = [] - # First read what is already in the receive buffer - while (self.serialPortOrig.in_waiting and - self.LINE_TERMINATOR not in self._buffer): - self._buffer += self.serialPortOrig.read(1) - - # If we still don't have a complete message, do a blocking read - if self.LINE_TERMINATOR not in self._buffer: - self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR) - - if self.LINE_TERMINATOR not in self._buffer: - # Timed out - return None, False + string = self._read(timeout) - readStr = self._buffer.decode() - del self._buffer[:] - if not readStr: + if not string: pass - elif readStr[0] == 'T': + elif string[0] == "T": # extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) + canId = int(string[1:9], 16) + dlc = int(string[9]) extended = True for i in range(0, dlc): - frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) - elif readStr[0] == 't': + frame.append(int(string[10 + i * 2 : 12 + i * 2], 16)) + elif string[0] == "t": # normal frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) + canId = int(string[1:4], 16) + dlc = int(string[4]) for i in range(0, dlc): - frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) - elif readStr[0] == 'r': + frame.append(int(string[5 + i * 2 : 7 + i * 2], 16)) + elif string[0] == "r": # remote frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) + canId = int(string[1:4], 16) + dlc = int(string[4]) remote = True - elif readStr[0] == 'R': + elif string[0] == "R": # remote extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) + canId = int(string[1:9], 16) + dlc = int(string[9]) extended = True remote = True - if canId is not None: - msg = Message(arbitration_id=canId, - is_extended_id=extended, - timestamp=time.time(), # Better than nothing... - is_remote_frame=remote, - dlc=dlc, - data=frame) + msg = Message( + arbitration_id=canId, + is_extended_id=extended, + timestamp=time.time(), # Better than nothing... + is_remote_frame=remote, + dlc=dlc, + data=frame, + ) return msg, False return None, False def send(self, msg, timeout=None): if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout - if msg.is_remote_frame: if msg.is_extended_id: sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc) @@ -189,16 +236,90 @@ def send(self, msg, timeout=None): sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc) else: sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) - sendStr += "".join(["%02X" % b for b in msg.data]) - self.write(sendStr) + self._write(sendStr) def shutdown(self): self.close() self.serialPortOrig.close() def fileno(self): - if hasattr(self.serialPortOrig, 'fileno'): + if hasattr(self.serialPortOrig, "fileno"): return self.serialPortOrig.fileno() # Return an invalid file descriptor on Windows return -1 + + def get_version(self, timeout): + """Get HW and SW version of the slcan interface. + + :type timeout: int or None + :param timeout: + seconds to wait for version or None to wait indefinitely + + :returns: tuple (hw_version, sw_version) + WHERE + int hw_version is the hardware version or None on timeout + int sw_version is the software version or None on timeout + """ + cmd = "V" + self._write(cmd) + + start = time.time() + time_left = timeout + while True: + string = self._read(time_left) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + # convert ASCII coded version + hw_version = int(string[1:3]) + sw_version = int(string[3:5]) + return hw_version, sw_version + # if timeout is None, try indefinitely + if timeout is None: + continue + # try next one only if there still is time, and with + # reduced timeout + else: + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None, None + + def get_serial_number(self, timeout): + """Get serial number of the slcan interface. + + :type timeout: int or None + :param timeout: + seconds to wait for serial number or None to wait indefinitely + + :rtype str or None + :return: + None on timeout or a str object. + """ + cmd = "N" + self._write(cmd) + + start = time.time() + time_left = timeout + while True: + string = self._read(time_left) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + serial_number = string[1:-1] + return serial_number + # if timeout is None, try indefinitely + if timeout is None: + continue + # try next one only if there still is time, and with + # reduced timeout + else: + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 8a2105598..e08c18f50 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -1,7 +1,5 @@ -# coding: utf-8 - """ See: https://www.kernel.org/doc/Documentation/networking/can.txt """ -from can.interfaces.socketcan.socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask +from .socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index b56eaae64..0db298371 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -1,63 +1,62 @@ -# coding: utf-8 - """ Defines shared CAN constants. """ -CAN_ERR_FLAG = 0x20000000 -CAN_RTR_FLAG = 0x40000000 -CAN_EFF_FLAG = 0x80000000 +CAN_ERR_FLAG = 0x20000000 +CAN_RTR_FLAG = 0x40000000 +CAN_EFF_FLAG = 0x80000000 # BCM opcodes -CAN_BCM_TX_SETUP = 1 -CAN_BCM_TX_DELETE = 2 +CAN_BCM_TX_SETUP = 1 +CAN_BCM_TX_DELETE = 2 +CAN_BCM_TX_READ = 3 # BCM flags -SETTIMER = 0x0001 -STARTTIMER = 0x0002 -TX_COUNTEVT = 0x0004 -TX_ANNOUNCE = 0x0008 -TX_CP_CAN_ID = 0x0010 -RX_FILTER_ID = 0x0020 -RX_CHECK_DLC = 0x0040 -RX_NO_AUTOTIMER = 0x0080 -RX_ANNOUNCE_RESUME = 0x0100 -TX_RESET_MULTI_IDX = 0x0200 -RX_RTR_FRAME = 0x0400 -CAN_FD_FRAME = 0x0800 - -CAN_RAW = 1 -CAN_BCM = 2 - -SOL_CAN_BASE = 100 -SOL_CAN_RAW = SOL_CAN_BASE + CAN_RAW - -CAN_RAW_FILTER = 1 -CAN_RAW_ERR_FILTER = 2 -CAN_RAW_LOOPBACK = 3 +SETTIMER = 0x0001 +STARTTIMER = 0x0002 +TX_COUNTEVT = 0x0004 +TX_ANNOUNCE = 0x0008 +TX_CP_CAN_ID = 0x0010 +RX_FILTER_ID = 0x0020 +RX_CHECK_DLC = 0x0040 +RX_NO_AUTOTIMER = 0x0080 +RX_ANNOUNCE_RESUME = 0x0100 +TX_RESET_MULTI_IDX = 0x0200 +RX_RTR_FRAME = 0x0400 +CAN_FD_FRAME = 0x0800 + +CAN_RAW = 1 +CAN_BCM = 2 + +SOL_CAN_BASE = 100 +SOL_CAN_RAW = SOL_CAN_BASE + CAN_RAW + +CAN_RAW_FILTER = 1 +CAN_RAW_ERR_FILTER = 2 +CAN_RAW_LOOPBACK = 3 CAN_RAW_RECV_OWN_MSGS = 4 -CAN_RAW_FD_FRAMES = 5 +CAN_RAW_FD_FRAMES = 5 -MSK_ARBID = 0x1FFFFFFF -MSK_FLAGS = 0xE0000000 +MSK_ARBID = 0x1FFFFFFF +MSK_FLAGS = 0xE0000000 -PF_CAN = 29 -SOCK_RAW = 3 -SOCK_DGRAM = 2 -AF_CAN = PF_CAN +PF_CAN = 29 +SOCK_RAW = 3 +SOCK_DGRAM = 2 +AF_CAN = PF_CAN -SIOCGIFNAME = 0x8910 -SIOCGIFINDEX = 0x8933 -SIOCGSTAMP = 0x8906 -EXTFLG = 0x0004 +SIOCGIFNAME = 0x8910 +SIOCGIFINDEX = 0x8933 +SIOCGSTAMP = 0x8906 +EXTFLG = 0x0004 -CANFD_BRS = 0x01 -CANFD_ESI = 0x02 +CANFD_BRS = 0x01 +CANFD_ESI = 0x02 CANFD_MTU = 72 -STD_ACCEPTANCE_MASK_ALL_BITS = (2**11 - 1) +STD_ACCEPTANCE_MASK_ALL_BITS = 2 ** 11 - 1 MAX_11_BIT_ID = STD_ACCEPTANCE_MASK_ALL_BITS -EXT_ACCEPTANCE_MASK_ALL_BITS = (2**29 - 1) +EXT_ACCEPTANCE_MASK_ALL_BITS = 2 ** 29 - 1 MAX_29_BIT_ID = EXT_ACCEPTANCE_MASK_ALL_BITS diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 633c87b22..e16ad0c94 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,21 +1,26 @@ -# coding: utf-8 -import logging +""" +The main module of the socketcan interface containing most user-facing classes and methods +along some internal methods. + +At the end of the file the usage of the internal methods is shown. +""" +from typing import Dict, List, Optional, Sequence, Tuple, Type, Union + +import logging import ctypes import ctypes.util -import os import select import socket import struct import time +import threading import errno log = logging.getLogger(__name__) log_tx = log.getChild("tx") log_rx = log.getChild("rx") -log.debug("Loading socketcan native backend") - try: import fcntl except ImportError: @@ -24,53 +29,25 @@ import can from can import Message, BusABC -from can.broadcastmanager import ModifiableCyclicTaskABC, \ - RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC -from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG -from can.interfaces.socketcan.utils import \ - pack_filters, find_available_interfaces, error_code_to_str - - -try: - socket.CAN_BCM -except AttributeError: - HAS_NATIVE_SUPPORT = False -else: - HAS_NATIVE_SUPPORT = True - - -if not HAS_NATIVE_SUPPORT: - def check_status(result, function, arguments): - if result < 0: - raise can.CanError(error_code_to_str(ctypes.get_errno())) - return result - - try: - libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) - libc.bind.errcheck = check_status - libc.connect.errcheck = check_status - libc.sendto.errcheck = check_status - libc.recvfrom.errcheck = check_status - except: - log.warning("libc is unavailable") - libc = None - - def get_addr(sock, channel): - """Get sockaddr for a channel.""" - if channel: - data = struct.pack("16si", channel.encode(), 0) - res = fcntl.ioctl(sock, SIOCGIFINDEX, data) - idx, = struct.unpack("16xi", res) - else: - # All channels - idx = 0 - return struct.pack("HiLL", AF_CAN, idx, 0, 0) - +from can.broadcastmanager import ( + ModifiableCyclicTaskABC, + RestartableCyclicTaskABC, + LimitedDurationCyclicSendTaskABC, +) +from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG +from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces # Setup BCM struct -def bcm_header_factory(fields, alignment=8): +def bcm_header_factory( + fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], + alignment: int = 8, +): curr_stride = 0 - results = [] + results: List[ + Tuple[ + str, Union[Type[ctypes.c_uint8], Type[ctypes.c_uint32], Type[ctypes.c_long]] + ] + ] = [] pad_index = 0 for field in fields: field_alignment = ctypes.alignment(field[1]) @@ -102,6 +79,7 @@ def bcm_header_factory(fields, alignment=8): return type("BcmMsgHead", (ctypes.Structure,), {"_fields_": results}) + # The fields definition is taken from the C struct definitions in # # @@ -153,7 +131,7 @@ def bcm_header_factory(fields, alignment=8): CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB2x") -def build_can_frame(msg): +def build_can_frame(msg: Message) -> bytes: """ CAN frame packing/unpacking (see 'struct can_frame' in ) /** * struct can_frame - basic CAN frame structure @@ -185,28 +163,28 @@ def build_can_frame(msg): __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8))); }; """ - can_id = _add_flags_to_can_id(msg) + can_id = _compose_arbitration_id(msg) flags = 0 if msg.bitrate_switch: flags |= CANFD_BRS if msg.error_state_indicator: flags |= CANFD_ESI max_len = 64 if msg.is_fd else 8 - data = bytes(msg.data).ljust(max_len, b'\x00') + data = bytes(msg.data).ljust(max_len, b"\x00") return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data def build_bcm_header( - opcode, - flags, - count, - ival1_seconds, - ival1_usec, - ival2_seconds, - ival2_usec, - can_id, - nframes, -): + opcode: int, + flags: int, + count: int, + ival1_seconds: int, + ival1_usec: int, + ival2_seconds: int, + ival2_usec: int, + can_id: int, + nframes: int, +) -> bytes: result = BcmMsgHead( opcode=opcode, flags=flags, @@ -221,13 +199,19 @@ def build_bcm_header( return ctypes.string_at(ctypes.addressof(result), ctypes.sizeof(result)) -def build_bcm_tx_delete_header(can_id, flags): +def build_bcm_tx_delete_header(can_id: int, flags: int) -> bytes: opcode = CAN_BCM_TX_DELETE return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1) -def build_bcm_transmit_header(can_id, count, initial_period, subsequent_period, - msg_flags): +def build_bcm_transmit_header( + can_id: int, + count: int, + initial_period: float, + subsequent_period: float, + msg_flags: int, + nframes: int = 1, +) -> bytes: opcode = CAN_BCM_TX_SETUP flags = msg_flags | SETTIMER | STARTTIMER @@ -236,7 +220,7 @@ def build_bcm_transmit_header(can_id, count, initial_period, subsequent_period, # Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires flags |= TX_COUNTEVT - def split_time(value): + def split_time(value: float) -> Tuple[int, int]: """Given seconds as a float, return whole seconds and microseconds""" seconds = int(value) microseconds = int(1e6 * (value - seconds)) @@ -244,45 +228,54 @@ def split_time(value): ival1_seconds, ival1_usec = split_time(initial_period) ival2_seconds, ival2_usec = split_time(subsequent_period) - nframes = 1 - return build_bcm_header(opcode, flags, count, ival1_seconds, ival1_usec, ival2_seconds, ival2_usec, can_id, nframes) + return build_bcm_header( + opcode, + flags, + count, + ival1_seconds, + ival1_usec, + ival2_seconds, + ival2_usec, + can_id, + nframes, + ) -def build_bcm_update_header(can_id, msg_flags): - return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, 1) +def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> bytes: + return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes) -def dissect_can_frame(frame): +def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) if len(frame) != CANFD_MTU: # Flags not valid in non-FD frames flags = 0 - return can_id, can_dlc, flags, frame[8:8+can_dlc] + return can_id, can_dlc, flags, frame[8 : 8 + can_dlc] -def create_bcm_socket(channel): +def create_bcm_socket(channel: str) -> socket.socket: """create a broadcast manager socket and connect to the given interface""" s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM) - if HAS_NATIVE_SUPPORT: - s.connect((channel,)) - else: - addr = get_addr(s, channel) - libc.connect(s.fileno(), addr, len(addr)) + s.connect((channel,)) return s -def send_bcm(bcm_socket, data): +def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: """ Send raw frame to a BCM socket and handle errors. """ try: return bcm_socket.send(data) except OSError as e: - base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format(e.errno, e.strerror) + base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format( + e.errno, e.strerror + ) if e.errno == errno.EINVAL: - raise can.CanError(base + "You are probably referring to a non-existing frame.") + raise can.CanError( + base + "You are probably referring to a non-existing frame." + ) elif e.errno == errno.ENETDOWN: raise can.CanError(base + "The CAN interface appears to be down.") @@ -294,7 +287,7 @@ def send_bcm(bcm_socket, data): raise e -def _add_flags_to_can_id(message): +def _compose_arbitration_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: log.debug("sending an extended id type message") @@ -305,79 +298,150 @@ def _add_flags_to_can_id(message): if message.is_error_frame: log.debug("sending error frame") can_id |= CAN_ERR_FLAG - return can_id -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - ModifiableCyclicTaskABC, RestartableCyclicTaskABC): +class CyclicSendTask( + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC +): """ - A socketcan cyclic send task supports: + A SocketCAN cyclic send task supports: - setting of a task duration - modifying the data - stopping then subsequent restarting of the task - """ - def __init__(self, bcm_socket, message, period, duration=None): + def __init__( + self, + bcm_socket: socket.socket, + task_id: int, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ): + """Construct and :meth:`~start` a task. + + :param bcm_socket: An open BCM socket on the desired CAN channel. + :param task_id: + The identifier used to uniquely reference particular cyclic send task + within Linux BCM. + :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 send the messages for. """ - :param bcm_socket: An open bcm socket on the desired CAN channel. - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. - :param float duration: Approximate duration in seconds to send the message. - """ - super(CyclicSendTask, self).__init__(message, period, duration) - self.bcm_socket = bcm_socket - self.duration = duration - self._tx_setup(message) - self.message = message + # The following are assigned by LimitedDurationCyclicSendTaskABC: + # - self.messages + # - self.period + # - self.duration + super().__init__(messages, period, duration) - def _tx_setup(self, message): + self.bcm_socket = bcm_socket + self.task_id = task_id + self._tx_setup(self.messages) + def _tx_setup(self, messages: Sequence[Message]) -> None: # Create a low level packed frame to pass to the kernel - self.can_id_with_flags = _add_flags_to_can_id(message) - self.flags = CAN_FD_FRAME if message.is_fd else 0 + body = bytearray() + self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 + if self.duration: count = int(self.duration / self.period) ival1 = self.period - ival2 = 0 + ival2 = 0.0 else: count = 0 - ival1 = 0 + ival1 = 0.0 ival2 = self.period - header = build_bcm_transmit_header(self.can_id_with_flags, count, ival1, - ival2, self.flags) - frame = build_can_frame(message) + + self._check_bcm_task() + + header = build_bcm_transmit_header( + self.task_id, count, ival1, ival2, self.flags, nframes=len(messages) + ) + for message in messages: + body += build_can_frame(message) log.debug("Sending BCM command") - send_bcm(self.bcm_socket, header + frame) + send_bcm(self.bcm_socket, header + body) + + def _check_bcm_task(self): + # Do a TX_READ on a task ID, and check if we get EINVAL. If so, + # then we are referring to a CAN message with the existing ID + check_header = build_bcm_header( + opcode=CAN_BCM_TX_READ, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=self.task_id, + nframes=0, + ) + try: + self.bcm_socket.send(check_header) + except OSError as e: + if e.errno != errno.EINVAL: + raise e + else: + raise ValueError( + "A periodic task for Task ID {} is already in progress by SocketCAN Linux layer".format( + self.task_id + ) + ) - def stop(self): - """Send a TX_DELETE message to cancel this task. + def stop(self) -> None: + """Stop a task by sending TX_DELETE message to Linux kernel. This will delete the entry for the transmission of the CAN-message - with the specified can_id CAN identifier. The message length for the command - TX_DELETE is {[bcm_msg_head]} (only the header). + with the specified :attr:`~task_id` identifier. The message length + for the command TX_DELETE is {[bcm_msg_head]} (only the header). """ log.debug("Stopping periodic task") - stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) + stopframe = build_bcm_tx_delete_header(self.task_id, self.flags) send_bcm(self.bcm_socket, stopframe) - def modify_data(self, message): - """Update the contents of this periodically sent message. + def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: + """Update the contents of the periodically sent CAN messages by + sending TX_SETUP message to Linux kernel. - Note the Message must have the same :attr:`~can.Message.arbitration_id` - like the first message. + The number of new cyclic messages to be sent must be equal to the + original number of messages originally specified for this task. + + .. note:: The messages must all have the same + :attr:`~can.Message.arbitration_id` like the first message. + + :param messages: + The messages with the new :attr:`can.Message.data`. """ - assert message.arbitration_id == self.can_id, "You cannot modify the can identifier" - self.message = message - header = build_bcm_update_header(self.can_id_with_flags, self.flags) - frame = build_can_frame(message) - send_bcm(self.bcm_socket, header + frame) + messages = self._check_and_convert_messages(messages) + self._check_modified_messages(messages) + + self.messages = messages - def start(self): - self._tx_setup(self.message) + body = bytearray() + header = build_bcm_update_header( + can_id=self.task_id, msg_flags=self.flags, nframes=len(messages) + ) + for message in messages: + body += build_can_frame(message) + log.debug("Sending BCM command") + send_bcm(self.bcm_socket, header + body) + + def start(self) -> None: + """Start a periodic task by sending TX_SETUP message to Linux kernel. + + It verifies presence of the particular BCM task through sending TX_READ + message to Linux kernel prior to scheduling. + + :raises ValueError: + If the task referenced by :attr:`~task_id` is already running. + """ + self._tx_setup(self.messages) class MultiRateCyclicSendTask(CyclicSendTask): @@ -386,59 +450,69 @@ class MultiRateCyclicSendTask(CyclicSendTask): """ - def __init__(self, channel, message, count, initial_period, subsequent_period): - super(MultiRateCyclicSendTask, self).__init__(channel, message, subsequent_period) + def __init__( + self, + channel: socket.socket, + task_id: int, + messages: Sequence[Message], + count: int, + initial_period: float, + subsequent_period: float, + ): + super().__init__(channel, task_id, messages, subsequent_period) # Create a low level packed frame to pass to the kernel - frame = build_can_frame(message) header = build_bcm_transmit_header( - self.can_id_with_flags, + self.task_id, count, initial_period, subsequent_period, - self.flags) + self.flags, + nframes=len(messages), + ) + + body = bytearray() + for message in messages: + body += build_can_frame(message) log.info("Sending BCM TX_SETUP command") - send_bcm(self.bcm_socket, header + frame) + send_bcm(self.bcm_socket, header + body) -def create_socket(): +def create_socket() -> socket.socket: """Creates a raw CAN socket. The socket will be returned unbound to any interface. """ sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW) - log.info('Created a socket') + log.info("Created a socket") return sock -def bind_socket(sock, channel='can0'): +def bind_socket(sock: socket.socket, channel: str = "can0") -> None: """ Binds the given socket to the given interface. - :param socket.socket sock: + :param sock: The socket to be bound :raises OSError: If the specified interface isn't found. """ - log.debug('Binding socket to channel=%s', channel) - if HAS_NATIVE_SUPPORT: - sock.bind((channel,)) - else: - # For Python 2.7 - addr = get_addr(sock, channel) - libc.bind(sock.fileno(), addr, len(addr)) - log.debug('Bound socket.') + log.debug("Binding socket to channel=%s", channel) + sock.bind((channel,)) + log.debug("Bound socket.") -def capture_message(sock, get_channel=False): +def capture_message( + sock: socket.socket, get_channel: bool = False +) -> Optional[Message]: """ Captures a message from given socket. - :param socket.socket sock: + :param sock: The socket to read a message from. - :param bool get_channel: + :param get_channel: Find out which channel the message comes from. :return: The received message, or None on failure. @@ -446,34 +520,20 @@ def capture_message(sock, get_channel=False): # Fetching the Arb ID, DLC and Data try: if get_channel: - if HAS_NATIVE_SUPPORT: - cf, addr = sock.recvfrom(CANFD_MTU) - channel = addr[0] if isinstance(addr, tuple) else addr - else: - data = ctypes.create_string_buffer(CANFD_MTU) - addr = ctypes.create_string_buffer(32) - addrlen = ctypes.c_int(len(addr)) - received = libc.recvfrom(sock.fileno(), data, len(data), 0, - addr, ctypes.byref(addrlen)) - cf = data.raw[:received] - # Figure out the channel name - family, ifindex = struct.unpack_from("Hi", addr.raw) - assert family == AF_CAN - data = struct.pack("16xi", ifindex) - res = fcntl.ioctl(sock, SIOCGIFNAME, data) - channel = ctypes.create_string_buffer(res).value.decode() + cf, _, msg_flags, addr = sock.recvmsg(CANFD_MTU) + channel = addr[0] if isinstance(addr, tuple) else addr else: - cf = sock.recv(CANFD_MTU) + cf, _, msg_flags, _ = sock.recvmsg(CANFD_MTU) channel = None except socket.error as exc: raise can.CanError("Error receiving: %s" % exc) can_id, can_dlc, flags, data = dissect_can_frame(cf) - #log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) + # log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) # Fetching the timestamp binary_structure = "@LL" - res = fcntl.ioctl(sock, SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) + res = fcntl.ioctl(sock.fileno(), SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) seconds, microseconds = struct.unpack(binary_structure, res) timestamp = seconds + microseconds * 1e-6 @@ -490,47 +550,61 @@ def capture_message(sock, get_channel=False): bitrate_switch = bool(flags & CANFD_BRS) error_state_indicator = bool(flags & CANFD_ESI) + # Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host. + is_rx = not bool(msg_flags & socket.MSG_DONTROUTE) + if is_extended_frame_format: - #log.debug("CAN: Extended") + # log.debug("CAN: Extended") # TODO does this depend on SFF or EFF? arbitration_id = can_id & 0x1FFFFFFF else: - #log.debug("CAN: Standard") + # log.debug("CAN: Standard") arbitration_id = can_id & 0x000007FF - msg = Message(timestamp=timestamp, - channel=channel, - arbitration_id=arbitration_id, - is_extended_id=is_extended_frame_format, - is_remote_frame=is_remote_transmission_request, - is_error_frame=is_error_frame, - is_fd=is_fd, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, - dlc=can_dlc, - data=data) + msg = Message( + timestamp=timestamp, + channel=channel, + arbitration_id=arbitration_id, + is_extended_id=is_extended_frame_format, + is_remote_frame=is_remote_transmission_request, + is_error_frame=is_error_frame, + is_fd=is_fd, + is_rx=is_rx, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + dlc=can_dlc, + data=data, + ) - #log_rx.debug('Received: %s', msg) + # log_rx.debug('Received: %s', msg) return msg class SocketcanBus(BusABC): - """ - Implements :meth:`can.BusABC._detect_available_configs`. + """ A SocketCAN interface to CAN. + + It implements :meth:`can.BusABC._detect_available_configs` to search for + available interfaces. """ - def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): + def __init__( + self, + channel: str = "", + receive_own_messages: bool = False, + fd: bool = False, + **kwargs, + ) -> None: """ - :param str channel: + :param channel: The can interface name with which to create this bus. An example channel would be 'vcan0' or 'can0'. An empty string '' will receive messages from all channels. In that case any sent messages must be explicitly addressed to a channel using :attr:`can.Message.channel`. - :param bool receive_own_messages: + :param receive_own_messages: If transmitted messages should also be received by this bus. - :param bool fd: + :param fd: If CAN-FD frames should be supported. :param list can_filters: See :meth:`can.BusABC.set_filters`. @@ -538,32 +612,31 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): self.socket = create_socket() self.channel = channel self.channel_info = "socketcan channel '%s'" % channel - self._bcm_sockets = {} + self._bcm_sockets: Dict[str, socket.socket] = {} + self._is_filtered = False + self._task_id = 0 + self._task_id_guard = threading.Lock() # set the receive_own_messages parameter try: - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_RECV_OWN_MSGS, - 1 if receive_own_messages else 0) + self.socket.setsockopt( + SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0 + ) except socket.error as e: log.error("Could not receive own messages (%s)", e) if fd: # TODO handle errors - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_FD_FRAMES, - 1) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) # Enable error frames - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_ERR_FILTER, - 0x1FFFFFFF) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) bind_socket(self.socket, channel) - kwargs.update({'receive_own_messages': receive_own_messages, 'fd': fd}) - super(SocketcanBus, self).__init__(channel=channel, **kwargs) + kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) + super().__init__(channel=channel, **kwargs) - def shutdown(self): + def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" self.stop_all_periodic_tasks() for channel in self._bcm_sockets: @@ -573,7 +646,9 @@ def shutdown(self): log.debug("Closing raw can socket") self.socket.close() - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) try: @@ -584,10 +659,10 @@ def _recv_internal(self, timeout): # something bad happened (e.g. the interface went down) raise can.CanError("Failed to receive: %s" % exc) - if ready_receive_sockets: # not empty or True + if ready_receive_sockets: # not empty or True get_channel = self.channel == "" msg = capture_message(self.socket, get_channel) - if not msg.channel and self.channel: + if msg and not msg.channel and self.channel: # Default to our own channel msg.channel = self.channel return msg, self._is_filtered @@ -595,11 +670,11 @@ def _recv_internal(self, timeout): # socket wasn't readable or timeout occurred return None, self._is_filtered - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """Transmit a message to the CAN bus. - :param can.Message msg: A message object. - :param float timeout: + :param msg: A message object. + :param timeout: Wait up to this many seconds for the transmit queue to be ready. If not given, the call may fail immediately. @@ -623,7 +698,8 @@ def send(self, msg, timeout=None): if not ready: # Timeout break - sent = self._send_once(data, msg.channel) + channel = str(msg.channel) if msg.channel else None + sent = self._send_once(data, channel) if sent == len(data): return # Not all data were sent, try again with remaining data @@ -632,109 +708,126 @@ def send(self, msg, timeout=None): raise can.CanError("Transmit buffer full") - def _send_once(self, data, channel=None): + def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: try: if self.channel == "" and channel: # Message must be addressed to a specific channel - if HAS_NATIVE_SUPPORT: - sent = self.socket.sendto(data, (channel, )) - else: - addr = get_addr(self.socket, channel) - sent = libc.sendto(self.socket.fileno(), - data, len(data), 0, - addr, len(addr)) + sent = self.socket.sendto(data, (channel,)) else: sent = self.socket.send(data) except socket.error as exc: raise can.CanError("Failed to transmit: %s" % exc) return sent - def _send_periodic_internal(self, msg, period, duration=None): - """Start sending a message at a given period on this bus. - - The kernel's broadcast manager will be used. - - :param can.Message msg: - Message to transmit - :param float period: - Period in seconds between each message - :param float duration: - The duration to keep sending this message at given rate. If + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ) -> CyclicSendTask: + """Start sending messages at a given period on this bus. + + The Linux kernel's Broadcast Manager SocketCAN API is used to schedule + periodic sending of CAN messages. The wrapping 32-bit counter (see + :meth:`~_get_next_task_id()`) designated to distinguish different + :class:`CyclicSendTask` within BCM provides flexibility to schedule + CAN messages sending with the same CAN ID, but different CAN data. + + :param messages: + The message(s) 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 task identifier passed to :class:`CyclicSendTask` can't be used + to schedule new task in Linux BCM. + :return: - A started task instance. This can be used to modify the data, + A :class:`CyclicSendTask` task instance. This can be used to modify the data, pause/resume the transmission and to stop the transmission. - :rtype: can.interfaces.socketcan.CyclicSendTask .. note:: - Note the duration before the message stops being sent may not + 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. - """ - bcm_socket = self._get_bcm_socket(msg.channel or self.channel) - task = CyclicSendTask(bcm_socket, msg, period, duration) + msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) + + msgs_channel = str(msgs[0].channel) if msgs[0].channel else None + bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) + task_id = self._get_next_task_id() + task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) return task - def _get_bcm_socket(self, channel): + def _get_next_task_id(self) -> int: + with self._task_id_guard: + self._task_id = (self._task_id + 1) % (2 ** 32 - 1) + return self._task_id + + def _get_bcm_socket(self, channel: str) -> socket.socket: if channel not in self._bcm_sockets: self._bcm_sockets[channel] = create_bcm_socket(self.channel) return self._bcm_sockets[channel] - def _apply_filters(self, filters): + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: try: - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_FILTER, - pack_filters(filters)) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) except socket.error as err: # fall back to "software filtering" (= not in kernel) self._is_filtered = False # TODO Is this serious enough to raise a CanError exception? - log.error('Setting filters failed; falling back to software filtering (not in kernel): %s', err) + log.error( + "Setting filters failed; falling back to software filtering (not in kernel): %s", + err, + ) else: self._is_filtered = True - def fileno(self): + def fileno(self) -> int: return self.socket.fileno() @staticmethod - def _detect_available_configs(): - return [{'interface': 'socketcan', 'channel': channel} - for channel in find_available_interfaces()] + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + return [ + {"interface": "socketcan", "channel": channel} + for channel in find_available_interfaces() + ] if __name__ == "__main__": - # TODO move below to examples? - - # Create two sockets on vcan0 to test send and receive + # This example demonstrates how to use the internal methods of this module. + # It creates two sockets on vcan0 to test sending and receiving. # # If you want to try it out you can do the following (possibly using sudo): # # modprobe vcan # ip link add dev vcan0 type vcan - # ifconfig vcan0 up + # ip link set vcan0 up # log.setLevel(logging.DEBUG) - def receiver(event): + def receiver(event: threading.Event) -> None: receiver_socket = create_socket() - bind_socket(receiver_socket, 'vcan0') + bind_socket(receiver_socket, "vcan0") print("Receiver is waiting for a message...") event.set() - print("Receiver got: ", capture_message(receiver_socket)) + print(f"Receiver got: {capture_message(receiver_socket)}") - def sender(event): + def sender(event: threading.Event) -> None: event.wait() sender_socket = create_socket() - bind_socket(sender_socket, 'vcan0') - msg = Message(arbitration_id=0x01, data=b'\x01\x02\x03') + bind_socket(sender_socket, "vcan0") + msg = Message(arbitration_id=0x01, data=b"\x01\x02\x03") sender_socket.send(build_can_frame(msg)) print("Sender sent a message.") import threading + e = threading.Event() threading.Thread(target=receiver, args=(e,)).start() threading.Thread(target=sender, args=(e,)).start() diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 44d356920..ee89b142e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -1,14 +1,14 @@ -# coding: utf-8 - """ Defines common socketcan functions. """ +from typing import cast, Iterable, Optional +import can.typechecking as typechecking + import logging import os import errno import struct -import sys import subprocess import re @@ -16,23 +16,22 @@ log = logging.getLogger(__name__) -def pack_filters(can_filters=None): + +def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes: if can_filters is None: # Pass all messages - can_filters = [{ - 'can_id': 0, - 'can_mask': 0 - }] + can_filters = [{"can_id": 0, "can_mask": 0}] can_filter_fmt = "={}I".format(2 * len(can_filters)) filter_data = [] for can_filter in can_filters: - can_id = can_filter['can_id'] - can_mask = can_filter['can_mask'] - if 'extended' in can_filter: + can_id = can_filter["can_id"] + can_mask = can_filter["can_mask"] + if "extended" in can_filter: + can_filter = cast(typechecking.CanFilterExtended, can_filter) # Match on either 11-bit OR 29-bit messages instead of both can_mask |= CAN_EFF_FLAG - if can_filter['extended']: + if can_filter["extended"]: can_id |= CAN_EFF_FLAG filter_data.append(can_id) filter_data.append(can_mask) @@ -42,7 +41,8 @@ def pack_filters(can_filters=None): _PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") -def find_available_interfaces(): + +def find_available_interfaces() -> Iterable[str]: """Returns the names of all open can/vcan interfaces using the ``ip link list`` command. If the lookup fails, an error is logged to the console and an empty list is returned. @@ -55,19 +55,20 @@ def find_available_interfaces(): command = ["ip", "-o", "link", "list", "up"] output = subprocess.check_output(command, universal_newlines=True) - except Exception as e: # subprocess.CalledProcessError was too specific + except Exception as e: # subprocess.CalledProcessError was too specific log.error("failed to fetch opened can devices: %s", e) return [] else: - #log.debug("find_available_interfaces(): output=\n%s", output) + # log.debug("find_available_interfaces(): output=\n%s", output) # output contains some lines like "1: vcan42: ..." # extract the "vcan42" of each line interface_names = [line.split(": ", 3)[1] for line in output.splitlines()] log.debug("find_available_interfaces(): detected: %s", interface_names) return filter(_PATTERN_CAN_INTERFACE.match, interface_names) -def error_code_to_str(code): + +def error_code_to_str(code: int) -> str: """ Converts a given error code (errno) to a useful and human readable string. diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index ed8eb8eb7..4ecd39b4c 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py index 64122dac9..96952c17e 100644 --- a/can/interfaces/systec/constants.py +++ b/can/interfaces/systec/constants.py @@ -1,5 +1,3 @@ -# coding: utf-8 - from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD #: Maximum number of modules that are supported. @@ -364,6 +362,7 @@ class OutputControl(BYTE): These values are only important for GW-001 and GW-002. They does not have an effect on systec USB-CANmoduls. """ + #: default OCR value for the standard USB-CANmodul GW-001/GW-002 OCR_DEFAULT = 0x1A #: OCR value for RS485 interface and galvanic isolation @@ -433,42 +432,45 @@ class ResetFlags(DWORD): RESET_FIRMWARE = 0xFFFFFFFF #: no reset of all message counters - RESET_NO_COUNTER_ALL = (RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER) + RESET_NO_COUNTER_ALL = RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER #: no reset of transmit message buffers at communication level (firmware, kernel and library) - RESET_NO_TXBUFFER_COMM = (RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW) + RESET_NO_TXBUFFER_COMM = RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW #: no reset of receive message buffers at communication level (firmware, kernel and library) - RESET_NO_RXBUFFER_COMM = (RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW) + RESET_NO_RXBUFFER_COMM = ( + RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW + ) #: no reset of all transmit message buffers - RESET_NO_TXBUFFER_ALL = (RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM) + RESET_NO_TXBUFFER_ALL = RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM #: no reset of all receive message buffers - RESET_NO_RXBUFFER_ALL = (RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM) + RESET_NO_RXBUFFER_ALL = RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM #: no reset of all message buffers at communication level (firmware, kernel and library) - RESET_NO_BUFFER_COMM = (RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM) + RESET_NO_BUFFER_COMM = RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM #: no reset of all message buffers - RESET_NO_BUFFER_ALL = (RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL) + RESET_NO_BUFFER_ALL = RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL #: reset of the CAN status only - RESET_ONLY_STATUS = (0xFFFF & ~RESET_NO_STATUS) + RESET_ONLY_STATUS = 0xFFFF & ~RESET_NO_STATUS #: reset of the CAN controller only - RESET_ONLY_CANCTRL = (0xFFFF & ~RESET_NO_CANCTRL) + RESET_ONLY_CANCTRL = 0xFFFF & ~RESET_NO_CANCTRL #: reset of the transmit buffer in firmware only - RESET_ONLY_TXBUFFER_FW = (0xFFFF & ~RESET_NO_TXBUFFER_FW) + RESET_ONLY_TXBUFFER_FW = 0xFFFF & ~RESET_NO_TXBUFFER_FW #: reset of the receive buffer in firmware only - RESET_ONLY_RXBUFFER_FW = (0xFFFF & ~RESET_NO_RXBUFFER_FW) + RESET_ONLY_RXBUFFER_FW = 0xFFFF & ~RESET_NO_RXBUFFER_FW #: reset of the specified channel of the receive buffer only - RESET_ONLY_RXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_RXBUFFER_CH) + RESET_ONLY_RXCHANNEL_BUFF = 0xFFFF & ~RESET_NO_RXBUFFER_CH #: reset of the specified channel of the transmit buffer only - RESET_ONLY_TXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_TXBUFFER_CH) + RESET_ONLY_TXCHANNEL_BUFF = 0xFFFF & ~RESET_NO_TXBUFFER_CH #: reset of the receive buffer and receive message counter only - RESET_ONLY_RX_BUFF = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER)) + RESET_ONLY_RX_BUFF = 0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER) #: reset of the receive buffer and receive message counter (for GW-002) only - RESET_ONLY_RX_BUFF_GW002 = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | - RESET_NO_TXBUFFER_FW)) + RESET_ONLY_RX_BUFF_GW002 = 0xFFFF & ~( + RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | RESET_NO_TXBUFFER_FW + ) #: reset of the transmit buffer and transmit message counter only - RESET_ONLY_TX_BUFF = (0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER)) + RESET_ONLY_TX_BUFF = 0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER) #: reset of all buffers and all message counters only - RESET_ONLY_ALL_BUFF = (RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF) + RESET_ONLY_ALL_BUFF = RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF #: reset of all message counters only - RESET_ONLY_ALL_COUNTER = (0xFFFF & ~RESET_NO_COUNTER_ALL) + RESET_ONLY_ALL_COUNTER = 0xFFFF & ~RESET_NO_COUNTER_ALL PRODCODE_PID_TWO_CHA = 0x1 @@ -480,7 +482,7 @@ class ResetFlags(DWORD): PRODCODE_MASK_DID = 0xFFFF0000 PRODCODE_MASK_PID = 0xFFFF -PRODCODE_MASK_PIDG3 = (PRODCODE_MASK_PID & 0xFFFFFFBF) +PRODCODE_MASK_PIDG3 = PRODCODE_MASK_PID & 0xFFFFFFBF class ProductCode(WORD): @@ -597,11 +599,11 @@ class PendingFlags(BYTE): #: number of pending CAN messages in transmit buffer of firmware PENDING_FLAG_TX_FW = 0x40 #: number of pending CAN messages in all receive buffers - PENDING_FLAG_RX_ALL = (PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW) + PENDING_FLAG_RX_ALL = PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW #: number of pending CAN messages in all transmit buffers - PENDING_FLAG_TX_ALL = (PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW) + PENDING_FLAG_TX_ALL = PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW #: number of pending CAN messages in all buffers - PENDING_FLAG_ALL = (PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL) + PENDING_FLAG_ALL = PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL class Mode(BYTE): diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index d3525cd88..72ec92cfa 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,33 +1,34 @@ -# coding: utf-8 - from .constants import ReturnCode from can import CanError class UcanException(CanError): """ Base class for USB can errors. """ + def __init__(self, result, func, arguments): self.result = result.value self.func = func self.arguments = arguments - self.return_msgs = NotImplemented + self.return_msgs = {} + super().__init__() def __str__(self): - return "Function %s returned %d: %s" % \ - (self.func.__name__, self.result, self.return_msgs.get(self.result, "unknown")) + message = self.return_msgs.get(self.result, "unknown") + return f"Function {self.func.__name__} returned {self.result}: {message}" class UcanError(UcanException): """ Exception class for errors from USB-CAN-library. """ + def __init__(self, result, func, arguments): - super(UcanError, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", ReturnCode.ERR_HWINUSE: "the specified module is already in use", ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " - "(or used by an other application)", + "(or used by an other application)", ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", ReturnCode.ERR_BUSY: "instruction can not be processed at this time", @@ -46,8 +47,9 @@ def __init__(self, result, func, arguments): class UcanCmdError(UcanException): """ Exception class for errors from firmware in USB-CANmodul.""" + def __init__(self, result, func, arguments): - super(UcanCmdError, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", @@ -57,20 +59,21 @@ def __init__(self, result, func, arguments): ReturnCode.ERRCMD_RESERVED2: "reserved", ReturnCode.ERRCMD_RESERVED3: "reserved", ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " - "USB-CANmoduls", + "USB-CANmoduls", ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " - "cyclic CAN messages is already running", + "cyclic CAN messages is already running", } class UcanWarning(UcanException): """ Exception class for warnings, the function has been executed anyway. """ + def __init__(self, result, func, arguments): - super(UcanWarning, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.WARN_NODATA: "no CAN messages received", ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", @@ -78,12 +81,12 @@ def __init__(self, result, func, arguments): ReturnCode.WARN_RESERVED1: "reserved", ReturnCode.WARN_RESERVED2: "reserved", ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " - "was successfully stored in buffer of the ibrary)", + "was successfully stored in buffer of the ibrary)", ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " - "was successfully read)", + "was successfully read)", ReturnCode.WARN_FW_TXMSGLOST: "reserved", ReturnCode.WARN_NULL_PTR: "pointer is NULL", ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " - "USB-CAN-library", - ReturnCode.WARN_BUSY: "reserved" + "USB-CAN-library", + ReturnCode.WARN_BUSY: "reserved", } diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index a521e044f..fbebdcdbd 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -1,8 +1,13 @@ -# coding: utf-8 - from ctypes import Structure, POINTER, sizeof -from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD, c_long as BOOL, c_void_p as LPVOID +from ctypes import ( + c_ubyte as BYTE, + c_ushort as WORD, + c_ulong as DWORD, + c_long as BOOL, + c_void_p as LPVOID, +) import os + # Workaround for Unix based platforms to be able to load structures for testing, etc... if os.name == "nt": from ctypes import WINFUNCTYPE as FUNCTYPE @@ -26,38 +31,52 @@ class CanMsg(Structure): :meth:`UcanServer.read_cyclic_can_msg` """ + _pack_ = 1 _fields_ = [ ("m_dwID", DWORD), # CAN Identifier ("m_bFF", BYTE), # CAN Frame Format (see enum :class:`MsgFrameFormat`) ("m_bDLC", BYTE), # CAN Data Length Code ("m_bData", BYTE * 8), # CAN Data (array of 8 bytes) - ("m_dwTime", DWORD,) # Receive time stamp in ms (for transmit messages no meaning) + ( + "m_dwTime", + DWORD, + ), # Receive time stamp in ms (for transmit messages no meaning) ] - def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=[]): - super(CanMsg, self).__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): + data = [] if data is None else data + super().__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) def __eq__(self, other): if not isinstance(other, CanMsg): return False - return self.id == other.id and self.frame_format == other.frame_format and self.data == other.data + return ( + self.id == other.id + and self.frame_format == other.frame_format + and self.data == other.data + ) @property - def id(self): return self.m_dwID + def id(self): + return self.m_dwID @id.setter - def id(self, id): self.m_dwID = id + def id(self, id): + self.m_dwID = id @property - def frame_format(self): return self.m_bFF + def frame_format(self): + return self.m_bFF @frame_format.setter - def frame_format(self, frame_format): self.m_bFF = frame_format + def frame_format(self, frame_format): + self.m_bFF = frame_format @property - def data(self): return self.m_bData[:self.m_bDLC] + def data(self): + return self.m_bData[: self.m_bDLC] @data.setter def data(self, data): @@ -65,7 +84,8 @@ def data(self, data): self.m_bData((BYTE * 8)(*data)) @property - def time(self): return self.m_dwTime + def time(self): + return self.m_dwTime class Status(Structure): @@ -79,6 +99,7 @@ class Status(Structure): :meth:`UcanServer.get_can_status_message` """ + _pack_ = 1 _fields_ = [ ("m_wCanStatus", WORD), # CAN error status (see enum :class:`CanStatus`) @@ -89,13 +110,17 @@ def __eq__(self, other): if not isinstance(other, Status): return False - return self.can_status == other.can_status and self.usb_status == other.usb_status + return ( + self.can_status == other.can_status and self.usb_status == other.usb_status + ) @property - def can_status(self): return self.m_wCanStatus + def can_status(self): + return self.m_wCanStatus @property - def usb_status(self): return self.m_wUsbStatus + def usb_status(self): + return self.m_wUsbStatus class InitCanParam(Structure): @@ -104,69 +129,114 @@ class InitCanParam(Structure): .. note:: This structure is only used internally. """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure (only used internally) - ("m_bMode", BYTE), # selects the mode of CAN controller (see enum :class:`Mode`) + ( + "m_bMode", + BYTE, + ), # selects the mode of CAN controller (see enum :class:`Mode`) # Baudrate Registers for GW-001 or GW-002 ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) - ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) - ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwAMR", + DWORD, + ), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwACR", + DWORD, + ), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls - # (see enum :class:`BaudrateEx`) - ("m_wNrOfRxBufferEntries", WORD), # number of receive buffer entries (default is 4096) - ("m_wNrOfTxBufferEntries", WORD), # number of transmit buffer entries (default is 4096) + # (see enum :class:`BaudrateEx`) + ( + "m_wNrOfRxBufferEntries", + WORD, + ), # number of receive buffer entries (default is 4096) + ( + "m_wNrOfTxBufferEntries", + WORD, + ), # number of transmit buffer entries (default is 4096) ] - def __init__(self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries): - super(InitCanParam, self).__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, - baudrate, rx_buffer_entries, tx_buffer_entries) + def __init__( + self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries + ): + super().__init__( + sizeof(InitCanParam), + mode, + BTR >> 8, + BTR, + OCR, + AMR, + ACR, + baudrate, + rx_buffer_entries, + tx_buffer_entries, + ) def __eq__(self, other): if not isinstance(other, InitCanParam): return False - return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ - self.baudrate == other.baudrate and self.rx_buffer_entries == other.rx_buffer_entries and \ - self.tx_buffer_entries == other.tx_buffer_entries + return ( + self.mode == other.mode + and self.BTR == other.BTR + and self.OCR == other.OCR + and self.baudrate == other.baudrate + and self.rx_buffer_entries == other.rx_buffer_entries + and self.tx_buffer_entries == other.tx_buffer_entries + ) @property - def mode(self): return self.m_bMode + def mode(self): + return self.m_bMode @mode.setter - def mode(self, mode): self.m_bMode = mode + def mode(self, mode): + self.m_bMode = mode @property - def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + def BTR(self): + return self.m_bBTR0 << 8 | self.m_bBTR1 @BTR.setter - def BTR(self, BTR): self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR + def BTR(self, BTR): + self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR @property - def OCR(self): return self.m_bOCR + def OCR(self): + return self.m_bOCR @OCR.setter - def OCR(self, OCR): self.m_bOCR = OCR + def OCR(self, OCR): + self.m_bOCR = OCR @property - def baudrate(self): return self.m_dwBaudrate + def baudrate(self): + return self.m_dwBaudrate @baudrate.setter - def baudrate(self, baudrate): self.m_dwBaudrate = baudrate + def baudrate(self, baudrate): + self.m_dwBaudrate = baudrate @property - def rx_buffer_entries(self): return self.m_wNrOfRxBufferEntries + def rx_buffer_entries(self): + return self.m_wNrOfRxBufferEntries @rx_buffer_entries.setter - def rx_buffer_entries(self, rx_buffer_entries): self.m_wNrOfRxBufferEntries = rx_buffer_entries + def rx_buffer_entries(self, rx_buffer_entries): + self.m_wNrOfRxBufferEntries = rx_buffer_entries @property - def tx_buffer_entries(self): return self.m_wNrOfTxBufferEntries + def tx_buffer_entries(self): + return self.m_wNrOfTxBufferEntries - @rx_buffer_entries.setter - def tx_buffer_entries(self, tx_buffer_entries): self.m_wNrOfTxBufferEntries = tx_buffer_entries + @tx_buffer_entries.setter + def tx_buffer_entries(self, tx_buffer_entries): + self.m_wNrOfTxBufferEntries = tx_buffer_entries class Handle(BYTE): @@ -180,6 +250,7 @@ class HardwareInfoEx(Structure): .. seealso:: :meth:`UcanServer.get_hardware_info` """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure (only used internally) @@ -203,27 +274,43 @@ def __eq__(self, other): if not isinstance(other, HardwareInfoEx): return False - return self.device_number == other.device_number and self.serial == other.serial and \ - self.fw_version == other.fw_version and self.product_code == other.product_code and \ - self.unique_id == other.unique_id and self.flags == other.flags + return ( + self.device_number == other.device_number + and self.serial == other.serial + and self.fw_version == other.fw_version + and self.product_code == other.product_code + and self.unique_id == other.unique_id + and self.flags == other.flags + ) @property - def device_number(self): return self.m_bDeviceNr + def device_number(self): + return self.m_bDeviceNr @property - def serial(self): return self.m_dwSerialNr + def serial(self): + return self.m_dwSerialNr @property - def fw_version(self): return self.m_dwFwVersionEx + def fw_version(self): + return self.m_dwFwVersionEx @property - def product_code(self): return self.m_dwProductCode + def product_code(self): + return self.m_dwProductCode @property - def unique_id(self): return self.m_dwUniqueId0, self.m_dwUniqueId1, self.m_dwUniqueId2, self.m_dwUniqueId3 + def unique_id(self): + return ( + self.m_dwUniqueId0, + self.m_dwUniqueId1, + self.m_dwUniqueId2, + self.m_dwUniqueId3, + ) @property - def flags(self): return self.m_dwFlags + def flags(self): + return self.m_dwFlags # void PUBLIC UcanCallbackFktEx (Handle UcanHandle_p, DWORD dwEvent_p, @@ -239,13 +326,20 @@ class HardwareInitInfo(Structure): .. note:: This structure is only used internally. """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure - ("m_fDoInitialize", BOOL), # specifies if the found module should be initialized by the DLL + ( + "m_fDoInitialize", + BOOL, + ), # specifies if the found module should be initialized by the DLL ("m_pUcanHandle", Handle), # pointer to variable receiving the USB-CAN-Handle ("m_fpCallbackFktEx", CallbackFktEx), # pointer to callback function - ("m_pCallbackArg", LPVOID), # pointer to user defined parameter for callback function + ( + "m_pCallbackArg", + LPVOID, + ), # pointer to user defined parameter for callback function ("m_fTryNext", BOOL), # specifies if a further module should be found ] @@ -257,6 +351,7 @@ class ChannelInfo(Structure): .. seealso:: :meth:`UcanServer.get_hardware_info` """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure @@ -264,48 +359,75 @@ class ChannelInfo(Structure): ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) - ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) - ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwAMR", + DWORD, + ), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwACR", + DWORD, + ), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls - # (see enum :class:`BaudrateEx`) - ("m_fCanIsInit", BOOL), # True if the CAN interface is initialized, otherwise false - ("m_wCanStatus", WORD), # CAN status (same as received by method :meth:`UcanServer.get_status`) + # (see enum :class:`BaudrateEx`) + ( + "m_fCanIsInit", + BOOL, + ), # True if the CAN interface is initialized, otherwise false + ( + "m_wCanStatus", + WORD, + ), # CAN status (same as received by method :meth:`UcanServer.get_status`) ] def __init__(self): - super(ChannelInfo, self).__init__(sizeof(ChannelInfo)) + super().__init__(sizeof(ChannelInfo)) def __eq__(self, other): if not isinstance(other, ChannelInfo): return False - return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ - self.AMR == other.AMR and self.ACR == other.ACR and self.baudrate == other.baudrate and \ - self.can_is_init == other.can_is_init and self.can_status == other.can_status + return ( + self.mode == other.mode + and self.BTR == other.BTR + and self.OCR == other.OCR + and self.AMR == other.AMR + and self.ACR == other.ACR + and self.baudrate == other.baudrate + and self.can_is_init == other.can_is_init + and self.can_status == other.can_status + ) @property - def mode(self): return self.m_bMode + def mode(self): + return self.m_bMode @property - def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + def BTR(self): + return self.m_bBTR0 << 8 | self.m_bBTR1 @property - def OCR(self): return self.m_bOCR + def OCR(self): + return self.m_bOCR @property - def AMR(self): return self.m_dwAMR + def AMR(self): + return self.m_dwAMR @property - def ACR(self): return self.m_dwACR + def ACR(self): + return self.m_dwACR @property - def baudrate(self): return self.m_dwBaudrate + def baudrate(self): + return self.m_dwBaudrate @property - def can_is_init(self): return self.m_fCanIsInit + def can_is_init(self): + return self.m_fCanIsInit @property - def can_status(self): return self.m_wCanStatus + def can_status(self): + return self.m_wCanStatus class MsgCountInfo(Structure): @@ -317,16 +439,19 @@ class MsgCountInfo(Structure): .. note:: This structure is only used internally. """ + _fields_ = [ ("m_wSentMsgCount", WORD), # number of sent CAN messages ("m_wRecvdMsgCount", WORD), # number of received CAN messages ] @property - def sent_msg_count(self): return self.m_wSentMsgCount + def sent_msg_count(self): + return self.m_wSentMsgCount @property - def recv_msg_count(self): return self.m_wRecvdMsgCount + def recv_msg_count(self): + return self.m_wRecvdMsgCount # void (PUBLIC *ConnectControlFktEx) (DWORD dwEvent_p, DWORD dwParam_p, void* pArg_p); @@ -334,4 +459,6 @@ def recv_msg_count(self): return self.m_wRecvdMsgCount # typedef void (PUBLIC *EnumCallback) (DWORD dwIndex_p, BOOL fIsUsed_p, # HardwareInfoEx* pHwInfoEx_p, HardwareInitInfo* pInitInfo_p, void* pArg_p); -EnumCallback = FUNCTYPE(None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID) +EnumCallback = FUNCTYPE( + None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID +) diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index e42c187eb..3f90e6cc2 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -1,5 +1,3 @@ -# coding: utf-8 - import logging import sys @@ -21,7 +19,9 @@ def check_valid_rx_can_msg(result): :return: True if a valid CAN messages was received, otherwise False. :rtype: bool """ - return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + return (result.value == ReturnCode.SUCCESSFUL) or ( + result.value > ReturnCode.WARNING + ) def check_tx_ok(result): @@ -37,7 +37,9 @@ def check_tx_ok(result): .. :seealso: :const:`ReturnCode.WARN_TXLIMIT` """ - return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + return (result.value == ReturnCode.SUCCESSFUL) or ( + result.value > ReturnCode.WARNING + ) def check_tx_success(result): @@ -81,7 +83,9 @@ def check_error(result): :return: True if a function returned error, otherwise False. :rtype: bool """ - return (result.value != ReturnCode.SUCCESSFUL) and (result.value < ReturnCode.WARNING) + return (result.value != ReturnCode.SUCCESSFUL) and ( + result.value < ReturnCode.WARNING + ) def check_error_cmd(result): @@ -113,7 +117,7 @@ def check_result(result, func, arguments): try: # Select the proper dll architecture - lib = WinDLL('usbcan64.dll' if sys.maxsize > 2 ** 32 else 'usbcan32.dll') + lib = WinDLL("usbcan64.dll" if sys.maxsize > 2 ** 32 else "usbcan32.dll") # BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p); UcanSetDebugMode = lib.UcanSetDebugMode @@ -149,7 +153,17 @@ def check_result(result, func, arguments): # DWORD dwProductCodeLow_p, DWORD dwProductCodeHigh_p); UcanEnumerateHardware = lib.UcanEnumerateHardware UcanEnumerateHardware.restype = DWORD - UcanEnumerateHardware.argtypes = [EnumCallback, LPVOID, BOOL, BYTE, BYTE, DWORD, DWORD, DWORD, DWORD] + UcanEnumerateHardware.argtypes = [ + EnumCallback, + LPVOID, + BOOL, + BYTE, + BYTE, + DWORD, + DWORD, + DWORD, + DWORD, + ] # BYTE PUBLIC UcanInitHardwareEx (Handle* pUcanHandle_p, BYTE bDeviceNr_p, # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); @@ -176,8 +190,12 @@ def check_result(result, func, arguments): # ChannelInfo* pCanInfoCh0_p, ChannelInfo* pCanInfoCh1_p); UcanGetHardwareInfoEx2 = lib.UcanGetHardwareInfoEx2 UcanGetHardwareInfoEx2.restype = ReturnCode - UcanGetHardwareInfoEx2.argtypes = [Handle, POINTER(HardwareInfoEx), POINTER(ChannelInfo), - POINTER(ChannelInfo)] + UcanGetHardwareInfoEx2.argtypes = [ + Handle, + POINTER(HardwareInfoEx), + POINTER(ChannelInfo), + POINTER(ChannelInfo), + ] UcanGetHardwareInfoEx2.errcheck = check_result # BYTE PUBLIC UcanInitCanEx2 (Handle UcanHandle_p, BYTE bChannel_p, tUcaninit_canParam* pinit_canParam_p); @@ -210,7 +228,12 @@ def check_result(result, func, arguments): # CanMsg* pCanMsg_p, DWORD* pdwCount_p); UcanReadCanMsgEx = lib.UcanReadCanMsgEx UcanReadCanMsgEx.restype = ReturnCode - UcanReadCanMsgEx.argtypes = [Handle, POINTER(BYTE), POINTER(CanMsg), POINTER(DWORD)] + UcanReadCanMsgEx.argtypes = [ + Handle, + POINTER(BYTE), + POINTER(CanMsg), + POINTER(DWORD), + ] UcanReadCanMsgEx.errcheck = check_result # BYTE PUBLIC UcanWriteCanMsgEx (Handle UcanHandle_p, BYTE bChannel_p, @@ -291,10 +314,11 @@ def check_result(result, func, arguments): log.warning("Cannot load SYSTEC ucan library: %s.", ex) -class UcanServer(object): +class UcanServer: """ UcanServer is a Python wrapper class for using the usbcan32.dll / usbcan64.dll. """ + _modules_found = [] _connect_control_ref = None @@ -304,7 +328,7 @@ def __init__(self): self._hw_is_initialized = False self._ch_is_initialized = { Channel.CHANNEL_CH0: False, - Channel.CHANNEL_CH1: False + Channel.CHANNEL_CH1: False, } self._callback_ref = CallbackFktEx(self._callback) if self._connect_control_ref is None: @@ -343,16 +367,33 @@ def is_can1_initialized(self): @classmethod def _enum_callback(cls, index, is_used, hw_info_ex, init_info, arg): - cls._modules_found.append((index, bool(is_used), hw_info_ex.contents, init_info.contents)) + cls._modules_found.append( + (index, bool(is_used), hw_info_ex.contents, init_info.contents) + ) @classmethod - def enumerate_hardware(cls, device_number_low=0, device_number_high=-1, serial_low=0, serial_high=-1, - product_code_low=0, product_code_high=-1, enum_used_devices=False): + def enumerate_hardware( + cls, + device_number_low=0, + device_number_high=-1, + serial_low=0, + serial_high=-1, + product_code_low=0, + product_code_high=-1, + enum_used_devices=False, + ): cls._modules_found = [] - UcanEnumerateHardware(cls._enum_callback_ref, None, enum_used_devices, - device_number_low, device_number_high, - serial_low, serial_high, - product_code_low, product_code_high) + UcanEnumerateHardware( + cls._enum_callback_ref, + None, + enum_used_devices, + device_number_low, + device_number_high, + serial_low, + serial_high, + product_code_low, + product_code_high, + ) return cls._modules_found def init_hardware(self, serial=None, device_number=ANY_MODULE): @@ -365,14 +406,27 @@ def init_hardware(self, serial=None, device_number=ANY_MODULE): if not self._hw_is_initialized: # initialize hardware either by device number or serial if serial is None: - UcanInitHardwareEx(byref(self._handle), device_number, self._callback_ref, None) + UcanInitHardwareEx( + byref(self._handle), device_number, self._callback_ref, None + ) else: - UcanInitHardwareEx2(byref(self._handle), serial, self._callback_ref, None) + UcanInitHardwareEx2( + byref(self._handle), serial, self._callback_ref, None + ) self._hw_is_initialized = True - def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrate=BaudrateEx.BAUDEX_USE_BTR01, - AMR=AMR_ALL, ACR=ACR_ALL, mode=Mode.MODE_NORMAL, OCR=OutputControl.OCR_DEFAULT, - rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, tx_buffer_entries=DEFAULT_BUFFER_ENTRIES): + def init_can( + self, + channel=Channel.CHANNEL_CH0, + BTR=Baudrate.BAUD_1MBit, + baudrate=BaudrateEx.BAUDEX_USE_BTR01, + AMR=AMR_ALL, + ACR=ACR_ALL, + mode=Mode.MODE_NORMAL, + OCR=OutputControl.OCR_DEFAULT, + rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, + tx_buffer_entries=DEFAULT_BUFFER_ENTRIES, + ): """ Initializes a specific CAN channel of a device. @@ -388,7 +442,9 @@ def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrat :param int tx_buffer_entries: The number of maximum entries in the transmit buffer. """ if not self._ch_is_initialized.get(channel, False): - init_param = InitCanParam(mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries) + init_param = InitCanParam( + mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries + ) UcanInitCanEx2(self._handle, channel, init_param) self._ch_is_initialized[channel] = True @@ -407,7 +463,7 @@ def read_can_msg(self, channel, count): c_can_msg = (CanMsg * count)() c_count = DWORD(count) UcanReadCanMsgEx(self._handle, byref(c_channel), c_can_msg, byref(c_count)) - return c_can_msg[:c_count.value], c_channel.value + return c_can_msg[: c_count.value], c_channel.value def write_can_msg(self, channel, can_msg): """ @@ -493,7 +549,9 @@ def get_hardware_info(self): """ hw_info_ex = HardwareInfoEx() can_info_ch0, can_info_ch1 = ChannelInfo(), ChannelInfo() - UcanGetHardwareInfoEx2(self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1)) + UcanGetHardwareInfoEx2( + self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1) + ) return hw_info_ex, can_info_ch0, can_info_ch1 def get_fw_version(self): @@ -534,7 +592,7 @@ def read_cyclic_can_msg(self, channel, count): c_can_msg = (CanMsg * count)() c_count = DWORD(count) UcanReadCyclicCanMsg(self._handle, byref(c_channel), c_can_msg, c_count) - return c_can_msg[:c_count.value] + return c_can_msg[: c_count.value] def enable_cyclic_can_msg(self, channel, flags): """ @@ -570,7 +628,9 @@ def get_can_error_counter(self, channel): """ tx_error_counter = DWORD(0) rx_error_counter = DWORD(0) - UcanGetCanErrorCounter(self._handle, channel, byref(tx_error_counter), byref(rx_error_counter)) + UcanGetCanErrorCounter( + self._handle, channel, byref(tx_error_counter), byref(rx_error_counter) + ) return tx_error_counter, rx_error_counter def set_tx_timeout(self, channel, timeout): @@ -593,7 +653,11 @@ def shutdown(self, channel=Channel.CHANNEL_ALL, shutdown_hardware=True): """ # shutdown each channel if it's initialized for _channel, is_initialized in self._ch_is_initialized.items(): - if is_initialized and (_channel == channel or channel == Channel.CHANNEL_ALL or shutdown_hardware): + if is_initialized and ( + _channel == channel + or channel == Channel.CHANNEL_ALL + or shutdown_hardware + ): UcanDeinitCanEx(self._handle, _channel) self._ch_is_initialized[_channel] = False @@ -651,8 +715,13 @@ def get_can_status_message(can_status): CanStatus.CANERR_OVERRUN: "Rx-buffer is full", CanStatus.CANERR_XMTFULL: "Tx-buffer is full", } - return "OK" if can_status == CanStatus.CANERR_OK \ - else ", ".join(msg for status, msg in status_msgs.items() if can_status & status) + return ( + "OK" + if can_status == CanStatus.CANERR_OK + else ", ".join( + msg for status, msg in status_msgs.items() if can_status & status + ) + ) @staticmethod def get_baudrate_message(baudrate): @@ -729,7 +798,9 @@ def get_product_code_message(product_code): ProductCode.PRODCODE_PID_RESERVED1: "Reserved", ProductCode.PRODCODE_PID_RESERVED2: "Reserved", } - return product_code_msgs.get(product_code & PRODCODE_MASK_PID, "Product code is unknown") + return product_code_msgs.get( + product_code & PRODCODE_MASK_PID, "Product code is unknown" + ) @classmethod def convert_to_major_ver(cls, version): @@ -775,8 +846,10 @@ def check_version_is_equal_or_higher(cls, version, cmp_major, cmp_minor): :return: True if equal or higher, otherwise False. :rtype: bool """ - return (cls.convert_to_major_ver(version) > cmp_major) or \ - (cls.convert_to_major_ver(version) == cmp_major and cls.convert_to_minor_ver(version) >= cmp_minor) + return (cls.convert_to_major_ver(version) > cmp_major) or ( + cls.convert_to_major_ver(version) == cmp_major + and cls.convert_to_minor_ver(version) >= cmp_minor + ) @classmethod def check_is_systec(cls, hw_info_ex): @@ -788,7 +861,9 @@ def check_is_systec(cls, hw_info_ex): :return: True when the module is a systec USB-CANmodul, otherwise False. :rtype: bool """ - return (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) >= ProductCode.PRODCODE_PID_MULTIPORT + return ( + hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID + ) >= ProductCode.PRODCODE_PID_MULTIPORT @classmethod def check_is_G4(cls, hw_info_ex): @@ -824,8 +899,9 @@ def check_support_cyclic_msg(cls, hw_info_ex): :return: True when the module does support cyclic CAN messages, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and \ - cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 6) + return cls.check_is_systec(hw_info_ex) and cls.check_version_is_equal_or_higher( + hw_info_ex.m_dwFwVersionEx, 3, 6 + ) @classmethod def check_support_two_channel(cls, hw_info_ex): @@ -837,7 +913,9 @@ def check_support_two_channel(cls, hw_info_ex): :return: True when the module (logical device) does support two CAN channels, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and (hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA) + return cls.check_is_systec(hw_info_ex) and ( + hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA + ) @classmethod def check_support_term_resistor(cls, hw_info_ex): @@ -861,9 +939,17 @@ def check_support_user_port(cls, hw_info_ex): :return: True when the module supports a user I/O port, otherwise False. :rtype: bool """ - return ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_BASIC) \ - and ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_RESERVED1) \ - and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + return ( + ( + (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) + != ProductCode.PRODCODE_PID_BASIC + ) + and ( + (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) + != ProductCode.PRODCODE_PID_RESERVED1 + ) + and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + ) @classmethod def check_support_rb_user_port(cls, hw_info_ex): @@ -899,8 +985,9 @@ def check_support_ucannet(cls, hw_info_ex): :return: True when the module does support the usage of the USB-CANnetwork driver, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and \ - cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 8) + return cls.check_is_systec(hw_info_ex) and cls.check_version_is_equal_or_higher( + hw_info_ex.m_dwFwVersionEx, 3, 8 + ) @classmethod def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): @@ -915,8 +1002,14 @@ def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True :return: Value for AMR. :rtype: int """ - return (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) if is_extended else \ - (((from_id ^ to_id) << 21) | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF)) + return ( + (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) + if is_extended + else ( + ((from_id ^ to_id) << 21) + | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF) + ) + ) @classmethod def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): @@ -931,8 +1024,11 @@ def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True :return: Value for ACR. :rtype: int """ - return (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) if is_extended else \ - (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + return ( + (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) + if is_extended + else (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + ) def _connect_control(self, event, param, arg): """ @@ -947,7 +1043,7 @@ def _connect_control(self, event, param, arg): - CbEvent.EVENT_FATALDISCON: USB-CAN-Handle of the disconnected module :param arg: Additional parameter defined with :meth:`init_hardware_ex` (not used in this wrapper class). """ - log.debug("Event: %s, Param: %s" % (event, param)) + log.debug("Event: %s, Param: %s", event, param) if event == CbEvent.EVENT_FATALDISCON: self.fatal_disconnect_event(param) @@ -966,7 +1062,7 @@ def _callback(self, handle, event, channel, arg): CAN channel (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or :data:`Channel.CHANNEL_ANY`). :param arg: Additional parameter defined with :meth:`init_hardware_ex`. """ - log.debug("Handle: %s, Event: %s, Channel: %s" % (handle, event, channel)) + log.debug("Handle: %s, Event: %s, Channel: %s", handle, event, channel) if event == CbEvent.EVENT_INITHW: self.init_hw_event() @@ -987,7 +1083,6 @@ def init_hw_event(self): .. note:: To be overridden by subclassing. """ - pass def init_can_event(self, channel): """ @@ -997,7 +1092,6 @@ def init_can_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def can_msg_received_event(self, channel): """ @@ -1009,7 +1103,6 @@ def can_msg_received_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def status_event(self, channel): """ @@ -1021,7 +1114,6 @@ def status_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def deinit_can_event(self, channel): """ @@ -1031,7 +1123,6 @@ def deinit_can_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def deinit_hw_event(self): """ @@ -1039,7 +1130,6 @@ def deinit_hw_event(self): .. note:: To be overridden by subclassing. """ - pass def connect_event(self): """ @@ -1047,7 +1137,6 @@ def connect_event(self): .. note:: To be overridden by subclassing. """ - pass def disconnect_event(self): """ @@ -1055,7 +1144,6 @@ def disconnect_event(self): .. note:: To be overridden by subclassing. """ - pass def fatal_disconnect_event(self, device_number): """ @@ -1067,7 +1155,6 @@ def fatal_disconnect_event(self, device_number): .. note:: To be overridden by subclassing. """ - pass UcanServer._enum_callback_ref = EnumCallback(UcanServer._enum_callback) diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 9731398bd..2d3a23777 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -1,5 +1,3 @@ -# coding: utf-8 - import logging from threading import Event @@ -9,15 +7,16 @@ from .structures import * from .ucan import UcanServer -log = logging.getLogger('can.systec') +log = logging.getLogger("can.systec") class Ucan(UcanServer): """ Wrapper around UcanServer to read messages with timeout using events. """ + def __init__(self): - super(Ucan, self).__init__() + super().__init__() self._msg_received_event = Event() def can_msg_received_event(self, channel): @@ -28,7 +27,7 @@ def read_can_msg(self, channel, count, timeout): if self.get_msg_pending(channel, PendingFlags.PENDING_FLAG_RX_DLL) == 0: if not self._msg_received_event.wait(timeout): return None, False - return super(Ucan, self).read_can_msg(channel, 1) + return super().read_can_msg(channel, 1) class UcanBus(BusABC): @@ -45,7 +44,7 @@ class UcanBus(BusABC): 250000: Baudrate.BAUD_250kBit, 500000: Baudrate.BAUD_500kBit, 800000: Baudrate.BAUD_800kBit, - 1000000: Baudrate.BAUD_1MBit + 1000000: Baudrate.BAUD_1MBit, } def __init__(self, channel, can_filters=None, **kwargs): @@ -96,14 +95,14 @@ def __init__(self, channel, can_filters=None, **kwargs): raise ImportError("The SYSTEC ucan library has not been initialized.") self.channel = int(channel) - device_number = int(kwargs.get('device_number', ANY_MODULE)) + device_number = int(kwargs.get("device_number", ANY_MODULE)) # configuration options - bitrate = kwargs.get('bitrate', 500000) + bitrate = kwargs.get("bitrate", 500000) if bitrate not in self.BITRATES: raise ValueError("Invalid bitrate {}".format(bitrate)) - state = kwargs.get('state', BusState.ACTIVE) + state = kwargs.get("state", BusState.ACTIVE) if state is BusState.ACTIVE or state is BusState.PASSIVE: self._state = state else: @@ -111,10 +110,10 @@ def __init__(self, channel, can_filters=None, **kwargs): # get parameters self._params = { - "mode": Mode.MODE_NORMAL | - (Mode.MODE_TX_ECHO if kwargs.get('receive_own_messages') else 0) | - (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), - "BTR": self.BITRATES[bitrate] + "mode": Mode.MODE_NORMAL + | (Mode.MODE_TX_ECHO if kwargs.get("receive_own_messages") else 0) + | (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), + "BTR": self.BITRATES[bitrate], } # get extra parameters if kwargs.get("rx_buffer_entries"): @@ -125,25 +124,29 @@ def __init__(self, channel, can_filters=None, **kwargs): self._ucan.init_hardware(device_number=device_number) self._ucan.init_can(self.channel, **self._params) hw_info_ex, _, _ = self._ucan.get_hardware_info() - self.channel_info = '%s, S/N %s, CH %s, BTR %s' % ( + self.channel_info = "%s, S/N %s, CH %s, BTR %s" % ( self._ucan.get_product_code_message(hw_info_ex.product_code), hw_info_ex.serial, self.channel, - self._ucan.get_baudrate_message(self.BITRATES[bitrate]) + self._ucan.get_baudrate_message(self.BITRATES[bitrate]), ) - super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + self._is_filtered = False + + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) if not message: return None, False - msg = Message(timestamp=float(message[0].time) / 1000.0, - is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), - is_extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), - arbitration_id=message[0].id, - dlc=len(message[0].data), - data=message[0].data) + msg = Message( + timestamp=float(message[0].time) / 1000.0, + is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), + is_extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), + arbitration_id=message[0].id, + dlc=len(message[0].data), + data=message[0].data, + ) return msg, self._is_filtered def send(self, msg, timeout=None): @@ -168,40 +171,51 @@ def send(self, msg, timeout=None): if timeout is not None and timeout >= 0: self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) - message = CanMsg(msg.arbitration_id, - MsgFrameFormat.MSG_FF_STD | - (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) | - (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), - msg.data) + message = CanMsg( + msg.arbitration_id, + MsgFrameFormat.MSG_FF_STD + | (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) + | (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), + msg.data, + ) self._ucan.write_can_msg(self.channel, [message]) @staticmethod def _detect_available_configs(): configs = [] try: - for index, is_used, hw_info_ex, init_info in Ucan.enumerate_hardware(): - configs.append({'interface': 'systec', - 'channel': Channel.CHANNEL_CH0, - 'device_number': hw_info_ex.device_number}) + # index, is_used, hw_info_ex, init_info + for _, _, hw_info_ex, _ in Ucan.enumerate_hardware(): + configs.append( + { + "interface": "systec", + "channel": Channel.CHANNEL_CH0, + "device_number": hw_info_ex.device_number, + } + ) if Ucan.check_support_two_channel(hw_info_ex): - configs.append({'interface': 'systec', - 'channel': Channel.CHANNEL_CH1, - 'device_number': hw_info_ex.device_number}) - except: + configs.append( + { + "interface": "systec", + "channel": Channel.CHANNEL_CH1, + "device_number": hw_info_ex.device_number, + } + ) + except Exception: log.warning("The SYSTEC ucan library has not been initialized.") return configs def _apply_filters(self, filters): if filters and len(filters) == 1: - can_id = filters[0]['can_id'] - can_mask = filters[0]['can_mask'] + can_id = filters[0]["can_id"] + can_mask = filters[0]["can_mask"] self._ucan.set_acceptance(self.channel, can_mask, can_id) self._is_filtered = True - log.info('Hardware filtering on ID 0x%X, mask 0x%X', can_id, can_mask) + log.info("Hardware filtering on ID 0x%X, mask 0x%X", can_id, can_mask) else: self._ucan.set_acceptance(self.channel) self._is_filtered = False - log.info('Hardware filtering has been disabled') + log.info("Hardware filtering has been disabled") def flush_tx_buffer(self): """ @@ -210,7 +224,7 @@ def flush_tx_buffer(self): :raises can.CanError: If flushing of the transmit buffer failed. """ - log.info('Flushing transmit buffer') + log.info("Flushing transmit buffer") self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) @staticmethod @@ -235,11 +249,17 @@ def create_filter(extended, from_id, to_id, rtr_only, rtr_too): :return: Returns list with one filter containing a "can_id", a "can_mask" and "extended" key. """ - return [{ - "can_id": Ucan.calculate_acr(extended, from_id, to_id, rtr_only, rtr_too), - "can_mask": Ucan.calculate_amr(extended, from_id, to_id, rtr_only, rtr_too), - "extended": extended - }] + return [ + { + "can_id": Ucan.calculate_acr( + extended, from_id, to_id, rtr_only, rtr_too + ), + "can_mask": Ucan.calculate_amr( + extended, from_id, to_id, rtr_only, rtr_too + ), + "extended": extended, + } + ] @property def state(self): @@ -247,7 +267,9 @@ def state(self): @state.setter def state(self, new_state): - if self._state is not BusState.ERROR and (new_state is BusState.ACTIVE or new_state is BusState.PASSIVE): + if self._state is not BusState.ERROR and ( + new_state is BusState.ACTIVE or new_state is BusState.PASSIVE + ): # close the CAN channel self._ucan.shutdown(self.channel, False) # set mode diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 454942934..4ccff1cb0 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,9 +1,5 @@ -# coding: utf-8 - """ """ -from __future__ import absolute_import - from .usb2canInterface import Usb2canBus from .usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index b47396876..d9beb5df4 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,10 +1,6 @@ -# coding: utf-8 - """ """ -from __future__ import division, print_function, absolute_import - import logging try: @@ -15,17 +11,31 @@ def WMIDateStringToDate(dtmDate): - if (dtmDate[4] == 0): - strDateTime = dtmDate[5] + '/' + if dtmDate[4] == 0: + strDateTime = dtmDate[5] + "/" else: - strDateTime = dtmDate[4] + dtmDate[5] + '/' + strDateTime = dtmDate[4] + dtmDate[5] + "/" - if (dtmDate[6] == 0): - strDateTime = strDateTime + dtmDate[7] + '/' + if dtmDate[6] == 0: + strDateTime = strDateTime + dtmDate[7] + "/" else: - strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + '/' - strDateTime = strDateTime + dtmDate[0] + dtmDate[1] + dtmDate[2] + dtmDate[3] + ' ' + dtmDate[8] + dtmDate[9] \ - + ':' + dtmDate[10] + dtmDate[11] + ':' + dtmDate[12] + dtmDate[13] + strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + "/" + strDateTime = ( + strDateTime + + dtmDate[0] + + dtmDate[1] + + dtmDate[2] + + dtmDate[3] + + " " + + dtmDate[8] + + dtmDate[9] + + ":" + + dtmDate[10] + + dtmDate[11] + + ":" + + dtmDate[12] + + dtmDate[13] + ) return strDateTime @@ -39,7 +49,7 @@ def find_serial_devices(serial_matcher="ED"): :rtype: List[str] """ objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") - objSWbemServices = objWMIService.ConnectServer(".", "root\cimv2") + objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2") items = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") ids = (item.Dependent.strip('"')[-8:] for item in items) return [e for e in ids if e.startswith(serial_matcher)] diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index eb87ffbd7..8e0f54687 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,11 +1,7 @@ -# coding: utf-8 - """ This interface is for Windows only, otherwise use socketCAN. """ -from __future__ import division, print_function, absolute_import - import logging from ctypes import byref @@ -14,7 +10,7 @@ from .serial_selector import find_serial_devices # Set up logging -log = logging.getLogger('can.usb2can') +log = logging.getLogger("can.usb2can") def message_convert_tx(msg): @@ -48,13 +44,15 @@ def message_convert_rx(message_rx): is_remote_frame = bool(message_rx.flags & IS_REMOTE_FRAME) is_error_frame = bool(message_rx.flags & IS_ERROR_FRAME) - return Message(timestamp=message_rx.timestamp, - is_remote_frame=is_remote_frame, - is_extended_id=is_extended_id, - is_error_frame=is_error_frame, - arbitration_id=message_rx.id, - dlc=message_rx.sizeData, - data=message_rx.data[:message_rx.sizeData]) + return Message( + timestamp=message_rx.timestamp, + is_remote_frame=is_remote_frame, + is_extended_id=is_extended_id, + is_error_frame=is_error_frame, + arbitration_id=message_rx.id, + dlc=message_rx.sizeData, + data=message_rx.data[: message_rx.sizeData], + ) class Usb2canBus(BusABC): @@ -85,16 +83,20 @@ class Usb2canBus(BusABC): """ - def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, - bitrate=500000, *args, **kwargs): + def __init__( + self, + channel=None, + dll="usb2can.dll", + flags=0x00000008, + *args, + bitrate=500000, + **kwargs + ): self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device - if "serial" in kwargs: - device_id = kwargs["serial"] - else: - device_id = channel + device_id = kwargs.get("serial", d=channel) # search for a serial number if the device_id is None or empty if not device_id: @@ -109,10 +111,11 @@ def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, self.channel_info = "USB2CAN device {}".format(device_id) connector = "{}; {}".format(device_id, baudrate) - self.handle = self.can.open(connector, flags) + self.handle = self.can.open(connector, flags_t) - super(Usb2canBus, self).__init__(channel=channel, dll=dll, flags=flags, - bitrate=bitrate, *args, **kwargs) + super().__init__( + channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, *args, **kwargs + ) def send(self, msg, timeout=None): tx = message_convert_tx(msg) @@ -125,7 +128,6 @@ def send(self, msg, timeout=None): if status != CANAL_ERROR_SUCCESS: raise CanError("could not send message: status == {}".format(status)) - def _recv_internal(self, timeout): messagerx = CanalMsg() @@ -139,10 +141,10 @@ def _recv_internal(self, timeout): if status == CANAL_ERROR_SUCCESS: rx = message_convert_rx(messagerx) - elif status == CANAL_ERROR_RCV_EMPTY or status == CANAL_ERROR_TIMEOUT: + elif status in (CANAL_ERROR_RCV_EMPTY, CANAL_ERROR_TIMEOUT): rx = None else: - log.error('Canal Error %s', status) + log.error("Canal Error %s", status) rx = None return rx, False @@ -159,7 +161,11 @@ def shutdown(self): raise CanError("could not shut down bus: status == {}".format(status)) @staticmethod - def _detect_available_configs(serial_matcher=None): + def _detect_available_configs(): + return Usb2canBus.detect_available_configs() + + @staticmethod + def detect_available_configs(serial_matcher=None): """ Uses the Windows Management Instrumentation to identify serial devices. @@ -171,4 +177,4 @@ def _detect_available_configs(serial_matcher=None): else: channels = find_serial_devices() - return [{'interface': 'usb2can', 'channel': c} for c in channels] + return [{"interface": "usb2can", "channel": c} for c in channels] diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a318bcd6f..1f336b241 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -1,25 +1,21 @@ -# coding: utf-8 - """ This wrapper is for windows or direct access via CANAL API. Socket CAN is recommended under Unix/Linux systems. """ -from __future__ import division, print_function, absolute_import - from ctypes import * from struct import * import logging import can -log = logging.getLogger('can.usb2can') +log = logging.getLogger("can.usb2can") # type definitions -flags = c_ulong +flags_t = c_ulong pConfigureStr = c_char_p -handle = c_long -timeout = c_ulong +handle_t = c_long +timeout_t = c_ulong filter_t = c_ulong # flags mappings @@ -33,33 +29,39 @@ class CanalStatistics(Structure): - _fields_ = [('ReceiveFrams', c_ulong), - ('TransmistFrams', c_ulong), - ('ReceiveData', c_ulong), - ('TransmitData', c_ulong), - ('Overruns', c_ulong), - ('BusWarnings', c_ulong), - ('BusOff', c_ulong)] + _fields_ = [ + ("ReceiveFrams", c_ulong), + ("TransmistFrams", c_ulong), + ("ReceiveData", c_ulong), + ("TransmitData", c_ulong), + ("Overruns", c_ulong), + ("BusWarnings", c_ulong), + ("BusOff", c_ulong), + ] stat = CanalStatistics class CanalStatus(Structure): - _fields_ = [('channel_status', c_ulong), - ('lasterrorcode', c_ulong), - ('lasterrorsubcode', c_ulong), - ('lasterrorstr', c_byte * 80)] + _fields_ = [ + ("channel_status", c_ulong), + ("lasterrorcode", c_ulong), + ("lasterrorsubcode", c_ulong), + ("lasterrorstr", c_byte * 80), + ] # data type for the CAN Message class CanalMsg(Structure): - _fields_ = [('flags', c_ulong), - ('obid', c_ulong), - ('id', c_ulong), - ('sizeData', c_ubyte), - ('data', c_ubyte * 8), - ('timestamp', c_ulong)] + _fields_ = [ + ("flags", c_ulong), + ("obid", c_ulong), + ("id", c_ulong), + ("sizeData", c_ubyte), + ("data", c_ubyte * 8), + ("timestamp", c_ulong), + ] class Usb2CanAbstractionLayer: @@ -77,7 +79,7 @@ def __init__(self, dll="usb2can.dll"): self.__m_dllBasic = windll.LoadLibrary(dll) if self.__m_dllBasic is None: - log.warning('DLL failed to load at path: {}'.format(dll)) + log.warning("DLL failed to load at path: {}".format(dll)) def open(self, configuration, flags): """ @@ -92,19 +94,25 @@ def open(self, configuration, flags): try: # we need to convert this into bytes, since the underlying DLL cannot # handle non-ASCII configuration strings - config_ascii = configuration.encode('ascii', 'ignore') + config_ascii = configuration.encode("ascii", "ignore") result = self.__m_dllBasic.CanalOpen(config_ascii, flags) except Exception as ex: # catch any errors thrown by this call and re-raise - raise can.CanError('CanalOpen() failed, configuration: "{}", error: {}' - .format(configuration, ex)) + raise can.CanError( + 'CanalOpen() failed, configuration: "{}", error: {}'.format( + configuration, ex + ) + ) else: # any greater-than-zero return value indicates a success # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) # raise an error if the return code is <= 0 if result <= 0: - raise can.CanError('CanalOpen() failed, configuration: "{}", return code: {}' - .format(configuration, result)) + raise can.CanError( + 'CanalOpen() failed, configuration: "{}", return code: {}'.format( + configuration, result + ) + ) else: return result @@ -113,7 +121,7 @@ def close(self, handle): res = self.__m_dllBasic.CanalClose(handle) return res except: - log.warning('Failed to close') + log.warning("Failed to close") raise def send(self, handle, msg): @@ -121,7 +129,7 @@ def send(self, handle, msg): res = self.__m_dllBasic.CanalSend(handle, msg) return res except: - log.warning('Sending error') + log.warning("Sending error") raise can.CanError("Failed to transmit frame") def receive(self, handle, msg): @@ -129,7 +137,7 @@ def receive(self, handle, msg): res = self.__m_dllBasic.CanalReceive(handle, msg) return res except: - log.warning('Receive error') + log.warning("Receive error") raise def blocking_send(self, handle, msg, timeout): @@ -137,7 +145,7 @@ def blocking_send(self, handle, msg, timeout): res = self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout) return res except: - log.warning('Blocking send error') + log.warning("Blocking send error") raise def blocking_receive(self, handle, msg, timeout): @@ -145,23 +153,23 @@ def blocking_receive(self, handle, msg, timeout): res = self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) return res except: - log.warning('Blocking Receive Failed') + log.warning("Blocking Receive Failed") raise - def get_status(self, handle, CanalStatus): + def get_status(self, handle, status): try: - res = self.__m_dllBasic.CanalGetStatus(handle, CanalStatus) + res = self.__m_dllBasic.CanalGetStatus(handle, status) return res except: - log.warning('Get status failed') + log.warning("Get status failed") raise - def get_statistics(self, handle, CanalStatistics): + def get_statistics(self, handle, statistics): try: - res = self.__m_dllBasic.CanalGetStatistics(handle, CanalStatistics) + res = self.__m_dllBasic.CanalGetStatistics(handle, statistics) return res except: - log.warning('Get Statistics failed') + log.warning("Get Statistics failed") raise def get_version(self): @@ -169,7 +177,7 @@ def get_version(self): res = self.__m_dllBasic.CanalGetVersion() return res except: - log.warning('Failed to get version info') + log.warning("Failed to get version info") raise def get_library_version(self): @@ -177,7 +185,7 @@ def get_library_version(self): res = self.__m_dllBasic.CanalGetDllVersion() return res except: - log.warning('Failed to get DLL version') + log.warning("Failed to get DLL version") raise def get_vendor_string(self): @@ -185,5 +193,5 @@ def get_vendor_string(self): res = self.__m_dllBasic.CanalGetVendorString() return res except: - log.warning('Failed to get vendor string') + log.warning("Failed to get vendor string") raise diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index dac47be4a..813608a86 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 251b9fa56..ac85ef4ce 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. @@ -10,17 +8,19 @@ # ============================== import ctypes import logging -import sys import time +import os try: # Try builtin Python 3 Windows API from _winapi import WaitForSingleObject, INFINITE + HAS_EVENTS = True except ImportError: try: # Try pywin32 package from win32event import WaitForSingleObject, INFINITE + HAS_EVENTS = True except ImportError: # Use polling instead @@ -28,7 +28,7 @@ # Import Modules # ============== -from can import BusABC, Message, CanError +from can import BusABC, Message from can.util import len2dlc, dlc2len from .exceptions import VectorError @@ -36,22 +36,41 @@ # ==================== LOG = logging.getLogger(__name__) +# Import Vector API module +# ======================== +from . import xldefine, xlclass + # Import safely Vector API module for Travis tests -vxlapi = None +xldriver = None try: - from . import vxlapi + from . import xldriver except Exception as exc: - LOG.warning('Could not import vxlapi: %s', exc) + LOG.warning("Could not import vxlapi: %s", exc) class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" - def __init__(self, channel, can_filters=None, poll_interval=0.01, - receive_own_messages=False, - bitrate=None, rx_queue_size=2**14, app_name="CANalyzer", - serial=None, fd=False, data_bitrate=None, sjwAbr=2, tseg1Abr=6, - tseg2Abr=3, sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, **kwargs): + def __init__( + self, + channel, + can_filters=None, + poll_interval=0.01, + receive_own_messages=False, + bitrate=None, + rx_queue_size=2 ** 14, + app_name="CANalyzer", + serial=None, + fd=False, + data_bitrate=None, + sjwAbr=2, + tseg1Abr=6, + tseg2Abr=3, + sjwDbr=2, + tseg1Dbr=6, + tseg2Dbr=3, + **kwargs, + ): """ :param list channel: The channel indexes to create this bus with. @@ -77,8 +96,14 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ - if vxlapi is None: + if os.name != "nt" and not kwargs.get("_testing", False): + raise OSError( + f'The Vector interface is only supported on Windows, but you are running "{os.name}"' + ) + + if xldriver is None: raise ImportError("The Vector API has not been loaded") + self.poll_interval = poll_interval if isinstance(channel, (list, tuple)): self.channels = channel @@ -86,10 +111,12 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [int(ch.strip()) for ch in channel.split(',')] - self._app_name = app_name.encode() if app_name is not None else '' - self.channel_info = 'Application %s: %s' % ( - app_name, ', '.join('CAN %d' % (ch + 1) for ch in self.channels)) + self.channels = [int(ch.strip()) for ch in channel.split(",")] + self._app_name = app_name.encode() if app_name is not None else b"" + self.channel_info = "Application %s: %s" % ( + app_name, + ", ".join("CAN %d" % (ch + 1) for ch in self.channels), + ) if serial is not None: app_name = None @@ -99,16 +126,20 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, if channel_config.serialNumber == serial: if channel_config.hwChannel in self.channels: channel_index.append(channel_config.channelIndex) - if len(channel_index) > 0: + if channel_index: if len(channel_index) != len(self.channels): - LOG.info("At least one defined channel wasn't found on the specified hardware.") + LOG.info( + "At least one defined channel wasn't found on the specified hardware." + ) self.channels = channel_index else: # Is there any better way to raise the error? - raise Exception("None of the configured channels could be found on the specified hardware.") + raise Exception( + "None of the configured channels could be found on the specified hardware." + ) - vxlapi.xlOpenDriver() - self.port_handle = vxlapi.XLportHandle(vxlapi.XL_INVALID_PORTHANDLE) + xldriver.xlOpenDriver() + self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) self.mask = 0 self.fd = fd # Get channels masks @@ -121,19 +152,28 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, hw_type = ctypes.c_uint(0) hw_index = ctypes.c_uint(0) hw_channel = ctypes.c_uint(0) - vxlapi.xlGetApplConfig(self._app_name, channel, hw_type, hw_index, - hw_channel, vxlapi.XL_BUS_TYPE_CAN) - LOG.debug('Channel index %d found', channel) - idx = vxlapi.xlGetChannelIndex(hw_type.value, hw_index.value, - hw_channel.value) + xldriver.xlGetApplConfig( + self._app_name, + channel, + hw_type, + hw_index, + hw_channel, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + ) + LOG.debug("Channel index %d found", channel) + idx = xldriver.xlGetChannelIndex( + hw_type.value, hw_index.value, hw_channel.value + ) if idx < 0: # Undocumented behavior! See issue #353. # If hardware is unavailable, this function returns -1. # Raise an exception as if the driver # would have signalled XL_ERR_HW_NOT_PRESENT. - raise VectorError(vxlapi.XL_ERR_HW_NOT_PRESENT, - "XL_ERR_HW_NOT_PRESENT", - "xlGetChannelIndex") + raise VectorError( + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.value, + "XL_ERR_HW_NOT_PRESENT", + "xlGetChannelIndex", + ) else: # Channel already given as global channel idx = channel @@ -142,25 +182,39 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.index_to_channel[idx] = channel self.mask |= mask - permission_mask = vxlapi.XLaccess() + permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed if bitrate or fd: permission_mask.value = self.mask if fd: - vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION_V4, vxlapi.XL_BUS_TYPE_CAN) + xldriver.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + ) else: - vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION, vxlapi.XL_BUS_TYPE_CAN) + xldriver.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + ) LOG.debug( - 'Open Port: PortHandle: %d, PermissionMask: 0x%X', - self.port_handle.value, permission_mask.value) + "Open Port: PortHandle: %d, PermissionMask: 0x%X", + self.port_handle.value, + permission_mask.value, + ) if permission_mask.value == self.mask: if fd: - self.canFdConf = vxlapi.XLcanFdConf() + self.canFdConf = xlclass.XLcanFdConf() if bitrate: self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) else: @@ -175,53 +229,89 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.canFdConf.sjwDbr = ctypes.c_uint(sjwDbr) self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) - - vxlapi.xlCanFdSetConfiguration(self.port_handle, self.mask, self.canFdConf) - LOG.info('SetFdConfig.: ABaudr.=%u, DBaudr.=%u', self.canFdConf.arbitrationBitRate, self.canFdConf.dataBitRate) - LOG.info('SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u', self.canFdConf.sjwAbr, self.canFdConf.tseg1Abr, self.canFdConf.tseg2Abr) - LOG.info('SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u', self.canFdConf.sjwDbr, self.canFdConf.tseg1Dbr, self.canFdConf.tseg2Dbr) + + xldriver.xlCanFdSetConfiguration( + self.port_handle, self.mask, self.canFdConf + ) + LOG.info( + "SetFdConfig.: ABaudr.=%u, DBaudr.=%u", + self.canFdConf.arbitrationBitRate, + self.canFdConf.dataBitRate, + ) + LOG.info( + "SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + self.canFdConf.sjwAbr, + self.canFdConf.tseg1Abr, + self.canFdConf.tseg2Abr, + ) + LOG.info( + "SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", + self.canFdConf.sjwDbr, + self.canFdConf.tseg1Dbr, + self.canFdConf.tseg2Dbr, + ) else: if bitrate: - vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) - LOG.info('SetChannelBitrate: baudr.=%u',bitrate) + xldriver.xlCanSetChannelBitrate( + self.port_handle, permission_mask, bitrate + ) + LOG.info("SetChannelBitrate: baudr.=%u", bitrate) else: - LOG.info('No init access!') + LOG.info("No init access!") # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 - vxlapi.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) + xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) if HAS_EVENTS: - self.event_handle = vxlapi.XLhandle() - vxlapi.xlSetNotification(self.port_handle, self.event_handle, 1) + self.event_handle = xlclass.XLhandle() + xldriver.xlSetNotification(self.port_handle, self.event_handle, 1) else: - LOG.info('Install pywin32 to avoid polling') + LOG.info("Install pywin32 to avoid polling") try: - vxlapi.xlActivateChannel(self.port_handle, self.mask, - vxlapi.XL_BUS_TYPE_CAN, 0) + xldriver.xlActivateChannel( + self.port_handle, + self.mask, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + 0, + ) except VectorError: self.shutdown() raise # Calculate time offset for absolute timestamps - offset = vxlapi.XLuint64() - vxlapi.xlGetSyncTime(self.port_handle, offset) - self._time_offset = time.time() - offset.value * 1e-9 + offset = xlclass.XLuint64() + try: + try: + xldriver.xlGetSyncTime(self.port_handle, offset) + except VectorError: + xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + self._time_offset = time.time() - offset.value * 1e-9 + except VectorError: + self._time_offset = 0.0 self._is_filtered = False - super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _apply_filters(self, filters): if filters: # Only up to one filter per ID type allowed - if len(filters) == 1 or (len(filters) == 2 and - filters[0].get("extended") != filters[1].get("extended")): + if len(filters) == 1 or ( + len(filters) == 2 + and filters[0].get("extended") != filters[1].get("extended") + ): try: for can_filter in filters: - vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, - can_filter["can_id"], can_filter["can_mask"], - vxlapi.XL_CAN_EXT if can_filter.get("extended") else vxlapi.XL_CAN_STD) + xldriver.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + can_filter["can_id"], + can_filter["can_mask"], + xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value + if can_filter.get("extended") + else xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, + ) except VectorError as exc: LOG.warning("Could not set filters: %s", exc) # go to fallback @@ -235,8 +325,20 @@ def _apply_filters(self, filters): # fallback: reset filters self._is_filtered = False try: - vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_EXT) - vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_STD) + xldriver.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + 0x0, + 0x0, + xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value, + ) + xldriver.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + 0x0, + 0x0, + xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, + ) except VectorError as exc: LOG.warning("Could not reset filters: %s", exc) @@ -244,20 +346,25 @@ def _recv_internal(self, timeout): end_time = time.time() + timeout if timeout is not None else None if self.fd: - event = vxlapi.XLcanRxEvent() + event = xlclass.XLcanRxEvent() else: - event = vxlapi.XLevent() + event = xlclass.XLevent() event_count = ctypes.c_uint() while True: if self.fd: try: - vxlapi.xlCanReceive(self.port_handle, event) + xldriver.xlCanReceive(self.port_handle, event) except VectorError as exc: - if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: raise else: - if event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK: + if ( + event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value + or event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_TX_OK.value + ): msg_id = event.tagData.canRxOkMsg.canId dlc = dlc2len(event.tagData.canRxOkMsg.dlc) flags = event.tagData.canRxOkMsg.msgFlags @@ -266,25 +373,47 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), - is_remote_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_RTR), - is_error_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EF), - is_fd=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EDL), - error_state_indicator=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI), - bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), + is_extended_id=bool( + msg_id + & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + ), + is_remote_frame=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_RTR.value + ), + is_error_frame=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EF.value + ), + is_fd=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EDL.value + ), + error_state_indicator=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_ESI.value + ), + bitrate_switch=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_BRS.value + ), dlc=dlc, data=event.tagData.canRxOkMsg.data[:dlc], - channel=channel) + channel=channel, + ) return msg, self._is_filtered + else: + self.handle_canfd_event(event) + else: event_count.value = 1 try: - vxlapi.xlReceive(self.port_handle, event_count, event) + xldriver.xlReceive(self.port_handle, event_count, event) except VectorError as exc: - if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: raise else: - if event.tag == vxlapi.XL_RECEIVE_MSG: + if event.tag == xldefine.XL_EventTags.XL_RECEIVE_MSG.value: msg_id = event.tagData.msg.id dlc = event.tagData.msg.dlc flags = event.tagData.msg.flags @@ -293,14 +422,26 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), - is_remote_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME), - is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), + is_extended_id=bool( + msg_id + & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + ), + is_remote_frame=bool( + flags + & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + ), + is_error_frame=bool( + flags + & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME.value + ), is_fd=False, dlc=dlc, data=event.tagData.msg.data[:dlc], - channel=channel) + channel=channel, + ) return msg, self._is_filtered + else: + self.handle_can_event(event) if end_time is not None and time.time() > end_time: return None, self._is_filtered @@ -317,11 +458,35 @@ def _recv_internal(self, timeout): # Wait a short time until we try again time.sleep(self.poll_interval) + def handle_can_event(self, event: xlclass.XLevent) -> None: + """Handle non-message CAN events. + + Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` + when `event.tag` is not `XL_CAN_EV_TAG_RX_OK` or `XL_CAN_EV_TAG_TX_OK`. + Subclasses can implement this method. + + :param event: XLevent that could have a `XL_CHIP_STATE`, `XL_TIMER` or `XL_SYNC_PULSE` tag. + :return: None + """ + pass + + def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: + """Handle non-message CAN FD events. + + Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` + when `event.tag` is not `XL_RECEIVE_MSG`. Subclasses can implement this method. + + :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR` + or `XL_CAN_EV_TAG_CHIP_STATE` tag. + :return: None + """ + pass + def send(self, msg, timeout=None): msg_id = msg.arbitration_id if msg.is_extended_id: - msg_id |= vxlapi.XL_CAN_EXT_MSG_ID + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value flags = 0 @@ -331,78 +496,105 @@ def send(self, msg, timeout=None): if self.fd: if msg.is_fd: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_EDL + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL.value if msg.bitrate_switch: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_BRS + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS.value if msg.is_remote_frame: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_RTR - + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR.value + message_count = 1 MsgCntSent = ctypes.c_uint(1) - - XLcanTxEvent = vxlapi.XLcanTxEvent() - XLcanTxEvent.tag = vxlapi.XL_CAN_EV_TAG_TX_MSG - XLcanTxEvent.transId = 0xffff - + + XLcanTxEvent = xlclass.XLcanTxEvent() + XLcanTxEvent.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG.value + XLcanTxEvent.transId = 0xFFFF + XLcanTxEvent.tagData.canMsg.canId = msg_id XLcanTxEvent.tagData.canMsg.msgFlags = flags XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) for idx, value in enumerate(msg.data): XLcanTxEvent.tagData.canMsg.data[idx] = value - vxlapi.xlCanTransmitEx(self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent) + xldriver.xlCanTransmitEx( + self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent + ) else: if msg.is_remote_frame: - flags |= vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME + flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value message_count = ctypes.c_uint(1) - - xl_event = vxlapi.XLevent() - xl_event.tag = vxlapi.XL_TRANSMIT_MSG - + + xl_event = xlclass.XLevent() + xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG.value + xl_event.tagData.msg.id = msg_id xl_event.tagData.msg.dlc = msg.dlc xl_event.tagData.msg.flags = flags for idx, value in enumerate(msg.data): xl_event.tagData.msg.data[idx] = value - vxlapi.xlCanTransmit(self.port_handle, mask, message_count, xl_event) + xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event) - def flush_tx_buffer(self): - vxlapi.xlCanFlushTransmitQueue(self.port_handle, self.mask) + xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) def shutdown(self): - vxlapi.xlDeactivateChannel(self.port_handle, self.mask) - vxlapi.xlClosePort(self.port_handle) - vxlapi.xlCloseDriver() - + xldriver.xlDeactivateChannel(self.port_handle, self.mask) + xldriver.xlClosePort(self.port_handle) + xldriver.xlCloseDriver() + def reset(self): - vxlapi.xlDeactivateChannel(self.port_handle, self.mask) - vxlapi.xlActivateChannel(self.port_handle, self.mask, - vxlapi.XL_BUS_TYPE_CAN, 0) + xldriver.xlDeactivateChannel(self.port_handle, self.mask) + xldriver.xlActivateChannel( + self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, 0 + ) @staticmethod def _detect_available_configs(): configs = [] channel_configs = get_channel_configs() - LOG.info('Found %d channels', len(channel_configs)) + LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: - LOG.info('Channel index %d: %s', - channel_config.channelIndex, - channel_config.name.decode('ascii')) - configs.append({'interface': 'vector', - 'app_name': None, - 'channel': channel_config.channelIndex}) + if ( + not channel_config.channelBusCapabilities + & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN.value + ): + continue + LOG.info( + "Channel index %d: %s", + channel_config.channelIndex, + channel_config.name.decode("ascii"), + ) + configs.append( + { + "interface": "vector", + "app_name": None, + "channel": channel_config.channelIndex, + "supports_fd": bool( + channel_config.channelBusCapabilities + & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT.value + ), + } + ) return configs + @staticmethod + def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: + """Open vector hardware configuration window. + + :param int wait_for_finish: + Time to wait for user input in milliseconds. + """ + xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) + + def get_channel_configs(): - if vxlapi is None: + if xldriver is None: return [] - driver_config = vxlapi.XLdriverConfig() + driver_config = xlclass.XLdriverConfig() try: - vxlapi.xlOpenDriver() - vxlapi.xlGetDriverConfig(driver_config) - vxlapi.xlCloseDriver() - except: + xldriver.xlOpenDriver() + xldriver.xlGetDriverConfig(driver_config) + xldriver.xlCloseDriver() + except Exception: pass return [driver_config.channel[i] for i in range(driver_config.channelCount)] diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 8715c276f..042c9d73a 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ """ @@ -7,8 +5,6 @@ class VectorError(CanError): - def __init__(self, error_code, error_string, function): self.error_code = error_code - text = "%s failed (%s)" % (function, error_string) - super(VectorError, self).__init__(text) + super().__init__(f"{function} failed ({error_string})") diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py deleted file mode 100644 index ae87706c4..000000000 --- a/can/interfaces/vector/vxlapi.py +++ /dev/null @@ -1,340 +0,0 @@ -# coding: utf-8 - -""" -Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. - -Authors: Julien Grave , Christian Sandberg -""" - -# Import Standard Python Modules -# ============================== -import ctypes -import logging -import platform -from .exceptions import VectorError - -# Define Module Logger -# ==================== -LOG = logging.getLogger(__name__) - -# Vector XL API Definitions -# ========================= -# Load Windows DLL -DLL_NAME = 'vxlapi64' if platform.architecture()[0] == '64bit' else 'vxlapi' -_xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) - -XL_BUS_TYPE_CAN = 0x00000001 - -XL_ERR_QUEUE_IS_EMPTY = 10 -XL_ERR_HW_NOT_PRESENT = 129 - -XL_RECEIVE_MSG = 1 -XL_CAN_EV_TAG_RX_OK = 1024 -XL_CAN_EV_TAG_TX_OK = 1028 -XL_TRANSMIT_MSG = 10 -XL_CAN_EV_TAG_TX_MSG = 1088 - -XL_CAN_EXT_MSG_ID = 0x80000000 -XL_CAN_MSG_FLAG_ERROR_FRAME = 0x01 -XL_CAN_MSG_FLAG_REMOTE_FRAME = 0x10 -XL_CAN_MSG_FLAG_TX_COMPLETED = 0x40 - -XL_CAN_TXMSG_FLAG_EDL = 0x0001 -XL_CAN_TXMSG_FLAG_BRS = 0x0002 -XL_CAN_TXMSG_FLAG_RTR = 0x0010 -XL_CAN_RXMSG_FLAG_EDL = 0x0001 -XL_CAN_RXMSG_FLAG_BRS = 0x0002 -XL_CAN_RXMSG_FLAG_ESI = 0x0004 -XL_CAN_RXMSG_FLAG_RTR = 0x0010 -XL_CAN_RXMSG_FLAG_EF = 0x0200 - -XL_CAN_STD = 1 -XL_CAN_EXT = 2 - -XLuint64 = ctypes.c_int64 -XLaccess = XLuint64 -XLhandle = ctypes.c_void_p - -MAX_MSG_LEN = 8 - -XL_CAN_MAX_DATA_LEN = 64 - -# current version -XL_INTERFACE_VERSION = 3 -XL_INTERFACE_VERSION_V4 = 4 - -XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 0x80000000 - -# structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG -class s_xl_can_msg(ctypes.Structure): - _fields_ = [('id', ctypes.c_ulong), ('flags', ctypes.c_ushort), - ('dlc', ctypes.c_ushort), ('res1', XLuint64), - ('data', ctypes.c_ubyte * MAX_MSG_LEN), ('res2', XLuint64)] - - - -class s_xl_can_ev_error(ctypes.Structure): - _fields_ = [('errorCode', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 95)] - -class s_xl_can_ev_chip_state(ctypes.Structure): - _fields_ = [('busStatus', ctypes.c_ubyte), ('txErrorCounter', ctypes.c_ubyte), - ('rxErrorCounter', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), - ('reserved0', ctypes.c_uint)] - -class s_xl_can_ev_sync_pulse(ctypes.Structure): - _fields_ = [('triggerSource', ctypes.c_uint), ('reserved', ctypes.c_uint), - ('time', XLuint64)] - -# BASIC bus message structure -class s_xl_tag_data(ctypes.Union): - _fields_ = [('msg', s_xl_can_msg)] - -# CAN FD messages -class s_xl_can_ev_rx_msg(ctypes.Structure): - _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), - ('crc', ctypes.c_uint), ('reserved1', ctypes.c_ubyte * 12), - ('totalBitCnt', ctypes.c_ushort), ('dlc', ctypes.c_ubyte), - ('reserved', ctypes.c_ubyte * 5), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] - -class s_xl_can_ev_tx_request(ctypes.Structure): - _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), - ('dlc', ctypes.c_ubyte),('txAttemptConf', ctypes.c_ubyte), - ('reserved', ctypes.c_ushort), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] - -class s_xl_can_tx_msg(ctypes.Structure): - _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), - ('dlc', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 7), - ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] - -class s_rxTagData(ctypes.Union): - _fields_ = [('canRxOkMsg', s_xl_can_ev_rx_msg), ('canTxOkMsg', s_xl_can_ev_rx_msg), - ('canTxRequest', s_xl_can_ev_tx_request),('canError', s_xl_can_ev_error), - ('canChipState', s_xl_can_ev_chip_state),('canSyncPulse', s_xl_can_ev_sync_pulse)] - -class s_txTagData(ctypes.Union): - _fields_ = [('canMsg', s_xl_can_tx_msg)] - -# BASIC events -XLeventTag = ctypes.c_ubyte - -class XLevent(ctypes.Structure): - _fields_ = [('tag', XLeventTag), ('chanIndex', ctypes.c_ubyte), - ('transId', ctypes.c_ushort), ('portHandle', ctypes.c_ushort), - ('flags', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte), - ('timeStamp', XLuint64), ('tagData', s_xl_tag_data)] - -# CAN FD events -class XLcanRxEvent(ctypes.Structure): - _fields_ = [('size',ctypes.c_int),('tag', ctypes.c_ushort), - ('chanIndex', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), - ('userHandle', ctypes.c_int),('flagsChip', ctypes.c_ushort), - ('reserved0', ctypes.c_ushort),('reserved1', XLuint64), - ('timeStamp', XLuint64),('tagData', s_rxTagData)] - -class XLcanTxEvent(ctypes.Structure): - _fields_ = [('tag', ctypes.c_ushort), ('transId', ctypes.c_ushort), - ('chanIndex', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 3), - ('tagData', s_txTagData)] - -# CAN FD configuration structure -class XLcanFdConf(ctypes.Structure): - _fields_ = [('arbitrationBitRate', ctypes.c_uint), ('sjwAbr', ctypes.c_uint), - ('tseg1Abr', ctypes.c_uint), ('tseg2Abr', ctypes.c_uint), - ('dataBitRate', ctypes.c_uint), ('sjwDbr', ctypes.c_uint), - ('tseg1Dbr', ctypes.c_uint), ('tseg2Dbr', ctypes.c_uint), - ('reserved', ctypes.c_uint * 2)] - -class XLchannelConfig(ctypes.Structure): - _pack_ = 1 - _fields_ = [ - ('name', ctypes.c_char * 32), - ('hwType', ctypes.c_ubyte), - ('hwIndex', ctypes.c_ubyte), - ('hwChannel', ctypes.c_ubyte), - ('transceiverType', ctypes.c_ushort), - ('transceiverState', ctypes.c_ushort), - ('configError', ctypes.c_ushort), - ('channelIndex', ctypes.c_ubyte), - ('channelMask', XLuint64), - ('channelCapabilities', ctypes.c_uint), - ('channelBusCapabilities', ctypes.c_uint), - ('isOnBus', ctypes.c_ubyte), - ('connectedBusType', ctypes.c_uint), - ('busParams', ctypes.c_ubyte * 32), - ('_doNotUse', ctypes.c_uint), - ('driverVersion', ctypes.c_uint), - ('interfaceVersion', ctypes.c_uint), - ('raw_data', ctypes.c_uint * 10), - ('serialNumber', ctypes.c_uint), - ('articleNumber', ctypes.c_uint), - ('transceiverName', ctypes.c_char * 32), - ('specialCabFlags', ctypes.c_uint), - ('dominantTimeout', ctypes.c_uint), - ('dominantRecessiveDelay', ctypes.c_ubyte), - ('recessiveDominantDelay', ctypes.c_ubyte), - ('connectionInfo', ctypes.c_ubyte), - ('currentlyAvailableTimestamps', ctypes.c_ubyte), - ('minimalSupplyVoltage', ctypes.c_ushort), - ('maximalSupplyVoltage', ctypes.c_ushort), - ('maximalBaudrate', ctypes.c_uint), - ('fpgaCoreCapabilities', ctypes.c_ubyte), - ('specialDeviceStatus', ctypes.c_ubyte), - ('channelBusActiveCapabilities', ctypes.c_ushort), - ('breakOffset', ctypes.c_ushort), - ('delimiterOffset', ctypes.c_ushort), - ('reserved', ctypes.c_uint * 3) - ] - -class XLdriverConfig(ctypes.Structure): - _fields_ = [ - ('dllVersion', ctypes.c_uint), - ('channelCount', ctypes.c_uint), - ('reserved', ctypes.c_uint * 10), - ('channel', XLchannelConfig * 64) - ] - -# driver status -XLstatus = ctypes.c_short - -# porthandle -XL_INVALID_PORTHANDLE = (-1) -XLportHandle = ctypes.c_long - - -def check_status(result, function, arguments): - if result > 0: - raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) - return result - - -xlGetDriverConfig = _xlapi_dll.xlGetDriverConfig -xlGetDriverConfig.argtypes = [ctypes.POINTER(XLdriverConfig)] -xlGetDriverConfig.restype = XLstatus -xlGetDriverConfig.errcheck = check_status - -xlOpenDriver = _xlapi_dll.xlOpenDriver -xlOpenDriver.argtypes = [] -xlOpenDriver.restype = XLstatus -xlOpenDriver.errcheck = check_status - -xlCloseDriver = _xlapi_dll.xlCloseDriver -xlCloseDriver.argtypes = [] -xlCloseDriver.restype = XLstatus -xlCloseDriver.errcheck = check_status - -xlGetApplConfig = _xlapi_dll.xlGetApplConfig -xlGetApplConfig.argtypes = [ - ctypes.c_char_p, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint), ctypes.c_uint -] -xlGetApplConfig.restype = XLstatus -xlGetApplConfig.errcheck = check_status - -xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex -xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] -xlGetChannelIndex.restype = ctypes.c_int - -xlGetChannelMask = _xlapi_dll.xlGetChannelMask -xlGetChannelMask.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] -xlGetChannelMask.restype = XLaccess - -xlOpenPort = _xlapi_dll.xlOpenPort -xlOpenPort.argtypes = [ - ctypes.POINTER(XLportHandle), ctypes.c_char_p, XLaccess, - ctypes.POINTER(XLaccess), ctypes.c_uint, ctypes.c_uint, ctypes.c_uint -] -xlOpenPort.restype = XLstatus -xlOpenPort.errcheck = check_status - -xlGetSyncTime = _xlapi_dll.xlGetSyncTime -xlGetSyncTime.argtypes = [XLportHandle, ctypes.POINTER(XLuint64)] -xlGetSyncTime.restype = XLstatus -xlGetSyncTime.errcheck = check_status - -xlClosePort = _xlapi_dll.xlClosePort -xlClosePort.argtypes = [XLportHandle] -xlClosePort.restype = XLstatus -xlClosePort.errcheck = check_status - -xlSetNotification = _xlapi_dll.xlSetNotification -xlSetNotification.argtypes = [XLportHandle, ctypes.POINTER(XLhandle), - ctypes.c_int] -xlSetNotification.restype = XLstatus -xlSetNotification.errcheck = check_status - -xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode -xlCanSetChannelMode.argtypes = [ - XLportHandle, XLaccess, ctypes.c_int, ctypes.c_int -] -xlCanSetChannelMode.restype = XLstatus -xlCanSetChannelMode.errcheck = check_status - -xlActivateChannel = _xlapi_dll.xlActivateChannel -xlActivateChannel.argtypes = [ - XLportHandle, XLaccess, ctypes.c_uint, ctypes.c_uint -] -xlActivateChannel.restype = XLstatus -xlActivateChannel.errcheck = check_status - -xlDeactivateChannel = _xlapi_dll.xlDeactivateChannel -xlDeactivateChannel.argtypes = [XLportHandle, XLaccess] -xlDeactivateChannel.restype = XLstatus -xlDeactivateChannel.errcheck = check_status - -xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration -xlCanFdSetConfiguration.argtypes = [XLportHandle, XLaccess, ctypes.POINTER(XLcanFdConf)] -xlCanFdSetConfiguration.restype = XLstatus -xlCanFdSetConfiguration.errcheck = check_status - -xlReceive = _xlapi_dll.xlReceive -xlReceive.argtypes = [ - XLportHandle, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLevent) -] -xlReceive.restype = XLstatus -xlReceive.errcheck = check_status - -xlCanReceive = _xlapi_dll.xlCanReceive -xlCanReceive.argtypes = [ - XLportHandle, ctypes.POINTER(XLcanRxEvent) -] -xlCanReceive.restype = XLstatus -xlCanReceive.errcheck = check_status - -xlGetErrorString = _xlapi_dll.xlGetErrorString -xlGetErrorString.argtypes = [XLstatus] -xlGetErrorString.restype = ctypes.c_char_p - -xlCanSetChannelBitrate = _xlapi_dll.xlCanSetChannelBitrate -xlCanSetChannelBitrate.argtypes = [XLportHandle, XLaccess, ctypes.c_ulong] -xlCanSetChannelBitrate.restype = XLstatus -xlCanSetChannelBitrate.errcheck = check_status - -xlCanTransmit = _xlapi_dll.xlCanTransmit -xlCanTransmit.argtypes = [ - XLportHandle, XLaccess, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLevent) -] -xlCanTransmit.restype = XLstatus -xlCanTransmit.errcheck = check_status - -xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx -xlCanTransmitEx.argtypes = [ - XLportHandle, XLaccess, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLcanTxEvent) -] -xlCanTransmitEx.restype = XLstatus -xlCanTransmitEx.errcheck = check_status - -xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue -xlCanFlushTransmitQueue.argtypes = [XLportHandle, XLaccess] -xlCanFlushTransmitQueue.restype = XLstatus -xlCanFlushTransmitQueue.errcheck = check_status - -xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance -xlCanSetChannelAcceptance.argtypes = [ - XLportHandle, XLaccess, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_uint] -xlCanSetChannelAcceptance.restype = XLstatus -xlCanSetChannelAcceptance.errcheck = check_status - -xlCanResetAcceptance = _xlapi_dll.xlCanResetAcceptance -xlCanResetAcceptance.argtypes = [XLportHandle, XLaccess, ctypes.c_uint] -xlCanResetAcceptance.restype = XLstatus -xlCanResetAcceptance.errcheck = check_status diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py new file mode 100644 index 000000000..8c55bf058 --- /dev/null +++ b/can/interfaces/vector/xlclass.py @@ -0,0 +1,280 @@ +""" +Definition of data types and structures for vxlapi. + +Authors: Julien Grave , Christian Sandberg +""" + +# Import Standard Python Modules +# ============================== +import ctypes + +# Vector XL API Definitions +# ========================= +from . import xldefine + +XLuint64 = ctypes.c_int64 +XLaccess = XLuint64 +XLhandle = ctypes.c_void_p +XLstatus = ctypes.c_short +XLportHandle = ctypes.c_long +XLeventTag = ctypes.c_ubyte + +# structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG +class s_xl_can_msg(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_ulong), + ("flags", ctypes.c_ushort), + ("dlc", ctypes.c_ushort), + ("res1", XLuint64), + ("data", ctypes.c_ubyte * xldefine.MAX_MSG_LEN), + ("res2", XLuint64), + ] + + +class s_xl_can_ev_error(ctypes.Structure): + _fields_ = [("errorCode", ctypes.c_ubyte), ("reserved", ctypes.c_ubyte * 95)] + + +class s_xl_chip_state(ctypes.Structure): + _fields_ = [ + ("busStatus", ctypes.c_ubyte), + ("txErrorCounter", ctypes.c_ubyte), + ("rxErrorCounter", ctypes.c_ubyte), + ] + + +class s_xl_can_ev_chip_state(ctypes.Structure): + _fields_ = [ + ("busStatus", ctypes.c_ubyte), + ("txErrorCounter", ctypes.c_ubyte), + ("rxErrorCounter", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("reserved0", ctypes.c_uint), + ] + + +class s_xl_can_ev_sync_pulse(ctypes.Structure): + _fields_ = [ + ("triggerSource", ctypes.c_uint), + ("reserved", ctypes.c_uint), + ("time", XLuint64), + ] + + +# BASIC bus message structure +class s_xl_tag_data(ctypes.Union): + _fields_ = [("msg", s_xl_can_msg), ("chipState", s_xl_chip_state)] + + +# CAN FD messages +class s_xl_can_ev_rx_msg(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("crc", ctypes.c_uint), + ("reserved1", ctypes.c_ubyte * 12), + ("totalBitCnt", ctypes.c_ushort), + ("dlc", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 5), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_xl_can_ev_tx_request(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("dlc", ctypes.c_ubyte), + ("txAttemptConf", ctypes.c_ubyte), + ("reserved", ctypes.c_ushort), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_xl_can_tx_msg(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("dlc", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 7), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_rxTagData(ctypes.Union): + _fields_ = [ + ("canRxOkMsg", s_xl_can_ev_rx_msg), + ("canTxOkMsg", s_xl_can_ev_rx_msg), + ("canTxRequest", s_xl_can_ev_tx_request), + ("canError", s_xl_can_ev_error), + ("canChipState", s_xl_can_ev_chip_state), + ("canSyncPulse", s_xl_can_ev_sync_pulse), + ] + + +class s_txTagData(ctypes.Union): + _fields_ = [("canMsg", s_xl_can_tx_msg)] + + +class XLevent(ctypes.Structure): + _fields_ = [ + ("tag", XLeventTag), + ("chanIndex", ctypes.c_ubyte), + ("transId", ctypes.c_ushort), + ("portHandle", ctypes.c_ushort), + ("flags", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("timeStamp", XLuint64), + ("tagData", s_xl_tag_data), + ] + + +# CAN FD events +class XLcanRxEvent(ctypes.Structure): + _fields_ = [ + ("size", ctypes.c_int), + ("tag", ctypes.c_ushort), + ("chanIndex", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("userHandle", ctypes.c_int), + ("flagsChip", ctypes.c_ushort), + ("reserved0", ctypes.c_ushort), + ("reserved1", XLuint64), + ("timeStamp", XLuint64), + ("tagData", s_rxTagData), + ] + + +class XLcanTxEvent(ctypes.Structure): + _fields_ = [ + ("tag", ctypes.c_ushort), + ("transId", ctypes.c_ushort), + ("chanIndex", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 3), + ("tagData", s_txTagData), + ] + + +# CAN configuration structure +class XLchipParams(ctypes.Structure): + _fields_ = [ + ("bitRate", ctypes.c_ulong), + ("sjw", ctypes.c_ubyte), + ("tseg1", ctypes.c_ubyte), + ("tseg2", ctypes.c_ubyte), + ("sam", ctypes.c_ubyte), + ] + + +# CAN FD configuration structure +class XLcanFdConf(ctypes.Structure): + _fields_ = [ + ("arbitrationBitRate", ctypes.c_uint), + ("sjwAbr", ctypes.c_uint), + ("tseg1Abr", ctypes.c_uint), + ("tseg2Abr", ctypes.c_uint), + ("dataBitRate", ctypes.c_uint), + ("sjwDbr", ctypes.c_uint), + ("tseg1Dbr", ctypes.c_uint), + ("tseg2Dbr", ctypes.c_uint), + ("reserved", ctypes.c_ubyte), + ("options", ctypes.c_ubyte), + ("reserved1", ctypes.c_ubyte * 2), + ("reserved2", ctypes.c_ubyte), + ] + + +# channel configuration structures +class s_xl_bus_params_data_can(ctypes.Structure): + _fields_ = [ + ("bitRate", ctypes.c_uint), + ("sjw", ctypes.c_ubyte), + ("tseg1", ctypes.c_ubyte), + ("tseg2", ctypes.c_ubyte), + ("sam", ctypes.c_ubyte), + ("outputMode", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 7), + ("canOpMode", ctypes.c_ubyte), + ] + + +class s_xl_bus_params_data_canfd(ctypes.Structure): + _fields_ = [ + ("arbitrationBitRate", ctypes.c_uint), + ("sjwAbr", ctypes.c_ubyte), + ("tseg1Abr", ctypes.c_ubyte), + ("tseg2Abr", ctypes.c_ubyte), + ("samAbr", ctypes.c_ubyte), + ("outputMode", ctypes.c_ubyte), + ("sjwDbr", ctypes.c_ubyte), + ("tseg1Dbr", ctypes.c_ubyte), + ("tseg2Dbr", ctypes.c_ubyte), + ("dataBitRate", ctypes.c_uint), + ("canOpMode", ctypes.c_ubyte), + ] + + +class s_xl_bus_params_data(ctypes.Union): + _fields_ = [ + ("can", s_xl_bus_params_data_can), + ("canFD", s_xl_bus_params_data_canfd), + ("most", ctypes.c_ubyte * 12), + ("flexray", ctypes.c_ubyte * 12), + ("ethernet", ctypes.c_ubyte * 12), + ("a429", ctypes.c_ubyte * 28), + ] + + +class XLbusParams(ctypes.Structure): + _fields_ = [("busType", ctypes.c_uint), ("data", s_xl_bus_params_data)] + + +class XLchannelConfig(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("name", ctypes.c_char * 32), + ("hwType", ctypes.c_ubyte), + ("hwIndex", ctypes.c_ubyte), + ("hwChannel", ctypes.c_ubyte), + ("transceiverType", ctypes.c_ushort), + ("transceiverState", ctypes.c_ushort), + ("configError", ctypes.c_ushort), + ("channelIndex", ctypes.c_ubyte), + ("channelMask", XLuint64), + ("channelCapabilities", ctypes.c_uint), + ("channelBusCapabilities", ctypes.c_uint), + ("isOnBus", ctypes.c_ubyte), + ("connectedBusType", ctypes.c_uint), + ("busParams", XLbusParams), + ("_doNotUse", ctypes.c_uint), + ("driverVersion", ctypes.c_uint), + ("interfaceVersion", ctypes.c_uint), + ("raw_data", ctypes.c_uint * 10), + ("serialNumber", ctypes.c_uint), + ("articleNumber", ctypes.c_uint), + ("transceiverName", ctypes.c_char * 32), + ("specialCabFlags", ctypes.c_uint), + ("dominantTimeout", ctypes.c_uint), + ("dominantRecessiveDelay", ctypes.c_ubyte), + ("recessiveDominantDelay", ctypes.c_ubyte), + ("connectionInfo", ctypes.c_ubyte), + ("currentlyAvailableTimestamps", ctypes.c_ubyte), + ("minimalSupplyVoltage", ctypes.c_ushort), + ("maximalSupplyVoltage", ctypes.c_ushort), + ("maximalBaudrate", ctypes.c_uint), + ("fpgaCoreCapabilities", ctypes.c_ubyte), + ("specialDeviceStatus", ctypes.c_ubyte), + ("channelBusActiveCapabilities", ctypes.c_ushort), + ("breakOffset", ctypes.c_ushort), + ("delimiterOffset", ctypes.c_ushort), + ("reserved", ctypes.c_uint * 3), + ] + + +class XLdriverConfig(ctypes.Structure): + _fields_ = [ + ("dllVersion", ctypes.c_uint), + ("channelCount", ctypes.c_uint), + ("reserved", ctypes.c_uint * 10), + ("channel", XLchannelConfig * 64), + ] diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py new file mode 100644 index 000000000..1d130624b --- /dev/null +++ b/can/interfaces/vector/xldefine.py @@ -0,0 +1,170 @@ +""" +Definition of constants for vxlapi. +""" + +# Import Python Modules +# ============================== +from enum import Enum + + +MAX_MSG_LEN = 8 +XL_CAN_MAX_DATA_LEN = 64 +XL_INVALID_PORTHANDLE = -1 + + +class XL_AC_Flags(Enum): + XL_ACTIVATE_NONE = 0 + XL_ACTIVATE_RESET_CLOCK = 8 + + +class XL_AcceptanceFilter(Enum): + XL_CAN_STD = 1 + XL_CAN_EXT = 2 + + +class XL_BusCapabilities(Enum): + XL_BUS_COMPATIBLE_CAN = 1 + XL_BUS_ACTIVE_CAP_CAN = 65536 + + +class XL_BusStatus(Enum): + XL_CHIPSTAT_BUSOFF = 1 + XL_CHIPSTAT_ERROR_PASSIVE = 2 + XL_CHIPSTAT_ERROR_WARNING = 4 + XL_CHIPSTAT_ERROR_ACTIVE = 8 + + +class XL_BusTypes(Enum): + XL_BUS_TYPE_NONE = 0 + XL_BUS_TYPE_CAN = 1 + + +class XL_CANFD_BusParams_CanOpMode(Enum): + XL_BUS_PARAMS_CANOPMODE_CAN20 = 1 + XL_BUS_PARAMS_CANOPMODE_CANFD = 2 + XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8 + + +class XL_CANFD_ConfigOptions(Enum): + CANFD_CONFOPT_NO_ISO = 8 + + +class XL_CANFD_RX_EV_ERROR_errorCode(Enum): + XL_CAN_ERRC_BIT_ERROR = 1 + XL_CAN_ERRC_FORM_ERROR = 2 + XL_CAN_ERRC_STUFF_ERROR = 3 + XL_CAN_ERRC_OTHER_ERROR = 4 + XL_CAN_ERRC_CRC_ERROR = 5 + XL_CAN_ERRC_ACK_ERROR = 6 + XL_CAN_ERRC_NACK_ERROR = 7 + XL_CAN_ERRC_OVLD_ERROR = 8 + XL_CAN_ERRC_EXCPT_ERROR = 9 + + +class XL_CANFD_RX_EventTags(Enum): + XL_SYNC_PULSE = 11 + XL_CAN_EV_TAG_RX_OK = 1024 + XL_CAN_EV_TAG_RX_ERROR = 1025 + XL_CAN_EV_TAG_TX_ERROR = 1026 + XL_CAN_EV_TAG_TX_REQUEST = 1027 + XL_CAN_EV_TAG_TX_OK = 1028 + XL_CAN_EV_TAG_CHIP_STATE = 1033 + + +class XL_CANFD_RX_MessageFlags(Enum): + XL_CAN_RXMSG_FLAG_NONE = 0 + XL_CAN_RXMSG_FLAG_EDL = 1 + XL_CAN_RXMSG_FLAG_BRS = 2 + XL_CAN_RXMSG_FLAG_ESI = 4 + XL_CAN_RXMSG_FLAG_RTR = 16 + XL_CAN_RXMSG_FLAG_EF = 512 + XL_CAN_RXMSG_FLAG_ARB_LOST = 1024 + XL_CAN_RXMSG_FLAG_WAKEUP = 8192 + XL_CAN_RXMSG_FLAG_TE = 16384 + + +class XL_CANFD_TX_EventTags(Enum): + XL_CAN_EV_TAG_TX_MSG = 1088 + + +class XL_CANFD_TX_MessageFlags(Enum): + XL_CAN_TXMSG_FLAG_NONE = 0 + XL_CAN_TXMSG_FLAG_EDL = 1 + XL_CAN_TXMSG_FLAG_BRS = 2 + XL_CAN_TXMSG_FLAG_RTR = 16 + XL_CAN_TXMSG_FLAG_HIGHPRIO = 128 + XL_CAN_TXMSG_FLAG_WAKEUP = 512 + + +class XL_ChannelCapabilities(Enum): + XL_CHANNEL_FLAG_TIME_SYNC_RUNNING = 1 + XL_CHANNEL_FLAG_NO_HWSYNC_SUPPORT = 1024 + XL_CHANNEL_FLAG_SPDIF_CAPABLE = 16384 + XL_CHANNEL_FLAG_CANFD_BOSCH_SUPPORT = 536870912 + XL_CHANNEL_FLAG_CMACTLICENSE_SUPPORT = 1073741824 + XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 2147483648 + + +class XL_EventTags(Enum): + XL_NO_COMMAND = 0 + XL_RECEIVE_MSG = 1 + XL_CHIP_STATE = 4 + XL_TRANSCEIVER = 6 + XL_TIMER = 8 + XL_TRANSMIT_MSG = 10 + XL_SYNC_PULSE = 11 + XL_APPLICATION_NOTIFICATION = 15 + + +class XL_InterfaceVersion(Enum): + XL_INTERFACE_VERSION_V2 = 2 + XL_INTERFACE_VERSION_V3 = 3 + XL_INTERFACE_VERSION = XL_INTERFACE_VERSION_V3 + XL_INTERFACE_VERSION_V4 = 4 + + +class XL_MessageFlags(Enum): + XL_CAN_MSG_FLAG_NONE = 0 + XL_CAN_MSG_FLAG_ERROR_FRAME = 1 + XL_CAN_MSG_FLAG_OVERRUN = 2 + XL_CAN_MSG_FLAG_NERR = 4 + XL_CAN_MSG_FLAG_WAKEUP = 8 + XL_CAN_MSG_FLAG_REMOTE_FRAME = 16 + XL_CAN_MSG_FLAG_RESERVED_1 = 32 + XL_CAN_MSG_FLAG_TX_COMPLETED = 64 + XL_CAN_MSG_FLAG_TX_REQUEST = 128 + XL_CAN_MSG_FLAG_SRR_BIT_DOM = 512 + XL_EVENT_FLAG_OVERRUN = 1 + + +class XL_MessageFlagsExtended(Enum): + XL_CAN_EXT_MSG_ID = 2147483648 + + +class XL_OutputMode(Enum): + XL_OUTPUT_MODE_SILENT = 0 + XL_OUTPUT_MODE_NORMAL = 1 + XL_OUTPUT_MODE_TX_OFF = 2 + XL_OUTPUT_MODE_SJA_1000_SILENT = 3 + + +class XL_Sizes(Enum): + XL_MAX_LENGTH = 31 + XL_MAX_APPNAME = 32 + XL_MAX_NAME_LENGTH = 48 + XLEVENT_SIZE = 48 + XL_CONFIG_MAX_CHANNELS = 64 + XL_APPLCONFIG_MAX_CHANNELS = 256 + + +class XL_Status(Enum): + XL_SUCCESS = 0 + XL_PENDING = 1 + XL_ERR_QUEUE_IS_EMPTY = 10 + XL_ERR_HW_NOT_PRESENT = 129 + + +class XL_TimeSyncNewValue(Enum): + XL_SET_TIMESYNC_NO_CHANGE = 0 + XL_SET_TIMESYNC_ON = 1 + XL_SET_TIMESYNC_OFF = 2 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py new file mode 100644 index 000000000..7a361a29d --- /dev/null +++ b/can/interfaces/vector/xldriver.py @@ -0,0 +1,236 @@ +""" +Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. + +Authors: Julien Grave , Christian Sandberg +""" + +# Import Standard Python Modules +# ============================== +import ctypes +import logging +import platform +from .exceptions import VectorError + +# Define Module Logger +# ==================== +LOG = logging.getLogger(__name__) + +# Vector XL API Definitions +# ========================= +from . import xlclass + +# Load Windows DLL +DLL_NAME = "vxlapi64" if platform.architecture()[0] == "64bit" else "vxlapi" +_xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) + + +# ctypes wrapping for API functions +xlGetErrorString = _xlapi_dll.xlGetErrorString +xlGetErrorString.argtypes = [xlclass.XLstatus] +xlGetErrorString.restype = ctypes.c_char_p + + +def check_status(result, function, arguments): + if result > 0: + raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) + return result + + +xlGetDriverConfig = _xlapi_dll.xlGetDriverConfig +xlGetDriverConfig.argtypes = [ctypes.POINTER(xlclass.XLdriverConfig)] +xlGetDriverConfig.restype = xlclass.XLstatus +xlGetDriverConfig.errcheck = check_status + +xlOpenDriver = _xlapi_dll.xlOpenDriver +xlOpenDriver.argtypes = [] +xlOpenDriver.restype = xlclass.XLstatus +xlOpenDriver.errcheck = check_status + +xlCloseDriver = _xlapi_dll.xlCloseDriver +xlCloseDriver.argtypes = [] +xlCloseDriver.restype = xlclass.XLstatus +xlCloseDriver.errcheck = check_status + +xlGetApplConfig = _xlapi_dll.xlGetApplConfig +xlGetApplConfig.argtypes = [ + ctypes.c_char_p, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(ctypes.c_uint), + ctypes.c_uint, +] +xlGetApplConfig.restype = xlclass.XLstatus +xlGetApplConfig.errcheck = check_status + +xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex +xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] +xlGetChannelIndex.restype = ctypes.c_int + +xlGetChannelMask = _xlapi_dll.xlGetChannelMask +xlGetChannelMask.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] +xlGetChannelMask.restype = xlclass.XLaccess + +xlOpenPort = _xlapi_dll.xlOpenPort +xlOpenPort.argtypes = [ + ctypes.POINTER(xlclass.XLportHandle), + ctypes.c_char_p, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLaccess), + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, +] +xlOpenPort.restype = xlclass.XLstatus +xlOpenPort.errcheck = check_status + +xlGetSyncTime = _xlapi_dll.xlGetSyncTime +xlGetSyncTime.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLuint64)] +xlGetSyncTime.restype = xlclass.XLstatus +xlGetSyncTime.errcheck = check_status + +xlGetChannelTime = _xlapi_dll.xlGetChannelTime +xlGetChannelTime.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLuint64), +] +xlGetChannelTime.restype = xlclass.XLstatus +xlGetChannelTime.errcheck = check_status + +xlClosePort = _xlapi_dll.xlClosePort +xlClosePort.argtypes = [xlclass.XLportHandle] +xlClosePort.restype = xlclass.XLstatus +xlClosePort.errcheck = check_status + +xlSetNotification = _xlapi_dll.xlSetNotification +xlSetNotification.argtypes = [ + xlclass.XLportHandle, + ctypes.POINTER(xlclass.XLhandle), + ctypes.c_int, +] +xlSetNotification.restype = xlclass.XLstatus +xlSetNotification.errcheck = check_status + +xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode +xlCanSetChannelMode.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_int, + ctypes.c_int, +] +xlCanSetChannelMode.restype = xlclass.XLstatus +xlCanSetChannelMode.errcheck = check_status + +xlActivateChannel = _xlapi_dll.xlActivateChannel +xlActivateChannel.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_uint, + ctypes.c_uint, +] +xlActivateChannel.restype = xlclass.XLstatus +xlActivateChannel.errcheck = check_status + +xlDeactivateChannel = _xlapi_dll.xlDeactivateChannel +xlDeactivateChannel.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlDeactivateChannel.restype = xlclass.XLstatus +xlDeactivateChannel.errcheck = check_status + +xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration +xlCanFdSetConfiguration.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLcanFdConf), +] +xlCanFdSetConfiguration.restype = xlclass.XLstatus +xlCanFdSetConfiguration.errcheck = check_status + +xlReceive = _xlapi_dll.xlReceive +xlReceive.argtypes = [ + xlclass.XLportHandle, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLevent), +] +xlReceive.restype = xlclass.XLstatus +xlReceive.errcheck = check_status + +xlCanReceive = _xlapi_dll.xlCanReceive +xlCanReceive.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLcanRxEvent)] +xlCanReceive.restype = xlclass.XLstatus +xlCanReceive.errcheck = check_status + +xlCanSetChannelBitrate = _xlapi_dll.xlCanSetChannelBitrate +xlCanSetChannelBitrate.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_ulong, +] +xlCanSetChannelBitrate.restype = xlclass.XLstatus +xlCanSetChannelBitrate.errcheck = check_status + +xlCanSetChannelParams = _xlapi_dll.xlCanSetChannelParams +xlCanSetChannelParams.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLchipParams), +] +xlCanSetChannelParams.restype = xlclass.XLstatus +xlCanSetChannelParams.errcheck = check_status + +xlCanTransmit = _xlapi_dll.xlCanTransmit +xlCanTransmit.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLevent), +] +xlCanTransmit.restype = xlclass.XLstatus +xlCanTransmit.errcheck = check_status + +xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx +xlCanTransmitEx.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLcanTxEvent), +] +xlCanTransmitEx.restype = xlclass.XLstatus +xlCanTransmitEx.errcheck = check_status + +xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue +xlCanFlushTransmitQueue.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlCanFlushTransmitQueue.restype = xlclass.XLstatus +xlCanFlushTransmitQueue.errcheck = check_status + +xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance +xlCanSetChannelAcceptance.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_uint, +] +xlCanSetChannelAcceptance.restype = xlclass.XLstatus +xlCanSetChannelAcceptance.errcheck = check_status + +xlCanResetAcceptance = _xlapi_dll.xlCanResetAcceptance +xlCanResetAcceptance.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_uint] +xlCanResetAcceptance.restype = xlclass.XLstatus +xlCanResetAcceptance.errcheck = check_status + +xlCanRequestChipState = _xlapi_dll.xlCanRequestChipState +xlCanRequestChipState.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlCanRequestChipState.restype = xlclass.XLstatus +xlCanRequestChipState.errcheck = check_status + +xlCanSetChannelOutput = _xlapi_dll.xlCanSetChannelOutput +xlCanSetChannelOutput.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_char] +xlCanSetChannelOutput.restype = xlclass.XLstatus +xlCanSetChannelOutput.errcheck = check_status + +xlPopupHwConfig = _xlapi_dll.xlPopupHwConfig +xlPopupHwConfig.argtypes = [ctypes.c_char_p, ctypes.c_uint] +xlPopupHwConfig.restype = xlclass.XLstatus +xlPopupHwConfig.errcheck = check_status diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 6f24c73f2..937084095 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ This module implements an OS and hardware independent virtual CAN interface for testing purposes. @@ -7,25 +5,29 @@ Any VirtualBus instances connecting to the same channel and reside in the same process will receive the same messages. """ +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING +from can import typechecking from copy import deepcopy import logging import time -try: - import queue -except ImportError: - import Queue as queue +import queue from threading import RLock from random import randint -from can.bus import BusABC from can import CanError +from can.bus import BusABC +from can.message import Message logger = logging.getLogger(__name__) # Channels are lists of queues, one for each connection -channels = {} +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/common_issues.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime + channels: Dict[Optional[Any], List[queue.Queue[Message]]] = {} +else: + channels = {} channels_lock = RLock() @@ -47,8 +49,16 @@ class VirtualBus(BusABC): if a message is sent to 5 receivers with the timeout set to 1.0. """ - def __init__(self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs): - super(VirtualBus, self).__init__(channel=channel, receive_own_messages=receive_own_messages, **kwargs) + def __init__( + self, + channel: Any = None, + receive_own_messages: bool = False, + rx_queue_size: int = 0, + **kwargs: Any + ) -> None: + super().__init__( + channel=channel, receive_own_messages=receive_own_messages, **kwargs + ) # the channel identifier may be an arbitrary object self.channel_id = channel @@ -63,10 +73,10 @@ def __init__(self, channel=None, receive_own_messages=False, rx_queue_size=0, ** channels[self.channel_id] = [] self.channel = channels[self.channel_id] - self.queue = queue.Queue(rx_queue_size) + self.queue: queue.Queue[Message] = queue.Queue(rx_queue_size) self.channel.append(self.queue) - def _check_if_open(self): + def _check_if_open(self) -> None: """Raises CanError if the bus is not open. Has to be called in every method that accesses the bus. @@ -74,7 +84,9 @@ def _check_if_open(self): if not self._open: raise CanError("Operation on closed bus") - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: self._check_if_open() try: msg = self.queue.get(block=True, timeout=timeout) @@ -83,25 +95,27 @@ def _recv_internal(self, timeout): else: return msg, False - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: self._check_if_open() - msg_copy = deepcopy(msg) - msg_copy.timestamp = time.time() - msg_copy.channel = self.channel_id - + timestamp = time.time() # Add message to all listening on this channel all_sent = True for bus_queue in self.channel: - if bus_queue is not self.queue or self.receive_own_messages: - try: - bus_queue.put(msg_copy, block=True, timeout=timeout) - except queue.Full: - all_sent = False + if bus_queue is self.queue and not self.receive_own_messages: + continue + msg_copy = deepcopy(msg) + msg_copy.timestamp = timestamp + msg_copy.channel = self.channel_id + msg_copy.is_rx = bus_queue is not self.queue + try: + bus_queue.put(msg_copy, block=True, timeout=timeout) + except queue.Full: + all_sent = False if not all_sent: raise CanError("Could not send message to one or more recipients") - def shutdown(self): + def shutdown(self) -> None: self._check_if_open() self._open = False @@ -136,6 +150,6 @@ def _detect_available_configs(): available_channels += [extra] return [ - {'interface': 'virtual', 'channel': channel} + {"interface": "virtual", "channel": channel} for channel in available_channels ] diff --git a/can/io/__init__.py b/can/io/__init__.py index a0d89f28b..53389e91b 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -1,12 +1,8 @@ -# coding: utf-8 - """ Read and write CAN bus messages using a range of Readers and Writers based off the file extension. """ -from __future__ import absolute_import - # Generic from .logger import Logger from .player import LogReader, MessageSync diff --git a/can/io/asc.py b/can/io/asc.py index 3ed50f04a..1ad3acef6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Contains handling of ASC logging files. @@ -8,7 +6,8 @@ - under `test/data/logfile.asc` """ -from __future__ import absolute_import +from typing import cast, Any, Generator, IO, List, Optional, Tuple, Union +from can import typechecking from datetime import datetime import time @@ -22,91 +21,140 @@ CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF +BASE_HEX = 16 +BASE_DEC = 10 -logger = logging.getLogger('can.io.asc') +logger = logging.getLogger("can.io.asc") class ASCReader(BaseIOHandler): """ - Iterator of CAN messages from a ASC logging file. + Iterator of CAN messages from a ASC logging file. Meta data (comments, + bus statistics, J1939 Transport Protocol messages) is ignored. TODO: turn relative timestamps back to absolute form """ - def __init__(self, file): + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + base: str = "hex", + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text read mode, not binary read mode. + :param base: Select the base(hex or dec) of id and data. + If the header of the asc file contains base information, + this value will be overwritten. Default "hex". """ - super(ASCReader, self).__init__(file, mode='r') + super().__init__(file, mode="r") + + if not self.file: + raise ValueError("The given file cannot be None") + self.base = base @staticmethod - def _extract_can_id(str_can_id): - if str_can_id[-1:].lower() == 'x': + def _extract_can_id(str_can_id: str, base: int) -> Tuple[int, bool]: + if str_can_id[-1:].lower() == "x": is_extended = True - can_id = int(str_can_id[0:-1], 16) + can_id = int(str_can_id[0:-1], base) else: is_extended = False - can_id = int(str_can_id, 16) + can_id = int(str_can_id, base) return can_id, is_extended - def __iter__(self): + @staticmethod + def _check_base(base: str) -> int: + if base not in ["hex", "dec"]: + raise ValueError('base should be either "hex" or "dec"') + return BASE_DEC if base == "dec" else BASE_HEX + + def __iter__(self) -> Generator[Message, None, None]: + base = self._check_base(self.base) + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) for line in self.file: - #logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) + # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) + if line.split(" ")[0] == "base": + base = self._check_base(line.split(" ")[1]) temp = line.strip() if not temp or not temp[0].isdigit(): continue - + is_fd = False + is_rx = True try: - timestamp, channel, dummy = temp.split(None, 2) # , frameType, dlc, frameData + timestamp, channel, dummy = temp.split( + None, 2 + ) # , frameType, dlc, frameData + if channel == "CANFD": + timestamp, _, channel, direction, dummy = temp.split(None, 4) + is_fd = True + is_rx = direction == "Rx" except ValueError: # we parsed an empty comment continue - timestamp = float(timestamp) try: # See ASCWriter channel = int(channel) - 1 except ValueError: pass - - if dummy.strip()[0:10].lower() == 'errorframe': - msg = Message(timestamp=timestamp, is_error_frame=True, - channel=channel) + if dummy.strip()[0:10].lower() == "errorframe": + msg = Message(timestamp=timestamp, is_error_frame=True, channel=channel) yield msg - - elif not isinstance(channel, int) or dummy.strip()[0:10].lower() == 'statistic:': + elif ( + not isinstance(channel, int) + or dummy.strip()[0:10].lower() == "statistic:" + or dummy.split(None, 1)[0] == "J1939TP" + ): pass - - elif dummy[-1:].lower() == 'r': - can_id_str, _ = dummy.split(None, 1) - can_id_num, is_extended_id = self._extract_can_id(can_id_str) - msg = Message(timestamp=timestamp, - arbitration_id=can_id_num & CAN_ID_MASK, - is_extended_id=is_extended_id, - is_remote_frame=True, - channel=channel) + elif dummy[-1:].lower() == "r": + can_id_str, direction, _ = dummy.split(None, 2) + can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) + msg = Message( + timestamp=timestamp, + arbitration_id=can_id_num & CAN_ID_MASK, + is_extended_id=is_extended_id, + is_remote_frame=True, + is_rx=direction == "Rx", + channel=channel, + ) yield msg - else: + brs = None + esi = None + data_length = 0 try: - # this only works if dlc > 0 and thus data is availabe - can_id_str, _, _, dlc, data = dummy.split(None, 4) + # this only works if dlc > 0 and thus data is available + if not is_fd: + can_id_str, direction, _, dlc, data = dummy.split(None, 4) + is_rx = direction == "Rx" + else: + can_id_str, frame_name, brs, esi, dlc, data_length, data = dummy.split( + None, 6 + ) + if frame_name.isdigit(): + # Empty frame_name + can_id_str, brs, esi, dlc, data_length, data = dummy.split( + None, 5 + ) except ValueError: # but if not, we only want to get the stuff up to the dlc - can_id_str, _, _, dlc = dummy.split(None, 3) + can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually - data = '' - - dlc = int(dlc) + data = "" + dlc = int(dlc, base) + if is_fd: + # For fd frames, dlc and data length might not be equal and + # data_length is the actual size of the data + dlc = int(data_length) frame = bytearray() data = data.split() for byte in data[0:dlc]: - frame.append(int(byte, 16)) - - can_id_num, is_extended_id = self._extract_can_id(can_id_str) + frame.append(int(byte, base)) + can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) yield Message( timestamp=timestamp, @@ -115,9 +163,12 @@ def __iter__(self): is_remote_frame=False, dlc=dlc, data=frame, - channel=channel + is_fd=is_fd, + is_rx=is_rx, + channel=channel, + bitrate_switch=is_fd and brs == "1", + error_state_indicator=is_fd and esi == "1", ) - self.stop() @@ -130,11 +181,36 @@ class ASCWriter(BaseIOHandler, Listener): It the first message does not have a timestamp, it is set to zero. """ - FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" - FORMAT_DATE = "%a %b %m %I:%M:%S.{} %p %Y" + FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}" + FORMAT_MESSAGE_FD = " ".join( + [ + "CANFD", + "{channel:>3}", + "{dir:<4}", + "{id:>8} {symbolic_name:>32}", + "{brs}", + "{esi}", + "{dlc}", + "{data_length:>2}", + "{data}", + "{message_duration:>8}", + "{message_length:>4}", + "{flags:>8X}", + "{crc:>8}", + "{bit_timing_conf_arb:>8}", + "{bit_timing_conf_data:>8}", + "{bit_timing_conf_ext_arb:>8}", + "{bit_timing_conf_ext_data:>8}", + ] + ) + FORMAT_DATE = "%a %b %d %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" - def __init__(self, file, channel=1): + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + channel: int = 1, + ) -> None: """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text @@ -142,84 +218,115 @@ def __init__(self, file, channel=1): :param channel: a default channel to use when the message does not have a channel set """ - super(ASCWriter, self).__init__(file, mode='w') + super().__init__(file, mode="w") + if not self.file: + raise ValueError("The given file cannot be None") + self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") + now = datetime.now().strftime("%a %b %d %I:%M:%S.%f %p %Y") self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") # the last part is written with the timestamp of the first message self.header_written = False - self.last_timestamp = None - self.started = None + self.last_timestamp = 0.0 + self.started = 0.0 - def stop(self): + def stop(self) -> None: + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) if not self.file.closed: self.file.write("End TriggerBlock\n") - super(ASCWriter, self).stop() + super().stop() - def log_event(self, message, timestamp=None): + def log_event(self, message: str, timestamp: Optional[float] = None) -> None: """Add a message to the log file. - :param str message: an arbitrary message - :param float timestamp: the absolute timestamp of the event + :param message: an arbitrary message + :param timestamp: the absolute timestamp of the event """ - if not message: # if empty or None + if not message: # if empty or None logger.debug("ASCWriter: ignoring empty message") return + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) # this is the case for the very first message: if not self.header_written: - self.last_timestamp = (timestamp or 0.0) + self.last_timestamp = timestamp or 0.0 self.started = self.last_timestamp - mlsec = repr(self.last_timestamp).split('.')[1][:3] - formatted_date = time.strftime(self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp)) + mlsec = repr(self.last_timestamp).split(".")[1][:3] + formatted_date = time.strftime( + self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) + ) self.file.write("Begin Triggerblock %s\n" % formatted_date) self.header_written = True - self.log_event("Start of measurement") # caution: this is a recursive call! - + self.log_event("Start of measurement") # caution: this is a recursive call! # Use last known timestamp if unknown if timestamp is None: timestamp = self.last_timestamp - # turn into relative timestamps if necessary if timestamp >= self.started: timestamp -= self.started - line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message) self.file.write(line) - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: if msg.is_error_frame: self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) return - if msg.is_remote_frame: - dtype = 'r' - data = [] + dtype = "r" + data: List[str] = [] else: dtype = "d {}".format(msg.dlc) data = ["{:02X}".format(byte) for byte in msg.data] - arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: - arb_id += 'x' - + arb_id += "x" channel = channel2int(msg.channel) if channel is None: channel = self.channel else: # Many interfaces start channel numbering at 0 which is invalid channel += 1 - - serialized = self.FORMAT_MESSAGE.format(channel=channel, - id=arb_id, - dtype=dtype, - data=' '.join(data)) - + if msg.is_fd: + flags = 0 + flags |= 1 << 12 + if msg.bitrate_switch: + flags |= 1 << 13 + if msg.error_state_indicator: + flags |= 1 << 14 + serialized = self.FORMAT_MESSAGE_FD.format( + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + symbolic_name="", + brs=1 if msg.bitrate_switch else 0, + esi=1 if msg.error_state_indicator else 0, + dlc=msg.dlc, + data_length=len(data), + data=" ".join(data), + message_duration=0, + message_length=0, + flags=flags, + crc=0, + bit_timing_conf_arb=0, + bit_timing_conf_data=0, + bit_timing_conf_ext_arb=0, + bit_timing_conf_ext_data=0, + ) + else: + serialized = self.FORMAT_MESSAGE.format( + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dtype=dtype, + data=" ".join(data), + ) self.log_event(serialized, msg.timestamp) diff --git a/can/io/blf.py b/can/io/blf.py index d162fdebc..064a95f7d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Implements support for BLF (Binary Logging Format) which is a proprietary CAN log format from Vector Informatik GmbH (Germany). @@ -14,13 +12,12 @@ objects types. """ -from __future__ import absolute_import - import struct import zlib import datetime import time import logging +from typing import List from can.message import Message from can.listener import Listener @@ -30,12 +27,9 @@ class BLFParseError(Exception): """BLF file could not be parsed correctly.""" - pass -LOG = logging.getLogger(__name__) -# 0 = unknown, 2 = CANoe -APPLICATION_ID = 5 +LOG = logging.getLogger(__name__) # signature ("LOGG"), header size, # application ID, application major, application minor, application build, @@ -53,8 +47,8 @@ class BLFParseError(Exception): # flags, client index, object version, timestamp OBJ_HEADER_V1_STRUCT = struct.Struct(" len(data): - # Object continues in next log container - break - pos += OBJ_HEADER_BASE_STRUCT.size - - # Read rest of header - header_version = header[2] - if header_version == 1: - flags, _, _, timestamp = OBJ_HEADER_V1_STRUCT.unpack_from(data, pos) - pos += OBJ_HEADER_V1_STRUCT.size - elif header_version == 2: - flags, _, _, timestamp, _ = OBJ_HEADER_V2_STRUCT.unpack_from(data, pos) - pos += OBJ_HEADER_V2_STRUCT.size - else: - # Unknown header version - LOG.warning("Unknown object header version (%d)", header_version) - pos = next_pos - continue - - if flags == TIME_TEN_MICS: - factor = 10 * 1e-6 - else: - factor = 1e-9 - timestamp = timestamp * factor + self.start_timestamp - - obj_type = header[4] - # Both CAN message types have the same starting content - if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): - (channel, flags, dlc, can_id, - can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) - msg = Message(timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1) - yield msg - elif obj_type == CAN_FD_MESSAGE: - (channel, flags, dlc, can_id, _, _, fd_flags, - _, can_data) = CAN_FD_MSG_STRUCT.unpack_from(data, pos) - length = dlc2len(dlc) - msg = Message(timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - is_fd=bool(fd_flags & EDL), - bitrate_switch=bool(fd_flags & BRS), - error_state_indicator=bool(fd_flags & ESI), - dlc=length, - data=can_data[:length], - channel=channel - 1) - yield msg - elif obj_type == CAN_FD_MESSAGE_64: - ( - channel, dlc, _, _, can_id, _, fd_flags - ) = CAN_FD_MSG_64_STRUCT.unpack_from(data, pos)[:7] - length = dlc2len(dlc) - can_data = struct.unpack_from( - "<{}s".format(length), - data, - pos + CAN_FD_MSG_64_STRUCT.size - )[0] - msg = Message( - timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(fd_flags & REMOTE_FLAG_64), - is_fd=bool(fd_flags & EDL_64), - bitrate_switch=bool(fd_flags & BRS_64), - error_state_indicator=bool(fd_flags & ESI_64), - dlc=length, - data=can_data[:length], - channel=channel - 1 - ) - yield msg - elif obj_type == CAN_ERROR_EXT: - (channel, _, _, _, _, dlc, _, can_id, _, - can_data) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) - msg = Message(timestamp=timestamp, - is_error_frame=True, - is_extended_id=bool(can_id & CAN_MSG_EXT), - arbitration_id=can_id & 0x1FFFFFFF, - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1) - yield msg - # else: - # LOG.warning("Unknown object type (%d)", obj_type) - - pos = next_pos - - # save the remaining data that could not be processed - tail = data[pos:] + def _parse_container(self, data): + if self._tail: + data = b"".join((self._tail, data)) + try: + yield from self._parse_data(data) + except struct.error: + # There was not enough data in the container to unpack a struct + pass + # Save the remaining data that could not be processed + self._tail = data[self._pos :] + + def _parse_data(self, data): + """Optimized inner loop by making local copies of global variables + and class members and hardcoding some values.""" + unpack_obj_header_base = OBJ_HEADER_BASE_STRUCT.unpack_from + obj_header_base_size = OBJ_HEADER_BASE_STRUCT.size + unpack_obj_header_v1 = OBJ_HEADER_V1_STRUCT.unpack_from + obj_header_v1_size = OBJ_HEADER_V1_STRUCT.size + unpack_obj_header_v2 = OBJ_HEADER_V2_STRUCT.unpack_from + obj_header_v2_size = OBJ_HEADER_V2_STRUCT.size + unpack_can_msg = CAN_MSG_STRUCT.unpack_from + unpack_can_fd_msg = CAN_FD_MSG_STRUCT.unpack_from + unpack_can_fd_64_msg = CAN_FD_MSG_64_STRUCT.unpack_from + can_fd_64_msg_size = CAN_FD_MSG_64_STRUCT.size + unpack_can_error_ext = CAN_ERROR_EXT_STRUCT.unpack_from + + start_timestamp = self.start_timestamp + max_pos = len(data) + pos = 0 + + # Loop until a struct unpack raises an exception + while True: + self._pos = pos + # Find next object after padding (depends on object type) + try: + pos = data.index(b"LOBJ", pos, pos + 8) + except ValueError: + if pos + 8 > max_pos: + # Not enough data in container + return + raise BLFParseError("Could not find next object") + header = unpack_obj_header_base(data, pos) + # print(header) + signature, _, header_version, obj_size, obj_type = header + if signature != b"LOBJ": + raise BLFParseError() - self.stop() + # Calculate position of next object + next_pos = pos + obj_size + if next_pos > max_pos: + # This object continues in the next container + return + pos += obj_header_base_size + + # Read rest of header + if header_version == 1: + flags, _, _, timestamp = unpack_obj_header_v1(data, pos) + pos += obj_header_v1_size + elif header_version == 2: + flags, _, _, timestamp = unpack_obj_header_v2(data, pos) + pos += obj_header_v2_size + else: + LOG.warning("Unknown object header version (%d)", header_version) + pos = next_pos + continue + + # Calculate absolute timestamp in seconds + factor = 1e-5 if flags == 1 else 1e-9 + timestamp = timestamp * factor + start_timestamp + + if obj_type == CAN_MESSAGE or obj_type == CAN_MESSAGE2: + channel, flags, dlc, can_id, can_data = unpack_can_msg(data, pos) + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + is_rx=not bool(flags & DIR), + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) + elif obj_type == CAN_ERROR_EXT: + members = unpack_can_error_ext(data, pos) + channel = members[0] + dlc = members[5] + can_id = members[7] + can_data = members[9] + yield Message( + timestamp=timestamp, + is_error_frame=True, + is_extended_id=bool(can_id & CAN_MSG_EXT), + arbitration_id=can_id & 0x1FFFFFFF, + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) + elif obj_type == CAN_FD_MESSAGE: + members = unpack_can_fd_msg(data, pos) + channel, flags, dlc, can_id, _, _, fd_flags, valid_bytes, can_data = ( + members + ) + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + is_fd=bool(fd_flags & 0x1), + is_rx=not bool(flags & DIR), + bitrate_switch=bool(fd_flags & 0x2), + error_state_indicator=bool(fd_flags & 0x4), + dlc=dlc2len(dlc), + data=can_data[:valid_bytes], + channel=channel - 1, + ) + elif obj_type == CAN_FD_MESSAGE_64: + members = unpack_can_fd_64_msg(data, pos)[:7] + channel, dlc, valid_bytes, _, can_id, _, fd_flags = members + pos += can_fd_64_msg_size + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(fd_flags & 0x0010), + is_fd=bool(fd_flags & 0x1000), + bitrate_switch=bool(fd_flags & 0x2000), + error_state_indicator=bool(fd_flags & 0x4000), + dlc=dlc2len(dlc), + data=data[pos : pos + valid_bytes], + channel=channel - 1, + ) + + pos = next_pos class BLFWriter(BaseIOHandler, Listener): @@ -301,27 +328,72 @@ class BLFWriter(BaseIOHandler, Listener): """ #: Max log container size of uncompressed data - MAX_CACHE_SIZE = 128 * 1024 + max_container_size = 128 * 1024 - #: ZLIB compression level - COMPRESSION_LEVEL = 9 + #: Application identifier for the log writer + application_id = 5 - def __init__(self, file, channel=1): + def __init__( + self, file, append: bool = False, channel: int = 1, compression_level: int = -1 + ): """ :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in binary - write mode, not text write mode. + If this is a file-like object, is has to opened in mode "wb+". + :param channel: + Default channel to log as if not specified by the interface. + :param append: + Append messages to an existing log file. + :param compression_level: + An integer from 0 to 9 or -1 controlling the level of compression. + 1 (Z_BEST_SPEED) is fastest and produces the least compression. + 9 (Z_BEST_COMPRESSION) is slowest and produces the most. + 0 means that data will be stored without processing. + The default value is -1 (Z_DEFAULT_COMPRESSION). + Z_DEFAULT_COMPRESSION represents a default compromise between + speed and compression (currently equivalent to level 6). """ - super(BLFWriter, self).__init__(file, mode='wb') + mode = "rb+" if append else "wb" + try: + super().__init__(file, mode=mode) + except FileNotFoundError: + # Trying to append to a non-existing file, create a new one + append = False + mode = "wb" + super().__init__(file, mode=mode) + assert self.file is not None self.channel = channel - # Header will be written after log is done - self.file.write(b"\x00" * FILE_HEADER_SIZE) - self.cache = [] - self.cache_size = 0 - self.count_of_objects = 0 - self.uncompressed_size = FILE_HEADER_SIZE - self.start_timestamp = None - self.stop_timestamp = None + self.compression_level = compression_level + self._buffer: List[bytes] = [] + self._buffer_size = 0 + if append: + # Parse file header + data = self.file.read(FILE_HEADER_STRUCT.size) + header = FILE_HEADER_STRUCT.unpack(data) + if header[0] != b"LOGG": + raise BLFParseError("Unexpected file format") + self.uncompressed_size = header[11] + self.object_count = header[12] + self.start_timestamp = systemtime_to_timestamp(header[14:22]) + self.stop_timestamp = systemtime_to_timestamp(header[22:30]) + # Jump to the end of the file + self.file.seek(0, 2) + else: + self.object_count = 0 + self.uncompressed_size = FILE_HEADER_SIZE + self.start_timestamp = None + self.stop_timestamp = None + # Write a default header which will be updated when stopped + self._write_header(FILE_HEADER_SIZE) + + def _write_header(self, filesize): + header = [b"LOGG", FILE_HEADER_SIZE, self.application_id, 0, 0, 0, 2, 6, 8, 1] + # The meaning of "count of objects read" is unknown + header.extend([filesize, self.uncompressed_size, self.object_count, 0]) + header.extend(timestamp_to_systemtime(self.start_timestamp)) + header.extend(timestamp_to_systemtime(self.stop_timestamp)) + self.file.write(FILE_HEADER_STRUCT.pack(*header)) + # Pad to header size + self.file.write(b"\x00" * (FILE_HEADER_SIZE - FILE_HEADER_STRUCT.size)) def on_message_received(self, msg): channel = channel2int(msg.channel) @@ -335,19 +407,23 @@ def on_message_received(self, msg): if msg.is_extended_id: arb_id |= CAN_MSG_EXT flags = REMOTE_FLAG if msg.is_remote_frame else 0 - data = bytes(msg.data) + if not msg.is_rx: + flags |= DIR + can_data = bytes(msg.data) if msg.is_error_frame: - data = CAN_ERROR_EXT_STRUCT.pack(channel, - 0, # length - 0, # flags - 0, # ecc - 0, # position - len2dlc(msg.dlc), - 0, # frame length - arb_id, - 0, # ext flags - data) + data = CAN_ERROR_EXT_STRUCT.pack( + channel, + 0, # length + 0, # flags + 0, # ecc + 0, # position + len2dlc(msg.dlc), + 0, # frame length + arb_id, + 0, # ext flags + can_data, + ) self._add_object(CAN_ERROR_EXT, data, msg.timestamp) elif msg.is_fd: fd_flags = EDL @@ -355,11 +431,20 @@ def on_message_received(self, msg): fd_flags |= BRS if msg.error_state_indicator: fd_flags |= ESI - data = CAN_FD_MSG_STRUCT.pack(channel, flags, len2dlc(msg.dlc), - arb_id, 0, 0, fd_flags, msg.dlc, data) + data = CAN_FD_MSG_STRUCT.pack( + channel, + flags, + len2dlc(msg.dlc), + arb_id, + 0, + 0, + fd_flags, + len(can_data), + can_data, + ) self._add_object(CAN_FD_MESSAGE, data, msg.timestamp) else: - data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, data) + data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, can_data) self._add_object(CAN_MESSAGE, data, msg.timestamp) def log_event(self, text, timestamp=None): @@ -379,7 +464,8 @@ def log_event(self, text, timestamp=None): comment = b"Added by python-can" marker = b"python-can" data = GLOBAL_MARKER_STRUCT.pack( - 0, 0xFFFFFF, 0xFF3300, 0, len(text), len(marker), len(comment)) + 0, 0xFFFFFF, 0xFF3300, 0, len(text), len(marker), len(comment) + ) self._add_object(GLOBAL_MARKER, data + text + marker + comment, timestamp) def _add_object(self, obj_type, data, timestamp=None): @@ -392,63 +478,63 @@ def _add_object(self, obj_type, data, timestamp=None): header_size = OBJ_HEADER_BASE_STRUCT.size + OBJ_HEADER_V1_STRUCT.size obj_size = header_size + len(data) base_header = OBJ_HEADER_BASE_STRUCT.pack( - b"LOBJ", header_size, 1, obj_size, obj_type) + b"LOBJ", header_size, 1, obj_size, obj_type + ) obj_header = OBJ_HEADER_V1_STRUCT.pack(TIME_ONE_NANS, 0, 0, max(timestamp, 0)) - self.cache.append(base_header) - self.cache.append(obj_header) - self.cache.append(data) + self._buffer.append(base_header) + self._buffer.append(obj_header) + self._buffer.append(data) padding_size = len(data) % 4 if padding_size: - self.cache.append(b"\x00" * padding_size) + self._buffer.append(b"\x00" * padding_size) - self.cache_size += obj_size + padding_size - self.count_of_objects += 1 - if self.cache_size >= self.MAX_CACHE_SIZE: + self._buffer_size += obj_size + padding_size + self.object_count += 1 + if self._buffer_size >= self.max_container_size: self._flush() def _flush(self): - """Compresses and writes data in the cache to file.""" + """Compresses and writes data in the buffer to file.""" if self.file.closed: return - cache = b"".join(self.cache) - if not cache: + buffer = b"".join(self._buffer) + if not buffer: # Nothing to write return - uncompressed_data = cache[:self.MAX_CACHE_SIZE] - # Save data that comes after max size to next round - tail = cache[self.MAX_CACHE_SIZE:] - self.cache = [tail] - self.cache_size = len(tail) - compressed_data = zlib.compress(uncompressed_data, - self.COMPRESSION_LEVEL) - obj_size = (OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + - len(compressed_data)) + uncompressed_data = memoryview(buffer)[: self.max_container_size] + # Save data that comes after max size to next container + tail = buffer[self.max_container_size :] + self._buffer = [tail] + self._buffer_size = len(tail) + if not self.compression_level: + data = uncompressed_data + method = NO_COMPRESSION + else: + data = zlib.compress(uncompressed_data, self.compression_level) + method = ZLIB_DEFLATE + obj_size = OBJ_HEADER_BASE_STRUCT.size + LOG_CONTAINER_STRUCT.size + len(data) base_header = OBJ_HEADER_BASE_STRUCT.pack( - b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER) - container_header = LOG_CONTAINER_STRUCT.pack( - ZLIB_DEFLATE, len(uncompressed_data)) + b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER + ) + container_header = LOG_CONTAINER_STRUCT.pack(method, len(uncompressed_data)) self.file.write(base_header) self.file.write(container_header) - self.file.write(compressed_data) + self.file.write(data) # Write padding bytes self.file.write(b"\x00" * (obj_size % 4)) - self.uncompressed_size += OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + self.uncompressed_size += OBJ_HEADER_BASE_STRUCT.size + self.uncompressed_size += LOG_CONTAINER_STRUCT.size self.uncompressed_size += len(uncompressed_data) def stop(self): """Stops logging and closes the file.""" self._flush() - filesize = self.file.tell() - super(BLFWriter, self).stop() - - # Write header in the beginning of the file - header = [b"LOGG", FILE_HEADER_SIZE, - APPLICATION_ID, 0, 0, 0, 2, 6, 8, 1] - # The meaning of "count of objects read" is unknown - header.extend([filesize, self.uncompressed_size, - self.count_of_objects, 0]) - header.extend(timestamp_to_systemtime(self.start_timestamp)) - header.extend(timestamp_to_systemtime(self.stop_timestamp)) - with open(self.file.name, "r+b") as f: - f.write(FILE_HEADER_STRUCT.pack(*header)) + if self.file.seekable(): + filesize = self.file.tell() + # Write header in the beginning of the file + self.file.seek(0) + self._write_header(filesize) + else: + LOG.error("Could not write BLF header since file is not seekable") + super().stop() diff --git a/can/io/canutils.py b/can/io/canutils.py index 69c0227a4..5c08e9050 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -1,15 +1,9 @@ -# coding: utf-8 - """ This module works with CAN data in ASCII log files (*.log). It is is compatible with "candump -L" from the canutils program (https://github.com/linux-can/can-utils). """ -from __future__ import absolute_import, division - -import time -import datetime import logging from can.message import Message @@ -17,12 +11,12 @@ from .generic import BaseIOHandler -log = logging.getLogger('can.io.canutils') +log = logging.getLogger("can.io.canutils") -CAN_MSG_EXT = 0x80000000 -CAN_ERR_FLAG = 0x20000000 -CAN_ERR_BUSERROR = 0x00000080 -CAN_ERR_DLC = 8 +CAN_MSG_EXT = 0x80000000 +CAN_ERR_FLAG = 0x20000000 +CAN_ERR_BUSERROR = 0x00000080 +CAN_ERR_DLC = 8 class CanutilsLogReader(BaseIOHandler): @@ -41,7 +35,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(CanutilsLogReader, self).__init__(file, mode='r') + super().__init__(file, mode="r") def __iter__(self): for line in self.file: @@ -53,36 +47,42 @@ def __iter__(self): timestamp, channel, frame = temp.split() timestamp = float(timestamp[1:-1]) - canId, data = frame.split('#') + canId, data = frame.split("#") if channel.isdigit(): channel = int(channel) - if len(canId) > 3: - isExtended = True - else: - isExtended = False + isExtended = len(canId) > 3 canId = int(canId, 16) - if data and data[0].lower() == 'r': + if data and data[0].lower() == "r": isRemoteFrame = True + if len(data) > 1: dlc = int(data[1:]) else: dlc = 0 + + dataBin = None else: isRemoteFrame = False dlc = len(data) // 2 dataBin = bytearray() for i in range(0, len(data), 2): - dataBin.append(int(data[i:(i + 2)], 16)) + dataBin.append(int(data[i : (i + 2)], 16)) if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR: msg = Message(timestamp=timestamp, is_error_frame=True) else: - msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - is_extended_id=isExtended, is_remote_frame=isRemoteFrame, - dlc=dlc, data=dataBin, channel=channel) + msg = Message( + timestamp=timestamp, + arbitration_id=canId & 0x1FFFFFFF, + is_extended_id=isExtended, + is_remote_frame=isRemoteFrame, + dlc=dlc, + data=dataBin, + channel=channel, + ) yield msg self.stop() @@ -107,8 +107,8 @@ def __init__(self, file, channel="vcan0", append=False): :param bool append: if set to `True` messages are appended to the file, else the file is truncated """ - mode = 'a' if append else 'w' - super(CanutilsLogWriter, self).__init__(file, mode=mode) + mode = "a" if append else "w" + super().__init__(file, mode=mode) self.channel = channel self.last_timestamp = None @@ -116,7 +116,7 @@ def __init__(self, file, channel="vcan0", append=False): def on_message_received(self, msg): # this is the case for the very first message: if self.last_timestamp is None: - self.last_timestamp = (msg.timestamp or 0.0) + self.last_timestamp = msg.timestamp or 0.0 # figure out the correct timestamp if msg.timestamp is None or msg.timestamp < self.last_timestamp: @@ -127,17 +127,30 @@ def on_message_received(self, msg): channel = msg.channel if msg.channel is not None else self.channel if msg.is_error_frame: - self.file.write("(%f) %s %08X#0000000000000000\n" % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR)) + self.file.write( + "(%f) %s %08X#0000000000000000\n" + % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR) + ) elif msg.is_remote_frame: if msg.is_extended_id: - self.file.write("(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id)) + self.file.write( + "(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id) + ) else: - self.file.write("(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id)) + self.file.write( + "(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id) + ) else: data = ["{:02X}".format(byte) for byte in msg.data] if msg.is_extended_id: - self.file.write("(%f) %s %08X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) + self.file.write( + "(%f) %s %08X#%s\n" + % (timestamp, channel, msg.arbitration_id, "".join(data)) + ) else: - self.file.write("(%f) %s %03X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) + self.file.write( + "(%f) %s %03X#%s\n" + % (timestamp, channel, msg.arbitration_id, "".join(data)) + ) diff --git a/can/io/csv.py b/can/io/csv.py index 92f841f8f..35cfcc697 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ This module contains handling for CSV (comma separated values) files. @@ -11,8 +9,6 @@ of a CSV file. """ -from __future__ import absolute_import - from base64 import b64encode, b64decode from can.message import Message @@ -51,25 +47,27 @@ def __init__(self, file, append=False): the file is truncated and starts with a newly written header line """ - mode = 'a' if append else 'w' - super(CSVWriter, self).__init__(file, mode=mode) + mode = "a" if append else "w" + super().__init__(file, mode=mode) # Write a header row if not append: self.file.write("timestamp,arbitration_id,extended,remote,error,dlc,data\n") def on_message_received(self, msg): - row = ','.join([ - repr(msg.timestamp), # cannot use str() here because that is rounding - hex(msg.arbitration_id), - '1' if msg.is_extended_id else '0', - '1' if msg.is_remote_frame else '0', - '1' if msg.is_error_frame else '0', - str(msg.dlc), - b64encode(msg.data).decode('utf8') - ]) + row = ",".join( + [ + repr(msg.timestamp), # cannot use str() here because that is rounding + hex(msg.arbitration_id), + "1" if msg.is_extended_id else "0", + "1" if msg.is_remote_frame else "0", + "1" if msg.is_error_frame else "0", + str(msg.dlc), + b64encode(msg.data).decode("utf8"), + ] + ) self.file.write(row) - self.file.write('\n') + self.file.write("\n") class CSVReader(BaseIOHandler): @@ -87,21 +85,27 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(CSVReader, self).__init__(file, mode='r') + super().__init__(file, mode="r") def __iter__(self): # skip the header line - next(self.file) + try: + next(self.file) + except StopIteration: + # don't crash on a file with only a header + return for line in self.file: - timestamp, arbitration_id, extended, remote, error, dlc, data = line.split(',') + timestamp, arbitration_id, extended, remote, error, dlc, data = line.split( + "," + ) yield Message( timestamp=float(timestamp), - is_remote_frame=(remote == '1'), - is_extended_id=(extended == '1'), - is_error_frame=(error == '1'), + is_remote_frame=(remote == "1"), + is_extended_id=(extended == "1"), + is_error_frame=(error == "1"), arbitration_id=int(arbitration_id, base=16), dlc=int(dlc), data=b64decode(data), diff --git a/can/io/generic.py b/can/io/generic.py index a61c33a9f..eefa3d028 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,50 +1,59 @@ -# coding: utf-8 - """ Contains a generic class for file IO. """ -from abc import ABCMeta, abstractmethod +from abc import ABCMeta +from typing import Optional, cast -from can import Listener +import can +import can.typechecking -class BaseIOHandler(object): +class BaseIOHandler(metaclass=ABCMeta): """A generic file handler that can be used for reading and writing. Can be used as a context manager. - :attr file-like file: + :attr Optional[FileLike] file: the file-like object that is kept internally, or None if none was opened """ - __metaclass__ = ABCMeta - - def __init__(self, file, mode='rt'): + def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: """ :param file: a path-like object to open a file, a file-like object to be used as a file or `None` to not use a file at all - :param str mode: the mode that should be used to open the file, see - :func:`open`, ignored if *file* is `None` + :param mode: the mode that should be used to open the file, see + :func:`open`, ignored if *file* is `None` """ - if file is None or (hasattr(file, 'read') and hasattr(file, 'write')): + if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object - self.file = file + self.file = cast(Optional[can.typechecking.FileLike], file) else: # file is some path-like object - self.file = open(file, mode) + self.file = open(cast(can.typechecking.StringPathLike, file), mode) # for multiple inheritance - super(BaseIOHandler, self).__init__() + super().__init__() - def __enter__(self): + def __enter__(self) -> "BaseIOHandler": return self - def __exit__(self, *args): + def __exit__(self, *args) -> None: self.stop() - def stop(self): + def stop(self) -> None: + """Closes the undelying file-like object and flushes it, if it was opened in write mode.""" if self.file is not None: # this also implies a flush() self.file.close() + + +# pylint: disable=abstract-method,too-few-public-methods +class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): + """The base class for all writers.""" + + +# pylint: disable=too-few-public-methods +class MessageReader(BaseIOHandler, metaclass=ABCMeta): + """The base class for all readers.""" diff --git a/can/io/logger.py b/can/io/logger.py index 52d2e8d83..a4544b87d 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,12 +1,12 @@ -# coding: utf-8 - """ See the :class:`Logger` class. """ -from __future__ import absolute_import +import pathlib +import typing -import logging +from pkg_resources import iter_entry_points +import can.typechecking from ..listener import Listener from .generic import BaseIOHandler @@ -17,10 +17,8 @@ from .sqlite import SqliteWriter from .printer import Printer -log = logging.getLogger("can.io.logger") - -class Logger(BaseIOHandler, Listener): +class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ Logs CAN messages to a file. @@ -30,36 +28,53 @@ class Logger(BaseIOHandler, Listener): * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` - * other: :class:`can.Printer` + * .txt :class:`can.Printer` + + The **filename** may also be *None*, to fall back to :class:`can.Printer`. The log files may be incomplete until `stop()` is called due to buffering. .. note:: - This class itself is just a dispatcher, and any positional an keyword + This class itself is just a dispatcher, and any positional and keyword arguments are passed on to the returned instance. """ + fetched_plugins = False + message_writers = { + ".asc": ASCWriter, + ".blf": BLFWriter, + ".csv": CSVWriter, + ".db": SqliteWriter, + ".log": CanutilsLogWriter, + ".txt": Printer, + } + @staticmethod - def __new__(cls, filename, *args, **kwargs): + def __new__( + cls, filename: typing.Optional[can.typechecking.StringPathLike], *args, **kwargs + ): """ - :type filename: str or None or path-like - :param filename: the filename/path the file to write to, - may be a path-like object if the target logger supports - it, and may be None to instantiate a :class:`~can.Printer` - + :param filename: the filename/path of the file to write to, + may be a path-like object or None to + instantiate a :class:`~can.Printer` + :raises ValueError: if the filename's suffix is of an unknown file type """ - if filename: - if filename.endswith(".asc"): - return ASCWriter(filename, *args, **kwargs) - elif filename.endswith(".blf"): - return BLFWriter(filename, *args, **kwargs) - elif filename.endswith(".csv"): - return CSVWriter(filename, *args, **kwargs) - elif filename.endswith(".db"): - return SqliteWriter(filename, *args, **kwargs) - elif filename.endswith(".log"): - return CanutilsLogWriter(filename, *args, **kwargs) + if filename is None: + return Printer(*args, **kwargs) + + if not Logger.fetched_plugins: + Logger.message_writers.update( + { + writer.name: writer.load() + for writer in iter_entry_points("can.io.message_writer") + } + ) + Logger.fetched_plugins = True - # else: - log.info('unknown file type "%s", falling pack to can.Printer', filename) - return Printer(filename, *args, **kwargs) + suffix = pathlib.PurePath(filename).suffix + try: + return Logger.message_writers[suffix](filename, *args, **kwargs) + except KeyError: + raise ValueError( + f'No write support for this unknown log format "{suffix}"' + ) from None diff --git a/can/io/player.py b/can/io/player.py index 229c157c3..88d3497fa 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -1,15 +1,17 @@ -# coding: utf-8 - """ This module contains the generic :class:`LogReader` as well as :class:`MessageSync` which plays back messages in the recorded order an time intervals. """ -from __future__ import absolute_import - +import pathlib from time import time, sleep -import logging +import typing + +from pkg_resources import iter_entry_points + +if typing.TYPE_CHECKING: + import can from .generic import BaseIOHandler from .asc import ASCReader @@ -18,8 +20,6 @@ from .csv import CSVReader from .sqlite import SqliteReader -log = logging.getLogger('can.io.player') - class LogReader(BaseIOHandler): """ @@ -46,44 +46,65 @@ class LogReader(BaseIOHandler): arguments are passed on to the returned instance. """ + fetched_plugins = False + message_readers = { + ".asc": ASCReader, + ".blf": BLFReader, + ".csv": CSVReader, + ".db": SqliteReader, + ".log": CanutilsLogReader, + } + @staticmethod - def __new__(cls, filename, *args, **kwargs): + def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): """ - :param str filename: the filename/path the file to read from + :param filename: the filename/path of the file to read from + :raises ValueError: if the filename's suffix is of an unknown file type """ - if filename.endswith(".asc"): - return ASCReader(filename, *args, **kwargs) - elif filename.endswith(".blf"): - return BLFReader(filename, *args, **kwargs) - elif filename.endswith(".csv"): - return CSVReader(filename, *args, **kwargs) - elif filename.endswith(".db"): - return SqliteReader(filename, *args, **kwargs) - elif filename.endswith(".log"): - return CanutilsLogReader(filename, *args, **kwargs) - else: - raise NotImplementedError("No read support for this log format: {}".format(filename)) - - -class MessageSync(object): + if not LogReader.fetched_plugins: + LogReader.message_readers.update( + { + reader.name: reader.load() + for reader in iter_entry_points("can.io.message_reader") + } + ) + LogReader.fetched_plugins = True + + suffix = pathlib.PurePath(filename).suffix + try: + return LogReader.message_readers[suffix](filename, *args, **kwargs) + except KeyError: + raise ValueError( + f'No read support for this unknown log format "{suffix}"' + ) from None + + +class MessageSync: # pylint: disable=too-few-public-methods """ Used to iterate over some given messages in the recorded time. """ - def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): + def __init__( + self, + messages: typing.Iterable["can.Message"], + timestamps: bool = True, + gap: float = 0.0001, + skip: float = 60.0, + ) -> None: """Creates an new **MessageSync** instance. - :param Iterable[can.Message] messages: An iterable of :class:`can.Message` instances. - :param bool timestamps: Use the messages' timestamps. If False, uses the *gap* parameter as the time between messages. - :param float gap: Minimum time between sent messages in seconds - :param float skip: Skip periods of inactivity greater than this (in seconds). + :param messages: An iterable of :class:`can.Message` instances. + :param timestamps: Use the messages' timestamps. If False, uses the *gap* parameter + as the time between messages. + :param gap: Minimum time between sent messages in seconds + :param skip: Skip periods of inactivity greater than this (in seconds). """ self.raw_messages = messages self.timestamps = timestamps self.gap = gap self.skip = skip - def __iter__(self): + def __iter__(self) -> typing.Generator["can.Message", None, None]: playback_start_time = time() recorded_start_time = None diff --git a/can/io/printer.py b/can/io/printer.py index 6cc01f69b..ed3006de2 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -1,17 +1,13 @@ -# coding: utf-8 - """ This Listener simply prints to stdout / the terminal or a file. """ -from __future__ import print_function, absolute_import - import logging from can.listener import Listener from .generic import BaseIOHandler -log = logging.getLogger('can.io.printer') +log = logging.getLogger("can.io.printer") class Printer(BaseIOHandler, Listener): @@ -24,18 +20,21 @@ class Printer(BaseIOHandler, Listener): standard out """ - def __init__(self, file=None): + def __init__(self, file=None, append=False): """ :param file: an optional path-like object or as file-like object to "print" to instead of writing to standard out (stdout) If this is a file-like object, is has to opened in text write mode, not binary write mode. + :param bool append: if set to `True` messages are appended to + the file, else the file is truncated """ self.write_to_file = file is not None - super(Printer, self).__init__(file, mode='w') + mode = "a" if append else "w" + super().__init__(file, mode=mode) def on_message_received(self, msg): if self.write_to_file: - self.file.write(str(msg) + '\n') + self.file.write(str(msg) + "\n") else: print(msg) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 21cd2aafc..104ceb77d 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -1,14 +1,9 @@ -# coding: utf-8 - """ Implements an SQL database writer and reader for storing CAN messages. .. note:: The database schema is given in the documentation of the loggers. """ -from __future__ import absolute_import - -import sys import time import threading import logging @@ -18,11 +13,7 @@ from can.message import Message from .generic import BaseIOHandler -log = logging.getLogger('can.io.sqlite') - -if sys.version_info.major < 3: - # legacy fallback for Python 2 - memoryview = buffer +log = logging.getLogger("can.io.sqlite") class SqliteReader(BaseIOHandler): @@ -49,13 +40,15 @@ def __init__(self, file, table_name="messages"): do not accept file-like objects as the `file` parameter. It also runs in ``append=True`` mode all the time. """ - super(SqliteReader, self).__init__(file=None) + super().__init__(file=None) self._conn = sqlite3.connect(file) self._cursor = self._conn.cursor() self.table_name = table_name def __iter__(self): - for frame_data in self._cursor.execute("SELECT * FROM {}".format(self.table_name)): + for frame_data in self._cursor.execute( + "SELECT * FROM {}".format(self.table_name) + ): yield SqliteReader._assemble_message(frame_data) @staticmethod @@ -68,7 +61,7 @@ def _assemble_message(frame_data): is_error_frame=bool(is_error), arbitration_id=can_id, dlc=dlc, - data=data + data=data, ) def __len__(self): @@ -81,13 +74,15 @@ def read_all(self): :rtype: Generator[can.Message] """ - result = self._cursor.execute("SELECT * FROM {}".format(self.table_name)).fetchall() + result = self._cursor.execute( + "SELECT * FROM {}".format(self.table_name) + ).fetchall() return (SqliteReader._assemble_message(frame) for frame in result) def stop(self): """Closes the connection to the database. """ - super(SqliteReader, self).stop() + super().stop() self._conn.close() @@ -145,14 +140,18 @@ def __init__(self, file, table_name="messages"): .. warning:: In contrary to all other readers/writers the Sqlite handlers do not accept file-like objects as the `file` parameter. """ - super(SqliteWriter, self).__init__(file=None) + super().__init__(file=None) self.table_name = table_name self._db_filename = file self._stop_running_event = threading.Event() + self._conn = None self._writer_thread = threading.Thread(target=self._db_writer_thread) self._writer_thread.start() self.num_frames = 0 self.last_write = time.time() + self._insert_template = ( + f"INSERT INTO {self.table_name} VALUES (?, ?, ?, ?, ?, ?, ?)" + ) def _create_db(self): """Creates a new databae or opens a connection to an existing one. @@ -165,7 +164,8 @@ def _create_db(self): self._conn = sqlite3.connect(self._db_filename) # create table structure - self._conn.cursor().execute(""" + self._conn.cursor().execute( + """ CREATE TABLE IF NOT EXISTS {} ( ts REAL, @@ -176,35 +176,40 @@ def _create_db(self): dlc INTEGER, data BLOB ) - """.format(self.table_name)) + """.format( + self.table_name + ) + ) self._conn.commit() - self._insert_template = "INSERT INTO {} VALUES (?, ?, ?, ?, ?, ?, ?)".format(self.table_name) - def _db_writer_thread(self): self._create_db() try: while True: - messages = [] # reset buffer + messages = [] # reset buffer msg = self.get_message(self.GET_MESSAGE_TIMEOUT) while msg is not None: - #log.debug("SqliteWriter: buffering message") - - messages.append(( - msg.timestamp, - msg.arbitration_id, - msg.is_extended_id, - msg.is_remote_frame, - msg.is_error_frame, - msg.dlc, - memoryview(msg.data) - )) - - if time.time() - self.last_write > self.MAX_TIME_BETWEEN_WRITES or \ - len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES: - break + # log.debug("SqliteWriter: buffering message") + + messages.append( + ( + msg.timestamp, + msg.arbitration_id, + msg.is_extended_id, + msg.is_remote_frame, + msg.is_error_frame, + msg.dlc, + memoryview(msg.data), + ) + ) + + if ( + time.time() - self.last_write > self.MAX_TIME_BETWEEN_WRITES + or len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES + ): + break else: # just go on msg = self.get_message(self.GET_MESSAGE_TIMEOUT) @@ -212,9 +217,9 @@ def _db_writer_thread(self): count = len(messages) if count > 0: with self._conn: - #log.debug("Writing %d frames to db", count) + # log.debug("Writing %d frames to db", count) self._conn.executemany(self._insert_template, messages) - self._conn.commit() # make the changes visible to the entire database + self._conn.commit() # make the changes visible to the entire database self.num_frames += count self.last_write = time.time() diff --git a/can/listener.py b/can/listener.py index a91b1dac1..d03a7e0c8 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,29 +1,25 @@ -# coding: utf-8 - """ This module contains the implementation of `can.Listener` and some readers. """ +from typing import AsyncIterator, Awaitable, Optional + +from can.message import Message +from can.bus import BusABC + from abc import ABCMeta, abstractmethod try: # Python 3.7 from queue import SimpleQueue, Empty except ImportError: - try: - # Python 3.0 - 3.6 - from queue import Queue as SimpleQueue, Empty - except ImportError: - # Python 2 - from Queue import Queue as SimpleQueue, Empty + # Python 3.0 - 3.6 + from queue import Queue as SimpleQueue, Empty # type: ignore -try: - import asyncio -except ImportError: - asyncio = None +import asyncio -class Listener(object): +class Listener(metaclass=ABCMeta): """The basic listener that can be called directly to handle some CAN message:: @@ -39,24 +35,21 @@ class Listener(object): listener.stop() """ - __metaclass__ = ABCMeta - @abstractmethod - def on_message_received(self, msg): + def on_message_received(self, msg: Message): """This method is called to handle the given message. - :param can.Message msg: the delivered message + :param msg: the delivered message """ - pass - def __call__(self, msg): - return self.on_message_received(msg) + def __call__(self, msg: Message): + self.on_message_received(msg) - def on_error(self, exc): + def on_error(self, exc: Exception): """This method is called to handle any exception in the receive thread. - :param Exception exc: The exception causing the thread to stop + :param exc: The exception causing the thread to stop """ def stop(self): @@ -74,10 +67,10 @@ class RedirectReader(Listener): """ - def __init__(self, bus): + def __init__(self, bus: BusABC): self.bus = bus - def on_message_received(self, msg): + def on_message_received(self, msg: Message): self.bus.send(msg) @@ -100,7 +93,7 @@ def __init__(self): self.buffer = SimpleQueue() self.is_stopped = False - def on_message_received(self, msg): + def on_message_received(self, msg: Message): """Append a message to the buffer. :raises: BufferError @@ -111,16 +104,15 @@ def on_message_received(self, msg): else: self.buffer.put(msg) - def get_message(self, timeout=0.5): + def get_message(self, timeout: float = 0.5) -> Optional[Message]: """ Attempts to retrieve the latest message received by the instance. If no message is available it blocks for given timeout or until a message is received, or else returns None (whichever is shorter). This method does not block after :meth:`can.BufferedReader.stop` has been called. - :param float timeout: The number of seconds to wait for a new message. - :rytpe: can.Message or None - :return: the message if there is one, or None if there is not. + :param timeout: The number of seconds to wait for a new message. + :return: the Message if there is one, or None if there is not. """ try: return self.buffer.get(block=not self.is_stopped, timeout=timeout) @@ -133,42 +125,40 @@ def stop(self): self.is_stopped = True -if asyncio is not None: - class AsyncBufferedReader(Listener): - """A message buffer for use with :mod:`asyncio`. +class AsyncBufferedReader(Listener): + """A message buffer for use with :mod:`asyncio`. + + See :ref:`asyncio` for how to use with :class:`can.Notifier`. - See :ref:`asyncio` for how to use with :class:`can.Notifier`. - - Can also be used as an asynchronous iterator:: + Can also be used as an asynchronous iterator:: - async for msg in reader: - print(msg) + async for msg in reader: + print(msg) + """ + + def __init__(self, loop: Optional[asyncio.events.AbstractEventLoop] = None): + # set to "infinite" size + self.buffer: "asyncio.Queue[Message]" = asyncio.Queue(loop=loop) + + def on_message_received(self, msg: Message): + """Append a message to the buffer. + + Must only be called inside an event loop! + """ + self.buffer.put_nowait(msg) + + async def get_message(self) -> Message: """ + Retrieve the latest message when awaited for:: + + msg = await reader.get_message() + + :return: The CAN message. + """ + return await self.buffer.get() + + def __aiter__(self) -> AsyncIterator[Message]: + return self - def __init__(self, loop=None): - # set to "infinite" size - self.buffer = asyncio.Queue(loop=loop) - - def on_message_received(self, msg): - """Append a message to the buffer. - - Must only be called inside an event loop! - """ - self.buffer.put_nowait(msg) - - def get_message(self): - """ - Retrieve the latest message when awaited for:: - - msg = await reader.get_message() - - :rtype: can.Message - :return: The CAN message. - """ - return self.buffer.get() - - def __aiter__(self): - return self - - def __anext__(self): - return self.buffer.get() + def __anext__(self) -> Awaitable[Message]: + return self.buffer.get() diff --git a/can/logger.py b/can/logger.py index 204eb8dfb..8a3a214e6 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ logger.py logs CAN traffic to the terminal and to a file on disk. @@ -16,8 +14,6 @@ Dynamic Controls 2010 """ -from __future__ import absolute_import, print_function - import sys import argparse import socket @@ -30,62 +26,101 @@ def main(): parser = argparse.ArgumentParser( "python -m can.logger", - description="Log CAN traffic, printing messages to stdout or to a given file.") - - parser.add_argument("-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.Logger.""", - default=None) - - parser.add_argument("-v", action="count", dest="verbosity", - help='''How much information do you want to see at the command line? - You can add several of these e.g., -vv is DEBUG''', default=2) - - parser.add_argument('-c', '--channel', help='''Most backend interfaces require some sort of channel. + description="Log CAN traffic, printing messages to stdout or to a given file.", + ) + + parser.add_argument( + "-f", + "--file_name", + dest="log_file", + help="""Path and base log filename, for supported types see can.Logger.""", + default=None, + ) + + parser.add_argument( + "-v", + action="count", + dest="verbosity", + help="""How much information do you want to see at the command line? + You can add several of these e.g., -vv is DEBUG""", + default=2, + ) + + parser.add_argument( + "-c", + "--channel", + help='''Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''') - - parser.add_argument('-i', '--interface', dest="interface", - help='''Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.''', - choices=can.VALID_INTERFACES) - - parser.add_argument('--filter', help='''Comma separated filters can be specified for the given CAN interface: + With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + ) + + parser.add_argument( + "-i", + "--interface", + dest="interface", + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + choices=can.VALID_INTERFACES, + ) + + parser.add_argument( + "--filter", + help="""Comma separated filters can be specified for the given CAN interface: : (matches when & mask == can_id & mask) ~ (matches when & mask != can_id & mask) - ''', nargs=argparse.REMAINDER, default='') + """, + nargs=argparse.REMAINDER, + default="", + ) + + parser.add_argument( + "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" + ) + + parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") - parser.add_argument('-b', '--bitrate', type=int, - help='''Bitrate to use for the CAN bus.''') + parser.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) state_group = parser.add_mutually_exclusive_group(required=False) - state_group.add_argument('--active', help="Start the bus as active, this is applied by default.", - action='store_true') - state_group.add_argument('--passive', help="Start the bus as passive.", - action='store_true') + state_group.add_argument( + "--active", + help="Start the bus as active, this is applied by default.", + action="store_true", + ) + state_group.add_argument( + "--passive", help="Start the bus as passive.", action="store_true" + ) # print help message when no arguments wre given if len(sys.argv) < 2: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) results = parser.parse_args() verbosity = results.verbosity - logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] + logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ + min(5, verbosity) + ] can.set_logging_level(logging_level_name) can_filters = [] - if len(results.filter) > 0: - print('Adding filter/s', results.filter) + if results.filter: + print(f"Adding filter(s): {results.filter}") for filt in results.filter: - if ':' in filt: + if ":" in filt: _ = filt.split(":") can_id, can_mask = int(_[0], base=16), int(_[1], base=16) elif "~" in filt: can_id, can_mask = filt.split("~") - can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER + can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER can_mask = int(can_mask, base=16) & socket.CAN_ERR_FLAG can_filters.append({"can_id": can_id, "can_mask": can_mask}) @@ -94,6 +129,10 @@ def main(): config["interface"] = results.interface if results.bitrate: config["bitrate"] = results.bitrate + if results.fd: + config["fd"] = True + if results.data_bitrate: + config["data_bitrate"] = results.data_bitrate bus = Bus(results.channel, **config) if results.active: @@ -101,8 +140,8 @@ def main(): elif results.passive: bus.state = BusState.PASSIVE - print('Connected to {}: {}'.format(bus.__class__.__name__, bus.channel_info)) - print('Can Logger (Started on {})\n'.format(datetime.now())) + print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") + print(f"Can Logger (Started on {datetime.now()})") logger = Logger(results.log_file) try: @@ -116,5 +155,6 @@ def main(): bus.shutdown() logger.stop() + if __name__ == "__main__": main() diff --git a/can/message.py b/can/message.py index f85218fc0..7ceaca489 100644 --- a/can/message.py +++ b/can/message.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ This module contains the implementation of :class:`can.Message`. @@ -8,14 +6,15 @@ starting with Python 3.7. """ -from __future__ import absolute_import, division +from typing import Optional, Union + +from . import typechecking -import warnings from copy import deepcopy from math import isinf, isnan -class Message(object): +class Message: """ The :class:`~can.Message` object is used to represent CAN messages for sending, receiving and other purposes like converting between different @@ -43,80 +42,48 @@ class Message(object): "dlc", "data", "is_fd", + "is_rx", "bitrate_switch", "error_state_indicator", - "__weakref__", # support weak references to messages - "_dict" # see __getattr__ + "__weakref__", # support weak references to messages ) - def __getattr__(self, key): - # TODO keep this for a version, in order to not break old code - # this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method) - # can be removed in 4.0 - # this method is only called if the attribute was not found elsewhere, like in __slots__ - try: - warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) - return self._dict[key] - except KeyError: - raise AttributeError("'message' object has no attribute '{}'".format(key)) - - def __setattr__(self, key, value): - # see __getattr__ - try: - super(Message, self).__setattr__(key, value) - except AttributeError: - warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) - self._dict[key] = value - - @property - def id_type(self): - # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - return self.is_extended_id - - @id_type.setter - def id_type(self, value): - # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - self.is_extended_id = value - - def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, - is_remote_frame=False, is_error_frame=False, channel=None, - dlc=None, data=None, - is_fd=False, bitrate_switch=False, error_state_indicator=False, - extended_id=None, # deprecated in 3.x, TODO remove in 4.x - check=False): + def __init__( + self, + timestamp: float = 0.0, + arbitration_id: int = 0, + is_extended_id: bool = True, + is_remote_frame: bool = False, + is_error_frame: bool = False, + channel: Optional[typechecking.Channel] = None, + dlc: Optional[int] = None, + data: Optional[typechecking.CanData] = None, + is_fd: bool = False, + is_rx: bool = True, + bitrate_switch: bool = False, + error_state_indicator: bool = False, + check: bool = False, + ): """ To create a message object, simply provide any of the below attributes together with additional parameters as keyword arguments to the constructor. - :param bool check: By default, the constructor of this class does not strictly check the input. - Thus, the caller must prevent the creation of invalid messages or - set this parameter to `True`, to raise an Error on invalid inputs. - Possible problems include the `dlc` field not matching the length of `data` - or creating a message with both `is_remote_frame` and `is_error_frame` set to `True`. + :param check: By default, the constructor of this class does not strictly check the input. + Thus, the caller must prevent the creation of invalid messages or + set this parameter to `True`, to raise an Error on invalid inputs. + Possible problems include the `dlc` field not matching the length of `data` + or creating a message with both `is_remote_frame` and `is_error_frame` set to `True`. :raises ValueError: iff `check` is set to `True` and one or more arguments were invalid """ - self._dict = dict() # see __getattr__ - self.timestamp = timestamp self.arbitration_id = arbitration_id - - if extended_id is not None: - # TODO remove in 4.0 - warnings.warn("The extended_id parameter is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - - if is_extended_id is not None: - self.is_extended_id = is_extended_id - else: - self.is_extended_id = True if extended_id is None else extended_id - + self.is_extended_id = is_extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame self.channel = channel - self.is_fd = is_fd + self.is_rx = is_rx self.bitrate_switch = bitrate_switch self.error_state_indicator = error_state_indicator @@ -139,7 +106,7 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if check: self._check() - def __str__(self): + def __str__(self) -> str: field_strings = ["Timestamp: {0:>15.6f}".format(self.timestamp)] if self.is_extended_id: arbitration_id_string = "ID: {0:08x}".format(self.arbitration_id) @@ -147,14 +114,17 @@ def __str__(self): arbitration_id_string = "ID: {0:04x}".format(self.arbitration_id) field_strings.append(arbitration_id_string.rjust(12, " ")) - flag_string = " ".join([ - "X" if self.is_extended_id else "S", - "E" if self.is_error_frame else " ", - "R" if self.is_remote_frame else " ", - "F" if self.is_fd else " ", - "BS" if self.bitrate_switch else " ", - "EI" if self.error_state_indicator else " " - ]) + flag_string = " ".join( + [ + "X" if self.is_extended_id else "S", + "Rx" if self.is_rx else "Tx", + "E" if self.is_error_frame else " ", + "R" if self.is_remote_frame else " ", + "F" if self.is_fd else " ", + "BS" if self.bitrate_switch else " ", + "EI" if self.error_state_indicator else " ", + ] + ) field_strings.append(flag_string) @@ -169,7 +139,7 @@ def __str__(self): field_strings.append(" " * 24) if (self.data is not None) and (self.data.isalnum()): - field_strings.append("'{}'".format(self.data.decode('utf-8', 'replace'))) + field_strings.append("'{}'".format(self.data.decode("utf-8", "replace"))) if self.channel is not None: try: @@ -179,22 +149,22 @@ def __str__(self): return " ".join(field_strings).strip() - def __len__(self): + def __len__(self) -> int: # return the dlc such that it also works on remote frames return self.dlc - def __bool__(self): - # For Python 3 + def __bool__(self) -> bool: return True - def __nonzero__(self): - # For Python 2 - return self.__bool__() + def __repr__(self) -> str: + args = [ + "timestamp={}".format(self.timestamp), + "arbitration_id={:#x}".format(self.arbitration_id), + "is_extended_id={}".format(self.is_extended_id), + ] - def __repr__(self): - args = ["timestamp={}".format(self.timestamp), - "arbitration_id={:#x}".format(self.arbitration_id), - "extended_id={}".format(self.is_extended_id)] + if not self.is_rx: + args.append("is_rx=False") if self.is_remote_frame: args.append("is_remote_frame={}".format(self.is_remote_frame)) @@ -203,11 +173,10 @@ def __repr__(self): args.append("is_error_frame={}".format(self.is_error_frame)) if self.channel is not None: - args.append("channel={!r}".format(self.channel)) + args.append("channel={!r}".format(self.channel)) data = ["{:#02x}".format(byte) for byte in self.data] - args += ["dlc={}".format(self.dlc), - "data=[{}]".format(", ".join(data))] + args += ["dlc={}".format(self.dlc), "data=[{}]".format(", ".join(data))] if self.is_fd: args.append("is_fd=True") @@ -216,16 +185,16 @@ def __repr__(self): return "can.Message({})".format(", ".join(args)) - def __format__(self, format_spec): + def __format__(self, format_spec: Optional[str]) -> str: if not format_spec: return self.__str__() else: raise ValueError("non empty format_specs are not supported") - def __bytes__(self): + def __bytes__(self) -> bytes: return bytes(self.data) - def __copy__(self): + def __copy__(self) -> "Message": new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, @@ -236,13 +205,13 @@ def __copy__(self): dlc=self.dlc, data=self.data, is_fd=self.is_fd, + is_rx=self.is_rx, bitrate_switch=self.bitrate_switch, - error_state_indicator=self.error_state_indicator + error_state_indicator=self.error_state_indicator, ) - new._dict.update(self._dict) return new - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: dict) -> "Message": new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, @@ -253,10 +222,10 @@ def __deepcopy__(self, memo): dlc=self.dlc, data=deepcopy(self.data, memo), is_fd=self.is_fd, + is_rx=self.is_rx, bitrate_switch=self.bitrate_switch, - error_state_indicator=self.error_state_indicator + error_state_indicator=self.error_state_indicator, ) - new._dict.update(self._dict) return new def _check(self): @@ -274,70 +243,91 @@ def _check(self): raise ValueError("the timestamp may not be NaN") if self.is_remote_frame and self.is_error_frame: - raise ValueError("a message cannot be a remote and an error frame at the sane time") + raise ValueError( + "a message cannot be a remote and an error frame at the sane time" + ) if self.arbitration_id < 0: raise ValueError("arbitration IDs may not be negative") if self.is_extended_id: - if 0x20000000 <= self.arbitration_id: + if self.arbitration_id >= 0x20000000: raise ValueError("Extended arbitration IDs must be less than 2^29") - elif 0x800 <= self.arbitration_id: + elif self.arbitration_id >= 0x800: raise ValueError("Normal arbitration IDs must be less than 2^11") if self.dlc < 0: raise ValueError("DLC may not be negative") if self.is_fd: - if 64 < self.dlc: - raise ValueError("DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc)) - elif 8 < self.dlc: - raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) + if self.dlc > 64: + raise ValueError( + "DLC was {} but it should be <= 64 for CAN FD frames".format( + self.dlc + ) + ) + elif self.dlc > 8: + raise ValueError( + "DLC was {} but it should be <= 8 for normal CAN frames".format( + self.dlc + ) + ) if self.is_remote_frame: - if self.data is not None and len(self.data) != 0: + if self.data: raise ValueError("remote frames may not carry any data") elif self.dlc != len(self.data): - raise ValueError("the DLC and the length of the data must match up for non remote frames") + raise ValueError( + "the DLC and the length of the data must match up for non remote frames" + ) if not self.is_fd: if self.bitrate_switch: raise ValueError("bitrate switch is only allowed for CAN FD frames") if self.error_state_indicator: - raise ValueError("error state indicator is only allowed for CAN FD frames") - - def equals(self, other, timestamp_delta=1.0e-6): + raise ValueError( + "error state indicator is only allowed for CAN FD frames" + ) + + def equals( + self, + other: "Message", + timestamp_delta: Optional[Union[float, int]] = 1.0e-6, + check_direction: bool = True, + ) -> bool: """ Compares a given message with this one. - :param can.Message other: the message to compare with + :param other: the message to compare with - :type timestamp_delta: float or int or None :param timestamp_delta: the maximum difference at which two timestamps are still considered equal or None to not compare timestamps - :rtype: bool + :param check_direction: do we compare the messages' directions (Tx/Rx) + :return: True iff the given message equals this one """ # see https://github.com/hardbyte/python-can/pull/413 for a discussion # on why a delta of 1.0e-6 was chosen return ( # check for identity first and finish fast - self is other or + self is other + or # then check for equality by value ( ( - timestamp_delta is None or - abs(self.timestamp - other.timestamp) <= timestamp_delta - ) and - self.arbitration_id == other.arbitration_id and - self.is_extended_id == other.is_extended_id and - self.dlc == other.dlc and - self.data == other.data and - self.is_remote_frame == other.is_remote_frame and - self.is_error_frame == other.is_error_frame and - self.channel == other.channel and - self.is_fd == other.is_fd and - self.bitrate_switch == other.bitrate_switch and - self.error_state_indicator == other.error_state_indicator + timestamp_delta is None + or abs(self.timestamp - other.timestamp) <= timestamp_delta + ) + and (self.is_rx == other.is_rx or not check_direction) + and self.arbitration_id == other.arbitration_id + and self.is_extended_id == other.is_extended_id + and self.dlc == other.dlc + and self.data == other.data + and self.is_remote_frame == other.is_remote_frame + and self.is_error_frame == other.is_error_frame + and self.channel == other.channel + and self.is_fd == other.is_fd + and self.bitrate_switch == other.bitrate_switch + and self.error_state_indicator == other.error_state_indicator ) ) diff --git a/can/notifier.py b/can/notifier.py index 737ec978e..de2894f64 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -1,23 +1,29 @@ -# coding: utf-8 - """ This module contains the implementation of :class:`~can.Notifier`. """ +from typing import Iterable, List, Optional, Union + +from can.bus import BusABC +from can.listener import Listener +from can.message import Message + import threading import logging import time -try: - import asyncio -except ImportError: - asyncio = None - -logger = logging.getLogger('can.Notifier') +import asyncio +logger = logging.getLogger("can.Notifier") -class Notifier(object): - def __init__(self, bus, listeners, timeout=1.0, loop=None): +class Notifier: + def __init__( + self, + bus: BusABC, + listeners: Iterable[Listener], + timeout: float = 1.0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): """Manages the distribution of :class:`can.Message` instances to listeners. Supports multiple buses and listeners. @@ -28,50 +34,56 @@ def __init__(self, bus, listeners, timeout=1.0, loop=None): many listeners carry out flush operations to persist data. - :param can.BusABC bus: A :ref:`bus` or a list of buses to listen to. - :param list listeners: An iterable of :class:`~can.Listener` - :param float timeout: An optional maximum number of seconds to wait for any message. - :param asyncio.AbstractEventLoop loop: - An :mod:`asyncio` event loop to schedule listeners in. + :param bus: A :ref:`bus` or a list of buses to listen to. + :param listeners: An iterable of :class:`~can.Listener` + :param timeout: An optional maximum number of seconds to wait for any message. + :param loop: An :mod:`asyncio` event loop to schedule listeners in. """ - self.listeners = listeners + self.listeners = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop #: Exception raised in thread - self.exception = None + self.exception: Optional[Exception] = None self._running = True self._lock = threading.Lock() - self._readers = [] + self._readers: List[Union[int, threading.Thread]] = [] buses = self.bus if isinstance(self.bus, list) else [self.bus] for bus in buses: self.add_bus(bus) - def add_bus(self, bus): + def add_bus(self, bus: BusABC): """Add a bus for notification. - :param can.BusABC bus: + :param bus: CAN bus instance. """ - if self._loop is not None and hasattr(bus, 'fileno') and bus.fileno() >= 0: + if ( + self._loop is not None + and hasattr(bus, "fileno") + and bus.fileno() >= 0 # type: ignore + ): # Use file descriptor to watch for messages - reader = bus.fileno() + reader = bus.fileno() # type: ignore self._loop.add_reader(reader, self._on_message_available, bus) else: - reader = threading.Thread(target=self._rx_thread, args=(bus,), - name='can.notifier for bus "{}"'.format(bus.channel_info)) + reader = threading.Thread( + target=self._rx_thread, + args=(bus,), + name='can.notifier for bus "{}"'.format(bus.channel_info), + ) reader.daemon = True reader.start() self._readers.append(reader) - def stop(self, timeout=5): + def stop(self, timeout: float = 5): """Stop notifying Listeners when new :class:`~can.Message` objects arrive and call :meth:`~can.Listener.stop` on each Listener. - :param float timeout: + :param timeout: Max time in seconds to wait for receive threads to finish. Should be longer than timeout given at instantiation. """ @@ -82,14 +94,14 @@ def stop(self, timeout=5): now = time.time() if now < end_time: reader.join(end_time - now) - else: + elif self._loop: # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: - if hasattr(listener, 'stop'): + if hasattr(listener, "stop"): listener.stop() - def _rx_thread(self, bus): + def _rx_thread(self, bus: BusABC): msg = None try: while self._running: @@ -97,7 +109,8 @@ def _rx_thread(self, bus): with self._lock: if self._loop is not None: self._loop.call_soon_threadsafe( - self._on_message_received, msg) + self._on_message_received, msg + ) else: self._on_message_received(msg) msg = bus.recv(self.timeout) @@ -105,44 +118,47 @@ def _rx_thread(self, bus): self.exception = exc if self._loop is not None: self._loop.call_soon_threadsafe(self._on_error, exc) - else: - self._on_error(exc) - raise + raise + elif not self._on_error(exc): + raise - def _on_message_available(self, bus): + def _on_message_available(self, bus: BusABC): msg = bus.recv(0) if msg is not None: self._on_message_received(msg) - def _on_message_received(self, msg): + def _on_message_received(self, msg: Message): for callback in self.listeners: res = callback(msg) if self._loop is not None and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) - def _on_error(self, exc): - for listener in self.listeners: - if hasattr(listener, 'on_error'): - listener.on_error(exc) + def _on_error(self, exc: Exception) -> bool: + listeners_with_on_error = [ + listener for listener in self.listeners if hasattr(listener, "on_error") + ] + + for listener in listeners_with_on_error: + listener.on_error(exc) + + return bool(listeners_with_on_error) - def add_listener(self, listener): + def add_listener(self, listener: Listener): """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. - :param can.Listener listener: Listener to be added to - the list to be notified + :param listener: Listener to be added to the list to be notified """ self.listeners.append(listener) - def remove_listener(self, listener): + def remove_listener(self, listener: Listener): """Remove a listener from the notification list. This method trows an exception if the given listener is not part of the stored listeners. - :param can.Listener listener: Listener to be removed from - the list to be notified + :param listener: Listener to be removed from the list to be notified :raises ValueError: if `listener` was never added to this notifier """ self.listeners.remove(listener) diff --git a/can/player.py b/can/player.py index c712f1714..d7ef866fb 100644 --- a/can/player.py +++ b/can/player.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Replays CAN traffic saved with can.logger back to a CAN bus. @@ -7,8 +5,6 @@ Similar to canplayer in the can-utils package. """ -from __future__ import absolute_import, print_function - import sys import argparse from datetime import datetime @@ -19,71 +15,131 @@ def main(): parser = argparse.ArgumentParser( - "python -m can.player", - description="Replay CAN traffic.") - - parser.add_argument("-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.LogReader.""", - default=None) - - parser.add_argument("-v", action="count", dest="verbosity", - help='''Also print can frames to stdout. - You can add several of these to enable debugging''', default=2) - - parser.add_argument('-c', '--channel', - help='''Most backend interfaces require some sort of channel. + "python -m can.player", description="Replay CAN traffic." + ) + + parser.add_argument( + "-f", + "--file_name", + dest="log_file", + help="""Path and base log filename, for supported types see can.LogReader.""", + default=None, + ) + + parser.add_argument( + "-v", + action="count", + dest="verbosity", + help="""Also print can frames to stdout. + You can add several of these to enable debugging""", + default=2, + ) + + parser.add_argument( + "-c", + "--channel", + help='''Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''') - - parser.add_argument('-i', '--interface', dest="interface", - help='''Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.''', - choices=can.VALID_INTERFACES) - - parser.add_argument('-b', '--bitrate', type=int, - help='''Bitrate to use for the CAN bus.''') - - parser.add_argument('--ignore-timestamps', dest='timestamps', - help='''Ignore timestamps (send all frames immediately with minimum gap between frames)''', - action='store_false') - - parser.add_argument('-g', '--gap', type=float, help=''' minimum time between replayed frames''', - default=0.0001) - parser.add_argument('-s', '--skip', type=float, default=60*60*24, - help=''' skip gaps greater than 's' seconds''') - - parser.add_argument('infile', metavar='input-file', type=str, - help='The file to replay. For supported types see can.LogReader.') + With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + ) + + parser.add_argument( + "-i", + "--interface", + dest="interface", + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + choices=can.VALID_INTERFACES, + ) + + parser.add_argument( + "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" + ) + + parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + parser.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) + + parser.add_argument( + "--ignore-timestamps", + dest="timestamps", + help="""Ignore timestamps (send all frames immediately with minimum gap between frames)""", + action="store_false", + ) + + parser.add_argument( + "--error-frames", + help="Also send error frames to the interface.", + action="store_true", + ) + + parser.add_argument( + "-g", + "--gap", + type=float, + help=""" minimum time between replayed frames""", + default=0.0001, + ) + parser.add_argument( + "-s", + "--skip", + type=float, + default=60 * 60 * 24, + help=""" skip gaps greater than 's' seconds""", + ) + + parser.add_argument( + "infile", + metavar="input-file", + type=str, + help="The file to replay. For supported types see can.LogReader.", + ) # print help message when no arguments were given if len(sys.argv) < 2: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) results = parser.parse_args() verbosity = results.verbosity - logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] + logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ + min(5, verbosity) + ] can.set_logging_level(logging_level_name) + error_frames = results.error_frames + config = {"single_handle": True} if results.interface: config["interface"] = results.interface if results.bitrate: config["bitrate"] = results.bitrate + if results.fd: + config["fd"] = True + if results.data_bitrate: + config["data_bitrate"] = results.data_bitrate bus = Bus(results.channel, **config) reader = LogReader(results.infile) - in_sync = MessageSync(reader, timestamps=results.timestamps, - gap=results.gap, skip=results.skip) + in_sync = MessageSync( + reader, timestamps=results.timestamps, gap=results.gap, skip=results.skip + ) - print('Can LogReader (Started on {})'.format(datetime.now())) + print(f"Can LogReader (Started on {datetime.now()})") try: for m in in_sync: + if m.is_error_frame and not error_frames: + continue if verbosity >= 3: print(m) bus.send(m) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index d82ac6bd6..f36119751 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,13 +1,10 @@ -# coding: utf-8 - -from __future__ import print_function, absolute_import - from threading import RLock try: # Only raise an exception on instantiation but allow module # to be imported from wrapt import ObjectProxy + import_exc = None except ImportError as exc: ObjectProxy = object @@ -17,10 +14,11 @@ try: - from contextlib import nullcontext + from contextlib import nullcontext # type: ignore except ImportError: - class nullcontext(object): + + class nullcontext: # type: ignore """A context manager that does nothing at all. A fallback for Python 3.7's :class:`contextlib.nullcontext` manager. """ @@ -35,7 +33,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(ObjectProxy): +class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods @@ -56,11 +54,13 @@ def __init__(self, *args, **kwargs): if import_exc is not None: raise import_exc - super(ThreadSafeBus, self).__init__(Bus(*args, **kwargs)) + super().__init__(Bus(*args, **kwargs)) # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager + # pylint: disable=protected-access self.__wrapped__._lock_send_periodic = nullcontext() + # pylint: enable=protected-access # init locks for sending and receiving separately self._lock_send = RLock() diff --git a/can/typechecking.py b/can/typechecking.py new file mode 100644 index 000000000..0327874f5 --- /dev/null +++ b/can/typechecking.py @@ -0,0 +1,36 @@ +"""Types for mypy type-checking +""" + +import typing + +if typing.TYPE_CHECKING: + import os + +import mypy_extensions + +CanFilter = mypy_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) +CanFilterExtended = mypy_extensions.TypedDict( + "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} +) +CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]] + +# TODO: Once buffer protocol support lands in typing, we should switch to that, +# since can.message.Message attempts to call bytearray() on the given data, so +# this should have the same typing info. +# +# See: https://github.com/python/typing/issues/593 +CanData = typing.Union[bytes, bytearray, int, typing.Iterable[int]] + +# Used for the Abstract Base Class +Channel = typing.Union[int, str] + +# Used by the IO module +FileLike = typing.IO[typing.Any] +StringPathLike = typing.Union[str, "os.PathLike[str]"] +AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] + +BusConfig = typing.NewType("BusConfig", dict) + +AutoDetectedConfig = mypy_extensions.TypedDict( + "AutoDetectedConfig", {"interface": str, "channel": Channel} +) diff --git a/can/util.py b/can/util.py index af421651f..968f1f7fd 100644 --- a/can/util.py +++ b/can/util.py @@ -1,61 +1,41 @@ -# coding: utf-8 - """ Utilities and configuration file parsing. """ -from __future__ import absolute_import, print_function +from typing import Dict, Optional, Union + +from can import typechecking +import json import os import os.path -import sys import platform import re import logging -import warnings - -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import SafeConfigParser as ConfigParser +from configparser import ConfigParser import can from can.interfaces import VALID_INTERFACES -log = logging.getLogger('can.util') +log = logging.getLogger("can.util") # List of valid data lengths for a CAN FD message -CAN_FD_DLC = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, - 12, 16, 20, 24, 32, 48, 64 -] +CAN_FD_DLC = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] -REQUIRED_KEYS = [ - 'interface', - 'channel', -] +REQUIRED_KEYS = ["interface", "channel"] -CONFIG_FILES = ['~/can.conf'] +CONFIG_FILES = ["~/can.conf"] if platform.system() == "Linux": - CONFIG_FILES.extend( - [ - '/etc/can.conf', - '~/.can', - '~/.canrc' - ] - ) + CONFIG_FILES.extend(["/etc/can.conf", "~/.can", "~/.canrc"]) elif platform.system() == "Windows" or platform.python_implementation() == "IronPython": - CONFIG_FILES.extend( - [ - 'can.ini', - os.path.join(os.getenv('APPDATA', ''), 'can.ini') - ] - ) + CONFIG_FILES.extend(["can.ini", os.path.join(os.getenv("APPDATA", ""), "can.ini")]) -def load_file_config(path=None, section=None): +def load_file_config( + path: Optional[typechecking.AcceptedIOType] = None, section: str = "default" +) -> Dict[str, str]: """ Loads configuration from file with following content:: @@ -77,38 +57,54 @@ def load_file_config(path=None, section=None): _config = {} - section = section if section is not None else 'default' if config.has_section(section): - if config.has_section('default'): - _config.update( - dict((key, val) for key, val in config.items('default'))) _config.update(dict((key, val) for key, val in config.items(section))) return _config -def load_environment_config(): +def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: """ Loads config dict from environmental variables (if set): * CAN_INTERFACE * CAN_CHANNEL * CAN_BITRATE + * CAN_CONFIG + + if context is supplied, "_{context}" is appended to the environment + variable name we will look at. For example if context="ABC": + + * CAN_INTERFACE_ABC + * CAN_CHANNEL_ABC + * CAN_BITRATE_ABC + * CAN_CONFIG_ABC """ mapper = { - 'interface': 'CAN_INTERFACE', - 'channel': 'CAN_CHANNEL', - 'bitrate': 'CAN_BITRATE', + "interface": "CAN_INTERFACE", + "channel": "CAN_CHANNEL", + "bitrate": "CAN_BITRATE", } - return dict( - (key, os.environ.get(val)) - for key, val in mapper.items() - if val in os.environ - ) + context_suffix = "_{}".format(context) if context else "" + + can_config_key = "CAN_CONFIG" + context_suffix + config: Dict[str, str] = json.loads(os.environ.get(can_config_key, "{}")) -def load_config(path=None, config=None, context=None): + for key, val in mapper.items(): + config_option = os.environ.get(val + context_suffix, None) + if config_option: + config[key] = config_option + + return config + + +def load_config( + path: Optional[typechecking.AcceptedIOType] = None, + config=None, + context: Optional[str] = None, +) -> typechecking.BusConfig: """ Returns a dict with configuration details which is loaded from (in this order): @@ -122,7 +118,7 @@ def load_config(path=None, config=None, context=None): kvaser, socketcan, pcan, usb2can, ixxat, nican, virtual. .. note:: - + The key ``bustype`` is copied to ``interface`` if that one is missing and does never appear in the result. @@ -163,8 +159,12 @@ def load_config(path=None, config=None, context=None): config_sources = [ given_config, can.rc, - lambda _context: load_environment_config(), # context is not supported - lambda _context: load_file_config(path, _context) + lambda _context: load_environment_config( # pylint: disable=unnecessary-lambda + _context + ), + lambda _context: load_environment_config(), + lambda _context: load_file_config(path, _context), + lambda _context: load_file_config(path), ] # Slightly complex here to only search for the file config if required @@ -172,10 +172,10 @@ def load_config(path=None, config=None, context=None): if callable(cfg): cfg = cfg(context) # remove legacy operator (and copy to interface if not already present) - if 'bustype' in cfg: - if 'interface' not in cfg or not cfg['interface']: - cfg['interface'] = cfg['bustype'] - del cfg['bustype'] + if "bustype" in cfg: + if "interface" not in cfg or not cfg["interface"]: + cfg["interface"] = cfg["bustype"] + del cfg["bustype"] # copy all new parameters for key in cfg: if key not in config: @@ -186,43 +186,60 @@ def load_config(path=None, config=None, context=None): if key not in config: config[key] = None - # Handle deprecated socketcan types - if config['interface'] in ('socketcan_native', 'socketcan_ctypes'): - # DeprecationWarning in 3.x releases - # TODO: Remove completely in 4.0 - warnings.warn('{} is deprecated, use socketcan instead'.format(config['interface']), DeprecationWarning) - config['interface'] = 'socketcan' - - if config['interface'] not in VALID_INTERFACES: - raise NotImplementedError('Invalid CAN Bus Type - {}'.format(config['interface'])) - - if 'bitrate' in config: - config['bitrate'] = int(config['bitrate']) + if config["interface"] not in VALID_INTERFACES: + raise NotImplementedError( + "Invalid CAN Bus Type - {}".format(config["interface"]) + ) + + if "bitrate" in config: + config["bitrate"] = int(config["bitrate"]) + if "fd" in config: + config["fd"] = config["fd"] not in ("0", "False", "false") + if "data_bitrate" in config: + config["data_bitrate"] = int(config["data_bitrate"]) + + # Create bit timing configuration if given + timing_conf = {} + for key in ( + "f_clock", + "brp", + "tseg1", + "tseg2", + "sjw", + "nof_samples", + "btr0", + "btr1", + ): + if key in config: + timing_conf[key] = int(config[key], base=0) + del config[key] + if timing_conf: + timing_conf["bitrate"] = config.get("bitrate") + config["timing"] = can.BitTiming(**timing_conf) can.log.debug("can config: {}".format(config)) return config - -def set_logging_level(level_name=None): + +def set_logging_level(level_name: Optional[str] = None): """Set the logging level for the "can" logger. Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' """ - can_logger = logging.getLogger('can') + can_logger = logging.getLogger("can") try: - can_logger.setLevel(getattr(logging, level_name.upper())) + can_logger.setLevel(getattr(logging, level_name.upper())) # type: ignore except AttributeError: can_logger.setLevel(logging.DEBUG) log.debug("Logging set to {}".format(level_name)) -def len2dlc(length): +def len2dlc(length: int) -> int: """Calculate the DLC from data length. :param int length: Length in number of bytes (0-64) :returns: DLC (0-15) - :rtype: int """ if length <= 8: return length @@ -232,25 +249,23 @@ def len2dlc(length): return 15 -def dlc2len(dlc): +def dlc2len(dlc: int) -> int: """Calculate the data length from DLC. - :param int dlc: DLC (0-15) + :param dlc: DLC (0-15) :returns: Data length in number of bytes (0-64) - :rtype: int """ return CAN_FD_DLC[dlc] if dlc <= 15 else 64 -def channel2int(channel): +def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int]: """Try to convert the channel to an integer. :param channel: Channel string (e.g. can0, CAN1) or integer - + :returns: Channel integer or `None` if unsuccessful - :rtype: int """ if channel is None: return None @@ -258,7 +273,7 @@ def channel2int(channel): return channel # String and byte objects have a lower() method if hasattr(channel, "lower"): - match = re.match(r'.*(\d+)$', channel) + match = re.match(r".*(\d+)$", channel) if match: return int(match.group(1)) return None diff --git a/can/viewer.py b/can/viewer.py index 316d3e3e4..107c028ae 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,5 +1,3 @@ -# coding: utf-8 -# # Copyright (C) 2018 Kristian Sloth Lauszus. # # This program is free software; you can redistribute it and/or @@ -22,32 +20,31 @@ # Web : http://www.lauszus.com # e-mail : lauszus@gmail.com -from __future__ import absolute_import, print_function - import argparse +import logging import os import struct import sys import time -import logging from typing import Dict, List, Tuple, Union + import can from can import __version__ -logger = logging.getLogger('can.serial') +logger = logging.getLogger("can.serial") try: import curses from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE except ImportError: # Probably on windows - logger.warning("You won't be able to use the viewer program without " - "curses installed!") - curses = None + logger.warning( + "You won't be able to use the viewer program without " "curses installed!" + ) + curses = None # type: ignore class CanViewer: - def __init__(self, stdscr, bus, data_structs, testing=False): self.stdscr = stdscr self.bus = bus @@ -81,37 +78,37 @@ def run(self): # Do not read the CAN-Bus when in paused mode if not self.paused: # Read the CAN-Bus and draw it in the terminal window - msg = self.bus.recv(timeout=1. / 1000.) + msg = self.bus.recv(timeout=1.0 / 1000.0) if msg is not None: self.draw_can_bus_message(msg) else: # Sleep 1 ms, so the application does not use 100 % of the CPU resources - time.sleep(1. / 1000.) + time.sleep(1.0 / 1000.0) # Read the terminal input key = self.stdscr.getch() # Stop program if the user presses ESC or 'q' - if key == KEY_ESC or key == ord('q'): + if key == KEY_ESC or key == ord("q"): break # Clear by pressing 'c' - elif key == ord('c'): + elif key == ord("c"): self.ids = {} self.start_time = None self.scroll = 0 self.draw_header() # Sort by pressing 's' - elif key == ord('s'): + elif key == ord("s"): # Sort frames based on the CAN-Bus ID self.draw_header() for i, key in enumerate(sorted(self.ids.keys())): # Set the new row index, but skip the header - self.ids[key]['row'] = i + 1 + self.ids[key]["row"] = i + 1 # Do a recursive call, so the frames are repositioned - self.draw_can_bus_message(self.ids[key]['msg'], sorting=True) + self.draw_can_bus_message(self.ids[key]["msg"], sorting=True) # Pause by pressing space elif key == KEY_SPACE: @@ -133,7 +130,7 @@ def run(self): resized = curses.is_term_resized(self.y, self.x) if resized is True: self.y, self.x = self.stdscr.getmaxyx() - if hasattr(curses, 'resizeterm'): # pragma: no cover + if hasattr(curses, "resizeterm"): # pragma: no cover curses.resizeterm(self.y, self.x) self.redraw_screen() @@ -142,29 +139,33 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod - def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[Union[float, int]] - if not cmd_to_struct or len(data) == 0: + def unpack_data( + cmd, cmd_to_struct, data + ): # type: (int, Dict, bytes) -> List[Union[float, int]] + if not cmd_to_struct or not data: # These messages do not contain a data package return [] - for key in cmd_to_struct.keys(): + for key in cmd_to_struct: if cmd == key if isinstance(key, int) else cmd in key: value = cmd_to_struct[key] if isinstance(value, tuple): # The struct is given as the fist argument - struct_t = value[0] # type: struct.Struct + struct_t: struct.Struct = value[0] # The conversion from raw values to SI-units are given in the rest of the tuple - values = [d // val if isinstance(val, int) else float(d) / val - for d, val in zip(struct_t.unpack(data), value[1:])] + values = [ + d // val if isinstance(val, int) else float(d) / val + for d, val in zip(struct_t.unpack(data), value[1:]) + ] else: # No conversion from SI-units is needed - struct_t = value # type: struct.Struct - values = list(struct_t.unpack(data)) + as_struct_t: struct.Struct = value + values = list(as_struct_t.unpack(data)) return values else: - raise ValueError('Unknown command: 0x{:02X}'.format(cmd)) + raise ValueError("Unknown command: 0x{:02X}".format(cmd)) def draw_can_bus_message(self, msg, sorting=False): # Use the CAN-Bus ID as the key in the dict @@ -172,7 +173,7 @@ def draw_can_bus_message(self, msg, sorting=False): # Sort the extended IDs at the bottom by setting the 32-bit high if msg.is_extended_id: - key |= (1 << 32) + key |= 1 << 32 new_id_added, length_changed = False, False if not sorting: @@ -182,35 +183,32 @@ def draw_can_bus_message(self, msg, sorting=False): # Set the start time when the first message has been received if not self.start_time: self.start_time = msg.timestamp - elif msg.dlc != self.ids[key]['msg'].dlc: + elif msg.dlc != self.ids[key]["msg"].dlc: length_changed = True if new_id_added or length_changed: # Increment the index if it was just added, but keep it if the length just changed - row = len(self.ids) + 1 if new_id_added else self.ids[key]['row'] + row = len(self.ids) + 1 if new_id_added else self.ids[key]["row"] # It's a new message ID or the length has changed, so add it to the dict # The first index is the row index, the second is the frame counter, # the third is a copy of the CAN-Bus frame # and the forth index is the time since the previous message - self.ids[key] = {'row': row, 'count': 0, 'msg': msg, 'dt': 0} + self.ids[key] = {"row": row, "count": 0, "msg": msg, "dt": 0} else: # Calculate the time since the last message and save the timestamp - self.ids[key]['dt'] = msg.timestamp - self.ids[key]['msg'].timestamp + self.ids[key]["dt"] = msg.timestamp - self.ids[key]["msg"].timestamp # Copy the CAN-Bus frame - this is used for sorting - self.ids[key]['msg'] = msg + self.ids[key]["msg"] = msg # Increment frame counter - self.ids[key]['count'] += 1 + self.ids[key]["count"] += 1 # Format the CAN-Bus ID as a hex value - arbitration_id_string = '0x{0:0{1}X}'.format(msg.arbitration_id, 8 if msg.is_extended_id else 3) - - # Generate data string - data_string = '' - if msg.dlc > 0: - data_string = ' '.join('{:02X}'.format(x) for x in msg.data) + arbitration_id_string = "0x{0:0{1}X}".format( + msg.arbitration_id, 8 if msg.is_extended_id else 3 + ) # Use red for error frames if msg.is_error_frame: @@ -219,24 +217,39 @@ def draw_can_bus_message(self, msg, sorting=False): color = curses.color_pair(0) # Now draw the CAN-Bus message on the terminal window - self.draw_line(self.ids[key]['row'], 0, str(self.ids[key]['count']), color) - self.draw_line(self.ids[key]['row'], 8, '{0:.6f}'.format(self.ids[key]['msg'].timestamp - self.start_time), - color) - self.draw_line(self.ids[key]['row'], 23, '{0:.6f}'.format(self.ids[key]['dt']), color) - self.draw_line(self.ids[key]['row'], 35, arbitration_id_string, color) - self.draw_line(self.ids[key]['row'], 47, str(msg.dlc), color) - self.draw_line(self.ids[key]['row'], 52, data_string, color) + self.draw_line(self.ids[key]["row"], 0, str(self.ids[key]["count"]), color) + self.draw_line( + self.ids[key]["row"], + 8, + "{0:.6f}".format(self.ids[key]["msg"].timestamp - self.start_time), + color, + ) + self.draw_line( + self.ids[key]["row"], 23, "{0:.6f}".format(self.ids[key]["dt"]), color + ) + self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color) + self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color) + for i, b in enumerate(msg.data): + col = 52 + i * 3 + if col > self.x - 2: + # Data does not fit + self.draw_line(self.ids[key]["row"], col - 4, "...", color) + break + text = "{:02X}".format(b) + self.draw_line(self.ids[key]["row"], col, text, color) if self.data_structs: try: values_list = [] - for x in self.unpack_data(msg.arbitration_id, self.data_structs, msg.data): + for x in self.unpack_data( + msg.arbitration_id, self.data_structs, msg.data + ): if isinstance(x, float): - values_list.append('{0:.6f}'.format(x)) + values_list.append("{0:.6f}".format(x)) else: values_list.append(str(x)) - values_string = ' '.join(values_list) - self.draw_line(self.ids[key]['row'], 77, values_string, color) + values_string = " ".join(values_list) + self.draw_line(self.ids[key]["row"], 77, values_string, color) except (ValueError, struct.error): pass @@ -255,43 +268,42 @@ def draw_line(self, row, col, txt, *args): def draw_header(self): self.stdscr.erase() - self.draw_line(0, 0, 'Count', curses.A_BOLD) - self.draw_line(0, 8, 'Time', curses.A_BOLD) - self.draw_line(0, 23, 'dt', curses.A_BOLD) - self.draw_line(0, 35, 'ID', curses.A_BOLD) - self.draw_line(0, 47, 'DLC', curses.A_BOLD) - self.draw_line(0, 52, 'Data', curses.A_BOLD) + self.draw_line(0, 0, "Count", curses.A_BOLD) + self.draw_line(0, 8, "Time", curses.A_BOLD) + self.draw_line(0, 23, "dt", curses.A_BOLD) + self.draw_line(0, 35, "ID", curses.A_BOLD) + self.draw_line(0, 47, "DLC", curses.A_BOLD) + self.draw_line(0, 52, "Data", curses.A_BOLD) if self.data_structs: # Only draw if the dictionary is not empty - self.draw_line(0, 77, 'Parsed values', curses.A_BOLD) + self.draw_line(0, 77, "Parsed values", curses.A_BOLD) def redraw_screen(self): # Trigger a complete redraw self.draw_header() - for key in self.ids.keys(): - self.draw_can_bus_message(self.ids[key]['msg']) + for key in self.ids: + self.draw_can_bus_message(self.ids[key]["msg"]) # noinspection PyProtectedMember class SmartFormatter(argparse.HelpFormatter): - def _get_default_metavar_for_optional(self, action): return action.dest.upper() def _format_usage(self, usage, actions, groups, prefix): # Use uppercase for "Usage:" text - return super(SmartFormatter, self)._format_usage(usage, actions, groups, 'Usage: ') + return super()._format_usage(usage, actions, groups, "Usage: ") def _format_args(self, action, default_metavar): if action.nargs != argparse.REMAINDER and action.nargs != argparse.ONE_OR_MORE: - return super(SmartFormatter, self)._format_args(action, default_metavar) + return super()._format_args(action, default_metavar) # Use the metavar if "REMAINDER" or "ONE_OR_MORE" is set get_metavar = self._metavar_formatter(action, default_metavar) - return '%s' % get_metavar(1) + return "%s" % get_metavar(1) def _format_action_invocation(self, action): if not action.option_strings or action.nargs == 0: - return super(SmartFormatter, self)._format_action_invocation(action) + return super()._format_action_invocation(action) # Modified so "-s ARGS, --long ARGS" is replaced with "-s, --long ARGS" else: @@ -300,133 +312,176 @@ def _format_action_invocation(self, action): args_string = self._format_args(action, default) for i, option_string in enumerate(action.option_strings): if i == len(action.option_strings) - 1: - parts.append('%s %s' % (option_string, args_string)) + parts.append("%s %s" % (option_string, args_string)) else: - parts.append('%s' % option_string) - return ', '.join(parts) + parts.append("%s" % option_string) + return ", ".join(parts) def _split_lines(self, text, width): # Allow to manually split the lines - if text.startswith('R|'): + if text.startswith("R|"): return text[2:].splitlines() - return super(SmartFormatter, self)._split_lines(text, width) + return super()._split_lines(text, width) def _fill_text(self, text, width, indent): - if text.startswith('R|'): + if text.startswith("R|"): # noinspection PyTypeChecker - return ''.join(indent + line + '\n' for line in text[2:].splitlines()) + return "".join(indent + line + "\n" for line in text[2:].splitlines()) else: - return super(SmartFormatter, self)._fill_text(text, width, indent) + return super()._fill_text(text, width, indent) def parse_args(args): # Python versions >= 3.5 kwargs = {} if sys.version_info[0] * 10 + sys.version_info[1] >= 35: # pragma: no cover - kwargs = {'allow_abbrev': False} + kwargs = {"allow_abbrev": False} # Parse command line arguments - parser = argparse.ArgumentParser('python -m can.viewer', - description='A simple CAN viewer terminal application written in Python', - epilog='R|Shortcuts: ' - '\n +---------+-------------------------+' - '\n | Key | Description |' - '\n +---------+-------------------------+' - '\n | ESQ/q | Exit the viewer |' - '\n | c | Clear the stored frames |' - '\n | s | Sort the stored frames |' - '\n | SPACE | Pause the viewer |' - '\n | UP/DOWN | Scroll the viewer |' - '\n +---------+-------------------------+', - formatter_class=SmartFormatter, add_help=False, **kwargs) - - optional = parser.add_argument_group('Optional arguments') - - optional.add_argument('-h', '--help', action='help', help='Show this help message and exit') - - optional.add_argument('--version', action='version', help="Show program's version number and exit", - version='%(prog)s (version {version})'.format(version=__version__)) + parser = argparse.ArgumentParser( + "python -m can.viewer", + description="A simple CAN viewer terminal application written in Python", + epilog="R|Shortcuts: " + "\n +---------+-------------------------+" + "\n | Key | Description |" + "\n +---------+-------------------------+" + "\n | ESQ/q | Exit the viewer |" + "\n | c | Clear the stored frames |" + "\n | s | Sort the stored frames |" + "\n | SPACE | Pause the viewer |" + "\n | UP/DOWN | Scroll the viewer |" + "\n +---------+-------------------------+", + formatter_class=SmartFormatter, + add_help=False, + **kwargs + ) + + optional = parser.add_argument_group("Optional arguments") + + optional.add_argument( + "-h", "--help", action="help", help="Show this help message and exit" + ) + + optional.add_argument( + "--version", + action="version", + help="Show program's version number and exit", + version="%(prog)s (version {version})".format(version=__version__), + ) # Copied from: https://github.com/hardbyte/python-can/blob/develop/can/logger.py - optional.add_argument('-b', '--bitrate', type=int, help='''Bitrate to use for the given CAN interface''') - - optional.add_argument('-c', '--channel', help='''Most backend interfaces require some sort of channel. + optional.add_argument( + "-b", + "--bitrate", + type=int, + help="""Bitrate to use for the given CAN interface""", + ) + + optional.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + optional.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) + + optional.add_argument( + "-c", + "--channel", + help="""Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" with the socketcan interfaces valid channel examples include: "can0", "vcan0". - (default: use default for the specified interface)''') - - optional.add_argument('-d', '--decode', dest='decode', - help='R|Specify how to convert the raw bytes into real values.' - '\nThe ID of the frame is given as the first argument and the format as the second.' - '\nThe Python struct package is used to unpack the received data' - '\nwhere the format characters have the following meaning:' - '\n < = little-endian, > = big-endian' - '\n x = pad byte' - '\n c = char' - '\n ? = bool' - '\n b = int8_t, B = uint8_t' - '\n h = int16, H = uint16' - '\n l = int32_t, L = uint32_t' - '\n q = int64_t, Q = uint64_t' - '\n f = float (32-bits), d = double (64-bits)' - '\nFx to convert six bytes with ID 0x100 into uint8_t, uint16 and uint32_t:' - '\n $ python -m can.viewer -d "100:: (matches when & mask == can_id & mask)' - '\n ~ (matches when & mask != can_id & mask)' - '\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:' - '\n python -m can.viewer -f 100:7FC 200:7F0' - '\nNote that the ID and mask are alway interpreted as hex values', - metavar='{:,~}', nargs=argparse.ONE_OR_MORE, default='') - - optional.add_argument('-i', '--interface', dest='interface', - help='R|Specify the backend CAN interface to use.', - choices=sorted(can.VALID_INTERFACES)) + (default: use default for the specified interface)""", + ) + + optional.add_argument( + "-d", + "--decode", + dest="decode", + help="R|Specify how to convert the raw bytes into real values." + "\nThe ID of the frame is given as the first argument and the format as the second." + "\nThe Python struct package is used to unpack the received data" + "\nwhere the format characters have the following meaning:" + "\n < = little-endian, > = big-endian" + "\n x = pad byte" + "\n c = char" + "\n ? = bool" + "\n b = int8_t, B = uint8_t" + "\n h = int16, H = uint16" + "\n l = int32_t, L = uint32_t" + "\n q = int64_t, Q = uint64_t" + "\n f = float (32-bits), d = double (64-bits)" + "\nFx to convert six bytes with ID 0x100 into uint8_t, uint16 and uint32_t:" + '\n $ python -m can.viewer -d "100:: (matches when & mask == can_id & mask)" + "\n ~ (matches when & mask != can_id & mask)" + "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" + "\n python -m can.viewer -f 100:7FC 200:7F0" + "\nNote that the ID and mask are alway interpreted as hex values", + metavar="{:,~}", + nargs=argparse.ONE_OR_MORE, + default="", + ) + + optional.add_argument( + "-i", + "--interface", + dest="interface", + help="R|Specify the backend CAN interface to use.", + choices=sorted(can.VALID_INTERFACES), + ) # Print help message when no arguments are given - if len(args) == 0: + if not args: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) parsed_args = parser.parse_args(args) can_filters = [] - if len(parsed_args.filter) > 0: + if parsed_args.filter: # print('Adding filter/s', parsed_args.filter) for flt in parsed_args.filter: # print(filter) - if ':' in flt: - _ = flt.split(':') + if ":" in flt: + _ = flt.split(":") can_id, can_mask = int(_[0], base=16), int(_[1], base=16) - elif '~' in flt: - can_id, can_mask = flt.split('~') + elif "~" in flt: + can_id, can_mask = flt.split("~") can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER can_mask = int(can_mask, base=16) & 0x20000000 # socket.CAN_ERR_FLAG else: - raise argparse.ArgumentError(None, 'Invalid filter argument') - can_filters.append({'can_id': can_id, 'can_mask': can_mask}) + raise argparse.ArgumentError(None, "Invalid filter argument") + can_filters.append({"can_id": can_id, "can_mask": can_mask}) # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. @@ -446,16 +501,18 @@ def parse_args(args): # An optional conversion from real units to integers can be given as additional arguments. # In order to convert from raw integer value the real units are multiplied with the values and similarly the values # are divided by the value in order to convert from real units to raw integer values. - data_structs = {} # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] - if len(parsed_args.decode) > 0: + data_structs = ( + {} + ) # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] + if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): - with open(parsed_args.decode[0], 'r') as f: + with open(parsed_args.decode[0], "r") as f: structs = f.readlines() else: structs = parsed_args.decode for s in structs: - tmp = s.rstrip('\n').split(':') + tmp = s.rstrip("\n").split(":") # The ID is given as a hex value, the format needs no conversion key, fmt = int(tmp[0], base=16), tmp[1] @@ -481,13 +538,17 @@ def parse_args(args): def main(): # pragma: no cover parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) - config = {'single_handle': True} + config = {"single_handle": True} if can_filters: - config['can_filters'] = can_filters + config["can_filters"] = can_filters if parsed_args.interface: - config['interface'] = parsed_args.interface + config["interface"] = parsed_args.interface if parsed_args.bitrate: - config['bitrate'] = parsed_args.bitrate + config["bitrate"] = parsed_args.bitrate + if parsed_args.fd: + config["fd"] = True + if parsed_args.data_bitrate: + config["data_bitrate"] = parsed_args.data_bitrate # Create a CAN-Bus interface bus = can.Bus(parsed_args.channel, **config) @@ -496,7 +557,7 @@ def main(): # pragma: no cover curses.wrapper(CanViewer, bus, data_structs) -if __name__ == '__main__': # pragma: no cover +if __name__ == "__main__": # Catch ctrl+c try: main() diff --git a/doc/api.rst b/doc/api.rst index 640f61e2d..b3191cad2 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -11,12 +11,13 @@ A form of CAN interface is also required. .. toctree:: :maxdepth: 1 - + bus message listeners asyncio bcm + bit_timing internal-api @@ -24,7 +25,7 @@ Utilities --------- -.. automethod:: can.detect_available_configs +.. autofunction:: can.detect_available_configs .. _notifier: @@ -32,7 +33,7 @@ Utilities Notifier -------- -The Notifier object is used as a message distributor for a bus. +The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners. .. autoclass:: can.Notifier :members: diff --git a/doc/bcm.rst b/doc/bcm.rst index 96e73d52d..549b06edd 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -42,14 +42,3 @@ which inherits from :class:`~can.broadcastmanager.CyclicTask`. .. autoclass:: can.RestartableCyclicTaskABC :members: - - -Functional API --------------- - -.. warning:: - The functional API in :func:`can.broadcastmanager.send_periodic` is now deprecated - and will be removed in version 4.0. - Use the object oriented API via :meth:`can.BusABC.send_periodic` instead. - -.. autofunction:: can.broadcastmanager.send_periodic diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst new file mode 100644 index 000000000..e1d2feeeb --- /dev/null +++ b/doc/bit_timing.rst @@ -0,0 +1,49 @@ +Bit Timing Configuration +======================== + +The CAN protocol allows the bitrate, sample point and number of samples to be +optimized for a given application. You can read more on Wikipedia_, Kvaser_ +and other sources. + +In most cases the recommended settings for a predefined set of common +bitrates will work just fine. In some cases it may however be necessary to specify +custom settings. The :class:`can.BitTiming` class can be used for this purpose to +specify them in a relatively interface agnostic manner. + +It is also possible to specify the same settings for a CAN 2.0 bus +using the config file: + + +.. code-block:: none + + [default] + bitrate=1000000 + f_clock=8000000 + tseg1=5 + tseg2=2 + sjw=1 + nof_samples=1 + + +.. code-block:: none + + [default] + brp=1 + tseg1=5 + tseg2=2 + sjw=1 + nof_samples=1 + + +.. code-block:: none + + [default] + btr0=0x00 + btr1=0x14 + + +.. autoclass:: can.BitTiming + + +.. _Wikipedia: https://en.wikipedia.org/wiki/CAN_bus#Bit_timing +.. _Kvaser: https://www.kvaser.com/about-can/the-can-protocol/can-bit-timing/ diff --git a/doc/bus.rst b/doc/bus.rst index 5c1e95606..b524d4868 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -67,7 +67,7 @@ This thread safe version of the :class:`~can.BusABC` class can be used by multip Sending and receiving is locked separately to avoid unnecessary delays. Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.BusABC`: +It can be used exactly like the normal :class:`~can.BusABC`:: # 'socketcan' is only an example interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') diff --git a/doc/conf.py b/doc/conf.py index 1c409a06a..568a5641d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,111 +1,112 @@ -# -*- coding: utf-8 -*- -# -# python-can documentation build configuration file -# -# This file is execfile()d with the current directory set to its containing dir. +""" +python-can documentation build configuration file + +This file is execfile()d with the current directory set to its containing dir. +""" -from __future__ import unicode_literals, absolute_import +# -- Imports ------------------------------------------------------------------- import sys import os -# General information about the project. -project = u'python-can' - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) + +import can # pylint: disable=wrong-import-position + +# -- General configuration ----------------------------------------------------- + +# pylint: disable=invalid-name -import can # The version info for the project, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = can.__version__.split('-')[0] +version = can.__version__.split("-")[0] release = can.__version__ -# -- General configuration ----------------------------------------------------- +# General information about the project. +project = "python-can" -primary_domain = 'py' +primary_domain = "py" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.extlinks', - 'sphinx.ext.todo', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx.ext.graphviz', - 'sphinxcontrib.programoutput' - ] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.extlinks", + "sphinx.ext.todo", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.graphviz", + "sphinxcontrib.programoutput", + "sphinx_autodoc_typehints", +] # Now, you can use the alias name as a new role, e.g. :issue:`123`. -extlinks = { - 'issue': ('https://github.com/hardbyte/python-can/issues/%s/', 'issue '), -} +extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue ")} -intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), -} +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} # If this is True, todo and todolist produce output, else they produce nothing. # The default is False. todo_include_todos = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -graphviz_output_format = 'png' # 'svg' +graphviz_output_format = "png" # 'svg' # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -language = 'en' +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # Include documentation from both the class level and __init__ autoclass_content = "both" # The default autodoc directive flags -autodoc_default_flags = ['members', 'show-inheritance'] +autodoc_default_flags = ["members", "show-inheritance"] # Keep cached intersphinx inventories indefinitely intersphinx_cache_limit = -1 @@ -114,77 +115,77 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-can' +htmlhelp_basename = "python-can" diff --git a/doc/configuration.rst b/doc/configuration.rst index dda2ace2a..9bda3030f 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -11,7 +11,7 @@ In Code ------- The ``can`` object exposes an ``rc`` dictionary which can be used to set -the **interface** and **channel** before importing from ``can.interfaces``. +the **interface** and **channel**. :: @@ -19,7 +19,7 @@ the **interface** and **channel** before importing from ``can.interfaces``. can.rc['interface'] = 'socketcan' can.rc['channel'] = 'vcan0' can.rc['bitrate'] = 500000 - from can.interfaces.interface import Bus + from can.interface import Bus bus = Bus() @@ -43,9 +43,9 @@ On Linux systems the config file is searched in the following paths: On Windows systems the config file is searched in the following paths: -#. ``~/can.conf`` +#. ``%USERPROFILE%/can.conf`` #. ``can.ini`` (current working directory) -#. ``$APPDATA/can.ini`` +#. ``%APPDATA%/can.ini`` The configuration file sets the default interface and channel: @@ -79,7 +79,7 @@ The configuration can also contain additional sections (or context): :: - from can.interfaces.interface import Bus + from can.interface import Bus hs_bus = Bus(context='HS') ms_bus = Bus(context='MS') @@ -92,6 +92,13 @@ Configuration can be pulled from these environmental variables: * CAN_INTERFACE * CAN_CHANNEL * CAN_BITRATE + * CAN_CONFIG + +The ``CAN_CONFIG`` environment variable allows to set any bus configuration using JSON. + +For example: + +``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}`` Interface Names @@ -126,3 +133,7 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ +| ``"canalystii"`` | :doc:`interfaces/canalystii` | ++---------------------+-------------------------------------+ +| ``"systec"`` | :doc:`interfaces/systec` | ++---------------------+-------------------------------------+ diff --git a/doc/development.rst b/doc/development.rst index 602e4e347..8bad5c58e 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -69,8 +69,6 @@ The modules in ``python-can`` are: |:doc:`broadcastmanager ` | Contains interface independent broadcast manager | | | code. | +---------------------------------+------------------------------------------------------+ -|:doc:`CAN ` | Legacy API. Deprecated. | -+---------------------------------+------------------------------------------------------+ Creating a new Release diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index 45026701b..a63beee71 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,2 +1,3 @@ sphinx>=1.8.1 sphinxcontrib-programoutput +sphinx-autodoc-typehints==1.6.0 diff --git a/doc/installation.rst b/doc/installation.rst index 147b27b74..a70f7d5ea 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -78,6 +78,17 @@ neoVI See :doc:`interfaces/neovi`. +Vector +~~~~~~ + +To install ``python-can`` using the XL Driver Library as the backend: + +1. Install the `latest drivers `__ for your Vector hardware interface. + +2. Install the `XL Driver Library `__ or copy the ``vxlapi.dll`` and/or + ``vxlapi64.dll`` into your working directory. + +3. Use Vector Hardware Configuration to assign a channel to your application. Installing python-can in development mode ----------------------------------------- diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 7c8253f9e..bd7a0d1df 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -15,6 +15,7 @@ The available interfaces are: interfaces/kvaser interfaces/serial interfaces/slcan + interfaces/robotell interfaces/ixxat interfaces/pcan interfaces/usb2can @@ -25,6 +26,7 @@ The available interfaces are: interfaces/virtual interfaces/canalystii interfaces/systec + interfaces/seeedstudio Additional interfaces can be added via a plugin interface. An external package can register a new interface by using the ``can.interface`` entry point in its setup.py. diff --git a/doc/interfaces/robotell.rst b/doc/interfaces/robotell.rst new file mode 100644 index 000000000..064e21725 --- /dev/null +++ b/doc/interfaces/robotell.rst @@ -0,0 +1,37 @@ +.. _robotell: + +Chinese CAN-USB interface (mfg. Robotell etc.) +============================================== + +An USB to CAN adapter sold on Aliexpress, etc. with the manufacturer name Robotell printed on the case. +There is also a USB stick version with a clear case. If the description or screenshots refer to ``EmbededDebug`` or ``EmbededConfig`` +the device should be compatible with this driver. +These USB devices are based on a STM32 controller with a CH340 serial interface and use a binary protocol - NOT compatible with SLCAN + +See `https://www.amobbs.com/thread-4651667-1-1.html `_ for some background on these devices. + +This driver directly uses either the local or remote (not tested) serial port. +Remote serial ports will be specified via special URL. Both raw TCP sockets as also RFC2217 ports are supported. + +Usage: use ``port or URL[@baurate]`` to open the device. +For example use ``/dev/ttyUSB0@115200`` or ``COM4@9600`` for local serial ports and +``socket://192.168.254.254:5000`` or ``rfc2217://192.168.254.254:5000`` for remote ports. + + +Supported devices +----------------- + +.. todo:: Document this. + + +Bus +--- + +.. autoclass:: can.interfaces.robotell.robotellBus + :members: + + +Internals +--------- + +.. todo:: Document the internals of robotell interface. diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst new file mode 100644 index 000000000..5c86fa688 --- /dev/null +++ b/doc/interfaces/seeedstudio.rst @@ -0,0 +1,85 @@ +.. _seeeddoc: + + +USB-CAN Analyzer +================ +...by Seeed Studio + +SKU: 114991193 + +Links: + +- https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html +- https://github.com/SeeedDocument/USB-CAN_Analyzer +- https://copperhilltech.com/blog/usbcan-analyzer-usb-to-can-bus-serial-protocol-definition/ + +^^^^^^^^^^ + +Installation +------------ +This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install an additional packages if not already available: + - pyserial + + +:: + + pip3 install python-can[seeedstudio] + + +^^^^^^^^^^ + + +Interface +--------- + +:: + + can.interfaces.seeedstudio.SeeedBus + +A bus example:: + + bus = can.interface.Bus(bustype='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000) + + +^^^^^^^^^^ + +Parameters +---------- +:: + + SeeedBus(channel, + baudrate=2000000, + timeout=0.1, + frame_type='STD', + operation_mode='normal', + bitrate=500000) + +ChANNEL + The serial port created by the USB device when connected. + +TIMEOUT + Only used by the underling serial port, it probably should not be changed. The serial port baudrate=2000000 and rtscts=false are also matched to the device so are not added here. + +FRAMETYPE + - "STD" + - "EXT" + +OPERATIONMODE + - "normal" + - "loopback" + - "silent" + - "loopback_and_silent" + +BITRATE + - 1000000 + - 800000 + - 500000 + - 400000 + - 250000 + - 200000 + - 125000 + - 100000 + - 50000 + - 20000 + - 10000 + - 5000 diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index bdd934ca7..1783c388c 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,16 +1,26 @@ SocketCAN ========= -The full documentation for socketcan can be found in the kernel docs at -`networking/can.txt `_. - - -.. note:: - - Versions before 2.2 had two different implementations named +The `SocketCAN`_ documentation can be found in the Linux kernel docs at +``networking`` directory. Quoting from the SocketCAN Linux documentation:: + +> The socketcan package is an implementation of CAN protocols +> (Controller Area Network) for Linux. CAN is a networking technology +> which has widespread use in automation, embedded devices, and +> automotive fields. While there have been other CAN implementations +> for Linux based on character devices, SocketCAN uses the Berkeley +> socket API, the Linux network stack and implements the CAN device +> drivers as network interfaces. The CAN socket API has been designed +> as similar as possible to the TCP/IP protocols to allow programmers, +> familiar with network programming, to easily learn how to use CAN +> sockets. + +.. important:: + + `python-can` versions before 2.2 had two different implementations named ``socketcan_ctypes`` and ``socketcan_native``. These are now deprecated and the aliases to ``socketcan`` will be removed in - version 4.0. 3.x releases raise a DeprecationWarning. + version 4.0. 3.x releases raise a DeprecationWarning. Socketcan Quickstart @@ -53,7 +63,8 @@ existing ``can0`` interface with a bitrate of 1MB: PCAN ~~~~ -Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, so there is no need to install any drivers. The CAN interface can be brought like so: +Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, +so there is no need to install any drivers. The CAN interface can be brought like so: :: @@ -61,12 +72,22 @@ Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketc sudo modprobe peak_pci sudo ip link set can0 up type can bitrate 500000 +Intrepid +~~~~~~~~ + +The Intrepid Control Systems, Inc provides several devices (e.g. ValueCAN) as well +as Linux module and user-space daemon to make it possible to use them via SocketCAN. + +Refer to below repositories for installation instructions: + +- `Intrepid kernel module`_ +- `Intrepid user-space daemon`_ + Send Test Message ^^^^^^^^^^^^^^^^^ -The `can-utils `_ library for linux -includes a script `cansend` which is useful to send known payloads. For -example to send a message on `vcan0`: +The `can-utils`_ library for Linux includes a `cansend` tool which is useful to +send known payloads. For example to send a message on `vcan0`: .. code-block:: bash @@ -138,7 +159,7 @@ To spam a bus: def producer(id): """:param id: Spam the bus with messages including the data id.""" - bus = can.interface.Bus(channel=channel, bustype=bustype) + bus = can.Bus(channel=channel, interface=bustype) for i in range(10): msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) @@ -170,8 +191,7 @@ function: import can - can_interface = 'vcan0' - bus = can.interface.Bus(can_interface, bustype='socketcan') + bus = can.Bus(channel='vcan0', interface='socketcan') message = bus.recv() By default, this performs a blocking read, which means ``bus.recv()`` won't @@ -204,30 +224,39 @@ socket api. This allows the cyclic transmission of CAN messages at given interva The overhead for periodic message sending is extremely low as all the heavy lifting occurs within the linux kernel. -send_periodic() -~~~~~~~~~~~~~~~ +The :class:`~can.BusABC` initialized for `socketcan` interface transparently handles +scheduling of CAN messages to Linux BCM via :meth:`~can.BusABC.send_periodic`: -An example that uses the send_periodic is included in ``python-can/examples/cyclic.py`` +.. code-block:: python -The object returned can be used to halt, alter or cancel the periodic message task. + with can.interface.Bus(interface="socketcan", channel="can0") as bus: + task = bus.send_periodic(...) -.. autoclass:: can.interfaces.socketcan.CyclicSendTask +More examples that uses :meth:`~can.BusABC.send_periodic` are included +in ``python-can/examples/cyclic.py``. + +The `task` object returned by :meth:`~can.BusABC.send_periodic` can be used to halt, +alter or cancel the periodic message task: +.. autoclass:: can.interfaces.socketcan.CyclicSendTask + :members: Bus --- -.. autoclass:: can.interfaces.socketcan.SocketcanBus +The :class:`~can.interfaces.socketcan.SocketcanBus` specializes :class:`~can.BusABC` +to ensure usage of SocketCAN Linux API. The most important differences are: - .. method:: recv(timeout=None) +- usage of SocketCAN BCM for periodic messages scheduling; +- filtering of CAN messages on Linux kernel level. - Block waiting for a message from the Bus. +.. autoclass:: can.interfaces.socketcan.SocketcanBus + :members: + :inherited-members: - :param float timeout: - seconds to wait for a message or None to wait indefinitely +.. External references - :rtype: can.Message or None - :return: - None on timeout or a :class:`can.Message` object. - :raises can.CanError: - if an error occurred while reading +.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt +.. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module +.. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand +.. _can-utils: https://github.com/linux-can/can-utils diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index a936e693e..dcd45f1bf 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -1,7 +1,7 @@ Vector ====== -This interface adds support for CAN controllers by `Vector`_. +This interface adds support for CAN controllers by `Vector`_. Only Windows is supported. By default this library uses the channel configuration for CANalyzer. To use a different application, open Vector Hardware Config program and create diff --git a/doc/listeners.rst b/doc/listeners.rst index 975de6fd1..8e2a79a8b 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -30,6 +30,24 @@ readers are also documented here. completely unchanged message again, since some properties are not (yet) supported by some file formats. +.. note :: + + Additional file formats for both reading/writing log files can be added via + a plugin reader/writer. An external package can register a new reader + by using the ``can.io.message_reader`` entry point. Similarly, a writer can + be added using the ``can.io.message_writer`` entry point. + + The format of the entry point is ``reader_name=module:classname`` where ``classname`` + is a :class:`can.io.generic.BaseIOHandler` concrete implementation. + + :: + + entry_points={ + 'can.io.message_reader': [ + '.asc = my_package.io.asc:ASCReader' + ] + }, + BufferedReader -------------- @@ -41,6 +59,13 @@ BufferedReader :members: +RedirectReader +-------------- + +.. autoclass:: can.RedirectReader + :members: + + Logger ------ diff --git a/doc/message.rst b/doc/message.rst index 921748cb9..e5745f6b5 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -145,6 +145,13 @@ Message Indicates that this message is a CAN FD message. + .. attribute:: is_rx + + :type: bool + + Indicates whether this message is a transmitted (Tx) or received (Rx) frame + + .. attribute:: bitrate_switch :type: bool diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 3e71ae6db..b2c84b9d1 100644 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -1,44 +1,61 @@ +#!/usr/bin/env python + +""" +This example demonstrates how to use async IO with python-can. +""" + import asyncio import can + def print_message(msg): """Regular callback function. Can also be a coroutine.""" print(msg) + async def main(): - can0 = can.Bus('vcan0', bustype='virtual', receive_own_messages=True) + """The main function that runs in the loop.""" + + bus = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) reader = can.AsyncBufferedReader() - logger = can.Logger('logfile.asc') + logger = can.Logger("logfile.asc") listeners = [ print_message, # Callback function - reader, # AsyncBufferedReader() listener - logger # Regular Listener object + reader, # AsyncBufferedReader() listener + logger, # Regular Listener object ] # Create Notifier with an explicit loop to use for scheduling of callbacks loop = asyncio.get_event_loop() - notifier = can.Notifier(can0, listeners, loop=loop) + notifier = can.Notifier(bus, listeners, loop=loop) # Start sending first message - can0.send(can.Message(arbitration_id=0)) + bus.send(can.Message(arbitration_id=0)) - print('Bouncing 10 messages...') + print("Bouncing 10 messages...") for _ in range(10): # Wait for next message from AsyncBufferedReader msg = await reader.get_message() # Delay response await asyncio.sleep(0.5) msg.arbitration_id += 1 - can0.send(msg) + bus.send(msg) # Wait for last message to arrive await reader.get_message() - print('Done!') + print("Done!") # Clean-up notifier.stop() - can0.shutdown() + bus.shutdown() + + +if __name__ == "__main": + try: + # Get the default event loop + LOOP = asyncio.get_event_loop() + # Run until main coroutine finishes + LOOP.run_until_complete(main()) + finally: + LOOP.close() -# Get the default event loop -loop = asyncio.get_event_loop() -# Run until main coroutine finishes -loop.run_until_complete(main()) -loop.close() + # or on Python 3.7+ simply + # asyncio.run(main()) diff --git a/examples/crc.py b/examples/crc.py new file mode 100644 index 000000000..4344fb2ea --- /dev/null +++ b/examples/crc.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +""" +This example exercises the periodic task's multiple message sending capabilities +to send a message containing a counter and a checksum. + +Expects a vcan0 interface: + + python3 -m examples.crc + +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def crc_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout. Modifies messages + after 8 seconds, sends for 10 more seconds, then stops. + """ + msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0]) + messages = build_crc_msgs(msg) + + print( + "Starting to send a message with updating counter and checksum every 1 s for 8 s" + ) + task = bus.send_periodic(messages, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(8) + + msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0]) + messages = build_crc_msgs(msg) + + print("Sending modified message data every 1 s for 10 s") + task.modify_data(messages) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def build_crc_msgs(msg): + """ + Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters + and SPN 3188 checksums placed in the final byte. + """ + messages = [] + + for counter in range(16): + checksum = compute_xbr_checksum(msg, counter) + msg.data[7] = counter + (checksum << 4) + messages.append( + can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:]) + ) + + return messages + + +def compute_xbr_checksum(message, counter): + """ + Computes an XBR checksum per SAE J1939 SPN 3188. + """ + checksum = sum(message.data[:7]) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) + checksum += counter & 0x0F + xbr_checksum = ((checksum >> 4) + checksum) & 0x0F + + return xbr_checksum + + +if __name__ == "__main__": + for interface, channel in [("socketcan", "vcan0")]: + print(f"Carrying out crc test with {interface} interface") + + with can.Bus( # type: ignore + interface=interface, channel=channel, bitrate=500000 + ) as BUS: + crc_send(BUS) + + time.sleep(2) diff --git a/examples/cyclic.py b/examples/cyclic.py index 021af14bf..573465d78 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This example exercises the periodic sending capabilities. @@ -10,8 +9,6 @@ """ -from __future__ import print_function - import logging import time @@ -26,7 +23,9 @@ def simple_periodic_send(bus): Sleeps for 2 seconds then stops the task. """ print("Starting to send a message every 200ms for 2s") - msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False) + msg = can.Message( + arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False + ) task = bus.send_periodic(msg, 0.20) assert isinstance(task, can.CyclicSendTaskABC) time.sleep(2) @@ -35,11 +34,14 @@ def simple_periodic_send(bus): def limited_periodic_send(bus): + """Send using LimitedDurationCyclicSendTaskABC.""" print("Starting to send a message every 200ms for 1s") - msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True) + msg = can.Message( + arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True + ) task = bus.send_periodic(msg, 0.20, 1, store_task=False) if not isinstance(task, can.LimitedDurationCyclicSendTaskABC): - print("This interface doesn't seem to support a ") + print("This interface doesn't seem to support LimitedDurationCyclicSendTaskABC") task.stop() return @@ -48,12 +50,13 @@ def limited_periodic_send(bus): # Note the (finished) task will still be tracked by the Bus # unless we pass `store_task=False` to bus.send_periodic # alternatively calling stop removes the task from the bus - #task.stop() + # task.stop() def test_periodic_send_with_modifying_data(bus): - print("Starting to send a message every 200ms. Initial data is ones") - msg = can.Message(arbitration_id=0x0cf02200, data=[1, 1, 1, 1]) + """Send using ModifiableCyclicTaskABC.""" + print("Starting to send a message every 200ms. Initial data is four consecutive 1s") + msg = can.Message(arbitration_id=0x0CF02200, data=[1, 1, 1, 1]) task = bus.send_periodic(msg, 0.20) if not isinstance(task, can.ModifiableCyclicTaskABC): print("This interface doesn't seem to support modification") @@ -68,7 +71,7 @@ def test_periodic_send_with_modifying_data(bus): task.stop() print("stopped cyclic send") print("Changing data of stopped task to single ff byte") - msg.data = bytearray([0xff]) + msg.data = bytearray([0xFF]) msg.dlc = 1 task.modify_data(msg) time.sleep(1) @@ -105,17 +108,13 @@ def test_periodic_send_with_modifying_data(bus): # print("done") -if __name__ == "__main__": - - reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False) - - for interface, channel in [ - ('socketcan', 'vcan0'), - #('ixxat', 0) - ]: - print("Carrying out cyclic tests with {} interface".format(interface)) +def main(): + """Test different cyclic sending tasks.""" + reset_msg = can.Message( + arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False + ) - bus = can.Bus(interface=interface, channel=channel, bitrate=500000) + with can.Bus(interface="virtual") as bus: bus.send(reset_msg) simple_periodic_send(bus) @@ -126,10 +125,12 @@ def test_periodic_send_with_modifying_data(bus): test_periodic_send_with_modifying_data(bus) - #print("Carrying out multirate cyclic test for {} interface".format(interface)) - #can.rc['interface'] = interface - #test_dual_rate_periodic_send() - - bus.shutdown() + # print("Carrying out multirate cyclic test for {} interface".format(interface)) + # can.rc['interface'] = interface + # test_dual_rate_periodic_send() time.sleep(2) + + +if __name__ == "__main__": + main() diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py new file mode 100644 index 000000000..64f0862d7 --- /dev/null +++ b/examples/cyclic_multiple.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +""" +This example exercises the periodic task's multiple message sending capabilities + +Expects a vcan0 interface: + + python3 -m examples.cyclic_multiple + +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def cyclic_multiple_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout + Sleeps for 10 seconds then stops the task. + """ + print("Starting to send a message every 1 s for 10 s") + messages = [] + + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + task = bus.send_periodic(messages, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def cyclic_multiple_send_modify(bus): + """ + Sends initial set of 3 Messages containing Odd data sent every 1 s with + no explicit timeout. Sleeps for 8 s. + + Then the set is updated to 3 Messages containing Even data. + Sleeps for 10 s. + """ + messages_odd = [] + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages_even = [] + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x66, 0x66, 0x66, 0x66, 0x66, 0x66], + is_extended_id=False, + ) + ) + print("Starting to send a message with odd every 1 s for 8 s with odd data") + task = bus.send_periodic(messages_odd, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(8) + print("Starting to send a message with even data every 1 s for 10 s with even data") + task.modify_data(messages_even) + time.sleep(10) + print("stopped cyclic modify send") + + +if __name__ == "__main__": + for interface, channel in [("socketcan", "vcan0")]: + print(f"Carrying out cyclic multiple tests with {interface} interface") + + with can.Bus( # type: ignore + interface=interface, channel=channel, bitrate=500000 + ) as BUS: + cyclic_multiple_send(BUS) + cyclic_multiple_send_modify(BUS) + + time.sleep(2) diff --git a/examples/receive_all.py b/examples/receive_all.py index 44a495de7..7ff532079 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -1,26 +1,33 @@ #!/usr/bin/env python -from __future__ import print_function +""" +Shows how the receive messages via polling. +""" import can from can.bus import BusState def receive_all(): + """Receives all messages and prints them to the console until Ctrl+C is pressed.""" - bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - #bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - #bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + with can.interface.Bus( + bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000 + ) as bus: + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) - bus.state = BusState.ACTIVE # or BusState.PASSIVE + # set to read-only, only supported on some interfaces + bus.state = BusState.PASSIVE - try: - while True: - msg = bus.recv(1) - if msg is not None: - print(msg) - except KeyboardInterrupt: - pass + try: + while True: + msg = bus.recv(1) + if msg is not None: + print(msg) + + except KeyboardInterrupt: + pass # exit normally if __name__ == "__main__": diff --git a/examples/send_one.py b/examples/send_one.py index 2533ca37c..49a4f1ee1 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -1,36 +1,36 @@ #!/usr/bin/env python -# coding: utf-8 """ This example shows how sending a single message works. """ -from __future__ import print_function - import can + def send_one(): + """Sends a single message.""" # this uses the default configuration (for example from the config file) # see https://python-can.readthedocs.io/en/stable/configuration.html - bus = can.interface.Bus() - - # Using specific buses works similar: - # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) - # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) - # ... - - msg = can.Message(arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4, 1], - is_extended_id=True) - - try: - bus.send(msg) - print("Message sent on {}".format(bus.channel_info)) - except can.CanError: - print("Message NOT sent") - -if __name__ == '__main__': + with can.interface.Bus() as bus: + + # Using specific buses works similar: + # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) + # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # ... + + msg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) + + try: + bus.send(msg) + print(f"Message sent on {bus.channel_info}") + except can.CanError: + print("Message NOT sent") + + +if __name__ == "__main__": send_one() diff --git a/examples/serial_com.py b/examples/serial_com.py index efa0bcdb5..60aeec4ce 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -1,8 +1,7 @@ #!/usr/bin/env python -# coding: utf-8 """ -This example sends every second a messages over the serial interface and also +This example sends every second a messages over the serial interface and also receives incoming messages. python3 -m examples.serial_com @@ -19,8 +18,6 @@ com0com: http://com0com.sourceforge.net/ """ -from __future__ import print_function - import time import threading @@ -28,17 +25,19 @@ def send_cyclic(bus, msg, stop_event): + """The loop for sending.""" print("Start to send a message every 1s") start_time = time.time() while not stop_event.is_set(): msg.timestamp = time.time() - start_time bus.send(msg) - print("tx: {}".format(tx_msg)) + print(f"tx: {msg}") time.sleep(1) print("Stopped sending messages") def receive(bus, stop_event): + """The loop for receiving.""" print("Start receiving messages") while not stop_event.is_set(): rx_msg = bus.recv(1) @@ -46,28 +45,37 @@ def receive(bus, stop_event): print("rx: {}".format(rx_msg)) print("Stopped receiving messages") -if __name__ == "__main__": - server = can.interface.Bus(bustype='serial', channel='/dev/ttyS10') - client = can.interface.Bus(bustype='serial', channel='/dev/ttyS11') - - tx_msg = can.Message(arbitration_id=0x01, data=[0x11, 0x22, 0x33, 0x44, - 0x55, 0x66, 0x77, 0x88]) - - # Thread for sending and receiving messages - stop_event = threading.Event() - t_send_cyclic = threading.Thread(target=send_cyclic, args=(server, tx_msg, - stop_event)) - t_receive = threading.Thread(target=receive, args=(client, stop_event)) - t_receive.start() - t_send_cyclic.start() - - try: - while True: - pass - except KeyboardInterrupt: - pass - - stop_event.set() - server.shutdown() - client.shutdown() + +def main(): + """Controles the sender and receiver.""" + with can.interface.Bus(bustype="serial", channel="/dev/ttyS10") as server: + with can.interface.Bus(bustype="serial", channel="/dev/ttyS11") as client: + + tx_msg = can.Message( + arbitration_id=0x01, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + + # Thread for sending and receiving messages + stop_event = threading.Event() + t_send_cyclic = threading.Thread( + target=send_cyclic, args=(server, tx_msg, stop_event) + ) + t_receive = threading.Thread(target=receive, args=(client, stop_event)) + t_receive.start() + t_send_cyclic.start() + + try: + while True: + time.sleep(0) # yield + except KeyboardInterrupt: + pass # exit normally + + stop_event.set() + time.sleep(0.5) + print("Stopped script") + + +if __name__ == "__main__": + main() diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py index 782ac9b7c..f01546375 100755 --- a/examples/simple_log_converter.py +++ b/examples/simple_log_converter.py @@ -1,19 +1,26 @@ #!/usr/bin/env python -# coding: utf-8 """ Use this to convert .can/.asc files to .log files. +Can be easily adapted for all sorts of files. -Usage: simpleLogConvert.py sourceLog.asc targetLog.log +Usage: python3 simple_log_convert.py sourceLog.asc targetLog.log """ import sys -import can.io.logger -import can.io.player +import can -reader = can.io.player.LogReader(sys.argv[1]) -writer = can.io.logger.Logger(sys.argv[2]) -for msg in reader: - writer.on_message_received(msg) +def main(): + """The transcoder""" + + with can.LogReader(sys.argv[1]) as reader: + with can.Logger(sys.argv[2]) as writer: + + for msg in reader: + writer.on_message_received(msg) + + +if __name__ == "__main__": + main() diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index cf7e1f8e3..fa6c71547 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This shows how message filtering works. @@ -9,16 +8,25 @@ import can -if __name__ == '__main__': - bus = can.interface.Bus(bustype='socketcan', - channel='vcan0', - receive_own_messages=True) - can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] - bus.set_filters(can_filters) - notifier = can.Notifier(bus, [can.Printer()]) - bus.send(can.Message(arbitration_id=1, is_extended_id=True)) - bus.send(can.Message(arbitration_id=2, is_extended_id=True)) - bus.send(can.Message(arbitration_id=1, is_extended_id=False)) +def main(): + """Send some messages to itself and apply filtering.""" + with can.Bus(bustype="virtual", receive_own_messages=True) as bus: - time.sleep(10) + can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] + bus.set_filters(can_filters) + + # print all incoming messages, wich includes the ones sent, + # since we set receive_own_messages to True + # assign to some variable so it does not garbage collected + notifier = can.Notifier(bus, [can.Printer()]) # pylint: disable=unused-variable + + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) + + time.sleep(1.0) + + +if __name__ == "__main__": + main() diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index b69fb28da..b3fdefc09 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This demo creates multiple processes of producers to spam a socketcan bus. @@ -11,21 +10,24 @@ import can -def producer(id, message_count=16): +def producer(thread_id: int, message_count: int = 16): """Spam the bus with messages including the data id. - :param int id: the id of the thread/process + :param thread_id: the id of the thread/process + :param message_count: the number of messages that shall be send """ - - with can.Bus(bustype='socketcan', channel='vcan0') as bus: + with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore for i in range(message_count): - msg = can.Message(arbitration_id=0x0cf02200+id, data=[id, i, 0, 1, 3, 1, 4, 1]) + msg = can.Message( + arbitration_id=0x0CF02200 + thread_id, + data=[thread_id, i, 0, 1, 3, 1, 4, 1], + ) bus.send(msg) sleep(1.0) - print("Producer #{} finished sending {} messages".format(id, message_count)) + print(f"Producer #{thread_id} finished sending {message_count} messages") if __name__ == "__main__": - with ProcessPoolExecutor(max_workers=4) as executor: + with ProcessPoolExecutor() as executor: executor.map(producer, range(5)) diff --git a/requirements-lint.txt b/requirements-lint.txt new file mode 100644 index 000000000..d0b374c20 --- /dev/null +++ b/requirements-lint.txt @@ -0,0 +1,4 @@ +pylint==2.3.1 +black==19.3b0 +mypy==0.740 +mypy-extensions==0.4.1 diff --git a/scripts/can_logger.py b/scripts/can_logger.py index 72a92b9d0..4202448e6 100644 --- a/scripts/can_logger.py +++ b/scripts/can_logger.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.logger`. """ -from __future__ import absolute_import - from can.logger import main diff --git a/scripts/can_player.py b/scripts/can_player.py index afbd3df6e..1fe44175d 100644 --- a/scripts/can_player.py +++ b/scripts/can_player.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.player`. """ -from __future__ import absolute_import - from can.player import main diff --git a/scripts/can_viewer.py b/scripts/can_viewer.py index 3c9ba738c..eef990b0e 100644 --- a/scripts/can_viewer.py +++ b/scripts/can_viewer.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.viewer`. """ -from __future__ import absolute_import - from can.viewer import main diff --git a/setup.cfg b/setup.cfg index 49177e68e..a0e8d5b6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [aliases] test=pytest @@ -15,9 +12,6 @@ addopts = -v --timeout=300 --cov=can --cov-config=setup.cfg branch = False # already specified by call to pytest using --cov=can #source = can -omit = - # legacy code - can/CAN.py [coverage:report] # two digits after decimal point diff --git a/setup.py b/setup.py index c600b7215..8327b1432 100644 --- a/setup.py +++ b/setup.py @@ -1,46 +1,57 @@ #!/usr/bin/env python -# coding: utf-8 """ python-can requires the setuptools package to be installed. """ +# pylint: disable=invalid-name + from __future__ import absolute_import from os import listdir from os.path import isfile, join import re import logging +import sys from setuptools import setup, find_packages logging.basicConfig(level=logging.WARNING) -with open('can/__init__.py', 'r') as fd: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - fd.read(), re.MULTILINE).group(1) +with open("can/__init__.py", "r") as fd: + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE + ).group(1) -with open('README.rst', 'r') as f: +with open("README.rst", "r") as f: long_description = f.read() # Dependencies extras_require = { - 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'] + "seeedstudio": ["pyserial>=3.0"], + "serial": ["pyserial~=3.0"], + "neovi": ["python-ics>=2.12", "filelock"], } tests_require = [ - 'mock~=2.0', - 'pytest~=4.3', - 'pytest-timeout~=1.3', - 'pytest-cov~=2.6', - 'codecov~=2.0', - 'future', - 'six', - 'hypothesis' -] + extras_require['serial'] - -extras_require['test'] = tests_require - + "pytest~=5.3", + "pytest-timeout~=1.3", + "pytest-cov~=2.8", + # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` + # on python 3.6 on MACOS + "coverage<5", + "codecov~=2.0", + "hypothesis~=4.56", +] + extras_require["serial"] + +extras_require["test"] = tests_require + +# Check for 'pytest-runner' only if setup.py was invoked with 'test'. +# This optimizes setup.py for cases when pytest-runner is not needed, +# using the approach that is suggested upstream. +# +# See https://pypi.org/project/pytest-runner/#conditional-requirement +needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] setup( # Description @@ -51,8 +62,6 @@ classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", @@ -73,38 +82,35 @@ "Topic :: System :: Monitoring", "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", - "Topic :: Utilities" + "Topic :: Utilities", ], - # Code version=version, - packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), + packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - # Author author="Brian Thorne", author_email="brian@thorne.link", - # License license="LGPL v3", - # Package data package_data={ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], "doc": ["*.*"], - "examples": ["*.py"] + "examples": ["*.py"], }, - # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires=">=2.7", + python_requires=">=3.6", install_requires=[ - 'wrapt~=1.10', - 'aenum', - 'typing;python_version<"3.5"', + "wrapt~=1.10", + "aenum", 'windows-curses;platform_system=="Windows"', + "filelock", + "mypy_extensions >= 0.4.0, < 0.5.0", + 'pywin32;platform_system=="Windows"', ], - setup_requires=["pytest-runner"], + setup_requires=pytest_runner, extras_require=extras_require, - tests_require=tests_require + tests_require=tests_require, ) diff --git a/test/back2back_test.py b/test/back2back_test.py index 4062d462a..8c1ded6c5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -5,8 +5,6 @@ This module tests two virtual buses attached to each other. """ -from __future__ import absolute_import, print_function - import sys import unittest from time import sleep @@ -29,30 +27,35 @@ class Back2BackTestCase(unittest.TestCase): BITRATE = 500000 TIMEOUT = 0.1 - INTERFACE_1 = 'virtual' - CHANNEL_1 = 'virtual_channel_0' - INTERFACE_2 = 'virtual' - CHANNEL_2 = 'virtual_channel_0' + INTERFACE_1 = "virtual" + CHANNEL_1 = "virtual_channel_0" + INTERFACE_2 = "virtual" + CHANNEL_2 = "virtual_channel_0" def setUp(self): - self.bus1 = can.Bus(channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.Bus(channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.Bus( + channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) + self.bus2 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) def tearDown(self): self.bus1.shutdown() self.bus2.shutdown() def _check_received_message(self, recv_msg, sent_msg): - self.assertIsNotNone(recv_msg, - "No message was received on %s" % self.INTERFACE_2) + self.assertIsNotNone( + recv_msg, "No message was received on %s" % self.INTERFACE_2 + ) self.assertEqual(recv_msg.arbitration_id, sent_msg.arbitration_id) self.assertEqual(recv_msg.is_extended_id, sent_msg.is_extended_id) self.assertEqual(recv_msg.is_remote_frame, sent_msg.is_remote_frame) @@ -81,7 +84,10 @@ def _send_and_receive(self, msg): def test_no_message(self): self.assertIsNone(self.bus1.recv(0.1)) - @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") + @unittest.skipIf( + IS_CI, + "the timing sensitive behaviour cannot be reproduced reliably on a CI server", + ) def test_timestamp(self): self.bus2.send(can.Message()) recv_msg1 = self.bus1.recv(self.TIMEOUT) @@ -89,98 +95,162 @@ def test_timestamp(self): self.bus2.send(can.Message()) recv_msg2 = self.bus1.recv(self.TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp - self.assertTrue(1.75 <= delta_time <= 2.25, - 'Time difference should have been 2s +/- 250ms.' - 'But measured {}'.format(delta_time)) + self.assertTrue( + 1.75 <= delta_time <= 2.25, + "Time difference should have been 2s +/- 250ms." + "But measured {}".format(delta_time), + ) def test_standard_message(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x100, - data=[1, 2, 3, 4, 5, 6, 7, 8]) + msg = can.Message( + is_extended_id=False, arbitration_id=0x100, data=[1, 2, 3, 4, 5, 6, 7, 8] + ) self._send_and_receive(msg) def test_extended_message(self): - msg = can.Message(is_extended_id=True, - arbitration_id=0x123456, - data=[10, 11, 12, 13, 14, 15, 16, 17]) + msg = can.Message( + is_extended_id=True, + arbitration_id=0x123456, + data=[10, 11, 12, 13, 14, 15, 16, 17], + ) self._send_and_receive(msg) def test_remote_message(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x200, - is_remote_frame=True, - dlc=4) + msg = can.Message( + is_extended_id=False, arbitration_id=0x200, is_remote_frame=True, dlc=4 + ) self._send_and_receive(msg) def test_dlc_less_than_eight(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x300, - data=[4, 5, 6]) + msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) + def test_message_direction(self): + # Verify that own message received has is_rx set to False while message + # received on the other virtual interfaces have is_rx set to True + if self.INTERFACE_1 != "virtual": + raise unittest.SkipTest( + "Message direction not yet implemented for socketcan" + ) + bus3 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + receive_own_messages=True, + ) + try: + msg = can.Message( + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] + ) + bus3.send(msg) + recv_msg_bus1 = self.bus1.recv(self.TIMEOUT) + recv_msg_bus2 = self.bus2.recv(self.TIMEOUT) + self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) + + self.assertTrue(recv_msg_bus1.is_rx) + self.assertTrue(recv_msg_bus2.is_rx) + self.assertFalse(self_recv_msg_bus3.is_rx) + finally: + bus3.shutdown() + + def test_unique_message_instances(self): + # Verify that we have a different instances of message for each bus + if self.INTERFACE_1 != "virtual": + raise unittest.SkipTest("Not relevant for socketcan") + bus3 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + receive_own_messages=True, + ) + try: + msg = can.Message( + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] + ) + bus3.send(msg) + recv_msg_bus1 = self.bus1.recv(self.TIMEOUT) + recv_msg_bus2 = self.bus2.recv(self.TIMEOUT) + self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) + + self._check_received_message(recv_msg_bus1, recv_msg_bus2) + self._check_received_message(recv_msg_bus2, self_recv_msg_bus3) + + recv_msg_bus1.data[0] = 4 + self.assertNotEqual(recv_msg_bus1.data, recv_msg_bus2.data) + self.assertEqual(recv_msg_bus2.data, self_recv_msg_bus3.data) + finally: + bus3.shutdown() + @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): - msg = can.Message(is_fd=True, - is_extended_id=True, - arbitration_id=0x56789, - data=[0xff] * 64) + msg = can.Message( + is_fd=True, is_extended_id=True, arbitration_id=0x56789, data=[0xFF] * 64 + ) self._send_and_receive(msg) @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message_with_brs(self): - msg = can.Message(is_fd=True, - bitrate_switch=True, - is_extended_id=True, - arbitration_id=0x98765, - data=[0xff] * 48) + msg = can.Message( + is_fd=True, + bitrate_switch=True, + is_extended_id=True, + arbitration_id=0x98765, + data=[0xFF] * 48, + ) self._send_and_receive(msg) @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): - INTERFACE_1 = 'socketcan' - CHANNEL_1 = 'vcan0' - INTERFACE_2 = 'socketcan' - CHANNEL_2 = 'vcan0' + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): - def setUp(self): - self.broadcast_bus = can.Bus(channel='', bustype='socketcan') - self.regular_bus = can.Bus(channel='vcan0', bustype='socketcan') + self.broadcast_bus = can.Bus(channel="", bustype="socketcan") + self.regular_bus = can.Bus(channel="vcan0", bustype="socketcan") def tearDown(self): self.broadcast_bus.shutdown() self.regular_bus.shutdown() def test_broadcast_channel(self): - self.broadcast_bus.send(can.Message(channel='vcan0')) + self.broadcast_bus.send(can.Message(channel="vcan0")) recv_msg = self.regular_bus.recv(1) self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 'vcan0') + self.assertEqual(recv_msg.channel, "vcan0") self.regular_bus.send(can.Message()) recv_msg = self.broadcast_bus.recv(1) self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 'vcan0') + self.assertEqual(recv_msg.channel, "vcan0") class TestThreadSafeBus(Back2BackTestCase): - def setUp(self): - self.bus1 = can.ThreadSafeBus(channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.ThreadSafeBus(channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.ThreadSafeBus( + channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) + self.bus2 = can.ThreadSafeBus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) @pytest.mark.timeout(5.0) def test_concurrent_writes(self): @@ -192,7 +262,7 @@ def test_concurrent_writes(self): channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.365, - data=[254, 255, 1, 2] + data=[254, 255, 1, 2], ) workload = 1000 * [message] @@ -223,19 +293,19 @@ def test_filtered_bus(self): channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.365, - data=[254, 255, 1, 2] + data=[254, 255, 1, 2], ) excluded_message = can.Message( arbitration_id=0x02, channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.300, - data=[1, 2, 3] + data=[1, 2, 3], ) workload = 500 * [included_message] + 500 * [excluded_message] random.shuffle(workload) - self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xff, "extended": True}]) + self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xFF, "extended": True}]) def sender(msg): self.bus1.send(msg) @@ -258,5 +328,5 @@ def receiver(_): receiver_pool.join() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/config.py b/test/config.py index 940ba7cf0..3a9072712 100644 --- a/test/config.py +++ b/test/config.py @@ -12,8 +12,8 @@ from os import environ as environment -def env(name): # type: bool - return environment.get(name, '').lower() in ("yes", "true", "t", "1") +def env(name): # type: bool + return environment.get(name, "").lower() in ("yes", "true", "t", "1") # ############################## Continuos integration @@ -22,13 +22,15 @@ def env(name): # type: bool # - https://docs.travis-ci.com/user/environment-variables/ # - https://www.appveyor.com/docs/environment-variables/ -IS_TRAVIS = env('TRAVIS') -IS_APPVEYOR = env('APPVEYOR') +IS_TRAVIS = env("TRAVIS") +IS_APPVEYOR = env("APPVEYOR") -IS_CI = IS_TRAVIS or IS_APPVEYOR or env('CI') or env('CONTINUOUS_INTEGRATION') +IS_CI = IS_TRAVIS or IS_APPVEYOR or env("CI") or env("CONTINUOUS_INTEGRATION") if IS_APPVEYOR and IS_TRAVIS: - raise EnvironmentError("IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time") + raise EnvironmentError( + "IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time" + ) # ############################## Platforms @@ -41,9 +43,11 @@ def env(name): # type: bool if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): raise EnvironmentError( - "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format(IS_WINDOWS, IS_LINUX, IS_OSX) + - "can be True at the same time " + - '(platform.system() == "{}")'.format(platform.system()) + "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format( + IS_WINDOWS, IS_LINUX, IS_OSX + ) + + "can be True at the same time " + + '(platform.system() == "{}")'.format(platform.system()) ) @@ -51,4 +55,4 @@ def env(name): # type: bool TEST_CAN_FD = True -TEST_INTERFACE_SOCKETCAN = IS_LINUX and env('TEST_SOCKETCAN') +TEST_INTERFACE_SOCKETCAN = IS_LINUX and env("TEST_SOCKETCAN") diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index 35bc045da..95785b128 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -10,13 +10,16 @@ class ContextManagerTest(unittest.TestCase): - def setUp(self): data = [0, 1, 2, 3, 4, 5, 6, 7] - self.msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) + self.msg_send = can.Message( + is_extended_id=False, arbitration_id=0x100, data=data + ) def test_open_buses(self): - with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: + with can.Bus(interface="virtual") as bus_send, can.Bus( + interface="virtual" + ) as bus_recv: bus_send.send(self.msg_send) msg_recv = bus_recv.recv() @@ -24,7 +27,9 @@ def test_open_buses(self): self.assertTrue(msg_recv) def test_use_closed_bus(self): - with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: + with can.Bus(interface="virtual") as bus_send, can.Bus( + interface="virtual" + ) as bus_recv: bus_send.send(self.msg_send) # Receiving a frame after bus has been closed should raise a CanException @@ -32,5 +37,5 @@ def test_use_closed_bus(self): self.assertRaises(can.CanError, bus_send.send, self.msg_send) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/data/example_data.py b/test/data/example_data.py index dd433dc3c..773212c32 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -22,7 +22,7 @@ def sort_messages(messages): :param Iterable[can.Message] messages: a sequence of messages to sort :rtype: list """ - return list(sorted(messages, key=attrgetter('timestamp'))) + return list(sorted(messages, key=attrgetter("timestamp"))) # some random number @@ -30,125 +30,147 @@ def sort_messages(messages): # List of messages of different types that can be used in tests -TEST_MESSAGES_BASE = sort_messages([ - Message( - # empty - ), - Message( - # only data - data=[0x00, 0x42] - ), - Message( - # no data - arbitration_id=0xAB, is_extended_id=False - ), - Message( - # no data - arbitration_id=0x42, is_extended_id=True - ), - Message( - # no data - arbitration_id=0xABCDEF, - ), - Message( - # empty data - data=[] - ), - Message( - # empty data - data=[0xFF, 0xFE, 0xFD], - ), - Message( - # with channel as integer - channel=0, - ), - Message( - # with channel as integer - channel=42, - ), - Message( - # with channel as string - channel="vcan0", - ), - Message( - # with channel as string - channel="awesome_channel", - ), - Message( - arbitration_id=0xABCDEF, is_extended_id=True, - timestamp=TEST_TIME, - data=[1, 2, 3, 4, 5, 6, 7, 8] - ), - Message( - arbitration_id=0x123, is_extended_id=False, - timestamp=TEST_TIME + 42.42, - data=[0xff, 0xff] - ), - Message( - arbitration_id=0xDADADA, is_extended_id=True, - timestamp=TEST_TIME + .165, - data=[1, 2, 3, 4, 5, 6, 7, 8] - ), - Message( - arbitration_id=0x123, is_extended_id=False, - timestamp=TEST_TIME + .365, - data=[254, 255] - ), - Message( - arbitration_id=0x768, is_extended_id=False, - timestamp=TEST_TIME + 3.165 - ), -]) - - -TEST_MESSAGES_REMOTE_FRAMES = sort_messages([ - Message( - arbitration_id=0xDADADA, is_extended_id=True, is_remote_frame=True, - timestamp=TEST_TIME + .165, - ), - Message( - arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, - timestamp=TEST_TIME + .365, - ), - Message( - arbitration_id=0x768, is_extended_id=False, is_remote_frame=True, - timestamp=TEST_TIME + 3.165 - ), - Message( - arbitration_id=0xABCDEF, is_extended_id=True, is_remote_frame=True, - timestamp=TEST_TIME + 7858.67 - ), -]) - - -TEST_MESSAGES_ERROR_FRAMES = sort_messages([ - Message( - is_error_frame=True - ), - Message( - is_error_frame=True, - timestamp=TEST_TIME + 0.170 - ), - Message( - is_error_frame=True, - timestamp=TEST_TIME + 17.157 - ) -]) - - -TEST_ALL_MESSAGES = sort_messages(TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + \ - TEST_MESSAGES_ERROR_FRAMES) +TEST_MESSAGES_BASE = sort_messages( + [ + Message( + # empty + ), + Message( + # only data + data=[0x00, 0x42] + ), + Message( + # no data + arbitration_id=0xAB, + is_extended_id=False, + ), + Message( + # no data + arbitration_id=0x42, + is_extended_id=True, + ), + Message( + # no data + arbitration_id=0xABCDEF + ), + Message( + # empty data + data=[] + ), + Message( + # empty data + data=[0xFF, 0xFE, 0xFD] + ), + Message( + # with channel as integer + channel=0 + ), + Message( + # with channel as integer + channel=42 + ), + Message( + # with channel as string + channel="vcan0" + ), + Message( + # with channel as string + channel="awesome_channel" + ), + Message( + arbitration_id=0xABCDEF, + is_extended_id=True, + timestamp=TEST_TIME, + data=[1, 2, 3, 4, 5, 6, 7, 8], + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + timestamp=TEST_TIME + 42.42, + data=[0xFF, 0xFF], + ), + Message( + arbitration_id=0xDADADA, + is_extended_id=True, + timestamp=TEST_TIME + 0.165, + data=[1, 2, 3, 4, 5, 6, 7, 8], + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + timestamp=TEST_TIME + 0.365, + data=[254, 255], + ), + Message( + arbitration_id=0x768, is_extended_id=False, timestamp=TEST_TIME + 3.165 + ), + ] +) + + +TEST_MESSAGES_CAN_FD = sort_messages( + [ + Message(is_fd=True, data=range(64)), + Message(is_fd=True, data=range(8)), + Message(is_fd=True, bitrate_switch=True), + Message(is_fd=True, error_state_indicator=True), + ] +) + + +TEST_MESSAGES_REMOTE_FRAMES = sort_messages( + [ + Message( + arbitration_id=0xDADADA, + is_extended_id=True, + is_remote_frame=True, + timestamp=TEST_TIME + 0.165, + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + is_remote_frame=True, + timestamp=TEST_TIME + 0.365, + ), + Message( + arbitration_id=0x768, + is_extended_id=False, + is_remote_frame=True, + timestamp=TEST_TIME + 3.165, + ), + Message( + arbitration_id=0xABCDEF, + is_extended_id=True, + is_remote_frame=True, + timestamp=TEST_TIME + 7858.67, + ), + ] +) + + +TEST_MESSAGES_ERROR_FRAMES = sort_messages( + [ + Message(is_error_frame=True), + Message(is_error_frame=True, timestamp=TEST_TIME + 0.170), + Message(is_error_frame=True, timestamp=TEST_TIME + 17.157), + ] +) + + +TEST_ALL_MESSAGES = sort_messages( + TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES +) TEST_COMMENTS = [ "This is the first comment", - "", # empty comment + "", # empty comment "This third comment contains some strange characters: 'ä\"§$%&/()=?__::_Öüßêè and ends here.", ( - "This fourth comment is quite long! " \ - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " \ - "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " \ - "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi." \ + "This fourth comment is quite long! " + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " + "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " + "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi." ), ] @@ -159,4 +181,9 @@ def generate_message(arbitration_id): and a non-extended ID. """ data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) - return Message(arbitration_id=arbitration_id, data=data, is_extended_id=False, timestamp=TEST_TIME) + return Message( + arbitration_id=arbitration_id, + data=data, + is_extended_id=False, + timestamp=TEST_TIME, + ) diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 4b7c64363..8582cbf05 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -9,10 +9,20 @@ Begin Triggerblock Sam Sep 30 15:06:13.191 2017 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x + 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x + 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% End TriggerBlock diff --git a/test/data/logfile.blf b/test/data/logfile.blf deleted file mode 100644 index 98cafe214..000000000 Binary files a/test/data/logfile.blf and /dev/null differ diff --git a/test/data/test_CanErrorFrameExt.blf b/test/data/test_CanErrorFrameExt.blf new file mode 100644 index 000000000..f7ea6eb35 Binary files /dev/null and b/test/data/test_CanErrorFrameExt.blf differ diff --git a/test/data/test_CanFdMessage.blf b/test/data/test_CanFdMessage.blf new file mode 100644 index 000000000..55b48bfe4 Binary files /dev/null and b/test/data/test_CanFdMessage.blf differ diff --git a/test/data/test_CanFdMessage64.blf b/test/data/test_CanFdMessage64.blf new file mode 100644 index 000000000..f26eccfde Binary files /dev/null and b/test/data/test_CanFdMessage64.blf differ diff --git a/test/data/test_CanMessage.blf b/test/data/test_CanMessage.blf new file mode 100644 index 000000000..f0ad20365 Binary files /dev/null and b/test/data/test_CanMessage.blf differ diff --git a/test/data/test_CanMessage2.blf b/test/data/test_CanMessage2.blf new file mode 100644 index 000000000..6beb4fa43 Binary files /dev/null and b/test/data/test_CanMessage2.blf differ diff --git a/test/listener_test.py b/test/listener_test.py index c25a6fb56..6d24df190 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -4,14 +4,10 @@ """ """ -from __future__ import absolute_import, print_function - -from time import sleep import unittest import random import logging import tempfile -import sqlite3 import os from os.path import join, dirname @@ -19,8 +15,8 @@ from .data.example_data import generate_message -channel = 'virtual_channel_0' -can.rc['interface'] = 'virtual' +channel = "virtual_channel_0" +can.rc["interface"] = "virtual" logging.basicConfig(level=logging.DEBUG) @@ -29,37 +25,35 @@ class ListenerImportTest(unittest.TestCase): - def testClassesImportable(self): - self.assertTrue(hasattr(can, 'Listener')) - self.assertTrue(hasattr(can, 'BufferedReader')) - self.assertTrue(hasattr(can, 'Notifier')) - self.assertTrue(hasattr(can, 'Logger')) + self.assertTrue(hasattr(can, "Listener")) + self.assertTrue(hasattr(can, "BufferedReader")) + self.assertTrue(hasattr(can, "Notifier")) + self.assertTrue(hasattr(can, "Logger")) - self.assertTrue(hasattr(can, 'ASCWriter')) - self.assertTrue(hasattr(can, 'ASCReader')) + self.assertTrue(hasattr(can, "ASCWriter")) + self.assertTrue(hasattr(can, "ASCReader")) - self.assertTrue(hasattr(can, 'BLFReader')) - self.assertTrue(hasattr(can, 'BLFWriter')) + self.assertTrue(hasattr(can, "BLFReader")) + self.assertTrue(hasattr(can, "BLFWriter")) - self.assertTrue(hasattr(can, 'CSVReader')) - self.assertTrue(hasattr(can, 'CSVWriter')) + self.assertTrue(hasattr(can, "CSVReader")) + self.assertTrue(hasattr(can, "CSVWriter")) - self.assertTrue(hasattr(can, 'CanutilsLogWriter')) - self.assertTrue(hasattr(can, 'CanutilsLogReader')) + self.assertTrue(hasattr(can, "CanutilsLogWriter")) + self.assertTrue(hasattr(can, "CanutilsLogReader")) - self.assertTrue(hasattr(can, 'SqliteReader')) - self.assertTrue(hasattr(can, 'SqliteWriter')) + self.assertTrue(hasattr(can, "SqliteReader")) + self.assertTrue(hasattr(can, "SqliteWriter")) - self.assertTrue(hasattr(can, 'Printer')) + self.assertTrue(hasattr(can, "Printer")) - self.assertTrue(hasattr(can, 'LogReader')) + self.assertTrue(hasattr(can, "LogReader")) - self.assertTrue(hasattr(can, 'MessageSync')) + self.assertTrue(hasattr(can, "MessageSync")) class BusTest(unittest.TestCase): - def setUp(self): self.bus = can.interface.Bus() @@ -68,7 +62,6 @@ def tearDown(self): class ListenerTest(BusTest): - def testBasicListenerCanBeAddedToNotifier(self): a_listener = can.Printer() notifier = can.Notifier(self.bus, [a_listener], 0.1) @@ -97,10 +90,14 @@ def test_filetype_to_instance(extension, klass): try: if extension == ".blf": delete = False - file_handler = open(join(dirname(__file__), "data/logfile.blf")) + file_handler = open( + join(dirname(__file__), "data", "test_CanMessage.blf") + ) else: delete = True - file_handler = tempfile.NamedTemporaryFile(suffix=extension, delete=False) + file_handler = tempfile.NamedTemporaryFile( + suffix=extension, delete=False + ) with file_handler as my_file: filename = my_file.name @@ -113,18 +110,22 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".asc", can.ASCReader) test_filetype_to_instance(".blf", can.BLFReader) test_filetype_to_instance(".csv", can.CSVReader) - test_filetype_to_instance(".db" , can.SqliteReader) + test_filetype_to_instance(".db", can.SqliteReader) test_filetype_to_instance(".log", can.CanutilsLogReader) - # test file extensions that are not supported - with self.assertRaisesRegexp(NotImplementedError, ".xyz_42"): - test_filetype_to_instance(".xyz_42", can.Printer) + def testPlayerTypeResolutionUnsupportedFileTypes(self): + for should_fail_with in ["", ".", ".some_unknown_extention_42"]: + with self.assertRaises(ValueError): + with can.LogReader(should_fail_with): # make sure we close it anyways + pass def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): print("testing: {}".format(extension)) try: - with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as my_file: + with tempfile.NamedTemporaryFile( + suffix=extension, delete=False + ) as my_file: filename = my_file.name with can.Logger(filename) as writer: self.assertIsInstance(writer, klass) @@ -134,17 +135,19 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".asc", can.ASCWriter) test_filetype_to_instance(".blf", can.BLFWriter) test_filetype_to_instance(".csv", can.CSVWriter) - test_filetype_to_instance(".db" , can.SqliteWriter) + test_filetype_to_instance(".db", can.SqliteWriter) test_filetype_to_instance(".log", can.CanutilsLogWriter) test_filetype_to_instance(".txt", can.Printer) - # test file extensions that should use a fallback - test_filetype_to_instance("", can.Printer) - test_filetype_to_instance(".", can.Printer) - test_filetype_to_instance(".some_unknown_extention_42", can.Printer) with can.Logger(None) as logger: self.assertIsInstance(logger, can.Printer) + def testLoggerTypeResolutionUnsupportedFileTypes(self): + for should_fail_with in ["", ".", ".some_unknown_extention_42"]: + with self.assertRaises(ValueError): + with can.Logger(should_fail_with): # make sure we close it anyways + pass + def testBufferedListenerReceives(self): a_listener = can.BufferedReader() a_listener(generate_message(0xDADADA)) @@ -154,5 +157,5 @@ def testBufferedListenerReceives(self): self.assertIsNotNone(a_listener.get_message(0.1)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/logformats_test.py b/test/logformats_test.py index d9551e5d6..4a5c408b5 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -14,32 +14,29 @@ TODO: implement CAN FD support testing """ -from __future__ import print_function, absolute_import, division - import logging import unittest import tempfile import os from abc import abstractmethod, ABCMeta - -try: - # Python 3 - from itertools import zip_longest -except ImportError: - # Python 2 - from itertools import izip_longest as zip_longest +from itertools import zip_longest import can -from .data.example_data import TEST_MESSAGES_BASE, TEST_MESSAGES_REMOTE_FRAMES, \ - TEST_MESSAGES_ERROR_FRAMES, TEST_COMMENTS, \ - sort_messages +from .data.example_data import ( + TEST_MESSAGES_BASE, + TEST_MESSAGES_REMOTE_FRAMES, + TEST_MESSAGES_ERROR_FRAMES, + TEST_MESSAGES_CAN_FD, + TEST_COMMENTS, + sort_messages, +) from .message_helper import ComparingMessagesTestCase logging.basicConfig(level=logging.DEBUG) -class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): +class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase, metaclass=ABCMeta): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed correctly. Optionally writes some comments as well. @@ -50,8 +47,6 @@ class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): (Source: `*Wojciech B.* on StackOverlfow `_) """ - __metaclass__ = ABCMeta - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self._setup_instance() @@ -61,12 +56,20 @@ def _setup_instance(self): """Hook for subclasses.""" raise NotImplementedError() - def _setup_instance_helper(self, - writer_constructor, reader_constructor, binary_file=False, - check_remote_frames=True, check_error_frames=True, check_fd=True, - check_comments=False, test_append=False, - allowed_timestamp_delta=0.0, - preserves_channel=True, adds_default_channel=None): + def _setup_instance_helper( + self, + writer_constructor, + reader_constructor, + binary_file=False, + check_remote_frames=True, + check_error_frames=True, + check_fd=True, + check_comments=False, + test_append=False, + allowed_timestamp_delta=0.0, + preserves_channel=True, + adds_default_channel=None, + ): """ :param Callable writer_constructor: the constructor of the writer class :param Callable reader_constructor: the constructor of the reader class @@ -87,13 +90,13 @@ def _setup_instance_helper(self, ignored, if *preserves_channel* is True """ # get all test messages - self.original_messages = TEST_MESSAGES_BASE + self.original_messages = list(TEST_MESSAGES_BASE) if check_remote_frames: self.original_messages += TEST_MESSAGES_REMOTE_FRAMES if check_error_frames: self.original_messages += TEST_MESSAGES_ERROR_FRAMES if check_fd: - self.original_messages += [] # TODO: add TEST_MESSAGES_CAN_FD + self.original_messages += TEST_MESSAGES_CAN_FD # sort them so that for example ASCWriter does not "fix" any messages with timestamp 0.0 self.original_messages = sort_messages(self.original_messages) @@ -102,9 +105,12 @@ def _setup_instance_helper(self, # we check this because of the lack of a common base class # we filter for not starts with '__' so we do not get all the builtin # methods when logging to the console - attrs = [attr for attr in dir(writer_constructor) if not attr.startswith('__')] - assert 'log_event' in attrs, \ - "cannot check comments with this writer: {}".format(writer_constructor) + attrs = [ + attr for attr in dir(writer_constructor) if not attr.startswith("__") + ] + assert ( + "log_event" in attrs + ), "cannot check comments with this writer: {}".format(writer_constructor) # get all test comments self.original_comments = TEST_COMMENTS if check_comments else () @@ -114,13 +120,15 @@ def _setup_instance_helper(self, self.binary_file = binary_file self.test_append_enabled = test_append - ComparingMessagesTestCase.__init__(self, + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=allowed_timestamp_delta, - preserves_channel=preserves_channel) - #adds_default_channel=adds_default_channel # TODO inlcude in tests + preserves_channel=preserves_channel, + ) + # adds_default_channel=adds_default_channel # TODO inlcude in tests def setUp(self): - with tempfile.NamedTemporaryFile('w+', delete=False) as test_file: + with tempfile.NamedTemporaryFile("w+", delete=False) as test_file: self.test_file_name = test_file.name def tearDown(self): @@ -136,7 +144,7 @@ def test_path_like_explicit_stop(self): self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(writer.file, 'closed'): + if hasattr(writer.file, "closed"): self.assertTrue(writer.file.closed) print("reading all messages") @@ -144,13 +152,16 @@ def test_path_like_explicit_stop(self): read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(writer.file, 'closed'): + if hasattr(writer.file, "closed"): self.assertTrue(writer.file.closed) # check if at least the number of messages matches # could use assertCountEqual in later versions of Python and in the other methods - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -164,7 +175,7 @@ def test_path_like_context_manager(self): self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(w.file, 'closed'): + if hasattr(w.file, "closed"): self.assertTrue(w.file.closed) # read all written messages @@ -172,12 +183,15 @@ def test_path_like_context_manager(self): with self.reader_constructor(self.test_file_name) as reader: read_messages = list(reader) r = reader - if hasattr(r.file, 'closed'): + if hasattr(r.file, "closed"): self.assertTrue(r.file.closed) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -187,27 +201,30 @@ def test_file_like_explicit_stop(self): # create writer print("writing all messages/comments") - my_file = open(self.test_file_name, 'wb' if self.binary_file else 'w') + my_file = open(self.test_file_name, "wb" if self.binary_file else "w") writer = self.writer_constructor(my_file) self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) print("reading all messages") - my_file = open(self.test_file_name, 'rb' if self.binary_file else 'r') + my_file = open(self.test_file_name, "rb" if self.binary_file else "r") reader = self.reader_constructor(my_file) read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) # check if at least the number of messages matches # could use assertCountEqual in later versions of Python and in the other methods - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -217,26 +234,29 @@ def test_file_like_context_manager(self): # create writer print("writing all messages/comments") - my_file = open(self.test_file_name, 'wb' if self.binary_file else 'w') + my_file = open(self.test_file_name, "wb" if self.binary_file else "w") with self.writer_constructor(my_file) as writer: self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) # read all written messages print("reading all messages") - my_file = open(self.test_file_name, 'rb' if self.binary_file else 'r') + my_file = open(self.test_file_name, "rb" if self.binary_file else "r") with self.reader_constructor(my_file) as reader: read_messages = list(reader) r = reader - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -249,8 +269,8 @@ def test_append_mode(self): raise unittest.SkipTest("do not test append mode") count = len(self.original_messages) - first_part = self.original_messages[:count // 2] - second_part = self.original_messages[count // 2:] + first_part = self.original_messages[: count // 2] + second_part = self.original_messages[count // 2 :] # write first half with self.writer_constructor(self.test_file_name) as writer: @@ -280,17 +300,19 @@ def test_append_mode(self): def _write_all(self, writer): """Writes messages and insert comments here and there.""" # Note: we make no assumptions about the length of original_messages and original_comments - for msg, comment in zip_longest(self.original_messages, self.original_comments, fillvalue=None): + for msg, comment in zip_longest( + self.original_messages, self.original_comments, fillvalue=None + ): # msg and comment might be None if comment is not None: print("writing comment: ", comment) - writer.log_event(comment) # we already know that this method exists + writer.log_event(comment) # we already know that this method exists if msg is not None: print("writing message: ", msg) writer(msg) def _ensure_fsync(self, io_handler): - if hasattr(io_handler.file, 'fileno'): + if hasattr(io_handler.file, "fileno"): io_handler.file.flush() os.fsync(io_handler.file.fileno()) @@ -302,7 +324,7 @@ def assertIncludesComments(self, filename): """ if self.original_comments: # read the entire outout file - with open(filename, 'rb' if self.binary_file else 'r') as file: + with open(filename, "rb" if self.binary_file else "r") as file: output_contents = file.read() # check each, if they can be found in there literally for comment in self.original_comments: @@ -313,57 +335,126 @@ class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" def _setup_instance(self): - super(TestAscFileFormat, self)._setup_instance_helper( - can.ASCWriter, can.ASCReader, - check_fd=False, + super()._setup_instance_helper( + can.ASCWriter, + can.ASCReader, + check_fd=True, check_comments=True, - preserves_channel=False, adds_default_channel=0 + preserves_channel=False, + adds_default_channel=0, ) class TestBlfFileFormat(ReaderWriterTest): - """Tests can.BLFWriter and can.BLFReader""" + """Tests can.BLFWriter and can.BLFReader. + + Uses log files created by Toby Lorenz: + https://bitbucket.org/tobylorenz/vector_blf/src/master/src/Vector/BLF/tests/unittests/events_from_binlog/ + """ def _setup_instance(self): - super(TestBlfFileFormat, self)._setup_instance_helper( - can.BLFWriter, can.BLFReader, + super()._setup_instance_helper( + can.BLFWriter, + can.BLFReader, binary_file=True, - check_fd=False, + check_fd=True, check_comments=False, + test_append=True, allowed_timestamp_delta=1.0e-6, - preserves_channel=False, adds_default_channel=0 + preserves_channel=False, + adds_default_channel=0, ) - def test_read_known_file(self): - logfile = os.path.join(os.path.dirname(__file__), "data", "logfile.blf") + def _read_log_file(self, filename): + logfile = os.path.join(os.path.dirname(__file__), "data", filename) with can.BLFReader(logfile) as reader: - messages = list(reader) - - expected = [ - can.Message( - timestamp=1.0, - is_extended_id=False, - arbitration_id=0x64, - data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]), - can.Message( - timestamp=73.0, - is_extended_id=True, - arbitration_id=0x1FFFFFFF, - is_error_frame=True,) - ] - - self.assertMessagesEqual(messages, expected) + return list(reader) + + def test_can_message(self): + expected = can.Message( + timestamp=2459565876.494607, + arbitration_id=0x4444444, + is_extended_id=False, + channel=0x1110, + dlc=0x33, + data=[0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC], + ) + actual = self._read_log_file("test_CanMessage.blf") + self.assertMessagesEqual(actual, [expected] * 2) + self.assertEqual(actual[0].channel, expected.channel) + + def test_can_message_2(self): + expected = can.Message( + timestamp=2459565876.494607, + arbitration_id=0x4444444, + is_extended_id=False, + channel=0x1110, + dlc=0x33, + data=[0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC], + ) + actual = self._read_log_file("test_CanMessage2.blf") + self.assertMessagesEqual(actual, [expected] * 2) + self.assertEqual(actual[0].channel, expected.channel) + + def test_can_fd_message(self): + expected = can.Message( + timestamp=2459565876.494607, + arbitration_id=0x4444444, + is_extended_id=False, + channel=0x1110, + dlc=64, + is_fd=True, + bitrate_switch=True, + error_state_indicator=True, + data=range(64), + ) + actual = self._read_log_file("test_CanFdMessage.blf") + self.assertMessagesEqual(actual, [expected] * 2) + self.assertEqual(actual[0].channel, expected.channel) + + def test_can_fd_message_64(self): + expected = can.Message( + timestamp=2459565876.494607, + arbitration_id=0x15555555, + is_extended_id=False, + is_remote_frame=True, + channel=0x10, + dlc=64, + is_fd=True, + bitrate_switch=True, + error_state_indicator=True, + ) + actual = self._read_log_file("test_CanFdMessage64.blf") + self.assertMessagesEqual(actual, [expected] * 2) + self.assertEqual(actual[0].channel, expected.channel) + + def test_can_error_frame_ext(self): + expected = can.Message( + timestamp=2459565876.494607, + is_error_frame=True, + arbitration_id=0x19999999, + is_extended_id=True, + channel=0x1110, + dlc=0x66, + data=[0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44], + ) + actual = self._read_log_file("test_CanErrorFrameExt.blf") + self.assertMessagesEqual(actual, [expected] * 2) + self.assertEqual(actual[0].channel, expected.channel) class TestCanutilsFileFormat(ReaderWriterTest): """Tests can.CanutilsLogWriter and can.CanutilsLogReader""" def _setup_instance(self): - super(TestCanutilsFileFormat, self)._setup_instance_helper( - can.CanutilsLogWriter, can.CanutilsLogReader, + super()._setup_instance_helper( + can.CanutilsLogWriter, + can.CanutilsLogReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel='vcan0' + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel="vcan0", ) @@ -371,11 +462,14 @@ class TestCsvFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" def _setup_instance(self): - super(TestCsvFileFormat, self)._setup_instance_helper( - can.CSVWriter, can.CSVReader, + super()._setup_instance_helper( + can.CSVWriter, + can.CSVReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel=None + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel=None, ) @@ -383,11 +477,14 @@ class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader""" def _setup_instance(self): - super(TestSqliteDatabaseFormat, self)._setup_instance_helper( - can.SqliteWriter, can.SqliteReader, + super()._setup_instance_helper( + can.SqliteWriter, + can.SqliteReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel=None + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel=None, ) @unittest.skip("not implemented") @@ -412,18 +509,26 @@ def test_read_all(self): with self.reader_constructor(self.test_file_name) as reader: read_messages = list(reader.read_all()) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) class TestPrinter(unittest.TestCase): - """Tests that can.Printer does not crash""" + """Tests that can.Printer does not crash + + TODO test append mode + """ # TODO add CAN FD messages - messages = TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + messages = ( + TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + ) def test_not_crashes_with_stdout(self): with can.Printer() as printer: @@ -431,15 +536,15 @@ def test_not_crashes_with_stdout(self): printer(message) def test_not_crashes_with_file(self): - with tempfile.NamedTemporaryFile('w', delete=False) as temp_file: + with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: with can.Printer(temp_file) as printer: for message in self.messages: printer(message) # this excludes the base class from being executed as a test case itself -del(ReaderWriterTest) +del ReaderWriterTest -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/message_helper.py b/test/message_helper.py index 9a4756207..5f437dbde 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -5,12 +5,10 @@ This module contains a helper for writing test cases that need to compare messages. """ -from __future__ import absolute_import, print_function - from copy import copy -class ComparingMessagesTestCase(object): +class ComparingMessagesTestCase: """ Must be extended by a class also extending a unittest.TestCase. @@ -36,24 +34,35 @@ def assertMessageEqual(self, message_1, message_2): elif self.preserves_channel: print("Comparing: message 1: {!r}".format(message_1)) print(" message 2: {!r}".format(message_2)) - self.fail("messages are unequal with allowed timestamp delta {}".format(self.allowed_timestamp_delta)) + self.fail( + "messages are unequal with allowed timestamp delta {}".format( + self.allowed_timestamp_delta + ) + ) else: - message_2 = copy(message_2) # make sure this method is pure + message_2 = copy(message_2) # make sure this method is pure message_2.channel = message_1.channel - if message_1.equals(message_2, timestamp_delta=self.allowed_timestamp_delta): + if message_1.equals( + message_2, timestamp_delta=self.allowed_timestamp_delta + ): return else: print("Comparing: message 1: {!r}".format(message_1)) print(" message 2: {!r}".format(message_2)) - self.fail("messages are unequal with allowed timestamp delta {} even when ignoring channels" \ - .format(self.allowed_timestamp_delta)) + self.fail( + "messages are unequal with allowed timestamp delta {} even when ignoring channels".format( + self.allowed_timestamp_delta + ) + ) def assertMessagesEqual(self, messages_1, messages_2): """ Checks the order and content of the individual messages pairwise. Raises an error if the lengths of the sequences are not equal. """ - self.assertEqual(len(messages_1), len(messages_2), "the number of messages differs") + self.assertEqual( + len(messages_1), len(messages_2), "the number of messages differs" + ) for message_1, message_2 in zip(messages_1, messages_2): self.assertMessageEqual(message_1, message_2) diff --git a/test/network_test.py b/test/network_test.py index f4163329d..2ee4795fb 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,17 +1,13 @@ #!/usr/bin/env python # coding: utf-8 -from __future__ import print_function import unittest import threading -try: - import queue -except ImportError: - import Queue as queue +import queue import random - import logging + logging.getLogger(__file__).setLevel(logging.WARNING) # make a random bool: @@ -19,11 +15,11 @@ import can -channel = 'vcan0' -can.rc['interface'] = 'virtual' +channel = "vcan0" +can.rc["interface"] = "virtual" -@unittest.skipIf('interface' not in can.rc, "Need a CAN interface") +@unittest.skipIf("interface" not in can.rc, "Need a CAN interface") class ControllerAreaNetworkTestCase(unittest.TestCase): """ This test ensures that what messages go in to the bus is what comes out. @@ -43,9 +39,10 @@ class ControllerAreaNetworkTestCase(unittest.TestCase): extended_flags = [rbool() for _ in range(num_messages)] ids = list(range(num_messages)) - data = list(bytearray([random.randrange(0, 2 ** 8 - 1) - for a in range(random.randrange(9))]) - for b in range(num_messages)) + data = list( + bytearray([random.randrange(0, 2 ** 8 - 1) for a in range(random.randrange(9))]) + for b in range(num_messages) + ) def producer(self, ready_event, msg_read): self.client_bus = can.interface.Bus(channel=channel) @@ -56,9 +53,9 @@ def producer(self, ready_event, msg_read): is_remote_frame=self.remote_flags[i], is_error_frame=self.error_flags[i], is_extended_id=self.extended_flags[i], - data=self.data[i] + data=self.data[i], ) - #logging.debug("writing message: {}".format(m)) + # logging.debug("writing message: {}".format(m)) if msg_read is not None: # Don't send until the other thread is ready msg_read.wait() @@ -99,7 +96,7 @@ def testProducerConsumer(self): msg_read.set() msg = self.server_bus.recv(timeout=0.5) self.assertIsNotNone(msg, "Didn't receive a message") - #logging.debug("Received message {} with data: {}".format(i, msg.data)) + # logging.debug("Received message {} with data: {}".format(i, msg.data)) self.assertEqual(msg.is_extended_id, self.extended_flags[i]) if not msg.is_remote_frame: @@ -115,5 +112,6 @@ def testProducerConsumer(self): self.server_bus.flush_tx_buffer() self.server_bus.shutdown() -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/test/notifier_test.py b/test/notifier_test.py index 3ab257cf7..0a60bd25d 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -3,18 +3,14 @@ import unittest import time -try: - import asyncio -except ImportError: - asyncio = None +import asyncio import can class NotifierTest(unittest.TestCase): - def test_single_bus(self): - bus = can.Bus('test', bustype='virtual', receive_own_messages=True) + bus = can.Bus("test", bustype="virtual", receive_own_messages=True) reader = can.BufferedReader() notifier = can.Notifier(bus, [reader], 0.1) msg = can.Message() @@ -24,8 +20,8 @@ def test_single_bus(self): bus.shutdown() def test_multiple_bus(self): - bus1 = can.Bus(0, bustype='virtual', receive_own_messages=True) - bus2 = can.Bus(1, bustype='virtual', receive_own_messages=True) + bus1 = can.Bus(0, bustype="virtual", receive_own_messages=True) + bus2 = can.Bus(1, bustype="virtual", receive_own_messages=True) reader = can.BufferedReader() notifier = can.Notifier([bus1, bus2], [reader], 0.1) msg = can.Message() @@ -44,11 +40,9 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): - - @unittest.skipIf(asyncio is None, 'Test requires asyncio') def test_asyncio_notifier(self): loop = asyncio.get_event_loop() - bus = can.Bus('test', bustype='virtual', receive_own_messages=True) + bus = can.Bus("test", bustype="virtual", receive_own_messages=True) reader = can.AsyncBufferedReader() notifier = can.Notifier(bus, [reader], 0.1, loop=loop) msg = can.Message() @@ -60,6 +54,5 @@ def test_asyncio_notifier(self): bus.shutdown() - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/serial_test.py b/test/serial_test.py index 5b26ae42a..d0e11f974 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -7,10 +7,8 @@ Copyright: 2017 Boris Wenzlaff """ -from __future__ import division - import unittest -from mock import patch +from unittest.mock import patch import can from can.interfaces.serial.serial_can import SerialBus @@ -18,10 +16,11 @@ from .message_helper import ComparingMessagesTestCase -class SerialDummy(object): +class SerialDummy: """ Dummy to mock the serial communication """ + msg = None def __init__(self): @@ -45,7 +44,9 @@ class SimpleSerialTestBase(ComparingMessagesTestCase): MAX_TIMESTAMP = 0xFFFFFFFF / 1000 def __init__(self): - ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=None, preserves_channel=True) + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=None, preserves_channel=True + ) def test_rx_tx_min_max_data(self): """ @@ -111,7 +112,7 @@ def test_rx_tx_max_timestamp_error(self): """ Tests for an exception with an out of range timestamp (max + 1) """ - msg = can.Message(timestamp=self.MAX_TIMESTAMP+1) + msg = can.Message(timestamp=self.MAX_TIMESTAMP + 1) self.assertRaises(ValueError, self.bus.send, msg) def test_rx_tx_min_timestamp(self): @@ -133,36 +134,34 @@ def test_rx_tx_min_timestamp_error(self): class SimpleSerialTest(unittest.TestCase, SimpleSerialTestBase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) SimpleSerialTestBase.__init__(self) def setUp(self): - self.patcher = patch('serial.Serial') + self.patcher = patch("serial.Serial") self.mock_serial = self.patcher.start() self.serial_dummy = SerialDummy() self.mock_serial.return_value.write = self.serial_dummy.write self.mock_serial.return_value.read = self.serial_dummy.read self.addCleanup(self.patcher.stop) - self.bus = SerialBus('bus') + self.bus = SerialBus("bus") def tearDown(self): self.serial_dummy.reset() class SimpleSerialLoopTest(unittest.TestCase, SimpleSerialTestBase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) SimpleSerialTestBase.__init__(self) def setUp(self): - self.bus = SerialBus('loop://') + self.bus = SerialBus("loop://") def tearDown(self): self.bus.shutdown() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index a10871648..927855867 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,10 +5,9 @@ This module tests cyclic send tasks. """ -from __future__ import absolute_import - from time import sleep import unittest +from unittest.mock import MagicMock import gc import can @@ -18,17 +17,23 @@ class SimpleCyclicSendTaskTest(unittest.TestCase, ComparingMessagesTestCase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=0.016, preserves_channel=True) - - @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=0.016, preserves_channel=True + ) + + @unittest.skipIf( + IS_CI, + "the timing sensitive behaviour cannot be reproduced reliably on a CI server", + ) def test_cycle_time(self): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] + ) - with can.interface.Bus(bustype='virtual') as bus1: - with can.interface.Bus(bustype='virtual') as bus2: + with can.interface.Bus(bustype="virtual") as bus1: + with can.interface.Bus(bustype="virtual") as bus2: # disabling the garbage collector makes the time readings more reliable gc.disable() @@ -39,8 +44,12 @@ def test_cycle_time(self): sleep(2) size = bus2.queue.qsize() # About 100 messages should have been transmitted - self.assertTrue(80 <= size <= 120, - '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) + self.assertTrue( + 80 <= size <= 120, + "100 +/- 20 messages should have been transmitted. But queue contained {}".format( + size + ), + ) last_msg = bus2.recv() next_last_msg = bus2.recv() @@ -59,10 +68,14 @@ def test_cycle_time(self): self.assertMessageEqual(msg, last_msg) def test_removing_bus_tasks(self): - bus = can.interface.Bus(bustype='virtual') + bus = can.interface.Bus(bustype="virtual") tasks = [] for task_i in range(10): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -78,10 +91,14 @@ def test_removing_bus_tasks(self): bus.shutdown() def test_managed_tasks(self): - bus = can.interface.Bus(bustype='virtual', receive_own_messages=True) + bus = can.interface.Bus(bustype="virtual", receive_own_messages=True) tasks = [] for task_i in range(3): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 10, store_task=False) tasks.append(task) @@ -104,10 +121,14 @@ def test_managed_tasks(self): bus.shutdown() def test_stopping_perodic_tasks(self): - bus = can.interface.Bus(bustype='virtual') + bus = can.interface.Bus(bustype="virtual") tasks = [] for task_i in range(10): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -131,6 +152,43 @@ def test_stopping_perodic_tasks(self): bus.shutdown() + def test_thread_based_cyclic_send_task(self): + bus = can.ThreadSafeBus(bustype="virtual") + msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] + ) + + # good case, bus is up + on_error_mock = MagicMock(return_value=False) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + on_error_mock.assert_not_called() + task.stop() + bus.shutdown() -if __name__ == '__main__': + # bus has been shutted down + on_error_mock = MagicMock(return_value=False) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + self.assertTrue(on_error_mock.call_count is 1) + task.stop() + + # bus is still shutted down, but on_error returns True + on_error_mock = MagicMock(return_value=True) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + self.assertTrue(on_error_mock.call_count > 1) + task.stop() + + +if __name__ == "__main__": unittest.main() diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py new file mode 100644 index 000000000..0b22e308f --- /dev/null +++ b/test/test_bit_timing.py @@ -0,0 +1,92 @@ +import can + + +def test_sja1000(): + """Test some values obtained using other bit timing calculators.""" + timing = can.BitTiming( + f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2, nof_samples=3 + ) + assert timing.f_clock == 8000000 + assert timing.bitrate == 125000 + assert timing.brp == 4 + assert timing.nbt == 16 + assert timing.tseg1 == 11 + assert timing.tseg2 == 4 + assert timing.sjw == 2 + assert timing.nof_samples == 3 + assert timing.sample_point == 75 + assert timing.btr0 == 0x43 + assert timing.btr1 == 0xBA + + timing = can.BitTiming(f_clock=8000000, bitrate=500000, tseg1=13, tseg2=2, sjw=1) + assert timing.f_clock == 8000000 + assert timing.bitrate == 500000 + assert timing.brp == 1 + assert timing.nbt == 16 + assert timing.tseg1 == 13 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.nof_samples == 1 + assert timing.sample_point == 87.5 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x1C + + timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) + assert timing.f_clock == 8000000 + assert timing.bitrate == 1000000 + assert timing.brp == 1 + assert timing.nbt == 8 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.nof_samples == 1 + assert timing.sample_point == 75 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x14 + + +def test_can_fd(): + timing = can.BitTiming( + f_clock=80000000, bitrate=500000, tseg1=119, tseg2=40, sjw=40 + ) + assert timing.f_clock == 80000000 + assert timing.bitrate == 500000 + assert timing.brp == 1 + assert timing.nbt == 160 + assert timing.tseg1 == 119 + assert timing.tseg2 == 40 + assert timing.sjw == 40 + assert timing.sample_point == 75 + + timing = can.BitTiming( + f_clock=80000000, bitrate=2000000, tseg1=29, tseg2=10, sjw=10 + ) + assert timing.f_clock == 80000000 + assert timing.bitrate == 2000000 + assert timing.brp == 1 + assert timing.nbt == 40 + assert timing.tseg1 == 29 + assert timing.tseg2 == 10 + assert timing.sjw == 10 + assert timing.sample_point == 75 + + +def test_from_btr(): + timing = can.BitTiming(f_clock=8000000, btr0=0x00, btr1=0x14) + assert timing.bitrate == 1000000 + assert timing.brp == 1 + assert timing.nbt == 8 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.sample_point == 75 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x14 + + +def test_string_representation(): + timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) + assert ( + str(timing) + == "1000000 bits/s, sample point: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, BTR: 0014h" + ) diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py new file mode 100644 index 000000000..40c7af582 --- /dev/null +++ b/test/test_cyclic_socketcan.py @@ -0,0 +1,626 @@ +""" +This module tests multiple message cyclic send tasks. +""" +import unittest + +import time +import can + +from .config import TEST_INTERFACE_SOCKETCAN + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class CyclicSocketCan(unittest.TestCase): + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" + + PERIOD = 1.0 + + DELTA = 0.01 + + def _find_start_index(self, tx_messages, message): + """ + :param tx_messages: + The list of messages that were passed to the periodic backend + :param message: + The message whose data we wish to match and align to + + :returns: start index in the tx_messages + """ + start_index = -1 + for index, tx_message in enumerate(tx_messages): + if tx_message.data == message.data: + start_index = index + break + return start_index + + def setUp(self): + self._send_bus = can.Bus( + interface=self.INTERFACE_1, channel=self.CHANNEL_1, bitrate=self.BITRATE + ) + self._recv_bus = can.Bus( + interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE + ) + + def tearDown(self): + self._send_bus.shutdown() + self._recv_bus.shutdown() + + def test_cyclic_initializer_list(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(messages, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + results = [] + for _ in range(len(messages) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results.append(result) + + task.stop() + + # Find starting index for each + start_index = self._find_start_index(messages, results[0]) + self.assertTrue(start_index != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results): + tx_message = messages[start_index] + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index = (start_index + 1) % len(messages) + + def test_cyclic_initializer_tuple(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages = tuple(messages) + + self.assertIsInstance(messages, tuple) + + task = self._send_bus.send_periodic(messages, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + results = [] + for _ in range(len(messages) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results.append(result) + + task.stop() + + # Find starting index for each + start_index = self._find_start_index(messages, results[0]) + self.assertTrue(start_index != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results): + tx_message = messages[start_index] + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index = (start_index + 1) % len(messages) + + def test_cyclic_initializer_message(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + # Take advantage of kernel's queueing mechanisms + time.sleep(4 * self.PERIOD) + task.stop() + + for _ in range(4): + tx_message = message + rx_message = self._recv_bus.recv(self.TIMEOUT) + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + def test_cyclic_initializer_invalid_none(self): + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic(None, self.PERIOD) + + def test_cyclic_initializer_invalid_empty_list(self): + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic([], self.PERIOD) + + def test_cyclic_initializer_different_arbitration_ids(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x3E1, + data=[0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE], + is_extended_id=False, + ) + ) + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic(messages, self.PERIOD) + + def test_start_already_started_task(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) + time.sleep(0.1) + + # Try to start it again, task_id is not incremented in this case + with self.assertRaises(ValueError) as ctx: + task_a.start() + self.assertEqual( + "A periodic task for Task ID 1 is already in progress by SocketCAN Linux layer", + str(ctx.exception), + ) + + task_a.stop() + + def test_create_same_id(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + messages_b = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) + self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + task_b = self._send_bus.send_periodic(messages_b, self.PERIOD) + self.assertIsInstance(task_b, can.broadcastmanager.CyclicSendTaskABC) + + time.sleep(self.PERIOD * 4) + + task_a.stop() + task_b.stop() + + msgs = [] + for _ in range(4): + msg = self._recv_bus.recv(self.PERIOD * 2) + self.assertIsNotNone(msg) + + msgs.append(msg) + + self.assertTrue(len(msgs) >= 4) + + # Both messages should be recevied on the bus, + # even with the same arbitration id + msg_a_data_present = msg_b_data_present = False + for rx_message in msgs: + self.assertTrue( + rx_message.arbitration_id + == messages_a.arbitration_id + == messages_b.arbitration_id + ) + if rx_message.data == messages_a.data: + msg_a_data_present = True + if rx_message.data == messages_b.data: + msg_b_data_present = True + + self.assertTrue(msg_a_data_present) + self.assertTrue(msg_b_data_present) + + def test_modify_data_list(self): + messages_odd = [] + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages_even = [] + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x66, 0x66, 0x66, 0x66, 0x66, 0x66], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(messages_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(len(messages_odd) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(messages_even) + for _ in range(len(messages_even) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Make sure we received some messages + self.assertTrue(len(results_even) != 0) + self.assertTrue(len(results_odd) != 0) + + # Find starting index for each + start_index_even = self._find_start_index(messages_even, results_even[0]) + self.assertTrue(start_index_even != -1) + + start_index_odd = self._find_start_index(messages_odd, results_odd[0]) + self.assertTrue(start_index_odd != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = messages_even[start_index_even] + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index_even = (start_index_even + 1) % len(messages_even) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = messages_odd[start_index_odd] + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index_odd = (start_index_odd + 1) % len(messages_odd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + def test_modify_data_message(self): + message_odd = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_even = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(message_even) + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = message_even + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = message_odd + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + def test_modify_data_invalid(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(None) + + def test_modify_data_unequal_lengths(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + new_messages = [] + new_messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + new_messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(new_messages) + + def test_modify_data_different_arbitration_id_than_original(self): + old_message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + new_message = can.Message( + arbitration_id=0x3E1, + data=[0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(old_message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(new_message) + + def test_stop_all_periodic_tasks_and_remove_task(self): + message_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_b = can.Message( + arbitration_id=0x402, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + message_c = can.Message( + arbitration_id=0x403, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + + # Start Tasks + task_a = self._send_bus.send_periodic(message_a, self.PERIOD) + task_b = self._send_bus.send_periodic(message_b, self.PERIOD) + task_c = self._send_bus.send_periodic(message_c, self.PERIOD) + + self.assertIsInstance(task_a, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_b, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_c, can.broadcastmanager.ModifiableCyclicTaskABC) + + for _ in range(6): + _ = self._recv_bus.recv(self.PERIOD) + + # Stop all tasks and delete + self._send_bus.stop_all_periodic_tasks(remove_tasks=True) + + # Now wait for a few periods, after which we should definitely not + # receive any CAN messages + time.sleep(4 * self.PERIOD) + + # If we successfully deleted everything, then we will eventually read + # 0 messages. + successfully_stopped = False + for _ in range(6): + rx_message = self._recv_bus.recv(self.PERIOD) + + if rx_message is None: + successfully_stopped = True + break + self.assertTrue(successfully_stopped, "Still received messages after stopping") + + # None of the tasks should still be associated with the bus + self.assertEqual(0, len(self._send_bus._periodic_tasks)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index ca2d82c15..0d94e31f1 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -6,12 +6,8 @@ :meth:`can.BusABC.detect_available_configs`. """ -from __future__ import absolute_import - import sys import unittest -if sys.version_info.major > 2: - basestring = str from can import detect_available_configs @@ -19,40 +15,41 @@ class TestDetectAvailableConfigs(unittest.TestCase): - def test_count_returned(self): # At least virtual has to always return at least one interface - self.assertGreaterEqual (len(detect_available_configs() ), 1) - self.assertEqual (len(detect_available_configs(interfaces=[]) ), 0) - self.assertGreaterEqual (len(detect_available_configs(interfaces='virtual') ), 1) - self.assertGreaterEqual (len(detect_available_configs(interfaces=['virtual']) ), 1) - self.assertGreaterEqual (len(detect_available_configs(interfaces=None) ), 1) + self.assertGreaterEqual(len(detect_available_configs()), 1) + self.assertEqual(len(detect_available_configs(interfaces=[])), 0) + self.assertGreaterEqual(len(detect_available_configs(interfaces="virtual")), 1) + self.assertGreaterEqual( + len(detect_available_configs(interfaces=["virtual"])), 1 + ) + self.assertGreaterEqual(len(detect_available_configs(interfaces=None)), 1) def test_general_values(self): configs = detect_available_configs() for config in configs: - self.assertIn('interface', config) - self.assertIn('channel', config) - self.assertIsInstance(config['interface'], basestring) + self.assertIn("interface", config) + self.assertIn("channel", config) + self.assertIsInstance(config["interface"], str) def test_content_virtual(self): - configs = detect_available_configs(interfaces='virtual') + configs = detect_available_configs(interfaces="virtual") for config in configs: - self.assertEqual(config['interface'], 'virtual') + self.assertEqual(config["interface"], "virtual") def test_content_socketcan(self): - configs = detect_available_configs(interfaces='socketcan') + configs = detect_available_configs(interfaces="socketcan") for config in configs: - self.assertEqual(config['interface'], 'socketcan') + self.assertEqual(config["interface"], "socketcan") @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "socketcan is not tested") def test_socketcan_on_ci_server(self): - configs = detect_available_configs(interfaces='socketcan') + configs = detect_available_configs(interfaces="socketcan") self.assertGreaterEqual(len(configs), 1) - self.assertIn('vcan0', [config['channel'] for config in configs]) + self.assertIn("vcan0", [config["channel"] for config in configs]) # see TestSocketCanHelpers.test_find_available_interfaces() too -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 3e0bcf396..733bbc367 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -8,10 +8,7 @@ import time import logging import unittest -try: - from unittest.mock import Mock, patch -except ImportError: - from mock import patch, Mock +from unittest.mock import Mock, patch import pytest @@ -21,7 +18,6 @@ class KvaserTest(unittest.TestCase): - def setUp(self): canlib.canGetNumberOfChannels = KvaserTest.canGetNumberOfChannels canlib.canOpenChannel = Mock(return_value=0) @@ -43,7 +39,7 @@ def setUp(self): self.msg = {} self.msg_in_cue = None - self.bus = can.Bus(channel=0, bustype='kvaser') + self.bus = can.Bus(channel=0, bustype="kvaser") def tearDown(self): if self.bus: @@ -63,67 +59,58 @@ def test_bus_shutdown(self): def test_filter_setup(self): # No filter in constructor expected_args = [ - ((0, 0, 0, 0),), # Disable filtering STD on read handle - ((0, 0, 0, 1),), # Disable filtering EXT on read handle - ((0, 0, 0, 0),), # Disable filtering STD on write handle - ((0, 0, 0, 1),), # Disable filtering EXT on write handle + ((0, 0, 0, 0),), # Disable filtering STD on read handle + ((0, 0, 0, 1),), # Disable filtering EXT on read handle + ((0, 0, 0, 0),), # Disable filtering STD on write handle + ((0, 0, 0, 1),), # Disable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) # One filter, will be handled by canlib canlib.canSetAcceptanceFilter.reset_mock() - self.bus.set_filters([ - {'can_id': 0x8, 'can_mask': 0xff, 'extended': True} - ]) + self.bus.set_filters([{"can_id": 0x8, "can_mask": 0xFF, "extended": True}]) expected_args = [ - ((0, 0x8, 0xff, 1),), # Enable filtering EXT on read handle - ((0, 0x8, 0xff, 1),), # Enable filtering EXT on write handle + ((0, 0x8, 0xFF, 1),), # Enable filtering EXT on read handle + ((0, 0x8, 0xFF, 1),), # Enable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) # Multiple filters, will be handled in Python canlib.canSetAcceptanceFilter.reset_mock() multiple_filters = [ - {'can_id': 0x8, 'can_mask': 0xff}, - {'can_id': 0x9, 'can_mask': 0xff} + {"can_id": 0x8, "can_mask": 0xFF}, + {"can_id": 0x9, "can_mask": 0xFF}, ] self.bus.set_filters(multiple_filters) expected_args = [ - ((0, 0, 0, 0),), # Disable filtering STD on read handle - ((0, 0, 0, 1),), # Disable filtering EXT on read handle - ((0, 0, 0, 0),), # Disable filtering STD on write handle - ((0, 0, 0, 1),), # Disable filtering EXT on write handle + ((0, 0, 0, 0),), # Disable filtering STD on read handle + ((0, 0, 0, 1),), # Disable filtering EXT on read handle + ((0, 0, 0, 0),), # Disable filtering STD on write handle + ((0, 0, 0, 1),), # Disable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) def test_send_extended(self): msg = can.Message( - arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4], - is_extended_id=True) + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4], is_extended_id=True + ) self.bus.send(msg) - self.assertEqual(self.msg['arb_id'], 0xc0ffee) - self.assertEqual(self.msg['dlc'], 7) - self.assertEqual(self.msg['flags'], constants.canMSG_EXT) - self.assertSequenceEqual(self.msg['data'], [0, 25, 0, 1, 3, 1, 4]) + self.assertEqual(self.msg["arb_id"], 0xC0FFEE) + self.assertEqual(self.msg["dlc"], 7) + self.assertEqual(self.msg["flags"], constants.canMSG_EXT) + self.assertSequenceEqual(self.msg["data"], [0, 25, 0, 1, 3, 1, 4]) def test_send_standard(self): - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) self.bus.send(msg) - self.assertEqual(self.msg['arb_id'], 0x321) - self.assertEqual(self.msg['dlc'], 2) - self.assertEqual(self.msg['flags'], constants.canMSG_STD) - self.assertSequenceEqual(self.msg['data'], [50, 51]) + self.assertEqual(self.msg["arb_id"], 0x321) + self.assertEqual(self.msg["dlc"], 2) + self.assertEqual(self.msg["flags"], constants.canMSG_STD) + self.assertSequenceEqual(self.msg["data"], [50, 51]) @pytest.mark.timeout(3.0) def test_recv_no_message(self): @@ -131,13 +118,12 @@ def test_recv_no_message(self): def test_recv_extended(self): self.msg_in_cue = can.Message( - arbitration_id=0xc0ffef, - data=[1, 2, 3, 4, 5, 6, 7, 8], - is_extended_id=True) + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) now = time.time() msg = self.bus.recv() - self.assertEqual(msg.arbitration_id, 0xc0ffef) + self.assertEqual(msg.arbitration_id, 0xC0FFEF) self.assertEqual(msg.dlc, 8) self.assertEqual(msg.is_extended_id, True) self.assertSequenceEqual(msg.data, self.msg_in_cue.data) @@ -145,53 +131,54 @@ def test_recv_extended(self): def test_recv_standard(self): self.msg_in_cue = can.Message( - arbitration_id=0x123, - data=[100, 101], - is_extended_id=False) + arbitration_id=0x123, data=[100, 101], is_extended_id=False + ) msg = self.bus.recv() self.assertEqual(msg.arbitration_id, 0x123) self.assertEqual(msg.dlc, 2) self.assertEqual(msg.is_extended_id, False) self.assertSequenceEqual(msg.data, [100, 101]) - + def test_available_configs(self): configs = canlib.KvaserBus._detect_available_configs() expected = [ - {'interface': 'kvaser', 'channel': 0}, - {'interface': 'kvaser', 'channel': 1} + {"interface": "kvaser", "channel": 0}, + {"interface": "kvaser", "channel": 1}, ] self.assertListEqual(configs, expected) def test_canfd_default_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() - can.Bus(channel=0, bustype='kvaser', fd=True) + can.Bus(channel=0, bustype="kvaser", fd=True) canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) canlib.canSetBusParamsFd.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0 + ) def test_canfd_nondefault_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 2000000 - can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) bitrate_constant = canlib.BITRATE_FD[data_bitrate] canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) - canlib.canSetBusParamsFd.assert_called_once_with( - 0, bitrate_constant, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) + canlib.canSetBusParamsFd.assert_called_once_with(0, bitrate_constant, 0, 0, 0) def test_canfd_custom_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 123456 - can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) - canlib.canSetBusParamsFd.assert_called_once_with( - 0, data_bitrate, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) + canlib.canSetBusParamsFd.assert_called_once_with(0, data_bitrate, 0, 0, 0) def test_bus_get_stats(self): stats = self.bus.get_stats() @@ -204,10 +191,10 @@ def canGetNumberOfChannels(count): count._obj.value = 2 def canWrite(self, handle, arb_id, buf, dlc, flags): - self.msg['arb_id'] = arb_id - self.msg['dlc'] = dlc - self.msg['flags'] = flags - self.msg['data'] = bytearray(buf._obj) + self.msg["arb_id"] = arb_id + self.msg["dlc"] = dlc + self.msg["flags"] = flags + self.msg["data"] = bytearray(buf._obj) def canReadWait(self, handle, arb_id, data, dlc, flags, timestamp, timeout): if not self.msg_in_cue: @@ -230,5 +217,6 @@ def canReadWait(self, handle, arb_id, data, dlc, flags, timestamp, timeout): return constants.canOK -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index 52a45d734..79b2e6c4b 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # coding: utf-8 + import shutil import tempfile import unittest @@ -10,10 +11,10 @@ class LoadFileConfigTest(unittest.TestCase): configuration = { - 'default': {'interface': 'virtual', 'channel': '0'}, - 'one': {'interface': 'virtual', 'channel': '1'}, - 'two': {'channel': '2'}, - 'three': {'extra': 'extra value'}, + "default": {"interface": "virtual", "channel": "0"}, + "one": {"interface": "virtual", "channel": "1"}, + "two": {"channel": "2"}, + "three": {"extra": "extra value"}, } def setUp(self): @@ -25,65 +26,70 @@ def tearDown(self): shutil.rmtree(self.test_dir) def _gen_configration_file(self, sections): - with NamedTemporaryFile(mode='w', dir=self.test_dir, - delete=False) as tmp_config_file: + with NamedTemporaryFile( + mode="w", dir=self.test_dir, delete=False + ) as tmp_config_file: content = [] for section in sections: content.append("[{}]".format(section)) for k, v in self.configuration[section].items(): content.append("{} = {}".format(k, v)) - tmp_config_file.write('\n'.join(content)) + tmp_config_file.write("\n".join(content)) return tmp_config_file.name def test_config_file_with_default(self): - tmp_config = self._gen_configration_file(['default']) + tmp_config = self._gen_configration_file(["default"]) config = can.util.load_file_config(path=tmp_config) - self.assertEqual(config, self.configuration['default']) + self.assertEqual(config, self.configuration["default"]) def test_config_file_with_default_and_section(self): - tmp_config = self._gen_configration_file(['default', 'one']) + tmp_config = self._gen_configration_file(["default", "one"]) - default = can.util.load_file_config(path=tmp_config) - self.assertEqual(default, self.configuration['default']) + config = can.util.load_file_config(path=tmp_config) + self.assertEqual(config, self.configuration["default"]) - one = can.util.load_file_config(path=tmp_config, section='one') - self.assertEqual(one, self.configuration['one']) + config.update(can.util.load_file_config(path=tmp_config, section="one")) + self.assertEqual(config, self.configuration["one"]) def test_config_file_with_section_only(self): - tmp_config = self._gen_configration_file(['one']) - config = can.util.load_file_config(path=tmp_config, section='one') - self.assertEqual(config, self.configuration['one']) + tmp_config = self._gen_configration_file(["one"]) + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="one")) + self.assertEqual(config, self.configuration["one"]) def test_config_file_with_section_and_key_in_default(self): - expected = self.configuration['default'].copy() - expected.update(self.configuration['two']) + expected = self.configuration["default"].copy() + expected.update(self.configuration["two"]) - tmp_config = self._gen_configration_file(['default', 'two']) - config = can.util.load_file_config(path=tmp_config, section='two') + tmp_config = self._gen_configration_file(["default", "two"]) + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="two")) self.assertEqual(config, expected) def test_config_file_with_section_missing_interface(self): - expected = self.configuration['two'].copy() - tmp_config = self._gen_configration_file(['two']) - config = can.util.load_file_config(path=tmp_config, section='two') + expected = self.configuration["two"].copy() + tmp_config = self._gen_configration_file(["two"]) + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="two")) self.assertEqual(config, expected) def test_config_file_extra(self): - expected = self.configuration['default'].copy() - expected.update(self.configuration['three']) + expected = self.configuration["default"].copy() + expected.update(self.configuration["three"]) - tmp_config = self._gen_configration_file(['default', 'three']) - config = can.util.load_file_config(path=tmp_config, section='three') + tmp_config = self._gen_configration_file(["default", "three"]) + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="three")) self.assertEqual(config, expected) def test_config_file_with_non_existing_section(self): - expected = {} + expected = self.configuration["default"].copy() - tmp_config = self._gen_configration_file([ - 'default', 'one', 'two', 'three']) - config = can.util.load_file_config(path=tmp_config, section='zero') + tmp_config = self._gen_configration_file(["default", "one", "two", "three"]) + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="zero")) self.assertEqual(config, expected) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_class.py b/test/test_message_class.py index 85dbe8560..760a848bd 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -30,47 +30,53 @@ class TestMessageClass(unittest.TestCase): data=st.one_of(st.binary(min_size=0, max_size=8), st.none()), is_fd=st.booleans(), bitrate_switch=st.booleans(), - error_state_indicator=st.booleans() + error_state_indicator=st.booleans(), ) @settings(max_examples=2000) def test_methods(self, **kwargs): is_valid = not ( - (not kwargs['is_remote_frame'] and (len(kwargs['data'] or []) != kwargs['dlc'])) or - (kwargs['arbitration_id'] >= 0x800 and not kwargs['is_extended_id']) or - kwargs['arbitration_id'] >= 0x20000000 or - kwargs['arbitration_id'] < 0 or - (kwargs['is_remote_frame'] and kwargs['is_error_frame']) or - (kwargs['is_remote_frame'] and len(kwargs['data'] or []) > 0) or - ((kwargs['bitrate_switch'] or kwargs['error_state_indicator']) and not kwargs['is_fd']) or - isnan(kwargs['timestamp']) or - isinf(kwargs['timestamp']) + ( + not kwargs["is_remote_frame"] + and (len(kwargs["data"] or []) != kwargs["dlc"]) + ) + or (kwargs["arbitration_id"] >= 0x800 and not kwargs["is_extended_id"]) + or kwargs["arbitration_id"] >= 0x20000000 + or kwargs["arbitration_id"] < 0 + or (kwargs["is_remote_frame"] and kwargs["is_error_frame"]) + or (kwargs["is_remote_frame"] and len(kwargs["data"] or []) > 0) + or ( + (kwargs["bitrate_switch"] or kwargs["error_state_indicator"]) + and not kwargs["is_fd"] + ) + or isnan(kwargs["timestamp"]) + or isinf(kwargs["timestamp"]) ) # this should return normally and not throw an exception message = Message(check=is_valid, **kwargs) - if kwargs['data'] is None or kwargs['is_remote_frame']: - kwargs['data'] = bytearray() + if kwargs["data"] is None or kwargs["is_remote_frame"]: + kwargs["data"] = bytearray() - if not is_valid and not kwargs['is_remote_frame']: + if not is_valid and not kwargs["is_remote_frame"]: with self.assertRaises(ValueError): Message(check=True, **kwargs) self.assertGreater(len(str(message)), 0) self.assertGreater(len(message.__repr__()), 0) if is_valid: - self.assertEqual(len(message), kwargs['dlc']) + self.assertEqual(len(message), kwargs["dlc"]) self.assertTrue(bool(message)) self.assertGreater(len("{}".format(message)), 0) _ = "{}".format(message) with self.assertRaises(Exception): _ = "{somespec}".format(message) if sys.version_info.major > 2: - self.assertEqual(bytearray(bytes(message)), kwargs['data']) + self.assertEqual(bytearray(bytes(message)), kwargs["data"]) # check copies and equalities if is_valid: - self.assertEqual(message, message) + self.assertEqual(message, message) normal_copy = copy(message) deep_copy = deepcopy(message) for other in (normal_copy, deep_copy, message): @@ -79,5 +85,5 @@ def test_methods(self, **kwargs): self.assertTrue(message.equals(other, timestamp_delta=0)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 1419a4439..18ddf9e19 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -5,8 +5,6 @@ This module tests :meth:`can.BusABC._matches_filters`. """ -from __future__ import absolute_import - import unittest from can import Bus, Message @@ -17,23 +15,14 @@ EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True) HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) -MATCH_EXAMPLE = [{ - "can_id": 0x123, - "can_mask": 0x1FFFFFFF, - "extended": True -}] +MATCH_EXAMPLE = [{"can_id": 0x123, "can_mask": 0x1FFFFFFF, "extended": True}] -MATCH_ONLY_HIGHEST = [{ - "can_id": 0xFFFFFFFF, - "can_mask": 0x1FFFFFFF, - "extended": True -}] +MATCH_ONLY_HIGHEST = [{"can_id": 0xFFFFFFFF, "can_mask": 0x1FFFFFFF, "extended": True}] class TestMessageFiltering(unittest.TestCase): - def setUp(self): - self.bus = Bus(bustype='virtual', channel='testy') + self.bus = Bus(bustype="virtual", channel="testy") def tearDown(self): self.bus.shutdown() @@ -60,5 +49,5 @@ def test_match_example_message(self): self.assertTrue(self.bus._matches_filters(HIGHEST_MSG)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_sync.py b/test/test_message_sync.py index ec21a0660..b7b911bd0 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -5,8 +5,6 @@ This module tests :class:`can.MessageSync`. """ -from __future__ import absolute_import - from copy import copy from time import time import gc @@ -32,10 +30,11 @@ def inc(value): return value -@unittest.skipIf(IS_APPVEYOR or (IS_TRAVIS and IS_OSX), - "this environment's timings are too unpredictable") +@unittest.skipIf( + IS_APPVEYOR or (IS_TRAVIS and IS_OSX), + "this environment's timings are too unpredictable", +) class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) ComparingMessagesTestCase.__init__(self) @@ -55,7 +54,7 @@ def test_general(self): Message(timestamp=50.0), Message(timestamp=50.0 + 0.05), Message(timestamp=50.0 + 0.05 + 0.08), - Message(timestamp=50.0) # back in time + Message(timestamp=50.0), # back in time ] sync = MessageSync(messages, gap=0.0) @@ -77,7 +76,7 @@ def test_general(self): self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3])) self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4])) - @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative + @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative def test_skip(self): messages = copy(TEST_FEWER_MESSAGES) sync = MessageSync(messages, skip=0.005, gap=0.0) @@ -94,19 +93,17 @@ def test_skip(self): self.assertMessagesEqual(messages, collected) -if not IS_APPVEYOR: # this environment's timings are too unpredictable +if not IS_APPVEYOR: # this environment's timings are too unpredictable @pytest.mark.timeout(inc(0.3)) - @pytest.mark.parametrize("timestamp_1,timestamp_2", [ - (0.0, 0.0), - (0.0, 0.01), - (0.01, 0.0), - ]) + @pytest.mark.parametrize( + "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)] + ) def test_gap(timestamp_1, timestamp_2): """This method is alone so it can be parameterized.""" messages = [ Message(arbitration_id=0x1, timestamp=timestamp_1), - Message(arbitration_id=0x2, timestamp=timestamp_2) + Message(arbitration_id=0x2, timestamp=timestamp_2), ] sync = MessageSync(messages, gap=0.1) @@ -121,5 +118,5 @@ def test_gap(timestamp_1, timestamp_2): assert messages == collected -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_robotell.py b/test/test_robotell.py new file mode 100644 index 000000000..58e2d9a7f --- /dev/null +++ b/test/test_robotell.py @@ -0,0 +1,946 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +import can + + +class robotellTestCase(unittest.TestCase): + def setUp(self): + # will log timeout messages since we are not feeding ack messages to the serial port at this stage + self.bus = can.Bus("loop://", bustype="robotell") + self.serial = self.bus.serialPortOrig + self.serial.read(self.serial.in_waiting) + + def tearDown(self): + self.bus.shutdown() + + def test_recv_extended(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 6) + self.assertSequenceEqual(msg.data, [0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA]) + data = self.serial.read(self.serial.in_waiting) + + def test_send_extended(self): + msg = can.Message( + arbitration_id=0x123456, + is_extended_id=True, + data=[0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA], + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ), + ) + + def test_recv_standard(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x7B, + 0x00, + 0x00, + 0x00, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 8) + self.assertSequenceEqual( + msg.data, [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33] + ) + data = self.serial.read(self.serial.in_waiting) + + def test_send_standard(self): + msg = can.Message( + arbitration_id=123, + is_extended_id=False, + data=[0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33], + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x7B, + 0x00, + 0x00, + 0x00, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ), + ) + + def test_recv_extended_remote(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x01, + 0x01, + 0xA5, + 0xA5, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 7) + data = self.serial.read(self.serial.in_waiting) + + def test_send_extended_remote(self): + msg = can.Message( + arbitration_id=0x123456, is_extended_id=True, is_remote_frame=True, dlc=7 + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x01, + 0x01, + 0xA5, + 0xA5, + 0x55, + 0x55, + ] + ), + ) + + def test_partial_recv(self): + # write some junk data and then start of message + self.serial.write( + bytearray([0x11, 0x22, 0x33, 0xAA, 0xAA, 0x7B, 0x00, 0x00, 0x00, 0x48]) + ) + msg = self.bus.recv(1) + self.assertIsNone(msg) + + # write rest of first message, and then a second message + self.serial.write( + bytearray( + [ + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 8) + self.assertSequenceEqual( + msg.data, [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33] + ) + + # now try to also receive 2nd message + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 6) + self.assertSequenceEqual(msg.data, [0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA]) + + # test nothing more left + msg = self.bus.recv(1) + self.assertIsNone(msg) + data = self.serial.read(self.serial.in_waiting) + + def test_serial_number(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xF0, + 0xFF, + 0xFF, + 0x01, + 0x53, + 0xFF, + 0x6A, + 0x06, + 0x49, + 0x72, + 0x48, + 0xA5, + 0x55, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x11, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xF1, + 0xFF, + 0xFF, + 0x01, + 0x40, + 0x60, + 0x17, + 0x87, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x36, + 0x55, + 0x55, + ] + ) + ) + sn = self.bus.get_serial_number(1) + self.assertEqual(sn, "53FF-6A06-4972-4855-4060-1787") + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xF0, + 0xFF, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xF8, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xF1, + 0xFF, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xF9, + 0x55, + 0x55, + ] + ), + ) + + sn = self.bus.get_serial_number(0) + self.assertIsNone(sn) + data = self.serial.read(self.serial.in_waiting) + + def test_set_bitrate(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xD0, + 0xFE, + 0xFF, + 0x01, + 0x40, + 0x42, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x01, + 0x64, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_bitrate(1000000) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xD0, + 0xFE, + 0xFF, + 0x01, + 0x40, + 0x42, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x00, + 0x63, + 0x55, + 0x55, + ] + ), + ) + + def test_set_auto_retransmit(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xA1, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xA0, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_auto_retransmit(True) + self.bus.set_auto_retransmit(False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xA0, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0x9F, + 0x55, + 0x55, + ] + ), + ) + + def test_set_auto_bus_management(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xB1, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xB0, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_auto_bus_management(True) + self.bus.set_auto_bus_management(False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xB0, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xAF, + 0x55, + 0x55, + ] + ), + ) + + def test_set_serial_rate(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x90, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0xC2, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x01, + 0x56, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_serial_rate(115200) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x90, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0xC2, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x00, + 0xA5, + 0x55, + 0x55, + 0x55, + ] + ), + ) + + def test_set_hw_filter(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0x67, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE1, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xA8, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE2, + 0xFE, + 0xFF, + 0x01, + 0xF0, + 0x01, + 0x00, + 0x00, + 0xF0, + 0x01, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xCB, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_hw_filter(1, True, 0, 0, False) + self.bus.set_hw_filter(2, True, 0, 0, True) + self.bus.set_hw_filter(3, False, 0x1F0, 0x1F0, False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xE0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x66, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xE1, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0xA7, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xE2, + 0xFE, + 0xFF, + 0x01, + 0xF0, + 0x01, + 0x00, + 0x00, + 0xF0, + 0x01, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0xCA, + 0x55, + 0x55, + ] + ), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_scripts.py b/test/test_scripts.py index 74ae71489..9504cf707 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -5,8 +5,6 @@ This module tests that the scripts are all callable. """ -from __future__ import absolute_import - import subprocess import unittest import sys @@ -16,10 +14,7 @@ from .config import * -class CanScriptTest(unittest.TestCase): - - __metaclass__ = ABCMeta - +class CanScriptTest(unittest.TestCase, metaclass=ABCMeta): @classmethod def setUpClass(cls): # clean up the argument list so the call to the main() functions @@ -42,9 +37,13 @@ def test_do_commands_exist(self): output = "-- NO OUTPUT --" allowed = [0, errno.EINVAL] - self.assertIn(return_code, allowed, - 'Calling "{}" failed (exit code was {} and not SUCCESS/0 or EINVAL/22):\n{}' - .format(command, return_code, output)) + self.assertIn( + return_code, + allowed, + 'Calling "{}" failed (exit code was {} and not SUCCESS/0 or EINVAL/22):\n{}'.format( + command, return_code, output + ), + ) def test_does_not_crash(self): # test import @@ -69,11 +68,10 @@ def _import(self): class TestLoggerScript(CanScriptTest): - def _commands(self): commands = [ "python -m can.logger --help", - "python scripts/can_logger.py --help" + "python scripts/can_logger.py --help", ] if IS_UNIX: commands += ["can_logger.py --help"] @@ -81,15 +79,15 @@ def _commands(self): def _import(self): import can.logger as module + return module class TestPlayerScript(CanScriptTest): - def _commands(self): commands = [ "python -m can.player --help", - "python scripts/can_player.py --help" + "python scripts/can_player.py --help", ] if IS_UNIX: commands += ["can_player.py --help"] @@ -97,6 +95,7 @@ def _commands(self): def _import(self): import can.player as module + return module @@ -104,8 +103,8 @@ def _import(self): # this excludes the base class from being executed as a test case itself -del(CanScriptTest) +del CanScriptTest -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_slcan.py b/test/test_slcan.py index 29869cb1c..781fa75df 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # coding: utf-8 + import unittest import can class slcanTestCase(unittest.TestCase): - def setUp(self): - self.bus = can.Bus('loop://', bustype='slcan', sleep_after_open=0) + self.bus = can.Bus("loop://", bustype="slcan", sleep_after_open=0) self.serial = self.bus.serialPortOrig self.serial.read(self.serial.in_waiting) @@ -15,7 +15,7 @@ def tearDown(self): self.bus.shutdown() def test_recv_extended(self): - self.serial.write(b'T12ABCDEF2AA55\r') + self.serial.write(b"T12ABCDEF2AA55\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -25,15 +25,15 @@ def test_recv_extended(self): self.assertSequenceEqual(msg.data, [0xAA, 0x55]) def test_send_extended(self): - msg = can.Message(arbitration_id=0x12ABCDEF, - is_extended_id=True, - data=[0xAA, 0x55]) + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'T12ABCDEF2AA55\r') + self.assertEqual(data, b"T12ABCDEF2AA55\r") def test_recv_standard(self): - self.serial.write(b't4563112233\r') + self.serial.write(b"t4563112233\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x456) @@ -43,15 +43,15 @@ def test_recv_standard(self): self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33]) def test_send_standard(self): - msg = can.Message(arbitration_id=0x456, - is_extended_id=False, - data=[0x11, 0x22, 0x33]) + msg = can.Message( + arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33] + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b't4563112233\r') + self.assertEqual(data, b"t4563112233\r") def test_recv_standard_remote(self): - self.serial.write(b'r1238\r') + self.serial.write(b"r1238\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x123) @@ -60,16 +60,15 @@ def test_recv_standard_remote(self): self.assertEqual(msg.dlc, 8) def test_send_standard_remote(self): - msg = can.Message(arbitration_id=0x123, - is_extended_id=False, - is_remote_frame=True, - dlc=8) + msg = can.Message( + arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8 + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'r1238\r') + self.assertEqual(data, b"r1238\r") def test_recv_extended_remote(self): - self.serial.write(b'R12ABCDEF6\r') + self.serial.write(b"R12ABCDEF6\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -78,20 +77,19 @@ def test_recv_extended_remote(self): self.assertEqual(msg.dlc, 6) def test_send_extended_remote(self): - msg = can.Message(arbitration_id=0x12ABCDEF, - is_extended_id=True, - is_remote_frame=True, - dlc=6) + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6 + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'R12ABCDEF6\r') + self.assertEqual(data, b"R12ABCDEF6\r") def test_partial_recv(self): - self.serial.write(b'T12ABCDEF') + self.serial.write(b"T12ABCDEF") msg = self.bus.recv(0) self.assertIsNone(msg) - self.serial.write(b'2AA55\rT12') + self.serial.write(b"2AA55\rT12") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -103,10 +101,28 @@ def test_partial_recv(self): msg = self.bus.recv(0) self.assertIsNone(msg) - self.serial.write(b'ABCDEF2AA55\r') + self.serial.write(b"ABCDEF2AA55\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) + def test_version(self): + self.serial.write(b"V1013\r") + hw_ver, sw_ver = self.bus.get_version(0) + self.assertEqual(hw_ver, 10) + self.assertEqual(sw_ver, 13) + + hw_ver, sw_ver = self.bus.get_version(0) + self.assertIsNone(hw_ver) + self.assertIsNone(sw_ver) + + def test_serial_number(self): + self.serial.write(b"NA123\r") + sn = self.bus.get_serial_number(0) + self.assertEqual(sn, "A123") + + sn = self.bus.get_serial_number(0) + self.assertIsNone(sn) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_socketcan.py b/test/test_socketcan.py index f010a6372..32389ff9f 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -3,18 +3,27 @@ """ import unittest -try: - from unittest.mock import Mock - from unittest.mock import patch - from unittest.mock import call -except ImportError: - from mock import Mock - from mock import patch - from mock import call +from unittest.mock import Mock +from unittest.mock import patch +from unittest.mock import call import ctypes -from can.interfaces.socketcan.socketcan import bcm_header_factory +from can.interfaces.socketcan.socketcan import ( + bcm_header_factory, + build_bcm_header, + build_bcm_tx_delete_header, + build_bcm_transmit_header, + build_bcm_update_header, + BcmMsgHead, +) +from can.interfaces.socketcan.constants import ( + CAN_BCM_TX_DELETE, + CAN_BCM_TX_SETUP, + SETTIMER, + STARTTIMER, + TX_COUNTEVT, +) class SocketCANTest(unittest.TestCase): @@ -93,7 +102,7 @@ def side_effect_ctypes_alignment(value): @patch("ctypes.sizeof") @patch("ctypes.alignment") - def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_8( + def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_long_8( self, ctypes_sizeof, ctypes_alignment ): """This tests a 32-bit platform (ex. Raspbian Stretch on armv7l), where: @@ -162,7 +171,7 @@ def side_effect_ctypes_alignment(value): @patch("ctypes.sizeof") @patch("ctypes.alignment") - def test_bcm_header_factory_64_bit_sizeof_long_4_alignof_long_4( + def test_bcm_header_factory_64_bit_sizeof_long_8_alignof_long_8( self, ctypes_sizeof, ctypes_alignment ): """This tests a 64-bit platform (ex. Ubuntu 18.04 on x86_64), where: @@ -229,6 +238,146 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) + @unittest.skipIf( + not ( + ctypes.sizeof(ctypes.c_long) == 4 and ctypes.alignment(ctypes.c_long) == 4 + ), + "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4", + ) + def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): + expected_result = b"" + expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x01\x04\x00\x00" + expected_result += b"\x01\x00\x00\x00\x00\x00\x00\x00" + + self.assertEqual( + expected_result, + build_bcm_header( + opcode=CAN_BCM_TX_DELETE, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=0x401, + nframes=1, + ), + ) + + @unittest.skipIf( + not ( + ctypes.sizeof(ctypes.c_long) == 8 and ctypes.alignment(ctypes.c_long) == 8 + ), + "Should only run on platforms where sizeof(long) == 8 and alignof(long) == 8", + ) + def test_build_bcm_header_sizeof_long_8_alignof_long_8(self): + expected_result = b"" + expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x01\x04\x00\x00\x01\x00\x00\x00" + + self.assertEqual( + expected_result, + build_bcm_header( + opcode=CAN_BCM_TX_DELETE, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=0x401, + nframes=1, + ), + ) + + def test_build_bcm_tx_delete_header(self): + can_id = 0x401 + flags = 0 + bcm_buffer = build_bcm_tx_delete_header(can_id=can_id, flags=flags) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_DELETE, result.opcode) + self.assertEqual(flags, result.flags) + self.assertEqual(0, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(0, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_transmit_header_initial_period_0(self): + can_id = 0x401 + flags = 0 + count = 42 + bcm_buffer = build_bcm_transmit_header( + can_id=can_id, + count=count, + initial_period=0, + subsequent_period=2, + msg_flags=flags, + ) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + # SETTIMER and STARTTIMER should be added to the initial flags + self.assertEqual(flags | SETTIMER | STARTTIMER, result.flags) + self.assertEqual(count, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(2, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_transmit_header_initial_period_1_24(self): + can_id = 0x401 + flags = 0 + count = 42 + bcm_buffer = build_bcm_transmit_header( + can_id=can_id, + count=count, + initial_period=1.24, + subsequent_period=2, + msg_flags=flags, + ) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + # SETTIMER, STARTTIMER, TX_COUNTEVT should be added to the initial flags + self.assertEqual(flags | SETTIMER | STARTTIMER | TX_COUNTEVT, result.flags) + self.assertEqual(count, result.count) + self.assertEqual(1, result.ival1_tv_sec) + self.assertEqual(240000, result.ival1_tv_usec) + self.assertEqual(2, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_update_header(self): + can_id = 0x401 + flags = 0 + bcm_buffer = build_bcm_update_header(can_id=can_id, msg_flags=flags) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + self.assertEqual(flags, result.flags) + self.assertEqual(0, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(0, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + if __name__ == "__main__": unittest.main() diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index f1462549a..311398657 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -5,18 +5,14 @@ Tests helpers in `can.interfaces.socketcan.socketcan_common`. """ -from __future__ import absolute_import - import unittest -from can.interfaces.socketcan.utils import \ - find_available_interfaces, error_code_to_str +from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str from .config import * class TestSocketCanHelpers(unittest.TestCase): - @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ @@ -29,18 +25,18 @@ def test_error_code_to_str(self): for error_code in test_data: string = error_code_to_str(error_code) - self.assertTrue(string) # not None or empty + self.assertTrue(string) # not None or empty @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_find_available_interfaces(self): result = list(find_available_interfaces()) self.assertGreaterEqual(len(result), 0) for entry in result: - self.assertRegexpMatches(entry, r"v?can\d+") + self.assertRegex(entry, r"v?can\d+") if TEST_INTERFACE_SOCKETCAN: self.assertGreaterEqual(len(result), 1) self.assertIn("vcan0", result) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_systec.py b/test/test_systec.py index ce5dda4a7..6910653fc 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -2,10 +2,7 @@ # coding: utf-8 import unittest -try: - from unittest.mock import Mock, patch -except ImportError: - from mock import Mock, patch +from unittest.mock import Mock, patch import can from can.interfaces.systec import ucan, ucanbus @@ -13,10 +10,12 @@ class SystecTest(unittest.TestCase): - def compare_message(self, first, second, msg): - if first.arbitration_id != second.arbitration_id or first.data != second.data or \ - first.is_extended_id != second.is_extended_id: + if ( + first.arbitration_id != second.arbitration_id + or first.data != second.data + or first.is_extended_id != second.is_extended_id + ): raise self.failureException(msg) def setUp(self): @@ -33,12 +32,14 @@ def setUp(self): ucan.UcanDeinitHardware = Mock() ucan.UcanWriteCanMsgEx = Mock() ucan.UcanResetCanEx = Mock() - self.bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + self.bus = can.Bus(bustype="systec", channel=0, bitrate=125000) def test_bus_creation(self): self.assertIsInstance(self.bus, ucanbus.UcanBus) self.assertTrue(ucan.UcanInitHwConnectControlEx.called) - self.assertTrue(ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called) + self.assertTrue( + ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called + ) self.assertTrue(ucan.UcanInitCanEx2.called) self.assertTrue(ucan.UcanGetHardwareInfoEx2.called) self.assertTrue(ucan.UcanSetAcceptanceEx.called) @@ -50,9 +51,7 @@ def test_bus_shutdown(self): def test_filter_setup(self): # no filter in the constructor - expected_args = ( - (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), - ) + expected_args = ((self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL),) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) # one filter is handled by the driver @@ -60,33 +59,30 @@ def test_filter_setup(self): can_filter = (True, 0x123, 0x123, False, False) self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter)) expected_args = ( - (self.bus._ucan._handle, - 0, - ucan.UcanServer.calculate_amr(*can_filter), - ucan.UcanServer.calculate_acr(*can_filter) - ), + ( + self.bus._ucan._handle, + 0, + ucan.UcanServer.calculate_amr(*can_filter), + ucan.UcanServer.calculate_acr(*can_filter), + ), ) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) # multiple filters are handled by the bus ucan.UcanSetAcceptanceEx.reset_mock() - can_filter = ( - (False, 0x8, 0x8, False, False), - (False, 0x9, 0x9, False, False) - ) - self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter[0]) + - ucanbus.UcanBus.create_filter(*can_filter[1])) - expected_args = ( - (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + can_filter = ((False, 0x8, 0x8, False, False), (False, 0x9, 0x9, False, False)) + self.bus.set_filters( + ucanbus.UcanBus.create_filter(*can_filter[0]) + + ucanbus.UcanBus.create_filter(*can_filter[1]) ) + expected_args = ((self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL),) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.write_can_msg") def test_send_extended(self, mock_write_can_msg): msg = can.Message( - arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4], - is_extended_id=True) + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4], is_extended_id=True + ) self.bus.send(msg) expected_args = ( @@ -94,12 +90,9 @@ def test_send_extended(self, mock_write_can_msg): ) self.assertEqual(mock_write_can_msg.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.write_can_msg") def test_send_standard(self, mock_write_can_msg): - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) self.bus.send(msg) expected_args = ( @@ -107,41 +100,43 @@ def test_send_standard(self, mock_write_can_msg): ) self.assertEqual(mock_write_can_msg.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") def test_recv_no_message(self, mock_get_msg_pending): mock_get_msg_pending.return_value = 0 self.assertEqual(self.bus.recv(timeout=0.5), None) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') - @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") + @patch("can.interfaces.systec.ucan.UcanServer.read_can_msg") def test_recv_extended(self, mock_read_can_msg, mock_get_msg_pending): - mock_read_can_msg.return_value = [CanMsg(0xc0ffef, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], 0 + mock_read_can_msg.return_value = ( + [CanMsg(0xC0FFEF, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], + 0, + ) mock_get_msg_pending.return_value = 1 msg = can.Message( - arbitration_id=0xc0ffef, - data=[1, 2, 3, 4, 5, 6, 7, 8], - is_extended_id=True) + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) can_msg = self.bus.recv() self.assertEqual(can_msg, msg) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') - @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") + @patch("can.interfaces.systec.ucan.UcanServer.read_can_msg") def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending): - mock_read_can_msg.return_value = [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], 0 + mock_read_can_msg.return_value = ( + [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], + 0, + ) mock_get_msg_pending.return_value = 1 - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) can_msg = self.bus.recv() self.assertEqual(can_msg, msg) @staticmethod def test_bus_defaults(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0) + bus = can.Bus(bustype="systec", channel=0) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -153,14 +148,14 @@ def test_bus_defaults(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_channel(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=1) + bus = can.Bus(bustype="systec", channel=1) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 1, @@ -172,14 +167,14 @@ def test_bus_channel(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_bitrate(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + bus = can.Bus(bustype="systec", channel=0, bitrate=125000) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -191,18 +186,18 @@ def test_bus_bitrate(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) def test_bus_custom_bitrate(self): with self.assertRaises(ValueError): - can.Bus(bustype='systec', channel=0, bitrate=123456) + can.Bus(bustype="systec", channel=0, bitrate=123456) @staticmethod def test_receive_own_messages(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, receive_own_messages=True) + bus = can.Bus(bustype="systec", channel=0, receive_own_messages=True) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -214,14 +209,14 @@ def test_receive_own_messages(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_passive_state(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, state=can.BusState.PASSIVE) + bus = can.Bus(bustype="systec", channel=0, state=can.BusState.PASSIVE) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -233,14 +228,14 @@ def test_bus_passive_state(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_rx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, rx_buffer_entries=1024) + bus = can.Bus(bustype="systec", channel=0, rx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -252,14 +247,14 @@ def test_rx_buffer_entries(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, 1024, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_tx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, tx_buffer_entries=1024) + bus = can.Bus(bustype="systec", channel=0, tx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -271,14 +266,16 @@ def test_tx_buffer_entries(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - 1024 - ) + 1024, + ), ) def test_flush_tx_buffer(self): self.bus.flush_tx_buffer() - ucan.UcanResetCanEx.assert_called_once_with(self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF) + ucan.UcanResetCanEx.assert_called_once_with( + self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_vector.py b/test/test_vector.py new file mode 100644 index 000000000..a2d47c74e --- /dev/null +++ b/test/test_vector.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Test for Vector Interface +""" + +import ctypes +import time +import logging +import os +import unittest +from unittest.mock import Mock + +import pytest + +import can +from can.interfaces.vector import canlib, xldefine, xlclass + + +class TestVectorBus(unittest.TestCase): + def setUp(self) -> None: + # basic mock for XLDriver + can.interfaces.vector.canlib.xldriver = Mock() + + # bus creation functions + can.interfaces.vector.canlib.xldriver.xlOpenDriver = Mock() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig = Mock( + side_effect=xlGetApplConfig + ) + can.interfaces.vector.canlib.xldriver.xlGetChannelIndex = Mock( + side_effect=xlGetChannelIndex + ) + can.interfaces.vector.canlib.xldriver.xlOpenPort = Mock(side_effect=xlOpenPort) + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelMode = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlActivateChannel = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlGetSyncTime = Mock( + side_effect=xlGetSyncTime + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelAcceptance = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlSetNotification = Mock( + side_effect=xlSetNotification + ) + + # bus deactivation functions + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlClosePort = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlCloseDriver = Mock() + + # sender functions + can.interfaces.vector.canlib.xldriver.xlCanTransmit = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx = Mock(return_value=0) + + # various functions + can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue = Mock() + can.interfaces.vector.canlib.WaitForSingleObject = Mock() + + self.bus = None + + def tearDown(self) -> None: + if self.bus: + self.bus.shutdown() + self.bus = None + + def test_bus_creation(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + def test_bus_creation_bitrate(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", bitrate=200000, _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called() + xlCanSetChannelBitrate_args = can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[ + 0 + ] + self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) + + def test_bus_creation_fd(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + def test_bus_creation_fd_bitrate_timings(self) -> None: + self.bus = can.Bus( + channel=0, + bustype="vector", + fd=True, + bitrate=500000, + data_bitrate=2000000, + sjwAbr=10, + tseg1Abr=11, + tseg2Abr=12, + sjwDbr=13, + tseg1Dbr=14, + tseg2Dbr=15, + _testing=True, + ) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + xlCanFdSetConfiguration_args = can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[ + 0 + ] + canFdConf = xlCanFdSetConfiguration_args[2] + self.assertEqual(canFdConf.arbitrationBitRate, 500000) + self.assertEqual(canFdConf.dataBitRate, 2000000) + self.assertEqual(canFdConf.sjwAbr, 10) + self.assertEqual(canFdConf.tseg1Abr, 11) + self.assertEqual(canFdConf.tseg2Abr, 12) + self.assertEqual(canFdConf.sjwDbr, 13) + self.assertEqual(canFdConf.tseg1Dbr, 14) + self.assertEqual(canFdConf.tseg2Dbr, 15) + + def test_receive(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + + def test_receive_fd(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive + ) + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + + def test_receive_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock( + side_effect=xlReceive_chipstate + ) + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.handle_can_event = Mock() + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + self.bus.handle_can_event.assert_called() + + def test_receive_fd_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive_chipstate + ) + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.handle_canfd_event = Mock() + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + self.bus.handle_canfd_event.assert_called() + + def test_send(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called() + + def test_send_fd(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() + + def test_flush_tx_buffer(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.flush_tx_buffer() + can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() + + def test_shutdown(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.shutdown() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() + can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() + + def test_reset(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.reset() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() + + def test_popup_hw_cfg(self) -> None: + canlib.xldriver.xlPopupHwConfig = Mock() + canlib.VectorBus.popup_vector_hw_configuration(10) + assert canlib.xldriver.xlPopupHwConfig.called + args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args + assert isinstance(args[0], ctypes.c_char_p) + assert isinstance(args[1], ctypes.c_uint) + + def test_called_without_testing_argument(self) -> None: + """This tests if an exception is thrown when we are not running on Windows.""" + if os.name != "nt": + with self.assertRaises(OSError): + # do not set the _testing argument, since it supresses the exception + can.Bus(channel=0, bustype="vector") + + +def xlGetApplConfig( + app_name_p: ctypes.c_char_p, + app_channel: ctypes.c_uint, + hw_type: ctypes.POINTER(ctypes.c_uint), + hw_index: ctypes.POINTER(ctypes.c_uint), + hw_channel: ctypes.POINTER(ctypes.c_uint), + bus_type: ctypes.c_uint, +) -> int: + hw_type.value = 1 + hw_channel.value = app_channel + return 0 + + +def xlGetChannelIndex( + hw_type: ctypes.c_int, hw_index: ctypes.c_int, hw_channel: ctypes.c_int +) -> int: + return hw_channel + + +def xlOpenPort( + port_handle_p: ctypes.POINTER(xlclass.XLportHandle), + app_name_p: ctypes.c_char_p, + access_mask: xlclass.XLaccess, + permission_mask_p: ctypes.POINTER(xlclass.XLaccess), + rx_queue_size: ctypes.c_uint, + xl_interface_version: ctypes.c_uint, + bus_type: ctypes.c_uint, +) -> int: + port_handle_p.value = 0 + return 0 + + +def xlGetSyncTime( + port_handle: xlclass.XLportHandle, time_p: ctypes.POINTER(xlclass.XLuint64) +) -> int: + time_p.value = 544219859027581 + return 0 + + +def xlSetNotification( + port_handle: xlclass.XLportHandle, + event_handle: ctypes.POINTER(xlclass.XLhandle), + queue_level: ctypes.c_int, +) -> int: + event_handle.value = 520 + return 0 + + +def xlReceive( + port_handle: xlclass.XLportHandle, + event_count_p: ctypes.POINTER(ctypes.c_uint), + event: ctypes.POINTER(xlclass.XLevent), +) -> int: + event.tag = xldefine.XL_EventTags.XL_RECEIVE_MSG.value + event.tagData.msg.id = 0x123 + event.tagData.msg.dlc = 8 + event.tagData.msg.flags = 0 + event.timeStamp = 0 + event.chanIndex = 0 + for idx, value in enumerate([1, 2, 3, 4, 5, 6, 7, 8]): + event.tagData.msg.data[idx] = value + return 0 + + +def xlCanReceive( + port_handle: xlclass.XLportHandle, event: ctypes.POINTER(xlclass.XLcanRxEvent) +) -> int: + event.tag = xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value + event.tagData.canRxOkMsg.canId = 0x123 + event.tagData.canRxOkMsg.dlc = 8 + event.tagData.canRxOkMsg.msgFlags = 0 + event.timeStamp = 0 + event.chanIndex = 0 + for idx, value in enumerate([1, 2, 3, 4, 5, 6, 7, 8]): + event.tagData.canRxOkMsg.data[idx] = value + return 0 + + +def xlReceive_chipstate( + port_handle: xlclass.XLportHandle, + event_count_p: ctypes.POINTER(ctypes.c_uint), + event: ctypes.POINTER(xlclass.XLevent), +) -> int: + event.tag = xldefine.XL_EventTags.XL_CHIP_STATE.value + event.tagData.chipState.busStatus = 8 + event.tagData.chipState.rxErrorCounter = 0 + event.tagData.chipState.txErrorCounter = 0 + event.timeStamp = 0 + event.chanIndex = 2 + return 0 + + +def xlCanReceive_chipstate( + port_handle: xlclass.XLportHandle, event: ctypes.POINTER(xlclass.XLcanRxEvent) +) -> int: + event.tag = xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_CHIP_STATE.value + event.tagData.canChipState.busStatus = 8 + event.tagData.canChipState.rxErrorCounter = 0 + event.tagData.canChipState.txErrorCounter = 0 + event.timeStamp = 0 + event.chanIndex = 2 + return 0 + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_viewer.py b/test/test_viewer.py index c4b7aa45f..a0873d02b 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -23,35 +23,25 @@ # Web : http://www.lauszus.com # e-mail : lauszus@gmail.com -from __future__ import absolute_import - import argparse -import can import curses import math -import pytest +import os import random import struct import time import unittest -import os -import six - from typing import Dict, Tuple, Union +from unittest.mock import patch -try: - # noinspection PyCompatibility - from unittest.mock import Mock, patch -except ImportError: - # noinspection PyPackageRequirements - from mock import Mock, patch +import pytest +import can from can.viewer import KEY_ESC, KEY_SPACE, CanViewer, parse_args # noinspection SpellCheckingInspection,PyUnusedLocal class StdscrDummy: - def __init__(self): self.key_counter = 0 @@ -74,7 +64,7 @@ def addstr(row, col, txt, *args): assert col >= 0 assert txt is not None # Raise an exception 50 % of the time, so we can make sure the code handles it - if random.random() < .5: + if random.random() < 0.5: raise curses.error @staticmethod @@ -87,13 +77,13 @@ def getch(self): # Send invalid key return -1 elif self.key_counter == 2: - return ord('c') # Clear + return ord("c") # Clear elif self.key_counter == 3: return KEY_SPACE # Pause elif self.key_counter == 4: return KEY_SPACE # Unpause elif self.key_counter == 5: - return ord('s') # Sort + return ord("s") # Sort # Keep scrolling until it exceeds the number of messages elif self.key_counter <= 100: @@ -106,7 +96,6 @@ def getch(self): class CanViewerTest(unittest.TestCase): - @classmethod def setUpClass(cls): # Set seed, so the tests are not affected @@ -114,33 +103,33 @@ def setUpClass(cls): def setUp(self): stdscr = StdscrDummy() - config = {'interface': 'virtual', 'receive_own_messages': True} + config = {"interface": "virtual", "receive_own_messages": True} bus = can.Bus(**config) data_structs = None - patch_curs_set = patch('curses.curs_set') + patch_curs_set = patch("curses.curs_set") patch_curs_set.start() self.addCleanup(patch_curs_set.stop) - patch_use_default_colors = patch('curses.use_default_colors') + patch_use_default_colors = patch("curses.use_default_colors") patch_use_default_colors.start() self.addCleanup(patch_use_default_colors.stop) - patch_init_pair = patch('curses.init_pair') + patch_init_pair = patch("curses.init_pair") patch_init_pair.start() self.addCleanup(patch_init_pair.stop) - patch_color_pair = patch('curses.color_pair') + patch_color_pair = patch("curses.color_pair") patch_color_pair.start() self.addCleanup(patch_color_pair.stop) - patch_is_term_resized = patch('curses.is_term_resized') + patch_is_term_resized = patch("curses.is_term_resized") mock_is_term_resized = patch_is_term_resized.start() - mock_is_term_resized.return_value = True if random.random() < .5 else False + mock_is_term_resized.return_value = True if random.random() < 0.5 else False self.addCleanup(patch_is_term_resized.stop) - if hasattr(curses, 'resizeterm'): - patch_resizeterm = patch('curses.resizeterm') + if hasattr(curses, "resizeterm"): + patch_resizeterm = patch("curses.resizeterm") patch_resizeterm.start() self.addCleanup(patch_resizeterm.stop) @@ -183,7 +172,7 @@ def test_send(self): # self.assertTupleEqual(self.can_viewer.parse_canopen_message(msg), (None, None)) # Send the same message again to make sure that resending works and dt is correct - time.sleep(.1) + time.sleep(0.1) self.can_viewer.bus.send(msg) # Send error message @@ -196,41 +185,46 @@ def test_receive(self): data_structs = { # For converting the EMCY and HEARTBEAT messages - 0x080 + 0x01: struct.Struct('ff'), + 0x123456: struct.Struct(">ff"), } # Receive the messages we just sent in 'test_canopen' while 1: msg = self.can_viewer.bus.recv(timeout=0) if msg is not None: - self.can_viewer.data_structs = data_structs if msg.arbitration_id != 0x101 else None + self.can_viewer.data_structs = ( + data_structs if msg.arbitration_id != 0x101 else None + ) _id = self.can_viewer.draw_can_bus_message(msg) - if _id['msg'].arbitration_id == 0x101: + if _id["msg"].arbitration_id == 0x101: # Check if the counter is reset when the length has changed - self.assertEqual(_id['count'], 1) - elif _id['msg'].arbitration_id == 0x123456: + self.assertEqual(_id["count"], 1) + elif _id["msg"].arbitration_id == 0x123456: # Check if the counter is incremented - if _id['dt'] == 0: - self.assertEqual(_id['count'], 1) + if _id["dt"] == 0: + self.assertEqual(_id["count"], 1) else: - self.assertTrue(pytest.approx(_id['dt'], 0.1)) # dt should be ~0.1 s - self.assertEqual(_id['count'], 2) + self.assertTrue( + pytest.approx(_id["dt"], 0.1) + ) # dt should be ~0.1 s + self.assertEqual(_id["count"], 2) else: # Make sure dt is 0 - if _id['count'] == 1: - self.assertEqual(_id['dt'], 0) + if _id["count"] == 1: + self.assertEqual(_id["dt"], 0) else: break # Convert it into raw integer values and then pack the data @staticmethod - def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *int]) -> bytes + def pack_data( + cmd, cmd_to_struct, *args + ): # type: (int, Dict, Union[*float, *int]) -> bytes if not cmd_to_struct or len(args) == 0: # If no arguments are given, then the message does not contain a data package - return b'' + return b"" for key in cmd_to_struct.keys(): if cmd == key if isinstance(key, int) else cmd in key: @@ -241,17 +235,17 @@ def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *in # The conversion from SI-units to raw values are given in the rest of the tuple fmt = struct_t.format - if isinstance(fmt, six.string_types): # pragma: no cover + if isinstance(fmt, str): # pragma: no cover # Needed for Python 3.7 - fmt = six.b(fmt) + fmt = fmt.encode() # Make sure the endian is given as the first argument - assert six.byte2int(fmt) == ord('<') or six.byte2int(fmt) == ord('>') + assert fmt[0] == ord("<") or fmt[0] == ord(">") # Disable rounding if the format is a float data = [] - for c, arg, val in zip(six.iterbytes(fmt[1:]), args, value[1:]): - if c == ord('f'): + for c, arg, val in zip(fmt[1:], args, value[1:]): + if c == ord("f"): data.append(arg * val) else: data.append(round(arg * val)) @@ -262,7 +256,7 @@ def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *in return struct_t.pack(*data) else: - raise ValueError('Unknown command: 0x{:02X}'.format(cmd)) + raise ValueError("Unknown command: 0x{:02X}".format(cmd)) def test_pack_unpack(self): CANOPEN_TPDO1 = 0x180 @@ -290,16 +284,18 @@ def test_pack_unpack(self): # are divided by the value in order to convert from real units to raw integer values. data_structs = { # CANopen node 1 - CANOPEN_TPDO1 + 1: struct.Struct('lL'), - (CANOPEN_TPDO3 + 2, CANOPEN_TPDO4 + 2): struct.Struct('>LL'), + CANOPEN_TPDO2 + 2: struct.Struct(">lL"), + (CANOPEN_TPDO3 + 2, CANOPEN_TPDO4 + 2): struct.Struct(">LL"), } # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] - raw_data = self.pack_data(CANOPEN_TPDO1 + 1, data_structs, -7, 13, -1024, 2048, 0xFFFF) + raw_data = self.pack_data( + CANOPEN_TPDO1 + 1, data_structs, -7, 13, -1024, 2048, 0xFFFF + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO1 + 1, data_structs, raw_data) self.assertListEqual(parsed_data, [-7, 13, -1024, 2048, 0xFFFF]) self.assertTrue(all(isinstance(d, int) for d in parsed_data)) @@ -307,17 +303,22 @@ def test_pack_unpack(self): raw_data = self.pack_data(CANOPEN_TPDO2 + 1, data_structs, 12.34, 4.5, 6) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 1, data_structs, raw_data) self.assertTrue(pytest.approx(parsed_data, [12.34, 4.5, 6])) - self.assertTrue(isinstance(parsed_data[0], float) and isinstance(parsed_data[1], float) and - isinstance(parsed_data[2], int)) + self.assertTrue( + isinstance(parsed_data[0], float) + and isinstance(parsed_data[1], float) + and isinstance(parsed_data[2], int) + ) raw_data = self.pack_data(CANOPEN_TPDO3 + 1, data_structs, 123.45, 67.89) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO3 + 1, data_structs, raw_data) self.assertTrue(pytest.approx(parsed_data, [123.45, 67.89])) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) - raw_data = self.pack_data(CANOPEN_TPDO4 + 1, data_structs, math.pi / 2., math.pi) + raw_data = self.pack_data( + CANOPEN_TPDO4 + 1, data_structs, math.pi / 2.0, math.pi + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 1, data_structs, raw_data) - self.assertTrue(pytest.approx(parsed_data, [math.pi / 2., math.pi])) + self.assertTrue(pytest.approx(parsed_data, [math.pi / 2.0, math.pi])) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) raw_data = self.pack_data(CANOPEN_TPDO1 + 2, data_structs) @@ -325,7 +326,9 @@ def test_pack_unpack(self): self.assertListEqual(parsed_data, []) self.assertIsInstance(parsed_data, list) - raw_data = self.pack_data(CANOPEN_TPDO2 + 2, data_structs, -2147483648, 0xFFFFFFFF) + raw_data = self.pack_data( + CANOPEN_TPDO2 + 2, data_structs, -2147483648, 0xFFFFFFFF + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 2, data_structs, raw_data) self.assertListEqual(parsed_data, [-2147483648, 0xFFFFFFFF]) @@ -337,59 +340,63 @@ def test_pack_unpack(self): parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 2, data_structs, raw_data) self.assertListEqual(parsed_data, [0xFFFFFF, 0xFFFFFFFF]) - # See: http://python-future.org/compatible_idioms.html#long-integers - from past.builtins import long - self.assertTrue(all(isinstance(d, (int, long)) for d in parsed_data)) + self.assertTrue(all(isinstance(d, int) for d in parsed_data)) # Make sure that the ValueError exception is raised with self.assertRaises(ValueError): self.pack_data(0x101, data_structs, 1, 2, 3, 4) with self.assertRaises(ValueError): - CanViewer.unpack_data(0x102, data_structs, b'\x01\x02\x03\x04\x05\x06\x07\x08') + CanViewer.unpack_data( + 0x102, data_structs, b"\x01\x02\x03\x04\x05\x06\x07\x08" + ) def test_parse_args(self): - parsed_args, _, _ = parse_args(['-b', '250000']) + parsed_args, _, _ = parse_args(["-b", "250000"]) self.assertEqual(parsed_args.bitrate, 250000) - parsed_args, _, _ = parse_args(['--bitrate', '500000']) + parsed_args, _, _ = parse_args(["--bitrate", "500000"]) self.assertEqual(parsed_args.bitrate, 500000) - parsed_args, _, _ = parse_args(['-c', 'can0']) - self.assertEqual(parsed_args.channel, 'can0') + parsed_args, _, _ = parse_args(["-c", "can0"]) + self.assertEqual(parsed_args.channel, "can0") - parsed_args, _, _ = parse_args(['--channel', 'PCAN_USBBUS1']) - self.assertEqual(parsed_args.channel, 'PCAN_USBBUS1') + parsed_args, _, _ = parse_args(["--channel", "PCAN_USBBUS1"]) + self.assertEqual(parsed_args.channel, "PCAN_USBBUS1") - parsed_args, _, data_structs = parse_args(['-d', '100: