diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1eaa090..b7aee22 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools - python -m pip install .[test] + python -m pip install .[dev] - name: Test with pytest run: | pytest --cov=objdictgen --cov-report=xml -p no:logging --tb=no diff --git a/.gitignore b/.gitignore index a9d6d13..d2c422c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ htmlcov/ .coverage tmp*/ dist/ +.nox +coverage.xml *.pyc *.bak diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index a5c7132..0000000 --- a/.pylintrc +++ /dev/null @@ -1,587 +0,0 @@ -[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-allow-list= - -# 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. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -extension-pkg-whitelist= - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -fail-on= - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10.0 - -# Files or directories to be skipped. They should be base names, not paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against paths and can be in Posix or Windows format. -ignore-paths= - -# Files or directories matching the regex patterns are skipped. 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 module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Minimum Python version to use for version dependent checks. Will default to -# the version used to run pylint. -py-version=3.10 - -# 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=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - missing-module-docstring, - missing-docstring, - consider-using-f-string, - unspecified-encoding, - useless-object-inheritance, - logging-format-interpolation, - logging-not-lazy, - duplicate-code, - no-self-use, - too-many-instance-attributes, - too-many-nested-blocks, - too-many-locals, - too-many-statements, - too-many-branches, - too-many-public-methods, - too-few-public-methods, - too-many-arguments, - too-many-return-statements, - -# 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 score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score 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=colorized - -# 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,argparse.parse_error - - -[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=any - -# 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 - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# 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 constant names. -class-const-naming-style=UPPER_CASE - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. -#class-const-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=any - -# 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, - _, - fn, od, kw, n, f, s, d, a, p, jd, m, v, dt, ir, w, tb, c - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# 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=any - -# 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= - - -[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=200 - -# Maximum number of lines in a module. -max-module-lines=2000 - -# 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 - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `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 - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[SIMILARITIES] - -# Comments are removed from the similarity computation -ignore-comments=yes - -# Docstrings are removed from the similarity computation -ignore-docstrings=yes - -# Imports are removed from the similarity computation -ignore-imports=no - -# Signatures are removed from the similarity computation -ignore-signatures=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the 'python-enchant' package. -spelling-dict= - -# List of comma separated words that should be considered directives if they -# appear and the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[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 -# class is considered mixin if its name matches the mixin-class-rgx option. -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 - -# Regex pattern to define which classes are considered mixins ignore-mixin- -# members is set to 'yes' -mixin-class-rgx=.*[Mm]ixin - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[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 names allowed to shadow builtins -allowed-redefined-builtins= - -# 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 - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# 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] - -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -exclude-too-few-public-methods= - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# 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 (see R0916). -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 - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# 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= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to 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 - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception diff --git a/TODO.md b/TODO.md deleted file mode 100644 index e9c6818..0000000 --- a/TODO.md +++ /dev/null @@ -1,34 +0,0 @@ -# TODO - -# Issues - -* [ ] Viewing of PDO Receive in UI it will crash it. *2022-08-03* - -* [ ] Add info in gen_cfile.py to indicate which tool that generated the output - -* [ ] Crash on GUI: new slave -> view server SDO parameter - -* [ ] Save as in editor doesn't set the filename. *2022-08-02* - -# Resolved - -* [X] Change `struct`, `need`, `type` to human readable variants in JSON - format. *2022-08-03* -* [X] Index 0x160E (5646) from ResusciFamilyMasterOD.od fails with - "Index 0x160e (5646): User parameters not empty. Programming error?" - *2022-08-03* -* [X] Logging doesn't seem to work in py. No WARNINGS printed. *2022-08-03* -* [X] Slave created in editor crash on save as json. "While processing index - 0x1401 (5121): Missing mapping". *2022-08-02* -* [-] How to add more than one parameter for NVAR, NARRAY and NRECORD from UI? -* [X] Add 3x 0x1400 PDO Receive in jsontest.od. Needed to verify NARRAY - *2022-08-03* -* [X] Running `odg diff jsontest.od jsontest.json` doesn't compare equal - *2022-08-03* -* [X] `odg diff` doesnt return error code even if difference. *2022-08-03* -* [X] Convert `odg conversion jsontest.od jsontest.json` fails with "Uexpected - parameters 'nbmin' in mapping values". *2022-08-03* -* [X] `odg list jsontest.od` fails on index 0x1600. *2022-08-03* -* [X] Crash on GUI: new master -> custom profile -> save json -* [X] Fix issue with 'incr', 'nbmax' and 'group' found in some OD -* [X] Add version info in json od to ensure we are not forward compatible? diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..6170fb9 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,8 @@ +import nox + +@nox.session( + python=["3.12", "3.11", "3.10"] +) +def test(session): + session.install(".[dev]") + session.run("pytest") diff --git a/pyproject.toml b/pyproject.toml index 57c52aa..8f12171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,606 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel" -] +requires = ["setuptools"] build-backend = "setuptools.build_meta" -[tool.vulture] -ignore_names = ["object", "main_*"] -# verbose = true +#=================== +# PYTEST +#=================== + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-l --tb=native" +testpaths = [ + "tests", +] +filterwarnings = "ignore::DeprecationWarning" + +#=================== +# MYPY +#=================== + +[tool.mypy] +# enable_incomplete_feature = ["Unpack"] + +# Start off with these +warn_unused_configs = true +warn_redundant_casts = true +warn_unused_ignores = true + +# Getting these passing should be easy +strict_equality = true +strict_concatenate = true + +# Strongly recommend enabling this one as soon as you can +check_untyped_defs = true + +# These shouldn't be too much additional work, but may be tricky to +# get passing if you use a lot of untyped libraries +# disallow_subclassing_any = true +# disallow_untyped_decorators = true +# disallow_any_generics = true + +# These next few are various gradations of forcing use of type annotations +# disallow_untyped_calls = true +# disallow_incomplete_defs = true +# disallow_untyped_defs = true + +# This one isn't too hard to get passing, but return on investment is lower +# no_implicit_reexport = true + +# This one can be tricky to get passing if you use a lot of untyped libraries +# warn_return_any = true + +#=================== +# PYLINT +#=================== + +[tool.pylint.main] +# 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 = + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint in +# a server-like mode. +# clear-cache-post-run = + +# Always return a 0 (non-error) status code, even if lint errors are found. This +# is primarily useful in continuous integration scripts. +# exit-zero = + +# 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-allow-list = + +# 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. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +# objdictgen: add this +extension-pkg-whitelist = "wx" + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +# fail-on = + +# Specify a score threshold under which the program will exit with error. +fail-under = 10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +# from-stdin = + +# Files or directories to be skipped. They should be base names, not paths. +ignore = ["CVS"] + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, it +# can't be used as an escape character. +# ignore-paths = + +# Files or directories matching the regular expression patterns are skipped. The +# regex matches against base names, not paths. The default value ignores Emacs +# file locks +ignore-patterns = ["^\\.#"] + +# 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 = + +# 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, and will cap the count on Windows to +# avoid hangs. +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 module names) to load, +# usually to register additional checkers. +# load-plugins = +load-plugins=[ "pylint.extensions.no_self_use" ] + +# Pickle collected data for later comparisons. +persistent = true + +# Minimum Python version to use for version dependent checks. Will default to the +# version used to run pylint. +py-version = "3.11" + +# Discover python modules and packages in the file system subtree. +# recursive = + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +# source-roots = + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode = true + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +# unsafe-load-any-extension = + +[tool.pylint.basic] +# Naming style matching correct argument names. +argument-naming-style = "snake_case" + +# Regular expression matching correct argument names. Overrides argument-naming- +# style. If left empty, argument names will be checked with the set naming style. +# argument-rgx = + +# Naming style matching correct attribute names. +# objdictgen: was "snake_case" +attr-naming-style = "any" + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +# attr-rgx = + +# Bad variable names which should always be refused, separated by a comma. +bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"] + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +# bad-names-rgxs = + +# Naming style matching correct class attribute names. +class-attribute-naming-style = "any" + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +# class-attribute-rgx = + +# Naming style matching correct class constant names. +class-const-naming-style = "UPPER_CASE" + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +# class-const-rgx = + +# Naming style matching correct class names. +class-naming-style = "PascalCase" + +# Regular expression matching correct class names. Overrides class-naming-style. +# If left empty, class names will be checked with the set 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. If left empty, constant names will be checked with the set 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. If left empty, function names will be checked with the set naming style. +# function-rgx = + +# Good variable names which should always be accepted, separated by a comma. +good-names = ["i", "j", "k", "ex", "Run", "_"] + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +# good-names-rgxs = + +# Include a hint for the correct naming format with invalid-name. +# include-naming-hint = + +# Naming style matching correct inline iteration names. +inlinevar-naming-style = "any" + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +# inlinevar-rgx = + +# Naming style matching correct method names. +# Objdictgen: was "snake_case" +method-naming-style = "any" + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set 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. If left empty, module names will be checked with the set 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"] + +# Regular expression matching correct type alias names. If left empty, type alias +# names will be checked with the set naming style. +# typealias-rgx = + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +# typevar-rgx = + +# Naming style matching correct variable names. +variable-naming-style = "snake_case" + +# Regular expression matching correct variable names. Overrides variable-naming- +# style. If left empty, variable names will be checked with the set naming style. +# variable-rgx = + +[tool.pylint.classes] +# Warn about protected attribute access inside special methods +# check-protected-access-in-special-methods = + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods = ["__init__", "__new__", "setUp", "asyncSetUp", "__post_init__"] + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make", "os._exit"] + +# 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 = ["mcs"] + +[tool.pylint.design] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +# exclude-too-few-public-methods = + +# List of qualified class names to ignore when counting class parents (see R0901) +# ignored-parents = + +# 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 (see R0916). +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 + +[tool.pylint.exceptions] +# Exceptions that will emit a warning when caught. +overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"] + +[tool.pylint.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. +# objdictgen: was 100 +# FIXME: Set to shorter +max-line-length = 120 + +# Maximum number of lines in a module. +# objdictgen: orig was 1000 +max-module-lines = 1500 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +# single-line-class-stmt = + +# Allow the body of an if to be on the same line as the test if there is no else. +# single-line-if-stmt = + +[tool.pylint.imports] +# List of modules that can be imported at any level, not just the top level one. +# allow-any-import-level = + +# Allow explicit reexports by alias from a package __init__. +# allow-reexport-from-package = + +# Allow wildcard imports from modules that define __all__. +# allow-wildcard-with-all = + +# Deprecated modules which should not be used, separated by a comma. +# deprecated-modules = + +# Output a graph (.gv or any supported image format) of external dependencies to +# the given file (report RP0402 must not be disabled). +# ext-import-graph = + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be disabled). +# import-graph = + +# Output a graph (.gv or any supported image format) of internal dependencies to +# 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"] + +# Couples of modules and preferred modules, separated by a comma. +# preferred-modules = + +[tool.pylint.logging] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `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"] + +[tool.pylint."messages control"] +# Only show warnings with the listed confidence levels. Leave empty to show all. +# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] + +# 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 re-enable 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". +# objdictgen: orig: "raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero", +disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero", +"missing-function-docstring", "duplicate-code" +] + +# 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. +# objdictgen: orig: # enable = +enable = ["no-self-use"] + +[tool.pylint.method_args] +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods = ["requests.api.delete", "requests.api.get", "requests.api.head", "requests.api.options", "requests.api.patch", "requests.api.post", "requests.api.put", "requests.api.request"] + +[tool.pylint.miscellaneous] +# List of note tags to take in consideration, separated by a comma. +notes = ["FIXME", "XXX", "TODO"] + +# Regular expression of note tags to take in consideration. +# notes-rgx = + +[tool.pylint.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", "argparse.parse_error"] + +[tool.pylint.reports] +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each category, +# as well as 'statement' which is the total number of statements analyzed. This +# score is used by the global evaluation report (RP0004). +evaluation = "max(0, 0 if fatal else 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, json2 +# (improved json format), json (old json format) and msvs (visual studio). You +# can also give a reporter class, e.g. mypackage.mymodule.MyReporterClass. +output-format = "colorized" + +# Tells whether to display a full report or only the messages. +# reports = + +# Activate the evaluation score. +score = true + +[tool.pylint.similarities] +# Comments are removed from the similarity computation +ignore-comments = true + +# Docstrings are removed from the similarity computation +ignore-docstrings = true + +# Imports are removed from the similarity computation +ignore-imports = true + +# Signatures are removed from the similarity computation +ignore-signatures = true + +# Minimum lines number of a similarity. +min-similarity-lines = 4 + +[tool.pylint.spelling] +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions = 4 + +# Spelling dictionary name. No available dictionaries : You need to install both +# the python package and the system dependency for enchant to work. +# spelling-dict = + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" + +# List of comma separated words that should not be checked. +# spelling-ignore-words = + +# A path to a file that contains the private dictionary; one word per line. +# spelling-private-dict-file = + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +# spelling-store-unknown-words = + +[tool.pylint.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. +# objdictgen: added +generated-members = "wx.*" + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +# Tells whether to warn about missing members when the owner of the attribute is +# inferred to be None. +ignore-none = true + +# 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 = true + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] + +# 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", "argparse.Namespace"] + +# 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 = true + +# 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 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx = ".*[Mm]ixin" + +# List of decorators that change the signature of a decorated function. +# signature-mutators = + +[tool.pylint.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 = true + +# List of names allowed to shadow builtins +# allowed-redefined-builtins = + +# 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. +ignored-argument-names = "_.*|^ignored_|^unused_" + +# Tells whether we should check for unused import in __init__ files. +# init-import = + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules = ["six.moves", "past.builtins", "future.builtins", "builtins", "io"] + + diff --git a/setup.cfg b/setup.cfg index 0d8270b..2519372 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,66 @@ -[flake8] -max-line-length = 200 -ignore = E128, W503, E303 - -[tool:pytest] -addopts = -l --tb=native --cov=objdictgen -testpaths = - tests -filterwarnings = - ignore::DeprecationWarning +[metadata] +name = objdictgen +version = attr: objdictgen.__version__ +description = CanFestival Object Dictionary tool +long_description = file: README.md +long_description_content_type = text/markdown +author = Svein Seldal +author_email = sveinse@seldal.com +url = https://github.com/Laerdal/python-objdictgen +keywords = build, development, canopen, canfestival, object dictionary, od +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + Topic :: Software Development :: Build Tools + Topic :: Software Development :: Code Generators + Topic :: Software Development :: Embedded Systems + Topic :: Software Development :: Libraries :: Application Frameworks + License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3 :: Only + +[options] +package_dir = + = src +packages = find_namespace: +include_package_data = True +python_requires = >=3.10, <4 +install_requires = + jsonschema + colorama + deepdiff + wxPython + +[options.packages.find] +where = src + +[options.package_data] +objdictgen = py.typed +objdictgen.config = *.prf +objdictgen.img = * +objdictgen.schema = *.json + +[options.extras_require] +dist = + build +dev = + pylint + isort + mypy + types-setuptools + types-colorama + types-wxpython + pytest + coverage + pytest-cov + pytest-mock + +[options.entry_points] +console_scripts = + odg = objdictgen.__main__:main + objdictgen = objdictgen.__main__:main_objdictgen + objdictedit = objdictgen.__main__:main_objdictedit + diff --git a/setup.py b/setup.py deleted file mode 100644 index b1e30c3..0000000 --- a/setup.py +++ /dev/null @@ -1,209 +0,0 @@ -# Always prefer setuptools over distutils -from setuptools import setup, find_packages -import os - -here = os.path.dirname(__file__) - -# Get the long description from the README file -long_description = open(os.path.join(here, 'README.md')).read() - -# Arguments marked as "Required" below must be included for upload to PyPI. -# Fields marked as "Optional" may be commented out. - -setup( - # This is the name of your project. The first time you publish this - # package, this name will be registered for you. It will determine how - # users can install this project, e.g.: - # - # $ pip install sampleproject - # - # And where it will live on PyPI: https://pypi.org/project/sampleproject/ - # - # There are some restrictions on what makes a valid project name - # specification here: - # https://packaging.python.org/specifications/core-metadata/#name - name='objdictgen', # Required - - # Versions should comply with PEP 440: - # https://www.python.org/dev/peps/pep-0440/ - # - # For a discussion on single-sourcing the version across setup.py and the - # project code, see - # https://packaging.python.org/en/latest/single_source_version.html - version='3.4.post1', # Required - - # This is a one-line description or tagline of what your project does. This - # corresponds to the "Summary" metadata field: - # https://packaging.python.org/specifications/core-metadata/#summary - description='CanFestival object dictionary tools', # Optional - - # This is an optional longer description of your project that represents - # the body of text which users will see when they visit PyPI. - # - # Often, this is the same as your README, so you can just read it in from - # that file directly (as we have already done above) - # - # This field corresponds to the "Description" metadata field: - # https://packaging.python.org/specifications/core-metadata/#description-optional - long_description=long_description, # Optional - - # Denotes that our long_description is in Markdown; valid values are - # text/plain, text/x-rst, and text/markdown - # - # Optional if long_description is written in reStructuredText (rst) but - # required for plain-text or Markdown; if unspecified, "applications should - # attempt to render [the long_description] as text/x-rst; charset=UTF-8 and - # fall back to text/plain if it is not valid rst" (see link below) - # - # This field corresponds to the "Description-Content-Type" metadata field: - # https://packaging.python.org/specifications/core-metadata/#description-content-type-optional - long_description_content_type='text/markdown', # Optional (see note above) - - # This should be a valid link to your project's main homepage. - # - # This field corresponds to the "Home-Page" metadata field: - # https://packaging.python.org/specifications/core-metadata/#home-page-optional - url='https://github.com/laerdal-svg/python-objdictgen', # Optional - - # This should be your name or the name of the organization which owns the - # project. - author='Svein Seldal', # Optional - - # This should be a valid email address corresponding to the author listed - # above. - author_email='sveinse@seldal.com', # Optional - - # Classifiers help users find your project by categorizing it. - # - # For a list of valid classifiers, see https://pypi.org/classifiers/ - classifiers=[ # Optional - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 4 - Beta', - - # Indicate who your project is intended for - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Software Development :: Embedded Systems', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - - # Pick your license as you wish - 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', - - # Specify the Python versions you support here. In particular, ensure - # that you indicate you support Python 3. These classifiers are *not* - # checked by 'pip install'. See instead 'python_requires' below. - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - #'Programming Language :: Python :: 3 :: Only', - ], - - # This field adds keywords for your project which will appear on the - # project page. What does your project relate to? - # - # Note that this is a list of additional keywords, separated - # by commas, to be used to assist searching for the distribution in a - # larger catalog. - keywords='build, development, canfestival, canopen, objdictgen', # Optional - - # When your source code is in a subdirectory under the project root, e.g. - # `src/`, it is necessary to specify the `package_dir` argument. - package_dir={'': 'src'}, # Optional - - # You can just specify package directories manually here if your project is - # simple. Or you can use find_packages(). - # - # Alternatively, if you just want to distribute a single Python file, use - # the `py_modules` argument instead as follows, which will expect a file - # called `my_module.py` to exist: - # - # py_modules=["my_module"], - # - packages=find_packages(where='src'), # Required - - # Specify which Python versions you support. In contrast to the - # 'Programming Language' classifiers above, 'pip install' will check this - # and refuse to install the project if the version does not match. See - # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires - python_requires='>=3.6, <4', - - # This field lists other packages that your project depends on to run. - # Any package you put here will be installed by pip when your project is - # installed, so they must be valid existing projects. - # - # For an analysis of "install_requires" vs pip's requirements files see: - # https://packaging.python.org/en/latest/requirements.html - install_requires=[ # Optional - 'jsonschema', - 'colorama', - 'deepdiff', - 'wxPython', - ], - - # List additional groups of dependencies here (e.g. development - # dependencies). Users will be able to install these using the "extras" - # syntax, for example: - # - # $ pip install sampleproject[dev] - # - # Similar to `install_requires` above, these must be valid existing - # projects. - extras_require={ # Optional - 'dist': ['build'], - 'lint': ['pylint', 'flake8', 'mypy'], - 'test': ['pytest', 'coverage', 'pytest-cov', 'pytest-mock'], - }, - - # If there are data files included in your packages that need to be - # installed, specify them here. - package_data={ # Optional - 'objdictgen': ['config/*.prf', 'schema/*.json'], - }, - - # Although 'package_data' is the preferred approach, in some case you may - # need to place data files outside of your packages. See: - # http://docs.python.org/distutils/setupscript.html#installing-additional-files - # - # In this case, 'data_file' will be installed into '/my_data' - #data_files=[('my_data', ['data/data_file'])], # Optional - - # To provide executable scripts, use entry points in preference to the - # "scripts" keyword. Entry points provide cross-platform support and allow - # `pip` to create the appropriate form of executable for the target - # platform. - # - # For example, the following would provide a command called `sample` which - # executes the function `main` from this package when invoked: - entry_points={ # Optional - 'console_scripts': [ - 'objdictgen=objdictgen.__main__:main_objdictgen', - 'objdictedit=objdictgen.__main__:main_objdictedit', - 'odg=objdictgen.__main__:main', - ], - }, - - # List additional URLs that are relevant to your project as a dict. - # - # This field corresponds to the "Project-URL" metadata fields: - # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use - # - # Examples listed include a pattern for specifying where the package tracks - # issues, where the source is hosted, where to say thanks to the package - # maintainers, and where to support the project financially. The key is - # what's used to render the link text on PyPI. - #project_urls={ # Optional - # 'Bug Reports': 'https://github.com/pypa/sampleproject/issues', - # 'Funding': 'https://donate.pypi.org', - # 'Say Thanks!': 'http://saythanks.io/to/example', - # 'Source': 'https://github.com/pypa/sampleproject/', - #}, -) diff --git a/src/objdictgen/__init__.py b/src/objdictgen/__init__.py index 7714d1a..5769ee8 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -18,33 +18,42 @@ # USA import os +from pathlib import Path -from objdictgen.maps import OD -from objdictgen.node import Find, ImportProfile, Node +from objdictgen.node import Node from objdictgen.nodemanager import NodeManager +__version__ = "3.4" + # Shortcuts LoadFile = Node.LoadFile LoadJson = Node.LoadJson ODG_PROGRAM = "odg" -ODG_VERSION = "3.4" -SCRIPT_DIRECTORY = os.path.split(__file__)[0] +SCRIPT_DIRECTORY = Path(__file__).parent + +PROFILE_DIRECTORIES: list[Path] = [ + SCRIPT_DIRECTORY / 'config' +] + +# Append the ODG_PROFILE_PATH to the PROFILE_DIRECTORIES +odgdir = os.environ.get('ODG_PROFILE_PATH', '') +for d in odgdir.split(";" if os.name == "nt" else ":;"): + if d: + PROFILE_DIRECTORIES.append(Path(d)) -PROFILE_DIRECTORIES = [os.path.join(SCRIPT_DIRECTORY, 'config')] -odgdir = os.environ.get('ODG_PROFILE_DIR') -if odgdir: - PROFILE_DIRECTORIES.append(odgdir) +# Make list of all discoverable profiles +PROFILES: list[Path] = [] +for p in PROFILE_DIRECTORIES: + if p.is_dir(): + PROFILES.extend(p.glob('*.prf')) -JSON_SCHEMA = os.path.join(SCRIPT_DIRECTORY, 'schema', 'od.schema.json') +JSON_SCHEMA = SCRIPT_DIRECTORY / 'schema' / 'od.schema.json' __all__ = [ - "Node", - "ImportProfile", - "Find", "LoadFile", "LoadJson", + "Node", "NodeManager", - "OD", ] diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index db73f6b..63a2735 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -1,4 +1,4 @@ -"""Main entry point for objdictgen""" +"""Main entry point for objdictgen / odg.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # @@ -24,11 +24,18 @@ import sys from dataclasses import dataclass, field from pprint import pformat +from typing import TYPE_CHECKING, Callable, Sequence, TypeVar from colorama import Fore, Style, init import objdictgen from objdictgen import jsonod +from objdictgen.typing import TDiffEntries, TDiffNodes, TPath + +T = TypeVar('T') + +if TYPE_CHECKING: + from objdictgen.node import Node # For colored output init() @@ -41,20 +48,21 @@ @dataclass class DebugOpts: - ''' Options for main to control the debug_wrapper ''' + """ Options for main to control the debug_wrapper """ show_debug: bool = field(default=False) - def set_debug(self, dbg): + def set_debug(self, dbg: bool) -> None: + """Set debug level""" self.show_debug = dbg log.setLevel(logging.DEBUG) -def debug_wrapper(): - ''' Wrapper to catch all exceptions and supress the output unless debug +def debug_wrapper() -> Callable[[Callable[..., T]], Callable[..., T]]: + """ Wrapper to catch all exceptions and supress the output unless debug is set - ''' - def decorator(fn): + """ + def decorator(fn: Callable[..., T]) -> Callable[..., T]: @functools.wraps(fn) def inner(*args, **kw): opts = DebugOpts() @@ -63,14 +71,14 @@ def inner(*args, **kw): except Exception as exc: # pylint: disable=broad-except if opts.show_debug: raise - print("{}: {}: {}".format(objdictgen.ODG_PROGRAM, exc.__class__.__name__, exc)) + print(f"{objdictgen.ODG_PROGRAM}: {exc.__class__.__name__}: {exc}") sys.exit(1) return inner return decorator -def open_od(fname, validate=True, fix=False): - ''' Open and validate the OD file''' +def open_od(fname: TPath|str, validate=True, fix=False) -> "Node": + """ Open and validate the OD file""" try: od = objdictgen.LoadFile(fname) @@ -80,44 +88,45 @@ def open_od(fname, validate=True, fix=False): return od except Exception as exc: - jsonod.exc_amend(exc, "{}: ".format(fname)) + jsonod.exc_amend(exc, f"{fname}: ") raise -def print_diffs(diffs, show=False): +def print_diffs(diffs: TDiffNodes, show=False): + """ Print the differences between two object dictionaries""" - def _pprint(text): + def _pprint(text: str): for line in pformat(text).splitlines(): print(" ", line) - def _printlines(entries): + def _printlines(entries: TDiffEntries): for chtype, change, path in entries: if 'removed' in chtype: - print("<<< {} only in LEFT".format(path)) + print(f"<<< {path} only in LEFT") if show: _pprint(change.t1) elif 'added' in chtype: - print(" >>> {} only in RIGHT".format(path)) + print(f" >>> {path} only in RIGHT") if show: _pprint(change.t2) elif 'changed' in chtype: - print("<< - >> {} value changed from '{}' to '{}'".format(path, change.t1, change.t2)) + print(f"<< - >> {path} value changed from '{change.t1}' to '{change.t2}'") else: - print("{}{} {} {}{}".format(Fore.RED, chtype, path, change, Style.RESET_ALL)) + print(f"{Fore.RED}{chtype} {path} {change}{Style.RESET_ALL}") rest = diffs.pop('', None) if rest: - print("{}Changes:{}".format(Fore.GREEN, Style.RESET_ALL)) + print(f"{Fore.GREEN}Changes:{Style.RESET_ALL}") _printlines(rest) for index in sorted(diffs): - print("{0}Index 0x{1:04x} ({1}){2}".format(Fore.GREEN, index, Style.RESET_ALL)) + print(f"{Fore.GREEN}Index 0x{index:04x} ({index}){Style.RESET_ALL}") _printlines(diffs[index]) @debug_wrapper() -def main(debugopts, args=None): - ''' Main command dispatcher ''' +def main(debugopts: DebugOpts, args: Sequence[str]|None = None): + """ Main command dispatcher """ parser = argparse.ArgumentParser( prog=objdictgen.ODG_PROGRAM, @@ -130,87 +139,94 @@ def main(debugopts, args=None): # FIXME: New options: new file, add parameter, delete parameter, copy parameter - subparser = parser.add_subparsers(title="command", dest="command", metavar="command", help=''' + subparser = parser.add_subparsers(title="command", dest="command", metavar="command", help=""" Commands - ''', required=True) + """, required=True) # -- COMMON -- opt_debug = dict(action='store_true', help="Debug: enable tracebacks on errors") opt_od = dict(metavar='od', default=None, help="Object dictionary") - parser.add_argument('--version', action='version', version='%(prog)s ' + objdictgen.ODG_VERSION) - parser.add_argument('-D', '--debug', **opt_debug) + parser.add_argument('--version', action='version', version='%(prog)s ' + objdictgen.__version__) + parser.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- HELP -- - subp = subparser.add_parser('help', help=''' + subp = subparser.add_parser('help', help=""" Show help of all commands - ''') - subp.add_argument('-D', '--debug', **opt_debug) + """) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- CONVERT -- - subp = subparser.add_parser('convert', help=''' + subp = subparser.add_parser('convert', help=""" Generate - ''', aliases=['gen', 'conv']) - subp.add_argument('od', **opt_od) + """, aliases=['gen', 'conv']) + subp.add_argument('od', **opt_od) # type: ignore[arg-type] subp.add_argument('out', default=None, help="Output file") - subp.add_argument('-i', '--index', action="append", help="OD Index to include. Filter out the rest.") + subp.add_argument('-i', '--index', action="append", + help="OD Index to include. Filter out the rest.") subp.add_argument('-x', '--exclude', action="append", help="OD Index to exclude.") subp.add_argument('-f', '--fix', action="store_true", - help="Fix any inconsistency errors in OD before generate output") - subp.add_argument('-t', '--type', choices=['od', 'eds', 'json', 'c'], help="Select output file type") + help="Fix any inconsistency errors in OD before generate output") + subp.add_argument('-t', '--type', choices=['od', 'eds', 'json', 'c'], + help="Select output file type") subp.add_argument('--drop-unused', action="store_true", help="Remove unused parameters") - subp.add_argument('--internal', action="store_true", help="Store in internal format (json only)") - subp.add_argument('--nosort', action="store_true", help="Don't order of parameters in output OD") - subp.add_argument('--novalidate', action="store_true", help="Don't validate files before conversion") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('--internal', action="store_true", + help="Store in internal format (json only)") + subp.add_argument('--nosort', action="store_true", + help="Don't order of parameters in output OD") + subp.add_argument('--novalidate', action="store_true", + help="Don't validate files before conversion") + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- DIFF -- - subp = subparser.add_parser('diff', help=''' + subp = subparser.add_parser('diff', help=""" Compare OD files - ''', aliases=['compare']) - subp.add_argument('od1', **opt_od) - subp.add_argument('od2', **opt_od) + """, aliases=['compare']) + subp.add_argument('od1', **opt_od) # type: ignore[arg-type] + subp.add_argument('od2', **opt_od) # type: ignore[arg-type] subp.add_argument('--internal', action="store_true", help="Diff internal object") - subp.add_argument('--novalidate', action="store_true", help="Don't validate input files before diff") + subp.add_argument('--novalidate', action="store_true", + help="Don't validate input files before diff") subp.add_argument('--show', action="store_true", help="Show difference data") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- EDIT -- - subp = subparser.add_parser('edit', help=''' + subp = subparser.add_parser('edit', help=""" Edit OD (UI) - ''') + """) subp.add_argument('od', nargs="*", help="Object dictionary") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- LIST -- - subp = subparser.add_parser('list', help=''' + subp = subparser.add_parser('list', help=""" List - ''') + """) subp.add_argument('od', nargs="+", help="Object dictionary") subp.add_argument('-i', '--index', action="append", help="Specify parameter index to show") - subp.add_argument('--all', action="store_true", help="Show all subindexes, including subindex 0") + subp.add_argument('--all', action="store_true", + help="Show all subindexes, including subindex 0") subp.add_argument('--asis', action="store_true", help="Do not sort output") subp.add_argument('--compact', action="store_true", help="Compact listing") subp.add_argument('--header', action="store_true", help="List header only") subp.add_argument('--raw', action="store_true", help="Show raw parameter values") subp.add_argument('--short', action="store_true", help="Do not list sub-index") subp.add_argument('--unused', action="store_true", help="Include unused profile parameters") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- NETWORK -- - subp = subparser.add_parser('network', help=''' + subp = subparser.add_parser('network', help=""" Edit network (UI) - ''') + """) subp.add_argument('dir', nargs="?", help="Project directory") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- NODELIST -- - subp = subparser.add_parser('nodelist', help=''' + subp = subparser.add_parser('nodelist', help=""" List project nodes - ''') + """) subp.add_argument('dir', nargs="?", help="Project directory") - subp.add_argument('-D', '--debug', **opt_debug) + subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- COMMON -- @@ -228,11 +244,14 @@ def main(debugopts, args=None): parser.print_help() print() - for subparsers_action in (a for a in parser._actions - if isinstance(a, argparse._SubParsersAction)): + for subparsers_action in ( + a for a in parser._actions # pylint: disable=protected-access + if isinstance(a, argparse._SubParsersAction) # pylint: disable=protected-access + ): for choice, subparser in subparsers_action.choices.items(): - print("command '{}'".format(choice)) - for info in subparser.format_help().split('\n'): + print(f"command '{choice}'") + # FIXME: Not sure why mypy doesn't know about format_help + for info in subparser.format_help().split('\n'): # type: ignore[attr-defined] print(" " + info) @@ -241,11 +260,11 @@ def main(debugopts, args=None): od = open_od(opts.od, fix=opts.fix) - to_remove = set() + to_remove: set[int] = set() # Drop excluded parameters if opts.exclude: - to_remove |= set(jsonod.str_to_number(i) for i in opts.exclude) + to_remove |= set(jsonod.str_to_int(i) for i in opts.exclude) # Drop unused parameters if opts.drop_unused: @@ -253,8 +272,8 @@ def main(debugopts, args=None): # Drop all other indexes than specified if opts.index: - index = [jsonod.str_to_number(i) for i in opts.index] - to_remove |= (set(od.GetAllParameters()) - set(index)) + index = [jsonod.str_to_int(i) for i in opts.index] + to_remove |= (set(od.GetAllIndices()) - set(index)) # Have any parameters to delete? if to_remove: @@ -263,8 +282,7 @@ def main(debugopts, args=None): od.GetPrintLine(k, unused=True) for k in sorted(to_remove) ] - for index in to_remove: - od.RemoveIndex(index) + od.RemoveIndex(to_remove) for line, fmt in info: print(line.format(**fmt)) @@ -281,26 +299,28 @@ def main(debugopts, args=None): od2 = open_od(opts.od2, validate=not opts.novalidate) diffs = jsonod.diff_nodes( - od1, od2, as_dict=not opts.internal, + od1, od2, asdict=not opts.internal, validate=not opts.novalidate, ) if diffs: errcode = 1 - print("{}: '{}' and '{}' differ".format(objdictgen.ODG_PROGRAM, opts.od1, opts.od2)) + print(f"{objdictgen.ODG_PROGRAM}: '{opts.od1}' and '{opts.od2}' differ") else: errcode = 0 - print("{}: '{}' and '{}' are equal".format(objdictgen.ODG_PROGRAM, opts.od1, opts.od2)) + print(f"{objdictgen.ODG_PROGRAM}: '{opts.od1}' and '{opts.od2}' are equal") print_diffs(diffs, show=opts.show) - parser.exit(errcode) + if errcode: + parser.exit(errcode) # -- EDIT command -- elif opts.command == "edit": # Import here to prevent including optional UI components for cmd-line use - from .ui.objdictedit import uimain # pylint: disable=import-outside-toplevel + from .ui.objdictedit import \ + uimain # pylint: disable=import-outside-toplevel uimain(opts.od) @@ -317,13 +337,13 @@ def main(debugopts, args=None): od = open_od(name) # Get the indexes to print and determine the order - keys = od.GetAllParameters(sort=not opts.asis) + keys = od.GetAllIndices(sort=not opts.asis) if opts.index: - index = tuple(jsonod.str_to_number(i) for i in opts.index) - keys = tuple(k for k in keys if k in index) - missing = ", ".join((str(k) for k in index if k not in keys)) + indexp = [jsonod.str_to_int(i) for i in opts.index] + keysp = [k for k in keys if k in indexp] + missing = ", ".join((str(k) for k in indexp if k not in keysp)) if missing: - parser.error("Unknown index {}".format(missing)) + parser.error(f"Unknown index {missing}") profiles = [] if od.DS302: @@ -340,19 +360,20 @@ def main(debugopts, args=None): if pname and pname != 'None': loaded, equal = jsonod.compare_profile(pname, od.Profile, od.SpecificMenu) if equal: - extra = "%s (equal)" % pname + extra = f"{pname} (equal)" elif loaded: - extra = "%s (not equal)" % pname + extra = f"{pname} (not equal)" else: - extra = "%s (not loaded)" % pname + extra = f"{pname} (not loaded)" profiles.append(extra) if not opts.compact: - print("File: %s" % (name)) - print("Name: %s [%s] %s" % (od.Name, od.Type.upper(), od.Description)) - print("Profiles: %s" % (", ".join(profiles) or None)) + print(f"File: {name}") + print(f"Name: {od.Name} [{od.Type.upper()}] {od.Description}") + tp = ", ".join(profiles) or None + print(f"Profiles: {tp}") if od.ID: - print("ID: %s" % (od.ID)) + print(f"ID: {od.ID}") print("") if opts.header: @@ -370,7 +391,8 @@ def main(debugopts, args=None): elif opts.command == "network": # Import here to prevent including optional UI components for cmd-line use - from .ui.networkedit import uimain # pylint: disable=import-outside-toplevel + from .ui.networkedit import \ + uimain # pylint: disable=import-outside-toplevel uimain(opts.dir) @@ -378,12 +400,13 @@ def main(debugopts, args=None): elif opts.command == "nodelist": # Import here to prevent including optional UI components for cmd-line use - from .nodelist import main as _main # pylint: disable=import-outside-toplevel + from .nodelist import \ + main as _main # pylint: disable=import-outside-toplevel _main(opts.dir) else: - parser.error("Programming error: Uknown option '%s'" % (opts.command)) + parser.error(f"Programming error: Uknown option '{opts.command}'") def main_objdictgen(): @@ -391,7 +414,7 @@ def main_objdictgen(): def usage(): print("\nUsage of objdictgen :") - print("\n %s XMLFilePath CFilePath\n" % sys.argv[0]) + print(f"\n {sys.argv[0]} XMLFilePath CFilePath\n") try: opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) @@ -426,11 +449,12 @@ def main_objdictedit(): """ Legacy objdictedit command """ # Import here to prevent including optional UI components for cmd-line use - from .ui.objdictedit import main as _main # pylint: disable=import-outside-toplevel + from .ui.objdictedit import \ + main as _main # pylint: disable=import-outside-toplevel _main() # To support -m objdictgen if __name__ == '__main__': # pylint: disable=no-value-for-parameter - main() # type: ignore + main() diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 84b056d..b355602 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -18,12 +18,23 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -import os +import logging import re +from pathlib import Path from time import localtime, strftime +from typing import TYPE_CHECKING, Any, Callable -import objdictgen +# Accessed by node.py, so we need to import module to avoid circular references +from objdictgen import maps +from objdictgen import node as nodelib from objdictgen.maps import OD +from objdictgen.typing import TEntry, TPath + +if TYPE_CHECKING: + from objdictgen.node import Node + from objdictgen.nodelist import NodeList + +log = logging.getLogger('objdictgen') # Regular expression for finding index section names RE_INDEX = re.compile(r'([0-9A-F]{1,4}$)') @@ -43,15 +54,17 @@ BOOL_TRANSLATE = {True: "1", False: "0"} # Dictionary for quickly translate eds access value into canfestival access value -ACCESS_TRANSLATE = {"RO": "ro", "WO": "wo", "RW": "rw", "RWR": "rw", "RWW": "rw", "CONST": "ro"} +ACCESS_TRANSLATE = { + "RO": "ro", "WO": "wo", "RW": "rw", "RWR": "rw", "RWW": "rw", "CONST": "ro" +} # Function for verifying data values -is_integer = lambda x: isinstance(x, int) # noqa: E731 -is_string = lambda x: isinstance(x, str) # noqa: E731 -is_boolean = lambda x: x in (0, 1) # noqa: E731 +is_integer = lambda x: isinstance(x, int) # pylint: disable=unnecessary-lambda-assignment +is_string = lambda x: isinstance(x, str) # pylint: disable=unnecessary-lambda-assignment +is_boolean = lambda x: x in (0, 1) # pylint: disable=unnecessary-lambda-assignment # Define checking of value for each attribute -ENTRY_ATTRIBUTES = { +ENTRY_ATTRIBUTES: dict[str, Callable[[Any], bool]] = { "SUBNUMBER": is_integer, "PARAMETERNAME": is_string, "OBJECTTYPE": lambda x: x in (2, 7, 8, 9), @@ -68,14 +81,14 @@ } # Define entry parameters by entry ObjectType number -ENTRY_TYPES = { +ENTRY_TYPES: dict[int, TEntry] = { 2: {"name": " DOMAIN", "require": ["PARAMETERNAME", "OBJECTTYPE"], "optional": ["DATATYPE", "ACCESSTYPE", "DEFAULTVALUE", "OBJFLAGS"]}, 7: {"name": " VAR", "require": ["PARAMETERNAME", "DATATYPE", "ACCESSTYPE"], "optional": ["OBJECTTYPE", "DEFAULTVALUE", "PDOMAPPING", "LOWLIMIT", - "HIGHLIMIT", "OBJFLAGS", "PARAMETERVALUE"]}, + "HIGHLIMIT", "OBJFLAGS", "PARAMETERVALUE"]}, 8: {"name": "n ARRAY", "require": ["PARAMETERNAME", "OBJECTTYPE", "SUBNUMBER"], "optional": ["OBJFLAGS"]}, @@ -85,9 +98,9 @@ } -# Function that search into Node Mappings the informations about an index or a subindex -# and return the default value -def GetDefaultValue(node, index, subindex=None): +def get_default_value(node: "Node", index: int, subindex: int = -1): + """Function that search into Node Mappings the informations about an index + or a subindex and return the default value.""" infos = node.GetEntryInfos(index) if infos["struct"] & OD.MultipleSubindexes: # First case entry is a array @@ -123,27 +136,29 @@ def GetDefaultValue(node, index, subindex=None): "STANDARDDATATYPES", "SUPPORTEDMODULES"] -# Function that extract sections from a file and returns a dictionary of the informations -def ExtractSections(file): +def extract_sections(data: str) -> list[tuple[str, list[str]]]: + """Extract sections from a file and returns a dictionary of the informations""" return [ - (blocktuple[0], # EntryName : Assignements dict - blocktuple[-1].splitlines()) # all the lines + ( + blocktuple[0], # EntryName : Assignements dict + blocktuple[-1].splitlines(), # all the lines + ) for blocktuple in [ # Split the eds files into block.split("]", 1) # (EntryName,Assignements) tuple for block in # for each blocks staring with '[' - ("\n" + file).split("\n[")] + ("\n" + data).split("\n[")] if blocktuple[0].isalnum()] # if EntryName exists -# Function that parse an CPJ file and returns a dictionary of the informations -def ParseCPJFile(filepath): +def parse_cpj_file(filepath: TPath): + """Parse a CPJ file and return a list of dictionaries of the informations""" networks = [] # Read file text - with open(filepath, "r") as f: - cpj_file = f.read() + with open(filepath, "r", encoding="utf-8") as f: + cpj_data = f.read() - sections = ExtractSections(cpj_file) + sections = extract_sections(cpj_data) # Parse assignments for each section for section_name, assignments in sections: @@ -151,7 +166,7 @@ def ParseCPJFile(filepath): if section_name.upper() in "TOPOLOGY": # Reset values for topology - topology = {"Name": "", "Nodes": {}} + topology: dict[str, Any] = {"Name": "", "Nodes": {}} for assignment in assignments: # Escape any comment @@ -167,13 +182,17 @@ def ParseCPJFile(filepath): if keyname.isalnum(): # value can be preceded and followed by whitespaces, so we escape them value = value.strip() + computed_value: int|str|bool # First case, value starts with "0x" or "-0x", then it's an hexadecimal value if value.startswith("0x") or value.startswith("-0x"): try: computed_value = int(value, 16) except ValueError: - raise ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None + raise ValueError( + f"'{value}' is not a valid value for attribute '{keyname}' " + f"of section '[{section_name}]'" + ) from None elif value.isdigit() or value.startswith("-") and value[1:].isdigit(): # Second case, value is a number and starts with "0" or "-0", then it's an octal value if value.startswith("0") or value.startswith("-0"): @@ -191,78 +210,102 @@ def ParseCPJFile(filepath): nodedcfname_result = RE_NODEDCFNAME.match(keyname.upper()) if keyname.upper() == "NETNAME": - if not is_string(computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) + if not isinstance(computed_value, str): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) topology["Name"] = computed_value + elif keyname.upper() == "NODES": - if not is_integer(computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) + if not isinstance(computed_value, int): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) topology["Number"] = computed_value + elif keyname.upper() == "EDSBASENAME": - if not is_string(computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) + if not isinstance(computed_value, str): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) topology["Path"] = computed_value + elif nodepresent_result: - if not is_boolean(computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) - nodeid = int(nodepresent_result.groups()[0]) + if not isinstance(computed_value, bool): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + "of section '[{section_name}]'" + ) + nodeid = int(nodepresent_result[1]) if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} topology["Nodes"][nodeid]["Present"] = computed_value + elif nodename_result: - if not is_string(value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) - nodeid = int(nodename_result.groups()[0]) + if not isinstance(computed_value, str): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) + nodeid = int(nodename_result[1]) if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} topology["Nodes"][nodeid]["Name"] = computed_value + elif nodedcfname_result: - if not is_string(computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) - nodeid = int(nodedcfname_result.groups()[0]) + if not isinstance(computed_value, str): + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) + nodeid = int(nodedcfname_result[1]) if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} topology["Nodes"][nodeid]["DCFName"] = computed_value + else: - raise ValueError("Keyname '%s' not recognised for section '[%s]'" % (keyname, section_name)) + raise ValueError(f"Keyname '{keyname}' not recognised for section '[{section_name}]'") # All lines that are not empty and are neither a comment neither not a valid assignment elif assignment.strip(): - raise ValueError("'%s' is not a valid CPJ line" % assignment.strip()) + raise ValueError(f"'{assignment.strip()}' is not a valid CPJ line") if "Number" not in topology: - raise ValueError("'Nodes' keyname in '[%s]' section is missing" % section_name) + raise ValueError(f"'Nodes' keyname in '[{section_name}]' section is missing") if topology["Number"] != len(topology["Nodes"]): raise ValueError("'Nodes' value not corresponding to number of nodes defined") for nodeid, node in topology["Nodes"].items(): if "Present" not in node: - raise ValueError("'Node%dPresent' keyname in '[%s]' section is missing" % (nodeid, section_name)) + raise ValueError(f"'Node{nodeid}Present' keyname in '[{section_name}]' section is missing") networks.append(topology) # In other case, there is a syntax problem into CPJ file else: - raise ValueError("Section '[%s]' is unrecognized" % section_name) + raise ValueError(f"Section '[{section_name}]' is unrecognized") return networks -# Function that parse an EDS file and returns a dictionary of the informations -def ParseEDSFile(filepath): - eds_dict = {} +def parse_eds_file(filepath: TPath) -> dict[str|int, Any]: + """Parse an EDS file and returns a dictionary of the informations""" + eds_dict: dict[str|int, Any] = {} # Read file text - with open(filepath, 'r') as f: + with open(filepath, 'r', encoding="utf-8") as f: eds_file = f.read() - sections = ExtractSections(eds_file) + sections = extract_sections(eds_file) # Parse assignments for each section for section_name, assignments in sections: # Reset values of entry - values = {} + values: dict[str, Any] = {} # Search if the section name match an index or subindex expression index_result = RE_INDEX.match(section_name.upper()) @@ -278,11 +321,11 @@ def ParseEDSFile(filepath): if section_name.upper() not in eds_dict: eds_dict[section_name.upper()] = values else: - raise ValueError("'[%s]' section is defined two times" % section_name) + raise ValueError(f"'[{section_name}]' section is defined two times") # Second case, section name is an index name elif index_result: # Extract index number - index = int(index_result.groups()[0], 16) + index = int(index_result[1], 16) # If index hasn't been referenced before, we add an entry into the dictionary if index not in eds_dict: eds_dict[index] = values @@ -291,7 +334,7 @@ def ParseEDSFile(filepath): values["subindexes"] = eds_dict[index]["subindexes"] eds_dict[index] = values else: - raise ValueError("'[%s]' section is defined two times" % section_name) + raise ValueError(f"'[{section_name}]' section is defined two times") is_entry = True # Third case, section name is a subindex name elif subindex_result: @@ -304,14 +347,14 @@ def ParseEDSFile(filepath): if subindex not in eds_dict[index]["subindexes"]: eds_dict[index]["subindexes"][subindex] = values else: - raise ValueError("'[%s]' section is defined two times" % section_name) + raise ValueError(f"'[{section_name}]' section is defined two times") is_entry = True # Third case, section name is a subindex name elif index_objectlinks_result: pass # In any other case, there is a syntax problem into EDS file else: - raise ValueError("Section '[%s]' is unrecognized" % section_name) + raise ValueError(f"Section '[{section_name}]' is unrecognized") for assignment in assignments: # Escape any comment @@ -328,18 +371,25 @@ def ParseEDSFile(filepath): # value can be preceded and followed by whitespaces, so we escape them value = value.strip() # First case, value starts with "$NODEID", then it's a formula + computed_value: int|str if value.upper().startswith("$NODEID"): try: _ = int(value.upper().replace("$NODEID+", ""), 16) - computed_value = '"%s"' % value + computed_value = f'"{value}"' except ValueError: - raise ValueError("'%s' is not a valid formula for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None + raise ValueError( + f"'{value}' is not a valid formula for attribute '{keyname}' " + f"of section '[{section_name}]'" + ) from None # Second case, value starts with "0x", then it's an hexadecimal value elif value.startswith("0x") or value.startswith("-0x"): try: computed_value = int(value, 16) except ValueError: - raise ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None + raise ValueError( + f"'{value}' is not a valid value for attribute '{keyname}' " + f"of section '[{section_name}]'" + ) from None elif value.isdigit() or value.startswith("-") and value[1:].isdigit(): # Third case, value is a number and starts with "0", then it's an octal value if value.startswith("0") or value.startswith("-0"): @@ -358,16 +408,20 @@ def ParseEDSFile(filepath): if is_entry: # Verify that keyname is a possible attribute if keyname.upper() not in ENTRY_ATTRIBUTES: - raise ValueError("Keyname '%s' not recognised for section '[%s]'" % (keyname, section_name)) + raise ValueError( + f"Keyname '{keyname}' not recognised for section '[{section_name}]'" + ) # Verify that value is valid if not ENTRY_ATTRIBUTES[keyname.upper()](computed_value): - raise ValueError("Invalid value '%s' for keyname '%s' of section '[%s]'" % (value, keyname, section_name)) + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'" + ) values[keyname.upper()] = computed_value else: values[keyname.upper()] = computed_value # All lines that are not empty and are neither a comment neither not a valid assignment elif assignment.strip(): - raise ValueError("'%s' is not a valid EDS line" % assignment.strip()) + raise ValueError(f"'{assignment.strip()}' is not a valid EDS line") # If entry is an index or a subindex if is_entry: @@ -377,33 +431,44 @@ def ParseEDSFile(filepath): keys = set(values) keys.discard("subindexes") # Extract possible parameters and parameters required - possible = set(ENTRY_TYPES[values["OBJECTTYPE"]]["require"] - + ENTRY_TYPES[values["OBJECTTYPE"]]["optional"]) + possible = set( + ENTRY_TYPES[values["OBJECTTYPE"]]["require"] + + ENTRY_TYPES[values["OBJECTTYPE"]]["optional"] + ) required = set(ENTRY_TYPES[values["OBJECTTYPE"]]["require"]) # Verify that parameters defined contains all the parameters required if not keys.issuperset(required): missing = required.difference(keys) if len(missing) > 1: - attributes = "Attributes %s are" % ", ".join(["'%s'" % attribute for attribute in missing]) + tp = ", ".join([f"'{attribute}'" for attribute in missing]) + attributes = f"Attributes {tp} are" else: - attributes = "Attribute '%s' is" % missing.pop() - raise ValueError("Error on section '[%s]': '%s' required for a '%s' entry" % (section_name, attributes, ENTRY_TYPES[values["OBJECTTYPE"]]["name"])) + attributes = f"Attribute '{missing.pop()}' is" + raise ValueError( + f"Error on section '[{section_name}]': '{attributes}' required " + f"for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry" + ) # Verify that parameters defined are all in the possible parameters if not keys.issubset(possible): unsupported = keys.difference(possible) if len(unsupported) > 1: - attributes = "Attributes %s are" % ", ".join(["'%s'" % attribute for attribute in unsupported]) + tp = ", ".join([f"'{attribute}'" for attribute in unsupported]) + attributes = f"Attributes {tp} are" else: - attributes = "Attribute '%s' is" % unsupported.pop() - raise ValueError("Error on section '[%s]': '%s' unsupported for a '%s' entry" % (section_name, attributes, ENTRY_TYPES[values["OBJECTTYPE"]]["name"])) + attributes = f"Attribute '{unsupported.pop()}' is" + raise ValueError( + f"Error on section '[{section_name}]': '{attributes}' unsupported " + f"for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry" + ) - VerifyValue(values, section_name, "ParameterValue") - VerifyValue(values, section_name, "DefaultValue") + verify_value(values, section_name, "ParameterValue") + verify_value(values, section_name, "DefaultValue") return eds_dict -def VerifyValue(values, section_name, param): +def verify_value(values: dict[str, Any], section_name: str, param: str): + """Verify that a value is compatible with the DataType of the entry""" uparam = param.upper() if uparam in values: try: @@ -414,13 +479,16 @@ def VerifyValue(values, section_name, param): elif values["DATATYPE"] == 0x01: values[uparam] = {0: False, 1: True}[values[uparam]] elif not isinstance(values[uparam], int) and "$NODEID" not in values[uparam].upper(): - raise ValueError() # FIXME: Should this get something more specific? + raise ValueError() except ValueError: - raise ValueError("Error on section '[%s]': '%s' incompatible with DataType" % (section_name, param)) from None + raise ValueError(f"Error on section '[{section_name}]': '{param}' incompatible with DataType") from None -# Function that generate the EDS file content for the current node in the manager -def GenerateFileContent(node, filepath): +def generate_eds_content(node: "Node", filepath: TPath): + """Generate the EDS file content for the current node in the manager.""" + + filepath = Path(filepath) + # Dictionary of each index contents indexcontents = {} @@ -433,43 +501,48 @@ def GenerateFileContent(node, filepath): description = node.Description or "" # Retreiving lists of indexes defined - entries = node.GetIndexes() + entries = list(node) # FIXME: Too many camelCase vars in here # pylint: disable=invalid-name + try: + value = node.GetEntry(0x1018) + except ValueError: + raise ValueError("Missing required Identity (0x1018) object") from None + # Generate FileInfo section fileContent = "[FileInfo]\n" - fileContent += "FileName=%s\n" % os.path.split(filepath)[-1] + fileContent += f"FileName={filepath.name}\n" fileContent += "FileVersion=1\n" fileContent += "FileRevision=1\n" fileContent += "EDSVersion=4.0\n" - fileContent += "Description=%s\n" % description - fileContent += "CreationTime=%s" % strftime("%I:%M", current_time) + fileContent += f"Description={description}\n" + fileContent += f"CreationTime={strftime('%I:%M', current_time)}" # %p option of strftime seems not working, then generate AM/PM by hands if strftime("%I", current_time) == strftime("%H", current_time): fileContent += "AM\n" else: fileContent += "PM\n" - fileContent += "CreationDate=%s\n" % strftime("%m-%d-%Y", current_time) + fileContent += f"CreationDate={strftime('%m-%d-%Y', current_time)}\n" fileContent += "CreatedBy=CANFestival\n" - fileContent += "ModificationTime=%s" % strftime("%I:%M", current_time) + fileContent += f"ModificationTime={strftime('%I:%M', current_time)}" # %p option of strftime seems not working, then generate AM/PM by hands if strftime("%I", current_time) == strftime("%H", current_time): fileContent += "AM\n" else: fileContent += "PM\n" - fileContent += "ModificationDate=%s\n" % strftime("%m-%d-%Y", current_time) + fileContent += f"ModificationDate={strftime('%m-%d-%Y', current_time)}\n" fileContent += "ModifiedBy=CANFestival\n" # Generate DeviceInfo section fileContent += "\n[DeviceInfo]\n" fileContent += "VendorName=CANFestival\n" # Use information typed by user in Identity entry - fileContent += "VendorNumber=0x%8.8X\n" % node.GetEntry(0x1018, 1) - fileContent += "ProductName=%s\n" % nodename - fileContent += "ProductNumber=0x%8.8X\n" % node.GetEntry(0x1018, 2) - fileContent += "RevisionNumber=0x%8.8X\n" % node.GetEntry(0x1018, 3) + fileContent += f"VendorNumber=0x{node.GetEntry(0x1018, 1):08X}\n" + fileContent += f"ProductName={nodename}\n" + fileContent += f"ProductNumber=0x{node.GetEntry(0x1018, 2):08X}\n" + fileContent += f"RevisionNumber=0x{node.GetEntry(0x1018, 3):08X}\n" # CANFestival support all baudrates as soon as driver choosen support them fileContent += "BaudRate_10=1\n" fileContent += "BaudRate_20=1\n" @@ -480,16 +553,16 @@ def GenerateFileContent(node, filepath): fileContent += "BaudRate_800=1\n" fileContent += "BaudRate_1000=1\n" # Select BootUp type from the informations given by user - fileContent += "SimpleBootUpMaster=%s\n" % BOOL_TRANSLATE[nodetype == "master"] - fileContent += "SimpleBootUpSlave=%s\n" % BOOL_TRANSLATE[nodetype == "slave"] + fileContent += f"SimpleBootUpMaster={BOOL_TRANSLATE[nodetype == 'master']}\n" + fileContent += f"SimpleBootUpSlave={BOOL_TRANSLATE[nodetype == 'slave']}\n" # CANFestival characteristics fileContent += "Granularity=8\n" fileContent += "DynamicChannelsSupported=0\n" fileContent += "CompactPDO=0\n" fileContent += "GroupMessaging=0\n" # Calculate receive and tranmit PDO numbers with the entry available - fileContent += "NrOfRXPDO=%d\n" % len([idx for idx in entries if 0x1400 <= idx <= 0x15FF]) - fileContent += "NrOfTXPDO=%d\n" % len([idx for idx in entries if 0x1800 <= idx <= 0x19FF]) + fileContent += f"NrOfRXPDO={len([idx for idx in entries if 0x1400 <= idx <= 0x15FF])}\n" + fileContent += f"NrOfTXPDO={len([idx for idx in entries if 0x1800 <= idx <= 0x19FF])}\n" # LSS not supported as soon as DS-302 was not fully implemented fileContent += "LSS_Supported=0\n" @@ -508,9 +581,9 @@ def GenerateFileContent(node, filepath): fileContent += "Lines=0\n" # List of entry by type (Mandatory, Optional or Manufacturer - mandatories = [] - optionals = [] - manufacturers = [] + mandatories: list[int] = [] + optionals: list[int] = [] + manufacturers: list[int] = [] # Remove all unused PDO # for entry in entries[:]: @@ -526,24 +599,24 @@ def GenerateFileContent(node, filepath): entry_infos = node.GetEntryInfos(entry) values = node.GetEntry(entry, compute=False) # Define section name - text = "\n[%X]\n" % entry + text = f"\n[{entry:X}]\n" # If there is only one value, it's a VAR entry if not isinstance(values, list): # Extract the informations of the first subindex subentry_infos = node.GetSubentryInfos(entry, 0) # Generate EDS informations for the entry - text += "ParameterName=%s\n" % subentry_infos["name"] + text += f"ParameterName={subentry_infos['name']}\n" text += "ObjectType=0x7\n" - text += "DataType=0x%4.4X\n" % subentry_infos["type"] - text += "AccessType=%s\n" % subentry_infos["access"] + text += f"DataType=0x{subentry_infos['type']:04X}\n" + text += f"AccessType={subentry_infos['access']}\n" if subentry_infos["type"] == 1: - text += "DefaultValue=%s\n" % BOOL_TRANSLATE[values] + text += f"DefaultValue={BOOL_TRANSLATE[bool(values)]}\n" else: - text += "DefaultValue=%s\n" % values - text += "PDOMapping=%s\n" % BOOL_TRANSLATE[subentry_infos["pdo"]] + text += f"DefaultValue={values}\n" + text += f"PDOMapping={BOOL_TRANSLATE[subentry_infos['pdo']]}\n" else: # Generate EDS informations for the entry - text += "ParameterName=%s\n" % entry_infos["name"] + text += f"ParameterName={entry_infos['name']}\n" if entry_infos["struct"] & OD.IdenticalSubindexes: text += "ObjectType=0x8\n" else: @@ -557,21 +630,22 @@ def GenerateFileContent(node, filepath): # Extract the informations of each subindex subentry_infos = node.GetSubentryInfos(entry, subentry) # If entry is not for the compatibility, generate informations for subindex - if subentry_infos["name"] != "Compatibility Entry": - subtext += "\n[%Xsub%X]\n" % (entry, subentry) - subtext += "ParameterName=%s\n" % subentry_infos["name"] - subtext += "ObjectType=0x7\n" - subtext += "DataType=0x%4.4X\n" % subentry_infos["type"] - subtext += "AccessType=%s\n" % subentry_infos["access"] - if subentry_infos["type"] == 1: - subtext += "DefaultValue=%s\n" % BOOL_TRANSLATE[value] - else: - subtext += "DefaultValue=%s\n" % value - subtext += "PDOMapping=%s\n" % BOOL_TRANSLATE[subentry_infos["pdo"]] - # Increment number of subindex defined - nb_subentry += 1 + if subentry_infos["name"] == "Compatibility Entry": + continue + subtext += f"\n[{entry:X}sub{subentry:X}]\n" + subtext += f"ParameterName={subentry_infos['name']}\n" + subtext += "ObjectType=0x7\n" + subtext += f"DataType=0x{subentry_infos['type']:04X}\n" + subtext += f"AccessType={subentry_infos['access']}\n" + if subentry_infos["type"] == 1: + subtext += f"DefaultValue={BOOL_TRANSLATE[bool(value)]}\n" + else: + subtext += f"DefaultValue={value}\n" + subtext += f"PDOMapping={BOOL_TRANSLATE[subentry_infos['pdo']]}\n" + # Increment number of subindex defined + nb_subentry += 1 # Write number of subindex defined for the entry - text += "SubNumber=%d\n" % nb_subentry + text += f"SubNumber={nb_subentry}\n" # Write subindex definitions text += subtext @@ -581,7 +655,7 @@ def GenerateFileContent(node, filepath): if 0x2000 <= entry <= 0x5FFF: manufacturers.append(entry) # Second case, entry is required, then it's a mandatory entry - elif entry_infos["need"]: + elif entry_infos.get("need"): mandatories.append(entry) # In any other case, it's an optional entry else: @@ -589,213 +663,182 @@ def GenerateFileContent(node, filepath): # Save text of the entry in the dictiionary of contents indexcontents[entry] = text - # Before generate File Content we sort the entry list - manufacturers.sort() - mandatories.sort() - optionals.sort() - - # Generate Definition of mandatory objects - fileContent += "\n[MandatoryObjects]\n" - fileContent += "SupportedObjects=%d\n" % len(mandatories) - for idx, entry in enumerate(mandatories): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) - # Write mandatory entries - for entry in mandatories: - fileContent += indexcontents[entry] - - # Generate Definition of optional objects - fileContent += "\n[OptionalObjects]\n" - fileContent += "SupportedObjects=%d\n" % len(optionals) - for idx, entry in enumerate(optionals): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) - # Write optional entries - for entry in optionals: - fileContent += indexcontents[entry] - - # Generate Definition of manufacturer objects - fileContent += "\n[ManufacturerObjects]\n" - fileContent += "SupportedObjects=%d\n" % len(manufacturers) - for idx, entry in enumerate(manufacturers): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) - # Write manufacturer entries - for entry in manufacturers: - fileContent += indexcontents[entry] + def generate_index_contents(name: str, entries: list[int]): + """Generate the index section for the index and the subindexes.""" + nonlocal fileContent + fileContent += f"\n[{name}]\n" + fileContent += f"SupportedObjects={len(entries)}\n" + entries.sort() + for idx, entry in enumerate(entries): + fileContent += f"{idx + 1}=0x{entry:04X}\n" + # Write entries + for entry in entries: + fileContent += indexcontents[entry] + + generate_index_contents("MandatoryObjects", mandatories) + generate_index_contents("OptionalObjects", optionals) + generate_index_contents("ManufacturerObjects", manufacturers) # Return File Content return fileContent -# Function that generates EDS file from current node edited -def GenerateEDSFile(filepath, node): - content = GenerateFileContent(node, filepath) - - with open(filepath, "w") as f: - f.write(content) - - -# Function that generate the CPJ file content for the nodelist -def GenerateCPJContent(nodelist): +def generate_cpj_content(nodelist: "NodeList"): + """Generate the CPJ file content for the nodelist.""" nodes = nodelist.SlaveNodes filecontent = "[TOPOLOGY]\n" - filecontent += "NetName=%s\n" % nodelist.NetworkName - filecontent += "Nodes=0x%2.2X\n" % len(nodes) + filecontent += f"NetName={nodelist.NetworkName}\n" + filecontent += f"Nodes=0x{len(nodes):02X}\n" for nodeid in sorted(nodes): - filecontent += "Node%dPresent=0x01\n" % nodeid - filecontent += "Node%dName=%s\n" % (nodeid, nodes[nodeid]["Name"]) - filecontent += "Node%dDCFName=%s\n" % (nodeid, nodes[nodeid]["EDS"]) + filecontent += f"Node{nodeid}Present=0x01\n" + filecontent += f"Node{nodeid}Name={nodes[nodeid].Name}\n" + filecontent += f"Node{nodeid}DCFName={nodes[nodeid].EDS}\n" filecontent += "EDSBaseName=eds\n" return filecontent -# Function that generates Node from an EDS file -def GenerateNode(filepath, nodeid=0): +def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": + """Generate a Node from an EDS file.""" # Create a new node - node = objdictgen.Node(id=nodeid) + node = nodelib.Node(id=nodeid) # Parse file and extract dictionary of EDS entry - eds_dict = ParseEDSFile(filepath) + eds_dict = parse_eds_file(filepath) + + # Extract the common informations for the node + fileinfo = eds_dict.get("FILEINFO", {}) + node.Description = fileinfo.get("DESCRIPTION", "") + + deviceinfo = eds_dict.get("DEVICEINFO", {}) + node.Name = deviceinfo.get("PRODUCTNAME", "") + if deviceinfo.get("SIMPLEBOOTUPSLAVE") == 1: + node.Type = "slave" + if deviceinfo.get("SIMPLEBOOTUPMASTER") == 1: + node.Type = "master" # Ensure we have the ODs we need - missing = ["0x%04X" % i for i in ( + missing = [f"0x{i:04X}" for i in ( 0x1000, ) if i not in eds_dict] if missing: - raise ValueError("EDS file is missing parameter index %s" % (",".join(missing),)) + tp = ",".join(missing) + raise ValueError(f"EDS file is missing parameter index {tp}") # Extract Profile Number from Device Type entry + # NOTE: Objdictgen does not export the profile number as default value + # in index 0x1000, so we can't rely on it to detect the profile. profilenb = eds_dict[0x1000].get("DEFAULTVALUE", 0) & 0x0000ffff - # If profile is not DS-301 or DS-302 if profilenb not in [0, 301, 302]: # Compile Profile name and path to .prf file try: # Import profile - profilename = "DS-%d" % profilenb - mapping, menuentries = objdictgen.ImportProfile(profilename) + profilename = f"DS-{profilenb}" + mapping, menuentries = maps.import_profile(profilename) node.ProfileName = profilename node.Profile = mapping node.SpecificMenu = menuentries - except ValueError: - # Loading profile failed and it will be silently ignored - pass + except ValueError as exc: + log.warning("WARNING: Loading profile '%s' failed: %s", profilename, exc) # Read all entries in the EDS dictionary for entry, values in eds_dict.items(): # All sections with a name in keynames are escaped if entry in SECTION_KEYNAMES: - pass - else: - # Extract informations for the entry - entry_infos = node.GetEntryInfos(entry) - - # If no informations are available, then we write them - if not entry_infos: - # First case, entry is a DOMAIN or VAR - if values["OBJECTTYPE"] in [2, 7]: - if values["OBJECTTYPE"] == 2: - values["DATATYPE"] = values.get("DATATYPE", 0xF) - if values["DATATYPE"] != 0xF: - raise ValueError("Domain entry 0x%4.4X DataType must be 0xF(DOMAIN) if defined" % entry) - # Add mapping for entry - node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.VAR) - # Add mapping for first subindex - node.AddMappingEntry(entry, 0, values={ - "name": values["PARAMETERNAME"], - "type": values["DATATYPE"], - "access": ACCESS_TRANSLATE[values["ACCESSTYPE"].upper()], - "pdo": values.get("PDOMAPPING", 0) == 1, - }) - # Second case, entry is an ARRAY or RECORD - elif values["OBJECTTYPE"] in [8, 9]: - # Extract maximum subindex number defined - max_subindex = max(values["subindexes"]) - # Add mapping for entry - node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.RECORD) - # Add mapping for first subindex - node.AddMappingEntry(entry, 0, values={ - "name": "Number of Entries", - "type": 0x05, - "access": "ro", - "pdo": False, - }) - # Add mapping for other subindexes - for subindex in range(1, int(max_subindex) + 1): - # if subindex is defined - if subindex in values["subindexes"]: - node.AddMappingEntry(entry, subindex, values={ - "name": values["subindexes"][subindex]["PARAMETERNAME"], - "type": values["subindexes"][subindex]["DATATYPE"], - "access": ACCESS_TRANSLATE[values["subindexes"][subindex]["ACCESSTYPE"].upper()], - "pdo": values["subindexes"][subindex].get("PDOMAPPING", 0) == 1, - }) - # if not, we add a mapping for compatibility - else: - node.AddMappingEntry(entry, subindex, values={ - "name": "Compatibility Entry", - "type": 0x05, - "access": "rw", - "pdo": False, - }) - - # # Third case, entry is an RECORD - # elif values["OBJECTTYPE"] == 9: - # # Verify that the first subindex is defined - # if 0 not in values["subindexes"]: - # raise ValueError("Error on entry 0x%4.4X: Subindex 0 must be defined for a RECORD entry" % entry) - # # Add mapping for entry - # node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.ARRAY) - # # Add mapping for first subindex - # node.AddMappingEntry(entry, 0, values={ - # "name": "Number of Entries", - # "type": 0x05, - # "access": "ro", - # "pdo": False, - # }) - # # Verify that second subindex is defined - # if 1 in values["subindexes"]: - # node.AddMappingEntry(entry, 1, values={ - # "name": values["PARAMETERNAME"] + " %d[(sub)]", - # "type": values["subindexes"][1]["DATATYPE"], - # "access": ACCESS_TRANSLATE[values["subindexes"][1]["ACCESSTYPE"].upper()], - # "pdo": values["subindexes"][1].get("PDOMAPPING", 0) == 1, - # "nbmax": 0xFE, - # }) - # else: - # raise ValueError("Error on entry 0x%4.4X: A RECORD entry must have at least 2 subindexes" % entry) - - # Define entry for the new node + continue + + # FIXME: entry should be integer, but can that be guaranteed? + assert isinstance(entry, int) + # If no informations are available, then we write them + if not node.IsMappingEntry(entry): # First case, entry is a DOMAIN or VAR if values["OBJECTTYPE"] in [2, 7]: - # Take default value if it is defined - if "PARAMETERVALUE" in values: - value = values["PARAMETERVALUE"] - elif "DEFAULTVALUE" in values: - value = values["DEFAULTVALUE"] - # Find default value for value type of the entry - else: - value = GetDefaultValue(node, entry) - node.AddEntry(entry, 0, value) - # Second case, entry is an ARRAY or a RECORD + if values["OBJECTTYPE"] == 2: + values["DATATYPE"] = values.get("DATATYPE", 0xF) + if values["DATATYPE"] != 0xF: + raise ValueError(f"Domain entry 0x{entry:04X} DataType must be 0xF(DOMAIN) if defined") + # Add mapping for entry + node.AddMappingEntry(entry, entry={ + "name": values["PARAMETERNAME"], + "struct": OD.VAR, + }) + # Add mapping for first subindex + node.AddMappingSubEntry(entry, 0, values={ + "name": values["PARAMETERNAME"], + "type": values["DATATYPE"], + "access": ACCESS_TRANSLATE[values["ACCESSTYPE"].upper()], + "pdo": values.get("PDOMAPPING", 0) == 1, + }) + + # Second case, entry is an ARRAY or RECORD elif values["OBJECTTYPE"] in [8, 9]: - # Verify that "Subnumber" attribute is defined and has a valid value - if "SUBNUMBER" in values and values["SUBNUMBER"] > 0: - # Extract maximum subindex number defined - max_subindex = max(values["subindexes"]) - node.AddEntry(entry, value=[]) - # Define value for all subindexes except the first - for subindex in range(1, int(max_subindex) + 1): - # Take default value if it is defined and entry is defined - if subindex in values["subindexes"] and "PARAMETERVALUE" in values["subindexes"][subindex]: - value = values["subindexes"][subindex]["PARAMETERVALUE"] - elif subindex in values["subindexes"] and "DEFAULTVALUE" in values["subindexes"][subindex]: - value = values["subindexes"][subindex]["DEFAULTVALUE"] - # Find default value for value type of the subindex - else: - value = GetDefaultValue(node, entry, subindex) - node.AddEntry(entry, subindex, value) - else: - raise ValueError("Array or Record entry 0x%4.4X must have a 'SubNumber' attribute" % entry) + # Extract maximum subindex number defined + max_subindex = max(values["subindexes"]) + # Add mapping for entry + node.AddMappingEntry(entry, entry={ + "name": values["PARAMETERNAME"], + "struct": OD.RECORD + }) + # Add mapping for first subindex + node.AddMappingSubEntry(entry, 0, values={ + "name": "Number of Entries", + "type": 0x05, + "access": "ro", + "pdo": False, + }) + # Add mapping for other subindexes + for subindex in range(1, int(max_subindex) + 1): + # if subindex is defined + if subindex in values["subindexes"]: + node.AddMappingSubEntry(entry, subindex, values={ + "name": values["subindexes"][subindex]["PARAMETERNAME"], + "type": values["subindexes"][subindex]["DATATYPE"], + "access": ACCESS_TRANSLATE[values["subindexes"][subindex]["ACCESSTYPE"].upper()], + "pdo": values["subindexes"][subindex].get("PDOMAPPING", 0) == 1, + }) + # if not, we add a mapping for compatibility + else: + node.AddMappingSubEntry(entry, subindex, values={ + "name": "Compatibility Entry", + "type": 0x05, + "access": "rw", + "pdo": False, + }) + + # First case, entry is a DOMAIN or VAR + if values["OBJECTTYPE"] in [2, 7]: + # Take default value if it is defined + if "PARAMETERVALUE" in values: + value = values["PARAMETERVALUE"] + elif "DEFAULTVALUE" in values: + value = values["DEFAULTVALUE"] + # Find default value for value type of the entry + else: + value = get_default_value(node, entry) + node.AddEntry(entry, 0, value) + + # Second case, entry is an ARRAY or a RECORD + elif values["OBJECTTYPE"] in [8, 9]: + # Verify that "Subnumber" attribute is defined and has a valid value + if "SUBNUMBER" in values and values["SUBNUMBER"] > 0: + # Extract maximum subindex number defined + max_subindex = max(values["subindexes"]) + node.AddEntry(entry, value=[]) + # Define value for all subindexes except the first + for subindex in range(1, int(max_subindex) + 1): + # Take default value if it is defined and entry is defined + if subindex in values["subindexes"] and "PARAMETERVALUE" in values["subindexes"][subindex]: + value = values["subindexes"][subindex]["PARAMETERVALUE"] + elif subindex in values["subindexes"] and "DEFAULTVALUE" in values["subindexes"][subindex]: + value = values["subindexes"][subindex]["DEFAULTVALUE"] + # Find default value for value type of the subindex + else: + value = get_default_value(node, entry, subindex) + node.AddEntry(entry, subindex, value) + else: + raise ValueError(f"Array or Record entry 0x{entry:04X} must have a 'SubNumber' attribute") + return node diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 96f7a8d..dec13fe 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -18,10 +18,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -import os import re +from collections import UserDict +from dataclasses import dataclass +from pathlib import Path +from typing import Any from objdictgen.maps import OD +from objdictgen.typing import NodeProtocol, TODValue, TPath RE_WORD = re.compile(r'([a-zA-Z_0-9]*)') RE_TYPE = re.compile(r'([\_A-Z]*)([0-9]*)') @@ -29,87 +33,158 @@ RE_STARTS_WITH_DIGIT = re.compile(r'^(\d.*)') RE_NOTW = re.compile(r"[^\w]") -CATEGORIES = [("SDO_SVR", 0x1200, 0x127F), ("SDO_CLT", 0x1280, 0x12FF), - ("PDO_RCV", 0x1400, 0x15FF), ("PDO_RCV_MAP", 0x1600, 0x17FF), - ("PDO_TRS", 0x1800, 0x19FF), ("PDO_TRS_MAP", 0x1A00, 0x1BFF)] +CATEGORIES: list[tuple[str, int, int]] = [ + ("SDO_SVR", 0x1200, 0x127F), ("SDO_CLT", 0x1280, 0x12FF), + ("PDO_RCV", 0x1400, 0x15FF), ("PDO_RCV_MAP", 0x1600, 0x17FF), + ("PDO_TRS", 0x1800, 0x19FF), ("PDO_TRS_MAP", 0x1A00, 0x1BFF) +] INDEX_CATEGORIES = ["firstIndex", "lastIndex"] -FILE_HEADER = """\n/* File generated by gen_cfile.py. Should not be modified. */\n""" +FILE_HEADER = """ +/* File generated by gen_cfile.py. Should not be modified. */ +""" + + +@dataclass +class TypeInfos: + """Type infos for a type.""" + type: str + size: int|None + ctype: str + is_unsigned: bool + + +class Text: + """Helper class for formatting text. The class store a string and supports + concatenation and formatting. Operators '+' and '+=' can be used to add + strings without formatting and '%=' can be used to add strings with + formatting. The string is formatted with varaibled from the context + dictionary. + + This exists as a workaround until the strings have been converted to + proper f-strings. + """ + + # FIXME: Remove all %= entries, use f-strings instead, and delete this class + + def __init__(self, context: "CFileContext", text: str): + self.text: str = text + self.context: "CFileContext" = context + def __iadd__(self, other: "str|Text") -> "Text": + """Add a string to the text without formatting.""" + self.text += str(other) + return self -class CFileContext: - def __init__(self): + def __add__(self, other: "str|Text") -> "Text": + """Add a string to the text without formatting.""" + return Text(self.context, self.text + str(other)) + + def __imod__(self, other: str) -> "Text": + """Add a string to the text with formatting.""" + self.text += other.format(**self.context) + return self + + def __str__(self) -> str: + """Return the text.""" + return self.text + + +class CFileContext(UserDict): + """Context for generating C file. It serves as a dictionary to store data + and as a helper for formatting text. + """ + internal_types: dict[str, TypeInfos] + default_string_size: int = 10 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.internal_types = {} - self.default_string_size = 10 + def __getattr__(self, name: str) -> Any: + """Look up unknown attributes in the data dictionary.""" + return self.data[name] -# Format a string for making a C++ variable -def FormatName(name): - wordlist = [word for word in RE_WORD.findall(name) if word] - return "_".join(wordlist) + # FIXME: Delete this method when everything is converted to f-strings + def text(self, s: str = "") -> Text: + """Start a new text object""" + return Text(self, s) + + # FIXME: Delete this method when everything is converted to f-strings + def ftext(self, s: str) -> Text: + """Format a text string.""" + return Text(self, "").__imod__(s) # pylint: disable=unnecessary-dunder-call + def get_valid_type_infos(self, typename: str, items=None) -> TypeInfos: + """Get valid type infos from a typename. + """ -# Extract the informations from a given type name -def GetValidTypeInfos(context, typename, items=None): - items = items or [] - if typename in context.internal_types: - return context.internal_types[typename] - result = RE_TYPE.match(typename) - if result: - values = result.groups() - if values[0] == "UNSIGNED" and int(values[1]) in [i * 8 for i in range(1, 9)]: - typeinfos = ("UNS%s" % values[1], None, "uint%s" % values[1], True) - elif values[0] == "INTEGER" and int(values[1]) in [i * 8 for i in range(1, 9)]: - typeinfos = ("INTEGER%s" % values[1], None, "int%s" % values[1], False) - elif values[0] == "REAL" and int(values[1]) in (32, 64): - typeinfos = ("%s%s" % (values[0], values[1]), None, "real%s" % values[1], False) - elif values[0] in ["VISIBLE_STRING", "OCTET_STRING"]: - size = context.default_string_size + # Return cached typeinfos + if typename in self.internal_types: + return self.internal_types[typename] + + items = items or [] + result = RE_TYPE.match(typename) + if not result: + # FIXME: The !!! is for special UI handling + raise ValueError(f"!!! '{typename}' isn't a valid type for CanFestival.") + + if result[1] == "UNSIGNED" and int(result[2]) in [i * 8 for i in range(1, 9)]: + typeinfos = TypeInfos(f"UNS{result[2]}", None, f"uint{result[2]}", True) + elif result[1] == "INTEGER" and int(result[2]) in [i * 8 for i in range(1, 9)]: + typeinfos = TypeInfos(f"INTEGER{result[2]}", None, f"int{result[2]}", False) + elif result[1] == "REAL" and int(result[2]) in (32, 64): + typeinfos = TypeInfos(f"{result[1]}{result[2]}", None, f"real{result[2]}", False) + elif result[1] in ["VISIBLE_STRING", "OCTET_STRING"]: + size = self.default_string_size for item in items: size = max(size, len(item)) - if values[1]: - size = max(size, int(values[1])) - typeinfos = ("UNS8", size, "visible_string", False) - elif values[0] == "DOMAIN": + if result[2]: + size = max(size, int(result[2])) + typeinfos = TypeInfos("UNS8", size, "visible_string", False) + elif result[1] == "DOMAIN": size = 0 for item in items: size = max(size, len(item)) - typeinfos = ("UNS8", size, "domain", False) - elif values[0] == "BOOLEAN": - typeinfos = ("UNS8", None, "boolean", False) + typeinfos = TypeInfos("UNS8", size, "domain", False) + elif result[1] == "BOOLEAN": + typeinfos = TypeInfos("UNS8", None, "boolean", False) else: # FIXME: The !!! is for special UI handling - raise ValueError("!!! '%s' isn't a valid type for CanFestival." % typename) - if typeinfos[2] not in ["visible_string", "domain"]: - context.internal_types[typename] = typeinfos - else: - # FIXME: The !!! is for special UI handling - raise ValueError("!!! '%s' isn't a valid type for CanFestival." % typename) - return typeinfos - - -def ComputeValue(type_, value): - if type_ == "visible_string": - return '"%s"' % value, "" - if type_ == "domain": - return '"%s"' % ''.join(["\\x%2.2x" % ord(char) for char in value]), "" - if type_.startswith("real"): - return "%f" % value, "" - # value is integer; make sure to handle negative numbers correctly - if value < 0: - return "-0x%X" % (-value), "\t/* %s */" % str(value) - return "0x%X" % value, "\t/* %s */" % str(value) + raise ValueError(f"!!! '{typename}' isn't a valid type for CanFestival.") + + # Cache the typeinfos + if typeinfos.ctype not in ["visible_string", "domain"]: + self.internal_types[typename] = typeinfos + return typeinfos + + +def format_name(name: str) -> str: + """Format a string for making a C++ variable.""" + wordlist = [word for word in RE_WORD.findall(name) if word] + return "_".join(wordlist) -def GetTypeName(node, typenumber): - typename = node.GetTypeName(typenumber) - if typename is None: - # FIXME: The !!! is for special UI handling - raise ValueError("!!! Datatype with value '0x%4.4X' isn't defined in CanFestival." % typenumber) - return typename +def compute_value(value: TODValue, ctype: str) -> tuple[str, str]: + """Compute value for C file.""" + if ctype == "visible_string": + return f'"{value}"', "" + if ctype == "domain": + # FIXME: This ctype assumes the value type + assert isinstance(value, str) + tp = ''.join([f"\\x{ord(char):02x}" for char in value]) + return f'"{tp}"', "" + if ctype.startswith("real"): + return str(value), "" + # FIXME: Assume value is an integer + assert not isinstance(value, str) + # Make sure to handle negative numbers correctly + if value < 0: + return f"-0x{-value:X}", f"\t/* {value} */" + return f"0x{value:X}", f"\t/* {value} */" -def GenerateFileContent(node, headerfilepath, pointers_dict=None): +def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=None) -> tuple[str, str, str]: """ pointers_dict = {(Idx,Sidx):"VariableName",...} """ @@ -117,19 +192,18 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): # FIXME: Too many camelCase vars in here # pylint: disable=invalid-name - context = CFileContext() + # Setup the main context to store the data + ctx = CFileContext() + pointers_dict = pointers_dict or {} - texts = {} - texts["maxPDOtransmit"] = 0 - texts["NodeName"] = node.Name - texts["NodeID"] = node.ID - texts["NodeType"] = node.Type - texts["Description"] = node.Description or "" - texts["iam_a_slave"] = 0 - if texts["NodeType"] == "slave": - texts["iam_a_slave"] = 1 - - context.default_string_size = node.DefaultStringSize + ctx["maxPDOtransmit"] = 0 + ctx["NodeName"] = node.Name + ctx["NodeID"] = node.ID + ctx["NodeType"] = node.Type + ctx["Description"] = node.Description or "" + ctx["iam_a_slave"] = 1 if node.Type == "slave" else 0 + + ctx.default_string_size = node.DefaultStringSize # Compiling lists of indexes rangelist = [idx for idx in node.GetIndexes() if 0 <= idx <= 0x260] @@ -139,16 +213,20 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): # pdolist = [idx for idx in node.GetIndexes() if 0x1400 <= idx <= 0x1BFF] variablelist = [idx for idx in node.GetIndexes() if 0x2000 <= idx <= 0xBFFF] -# ------------------------------------------------------------------------------ -# Declaration of the value range types -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Declaration of the value range types + # -------------------------------------------------------------------------- - valueRangeContent = "" - strDefine = "\n#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */" - strSwitch = """ case valueRange_EMC: + valueRangeContent = ctx.text() + strDefine = ctx.text( + "\n#define valueRange_EMC 0x9F " + "/* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */" + ) + strSwitch = ctx.text(""" case valueRange_EMC: if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break;\n""" - context.internal_types["valueRange_EMC"] = ("UNS8", "", "valueRange_EMC", True) + break; +""") + ctx.internal_types["valueRange_EMC"] = TypeInfos("UNS8", 0, "valueRange_EMC", True) num = 0 for index in rangelist: rangename = node.GetEntryName(index) @@ -156,328 +234,416 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): if result: num += 1 typeindex = node.GetEntry(index, 1) + # FIXME: It is assumed that rangelist contains propery formatted entries + # where index 1 is the object type as int + assert isinstance(typeindex, int) typename = node.GetTypeName(typeindex) - typeinfos = GetValidTypeInfos(context, typename) - context.internal_types[rangename] = (typeinfos[0], typeinfos[1], "valueRange_%d" % num) + typeinfos = ctx.get_valid_type_infos(typename) + ctx.internal_types[rangename] = TypeInfos( + typeinfos.type, typeinfos.size, f"valueRange_{num}", typeinfos.is_unsigned + ) minvalue = node.GetEntry(index, 2) maxvalue = node.GetEntry(index, 3) - strDefine += "\n#define valueRange_%d 0x%02X /* Type %s, %s < value < %s */" % (num, index, typeinfos[0], str(minvalue), str(maxvalue)) - strSwitch += " case valueRange_%d:\n" % (num) - if typeinfos[3] and minvalue <= 0: + # FIXME: It assumed the data is properly formatted + assert isinstance(minvalue, int) + assert isinstance(maxvalue, int) + strDefine += ( + f"\n#define valueRange_{num} 0x{index:02X} " + f"/* Type {typeinfos.type}, {minvalue} < value < {maxvalue} */" + ) + strSwitch += f" case valueRange_{num}:\n" + if typeinfos.is_unsigned and minvalue <= 0: strSwitch += " /* Negative or null low limit ignored because of unsigned type */;\n" else: - strSwitch += " if (*(%s*)value < (%s)%s) return OD_VALUE_TOO_LOW;\n" % (typeinfos[0], typeinfos[0], str(minvalue)) - strSwitch += " if (*(%s*)value > (%s)%s) return OD_VALUE_TOO_HIGH;\n" % (typeinfos[0], typeinfos[0], str(maxvalue)) + strSwitch += ( + f" if (*({typeinfos.type}*)value < ({typeinfos.type}){minvalue}) return OD_VALUE_TOO_LOW;\n" + ) + strSwitch += ( + f" if (*({typeinfos.type}*)value > ({typeinfos.type}){maxvalue}) return OD_VALUE_TOO_HIGH;\n" + ) strSwitch += " break;\n" valueRangeContent += strDefine - valueRangeContent += "\nUNS32 %(NodeName)s_valueRangeTest (UNS8 typeValue, void * value)\n{" % texts + valueRangeContent %= "\nUNS32 {NodeName}_valueRangeTest (UNS8 typeValue, void * value)\n{{" valueRangeContent += "\n switch (typeValue) {\n" valueRangeContent += strSwitch valueRangeContent += " }\n return 0;\n}\n" -# ------------------------------------------------------------------------------ -# Creation of the mapped variables and object dictionary -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Creation of the mapped variables and object dictionary + # -------------------------------------------------------------------------- - mappedVariableContent = "" - pointedVariableContent = "" - strDeclareHeader = "" - indexContents = {} - headerObjectDefinitionContent = "" + mappedVariableContent = ctx.text() + pointedVariableContent = ctx.text() + strDeclareHeader = ctx.text() + indexContents: dict[int, str|Text] = {} + headerObjDefinitionContent = ctx.text() for index in listindex: - texts["index"] = index - strindex = "" + ctx["index"] = index entry_infos = node.GetEntryInfos(index) params_infos = node.GetParamsEntry(index) - texts["EntryName"] = entry_infos["name"] + ctx["EntryName"] = entry_infos["name"] values = node.GetEntry(index) + + strindex = ctx.text() if index in variablelist: - strindex += "\n/* index 0x%(index)04X : Mapped variable %(EntryName)s */\n" % texts + strindex %= "\n/* index 0x{index:04X} : Mapped variable {EntryName} */\n" else: - strindex += "\n/* index 0x%(index)04X : %(EntryName)s. */\n" % texts + strindex %= "\n/* index 0x{index:04X} : {EntryName}. */\n" # Entry type is VAR if not isinstance(values, list): + # FIXME: It is assumed that the type of GetParamsEntry() follows the object type + # of GetEntry() + assert not isinstance(params_infos, list) subentry_infos = node.GetSubentryInfos(index, 0) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, [values]) + typename = node.GetTypeName(subentry_infos["type"]) + typeinfos = ctx.get_valid_type_infos(typename, [values]) if typename == "DOMAIN" and index in variablelist: - if not typeinfos[1]: - raise ValueError("Domain variable not initialized, index: 0x%04X, subindex: 0x00" % index) - texts["subIndexType"] = typeinfos[0] - if typeinfos[1] is not None: + if not typeinfos.size: + raise ValueError(f"Domain variable not initialized, index: 0x{index:04X}, subindex: 0x00") + ctx["subIndexType"] = typeinfos.type + if typeinfos.size is not None: if params_infos["buffer_size"]: - texts["suffixe"] = "[%s]" % params_infos["buffer_size"] + ctx["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = "[%d]" % typeinfos[1] + ctx["suffix"] = f"[{typeinfos.size}]" else: - texts["suffixe"] = "" - texts["value"], texts["comment"] = ComputeValue(typeinfos[2], values) + ctx["suffix"] = "" + ctx["value"], ctx["comment"] = compute_value(values, typeinfos.ctype) if index in variablelist: - texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(subentry_infos["name"])) - strDeclareHeader += "extern %(subIndexType)s %(name)s%(suffixe)s;\t\t/* Mapped at index 0x%(index)04X, subindex 0x00*/\n" % texts - mappedVariableContent += "%(subIndexType)s %(name)s%(suffixe)s = %(value)s;\t\t/* Mapped at index 0x%(index)04X, subindex 0x00 */\n" % texts + ctx["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(subentry_infos["name"])) + strDeclareHeader %= ( + "extern {subIndexType} {name}{suffix};" + "\t\t/* Mapped at index 0x{index:04X}, subindex 0x00*/\n" + ) + mappedVariableContent %= ( + "{subIndexType} {name}{suffix} = {value};" + "\t\t/* Mapped at index 0x{index:04X}, subindex 0x00 */\n" + ) else: - strindex += " %(subIndexType)s %(NodeName)s_obj%(index)04X%(suffixe)s = %(value)s;%(comment)s\n" % texts + strindex %= ( + " " + "{subIndexType} {NodeName}_obj{index:04X}{suffix} = {value};{comment}\n" + ) values = [values] else: subentry_infos = node.GetSubentryInfos(index, 0) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename) - if index == 0x1003: - texts["value"] = 0 - else: - texts["value"] = values[0] - texts["subIndexType"] = typeinfos[0] - strindex += " %(subIndexType)s %(NodeName)s_highestSubIndex_obj%(index)04X = %(value)d; /* number of subindex - 1*/\n" % texts + typename = node.GetTypeName(subentry_infos["type"]) + typeinfos = ctx.get_valid_type_infos(typename) + ctx["value"] = values[0] if index != 0x1003 else 0 + ctx["subIndexType"] = typeinfos.type + strindex %= ( + " " + "{subIndexType} {NodeName}_highestSubIndex_obj{index:04X} = {value}; " + "/* number of subindex - 1*/\n" + ) # Entry type is ARRAY if entry_infos["struct"] & OD.IdenticalSubindexes: subentry_infos = node.GetSubentryInfos(index, 1) typename = node.GetTypeName(subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, values[1:]) - texts["subIndexType"] = typeinfos[0] - if typeinfos[1] is not None: - texts["suffixe"] = "[%d]" % typeinfos[1] - texts["type_suffixe"] = "*" + typeinfos = ctx.get_valid_type_infos(typename, values[1:]) + ctx["subIndexType"] = typeinfos.type + if typeinfos.size is not None: + ctx["suffix"] = f"[{typeinfos.size}]" + ctx["type_suffix"] = "*" else: - texts["suffixe"] = "" - texts["type_suffixe"] = "" - texts["length"] = values[0] + ctx["suffix"] = "" + ctx["type_suffix"] = "" + ctx["length"] = values[0] if index in variablelist: - texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(entry_infos["name"])) - texts["values_count"] = str(len(values) - 1) - strDeclareHeader += "extern %(subIndexType)s %(name)s[%(values_count)s]%(suffixe)s;\t\t/* Mapped at index 0x%(index)04X, subindex 0x01 - 0x%(length)02X */\n" % texts - mappedVariableContent += "%(subIndexType)s %(name)s[]%(suffixe)s =\t\t/* Mapped at index 0x%(index)04X, subindex 0x01 - 0x%(length)02X */\n {\n" % texts + ctx["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(entry_infos["name"])) + ctx["values_count"] = str(len(values) - 1) + strDeclareHeader %= ( + "extern {subIndexType} {name}[{values_count}]{suffix};\t\t" + "/* Mapped at index 0x{index:04X}, subindex 0x01 - 0x{length:02X} */\n" + ) + mappedVariableContent %= ( + "{subIndexType} {name}[]{suffix} =\t\t" + "/* Mapped at index 0x{index:04X}, subindex 0x01 - 0x{length:02X} */\n {{\n" + ) for subindex, value in enumerate(values): sep = "," if subindex > 0: if subindex == len(values) - 1: sep = "" - value, comment = ComputeValue(typeinfos[2], value) + value, comment = compute_value(value, typeinfos.ctype) if len(value) == 2 and typename == "DOMAIN": - raise ValueError("Domain variable not initialized, index : 0x%04X, subindex : 0x%02X" % (index, subindex)) - mappedVariableContent += " %s%s%s\n" % (value, sep, comment) + raise ValueError( + "Domain variable not initialized, " + f"index: 0x{index:04X}, subindex: 0x{subindex:02X}" + ) + mappedVariableContent += f" {value}{sep}{comment}\n" mappedVariableContent += " };\n" else: - strindex += " %(subIndexType)s%(type_suffixe)s %(NodeName)s_obj%(index)04X[] = \n {\n" % texts + strindex %= ( + " " + "{subIndexType}{type_suffix} {NodeName}_obj{index:04X}[] = \n" + " {{\n" + ) for subindex, value in enumerate(values): sep = "," if subindex > 0: if subindex == len(values) - 1: sep = "" - value, comment = ComputeValue(typeinfos[2], value) - strindex += " %s%s%s\n" % (value, sep, comment) + value, comment = compute_value(value, typeinfos.ctype) + strindex += f" {value}{sep}{comment}\n" strindex += " };\n" else: - texts["parent"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(entry_infos["name"])) + ctx["parent"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(entry_infos["name"])) # Entry type is RECORD for subindex, value in enumerate(values): - texts["subindex"] = subindex + ctx["subindex"] = subindex + # FIXME: Are there any point in calling this for subindex 0? params_infos = node.GetParamsEntry(index, subindex) + # FIXME: Assumed params_info type is coherent with entry_infos["struct"] + assert not isinstance(params_infos, list) if subindex > 0: subentry_infos = node.GetSubentryInfos(index, subindex) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, [values[subindex]]) - texts["subIndexType"] = typeinfos[0] - if typeinfos[1] is not None: + typename = node.GetTypeName(subentry_infos["type"]) + typeinfos = ctx.get_valid_type_infos(typename, [values[subindex]]) + ctx["subIndexType"] = typeinfos.type + if typeinfos.size is not None: if params_infos["buffer_size"]: - texts["suffixe"] = "[%s]" % params_infos["buffer_size"] + ctx["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = "[%d]" % typeinfos[1] + ctx["suffix"] = f"[{typeinfos.size}]" else: - texts["suffixe"] = "" - texts["value"], texts["comment"] = ComputeValue(typeinfos[2], value) - texts["name"] = FormatName(subentry_infos["name"]) + ctx["suffix"] = "" + ctx["value"], ctx["comment"] = compute_value(value, typeinfos.ctype) + ctx["name"] = format_name(subentry_infos["name"]) if index in variablelist: - strDeclareHeader += "extern %(subIndexType)s %(parent)s_%(name)s%(suffixe)s;\t\t/* Mapped at index 0x%(index)04X, subindex 0x%(subindex)02X */\n" % texts - mappedVariableContent += "%(subIndexType)s %(parent)s_%(name)s%(suffixe)s = %(value)s;\t\t/* Mapped at index 0x%(index)04X, subindex 0x%(subindex)02X */\n" % texts + strDeclareHeader %= ( + "extern {subIndexType} {parent}_{name}{suffix};\t\t" + "/* Mapped at index 0x{index:04X}, subindex 0x{subindex:02X} */\n" + ) + mappedVariableContent %= ( + "{subIndexType} {parent}_{name}{suffix} = {value};\t\t" + "/* Mapped at index 0x{index:04X}, subindex 0x{subindex:02X} */\n" + ) else: - strindex += " %(subIndexType)s %(NodeName)s_obj%(index)04X_%(name)s%(suffixe)s = %(value)s;%(comment)s\n" % texts - headerObjectDefinitionContent += ( - "\n#define " - + RE_NOTW.sub("_", texts["NodeName"]) - + "_" - + RE_NOTW.sub("_", texts["EntryName"]) - + "_Idx " - + str(format(texts["index"], "#04x")) - + "\n") + strindex %= ( + " " + "{subIndexType} {NodeName}" + "_obj{index:04X}_{name}{suffix} = " + "{value};{comment}\n" + ) + + headerObjDefinitionContent += ( + f"\n#define {RE_NOTW.sub('_', ctx['NodeName'])}" + f"_{RE_NOTW.sub('_', ctx['EntryName'])}_Idx {ctx['index']:#04x}\n" + ) # Generating Dictionary C++ entry - strindex += " subindex %(NodeName)s_Index%(index)04X[] = \n {\n" % texts + strindex %= ( + " " + "subindex {NodeName}_Index{index:04X}[] = \n" + " {{\n" + ) generateSubIndexArrayComment = True for subindex, _ in enumerate(values): subentry_infos = node.GetSubentryInfos(index, subindex) params_infos = node.GetParamsEntry(index, subindex) + # FIXME: As subindex is non-zero, params can't be a list + assert not isinstance(params_infos, list) if subindex < len(values) - 1: sep = "," else: sep = "" typename = node.GetTypeName(subentry_infos["type"]) if entry_infos["struct"] & OD.IdenticalSubindexes: - typeinfos = GetValidTypeInfos(context, typename, values[1:]) + typeinfos = ctx.get_valid_type_infos(typename, values[1:]) else: - typeinfos = GetValidTypeInfos(context, typename, [values[subindex]]) + typeinfos = ctx.get_valid_type_infos(typename, [values[subindex]]) if subindex == 0: if index == 0x1003: - typeinfos = GetValidTypeInfos(context, "valueRange_EMC") + typeinfos = ctx.get_valid_type_infos("valueRange_EMC") if entry_infos["struct"] & OD.MultipleSubindexes: - name = "%(NodeName)s_highestSubIndex_obj%(index)04X" % texts + name = f"{ctx['NodeName']}_highestSubIndex_obj{ctx['index']:04X}" elif index in variablelist: - name = FormatName(subentry_infos["name"]) + name = format_name(subentry_infos["name"]) else: - name = FormatName("%s_obj%04X" % (texts["NodeName"], texts["index"])) + name = format_name(f"{ctx['NodeName']}_obj{ctx['index']:04X}") elif entry_infos["struct"] & OD.IdenticalSubindexes: if index in variablelist: - name = "%s[%d]" % (FormatName(entry_infos["name"]), subindex - 1) + name = f"{format_name(entry_infos['name'])}[{subindex - 1}]" else: - name = "%s_obj%04X[%d]" % (texts["NodeName"], texts["index"], subindex - 1) + name = f"{ctx['NodeName']}_obj{ctx['index']:04X}[{subindex - 1}]" else: if index in variablelist: - name = FormatName("%s_%s" % (entry_infos["name"], subentry_infos["name"])) + name = format_name(f"{entry_infos['name']}_{subentry_infos['name']}") else: - name = "%s_obj%04X_%s" % (texts["NodeName"], texts["index"], FormatName(subentry_infos["name"])) - if typeinfos[2] == "visible_string": + name = ( + f"{ctx['NodeName']}_obj{ctx['index']:04X}_" + f"{format_name(subentry_infos['name'])}" + ) + if typeinfos.ctype == "visible_string": if params_infos["buffer_size"]: - sizeof = params_infos["buffer_size"] + sizeof = str(params_infos["buffer_size"]) else: - sizeof = str(max(len(values[subindex]), context.default_string_size)) - elif typeinfos[2] == "domain": - sizeof = str(len(values[subindex])) + value = values[subindex] + # FIXME: It should be a str type with visible_string + assert isinstance(value, str) + sizeof = str(max(len(value), ctx.default_string_size)) + elif typeinfos.ctype == "domain": + value = values[subindex] + # FIXME: Value should be string + assert isinstance(value, str) + sizeof = str(len(value)) else: - sizeof = "sizeof (%s)" % typeinfos[0] + sizeof = f"sizeof ({typeinfos.type})" params = node.GetParamsEntry(index, subindex) + # FIXME: As subindex is non-zero, params can't be a list + assert not isinstance(params, list) if params["save"]: save = "|TO_BE_SAVE" else: save = "" - strindex += " { %s%s, %s, %s, (void*)&%s, NULL }%s\n" % (subentry_infos["access"].upper(), save, typeinfos[2], sizeof, RE_STARTS_WITH_DIGIT.sub(r'_\1', name), sep) + start_digit = RE_STARTS_WITH_DIGIT.sub(r'_\1', name) + strindex += ( + f" {{ " + f"{subentry_infos['access'].upper()}{save}, " + f"{typeinfos.ctype}, {sizeof}, (void*)&{start_digit}, NULL " + f"}}{sep}\n" + ) pointer_name = pointers_dict.get((index, subindex), None) if pointer_name is not None: - pointedVariableContent += "%s* %s = &%s;\n" % (typeinfos[0], pointer_name, name) + pointedVariableContent += f"{typeinfos.type}* {pointer_name} = &{name};\n" if not entry_infos["struct"] & OD.IdenticalSubindexes: generateSubIndexArrayComment = True - headerObjectDefinitionContent += ( - "#define " - + RE_NOTW.sub("_", texts["NodeName"]) - + "_" - + RE_NOTW.sub("_", texts["EntryName"]) - + "_" - + RE_NOTW.sub("_", subentry_infos["name"]) - + "_sIdx " - + str(format(subindex, "#04x"))) + headerObjDefinitionContent += ( + f"#define {RE_NOTW.sub('_', ctx['NodeName'])}" + f"_{RE_NOTW.sub('_', ctx['EntryName'])}" + f"_{RE_NOTW.sub('_', subentry_infos['name'])}" + f"_sIdx {subindex:#04x}" + ) if params_infos["comment"]: - headerObjectDefinitionContent += " /* " + params_infos["comment"] + " */\n" + headerObjDefinitionContent += " /* " + params_infos["comment"] + " */\n" else: - headerObjectDefinitionContent += "\n" + headerObjDefinitionContent += "\n" elif generateSubIndexArrayComment: generateSubIndexArrayComment = False - # Generate Number_of_Entries_sIdx define and write comment about not generating defines for the rest of the array objects - headerObjectDefinitionContent += ( - "#define " - + RE_NOTW.sub("_", texts["NodeName"]) - + "_" - + RE_NOTW.sub("_", texts["EntryName"]) - + "_" - + RE_NOTW.sub("_", subentry_infos["name"]) - + "_sIdx " + str(format(subindex, "#04x")) - + "\n") - headerObjectDefinitionContent += "/* subindex define not generated for array objects */\n" + # Generate Number_of_Entries_sIdx define and write comment + # about not generating defines for the rest of the array objects + headerObjDefinitionContent += ( + f"#define {RE_NOTW.sub('_', ctx['NodeName'])}" + f"_{RE_NOTW.sub('_', ctx['EntryName'])}" + f"_{RE_NOTW.sub('_', subentry_infos['name'])}" + f"_sIdx {subindex:#04x}\n" + ) + headerObjDefinitionContent += "/* subindex define not generated for array objects */\n" strindex += " };\n" indexContents[index] = strindex -# ------------------------------------------------------------------------------ -# Declaration of Particular Parameters -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Declaration of Particular Parameters + # -------------------------------------------------------------------------- if 0x1003 not in communicationlist: entry_infos = node.GetEntryInfos(0x1003) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1003] = """\n/* index 0x1003 : %(EntryName)s */ - UNS8 %(NodeName)s_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ - UNS32 %(NodeName)s_obj1003[] = - { + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1003] = ctx.ftext(""" +/* index 0x1003 : {EntryName} */ + UNS8 {NodeName}_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ + UNS32 {NodeName}_obj1003[] = + {{ 0x0 /* 0 */ - }; - subindex %(NodeName)s_Index1003[] = - { - { RW, valueRange_EMC, sizeof (UNS8), (void*)&%(NodeName)s_highestSubIndex_obj1003, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&%(NodeName)s_obj1003[0], NULL } - }; -""" % texts + }}; + subindex {NodeName}_Index1003[] = + {{ + {{ RW, valueRange_EMC, sizeof (UNS8), (void*)&{NodeName}_highestSubIndex_obj1003, NULL }}, + {{ RO, uint32, sizeof (UNS32), (void*)&{NodeName}_obj1003[0], NULL }} + }}; +""") if 0x1005 not in communicationlist: entry_infos = node.GetEntryInfos(0x1005) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1005] = """\n/* index 0x1005 : %(EntryName)s */ - UNS32 %(NodeName)s_obj1005 = 0x0; /* 0 */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1005] = ctx.ftext(""" +/* index 0x1005 : {EntryName} */ + UNS32 {NodeName}_obj1005 = 0x0; /* 0 */ +""") if 0x1006 not in communicationlist: entry_infos = node.GetEntryInfos(0x1006) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1006] = """\n/* index 0x1006 : %(EntryName)s */ - UNS32 %(NodeName)s_obj1006 = 0x0; /* 0 */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1006] = ctx.ftext(""" +/* index 0x1006 : {EntryName} */ + UNS32 {NodeName}_obj1006 = 0x0; /* 0 */ +""") if 0x1014 not in communicationlist: entry_infos = node.GetEntryInfos(0x1014) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1014] = """\n/* index 0x1014 : %(EntryName)s */ - UNS32 %(NodeName)s_obj1014 = 0x80 + 0x%(NodeID)02X; /* 128 + NodeID */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1014] = ctx.ftext(""" +/* index 0x1014 : {EntryName} */ + UNS32 {NodeName}_obj1014 = 0x80 + 0x{NodeID:02X}; /* 128 + NodeID */ +""") if 0x1016 in communicationlist: - texts["heartBeatTimers_number"] = node.GetEntry(0x1016, 0) + hbn = node.GetEntry(0x1016, 0) + # FIXME: Hardcoded assumption on data-type? + assert isinstance(hbn, int) + ctx["heartBeatTimers_number"] = hbn else: - texts["heartBeatTimers_number"] = 0 + ctx["heartBeatTimers_number"] = 0 entry_infos = node.GetEntryInfos(0x1016) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1016] = """\n/* index 0x1016 : %(EntryName)s */ - UNS8 %(NodeName)s_highestSubIndex_obj1016 = 0; - UNS32 %(NodeName)s_obj1016[]={0}; -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1016] = ctx.ftext(""" +/* index 0x1016 : {EntryName} */ + UNS8 {NodeName}_highestSubIndex_obj1016 = 0; + UNS32 {NodeName}_obj1016[]={{0}}; +""") if 0x1017 not in communicationlist: entry_infos = node.GetEntryInfos(0x1017) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1017] = """\n/* index 0x1017 : %(EntryName)s */ - UNS16 %(NodeName)s_obj1017 = 0x0; /* 0 */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x1017] = ctx.ftext(""" +/* index 0x1017 : {EntryName} */ + UNS16 {NodeName}_obj1017 = 0x0; /* 0 */ +""") if 0x100C not in communicationlist: entry_infos = node.GetEntryInfos(0x100C) - texts["EntryName"] = entry_infos["name"] - indexContents[0x100C] = """\n/* index 0x100C : %(EntryName)s */ - UNS16 %(NodeName)s_obj100C = 0x0; /* 0 */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x100C] = ctx.ftext(""" +/* index 0x100C : {EntryName} */ + UNS16 {NodeName}_obj100C = 0x0; /* 0 */ +""") if 0x100D not in communicationlist: entry_infos = node.GetEntryInfos(0x100D) - texts["EntryName"] = entry_infos["name"] - indexContents[0x100D] = """\n/* index 0x100D : %(EntryName)s */ - UNS8 %(NodeName)s_obj100D = 0x0; /* 0 */ -""" % texts + ctx["EntryName"] = entry_infos["name"] + indexContents[0x100D] = ctx.ftext(""" +/* index 0x100D : {EntryName} */ + UNS8 {NodeName}_obj100D = 0x0; /* 0 */ +""") -# ------------------------------------------------------------------------------ -# Declaration of navigation in the Object Dictionary -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Declaration of navigation in the Object Dictionary + # -------------------------------------------------------------------------- - strDeclareIndex = "" + strDeclareIndex = ctx.text() strDeclareSwitch = "" strQuickIndex = "" - quick_index = {} + + quick_index: dict[str, dict[str, int]] = {} for index_cat in INDEX_CATEGORIES: quick_index[index_cat] = {} for cat, idx_min, idx_max in CATEGORIES: quick_index[index_cat][cat] = 0 + maxPDOtransmit = 0 for i, index in enumerate(listindex): - texts["index"] = index - strDeclareIndex += " { (subindex*)%(NodeName)s_Index%(index)04X,sizeof(%(NodeName)s_Index%(index)04X)/sizeof(%(NodeName)s_Index%(index)04X[0]), 0x%(index)04X},\n" % texts - strDeclareSwitch += " case 0x%04X: i = %d;break;\n" % (index, i) + ctx["index"] = index + strDeclareIndex %= ( + " {{ (subindex*){NodeName}_Index{index:04X}," + "sizeof({NodeName}_Index{index:04X})/" + "sizeof({NodeName}_Index{index:04X}[0]), 0x{index:04X}}},\n" + ) + strDeclareSwitch += f" case 0x{index:04X}: i = {i};break;\n" for cat, idx_min, idx_max in CATEGORIES: if idx_min <= index <= idx_max: quick_index["lastIndex"][cat] = i @@ -485,55 +651,59 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): quick_index["firstIndex"][cat] = i if cat == "PDO_TRS": maxPDOtransmit += 1 - texts["maxPDOtransmit"] = max(1, maxPDOtransmit) + + ctx["maxPDOtransmit"] = max(1, maxPDOtransmit) for index_cat in INDEX_CATEGORIES: - strQuickIndex += "\nconst quick_index %s_%s = {\n" % (texts["NodeName"], index_cat) + strQuickIndex += f"\nconst quick_index {ctx['NodeName']}_{index_cat} = {{\n" sep = "," for i, (cat, idx_min, idx_max) in enumerate(CATEGORIES): if i == len(CATEGORIES) - 1: sep = "" - strQuickIndex += " %d%s /* %s */\n" % (quick_index[index_cat][cat], sep, cat) + strQuickIndex += f" {quick_index[index_cat][cat]}{sep} /* {cat} */\n" strQuickIndex += "};\n" -# ------------------------------------------------------------------------------ -# Write File Content -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Write File Content + # -------------------------------------------------------------------------- - fileContent = FILE_HEADER + """ -#include "%s" -""" % (headerfilepath) + fileContent = ctx.text(FILE_HEADER) + fileContent += f""" +#include "{headerfile}" +""" fileContent += """ /**************************************************************************/ /* Declaration of mapped variables */ /**************************************************************************/ -""" + mappedVariableContent - +""" + fileContent += mappedVariableContent fileContent += """ /**************************************************************************/ /* Declaration of value range types */ /**************************************************************************/ -""" + valueRangeContent - - fileContent += """ +""" + fileContent += valueRangeContent + fileContent %= """ /**************************************************************************/ /* The node id */ /**************************************************************************/ /* node_id default value.*/ -UNS8 %(NodeName)s_bDeviceNodeId = 0x%(NodeID)02X; +UNS8 {NodeName}_bDeviceNodeId = 0x{NodeID:02X}; /**************************************************************************/ /* Array of message processing information */ -const UNS8 %(NodeName)s_iam_a_slave = %(iam_a_slave)d; +const UNS8 {NodeName}_iam_a_slave = {iam_a_slave}; -""" % texts - if texts["heartBeatTimers_number"] > 0: - declaration = "TIMER_HANDLE %(NodeName)s_heartBeatTimers[%(heartBeatTimers_number)d]" % texts - initializer = "{TIMER_NONE" + ",TIMER_NONE" * (texts["heartBeatTimers_number"] - 1) + "}" +""" + if ctx["heartBeatTimers_number"] > 0: + declaration = ctx.ftext( + "TIMER_HANDLE {NodeName}_heartBeatTimers[{heartBeatTimers_number}]" + ) + initializer = "{TIMER_NONE" + ",TIMER_NONE" * (ctx["heartBeatTimers_number"] - 1) + "}" fileContent += declaration + " = " + initializer + ";\n" else: - fileContent += "TIMER_HANDLE %(NodeName)s_heartBeatTimers[1];\n" % texts + fileContent += f"TIMER_HANDLE {ctx['NodeName']}_heartBeatTimers[1];\n" fileContent += """ /* @@ -551,77 +721,76 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): /**************************************************************************/ /* Declaration of pointed variables */ /**************************************************************************/ -""" + pointedVariableContent - - fileContent += """ -const indextable %(NodeName)s_objdict[] = -{ -""" % texts +""" + fileContent += pointedVariableContent + fileContent %= """ +const indextable {NodeName}_objdict[] = +{{ +""" fileContent += strDeclareIndex - fileContent += """}; + fileContent %= """}}; -const indextable * %(NodeName)s_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) -{ +const indextable * {NodeName}_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) +{{ int i; (void)d; /* unused parameter */ - switch(wIndex){ -""" % texts + switch(wIndex){{ +""" fileContent += strDeclareSwitch - fileContent += """ default: + fileContent %= """ default: *errorCode = OD_NO_SUCH_OBJECT; return NULL; - } + }} *errorCode = OD_SUCCESSFUL; - return &%(NodeName)s_objdict[i]; -} + return &{NodeName}_objdict[i]; +}} /* * To count at which received SYNC a PDO must be sent. * Even if no pdoTransmit are defined, at least one entry is computed * for compilations issues. */ -s_PDO_status %(NodeName)s_PDO_status[%(maxPDOtransmit)d] = {""" % texts - - fileContent += ",".join(["s_PDO_status_Initializer"] * texts["maxPDOtransmit"]) + """}; -""" +s_PDO_status {NodeName}_PDO_status[{maxPDOtransmit}] = {{""" + fileContent += ",".join(["s_PDO_status_Initializer"] * ctx["maxPDOtransmit"]) + "};\n" fileContent += strQuickIndex - fileContent += """ -const UNS16 %(NodeName)s_ObjdictSize = sizeof(%(NodeName)s_objdict)/sizeof(%(NodeName)s_objdict[0]); + fileContent %= """ +const UNS16 {NodeName}_ObjdictSize = sizeof({NodeName}_objdict)/sizeof({NodeName}_objdict[0]); -CO_Data %(NodeName)s_Data = CANOPEN_NODE_DATA_INITIALIZER(%(NodeName)s); +CO_Data {NodeName}_Data = CANOPEN_NODE_DATA_INITIALIZER({NodeName}); -""" % texts +""" -# ------------------------------------------------------------------------------ -# Write Header File Content -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Write Header File Content + # -------------------------------------------------------------------------- - texts["file_include_name"] = headerfilepath.replace(".", "_").upper() - headerFileContent = FILE_HEADER + """ -#ifndef %(file_include_name)s -#define %(file_include_name)s + ctx["file_include_name"] = headerfile.replace(".", "_").upper() + headerFileContent = ctx.text(FILE_HEADER) + headerFileContent %= """ +#ifndef {file_include_name} +#define {file_include_name} #include "data.h" /* Prototypes of function provided by object dictionnary */ -UNS32 %(NodeName)s_valueRangeTest (UNS8 typeValue, void * value); -const indextable * %(NodeName)s_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); +UNS32 {NodeName}_valueRangeTest (UNS8 typeValue, void * value); +const indextable * {NodeName}_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); /* Master node data struct */ -extern CO_Data %(NodeName)s_Data; -""" % texts +extern CO_Data {NodeName}_Data; +""" headerFileContent += strDeclareHeader + headerFileContent %= "\n#endif // {file_include_name}\n" - headerFileContent += "\n#endif // %(file_include_name)s\n" % texts - -# ------------------------------------------------------------------------------ -# Write Header Object Defintions Content -# ------------------------------------------------------------------------------ - texts["file_include_objdef_name"] = headerfilepath.replace(".", "_OBJECTDEFINES_").upper() - headerObjectDefinitionContent = FILE_HEADER + """ -#ifndef %(file_include_objdef_name)s -#define %(file_include_objdef_name)s + # -------------------------------------------------------------------------- + # Write Header Object Defintions Content + # -------------------------------------------------------------------------- + file_include_objdef_name = headerfile.replace(".", "_OBJECTDEFINES_").upper() + headerObjectDefinitionContent = ctx.text(FILE_HEADER) + headerObjectDefinitionContent += f""" +#ifndef {file_include_objdef_name} +#define {file_include_objdef_name} /* Object defines naming convention: @@ -631,31 +800,36 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): Index : Node object dictionary name +_+ index name +_+ Idx SubIndex : Node object dictionary name +_+ index name +_+ subIndex name +_+ sIdx */ -""" % texts + headerObjectDefinitionContent + """ -#endif /* %(file_include_objdef_name)s */ -""" % texts +""" + headerObjectDefinitionContent += headerObjDefinitionContent + headerObjectDefinitionContent += f""" +#endif /* {file_include_objdef_name} */ +""" - return fileContent, headerFileContent, headerObjectDefinitionContent + return str(fileContent), str(headerFileContent), str(headerObjectDefinitionContent) # ------------------------------------------------------------------------------ # Main Function # ------------------------------------------------------------------------------ -def GenerateFile(filepath, node, pointers_dict=None): - pointers_dict = pointers_dict or {} - filebase = os.path.splitext(filepath)[0] - headerfilepath = filebase + ".h" - content, header, header_defs = GenerateFileContent(node, os.path.basename(headerfilepath), pointers_dict) +def GenerateFile(filepath: TPath, node: "NodeProtocol", pointers_dict=None): + """Main function to generate the C file from a object dictionary node.""" + filepath = Path(filepath) + headerpath = filepath.with_suffix(".h") + headerdefspath = Path(headerpath.parent / (headerpath.stem + "_objectdefines.h")) + content, header, header_defs = generate_file_content( + node, headerpath.name, pointers_dict, + ) # Write main .c contents - with open(filepath, "wb") as f: - f.write(content.encode('utf-8')) + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) # Write header file - with open(headerfilepath, "wb") as f: - f.write(header.encode('utf-8')) + with open(headerpath, "w", encoding="utf-8") as f: + f.write(header) # Write object definitions header - with open(filebase + "_objectdefines.h", "wb") as f: - f.write(header_defs.encode('utf-8')) + with open(headerdefspath, "w", encoding="utf-8") as f: + f.write(header_defs) diff --git a/src/objdictgen/ui/networkedit.ico b/src/objdictgen/img/networkedit.ico similarity index 100% rename from src/objdictgen/ui/networkedit.ico rename to src/objdictgen/img/networkedit.ico diff --git a/src/objdictgen/ui/networkedit.png b/src/objdictgen/img/networkedit.png similarity index 100% rename from src/objdictgen/ui/networkedit.png rename to src/objdictgen/img/networkedit.png diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 7cbb0a2..4f1d955 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -1,4 +1,4 @@ -""" OD dict/json serialization and deserialization functions """ +"""OD dict/json serialization and deserialization functions.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # @@ -17,27 +17,39 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import copy import json import logging -import os import re from datetime import datetime +from typing import (TYPE_CHECKING, Any, Iterable, Mapping, Sequence, TypeVar, cast) -import deepdiff +import deepdiff # type: ignore[import] # Due to missing typing stubs for deepdiff +import deepdiff.model # type: ignore[import] # Due to missing typing stubs for deepdiff import jsonschema import objdictgen +# Accessed by node.py, so we need to import node as module to avoid circular references from objdictgen import maps -from objdictgen.maps import OD +from objdictgen import node as nodelib +from objdictgen.maps import OD, ODMapping, ODMappingList +from objdictgen.typing import (TDiffNodes, TIndexEntry, TODJson, TODObjJson, + TODObj, TODSubObj, TODSubObjJson, TODValue, TParamEntry, TPath, TProfileMenu) + +T = TypeVar('T') +M = TypeVar('M', bound=Mapping) + +if TYPE_CHECKING: + from objdictgen.node import Node log = logging.getLogger('objdictgen') -SCHEMA = None +SCHEMA: dict[str, Any]|None = None class ValidationError(Exception): - ''' Validation failure ''' + """ Validation failure """ # JSON Version history/formats @@ -55,6 +67,7 @@ class ValidationError(Exception): "name", "description", "type", "id", "profile", "default_string_size", "dictionary", ) +# Output order for the "dictionary" list in the JSON file JSON_DICTIONARY_ORDER = ( "index", "name", "__name", "repeat", "struct", "group", @@ -65,6 +78,7 @@ class ValidationError(Exception): "values", "dictionary", "params", "user", "profile", "ds302", "built-in", ) +# Output order of the "sub" list in the JSON file JSON_SUB_ORDER = ( "name", "__name", "type", "__type", "access", "pdo", @@ -73,11 +87,10 @@ class ValidationError(Exception): "default", "value", ) - -# ---------- # Reverse validation (mem -> dict): +# --------------------------------- -# Fields that must be present in the mapping (where the parameter is defined) +# Fields that must be present in the mapping (where the object is defined) # mapping[index] = { ..dict.. } FIELDS_MAPPING_MUST = {'name', 'struct', 'values'} FIELDS_MAPPING_OPT = {'need', 'incr', 'nbmax', 'size', 'default'} @@ -87,7 +100,7 @@ class ValidationError(Exception): FIELDS_MAPVALS_MUST = {'name', 'type', 'access', 'pdo'} FIELDS_MAPVALS_OPT = {'nbmin', 'nbmax', 'default'} -# Fields that must be present in parameter dictionary (user settings) +# Fields that must be present in object dictionary (user settings) # node.ParamDictionary[index] = { N: { ..dict..}, ..dict.. } FIELDS_PARAMS = {'comment', 'save', 'buffer_size'} FIELDS_PARAMS_PROMOTE = {'callback'} @@ -95,8 +108,8 @@ class ValidationError(Exception): # Fields representing the dictionary value FIELDS_VALUE = {'value'} -# --------- # Forward validation (dict -> mem) +# -------------------------------- # Fields contents of the top-most level, json = { ..dict.. } FIELDS_DATA_MUST = { @@ -119,14 +132,14 @@ class ValidationError(Exception): 'sub', } FIELDS_DICT_OPT = { - # R = omitted if repeat is True # noqa: E126 - 'group', # R, default 'user' # noqa: E131 + # R = omitted if repeat is True + 'group', # R, default 'user' 'each', # R, only when struct != *var - 'callback', # set if present # noqa: E262 - 'profile_callback', # R, set if present # noqa: E261 + 'callback', # set if present + 'profile_callback', # R, set if present 'unused', # default False 'mandatory', # R, set if present - 'repeat', # default False # noqa: E262 + 'repeat', # default False 'incr', # R, only when struct is "N"-type 'nbmax', # R, only when struct is "N"-type 'size', # R, only when index < 0x1000 @@ -152,24 +165,23 @@ class ValidationError(Exception): 'pdo': False, } +# Remove jsonc annotations +# Copied from https://github.com/NickolaiBeloguzov/jsonc-parser/blob/master/jsonc_parser/parser.py#L11-L39 +RE_JSONC = re.compile(r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)", re.MULTILINE | re.DOTALL) + -def remove_jasonc(text): - ''' Remove jsonc annotations ''' - # Copied from https://github.com/NickolaiBeloguzov/jsonc-parser/blob/master/jsonc_parser/parser.py#L11-L39 - def __re_sub(match): +def remove_jasonc(text: str) -> str: + """ Remove jsonc annotations """ + def _re_sub(match: re.Match[str]) -> str: if match.group(2) is not None: return "" return match.group(1) - return re.sub( - r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)", - __re_sub, - text, - flags=re.MULTILINE | re.DOTALL - ) + return RE_JSONC.sub(_re_sub, text,) -def exc_amend(exc, text): +# FIXME: Move to generic utils/funcs? +def exc_amend(exc: Exception, text: str) -> Exception: """ Helper to prefix text to an exception """ args = list(exc.args) if len(args) > 0: @@ -180,9 +192,17 @@ def exc_amend(exc, text): return exc -def str_to_number(string): - """ Convert string to a number, otherwise pass it through """ - if string is None or isinstance(string, (int, float)): +def str_to_int(string: str|int) -> int: + """ Convert string or int to int. Fail if not possible.""" + i = maybe_number(string) + if not isinstance(i, int): + raise ValueError(f"Expected integer, got '{string}'") + return i + + +def maybe_number(string: str|int) -> int|str: + """ Convert string to a number, otherwise pass it through as-is""" + if isinstance(string, int): return string s = string.strip() if s.startswith('0x') or s.startswith('-0x'): @@ -192,7 +212,7 @@ def str_to_number(string): return string -def copy_in_order(d, order): +def copy_in_order(d: M, order: Sequence[T]) -> M: """ Remake dict d with keys in order """ out = { k: d[k] @@ -204,32 +224,38 @@ def copy_in_order(d, order): for k, v in d.items() if k not in out }) - return out + return cast(M, out) # FIXME: For mypy -def remove_underscore(d): +def remove_underscore(d: T) -> T: """ Recursively remove any keys prefixed with '__' """ if isinstance(d, dict): - return { + return { # type: ignore[return-value] k: remove_underscore(v) for k, v in d.items() if not k.startswith('__') } if isinstance(d, list): - return [ + return [ # type: ignore[return-value] remove_underscore(v) for v in d ] return d -def member_compare(a, must=None, optional=None, not_want=None, msg='', only_if=None): - ''' Compare the membes of a with set of wants +def member_compare( + a: Iterable[str], *, + must: set[str]|None = None, + optional: set[str]|None = None, + not_want: set[str]|None = None, + msg: str = '', only_if : bool|None = None + ) -> None: + """ Compare the membes of a with set of wants must: Raise if a is missing any from must optional: Raise if a contains members that is not must or optional not_want: Raise error if any is present in a only_if: If False, raise error if must is present in a - ''' + """ have = set(a) if only_if is False: # is is important here @@ -241,65 +267,75 @@ def member_compare(a, must=None, optional=None, not_want=None, msg='', only_if=N unexpected = must - have if unexpected: unexp = "', '".join(unexpected) - raise ValidationError("Missing required parameters '{}'{}".format(unexp, msg)) + raise ValidationError(f"Missing required parameters '{unexp}'{msg}") # Check if there are any fields beyond the expected if optional: unexpected = have - ((must or set()) | optional) if unexpected: unexp = "', '".join(unexpected) - raise ValidationError("Unexpected parameters '{}'{}".format(unexp, msg)) + raise ValidationError(f"Unexpected parameters '{unexp}'{msg}") if not_want: unexpected = have & not_want if unexpected: unexp = "', '".join(unexpected) - raise ValidationError("Unexpected parameters '{}'{}".format(unexp, msg)) + raise ValidationError(f"Unexpected parameters '{unexp}'{msg}") -def get_object_types(node=None, dictionary=None): - ''' Return two dicts with the object type mapping ''' +def get_object_types( + node: "Node|None" = None, + dictionary: list[TODObjJson]|None = None +) -> tuple[dict[int, str], dict[str, int]]: + """ Return two dicts with the object type mapping """ - groups = [maps.MAPPING_DICTIONARY] + # Get the object mappings, either supplied or built-in if node: - groups += node.GetMappings() + mappinglist = node.GetMappings(withmapping=True) + else: + mappinglist = ODMappingList([maps.MAPPING_DICTIONARY]) + # Build a integer to string and string to integer mapping for object types # i2s: integer to string, s2i: string to integer - i2s, s2i = {}, {} - for group in groups: - for k, v in group.items(): - if k >= 0x1000: - continue - n = v['name'] - i2s[k] = n - s2i[n] = k + i2s: dict[int, str] = {} + s2i: dict[str, int] = {} + for k, v in mappinglist.find(lambda i, o: i < 0x1000): + n = v['name'] + i2s[k] = n + s2i[n] = k if len(i2s) != len(s2i): raise ValidationError("Multiple names or numbers for object types in OD") + # Get the name and index from the dictionary input # Must check everything, as this is used with unvalidated input for obj in dictionary or []: if not isinstance(obj, dict): continue - index = str_to_number(obj.get('index')) + index = str_to_int(obj['index']) name = obj.get('name') + # FIXME: Maybe this check is not needed here? if not isinstance(index, int) or not isinstance(name, str): continue if index >= 0x1000 or not name: continue if index in i2s: - raise ValidationError("Index {} ('{}') is already defined as a type with name '{}'".format(index, name, i2s[index])) + raise ValidationError(f"Index {index} ('{name}') is already defined as a type with name '{i2s[index]}'") if name in s2i: - raise ValidationError("Name '{}' in index {} is already defined in index {}".format(name, index, s2i[name])) + raise ValidationError(f"Name '{name}' in index {index} is already defined in index {s2i[name]}") i2s[index] = name s2i[name] = index return i2s, s2i -def compare_profile(profilename, params, menu=None): +def compare_profile(profilename: TPath, params: ODMapping, menu: TProfileMenu|None = None) -> tuple[bool, bool]: + """Compare a profile with a set of parameters and menu. Return tuple of + (loaded, identical) where loaded is True if the profile was loaded and + identical is True if the profile is identical with the givens params. + """ try: - dsmap, menumap = objdictgen.ImportProfile(profilename) + dsmap, menumap = maps.import_profile(profilename) identical = all( k in dsmap and k in params and dsmap[k] == params[k] for k in set(dsmap) | set(params) @@ -309,15 +345,12 @@ def compare_profile(profilename, params, menu=None): return True, identical except ValueError as exc: - log.debug("Loading profile failed: {}".format(exc)) - # FIXME: Is this an error? - # Test case test-profile.od -> test-profile.json without access to profile - log.warning("WARNING: %s", exc) + log.warning("WARNING: Loading profile '%s' failed: %s", profilename, exc) return False, False -def GenerateJson(node, compact=False, sort=False, internal=False, validate=True): - ''' Export a JSON string representation of the node ''' +def generate_jsonc(node: "Node", compact=False, sort=False, internal=False, validate=True) -> str: + """ Export a JSONC string representation of the node """ # Get the dict representation jd, objtypes_s2i = node_todict( @@ -341,15 +374,15 @@ def GenerateJson(node, compact=False, sort=False, internal=False, validate=True) ) # Annotate symbolic fields with comments of the value - def _index_repl(m): + def _index_repl(m: re.Match[str]) -> str: p = m.group(1) n = v = m.group(2) if p == 'index': - n = str_to_number(v) + n = str_to_int(v) if p == 'type': n = objtypes_s2i.get(v, v) if n != v: - return m.group(0) + ' // {}'.format(n) + return m.group(0) + f' // {n}' return m.group(0) out = re.sub( # As object entries r'"(index|type)": "([a-zA-Z0-9_]+)",?$', @@ -367,39 +400,48 @@ def _index_repl(m): return out -def GenerateNode(contents): - ''' Import from JSON string or objects ''' +def generate_node(contents: str|TODJson) -> "Node": + """ Import from JSON string or objects """ - jd = contents if isinstance(contents, str): # Remove jsonc annotations jsontext = remove_jasonc(contents) # Load the json - jd = json.loads(jsontext) + jd: TODJson = json.loads(jsontext) # Remove any __ in the file jd = remove_underscore(jd) - # FIXME: Dilemma: Where to place this. It belongs here with JSON, but it - # would make sense to place it after running the built-in validator. - # Often the od validator is better at giving useful errors + else: + # Use provided object + jd = contents + + # FIXME: Dilemma: In what order to run validation? It would make sense to + # place it after running the built-in validator. Often + # validate_fromdict() is better at giving useful errors # than the json validator. However the type checking of the json # validator is better. global SCHEMA # pylint: disable=global-statement if not SCHEMA: - with open(os.path.join(objdictgen.JSON_SCHEMA), 'r') as f: + with open(objdictgen.JSON_SCHEMA, 'r', encoding="utf-8") as f: SCHEMA = json.loads(remove_jasonc(f.read())) - if SCHEMA: + if SCHEMA and jd.get('$version') == JSON_VERSION: jsonschema.validate(jd, schema=SCHEMA) - return node_fromdict(jd) + # Get the object type mappings forwards (int to str) and backwards (str to int) + objtypes_i2s, objtypes_s2i = get_object_types(dictionary=jd.get("dictionary", [])) + # Validate the input json against for the OD format specifics + validate_fromdict(jd, objtypes_i2s, objtypes_s2i) -def node_todict(node, sort=False, rich=True, internal=False, validate=True): - ''' + return node_fromdict(jd, objtypes_s2i) + + +def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=True) -> tuple[TODJson, dict[str, int]]: + """ Convert a node to dict representation for serialization. sort: Set if the output dictionary should be sorted before output. @@ -414,51 +456,28 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): low-level format debugging validate: Set if the output JSON should be validated to check if the output is valid. Used to double check format. - ''' - - # Get the dict representation of the node object - jd = node.GetDict() - - # Rename the top-level fields - for k, v in { - 'Name': 'name', - 'Description': 'description', - 'Type': 'type', - 'ID': 'id', - 'ProfileName': 'profile', - 'DefaultStringSize': 'default_string_size', - }.items(): - if k in jd: - jd[v] = jd.pop(k) - - # Insert meta-data - jd.update({ - '$id': JSON_ID, - '$version': JSON_INTERNAL_VERSION if internal else JSON_VERSION, - '$description': JSON_DESCRIPTION, - '$tool': str(objdictgen.ODG_PROGRAM) + ' ' + str(objdictgen.ODG_VERSION), - '$date': datetime.isoformat(datetime.now()), - }) - # Get the order for the indexes - order = node.GetAllParameters(sort=sort) + Returns a tuple with the JSON dict and the object type mapping + str to int. The latter is for convenience when importing the JSON and + can be used for display purposes. + """ # Get the object type mappings forwards (int to str) and backwards (str to int) objtypes_i2s, objtypes_s2i = get_object_types(node=node) - # Parse through all parameters - dictionary = [] - for index in order: - obj = None + # Parse through all parameters indexes + dictionary: list[TODObjJson] = [] + for index in node.GetAllIndices(sort=sort): try: - # Get the internal dict representation of the node parameter - obj = node.GetIndexDict(index) + obj: TODObjJson = {} - # Add in the index (as dictionary is a list) - obj["index"] = "0x{:04X}".format(index) if rich else index + # Get the internal dict representation of the object, termed "index entry" + ientry = node.GetIndexEntry(index) - # Don't wrangle further if the internal format is wanted + # Don't wrangle further if the internal format is wanted, just add it as-is if internal: + # FIXME: This works as long as GetIndexEntry() returns a dict + obj = cast(TODObjJson, ientry) continue # The internal memory model of Node is complex, this function exists @@ -466,87 +485,51 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): # to JSON format. This is mainly to ensure no wrong assumptions # produce unexpected output. if validate: - validate_nodeindex(node, index, obj) + validate_indexentry(ientry) - # Get the parameter for the index - obj = node_todict_parameter(obj, node, index) + # Convert the internal dict representation to generic dict structure + obj = indexentry_to_jsondict(ientry) # JSON format adoptions - # --------------------- - - # The struct describes what kind of object structure this object have - # See OD_* in node.py - struct = obj["struct"] - unused = obj.get("unused", False) - - info = [] - if not unused: - info = list(node.GetAllSubentryInfos(index)) - - # Rename the mandatory field - if "need" in obj: - obj["mandatory"] = obj.pop("need") - - # Replace numerical struct with symbolic value - if rich: - obj["struct"] = OD.to_string(struct, struct) - - if rich and "name" not in obj: - obj["__name"] = node.GetEntryName(index) - - # Iterater over the sub-indexes (if present) - for i, sub in enumerate(obj.get("sub", [])): - - # Add __name when rich format - if rich and info and "name" not in sub: - sub["__name"] = info[i]["name"] - - # Replace numeric type with string value - if rich and "type" in sub: - sub["type"] = objtypes_i2s.get(sub["type"], sub["type"]) - - # # Add __type when rich format - if rich and info and "type" not in sub: - sub["__type"] = objtypes_i2s.get(info[i]["type"], info[i]["type"]) - - if 'each' in obj: - sub = obj["each"] - - # Replace numeric type with string value - if rich and "type" in sub: - sub["type"] = objtypes_i2s.get(sub["type"], sub["type"]) - - # --------------------- - - # Rearrage order of 'sub' and 'each' - obj["sub"] = [ - copy_in_order(k, JSON_SUB_ORDER) - for k in obj["sub"] - ] - if 'each' in obj: - obj["each"] = copy_in_order(obj["each"], JSON_SUB_ORDER) + obj = rearrage_for_json(obj, node, objtypes_i2s, rich=rich) except Exception as exc: - exc_amend(exc, "Index 0x{0:04x} ({0}): ".format(index)) + exc_amend(exc, f"Index 0x{index:04x} ({index}): ") raise finally: + # Add in a fancyer index (do it here after index is finished being used) + if rich: + obj["index"] = f"0x{index:04X}" + dictionary.append(obj) - # Rearrange order of Dictionary[*] - jd["dictionary"] = [ - copy_in_order(k, JSON_DICTIONARY_ORDER) for k in dictionary - ] + # Make the json dict + jd: TODJson = copy_in_order({ + '$id': JSON_ID, + '$version': JSON_INTERNAL_VERSION if internal else JSON_VERSION, + '$description': JSON_DESCRIPTION, + '$tool': str(objdictgen.ODG_PROGRAM) + ' ' + str(objdictgen.__version__), + '$date': datetime.isoformat(datetime.now()), + 'name': node.Name, + 'description': node.Description, + 'type': node.Type, + 'id': node.ID, + 'profile': node.ProfileName, + 'default_string_size': node.DefaultStringSize, + 'dictionary': [ + copy_in_order(k, JSON_DICTIONARY_ORDER) + for k in dictionary + ], + }, JSON_TOP_ORDER) # type: ignore[assignment] + + # FIXME: Somewhat a hack, find better way to optionally include this + if 'DefaultStringSize' not in node.__dict__: + jd.pop('default_string_size') # type: ignore[misc] # Rearrange the order of the top-level dict jd = copy_in_order(jd, JSON_TOP_ORDER) - # Cleanup of unwanted members - # - NOTE: SpecificMenu is not used in dict representation - for k in ('Dictionary', 'ParamsDictionary', 'Profile', 'SpecificMenu', - 'DS302', 'UserMapping', 'IndexOrder'): - jd.pop(k, None) - # Cross check verification to see if we later can import the generated dict if validate and not internal: validate_fromdict(remove_underscore(jd), objtypes_i2s, objtypes_s2i) @@ -554,10 +537,13 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): return jd, objtypes_s2i -def node_todict_parameter(obj, node, index): - ''' Modify obj from internal dict representation to generic dict structure +def indexentry_to_jsondict(ientry: TIndexEntry) -> TODObjJson: + """ Modify obj from internal dict representation to generic dict structure which is suitable for serialization into JSON. - ''' + """ + + # Ensure the incoming object is not mutated + ientry = copy.deepcopy(ientry) # Observations: # ============= @@ -580,72 +566,86 @@ def node_todict_parameter(obj, node, index): # - NVAR with empty dictionary value is not possible # -- STEP 1) -- - # Blend the mapping type (baseobj) with obj + # Blend the mapping type (odobj) with obj # Get group membership (what object type it is) and if the prarmeter is repeated - groups = obj.pop('groups') - is_repeat = obj.pop('base', index) != index + index = ientry["index"] + + # New output object + obj: TODObjJson = { + "index": index, + } + odobj: TODObj # The OD object (set below) - # Is the definition for the parameter present? - if not is_repeat: + # Is the object not a repeat object (where base is the same)? + if ientry.get("base", index) == index: - # Extract mapping baseobject that contains the object definitions. Checked in A - group = groups[0] - if group != 'user': - obj['group'] = group + # The validator have checked that only one group is present + # Note the key rename + obj['group'] = ientry["groups"][0] - baseobj = obj.pop(group) - struct = baseobj["struct"] # Checked in B + # Get the object itself + odobj = ientry['object'] + struct = odobj["struct"] else: + # Mark the object a repeated object obj["repeat"] = True - info = node.GetEntryInfos(index) - baseobj = {} - struct = info['struct'] + + odobj = {} + struct = ientry["basestruct"] # Callback in mapping collides with the user set callback, so it is renamed - if 'callback' in baseobj: - obj['profile_callback'] = baseobj.pop('callback') + if 'callback' in odobj: + obj['profile_callback'] = odobj.pop('callback') - # Move members from baseobj to top-level object. Checked in B + # Move known members from odobj to top-level object. for k in FIELDS_MAPPING_MUST | FIELDS_MAPPING_OPT: - if k in baseobj: - obj[k] = baseobj.pop(k) + if k in odobj: + newk = k + if k == 'need': # Mutate the field name + newk = 'mandatory' + # FIXME: mypy: TypedDict doesn't work with k + obj[newk] = odobj.pop(k) # type: ignore[literal-required,misc] # Ensure fields exists obj['struct'] = struct - obj['sub'] = obj.pop('values', []) + obj['sub'] = obj.pop('values', []) # type: ignore[typeddict-item] # values is about to be renamed # Move subindex[1] to 'each' on objecs that contain 'nbmax' if len(obj['sub']) > 1 and 'nbmax' in obj['sub'][1]: - obj['each'] = obj['sub'].pop(1) + obj['each'] = obj['sub'].pop(1) # type: ignore[typeddict-item] # Baseobj should have been emptied - if baseobj != {}: - raise ValidationError("Mapping data not empty. Programming error?. Contains: {}".format(baseobj)) + if odobj != {}: + raise ValidationError(f"Mapping data not empty. Contains: {odobj}") # -- STEP 2) -- # Migrate 'params' and 'dictionary' to common 'sub' # Extract the params - has_params = 'params' in obj - has_dictionary = 'dictionary' in obj - params = obj.pop("params", {}) - dictvals = obj.pop("dictionary", []) + has_params = 'params' in ientry + has_dictionary = 'dictionary' in ientry + params = ientry.get("params", {}) + dictvals = ientry.get("dictionary", []) # These types places the params in the top-level dict if has_params and struct in (OD.VAR, OD.NVAR): - params = params.copy() # Important, as its mutated here + # FIXME: Here is would be nice to validate that 'params' is a TParamEntry param0 = {} for k in FIELDS_PARAMS: if k in params: - param0[k] = params.pop(k) - params[0] = param0 + param0[k] = params.pop(k) # type: ignore[misc,call-overload] + params[0] = param0 # type: ignore[literal-required,assignment,arg-type] # TypedDict doesn't work with 0 # Promote the global parameters from params into top-level object for k in FIELDS_PARAMS_PROMOTE: if k in params: - obj[k] = params.pop(k) + obj[k] = params.pop(k) # type: ignore[literal-required,misc,call-overload] # TypedDict doesn't work with k + + # FIXME: By now, params should contain only subindex parameters + if TYPE_CHECKING: + params = cast(dict[int, TParamEntry], params) # Extract the dictionary values # NOTE! It is important to capture that 'dictionary' exists is obj, even if @@ -653,133 +653,211 @@ def node_todict_parameter(obj, node, index): start = 0 if has_dictionary: if struct in (OD.VAR, OD.NVAR): + # FIXME: In this struct type it should never return a list + assert not isinstance(dictvals, list) + # Ensures dictvals is always a list dictvals = [dictvals] else: + # FIXME: In this struct type it should always return a list + assert isinstance(dictvals, list) start = 1 # Have "number of entries" first + # Write the dictionary into the ParameterEntry for i, v in enumerate(dictvals, start=start): - params.setdefault(i, {})['value'] = v + params.setdefault(i, {})['value'] = v # type: ignore[typeddict-unknown-key] else: - # This is now unused profile parameters are stored + # This is an unused object obj['unused'] = True # Commit the params to the 'sub' list if params: + # FIXME: This assumption should be true + assert isinstance(dictvals, list) + # Ensure there are enough items in 'sub' to hold the param items dictlen = start + len(dictvals) - sub = obj["sub"] + sub = obj["sub"] # Get the list of values, now sub if dictlen > len(sub): - sub += [{} for i in range(len(sub), dictlen)] + sub += [{} for i in range(len(sub), dictlen)] # type: ignore[typeddict-item] # Commit the params to 'sub' for i, val in enumerate(sub): - val.update(params.pop(i, {})) + val.update(params.pop(i, {})) # type: ignore[typeddict-item] # Params should have been emptied if params != {}: - raise ValidationError("User parameters not empty. Programming error? Contains: {}".format(params)) + raise ValidationError(f"User parameters not empty. Contains: {params}") + + return obj + + +def rearrage_for_json(obj: TODObjJson, node: "Node", objtypes_i2s: dict[int, str], rich=True) -> TODObjJson: + """ Rearrange the object to fit the wanted JSON format """ + + # The struct describes what kind of object structure this object have + # See OD_* in node.py + struct = obj["struct"] + index = obj["index"] + unused = obj.get("unused", False) + + # FIXME: In this context it should always be an integer + assert isinstance(index, int) + + # Replace numerical struct with symbolic value + if rich: + # FIXME: This gives mypy error because to_string() might return None + obj["struct"] = OD.to_string(struct, struct) # type: ignore[arg-type,typeddict-item] + + # Add duplicate name field which will be commented out + if rich and "name" not in obj: + obj["__name"] = node.GetEntryName(index) + + # Iterater over the sub-indexes (if present) + for i, sub in enumerate(obj.get("sub", [])): + + # Get the subentry info for rich format + info: TODSubObj = node.GetSubentryInfos(index, i) if rich and not unused else {} + + # Add __name when rich format + if info and "name" not in sub: + sub["__name"] = info["name"] + + # Replace numeric type with string value + if rich and "type" in sub: + # FIXME: The cast is to ensure mypy is able keep track + sub["type"] = objtypes_i2s.get(cast(int, sub["type"]), sub["type"]) + + # # Add __type when rich format + if info and "type" not in sub: + sub["__type"] = objtypes_i2s.get(info["type"], info["type"]) + + if 'each' in obj: + each = obj["each"] + + # Replace numeric type with string value + if rich and "type" in each: + # FIXME: The cast is to ensure mypy is able keep track + each["type"] = objtypes_i2s.get(cast(int, each["type"]), each["type"]) + + # Rearrage order of 'sub' and 'each' + obj["sub"] = [ + copy_in_order(k, JSON_SUB_ORDER) + for k in obj["sub"] + ] + if 'each' in obj: + obj["each"] = copy_in_order(obj["each"], JSON_SUB_ORDER) return obj -def validate_nodeindex(node, index, obj): +def validate_indexentry(ientry: TIndexEntry): """ Validate index dict contents (see Node.GetIndexDict). The purpose is to validate the assumptions in the data format. - NOTE: Do not implement two parallel validators. This function exists - to validate the data going into node_todict() in case the programmed - assumptions are wrong. For general data validation, see - validate_fromdict() + NOTE: This function exists to validate the node data in node_todict() + to verify that the programmed assumptions are not wrong. """ - groups = obj['groups'] - is_repeat = obj.get('base', index) != index + groups = ientry["groups"] + index = ientry["index"] - # Is the definition for the parameter present? - if not is_repeat: + # Is the definition for the object present? + if ientry.get("base", index) == index: # A) Ensure only one definition of the object group if len(groups) == 0: raise ValidationError("Missing mapping") if len(groups) != 1: - raise ValidationError("Contains uexpected number of definitions for the object") + raise ValidationError(f"Contains uexpected number of groups ({len(groups)}) for the object") # Extract the definition - group = groups[0] - baseobj = obj[group] + odobj = ientry["object"] # B) Check baseobj object members is present member_compare( - baseobj.keys(), - FIELDS_MAPPING_MUST, FIELDS_MAPPING_OPT | FIELDS_PARAMS_PROMOTE, + odobj.keys(), + must=FIELDS_MAPPING_MUST, optional=FIELDS_MAPPING_OPT | FIELDS_PARAMS_PROMOTE, msg=' in mapping object' ) - struct = baseobj['struct'] + struct = odobj['struct'] else: - # If this is a repeated paramter, this object should not contain any definitions + # If this is a repeated parameter, this object should not contain any definitions # A) Ensure no definition of the object group if len(groups) != 0: - raise ValidationError("Unexpected to find any groups ({}) in repeated object".format(", ".join(groups))) + t_gr = ", ".join(groups) + raise ValidationError(f"Unexpected to find any groups ({t_gr}) in repeated object") - info = node.GetEntryInfos(index) - baseobj = {} - struct = info["struct"] # Implicit + odobj = {} + struct = ientry["basestruct"] # Helpers is_var = struct in (OD.VAR, OD.NVAR) # Ensure obj does NOT contain any fields found in baseobj (sanity check really) member_compare( - obj.keys(), + ientry.keys(), not_want=FIELDS_MAPPING_MUST | FIELDS_MAPPING_OPT | FIELDS_PARAMS_PROMOTE, msg=' in object' ) # Check baseobj object members - for val in baseobj.get('values', []): + for val in odobj.get('values', []): member_compare( val.keys(), - FIELDS_MAPVALS_MUST, FIELDS_MAPVALS_OPT, + must=FIELDS_MAPVALS_MUST, optional=FIELDS_MAPVALS_OPT, msg=' in mapping values' ) # Collect some information - params = obj.get('params', {}) - dictvalues = obj.get('dictionary', []) + params = ientry.get('params', {}) + dictvalues = ientry.get('dictionary', []) dictlen = 0 # These types places the params in the top-level dict if params and is_var: - params = params.copy() # Important, as its mutated here - param0 = {} + # FIXME: Here it would be nice to validate that 'params' is a TParamEntry + if TYPE_CHECKING: + params = cast(TParamEntry, params) + + params = params.copy() # Important, as it is mutated below + + # Move all known paramtert fields to a separate dict indexed by 0 + param0: TParamEntry = {} for k in FIELDS_PARAMS: if k in params: - param0[k] = params.pop(k) - params[0] = param0 + param0[k] = params.pop(k) # type: ignore[literal-required,misc] + params[0] = param0 # type: ignore[typeddict-item,literal-required] # Verify type of dictionary - if 'dictionary' in obj: + if 'dictionary' in ientry: if is_var: + if isinstance(dictvalues, list): + raise ValidationError(f"Unexpected list type in dictionary '{dictvalues}'") dictlen = 1 # dictvalues = [dictvalues] else: if not isinstance(dictvalues, list): - raise ValidationError("Unexpected type in dictionary '{}'".format(dictvalues)) + raise ValidationError(f"Unexpected type in dictionary '{dictvalues}'") dictlen = len(dictvalues) + 1 # dictvalues = [None] + dictvalues # Which is a copy # Check numbered params - excessive = {} + excessive: dict[int, TParamEntry] = {} for param in params: # All int keys corresponds to a numbered index if isinstance(param, int): # Check that there are no unexpected fields in numbered param + + # FIXME: Need a separate type to get the type hinter to work + if TYPE_CHECKING: + params = cast(dict[int, TParamEntry], params) + member_compare(params[param].keys(), - {}, - FIELDS_PARAMS, + must=set(), + optional=FIELDS_PARAMS, not_want=FIELDS_PARAMS_PROMOTE | FIELDS_MAPVALS_MUST | FIELDS_MAPVALS_OPT, msg=' in params' ) @@ -789,18 +867,18 @@ def validate_nodeindex(node, index, obj): # Do we have too many params? if excessive: - raise ValidationError("Excessive params, or too few dictionary values: {}".format(excessive)) + raise ValidationError(f"Excessive params, or too few dictionary values: {excessive}") # Find all non-numbered params and check them against - promote = {k for k in params if not isinstance(k, int)} + promote: set[str] = {k for k in params if not isinstance(k, int)} if promote: member_compare(promote, optional=FIELDS_PARAMS_PROMOTE, msg=' in params') # Check that we got the number of values and nbmax we expect for the type - nbmax = ['nbmax' in v for v in baseobj.get('values', [])] + nbmax = ['nbmax' in v for v in odobj.get('values', [])] lenok, nbmaxok = False, False - if not baseobj: + if not odobj: # Bypass tests if no baseobj is present lenok, nbmaxok = True, True @@ -826,35 +904,21 @@ def validate_nodeindex(node, index, obj): if len(nbmax) > 1: lenok = True else: - raise ValidationError("Unknown struct '{}'".format(struct)) + raise ValidationError(f"Unknown struct '{struct}'") if not nbmaxok: - raise ValidationError("Unexpected 'nbmax' use in mapping values, used {} times".format(sum(nbmax))) + raise ValidationError(f"Unexpected 'nbmax' use in mapping values, used {sum(nbmax)} times") if not lenok: - raise ValidationError("Unexpexted count of subindexes in mapping object, found {}".format(len(nbmax))) - + raise ValidationError(f"Unexpexted count of subindexes in mapping object, found {len(nbmax)}") -def node_fromdict(jd, internal=False): - ''' Convert a dict jd into a Node ''' - # Remove all underscore keys from the file - jd = remove_underscore(jd) - assert isinstance(jd, dict) # For mypy - - # Get the object type mappings forwards (int to str) and backwards (str to int) - objtypes_i2s, objtypes_s2i = get_object_types(dictionary=jd.get("dictionary", [])) - - # Validate the input json against the schema - validate_fromdict(jd, objtypes_i2s, objtypes_s2i) - - # Create default values for optional components - jd.setdefault("id", 0) - jd.setdefault("profile", "None") +def node_fromdict(jd: TODJson, objtypes_s2i: dict[str, int]) -> "Node": + """ Convert a dict jd into a Node """ # Create the node and fill the most basic data - node = objdictgen.Node( - name=jd["name"], type=jd["type"], id=jd["id"], - description=jd["description"], profilename=jd["profile"], + node = nodelib.Node( + name=jd["name"], type=jd["type"], id=jd.get("id", 0), + description=jd["description"], profilename=jd.get("profile", "None"), ) # Restore optional values @@ -862,101 +926,127 @@ def node_fromdict(jd, internal=False): node.DefaultStringSize = jd["default_string_size"] # An import of a internal JSON file? - internal = internal or jd['$version'] == JSON_INTERNAL_VERSION + internal = jd['$version'] == JSON_INTERNAL_VERSION # Iterate over the items to convert them to Node object for obj in jd["dictionary"]: # Convert the index number (which might be "0x" string) - index = str_to_number(obj['index']) + index = str_to_int(obj['index']) obj["index"] = index - assert isinstance(index, int) # For mypy + + # There is a weakness to the Node implementation: There is no store + # of the order of the incoming parameters, instead the data is spread + # over many dicts, e.g. Profile, DS302, UserMapping, Dictionary, + # ParamsDictionary. Node.IndexOrder has been added to store the order + # of the parameters. + node.IndexOrder.append(index) try: if not internal: - # Mutate obj containing the generic dict to the internal node format - obj = node_fromdict_parameter(obj, objtypes_s2i) + # Mutate obj containing the generic dict to the TIndexEntry + ientry = rearrange_for_node(obj, objtypes_s2i) + + else: + # FIXME: Cast this to mutate the object type + ientry = cast(TIndexEntry, obj) except Exception as exc: - exc_amend(exc, "Index 0x{0:04x} ({0}): ".format(index)) + exc_amend(exc, f"Index 0x{index:04x} ({index}): ") raise # Copy the object to node object entries - if 'dictionary' in obj: - node.Dictionary[index] = obj['dictionary'] - if 'params' in obj: - node.ParamsDictionary[index] = {str_to_number(k): v for k, v in obj['params'].items()} - if 'profile' in obj: - node.Profile[index] = obj['profile'] - if 'ds302' in obj: - node.DS302[index] = obj['ds302'] - if 'user' in obj: - node.UserMapping[index] = obj['user'] - - # Verify against built-in data (don't verify repeated params) - if 'built-in' in obj and not obj.get('repeat', False): - baseobj = maps.MAPPING_DICTIONARY.get(index) - - diff = deepdiff.DeepDiff(baseobj, obj['built-in'], view='tree') + if 'dictionary' in ientry: + node.Dictionary[index] = ientry['dictionary'] + if 'params' in ientry: + node.ParamsDictionary[index] = { # pyright: ignore[reportArgumentType] + maybe_number(k): v # type: ignore[misc] + for k, v in ientry['params'].items() + } + + groups: list[str] = ientry.get('groups', ['user']) + + # Do not restore mapping object on repeated objects + if 'repeat' in groups: + continue + elif 'profile' in groups: + node.Profile[index] = ientry['object'] + elif 'ds302' in groups: + node.DS302[index] = ientry['object'] + elif 'user' in groups: + node.UserMapping[index] = ientry['object'] + + # Verify against built-in data + elif 'built-in' in groups: + refobj = maps.MAPPING_DICTIONARY.get(index) + + diff = deepdiff.DeepDiff(refobj, ientry['object'], view='tree') if diff: - log.debug("Index 0x{0:04x} ({0}) Difference between built-in object and imported:".format(index)) + log.debug("Index 0x%04x (%s) Difference between built-in object and imported:", index, index) for line in diff.pretty().splitlines(): - log.debug(' ' + line) - raise ValidationError("Built-in parameter index 0x{0:04x} ({0}) does not match against system parameters".format(index)) - - # There is a weakness to the Node implementation: There is no store - # of the order of the incoming parameters, instead the data is spread over - # many dicts, e.g. Profile, DS302, UserMapping, Dictionary, ParamsDictionary - # Node.IndexOrder has been added to store this information. - node.IndexOrder = [obj["index"] for obj in jd['dictionary']] + log.debug(' %s', line) + raise ValidationError( + f"Built-in object index 0x{index:04x} ({index}) " + "does not match against system parameters" + ) return node -def node_fromdict_parameter(obj, objtypes_s2i): +def rearrange_for_node(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TIndexEntry: + """ Convert a json OD object into an object adapted for load into a Node + object. + """ + + # This function is mutating obj, so we need to copy it + obj = copy.deepcopy(obj) - # -- STEP 1a) -- + # -- STEP 1) -- # Move 'definition' into individual mapping type category - baseobj = {} + ientry: TIndexEntry = {} + odobj: TODObj = {} + + # FIXME: We know by design this is already int + ientry["index"] = obj.pop("index") # type: ignore[typeddict-item] # Read "struct" (must) struct = obj["struct"] if not isinstance(struct, int): - struct = OD.from_string(struct) + # FIXME: The "or 0" can be removed when from_string() doesn't produce None + struct = OD.from_string(struct) or 0 obj["struct"] = struct # Write value back into object # Read "group" (optional, default 'user', omit if repeat is True - group = obj.pop("group", None) or 'user' + ientry["groups"] = [obj.pop("group", None) or 'user'] # Read "profile_callback" (optional) if 'profile_callback' in obj: - baseobj['callback'] = obj.pop('profile_callback') - - # Read "mandatory" (optional) into "need" - if 'mandatory' in obj: - obj['need'] = obj.pop('mandatory') + odobj['callback'] = obj.pop('profile_callback') # Restore the definition entries for k in FIELDS_MAPPING_MUST | FIELDS_MAPPING_OPT: - if k in obj: - baseobj[k] = obj.pop(k) + oldk = k + if k == "need": # Mutate the field name + oldk = "mandatory" + if oldk in obj: + odobj[k] = obj.pop(oldk) # type: ignore[literal-required,misc] # -- STEP 2) -- # Migrate 'sub' into 'params' and 'dictionary' # Restore the param entries that has been promoted to obj - params = {} + params: dict[int, TParamEntry] = {} for k in FIELDS_PARAMS_PROMOTE: if k in obj: - params[k] = obj.pop(k) + params[k] = obj.pop(k) # type: ignore[misc,index] # Restore the values and dictionary - subitems = obj.pop('sub') + subitems: list[TODSubObjJson] = obj.pop('sub') # Recreate the dictionary list - dictionary = [ - v.pop('value') + dictionary: list[TODValue] = [ + v.pop('value') # type: ignore[misc] for v in subitems if v and 'value' in v ] @@ -965,27 +1055,27 @@ def node_fromdict_parameter(obj, objtypes_s2i): if dictionary: # [N]VAR needs them as immediate values if struct in (OD.VAR, OD.NVAR): - dictionary = dictionary[0] - obj['dictionary'] = dictionary + dictionary = dictionary[0] # type: ignore[assignment] + ientry['dictionary'] = dictionary - # The "unused" field is used to indicate that the parameter has no + # The "unused" field is used to indicate that the object has no # dictionary value. Otherwise there must be an empty dictionary list # ==> "unused" is only read iff dictionary is empty - elif not obj.get('unused', False): + elif not obj.pop('unused', False): # NOTE: If struct in VAR and NVAR, it is not correct to set to [], but # the should be captured by the validator. - obj['dictionary'] = [] + ientry['dictionary'] = [] # Restore param dictionary for i, vals in enumerate(subitems): - pars = params.setdefault(i, {}) + paramentry = params.setdefault(i, {}) for k in FIELDS_PARAMS: if k in vals: - pars[k] = vals.pop(k) + paramentry[k] = vals.pop(k) # type: ignore[misc,literal-required] # Move entries from item 0 into the params object if 0 in params and struct in (OD.VAR, OD.NVAR): - params.update(params.pop(0)) + params.update(params.pop(0)) # type: ignore[arg-type] # Remove the empty params and values params = {k: v for k, v in params.items() if not isinstance(v, dict) or v} @@ -993,37 +1083,52 @@ def node_fromdict_parameter(obj, objtypes_s2i): # Commit params if there is any data if params: - obj['params'] = params + ientry['params'] = params - # -- STEP 1b) -- + # -- STEP 3) -- + # Rebuild the object # Move back the each object if 'each' in obj: - subitems.append(obj.pop('each')) + subitems.append(obj.pop('each')) # type: ignore[arg-type] + + # Check if the object is a repeat object + repeat = obj.pop('repeat', False) + if repeat: + ientry["groups"].append("repeat") # Restore optional items from subindex 0 - if not obj.get('repeat', False) and struct in (OD.ARRAY, OD.NARRAY, OD.RECORD, OD.NRECORD): + if not repeat and struct in (OD.ARRAY, OD.NARRAY, OD.RECORD, OD.NRECORD): index0 = subitems[0] for k, v in SUBINDEX0.items(): - index0.setdefault(k, v) + index0.setdefault(k, v) # type: ignore[misc] # Restore 'type' text encoding into value for sub in subitems: if 'type' in sub: - sub['type'] = objtypes_s2i.get(sub['type'], sub['type']) + # FIXME: Use case to help mypy + sub['type'] = objtypes_s2i.get(cast(str, sub['type']), sub['type']) # Restore values if subitems: - baseobj['values'] = subitems - obj[group] = baseobj + # FIXME: Remaining issue is to ensure the subitems object is correct + odobj['values'] = subitems + ientry["object"] = odobj - return obj + if obj: + raise ValidationError(f"Unexpected fields in object: {obj}") + # Params should have been emptied + if obj != {}: + raise ValidationError(f"JSON object not empty. Contains: {obj}") -def validate_fromdict(jsonobj, objtypes_i2s=None, objtypes_s2i=None): - ''' Validate that jsonobj is a properly formatted dictionary that may + return ientry + + +def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str], objtypes_s2i: dict[str, int]): + """ Validate that jsonobj is a properly formatted dictionary that may be imported to the internal OD-format - ''' + """ jd = jsonobj @@ -1047,20 +1152,22 @@ def validate_fromdict(jsonobj, objtypes_i2s=None, objtypes_s2i=None): # Validate "$id" (must) if jd.get('$id') != JSON_ID: - raise ValidationError("Unknown file format, expected '$id' to be '{}', found '{}'".format( - JSON_ID, jd.get('$id'))) + raise ValidationError( + f"Unknown file format, expected '$id' to be '{JSON_ID}', found '{jd.get('$id')}'" + ) # Validate "$version" (must) if jd.get('$version') not in (JSON_INTERNAL_VERSION, JSON_VERSION): - raise ValidationError("Unknown file version, expected '$version' to be '{}', found '{}'".format( - JSON_VERSION, jd.get('$version'))) + raise ValidationError( + f"Unknown file version, expected '$version' to be '{JSON_VERSION}', found '{jd.get('$version')}'" + ) # Don't validate the internal format any further if jd['$version'] == JSON_INTERNAL_VERSION: return # Verify that we have the expected members - member_compare(jsonobj.keys(), FIELDS_DATA_MUST, FIELDS_DATA_OPT) + member_compare(jsonobj.keys(), must=FIELDS_DATA_MUST, optional=FIELDS_DATA_OPT) def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False): @@ -1089,15 +1196,15 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False): member_compare(obj.keys(), not_want=FIELDS_VALUE) # Validate "nbmax" if parsing the "each" sub - member_compare(obj.keys(), {'nbmax'}, only_if=idx == -1) + member_compare(obj.keys(), must={'nbmax'}, only_if=idx == -1) - # Default parameter precense + # Default object presense defs = 'must' # Parameter definition (FIELDS_MAPVALS_*) params = 'opt' # User parameters (FIELDS_PARAMS) value = 'no' # User value (FIELDS_VALUE) # Set what parameters should be present, optional or not present - if idx == -1: # Checking "each" section. No parameter or value + if idx == -1: # Checking "each" section. No object or value params = 'no' elif is_repeat: # Object repeat = defined elsewhere. No definition needed. @@ -1135,7 +1242,7 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False): opts |= FIELDS_VALUE # Verify parameters - member_compare(obj.keys(), must, opts) + member_compare(obj.keys(), must=must, optional=opts) # Validate "name" if 'name' in obj and not obj['name']: @@ -1144,9 +1251,9 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False): # Validate "type" if 'type' in obj: if isinstance(obj['type'], str) and objtypes_s2i and obj['type'] not in objtypes_s2i: - raise ValidationError("Unknown object type '{}'".format(obj['type'])) + raise ValidationError(f"Unknown object type '{obj['type']}'") if isinstance(obj['type'], int) and objtypes_i2s and obj['type'] not in objtypes_i2s: - raise ValidationError("Unknown object type id {}".format(obj['type'])) + raise ValidationError(f"Unknown object type id {obj['type']}") def _validate_dictionary(index, obj): @@ -1173,30 +1280,33 @@ def _validate_dictionary(index, obj): # Validate all present fields if is_repeat: - member_compare(obj.keys(), FIELDS_DICT_REPEAT_MUST, FIELDS_DICT_REPEAT_OPT, - msg=' in dictionary') - + member_compare(obj.keys(), + must=FIELDS_DICT_REPEAT_MUST, optional=FIELDS_DICT_REPEAT_OPT, + msg=' in dictionary' + ) else: - member_compare(obj.keys(), FIELDS_DICT_MUST, FIELDS_DICT_OPT, - msg=' in dictionary') + member_compare(obj.keys(), + must=FIELDS_DICT_MUST, optional=FIELDS_DICT_OPT, + msg=' in dictionary' + ) # Validate "index" (must) if not isinstance(index, int): - raise ValidationError("Invalid dictionary index '{}'".format(obj['index'])) + raise ValidationError(f"Invalid dictionary index '{obj['index']}'") if index <= 0 or index > 0xFFFF: - raise ValidationError("Invalid dictionary index value '{}'".format(index)) + raise ValidationError(f"Invalid dictionary index value '{index}'") # Validate "struct" (must) struct = obj["struct"] if not isinstance(struct, int): struct = OD.from_string(struct) if struct not in OD.STRINGS: - raise ValidationError("Unknown struct value '{}'".format(obj['struct'])) + raise ValidationError(f"Unknown struct value '{obj['struct']}'") # Validate "group" (optional, default 'user', omit if repeat is True) group = obj.get("group", None) or 'user' if group and group not in GROUPS: - raise ValidationError("Unknown group value '{}'".format(group)) + raise ValidationError(f"Unknown group value '{group}'") # Validate "default" (optional) if 'default' in obj and index >= 0x1000: @@ -1208,7 +1318,7 @@ def _validate_dictionary(index, obj): # Validate that "nbmax" and "incr" is only present in right struct type need_nbmax = not is_repeat and struct in (OD.NVAR, OD.NARRAY, OD.NRECORD) - member_compare(obj.keys(), {'nbmax', 'incr'}, only_if=need_nbmax) + member_compare(obj.keys(), must={'nbmax', 'incr'}, only_if=need_nbmax) subitems = obj['sub'] if not isinstance(subitems, list): @@ -1223,7 +1333,7 @@ def _validate_dictionary(index, obj): is_var = struct in (OD.VAR, OD.NVAR) _validate_sub(sub, idx, is_var=is_var, is_repeat=is_repeat, is_each='each' in obj) except Exception as exc: - exc_amend(exc, "sub[{}]: ".format(idx)) + exc_amend(exc, f"sub[{idx}]: ") raise # Validate "each" (optional, omit if repeat is True) @@ -1247,7 +1357,7 @@ def _validate_dictionary(index, obj): # NOTE: Not all seems to be the same. E.g. default is 'access'='ro', # however in 0x1600, 'access'='rw'. # if not all(subitems[0].get(k, v) == v for k, v in SUBINDEX0.items()): - # raise ValidationError("Incorrect definition in subindex 0. Found {}, expects {}".format(subitems[0], SUBINDEX0)) + # raise ValidationError(f"Incorrect definition in subindex 0. Found {subitems[0]}, expects {SUBINDEX0}") elif not is_repeat: if struct in (OD.ARRAY, OD.NARRAY): @@ -1256,7 +1366,7 @@ def _validate_dictionary(index, obj): # Validate "unused" (optional) unused = obj.get('unused', False) if unused and sum(has_value): - raise ValidationError("There are {} values in subitems, but 'unused' is true".format(sum(has_value))) + raise ValidationError(f"There are {sum(has_value)} values in subitems, but 'unused' is true") if not unused and not sum(has_value) and struct in (OD.VAR, OD.NVAR): raise ValidationError("VAR/NVAR cannot have 'unused' false") @@ -1278,7 +1388,7 @@ def _validate_dictionary(index, obj): if struct in (OD.RECORD, OD.NRECORD): if not is_repeat and 'each' not in obj: if sum(has_name) != len(has_name): - raise ValidationError("Not all subitems have name, {} of {}".format(sum(has_name), len(has_name))) + raise ValidationError(f"Not all subitems have name, {sum(has_name)} of {len(has_name)}") # Validate "dictionary" (must) if not isinstance(jd['dictionary'], list): @@ -1286,36 +1396,44 @@ def _validate_dictionary(index, obj): for num, obj in enumerate(jd['dictionary']): if not isinstance(obj, dict): - raise ValidationError("Item number {} of 'dictionary' is not a dict".format(num)) + raise ValidationError(f"Item number {num} of 'dictionary' is not a dict") - sindex = obj.get('index', 'item {}'.format(num)) - index = str_to_number(sindex) + sindex = obj.get('index', f'item {num}') + index = str_to_int(sindex) try: _validate_dictionary(index, obj) except Exception as exc: - exc_amend(exc, "Index 0x{0:04x} ({0}): ".format(index)) + exc_amend(exc, f"Index 0x{index:04x} ({index}): ") raise -def diff_nodes(node1, node2, as_dict=True, validate=True): +def diff_nodes(node1: "Node", node2: "Node", asdict=True, validate=True) -> TDiffNodes: + """Compare two nodes and return the differences.""" - diffs = {} + diffs: dict[int|str, list] = {} - if as_dict: + if asdict: jd1, _ = node_todict(node1, sort=True, validate=validate) jd2, _ = node_todict(node2, sort=True, validate=validate) dt = datetime.isoformat(datetime.now()) jd1['$date'] = jd2['$date'] = dt + # DeepDiff does not have typing, but the diff object is a dict-like object + # DeepDiff[str, deepdiff.model.PrettyOrderedSet]. + # PrettyOrderedSet is a list-like object + # PrettyOrderedSet[deepdiff.model.DiffLevel] + diff = deepdiff.DeepDiff(jd1, jd2, exclude_paths=[ "root['dictionary']" ], view='tree') + chtype: str for chtype, changes in diff.items(): + change: deepdiff.model.DiffLevel for change in changes: - path = change.path() + path: str = change.path(force='fake') # pyright: ignore[reportAssignmentType] entries = diffs.setdefault('', []) entries.append((chtype, change, path.replace('root', ''))) @@ -1325,10 +1443,10 @@ def diff_nodes(node1, node2, as_dict=True, validate=True): for chtype, changes in diff.items(): for change in changes: - path = change.path() + path = change.path(force='fake') # pyright: ignore[reportAssignmentType] m = res.search(path) if m: - num = str_to_number(m.group(1).strip("'")) + num = str_to_int(m.group(1).strip("'")) entries = diffs.setdefault(num, []) entries.append((chtype, change, path.replace(m.group(0), ''))) else: @@ -1336,7 +1454,7 @@ def diff_nodes(node1, node2, as_dict=True, validate=True): entries.append((chtype, change, path.replace('root', ''))) else: - diff = deepdiff.DeepDiff(node1, node2, exclude_paths=[ + diff = deepdiff.DeepDiff(node1.__dict__, node2.__dict__, exclude_paths=[ "root.IndexOrder" ], view='tree') @@ -1344,7 +1462,7 @@ def diff_nodes(node1, node2, as_dict=True, validate=True): for chtype, changes in diff.items(): for change in changes: - path = change.path() + path = change.path(force='fake') # pyright: ignore[reportAssignmentType] m = res.search(path) if m: entries = diffs.setdefault(int(m.group(2)), []) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 60039f9..14c79da 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -1,4 +1,4 @@ -""" Object mappings """ +"""Object mappings.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -18,32 +18,63 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import ast +import itertools +import logging +import re +import traceback +from collections import UserDict, UserList +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable, Generator, TypeVar + +import objdictgen +from objdictgen.typing import (TODObj, TODSubObj, TODValue, TParamEntry, TPath, + TProfileMenu) + +T = TypeVar('T') + +if TYPE_CHECKING: + from objdictgen.node import Node + +log = logging.getLogger('objdictgen') + + # # Dictionary of translation between access symbol and their signification # ACCESS_TYPE = {"ro": "Read Only", "wo": "Write Only", "rw": "Read/Write"} BOOL_TYPE = {True: "True", False: "False"} OPTION_TYPE = {True: "Yes", False: "No"} -CUSTOMISABLE_TYPES = [ + +# The first value is the type of the object, the second is the type of the subobject +# 0 indicates a numerical value, 1 indicates a non-numerical value such as strings +CUSTOMISABLE_TYPES: list[tuple[int, int]] = [ (0x02, 0), (0x03, 0), (0x04, 0), (0x05, 0), (0x06, 0), (0x07, 0), (0x08, 0), (0x09, 1), (0x0A, 1), (0x0B, 1), (0x10, 0), (0x11, 0), (0x12, 0), (0x13, 0), (0x14, 0), (0x15, 0), (0x16, 0), (0x18, 0), (0x19, 0), (0x1A, 0), (0x1B, 0), ] -DEFAULT_PARAMS = {"comment": None, "save": False, "buffer_size": None} +# FIXME: Using None is not to the type of these fields. Consider changing this +DEFAULT_PARAMS: TParamEntry = {"comment": None, "save": False, "buffer_size": None} # ------------------------------------------------------------------------------ -# Dictionary Mapping and Organisation +# Object Types and Organisation # ------------------------------------------------------------------------------ class ODStructTypes: + """Object Dictionary Structure Types""" # # Properties of entry structure in the Object Dictionary # - Subindex = 1 # Entry has at least one subindex - MultipleSubindexes = 2 # Entry has more than one subindex - IdenticalSubindexes = 4 # Subindexes of entry have the same description - IdenticalIndexes = 8 # Entry has the same description on multiple indexes + Subindex = 1 + """Entry has at least one subindex""" + MultipleSubindexes = 2 + """Entry has more than one subindex""" + IdenticalSubindexes = 4 + """Subindexes of entry have the same description""" + IdenticalIndexes = 8 + """Entry has the same description on multiple objects""" # # Structures of entry in the Object Dictionary, sum of the properties described above @@ -51,17 +82,23 @@ class ODStructTypes: # NOSUB = 0 # Entry without subindex (only for type declaration) VAR = Subindex # 1 + """Variable object structure""" RECORD = Subindex | MultipleSubindexes # 3 + """Record object structure, i.e. subindexes with different descriptions""" ARRAY = Subindex | MultipleSubindexes | IdenticalSubindexes # 7 + """Array object structure, i.e. subindexes with the same type""" # Entries identical on multiple indexes NVAR = Subindex | IdenticalIndexes # 9 + """Variable object structure that spans several objects""" NRECORD = Subindex | MultipleSubindexes | IdenticalIndexes # 11, Example : PDO Parameters + """Record object structure that spans several objects""" NARRAY = Subindex | MultipleSubindexes | IdenticalSubindexes | IdenticalIndexes # 15, Example : PDO Mapping + """Array object structure that spans several objects""" # # Mapping against name and structure number # - STRINGS = { + STRINGS: dict[int, str|None] = { NOSUB: None, VAR: "var", RECORD: "record", @@ -70,15 +107,17 @@ class ODStructTypes: NRECORD: "nrecord", NARRAY: "narray", } + # FIXME: Having None here should be avoided. Look into setting this to + # an empty string instead. It will simplify the typing for to_string and from_string @classmethod - def to_string(cls, val, default=''): - # type: (type[ODStructTypes], int, str) -> str + def to_string(cls: type["ODStructTypes"], val: int, default: str = '') -> str|None: + """Return the string representation of the structure value.""" return cls.STRINGS.get(val, default) @classmethod - def from_string(cls, val, default=None): - # type: (type[ODStructTypes], str, int|None) -> int|None + def from_string(cls: type["ODStructTypes"], val: str, default: int|None = None) -> int|None: + """Return the structure value from the string representation.""" try: return next(k for k, v in cls.STRINGS.items() if v == val) except StopIteration: @@ -88,22 +127,520 @@ def from_string(cls, val, default=None): # Convenience shortcut OD = ODStructTypes + +@dataclass +class IndexRange: + """Object dictionary range classes.""" + min: int + max: int + name: str + description: str + + +class IndexRanges(UserList[IndexRange]): + """List of index ranges.""" + + def get_index_range(self, index: int) -> IndexRange: + """Return the index range for the given index""" + for irange in self: + if irange.min <= index <= irange.max: + return irange + raise ValueError(f"Cannot find index range for value '0x{index:x}'") + + # # List of the Object Dictionary ranges # -INDEX_RANGES = [ - {"min": 0x0001, "max": 0x0FFF, "name": "dtd", "description": "Data Type Definitions"}, - {"min": 0x1000, "max": 0x1029, "name": "cp", "description": "Communication Parameters"}, - {"min": 0x1200, "max": 0x12FF, "name": "sdop", "description": "SDO Parameters"}, - {"min": 0x1400, "max": 0x15FF, "name": "rpdop", "description": "Receive PDO Parameters"}, - {"min": 0x1600, "max": 0x17FF, "name": "rpdom", "description": "Receive PDO Mapping"}, - {"min": 0x1800, "max": 0x19FF, "name": "tpdop", "description": "Transmit PDO Parameters"}, - {"min": 0x1A00, "max": 0x1BFF, "name": "tpdom", "description": "Transmit PDO Mapping"}, - {"min": 0x1C00, "max": 0x1FFF, "name": "ocp", "description": "Other Communication Parameters"}, - {"min": 0x2000, "max": 0x5FFF, "name": "ms", "description": "Manufacturer Specific"}, - {"min": 0x6000, "max": 0x9FFF, "name": "sdp", "description": "Standardized Device Profile"}, - {"min": 0xA000, "max": 0xBFFF, "name": "sip", "description": "Standardized Interface Profile"}, -] +INDEX_RANGES = IndexRanges([ + IndexRange(min=0x0001, max=0x0FFF, name="dtd", description="Data Type Definitions"), + IndexRange(min=0x1000, max=0x1029, name="cp", description="Communication Parameters"), + IndexRange(min=0x1200, max=0x12FF, name="sdop", description="SDO Parameters"), + IndexRange(min=0x1400, max=0x15FF, name="rpdop", description="Receive PDO Parameters"), + IndexRange(min=0x1600, max=0x17FF, name="rpdom", description="Receive PDO Mapping"), + IndexRange(min=0x1800, max=0x19FF, name="tpdop", description="Transmit PDO Parameters"), + IndexRange(min=0x1A00, max=0x1BFF, name="tpdom", description="Transmit PDO Mapping"), + IndexRange(min=0x1C00, max=0x1FFF, name="ocp", description="Other Communication Parameters"), + IndexRange(min=0x2000, max=0x5FFF, name="ms", description="Manufacturer Specific"), + IndexRange(min=0x6000, max=0x9FFF, name="sdp", description="Standardized Device Profile"), + IndexRange(min=0xA000, max=0xBFFF, name="sip", description="Standardized Interface Profile"), +]) + + +# ------------------------------------------------------------------------------ +# Evaluation of values +# ------------------------------------------------------------------------------ + +# Used to match strings such as 'Additional Server SDO %d Parameter %d[(idx, sub)]' +# This example matches to two groups +# ['Additional Server SDO %d Parameter %d', 'idx, sub'] +RE_NAME_SYNTAX = re.compile(r'(.*)\[[(](.*)[)]\]') + +# Regular expression to match $NODEID in a string +RE_NODEID = re.compile(r'\$NODEID\b', re.IGNORECASE) + + +def eval_value(value: Any, base: int, nodeid: int, compute=True) -> Any: + """ + Evaluate the value. They can be strings that needs additional + parsing. Such as "'$NODEID+0x600'" and + "'{True:"$NODEID+0x%X00"%(base+2),False:0x80000000}[base<4]'". + """ + + # Non-string and strings that doens't contain $NODEID can return as-is + if not (isinstance(value, str) and RE_NODEID.search(value)): + return value + + # This will remove any surrouning quotes on strings ('"$NODEID+0x20"') + # and will resolve "{True:"$NODEID..." expressions. + value = evaluate_expression(value, + { # These are the vars that can be used within the string + 'base': base, + } + ) + + if compute and isinstance(value, str): + # Replace $NODEID with 'nodeid' so it can be evaluated. + value = RE_NODEID.sub("nodeid", value) + + # This will resolve '$NODEID' expressions + value = evaluate_expression(value, + { # These are the vars that can be used within the string + 'nodeid': nodeid, + } + ) + + return value + + +def eval_name(text: str, idx: int, sub: int) -> str: + """ + Format the text given with the index and subindex defined. + Used to parse dynamic values such as + "Additional Server SDO %d Parameter[(idx)]" + """ + result = RE_NAME_SYNTAX.match(text) + if not result: + return text + + # NOTE: Legacy Python2 format evaluations are baked + # into the OD and must be supported for legacy + return result[1] % evaluate_expression( + result[2].strip(), + { # These are the vars that can be used in the string + 'idx': idx, + 'sub': sub, + } + ) + + +def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None) -> int|float|complex|str|bool|tuple|dict: + """Parses a string expression and attempts to calculate the result + Supports: + - Binary operations: addition, subtraction, multiplication, modulo + - Comparisons: less than + - Subscripting: (i.e. "a[1]") + - Constants: int, float, complex, str, boolean + - Variable names: from the localvars dict + - Function calls: from the localvars dict + - Tuples: (i.e. "(1, 2, 3)") + - Dicts: (i.e. "{1: 2, 3: 4}") + Parameters: + expression (str): string to parse + localvars (dict): dictionary of local variables and functions to + access in the expression + """ + localvars = localvars or {} + + def _evnode(node: ast.AST|None): + """ + Recursively parses ast.Node objects to evaluate arithmatic expressions + """ + if isinstance(node, ast.BinOp): + if isinstance(node.op, ast.Add): + return _evnode(node.left) + _evnode(node.right) + if isinstance(node.op, ast.Sub): + return _evnode(node.left) - _evnode(node.right) + if isinstance(node.op, ast.Mult): + return _evnode(node.left) * _evnode(node.right) + if isinstance(node.op, ast.Mod): + return _evnode(node.left) % _evnode(node.right) + raise SyntaxError(f"Unsupported arithmetic operation {type(node.op)}") + if isinstance(node, ast.Compare): + if len(node.ops) != 1 or len(node.comparators) != 1: + raise SyntaxError("Chained comparisons not supported") + if isinstance(node.ops[0], ast.Lt): + return _evnode(node.left) < _evnode(node.comparators[0]) + raise SyntaxError(f"Unsupported comparison operation {type(node.ops[0])}") + if isinstance(node, ast.Subscript): + return _evnode(node.value)[_evnode(node.slice)] + if isinstance(node, ast.Constant): + if isinstance(node.value, int | float | complex | str): + return node.value + raise TypeError(f"Unsupported constant {node.value}") + if isinstance(node, ast.Name): + if node.id not in localvars: + raise NameError(f"Name '{node.id}' is not defined") + return localvars[node.id] + if isinstance(node, ast.Call): + return _evnode(node.func)( + *[_evnode(arg) for arg in node.args], + **{k.arg: _evnode(k.value) for k in node.keywords} + ) + if isinstance(node, ast.Tuple): + return tuple(_evnode(elt) for elt in node.elts) + if isinstance(node, ast.Dict): + return {_evnode(k): _evnode(v) for k, v in zip(node.keys, node.values)} + raise TypeError(f"Unsupported syntax of type {type(node)}") + + try: + tree = ast.parse(expression, mode="eval") + return _evnode(tree.body) + except Exception as exc: + raise type(exc)(f"{exc.args[0]} in parsing of expression '{expression}'" + ).with_traceback(exc.__traceback__) from None + + +# ------------------------------------------------------------------------------ +# Misc functions +# ------------------------------------------------------------------------------ + +def import_profile(profilename: TPath) -> tuple["ODMapping", TProfileMenu]: + + # Test if the profilename is a filepath which can be used directly. If not + # treat it as the name + # The UI use full filenames, while all other uses use profile names + profilepath = Path(profilename) + if not profilepath.exists(): + fname = f"{profilename}.prf" + + try: + profilepath = next( + base / fname + for base in objdictgen.PROFILE_DIRECTORIES + if (base / fname).exists() + ) + except StopIteration: + raise ValueError( + f"Unable to load profile '{profilename}': '{fname}': No such file or directory" + ) from None + + # Mapping and AddMenuEntries are expected to be defined by the execfile + # The profiles requires some vars to be set + # pylint: disable=unused-variable + try: + with open(profilepath, "r", encoding="utf-8") as f: + log.debug("EXECFILE %s", profilepath) + code = compile(f.read(), profilepath, 'exec') + exec(code, globals(), locals()) # FIXME: Using exec is unsafe + # pylint: disable=undefined-variable + return Mapping, AddMenuEntries # type: ignore[name-defined] # due to the exec() magic + except Exception as exc: # pylint: disable=broad-except + log.debug("EXECFILE FAILED: %s", exc) + log.debug(traceback.format_exc()) + raise ValueError(f"Loading profile '{profilepath}' failed: {exc}") from exc + + +def be_to_le(value): + """Convert Big Endian to Little Endian + + :param value: value expressed in Big Endian + :param size: number of bytes generated + :returns: a string containing the value converted + """ + + # FIXME: This function is used in assosciation with DCF files, but have + # not been able to figure out how that work. It is very likely that this + # function is not working properly after the py2 -> py3 conversion + raise NotImplementedError("be_to_le() may be broken in py3") + + # # FIXME: The function title is confusing as the input data type (str) is + # # different than the output (int) + # return int("".join([f"{ord(char):02X}" for char in reversed(value)]), 16) + + +def le_to_be(value, size): + """ + Convert Little Endian to Big Endian + :param value: value expressed in integer + :param size: number of bytes generated + :return: a string containing the value converted + """ + + # FIXME: This function is used in assosciation with DCF files, but have + # not been able to figure out how that work. It is very likely that this + # function is not working properly after the py2 -> py3 conversion due to + # the change of chr() behavior + raise NotImplementedError("le_to_be() is broken in py3") + + # # FIXME: The function title is confusing as the input data type (int) is + # # different than the output (str) + # data = ("%" + str(size * 2) + "." + str(size * 2) + "X") % value + # list_car = [data[i:i + 2] for i in range(0, len(data), 2)] + # list_car.reverse() + # return "".join([chr(int(car, 16)) for car in list_car]) + + +# ------------------------------------------------------------------------------ +# Objects and mapping +# ------------------------------------------------------------------------------ + +class ODMapping(UserDict[int, TODObj]): + """Object Dictionary Mapping.""" + + def FindBaseIndex(self, index: int) -> int: + """Return the index number of the base object for the given index. + Used with identical indexes.""" + if index in self: + return index + for idx, obj in self.find(lambda _, o: o["struct"] & OD.IdenticalIndexes): + nb_max = obj["nbmax"] + incr = obj["incr"] + if idx < index < idx + incr * nb_max and (index - idx) % incr == 0: + return idx + raise ValueError(f"Index 0x{index:04x} not found in mapping dictionary") + + def FindBaseIndexNumber(self, index: int) -> int: + """Return the index increment number from the base object""" + base_index = self.FindBaseIndex(index) + return (index - base_index) // self[base_index].get("incr", 1) + + def FindTypeIndex(self, typename: str) -> int: + """Return the object index of the given typename""" + for idx, _ in self.find(lambda i, o: i < 0x1000 and o["name"] == typename): + return idx + raise ValueError(f"Type '{typename}' not found in mapping dictionary") + + def FindTypeName(self, typeindex: int) -> str: + """Return the name of the type object index""" + if typeindex < 0x1000 and typeindex in self: + return self[typeindex]["name"] + raise ValueError(f"Type 0x{typeindex:04x} not found in mapping dictionary") + + def FindTypeDefaultValue(self, typeindex: int) -> TODValue: + """Return the default value of the given type index""" + if typeindex < 0x1000 and typeindex in self: + return self[typeindex]["default"] + raise ValueError(f"Type 0x{typeindex:04x} not found in mapping dictionary") + + def FindTypeList(self) -> list[str]: + """Return a list of all object type names""" + return [ + self[index]["name"] + for index in self + if index < 0x1000 + ] + + def FindMandatoryIndexes(self) -> list[int]: + """Return a list of all mandatory objects""" + return [ + index + for index in self + if index >= 0x1000 and self[index].get("need") + ] + + def FindEntryName(self, index: int, compute=True) -> str: + """Return the name of an entry. Compute the name if needed.""" + base_index = self.FindBaseIndex(index) + infos = self[base_index] + if infos["struct"] & OD.IdenticalIndexes and compute: + return eval_name( + infos["name"], idx=(index - base_index) // infos["incr"] + 1, sub=0 + ) + return infos["name"] + + def FindEntryInfos(self, index: int, compute=True) -> TODObj: + """Return the informations of one entry""" + base_index = self.FindBaseIndex(index) + obj = self[base_index].copy() + if obj["struct"] & OD.IdenticalIndexes and compute: + obj["name"] = eval_name( + obj["name"], idx=(index - base_index) // obj["incr"] + 1, sub=0 + ) + obj.pop("values") + return obj + + def FindSubentryInfos(self, index: int, subindex: int, compute=True) -> TODSubObj: + """Return the informations of one subentry of an entry""" + base_index = self.FindBaseIndex(index) + struct = self[base_index]["struct"] + if struct & OD.Subindex: + infos: TODSubObj|None = None + if struct & OD.IdenticalSubindexes: + if subindex == 0: + infos = self[base_index]["values"][0] + elif 0 < subindex <= self[base_index]["values"][1]["nbmax"]: + infos = self[base_index]["values"][1] + elif struct & OD.MultipleSubindexes: + idx = 0 + for subindex_infos in self[base_index]["values"]: + if "nbmax" in subindex_infos: + if idx <= subindex < idx + subindex_infos["nbmax"]: + infos = subindex_infos + break + idx += subindex_infos["nbmax"] + else: + if subindex == idx: + infos = subindex_infos + break + idx += 1 + elif subindex == 0: + infos = self[base_index]["values"][0] + + if infos is None: + raise ValueError(f"Subindex {subindex} does not exist for index 0x{index:04x} or wrong object type") + infos = infos.copy() + + if struct & OD.IdenticalIndexes: + incr = self[base_index]["incr"] + else: + incr = 1 + infos["name"] = eval_name( + infos["name"], idx=(index - base_index) // incr + 1, sub=subindex + ) + return infos + raise ValueError(f"Index 0x{index:04x} does not have subentries") + + def FindMapVariableList(self, node: "Node", compute=True) -> Generator[tuple[int, int, int, str], None, None]: + """ + Generator of all variables that can be mapped to in pdos. + It yields tuple of (index, subindex, size, name) + """ + for index, entry in self.items(): + values = entry["values"] + for subindex, subvalue in enumerate(values): + if not subvalue["pdo"]: + continue + # Get the info for the type + typeinfos = node.GetEntryInfos(subvalue["type"]) + name = subvalue["name"] + if entry["struct"] & OD.IdenticalSubindexes: + value = node.GetEntry(index) + # FIXME: With this struct type, GetEntry should always return a list + assert isinstance(value, list) + for i in range(len(value) - 1): + computed_name = name + if compute: + computed_name = eval_name(computed_name, idx=1, sub=i + 1) + yield (index, i + 1, typeinfos["size"], computed_name) + else: + computed_name = name + if compute: + computed_name = eval_name(computed_name, idx=1, sub=subindex) + yield (index, subindex, typeinfos["size"], computed_name) + + # + # HELPERS + # + + def find(self, predicate: Callable[[int, TODObj], bool|int]) -> Generator[tuple[int, TODObj], None, None]: + """Return the first object that matches the function""" + for index, obj in self.items(): + if predicate(index, obj): + yield index, obj + + +class ODMappingList(UserList[ODMapping]): + """List of Object Dictionary Mappings.""" + + # + # DUCK TYPED METHODS (with ODMapping) + # + + def FindBaseIndex(self, index: int) -> int: + """Return the index number of the base object for the given index. + Used with identical indexes.""" + try: + return self.findfirst(lambda m: m.FindBaseIndex(index)) + except StopIteration: + raise ValueError(f"Index 0x{index:04x} not found in mapping dictionary") from None + + def FindBaseIndexNumber(self, index: int) -> int: + """Return the index increment number from the base object""" + try: + return self.findfirst(lambda m: m.FindBaseIndexNumber(index)) + except StopIteration: + raise ValueError(f"Index 0x{index:04x} not found in mapping dictionary") from None + + def FindTypeIndex(self, typename: str) -> int: + """Return the object index of the given typename""" + try: + return self.findfirst(lambda m: m.FindTypeIndex(typename)) + except StopIteration: + raise ValueError(f"Type '{typename}' not found in mapping dictionary") from None + + def FindTypeName(self, typeindex: int) -> str: + """Return the name of the type object index""" + try: + return self.findfirst(lambda m: m.FindTypeName(typeindex)) + except StopIteration: + raise ValueError(f"Type 0x{typeindex:04x} not found in mapping dictionary") from None + + def FindTypeDefaultValue(self, typeindex: int) -> TODValue: + """Return the default value of the given type index""" + try: + return self.findfirst(lambda m: m.FindTypeDefaultValue(typeindex)) + except StopIteration: + raise ValueError(f"Type 0x{typeindex:04x} not found in mapping dictionary") from None + + def FindTypeList(self) -> list[str]: + """Return a list of all object type names""" + return list(itertools.chain.from_iterable( + mapping.FindTypeList() for mapping in self + )) + + def FindMandatoryIndexes(self) -> list[int]: + """Return a list of all mandatory objects""" + return list(itertools.chain.from_iterable( + mapping.FindMandatoryIndexes() for mapping in self + )) + + def FindEntryName(self, index: int, compute=True) -> str: + """Return the name of an entry. Compute the name if needed.""" + try: + return self.findfirst(lambda m: m.FindEntryName(index, compute)) + except StopIteration: + raise ValueError(f"Index 0x{index:04x} not found in mapping dictionary") from None + + def FindEntryInfos(self, index: int, compute=True) -> TODObj: + """Return the name of an entry. Compute the name if needed.""" + try: + return self.findfirst(lambda m: m.FindEntryInfos(index, compute)) + except StopIteration: + raise ValueError(f"Index 0x{index:04x} not found in mapping dictionary") from None + + def FindSubentryInfos(self, index: int, subindex: int, compute=True) -> TODSubObj: + """Return the informations of one subentry of an entry""" + try: + return self.findfirst(lambda m: m.FindSubentryInfos(index, subindex, compute)) + except StopIteration: + raise ValueError(f"Subindex 0x{index:04x}.{subindex:x} does not exist") from None + + def FindMapVariableList(self, node: "Node", compute=True) -> Generator[tuple[int, int, int, str], None, None]: + """ + Generator of all variables that can be mapped to in pdos. + It yields tuple of (index, subindex, size, name) + """ + for mapping in self: + yield from mapping.FindMapVariableList(node, compute) + + # + # HELPERS + # + + def find(self, predicate: Callable[[int, TODObj], bool|int]) -> Generator[tuple[int, TODObj], None, None]: + """Generate the objects that matches the function""" + for mapping in self: + yield from mapping.find(predicate) + + def findfirst(self, fn: Callable[[ODMapping], T]) -> T: + """Execute a function on each mapping and return the first result""" + for mapping in self: + try: + return fn(mapping) + except ValueError: + continue + raise StopIteration() + # # MAPPING_DICTIONARY is the structure used for writing a good organised Object @@ -111,7 +648,8 @@ def from_string(cls, val, default=None): # Change the informations within it if there is a mistake. But don't modify the # organisation of this object, it will involve in a malfunction of the application. # -MAPPING_DICTIONARY = { +# FIXME: Move this to a separate json file +MAPPING_DICTIONARY = ODMapping({ # -- Static Data Types 0x0001: {"name": "BOOLEAN", "struct": OD.NOSUB, "size": 1, "default": False, "values": []}, 0x0002: {"name": "INTEGER8", "struct": OD.NOSUB, "size": 8, "default": 0, "values": []}, @@ -143,145 +681,145 @@ def from_string(cls, val, default=None): # 0x001C-0x001F: RESERVED # -- Communication Profile Area - 0x1000: {"name": "Device Type", "struct": OD.VAR, "need": True, "values": - [{"name": "Device Type", "type": 0x07, "access": 'ro', "pdo": False}]}, - 0x1001: {"name": "Error Register", "struct": OD.VAR, "need": True, "values": - [{"name": "Error Register", "type": 0x05, "access": 'ro', "pdo": True}]}, - 0x1002: {"name": "Manufacturer Status Register", "struct": OD.VAR, "need": False, "values": - [{"name": "Manufacturer Status Register", "type": 0x07, "access": 'ro', "pdo": True}]}, - 0x1003: {"name": "Pre-defined Error Field", "struct": OD.ARRAY, "need": False, "callback": True, "values": - [{"name": "Number of Errors", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Standard Error Field", "type": 0x07, "access": 'ro', "pdo": False, "nbmin": 1, "nbmax": 0xFE}]}, - 0x1005: {"name": "SYNC COB ID", "struct": OD.VAR, "need": False, "callback": True, "values": - [{"name": "SYNC COB ID", "type": 0x07, "access": 'rw', "pdo": False}]}, - 0x1006: {"name": "Communication / Cycle Period", "struct": OD.VAR, "need": False, "callback": True, "values": - [{"name": "Communication Cycle Period", "type": 0x07, "access": 'rw', "pdo": False}]}, - 0x1007: {"name": "Synchronous Window Length", "struct": OD.VAR, "need": False, "values": - [{"name": "Synchronous Window Length", "type": 0x07, "access": 'rw', "pdo": False}]}, - 0x1008: {"name": "Manufacturer Device Name", "struct": OD.VAR, "need": False, "values": - [{"name": "Manufacturer Device Name", "type": 0x09, "access": 'ro', "pdo": False}]}, - 0x1009: {"name": "Manufacturer Hardware Version", "struct": OD.VAR, "need": False, "values": - [{"name": "Manufacturer Hardware Version", "type": 0x09, "access": 'ro', "pdo": False}]}, - 0x100A: {"name": "Manufacturer Software Version", "struct": OD.VAR, "need": False, "values": - [{"name": "Manufacturer Software Version", "type": 0x09, "access": 'ro', "pdo": False}]}, - 0x100C: {"name": "Guard Time", "struct": OD.VAR, "need": False, "values": - [{"name": "Guard Time", "type": 0x06, "access": 'rw', "pdo": False}]}, - 0x100D: {"name": "Life Time Factor", "struct": OD.VAR, "need": False, "values": - [{"name": "Life Time Factor", "type": 0x05, "access": 'rw', "pdo": False}]}, - 0x1010: {"name": "Store parameters", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Save All Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Save Communication Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Save Application Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Save Manufacturer Parameters %d[(sub - 3)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmax": 0x7C}]}, - 0x1011: {"name": "Restore Default Parameters", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Restore All Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Restore Communication Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Restore Application Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Restore Manufacturer Defined Default Parameters %d[(sub - 3)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmax": 0x7C}]}, - 0x1012: {"name": "TIME COB ID", "struct": OD.VAR, "need": False, "values": - [{"name": "TIME COB ID", "type": 0x07, "access": 'rw', "pdo": False}]}, - 0x1013: {"name": "High Resolution Timestamp", "struct": OD.VAR, "need": False, "values": - [{"name": "High Resolution Time Stamp", "type": 0x07, "access": 'rw', "pdo": True}]}, - 0x1014: {"name": "Emergency COB ID", "struct": OD.VAR, "need": False, "values": - [{"name": "Emergency COB ID", "type": 0x07, "access": 'rw', "pdo": False, "default": '"$NODEID+0x80"'}]}, - 0x1015: {"name": "Inhibit Time Emergency", "struct": OD.VAR, "need": False, "values": - [{"name": "Inhibit Time Emergency", "type": 0x06, "access": 'rw', "pdo": False}]}, - 0x1016: {"name": "Consumer Heartbeat Time", "struct": OD.ARRAY, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Consumer Heartbeat Time", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 1, "nbmax": 0x7F}]}, - 0x1017: {"name": "Producer Heartbeat Time", "struct": OD.VAR, "need": False, "callback": True, "values": - [{"name": "Producer Heartbeat Time", "type": 0x06, "access": 'rw', "pdo": False}]}, - 0x1018: {"name": "Identity", "struct": OD.RECORD, "need": True, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Vendor ID", "type": 0x07, "access": 'ro', "pdo": False}, - {"name": "Product Code", "type": 0x07, "access": 'ro', "pdo": False}, - {"name": "Revision Number", "type": 0x07, "access": 'ro', "pdo": False}, - {"name": "Serial Number", "type": 0x07, "access": 'ro', "pdo": False}]}, - 0x1019: {"name": "Synchronous counter overflow value", "struct": OD.VAR, "need": False, "values": - [{"name": "Synchronous counter overflow value", "type": 0x05, "access": 'rw', "pdo": False}]}, - 0x1020: {"name": "Verify Configuration", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Configuration Date", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Configuration Time", "type": 0x07, "access": 'rw', "pdo": False}]}, - # 0x1021: {"name": "Store EDS", "struct": OD.VAR, "need": False, "values": - # [{"name": "Store EDS", "type": 0x0F, "access": 'rw', "pdo": False}]}, - # 0x1022: {"name": "Storage Format", "struct": OD.VAR, "need": False, "values": - # [{"name": "Storage Format", "type": 0x06, "access": 'rw', "pdo": False}]}, - 0x1023: {"name": "OS Command", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Command", "type": 0x0A, "access": 'rw', "pdo": False}, - {"name": "Status", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Reply", "type": 0x0A, "access": 'ro', "pdo": False}]}, - 0x1024: {"name": "OS Command Mode", "struct": OD.VAR, "need": False, "values": - [{"name": "OS Command Mode", "type": 0x05, "access": 'wo', "pdo": False}]}, - 0x1025: {"name": "OS Debugger Interface", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Command", "type": 0x0A, "access": 'rw', "pdo": False}, - {"name": "Status", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Reply", "type": 0x0A, "access": 'ro', "pdo": False}]}, - 0x1026: {"name": "OS Prompt", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "StdIn", "type": 0x05, "access": 'wo', "pdo": True}, - {"name": "StdOut", "type": 0x05, "access": 'ro', "pdo": True}, - {"name": "StdErr", "type": 0x05, "access": 'ro', "pdo": True}]}, - 0x1027: {"name": "Module List", "struct": OD.ARRAY, "need": False, "values": - [{"name": "Number of Connected Modules", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Module %d[(sub)]", "type": 0x06, "access": 'ro', "pdo": False, "nbmin": 1, "nbmax": 0xFE}]}, - 0x1028: {"name": "Emergency Consumer", "struct": OD.ARRAY, "need": False, "values": - [{"name": "Number of Consumed Emergency Objects", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Emergency Consumer", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 1, "nbmax": 0x7F}]}, - 0x1029: {"name": "Error Behavior", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Error Classes", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "Communication Error", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Device Profile", "type": 0x05, "access": 'rw', "pdo": False, "nbmax": 0xFE}]}, + 0x1000: {"name": "Device Type", "struct": OD.VAR, "need": True, "values": [ + {"name": "Device Type", "type": 0x07, "access": 'ro', "pdo": False}]}, + 0x1001: {"name": "Error Register", "struct": OD.VAR, "need": True, "values": [ + {"name": "Error Register", "type": 0x05, "access": 'ro', "pdo": True}]}, + 0x1002: {"name": "Manufacturer Status Register", "struct": OD.VAR, "need": False, "values": [ + {"name": "Manufacturer Status Register", "type": 0x07, "access": 'ro', "pdo": True}]}, + 0x1003: {"name": "Pre-defined Error Field", "struct": OD.ARRAY, "need": False, "callback": True, "values": [ + {"name": "Number of Errors", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Standard Error Field", "type": 0x07, "access": 'ro', "pdo": False, "nbmin": 1, "nbmax": 0xFE}]}, + 0x1005: {"name": "SYNC COB ID", "struct": OD.VAR, "need": False, "callback": True, "values": [ + {"name": "SYNC COB ID", "type": 0x07, "access": 'rw', "pdo": False}]}, + 0x1006: {"name": "Communication / Cycle Period", "struct": OD.VAR, "need": False, "callback": True, "values": [ + {"name": "Communication Cycle Period", "type": 0x07, "access": 'rw', "pdo": False}]}, + 0x1007: {"name": "Synchronous Window Length", "struct": OD.VAR, "need": False, "values": [ + {"name": "Synchronous Window Length", "type": 0x07, "access": 'rw', "pdo": False}]}, + 0x1008: {"name": "Manufacturer Device Name", "struct": OD.VAR, "need": False, "values": [ + {"name": "Manufacturer Device Name", "type": 0x09, "access": 'ro', "pdo": False}]}, + 0x1009: {"name": "Manufacturer Hardware Version", "struct": OD.VAR, "need": False, "values": [ + {"name": "Manufacturer Hardware Version", "type": 0x09, "access": 'ro', "pdo": False}]}, + 0x100A: {"name": "Manufacturer Software Version", "struct": OD.VAR, "need": False, "values": [ + {"name": "Manufacturer Software Version", "type": 0x09, "access": 'ro', "pdo": False}]}, + 0x100C: {"name": "Guard Time", "struct": OD.VAR, "need": False, "values": [ + {"name": "Guard Time", "type": 0x06, "access": 'rw', "pdo": False}]}, + 0x100D: {"name": "Life Time Factor", "struct": OD.VAR, "need": False, "values": [ + {"name": "Life Time Factor", "type": 0x05, "access": 'rw', "pdo": False}]}, + 0x1010: {"name": "Store parameters", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Save All Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Save Communication Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Save Application Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Save Manufacturer Parameters %d[(sub - 3)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmax": 0x7C}]}, + 0x1011: {"name": "Restore Default Parameters", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Restore All Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Restore Communication Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Restore Application Default Parameters", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Restore Manufacturer Defined Default Parameters %d[(sub - 3)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmax": 0x7C}]}, + 0x1012: {"name": "TIME COB ID", "struct": OD.VAR, "need": False, "values": [ + {"name": "TIME COB ID", "type": 0x07, "access": 'rw', "pdo": False}]}, + 0x1013: {"name": "High Resolution Timestamp", "struct": OD.VAR, "need": False, "values": [ + {"name": "High Resolution Time Stamp", "type": 0x07, "access": 'rw', "pdo": True}]}, + 0x1014: {"name": "Emergency COB ID", "struct": OD.VAR, "need": False, "values": [ + {"name": "Emergency COB ID", "type": 0x07, "access": 'rw', "pdo": False, "default": '"$NODEID+0x80"'}]}, + 0x1015: {"name": "Inhibit Time Emergency", "struct": OD.VAR, "need": False, "values": [ + {"name": "Inhibit Time Emergency", "type": 0x06, "access": 'rw', "pdo": False}]}, + 0x1016: {"name": "Consumer Heartbeat Time", "struct": OD.ARRAY, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Consumer Heartbeat Time", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 1, "nbmax": 0x7F}]}, + 0x1017: {"name": "Producer Heartbeat Time", "struct": OD.VAR, "need": False, "callback": True, "values": [ + {"name": "Producer Heartbeat Time", "type": 0x06, "access": 'rw', "pdo": False}]}, + 0x1018: {"name": "Identity", "struct": OD.RECORD, "need": True, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Vendor ID", "type": 0x07, "access": 'ro', "pdo": False}, + {"name": "Product Code", "type": 0x07, "access": 'ro', "pdo": False}, + {"name": "Revision Number", "type": 0x07, "access": 'ro', "pdo": False}, + {"name": "Serial Number", "type": 0x07, "access": 'ro', "pdo": False}]}, + 0x1019: {"name": "Synchronous counter overflow value", "struct": OD.VAR, "need": False, "values": [ + {"name": "Synchronous counter overflow value", "type": 0x05, "access": 'rw', "pdo": False}]}, + 0x1020: {"name": "Verify Configuration", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Configuration Date", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Configuration Time", "type": 0x07, "access": 'rw', "pdo": False}]}, + # 0x1021: {"name": "Store EDS", "struct": OD.VAR, "need": False, "values": [ + # {"name": "Store EDS", "type": 0x0F, "access": 'rw', "pdo": False}]}, + # 0x1022: {"name": "Storage Format", "struct": OD.VAR, "need": False, "values": [ + # {"name": "Storage Format", "type": 0x06, "access": 'rw', "pdo": False}]}, + 0x1023: {"name": "OS Command", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Command", "type": 0x0A, "access": 'rw', "pdo": False}, + {"name": "Status", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Reply", "type": 0x0A, "access": 'ro', "pdo": False}]}, + 0x1024: {"name": "OS Command Mode", "struct": OD.VAR, "need": False, "values": [ + {"name": "OS Command Mode", "type": 0x05, "access": 'wo', "pdo": False}]}, + 0x1025: {"name": "OS Debugger Interface", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Command", "type": 0x0A, "access": 'rw', "pdo": False}, + {"name": "Status", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Reply", "type": 0x0A, "access": 'ro', "pdo": False}]}, + 0x1026: {"name": "OS Prompt", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "StdIn", "type": 0x05, "access": 'wo', "pdo": True}, + {"name": "StdOut", "type": 0x05, "access": 'ro', "pdo": True}, + {"name": "StdErr", "type": 0x05, "access": 'ro', "pdo": True}]}, + 0x1027: {"name": "Module List", "struct": OD.ARRAY, "need": False, "values": [ + {"name": "Number of Connected Modules", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Module %d[(sub)]", "type": 0x06, "access": 'ro', "pdo": False, "nbmin": 1, "nbmax": 0xFE}]}, + 0x1028: {"name": "Emergency Consumer", "struct": OD.ARRAY, "need": False, "values": [ + {"name": "Number of Consumed Emergency Objects", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Emergency Consumer", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 1, "nbmax": 0x7F}]}, + 0x1029: {"name": "Error Behavior", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Error Classes", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "Communication Error", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Device Profile", "type": 0x05, "access": 'rw', "pdo": False, "nbmax": 0xFE}]}, # -- Server SDO Parameters - 0x1200: {"name": "Server SDO Parameter", "struct": OD.RECORD, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "COB ID Client to Server (Receive SDO)", "type": 0x07, "access": 'ro', "pdo": False, "default": '"$NODEID+0x600"'}, - {"name": "COB ID Server to Client (Transmit SDO)", "type": 0x07, "access": 'ro', "pdo": False, "default": '"$NODEID+0x580"'}]}, - 0x1201: {"name": "Additional Server SDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x7F, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "COB ID Client to Server (Receive SDO)", "type": 0x07, "access": 'ro', "pdo": False}, - {"name": "COB ID Server to Client (Transmit SDO)", "type": 0x07, "access": 'ro', "pdo": False}, - {"name": "Node ID of the SDO Client", "type": 0x05, "access": 'ro', "pdo": False}]}, + 0x1200: {"name": "Server SDO Parameter", "struct": OD.RECORD, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "COB ID Client to Server (Receive SDO)", "type": 0x07, "access": 'ro', "pdo": False, "default": '"$NODEID+0x600"'}, + {"name": "COB ID Server to Client (Transmit SDO)", "type": 0x07, "access": 'ro', "pdo": False, "default": '"$NODEID+0x580"'}]}, + 0x1201: {"name": "Additional Server SDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x7F, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "COB ID Client to Server (Receive SDO)", "type": 0x07, "access": 'ro', "pdo": False}, + {"name": "COB ID Server to Client (Transmit SDO)", "type": 0x07, "access": 'ro', "pdo": False}, + {"name": "Node ID of the SDO Client", "type": 0x05, "access": 'ro', "pdo": False}]}, # -- Client SDO Parameters - 0x1280: {"name": "Client SDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x100, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "COB ID Client to Server (Transmit SDO)", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "COB ID Server to Client (Receive SDO)", "type": 0x07, "access": 'rw', "pdo": False}, - {"name": "Node ID of the SDO Server", "type": 0x05, "access": 'rw', "pdo": False}]}, + 0x1280: {"name": "Client SDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x100, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "COB ID Client to Server (Transmit SDO)", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "COB ID Server to Client (Receive SDO)", "type": 0x07, "access": 'rw', "pdo": False}, + {"name": "Node ID of the SDO Server", "type": 0x05, "access": 'rw', "pdo": False}]}, # -- Receive PDO Communication Parameters - 0x1400: {"name": "Receive PDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x200, "need": False, "values": - [{"name": "Highest SubIndex Supported", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "COB ID used by PDO", "type": 0x07, "access": 'rw', "pdo": False, "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]"}, - {"name": "Transmission Type", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Inhibit Time", "type": 0x06, "access": 'rw', "pdo": False}, - {"name": "Compatibility Entry", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Event Timer", "type": 0x06, "access": 'rw', "pdo": False}, - {"name": "SYNC start value", "type": 0x05, "access": 'rw', "pdo": False}]}, + 0x1400: {"name": "Receive PDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x200, "need": False, "values": [ + {"name": "Highest SubIndex Supported", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "COB ID used by PDO", "type": 0x07, "access": 'rw', "pdo": False, "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]"}, + {"name": "Transmission Type", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Inhibit Time", "type": 0x06, "access": 'rw', "pdo": False}, + {"name": "Compatibility Entry", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Event Timer", "type": 0x06, "access": 'rw', "pdo": False}, + {"name": "SYNC start value", "type": 0x05, "access": 'rw', "pdo": False}]}, # -- Receive PDO Mapping Parameters - 0x1600: {"name": "Receive PDO %d Mapping[(idx)]", "struct": OD.NARRAY, "incr": 1, "nbmax": 0x200, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "PDO %d Mapping for an application object %d[(idx,sub)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 0, "nbmax": 0x40}]}, + 0x1600: {"name": "Receive PDO %d Mapping[(idx)]", "struct": OD.NARRAY, "incr": 1, "nbmax": 0x200, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "PDO %d Mapping for an application object %d[(idx,sub)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 0, "nbmax": 0x40}]}, # -- Transmit PDO Communication Parameters - 0x1800: {"name": "Transmit PDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x200, "need": False, "callback": True, "values": - [{"name": "Highest SubIndex Supported", "type": 0x05, "access": 'ro', "pdo": False}, - {"name": "COB ID used by PDO", "type": 0x07, "access": 'rw', "pdo": False, "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]"}, - {"name": "Transmission Type", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Inhibit Time", "type": 0x06, "access": 'rw', "pdo": False}, - {"name": "Compatibility Entry", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "Event Timer", "type": 0x06, "access": 'rw', "pdo": False}, - {"name": "SYNC start value", "type": 0x05, "access": 'rw', "pdo": False}]}, + 0x1800: {"name": "Transmit PDO %d Parameter[(idx)]", "struct": OD.NRECORD, "incr": 1, "nbmax": 0x200, "need": False, "callback": True, "values": [ + {"name": "Highest SubIndex Supported", "type": 0x05, "access": 'ro', "pdo": False}, + {"name": "COB ID used by PDO", "type": 0x07, "access": 'rw', "pdo": False, "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]"}, + {"name": "Transmission Type", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Inhibit Time", "type": 0x06, "access": 'rw', "pdo": False}, + {"name": "Compatibility Entry", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "Event Timer", "type": 0x06, "access": 'rw', "pdo": False}, + {"name": "SYNC start value", "type": 0x05, "access": 'rw', "pdo": False}]}, # -- Transmit PDO Mapping Parameters - 0x1A00: {"name": "Transmit PDO %d Mapping[(idx)]", "struct": OD.NARRAY, "incr": 1, "nbmax": 0x200, "need": False, "values": - [{"name": "Number of Entries", "type": 0x05, "access": 'rw', "pdo": False}, - {"name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 0, "nbmax": 0x40}]}, -} + 0x1A00: {"name": "Transmit PDO %d Mapping[(idx)]", "struct": OD.NARRAY, "incr": 1, "nbmax": 0x200, "need": False, "values": [ + {"name": "Number of Entries", "type": 0x05, "access": 'rw', "pdo": False}, + {"name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", "type": 0x07, "access": 'rw', "pdo": False, "nbmin": 0, "nbmax": 0x40}]}, +}) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 1a7aa0f..210f78f 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -1,3 +1,4 @@ +"""Objectdict Node class containting the object dictionary.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -17,1045 +18,541 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -import ast import copy import logging -import os -import re -import traceback +from pathlib import Path +from typing import Any, Generator, Iterable, Iterator import colorama -import objdictgen +# The following import needs care when importing node from objdictgen import eds_utils, gen_cfile, jsonod, maps, nosis -from objdictgen.maps import MAPPING_DICTIONARY, OD +from objdictgen.maps import OD, ODMapping, ODMappingList +from objdictgen.typing import (NodeProtocol, TIndexEntry, TODObj, TODSubObj, + TODValue, TParamEntry, TPath, TProfileMenu) log = logging.getLogger('objdictgen') Fore = colorama.Fore Style = colorama.Style -#Used to match strings such as 'Additional Server SDO %d Parameter[(idx)]' -#The above example matches to two groups ['Additional Server SDO %d Parameter', 'idx'] -RE_NAME = re.compile(r'(.*)\[[(](.*)[)]\]') - # ------------------------------------------------------------------------------ -# Utils +# Definition of Node Object # ------------------------------------------------------------------------------ -def isXml(filepath): - with open(filepath, 'r') as f: - header = f.read(5) - return header == " py3 conversion - raise NotImplementedError("BE_to_LE() may be broken in py3") - # FIXME: The function title is confusing as the input data type (str) is - # different than the output (int) - return int("".join(["%2.2X" % ord(char) for char in reversed(value)]), 16) - - -def LE_to_BE(value, size): - """ - Convert Little Endian to Big Endian - @param value: value expressed in integer - @param size: number of bytes generated - @return: a string containing the value converted - """ + Name: str + """Name of the node""" - # FIXME: This function is used in assosciation with DCF files, but have - # not been able to figure out how that work. It is very likely that this - # function is not working properly after the py2 -> py3 conversion due to - # the change of chr() behavior - raise NotImplementedError("LE_to_BE() is broken in py3") + Type: str + """Type of the node. Should be 'slave' or 'master'""" - # FIXME: The function title is confusing as the input data type (int) is - # different than the output (str) - data = ("%" + str(size * 2) + "." + str(size * 2) + "X") % value - list_car = [data[i:i + 2] for i in range(0, len(data), 2)] - list_car.reverse() - return "".join([chr(int(car, 16)) for car in list_car]) + ID: int + """Node ID""" + Description: str + """Description of the node""" -# ------------------------------------------------------------------------------ -# Load mapping -# ------------------------------------------------------------------------------ -def ImportProfile(profilename): - # Import profile - - # Test if the profilename is a filepath which can be used directly. If not - # treat it as the name - # The UI use full filenames, while all other uses use profile names - profilepath = profilename - if not os.path.exists(profilepath): - fname = "%s.prf" % profilename - try: - profilepath = next( - os.path.join(base, fname) - for base in objdictgen.PROFILE_DIRECTORIES - if os.path.exists(os.path.join(base, fname)) - ) - except StopIteration: - raise ValueError("Unable to load profile '%s': '%s': No such file or directory" % (profilename, fname)) from None - - # Mapping and AddMenuEntries are expected to be defined by the execfile - # The profiles requires some vars to be set - # pylint: disable=unused-variable - try: - with open(profilepath, "r") as f: - log.debug("EXECFILE %s" % (profilepath,)) - code = compile(f.read(), profilepath, 'exec') - exec(code, globals(), locals()) # FIXME: Using exec is unsafe - # pylint: disable=undefined-variable - return Mapping, AddMenuEntries # pyright: ignore # noqa: F821 - except Exception as exc: # pylint: disable=broad-except - log.debug("EXECFILE FAILED: %s" % exc) - log.debug(traceback.format_exc()) - raise ValueError("Loading profile '%s' failed: %s" % (profilepath, exc)) from exc + Dictionary: dict[int, TODValue|list[TODValue]] + """Object dictionary of the node. The key is the index and the value is the + literal value. For objects that have multiple subindexes, the object + is a list of values.""" + ParamsDictionary: dict[int, TParamEntry|dict[int, TParamEntry]] + """Dictionary of parameters for the node. The key is the index and the value + contains the parameter for the index object. It can be a dict of subindexes. + """ + # FIXME: The type definition on ParamsDictionary is not precisely accurate. + # When self.Dictionary is not a list, ParamsDictionary is TParamEntry. + # When self.Dictionary is a list, ParamsDictionary is a dict with + # int subindexes as keys and "TParamEntryN" (a type without callback) as + # values. The subindex dict also may contain the "callback" key. -# ------------------------------------------------------------------------------ -# Search in a Mapping Dictionary -# ------------------------------------------------------------------------------ + Profile: ODMapping + """Profile object dictionary mapping""" -class Find: - """ Collection of static methods for seaching in a mapping directory """ + DS302: ODMapping + """DS-302 object dictionary mapping""" - @staticmethod - def TypeIndex(typename, mappingdictionary): - """ - Return the index of the typename given by searching in mappingdictionary - """ - return { - values["name"]: index - for index, values in mappingdictionary.items() - if index < 0x1000 - }.get(typename) + UserMapping: ODMapping + """Custom user object dictionary mapping""" - @staticmethod - def TypeName(typeindex, mappingdictionary): - """ - Return the name of the type by searching in mappingdictionary - """ - if typeindex < 0x1000 and typeindex in mappingdictionary: - return mappingdictionary[typeindex]["name"] - return None + ProfileName: str + """Name of the loaded profile. If no profile is loaded, it should be 'None' + """ - @staticmethod - def TypeDefaultValue(typeindex, mappingdictionary): - """ - Return the default value of the type by searching in mappingdictionary - """ - if typeindex < 0x1000 and typeindex in mappingdictionary: - return mappingdictionary[typeindex]["default"] - return None + SpecificMenu: TProfileMenu + """Specific menu for the profile""" + + IndexOrder: list[int] + """Order of the indexes in the object dictionary to preserve the order""" + + DefaultStringSize: int = 10 + """Default string size for the node""" + + def __init__( + self, name: str = "", type: str = "slave", id: int = 0, + description: str = "", profilename: str = "None", + profile: ODMapping | None = None, specificmenu: TProfileMenu | None = None, + ): + self.Name: str = name + self.Type: str = type + self.ID: int = id + self.Description: str = description + self.ProfileName: str = profilename + self.Profile: ODMapping = profile or ODMapping() + self.SpecificMenu: TProfileMenu = specificmenu or [] + self.Dictionary: dict[int, TODValue|list[TODValue]] = {} + self.ParamsDictionary: dict[int, TParamEntry|dict[int, TParamEntry]] = {} + self.DS302: ODMapping = ODMapping() + self.UserMapping: ODMapping = ODMapping() + self.IndexOrder: list[int] = [] - @staticmethod - def TypeList(mappingdictionary): - """ - Return the list of types defined in mappingdictionary - """ - return [ - mappingdictionary[index]["name"] - for index in mappingdictionary - if index < 0x1000 - ] - @staticmethod - def EntryName(index, mappingdictionary, compute=True): - """ - Return the name of an entry by searching in mappingdictionary - """ - base_index = Find.Index(index, mappingdictionary) - if base_index: - infos = mappingdictionary[base_index] - if infos["struct"] & OD.IdenticalIndexes and compute: - return StringFormat(infos["name"], (index - base_index) // infos["incr"] + 1, 0) - return infos["name"] - return None + # -------------------------------------------------------------------------- + # Dunders + # -------------------------------------------------------------------------- - @staticmethod - def EntryInfos(index, mappingdictionary, compute=True): - """ - Return the informations of one entry by searching in mappingdictionary - """ - base_index = Find.Index(index, mappingdictionary) - if base_index: - obj = mappingdictionary[base_index].copy() - if obj["struct"] & OD.IdenticalIndexes and compute: - obj["name"] = StringFormat(obj["name"], (index - base_index) // obj["incr"] + 1, 0) - obj.pop("values") - return obj - return None + def __iter__(self) -> Iterator[int]: + """Iterate over all indexes in the dictionary""" + return iter(sorted(self.Dictionary)) - @staticmethod - def SubentryInfos(index, subindex, mappingdictionary, compute=True): - """ - Return the informations of one subentry of an entry by searching in mappingdictionary - """ - base_index = Find.Index(index, mappingdictionary) - if base_index: - struct = mappingdictionary[base_index]["struct"] - if struct & OD.Subindex: - infos = None - if struct & OD.IdenticalSubindexes: - if subindex == 0: - infos = mappingdictionary[base_index]["values"][0].copy() - elif 0 < subindex <= mappingdictionary[base_index]["values"][1]["nbmax"]: - infos = mappingdictionary[base_index]["values"][1].copy() - elif struct & OD.MultipleSubindexes: - idx = 0 - for subindex_infos in mappingdictionary[base_index]["values"]: - if "nbmax" in subindex_infos: - if idx <= subindex < idx + subindex_infos["nbmax"]: - infos = subindex_infos.copy() - break - idx += subindex_infos["nbmax"] - else: - if subindex == idx: - infos = subindex_infos.copy() - break - idx += 1 - elif subindex == 0: - infos = mappingdictionary[base_index]["values"][0].copy() - - if infos is not None and compute: - if struct & OD.IdenticalIndexes: - incr = mappingdictionary[base_index]["incr"] - else: - incr = 1 - infos["name"] = StringFormat(infos["name"], (index - base_index) // incr + 1, subindex) - - return infos - return None + def __setattr__(self, name: str, value: Any): + """Ensure that that internal attrs are of the right datatype.""" + if name in ("Profile", "DS302", "UserMapping"): + if not isinstance(value, ODMapping): + value = ODMapping(value) + super().__setattr__(name, value) - @staticmethod - def MapVariableList(mappingdictionary, node, compute=True): - """ - Return the list of variables that can be mapped defined in mappingdictionary - """ - for index in mappingdictionary: - if node.IsEntry(index): - for subindex, values in enumerate(mappingdictionary[index]["values"]): - if mappingdictionary[index]["values"][subindex]["pdo"]: - infos = node.GetEntryInfos(mappingdictionary[index]["values"][subindex]["type"]) - name = mappingdictionary[index]["values"][subindex]["name"] - if mappingdictionary[index]["struct"] & OD.IdenticalSubindexes: - values = node.GetEntry(index) - for i in range(len(values) - 1): - computed_name = name - if compute: - computed_name = StringFormat(computed_name, 1, i + 1) - yield (index, i + 1, infos["size"], computed_name) - else: - computed_name = name - if compute: - computed_name = StringFormat(computed_name, 1, subindex) - yield (index, subindex, infos["size"], computed_name) + # -------------------------------------------------------------------------- + # Legacy access methods + # -------------------------------------------------------------------------- - @staticmethod - def MandatoryIndexes(mappingdictionary): - """ - Return the list of mandatory indexes defined in mappingdictionary - """ - return [ - index - for index in mappingdictionary - if index >= 0x1000 and mappingdictionary[index]["need"] - ] + def GetNodeName(self) -> str: + """Get the name of the node""" + return self.Name - @staticmethod - def Index(index, mappingdictionary): - """ - Return the index of the informations in the Object Dictionary in case of identical - indexes - """ - if index in mappingdictionary: - return index - listpluri = [ - idx for idx, mapping in mappingdictionary.items() - if mapping["struct"] & OD.IdenticalIndexes - ] - for idx in sorted(listpluri): - nb_max = mappingdictionary[idx]["nbmax"] - incr = mappingdictionary[idx]["incr"] - if idx < index < idx + incr * nb_max and (index - idx) % incr == 0: - return idx - return None + def GetNodeID(self) -> int: + """Get the ID of the node""" + return self.ID + def GetNodeType(self) -> str: + """Get the type of the node""" + return self.Type -# ------------------------------------------------------------------------------ -# Definition of Node Object -# ------------------------------------------------------------------------------ + def GetNodeDescription(self) -> str: + """Get the description of the node""" + return self.Description -class Node: - """ - Class recording the Object Dictionary entries. It checks at each modification - that the structure of the Object Dictionary stay coherent - """ + def GetDefaultStringSize(self) -> int: + """Get the default string size""" + return self.DefaultStringSize - DefaultStringSize = 10 - - def __init__(self, name="", type="slave", id=0, description="", profilename="DS-301", profile=None, specificmenu=None): # pylint: disable=redefined-builtin, invalid-name - self.Name = name - self.Type = type - self.ID = id - self.Description = description - self.ProfileName = profilename - self.Profile = profile or {} - self.SpecificMenu = specificmenu or [] - self.Dictionary = {} - self.ParamsDictionary = {} - self.DS302 = {} - self.UserMapping = {} - self.IndexOrder = [] + def GetIndexes(self) -> list[int]: + """ Return a sorted list of indexes in Object Dictionary """ + return list(self) # -------------------------------------------------------------------------- - # Node Input/Output + # Node Input/Output # -------------------------------------------------------------------------- @staticmethod - def LoadFile(filepath): - # type: (str) -> Node + def isXml(filepath: TPath) -> bool: + """Check if the file is an XML file""" + with open(filepath, 'r', encoding="utf-8") as f: + header = f.read(5) + return header == " bool: + """Check if the file is an EDS file""" + with open(filepath, 'r', encoding="utf-8") as f: + header = f.readline().rstrip() + return header == "[FileInfo]" + + @staticmethod + def LoadFile(filepath: TPath) -> "Node": """ Open a file and create a new node """ - if isXml(filepath): - log.debug("Loading XML OD '%s'" % filepath) - with open(filepath, "r") as f: - return nosis.xmlload(f) # type: ignore + if Node.isXml(filepath): + log.debug("Loading XML OD '%s'", filepath) + with open(filepath, "r", encoding="utf-8") as f: + return nosis.xmlload(f) - if isEds(filepath): - log.debug("Loading EDS '%s'" % filepath) - return eds_utils.GenerateNode(filepath) + if Node.isEds(filepath): + log.debug("Loading EDS '%s'", filepath) + return eds_utils.generate_node(filepath) - log.debug("Loading JSON OD '%s'" % filepath) - with open(filepath, "r") as f: + log.debug("Loading JSON OD '%s'", filepath) + with open(filepath, "r", encoding="utf-8") as f: return Node.LoadJson(f.read()) @staticmethod - def LoadJson(contents): + def LoadJson(contents: str) -> "Node": """ Import a new Node from a JSON string """ - return jsonod.GenerateNode(contents) + return jsonod.generate_node(contents) - def DumpFile(self, filepath, filetype="json", **kwargs): + def DumpFile(self, filepath: TPath, filetype: str|None = "json", **kwargs): """ Save node into file """ + + # Attempt to determine the filetype from the filepath + if not filetype: + filetype = Path(filepath).suffix[1:] + if not filetype: + filetype = "json" + if filetype == 'od': - log.debug("Writing XML OD '%s'" % filepath) - with open(filepath, "w") as f: + log.debug("Writing XML OD '%s'", filepath) + with open(filepath, "w", encoding="utf-8") as f: # Never generate an od with IndexOrder in it nosis.xmldump(f, self, omit=('IndexOrder', )) return if filetype == 'eds': - log.debug("Writing EDS '%s'" % filepath) - eds_utils.GenerateEDSFile(filepath, self) + log.debug("Writing EDS '%s'", filepath) + content = eds_utils.generate_eds_content(self, filepath) + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) return if filetype == 'json': - log.debug("Writing JSON OD '%s'" % filepath) + log.debug("Writing JSON OD '%s'", filepath) jdata = self.DumpJson(**kwargs) - with open(filepath, "w") as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(jdata) return if filetype == 'c': - log.debug("Writing C files '%s'" % filepath) - gen_cfile.GenerateFile(filepath, self) + log.debug("Writing C files '%s'", filepath) + # Convert filepath to str because it might be used with legacy code + gen_cfile.GenerateFile(str(filepath), self) return raise ValueError("Unknown file suffix, unable to write file") - def DumpJson(self, compact=False, sort=False, internal=False, validate=True): + def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> str: """ Dump the node into a JSON string """ - return jsonod.GenerateJson( + return jsonod.generate_jsonc( self, compact=compact, sort=sort, internal=internal, validate=validate ) - # -------------------------------------------------------------------------- - # Node Functions - # -------------------------------------------------------------------------- - - def GetMappings(self, userdefinedtoo=True): - """ - Function which return the different Mappings available for this node - """ - if userdefinedtoo: - return [self.Profile, self.DS302, self.UserMapping] - return [self.Profile, self.DS302] + def asdict(self) -> dict[str, Any]: + """ Return the class data as a dict """ + return copy.deepcopy(self.__dict__) - def AddEntry(self, index, subindex=None, value=None): + def copy(self) -> "Node": """ - Add a new entry in the Object Dictionary + Return a copy of the node """ - if index not in self.Dictionary: - if not subindex: - self.Dictionary[index] = value - return True - if subindex == 1: - self.Dictionary[index] = [value] - return True - elif subindex and isinstance(self.Dictionary[index], list) and subindex == len(self.Dictionary[index]) + 1: - self.Dictionary[index].append(value) - return True - return False + return copy.deepcopy(self) - def SetEntry(self, index, subindex=None, value=None): - """ - Warning ! Modifies an existing entry in the Object Dictionary. Can't add a new one. - """ - if index not in self.Dictionary: - return False - if not subindex: - if value is not None: - self.Dictionary[index] = value - return True - if isinstance(self.Dictionary[index], list) and 0 < subindex <= len(self.Dictionary[index]): - if value is not None: - self.Dictionary[index][subindex - 1] = value - return True - return False + # -------------------------------------------------------------------------- + # Node Informations Functions + # -------------------------------------------------------------------------- - def SetParamsEntry(self, index, subindex=None, comment=None, buffer_size=None, save=None, callback=None): - if index not in self.Dictionary: - return False - if (comment is not None or save is not None or callback is not None or buffer_size is not None) and index not in self.ParamsDictionary: - self.ParamsDictionary[index] = {} - if subindex is None or not isinstance(self.Dictionary[index], list) and subindex == 0: - if comment is not None: - self.ParamsDictionary[index]["comment"] = comment - if buffer_size is not None: - self.ParamsDictionary[index]["buffer_size"] = buffer_size - if save is not None: - self.ParamsDictionary[index]["save"] = save - if callback is not None: - self.ParamsDictionary[index]["callback"] = callback - return True - if isinstance(self.Dictionary[index], list) and 0 <= subindex <= len(self.Dictionary[index]): - if (comment is not None or save is not None or callback is not None or buffer_size is not None) and subindex not in self.ParamsDictionary[index]: - self.ParamsDictionary[index][subindex] = {} - if comment is not None: - self.ParamsDictionary[index][subindex]["comment"] = comment - if buffer_size is not None: - self.ParamsDictionary[index][subindex]["buffer_size"] = buffer_size - if save is not None: - self.ParamsDictionary[index][subindex]["save"] = save - return True - return False + def GetMappings(self, userdefinedtoo: bool=True, withmapping=False) -> ODMappingList: + """Return the different Mappings available for this node""" + mapping = ODMappingList([self.Profile, self.DS302]) + if userdefinedtoo: + mapping.append(self.UserMapping) + if withmapping: + mapping.append(maps.MAPPING_DICTIONARY) + return mapping - def RemoveEntry(self, index, subindex=None): + def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=False) -> list[TODValue]|TODValue: """ - Removes an existing entry in the Object Dictionary. If a subindex is specified - it will remove this subindex only if it's the last of the index. If no subindex - is specified it removes the whole index and subIndexes from the Object Dictionary. + Returns the value of the entry specified by the index and subindex. If + subindex is None, it will return the value or the list of values of the + entire index. If aslist is True, it will always return a list. """ if index not in self.Dictionary: - return False - if not subindex: - self.Dictionary.pop(index) - if index in self.ParamsDictionary: - self.ParamsDictionary.pop(index) - return True - if isinstance(self.Dictionary[index], list) and subindex == len(self.Dictionary[index]): - self.Dictionary[index].pop(subindex - 1) - if index in self.ParamsDictionary: - if subindex in self.ParamsDictionary[index]: - self.ParamsDictionary[index].pop(subindex) - if len(self.ParamsDictionary[index]) == 0: - self.ParamsDictionary.pop(index) - if len(self.Dictionary[index]) == 0: - self.Dictionary.pop(index) - if index in self.ParamsDictionary: - self.ParamsDictionary.pop(index) - return True - return False + raise KeyError(f"Index 0x{index:04x} does not exist") + dictval = self.Dictionary[index] - def IsEntry(self, index, subindex=None): - """ - Check if an entry exists in the Object Dictionary and returns the answer. - """ - if index in self.Dictionary: - if not subindex: - return True - return subindex <= len(self.Dictionary[index]) - return False + # Variables needed by the eval_value function + base = self.GetBaseIndexNumber(index) + nodeid = self.ID - def GetEntry(self, index, subindex=None, compute=True, aslist=False): - """ - Returns the value of the entry asked. If the entry has the value "count", it - returns the number of subindex in the entry except the first. - """ - if index not in self.Dictionary: - raise KeyError("Index 0x%04x does not exist" % index) if subindex is None: - if isinstance(self.Dictionary[index], list): - return [len(self.Dictionary[index])] + [ - self.CompileValue(value, index, compute) - for value in self.Dictionary[index] - ] - result = self.CompileValue(self.Dictionary[index], index, compute) + if isinstance(dictval, list): + out: list[TODValue] = [len(dictval)] + out.extend( + maps.eval_value(value, base, nodeid, compute) + for value in dictval + ) + return out # Type is list[TValue] + + result = maps.eval_value(dictval, base, nodeid, compute) # This option ensures that the function consistently returns a list if aslist: - return [result] - return result + return [result] # Type is list[TValue] + return result # Type is TValue + + if isinstance(dictval, list): + if subindex == 0: + return len(dictval) # Type is int + + if 0 < subindex <= len(dictval): + # Type is TValue + return maps.eval_value(dictval[subindex - 1], base, nodeid, compute) + + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") + + # Special case: If the dictionary value is not a list, subindex 0 + # can be used to retrieve the entry. if subindex == 0: - if isinstance(self.Dictionary[index], list): - return len(self.Dictionary[index]) - return self.CompileValue(self.Dictionary[index], index, compute) - if isinstance(self.Dictionary[index], list) and 0 < subindex <= len(self.Dictionary[index]): - return self.CompileValue(self.Dictionary[index][subindex - 1], index, compute) - raise ValueError("Invalid subindex %s for index 0x%04x" % (subindex, index)) - - def GetParamsEntry(self, index, subindex=None, aslist=False): + return maps.eval_value(dictval, base, nodeid, compute) + + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x} for a non-list entry") + + def GetParamsEntry(self, index: int, subindex: int|None = None, + aslist: bool = False) -> TParamEntry|list[TParamEntry]: """ Returns the value of the entry asked. If the entry has the value "count", it returns the number of subindex in the entry except the first. """ if index not in self.Dictionary: - raise KeyError("Index 0x%04x does not exist" % index) + raise KeyError(f"Index 0x{index:04x} does not exist") + dictval = self.Dictionary[index] + params = self.ParamsDictionary.get(index) + + def _get_param(v: TParamEntry|None) -> TParamEntry: + params = maps.DEFAULT_PARAMS.copy() + if v is not None: + params.update(v) + return params + if subindex is None: - if isinstance(self.Dictionary[index], list): - if index in self.ParamsDictionary: - result = [] - for i in range(len(self.Dictionary[index]) + 1): - line = maps.DEFAULT_PARAMS.copy() - if i in self.ParamsDictionary[index]: - line.update(self.ParamsDictionary[index][i]) - result.append(line) - return result - return [maps.DEFAULT_PARAMS.copy() for i in range(len(self.Dictionary[index]) + 1)] - result = maps.DEFAULT_PARAMS.copy() - if index in self.ParamsDictionary: - result.update(self.ParamsDictionary[index]) + if isinstance(dictval, list): + # FIXME: An interesting difference beween GetParamsEntry() and GetEntry() is that + # the latter returns the number of subindexes in index 0, while the former does not + + # FIXME: There is a programmed assumption here: It checks dictval for a + # list but it assumes then that param_value is a dict. This is not always the case. + + params = params or {} + return [_get_param(params.get(i)) for i in range(len(dictval) + 1)] # type: ignore[call-overload] + + # Dictionary value is not a list + result = _get_param(params) # type: ignore[arg-type] + # This option ensures that the function consistently returns a list if aslist: return [result] return result - if subindex == 0 and not isinstance(self.Dictionary[index], list): - result = maps.DEFAULT_PARAMS.copy() - if index in self.ParamsDictionary: - result.update(self.ParamsDictionary[index]) - return result - if isinstance(self.Dictionary[index], list) and 0 <= subindex <= len(self.Dictionary[index]): - result = maps.DEFAULT_PARAMS.copy() - if index in self.ParamsDictionary and subindex in self.ParamsDictionary[index]: - result.update(self.ParamsDictionary[index][subindex]) - return result - raise ValueError("Invalid subindex %s for index 0x%04x" % (subindex, index)) - def HasEntryCallbacks(self, index): - entry_infos = self.GetEntryInfos(index) - if entry_infos and "callback" in entry_infos: - return entry_infos["callback"] - if index in self.Dictionary and index in self.ParamsDictionary and "callback" in self.ParamsDictionary[index]: - return self.ParamsDictionary[index]["callback"] - return False + if isinstance(dictval, list): - def IsMappingEntry(self, index): - """ - Check if an entry exists in the User Mapping Dictionary and returns the answer. - """ - return index in self.UserMapping + if 0 <= subindex <= len(dictval): + params = params or {} + return _get_param(params.get(subindex)) # type: ignore[call-overload] - def AddMappingEntry(self, index, subindex=None, name="Undefined", struct=0, size=None, nbmax=None, default=None, values=None): - """ - Add a new entry in the User Mapping Dictionary - """ - if index not in self.UserMapping: - if values is None: - values = [] - if subindex is None: - self.UserMapping[index] = {"name": name, "struct": struct, "need": False, "values": values} - if size is not None: - self.UserMapping[index]["size"] = size - if nbmax is not None: - self.UserMapping[index]["nbmax"] = nbmax - if default is not None: - self.UserMapping[index]["default"] = default - return True - elif subindex is not None and subindex == len(self.UserMapping[index]["values"]): - if values is None: - values = {} - self.UserMapping[index]["values"].append(values) - return True - return False - - def SetMappingEntry(self, index, subindex=None, name=None, struct=None, size=None, nbmax=None, default=None, values=None): - """ - Warning ! Modifies an existing entry in the User Mapping Dictionary. Can't add a new one. - """ - if index not in self.UserMapping: - return False - if subindex is None: - if name is not None: - self.UserMapping[index]["name"] = name - if self.UserMapping[index]["struct"] & OD.IdenticalSubindexes: - self.UserMapping[index]["values"][1]["name"] = name + " %d[(sub)]" - elif not self.UserMapping[index]["struct"] & OD.MultipleSubindexes: - self.UserMapping[index]["values"][0]["name"] = name - if struct is not None: - self.UserMapping[index]["struct"] = struct - if size is not None: - self.UserMapping[index]["size"] = size - if nbmax is not None: - self.UserMapping[index]["nbmax"] = nbmax - if default is not None: - self.UserMapping[index]["default"] = default - if values is not None: - self.UserMapping[index]["values"] = values - return True - if 0 <= subindex < len(self.UserMapping[index]["values"]) and values is not None: - if "type" in values: - if self.UserMapping[index]["struct"] & OD.IdenticalSubindexes: - if self.IsStringType(self.UserMapping[index]["values"][subindex]["type"]): - if self.IsRealType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, 0.) - elif not self.IsStringType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, 0) - elif self.IsRealType(self.UserMapping[index]["values"][subindex]["type"]): - if self.IsStringType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, "") - elif not self.IsRealType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, 0) - elif self.IsStringType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, "") - elif self.IsRealType(values["type"]): - for i in range(len(self.Dictionary[index])): - self.SetEntry(index, i + 1, 0.) - else: - if self.IsStringType(self.UserMapping[index]["values"][subindex]["type"]): - if self.IsRealType(values["type"]): - self.SetEntry(index, subindex, 0.) - elif not self.IsStringType(values["type"]): - self.SetEntry(index, subindex, 0) - elif self.IsRealType(self.UserMapping[index]["values"][subindex]["type"]): - if self.IsStringType(values["type"]): - self.SetEntry(index, subindex, "") - elif not self.IsRealType(values["type"]): - self.SetEntry(index, subindex, 0) - elif self.IsStringType(values["type"]): - self.SetEntry(index, subindex, "") - elif self.IsRealType(values["type"]): - self.SetEntry(index, subindex, 0.) - self.UserMapping[index]["values"][subindex].update(values) - return True - return False + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - def RemoveMappingEntry(self, index, subindex=None): - """ - Removes an existing entry in the User Mapping Dictionary. If a subindex is specified - it will remove this subindex only if it's the last of the index. If no subindex - is specified it removes the whole index and subIndexes from the User Mapping Dictionary. - """ - if index in self.UserMapping: - if subindex is None: - self.UserMapping.pop(index) - return True - if subindex == len(self.UserMapping[index]["values"]) - 1: - self.UserMapping[index]["values"].pop(subindex) - return True - return False + # Special case: If the dictionary value is not a list, subindex 0 + # will fetch the parameter. + if subindex == 0: + return _get_param(params)# type: ignore[arg-type] - def RemoveMapVariable(self, index, subindex=None): - model = index << 16 - mask = 0xFFFF << 16 - if subindex: - model += subindex << 8 - mask += 0xFF << 8 - for i in self.Dictionary: # pylint: disable=consider-using-dict-items - if 0x1600 <= i <= 0x17FF or 0x1A00 <= i <= 0x1BFF: - for j, value in enumerate(self.Dictionary[i]): - if (value & mask) == model: - self.Dictionary[i][j] = 0 + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - def UpdateMapVariable(self, index, subindex, size): - model = index << 16 - mask = 0xFFFF << 16 - if subindex: - model += subindex << 8 - mask = 0xFF << 8 - for i in self.Dictionary: # pylint: disable=consider-using-dict-items - if 0x1600 <= i <= 0x17FF or 0x1A00 <= i <= 0x1BFF: - for j, value in enumerate(self.Dictionary[i]): - if (value & mask) == model: - self.Dictionary[i][j] = model + size + def GetIndexEntry(self, index: int) -> TIndexEntry: + """ Return a full and raw representation of the index """ - def RemoveLine(self, index, max_, incr=1): - i = index - while i < max_ and self.IsEntry(i + incr): - self.Dictionary[i] = self.Dictionary[i + incr] - i += incr - self.Dictionary.pop(i) + def _mapping_for_index(index: int) -> Generator[tuple[str, TODObj], None, None]: + for n, o in ( + ('profile', self.Profile), + ('ds302', self.DS302), + ('user', self.UserMapping), + ('built-in', maps.MAPPING_DICTIONARY), + ): + if index in o: + yield n, o[index] - def Copy(self): - """ - Return a copy of the node - """ - return copy.deepcopy(self) + objmaps = list(_mapping_for_index(index)) + firstobj: TODObj = objmaps[0][1] if objmaps else {} - def GetDict(self): - """ Return the class data as a dict """ - return copy.deepcopy(self.__dict__) + obj: TIndexEntry = { + "index": index, + "groups": list(n for n, _ in objmaps), + } - def GetIndexDict(self, index): - ''' Return a dict representation of the index ''' - obj = {} + if firstobj: # Safe to assume False here is not just an empty ODObj + obj['object'] = firstobj if index in self.Dictionary: obj['dictionary'] = self.Dictionary[index] if index in self.ParamsDictionary: obj['params'] = self.ParamsDictionary[index] - if index in self.Profile: - obj['profile'] = self.Profile[index] - if index in self.DS302: - obj['ds302'] = self.DS302[index] - if index in self.UserMapping: - obj['user'] = self.UserMapping[index] - if index in maps.MAPPING_DICTIONARY: - obj['built-in'] = maps.MAPPING_DICTIONARY[index] - obj['base'] = self.GetBaseIndex(index) - obj['groups'] = tuple(g for g in ('profile', 'ds302', 'user', 'built-in') if g in obj) - return copy.deepcopy(obj) - def GetIndexes(self): - """ - Return a sorted list of indexes in Object Dictionary - """ - return list(sorted(self.Dictionary)) + baseindex = self.GetBaseIndex(index) + if index != baseindex: + obj['base'] = baseindex + baseobject = next(_mapping_for_index(baseindex)) + obj['basestruct'] = baseobject[1]["struct"] - def CompileValue(self, value, index, compute=True): - if isinstance(value, str) and '$NODEID' in value.upper(): - # NOTE: Don't change base, as the eval() use this - base = self.GetBaseIndexNumber(index) # noqa: F841 pylint: disable=unused-variable - try: - log.debug("EVAL CompileValue() #1: '%s'" % (value,)) - raw = eval(value) # FIXME: Using eval is not safe - if compute and isinstance(raw, str): - raw = raw.upper().replace("$NODEID", "self.ID") - log.debug("EVAL CompileValue() #2: '%s'" % (raw,)) - return eval(raw) # FIXME: Using eval is not safe - # NOTE: This has a side effect: It will strip away # '"$NODEID"' into '$NODEID' - # even if compute is False. - # if not compute and raw != value: - # warning(f"CompileValue() changed '{value}' into '{raw}'") - return raw - except Exception as exc: # pylint: disable=broad-except - log.debug("EVAL FAILED: %s" % exc) - raise ValueError("CompileValue failed for '%s'" % (value,)) from exc - else: - return value + # Ensure that the object is safe to mutate + return copy.deepcopy(obj) - # -------------------------------------------------------------------------- - # Node Informations Functions - # -------------------------------------------------------------------------- + def GetSubentryLength(self, index: int) -> int: + """ Return the length of the subindex """ + val = self.Dictionary.get(index, []) + if not isinstance(val, list): + return 0 + return len(val) - def GetBaseIndex(self, index): + def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ - for mapping in self.GetMappings(): - result = Find.Index(index, mapping) - if result: - return result - return Find.Index(index, MAPPING_DICTIONARY) + return self.GetMappings(withmapping=True).FindBaseIndex(index) - def GetBaseIndexNumber(self, index): + def GetBaseIndexNumber(self, index: int) -> int: """ Return the index number from the base object """ - for mapping in self.GetMappings(): - result = Find.Index(index, mapping) - if result is not None: - return (index - result) // mapping[result].get("incr", 1) - result = Find.Index(index, MAPPING_DICTIONARY) - if result is not None: - return (index - result) // MAPPING_DICTIONARY[result].get("incr", 1) - return 0 - - def GetCustomisedTypeValues(self, index): + return self.GetMappings(withmapping=True).FindBaseIndexNumber(index) + + def GetCustomisedTypeValues(self, index: int) -> tuple[list[TODValue], int]: + """Return the customization struct type from the index. It returns + a tuple containing the entry value and the int of the type of the object. + 0 indicates numerical value, 1 indicates string value.""" values = self.GetEntry(index) + if not isinstance(values, list): + raise ValueError(f"Index 0x{index:04x} is not an entry with subobjects") customisabletypes = self.GetCustomisableTypes() - return values, customisabletypes[values[1]][1] # type: ignore - - def GetEntryName(self, index, compute=True): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.EntryName(index, mappings[i], compute) - i += 1 - if result is None: - result = Find.EntryName(index, MAPPING_DICTIONARY, compute) - return result + # values[1] contains the object type index + return values, customisabletypes[values[1]][1] # type: ignore[index] + + def GetEntryName(self, index: int, compute=True) -> str: + """Return the entry name for the given index""" + return self.GetMappings(withmapping=True).FindEntryName(index, compute) - def GetEntryInfos(self, index, compute=True): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.EntryInfos(index, mappings[i], compute) - i += 1 - r301 = Find.EntryInfos(index, MAPPING_DICTIONARY, compute) - if r301: - if result is not None: - r301.update(result) + def GetEntryInfos(self, index: int, compute=True) -> TODObj: + """Return the entry infos for the given index""" + # FIXME: Add flags. Add the ability to determine the mapping source + result = self.GetMappings(withmapping=True).FindEntryInfos(index, compute) + try: + # If present in built-in dictionary, use the built-in values + # and update with the user provided values + r301 = maps.MAPPING_DICTIONARY.FindEntryInfos(index, compute) + r301.update(result) return r301 + except ValueError: + pass return result - def GetSubentryInfos(self, index, subindex, compute=True): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.SubentryInfos(index, subindex, mappings[i], compute) - if result: - result["user_defined"] = i == len(mappings) - 1 and index >= 0x1000 - i += 1 - r301 = Find.SubentryInfos(index, subindex, MAPPING_DICTIONARY, compute) - if r301: - if result is not None: - r301.update(result) - else: - r301["user_defined"] = False + def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> TODSubObj: + """Return the subentry infos for the given index and subindex""" + # FIXME: Add flags. Add the ability to determine the mapping source + result = self.GetMappings(withmapping=True).FindSubentryInfos(index, subindex, compute) + # FIXME: This will alter objects in the mapping store. This is probably not intended + result["user_defined"] = index in self.UserMapping + try: + r301 = maps.MAPPING_DICTIONARY.FindSubentryInfos(index, subindex, compute) + r301.update(result) return r301 + except ValueError: + pass return result - def GetEntryFlags(self, index): - flags = [] + def GetEntryFlags(self, index: int) -> set[str]: + """Return the flags for the given index""" + flags: set[str] = set() info = self.GetEntryInfos(index) if not info: return flags if info.get('need'): - flags.append("Mandatory") + flags.add("Mandatory") if index in self.UserMapping: - flags.append("User") + flags.add("User") if index in self.DS302: - flags.append("DS-302") + flags.add("DS-302") if index in self.Profile: - flags.append("Profile") + flags.add("Profile") if self.HasEntryCallbacks(index): - flags.append('CB') + flags.add('CB') if index not in self.Dictionary: if index in self.DS302 or index in self.Profile: - flags.append("Unused") + flags.add("Unused") else: - flags.append("Missing") + flags.add("Missing") return flags - def GetAllSubentryInfos(self, index, compute=True): - values = self.GetEntry(index, compute=compute, aslist=True) - entries = self.GetParamsEntry(index, aslist=True) - for i, (value, entry) in enumerate(zip(values, entries)): # type: ignore - info = { - 'subindex': i, - 'value': value, - } - result = self.GetSubentryInfos(index, i) - if result: - info.update(result) - info.update(entry) - yield info - - def GetTypeIndex(self, typename): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.TypeIndex(typename, mappings[i]) - i += 1 - if result is None: - result = Find.TypeIndex(typename, MAPPING_DICTIONARY) - return result + def GetTypeIndex(self, typename: str) -> int: + """Return the type index for the given type name.""" + return self.GetMappings(withmapping=True).FindTypeIndex(typename) - def GetTypeName(self, typeindex): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.TypeName(typeindex, mappings[i]) - i += 1 - if result is None: - result = Find.TypeName(typeindex, MAPPING_DICTIONARY) - return result + def GetTypeName(self, index: int) -> str: + """Return the type name for the given type index.""" + return self.GetMappings(withmapping=True).FindTypeName(index) - def GetTypeDefaultValue(self, typeindex): - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = Find.TypeDefaultValue(typeindex, mappings[i]) - i += 1 - if result is None: - result = Find.TypeDefaultValue(typeindex, MAPPING_DICTIONARY) - return result + def GetTypeDefaultValue(self, index: int) -> TODValue: + """Return the default value for the given type index.""" + return self.GetMappings(withmapping=True).FindTypeDefaultValue(index) - def GetMapVariableList(self, compute=True): - list_ = list(Find.MapVariableList(MAPPING_DICTIONARY, self, compute)) - for mapping in self.GetMappings(): - list_.extend(Find.MapVariableList(mapping, self, compute)) - list_.sort() - return list_ + def GetMapVariableList(self, compute=True) -> list[tuple[int, int, int, str]]: + """Return a list of all objects and subobjects available for mapping into + pdos. Returns a list of tuples with the index, subindex, size and name of the object.""" + return list(sorted(self.GetMappings(withmapping=True).FindMapVariableList(self, compute))) - def GetMandatoryIndexes(self, node=None): # pylint: disable=unused-argument - list_ = Find.MandatoryIndexes(MAPPING_DICTIONARY) - for mapping in self.GetMappings(): - list_.extend(Find.MandatoryIndexes(mapping)) - return list_ + def GetMandatoryIndexes(self) -> list[int]: + """Return the mandatory indexes for the node.""" + # FIXME: Old code listed MAPPING_DIRECTORY first, this is last. Important? + return self.GetMappings(withmapping=True).FindMandatoryIndexes() - def GetCustomisableTypes(self): + def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: + """ Return the customisable types. It returns a dict by the index number. + The value is a tuple with the type name and the size of the type.""" return { index: (self.GetTypeName(index), valuetype) for index, valuetype in maps.CUSTOMISABLE_TYPES } - # -------------------------------------------------------------------------- - # Type helper functions - # -------------------------------------------------------------------------- - - def IsStringType(self, index): - if index in (0x9, 0xA, 0xB, 0xF): - return True - if 0xA0 <= index < 0x100: - result = self.GetEntry(index, 1) - if result in (0x9, 0xA, 0xB): - return True - return False - - def IsRealType(self, index): - if index in (0x8, 0x11): - return True - if 0xA0 <= index < 0x100: - result = self.GetEntry(index, 1) - if result in (0x8, 0x11): - return True - return False - - # -------------------------------------------------------------------------- - # Type and Map Variable Lists - # -------------------------------------------------------------------------- - - def GetTypeList(self): - list_ = Find.TypeList(MAPPING_DICTIONARY) - for mapping in self.GetMappings(): - list_.extend(Find.TypeList(mapping)) - return list_ + def GetTypeList(self) -> list[str]: + """Return a list of all object types available for the current node""" + # FIXME: Old code listed MAPPING_DIRECTORY first, this puts it last. Important? + return self.GetMappings(withmapping=True).FindTypeList() - def GenerateMapName(self, name, index, subindex): # pylint: disable=unused-argument - return "%s (0x%4.4X)" % (name, index) + @staticmethod + def GenerateMapName(name: str, index: int, subindex: int) -> str: + """Return how a mapping object should be named in UI""" + return f"{name} (0x{index:04X})" - def GetMapValue(self, mapname): + def GetMapValue(self, mapname: str) -> int: + """Return the mapping value from the given printable name""" if mapname == "None": return 0 - list_ = self.GetMapVariableList() - for index, subindex, size, name in list_: + def _get_buffer_size(index: int, subindex: int, size: int, name: str) -> int: + try: + params: TParamEntry = self.ParamsDictionary[index][subindex] # type: ignore[literal-required] + bs = params["buffer_size"] + if bs <= 8: + return (index << 16) + (subindex << 8) + size * bs + raise ValueError(f"String size of '{name}' too big to fit in a PDO") + except KeyError: + raise ValueError( + "No string length found and default string size too big to fit in a PDO" + ) from None + + varlist = self.GetMapVariableList() + for index, subindex, size, name in varlist: if mapname == self.GenerateMapName(name, index, subindex): - if self.UserMapping[index]["struct"] == OD.ARRAY: # array type, only look at subindex 1 in UserMapping + # array type, only look at subindex 1 in UserMapping + if self.UserMapping[index]["struct"] == OD.ARRAY: if self.IsStringType(self.UserMapping[index]["values"][1]["type"]): - try: - if int(self.ParamsDictionary[index][subindex]["buffer_size"]) <= 8: - return (index << 16) + (subindex << 8) + size * int(self.ParamsDictionary[index][subindex]["buffer_size"]) - raise ValueError("String size too big to fit in a PDO") - except KeyError: - raise ValueError("No string length found and default string size too big to fit in a PDO") from None + return _get_buffer_size(index, subindex, size, mapname) else: if self.IsStringType(self.UserMapping[index]["values"][subindex]["type"]): - try: - if int(self.ParamsDictionary[index][subindex]["buffer_size"]) <= 8: - return (index << 16) + (subindex << 8) + size * int(self.ParamsDictionary[index][subindex]["buffer_size"]) - raise ValueError("String size too big to fit in a PDO") - except KeyError: - raise ValueError("No string length found and default string size too big to fit in a PDO") from None + return _get_buffer_size(index, subindex, size, mapname) return (index << 16) + (subindex << 8) + size - return None - def GetMapIndex(self, value): + raise ValueError(f"Mapping '{mapname}' not found") + + @staticmethod + def GetMapIndex(value: int) -> tuple[int, int, int]: + """Return the index, subindex, size from a map value""" if value: index = value >> 16 subindex = (value >> 8) % (1 << 8) @@ -1063,31 +560,36 @@ def GetMapIndex(self, value): return index, subindex, size return 0, 0, 0 - def GetMapName(self, value): + def GetMapName(self, value: int) -> str: + """Return the printable name for the given map value.""" index, subindex, _ = self.GetMapIndex(value) if value: result = self.GetSubentryInfos(index, subindex) - if result: - return self.GenerateMapName(result["name"], index, subindex) + # FIXME: Removed a "if result" check here + return self.GenerateMapName(result["name"], index, subindex) return "None" - def GetMapList(self): + def GetMapList(self) -> list[str]: """ - Return the list of variables that can be mapped for the current node + Return the list of variables that can be mapped into pdos for the current node """ - list_ = ["None"] + [self.GenerateMapName(name, index, subindex) for index, subindex, size, name in self.GetMapVariableList()] - return list_ - - def GetAllParameters(self, sort=False): - """ Get a list of all the parameters """ + return ["None"] + [ + self.GenerateMapName(name, index, subindex) + for index, subindex, size, name in self.GetMapVariableList() + ] - order = list(self.UserMapping.keys()) - order += [k for k in self.Dictionary.keys() if k not in order] - order += [k for k in self.ParamsDictionary.keys() if k not in order] + def GetAllIndices(self, sort=False) -> list[int]: + """ Get a list of all indices. If node maintains a sort order, + it will be used. Otherwise if sort is False, the order + will be arbitrary. If sort is True they will be sorted. + """ + order = list(self.UserMapping) + order += [k for k in self.Dictionary if k not in order] + order += [k for k in self.ParamsDictionary if k not in order] if self.Profile: - order += [k for k in self.Profile.keys() if k not in order] + order += [k for k in self.Profile if k not in order] if self.DS302: - order += [k for k in self.DS302.keys() if k not in order] + order += [k for k in self.DS302 if k not in order] if sort: order = sorted(order) @@ -1106,72 +608,395 @@ def GetAllParameters(self, sort=False): def GetUnusedParameters(self): """ Return a list of all unused parameter indexes """ return [ - k for k in self.GetAllParameters() + k for k in self.GetAllIndices() if k not in self.Dictionary ] - def RemoveIndex(self, index): - """ Remove the given index """ - self.UserMapping.pop(index, None) - self.Dictionary.pop(index, None) - self.ParamsDictionary.pop(index, None) - if self.DS302: - self.DS302.pop(index, None) - if self.Profile: - self.Profile.pop(index, None) - if not self.Profile: - self.ProfileName = "None" + # -------------------------------------------------------------------------- + # Type helper functions + # -------------------------------------------------------------------------- + + def IsStringType(self, index: int) -> bool: + """Is the object index a string type?""" + if index in (0x9, 0xA, 0xB, 0xF): # VISIBLE_STRING, OCTET_STRING, UNICODE_STRING, DOMAIN + return True + if 0xA0 <= index < 0x100: # Custom types + result = self.GetEntry(index, 1) + if result in (0x9, 0xA, 0xB): + return True + return False + + def IsRealType(self, index: int) -> bool: + """Is the object index a real (float) type?""" + if index in (0x8, 0x11): # REAL32, REAL64 + return True + if 0xA0 <= index < 0x100: # Custom types + result = self.GetEntry(index, 1) + if result in (0x8, 0x11): + return True + return False + + def IsMappingEntry(self, index: int) -> bool: + """ + Check if an entry exists in the User Mapping Dictionary and returns the answer. + """ + # FIXME: Is usermapping only used when defining custom objects? + # Come back to this and test if this is the case. If it is the function + # should probably be renamed to "IsUserEntry" or somesuch + return index in self.UserMapping + + def IsEntry(self, index: int, subindex: int=0) -> bool: + """ + Check if an entry exists in the Object Dictionary + """ + if index in self.Dictionary: + if not subindex: + return True + dictval = self.Dictionary[index] + return isinstance(dictval, list) and subindex <= len(dictval) + return False + + def HasEntryCallbacks(self, index: int) -> bool: + """Check if entry has the callback flag defined.""" + entry_infos = self.GetEntryInfos(index) + if entry_infos and "callback" in entry_infos: + return entry_infos["callback"] + if index in self.Dictionary and index in self.ParamsDictionary: + params = self.ParamsDictionary[index] + return params.get("callback", False) # type: ignore[call-overload, return-value] + return False # -------------------------------------------------------------------------- - # Validator + # Node mutuation functions + # -------------------------------------------------------------------------- + + def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None): + """ + Add a new entry in the Object Dictionary + """ + # FIXME: It need a value, but the order of fn arguments is placed after an optional arg + assert value is not None + if index not in self.Dictionary: + if not subindex: + self.Dictionary[index] = value + return + if subindex == 1: + # FIXME: When specifying a subindex, the value should never be a list + assert not isinstance(value, list) + self.Dictionary[index] = [value] + return + raise ValueError(f"Invalid subindex {subindex} when 0x{index:04x} is not in the dictionary") + + dictval = self.Dictionary[index] + if subindex and isinstance(dictval, list) and subindex == len(dictval) + 1: + # FIXME: When specifying a subindex, the value should never be a list + assert not isinstance(value, list) + dictval.append(value) + return + raise ValueError(f"Unable to add entry 0x{index:04x} subindex {subindex}") + + def SetEntry(self, index: int, subindex: int|None = None, value: TODValue|None = None): + """Modify an existing entry in the Object Dictionary""" + # FIXME: Is it permissible to have value as None? The code seems to suggest that it is + assert value is not None + if index not in self.Dictionary: + raise ValueError(f"Index 0x{index:04x} does not exist") + dictval = self.Dictionary[index] + + if not subindex: + # if value is not None: # FIXME: Can this be None? + self.Dictionary[index] = value + return + + if isinstance(dictval, list) and 0 < subindex <= len(dictval): + # if value is not None: # FIXME: Can this be None? + dictval[subindex - 1] = value + return + raise ValueError(f"Failed to set entry 0x{index:04x} subindex {subindex}") + + def SetParamsEntry(self, index: int, subindex: int|None = None, params: TParamEntry|None = None): + """Set parameter values for an entry in the Object Dictionary.""" + if index not in self.Dictionary: + raise ValueError(f"Index 0x{index:04x} does not exist") + if not params: + raise ValueError("No parameters to set for index 0x{index:04x}") + + dictval = self.Dictionary[index] + pardict = self.ParamsDictionary.setdefault(index, {}) + + if subindex is None or (not isinstance(dictval, list) and subindex == 0): + pardict.update(params) # type: ignore[arg-type] + return + + if isinstance(dictval, list) and 0 <= subindex <= len(dictval): + subparam: TParamEntry = pardict.setdefault(subindex, {}) # type: ignore[typeddict-item,misc] + subparam.update(params) + return + + raise ValueError(f"Failed to set params entry 0x{index:04x} subindex {subindex}") + + def RemoveEntry(self, index: int, subindex: int|None = None): + """ + Removes an existing entry in the Object Dictionary. If a subindex is specified + it will remove this subindex only if it's the last of the index. If no subindex + is specified it removes the whole index and subIndexes from the Object Dictionary. + """ + if index not in self.Dictionary: + raise ValueError(f"Index 0x{index:04x} does not exist") + + if subindex is None: + self.Dictionary.pop(index) + self.ParamsDictionary.pop(index, None) + return + + dictval = self.Dictionary[index] + if isinstance(dictval, list) and subindex == len(dictval): + dictval.pop(subindex - 1) + if index in self.ParamsDictionary: + self.ParamsDictionary[index].pop(subindex, None) # type: ignore[typeddict-item,misc] + if len(self.ParamsDictionary[index]) == 0: + self.ParamsDictionary.pop(index) + if len(dictval) == 0: + self.Dictionary.pop(index) + self.ParamsDictionary.pop(index, None) + return + raise ValueError(f"Failed to remove entry 0x{index:04x} subindex {subindex}") + + def AddMappingEntry(self, index: int, entry: TODObj): + """ + Add a new entry in the User Mapping Dictionary + """ + if index in self.UserMapping: + raise ValueError(f"Index 0x{index:04x} already exists in UserMapping") + if not entry: + raise ValueError("No entry to set for index 0x{index:04x}") + if index not in self.UserMapping: + entry.setdefault("values", []) + self.UserMapping[index] = entry + return + raise ValueError(f"Failed to add mapping entry 0x{index:04x}") + + def AddMappingSubEntry(self, index: int, subindex: int, values: TODSubObj): + """ + Add a new subentry in the User Mapping Dictionary + """ + if not values: + raise ValueError("No values to set for index 0x{index:04x} subindex {subindex}") + if index not in self.UserMapping: + raise ValueError(f"Index 0x{index:04x} does not exist in User Mapping") + if subindex == len(self.UserMapping[index]["values"]): + self.UserMapping[index]["values"].append(values) + return + raise ValueError(f"Failed to add mapping entry 0x{index:04x} subindex {subindex}") + + def SetMappingEntry(self, index: int, entry: TODObj): + """ + Modify an existing entry in the User Mapping Dictionary + """ + if index not in self.UserMapping: + raise ValueError(f"Index 0x{index:04x} does not exist in User Mapping") + if not entry: + raise ValueError("No entry to set for index 0x{index:04x}") + usermap = self.UserMapping[index] + if "name" in entry: + name = entry["name"] + if usermap["struct"] & OD.IdenticalSubindexes: + usermap["values"][1]["name"] = name + " %d[(sub)]" + elif not usermap["struct"] & OD.MultipleSubindexes: + usermap["values"][0]["name"] = name + usermap.update(entry) + + def SetMappingSubEntry(self, index: int, subindex: int, values: TODSubObj): + """ + Modify an existing subentry in the User Mapping Dictionary + """ + if index not in self.UserMapping: + raise ValueError(f"Index 0x{index:04x} subindex {subindex} does not exist in User Mapping") + if not values: + raise ValueError(f"No values to set for index 0x{index:04x} subindex {subindex}") + usermap = self.UserMapping[index] + if subindex >= len(usermap["values"]): + raise ValueError(f"Subindex {subindex} for index 0x{index:04x} does not exist in User Mapping") + submap = usermap["values"][subindex] + if "type" in values: + if usermap["struct"] & OD.IdenticalSubindexes: + if self.IsStringType(submap["type"]): + if self.IsRealType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, 0.) + elif not self.IsStringType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, 0) + elif self.IsRealType(submap["type"]): + if self.IsStringType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, "") + elif not self.IsRealType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, 0) + elif self.IsStringType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, "") + elif self.IsRealType(values["type"]): + for i in range(len(self.Dictionary[index])): # type: ignore[arg-type] + self.SetEntry(index, i + 1, 0.) + else: + if self.IsStringType(submap["type"]): + if self.IsRealType(values["type"]): + self.SetEntry(index, subindex, 0.) + elif not self.IsStringType(values["type"]): + self.SetEntry(index, subindex, 0) + elif self.IsRealType(submap["type"]): + if self.IsStringType(values["type"]): + self.SetEntry(index, subindex, "") + elif not self.IsRealType(values["type"]): + self.SetEntry(index, subindex, 0) + elif self.IsStringType(values["type"]): + self.SetEntry(index, subindex, "") + elif self.IsRealType(values["type"]): + self.SetEntry(index, subindex, 0.) + submap.update(values) + + def RemoveMappingEntry(self, index: int, subindex: int|None = None): + """ + Removes an existing entry in the User Mapping Dictionary. If a subindex is specified + it will remove this subindex only if it's the last of the index. If no subindex + is specified it removes the whole index and subIndexes from the User Mapping Dictionary. + """ + if index not in self.UserMapping: + raise ValueError(f"Index 0x{index:04x} does not exist in User Mapping") + if subindex is None: + self.UserMapping.pop(index) + return + obj = self.UserMapping[index] + if subindex == len(obj["values"]) - 1: + obj["values"].pop(subindex) + return + if obj['struct'] & OD.IdenticalSubindexes: + return + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") + + def RemoveMapVariable(self, index: int, subindex: int = 0): + """ + Remove all PDO mappings references to the specificed index and subindex. + """ + model = index << 16 + mask = 0xFFFF << 16 + if subindex: + model += subindex << 8 + mask += 0xFF << 8 + # Iterate over all RPDO and TPDO mappings and remove the reference to this variable + for i, dictval in self.Dictionary.items(): + if 0x1600 <= i <= 0x17FF or 0x1A00 <= i <= 0x1BFF: + # FIXME: Assumes that PDO mappings are records + assert isinstance(dictval, list) + for j, value in enumerate(dictval): + # FIXME: Assumes that the data in the records are ints + assert isinstance(value, int) + if (value & mask) == model: + dictval[j] = 0 + + def UpdateMapVariable(self, index: int, subindex: int, size: int): + """ + Update the PDO mappings references to the specificed index and subindex + and set the size value. + """ + model = index << 16 + mask = 0xFFFF << 16 + if subindex: + model += subindex << 8 + mask = 0xFF << 8 + for i, dictval in self.Dictionary.items(): + if 0x1600 <= i <= 0x17FF or 0x1A00 <= i <= 0x1BFF: + # FIXME: Assumes that PDO mappings are records + assert isinstance(dictval, list) + for j, value in enumerate(dictval): + # FIXME: Assumes that the data in the records are ints + assert isinstance(value, int) + if (value & mask) == model: + dictval[j] = model + size + + def RemoveLine(self, index: int, maxval: int, incr: int = 1): + """ Remove the given index and shift all the following indexes """ + # FIXME: This function is called from NodeManager.RemoveCurrentVariable() + # but uncertain on how it is used. + i = index + while i < maxval and self.IsEntry(i + incr): + self.Dictionary[i] = self.Dictionary[i + incr] + i += incr + self.Dictionary.pop(i) + + def RemoveIndex(self, index: int|Iterable[int]) -> None: + """ Remove the given index or indexes """ + if isinstance(index, int): + index = [index] + for i in index: + self.UserMapping.pop(i, None) + self.Dictionary.pop(i, None) + self.ParamsDictionary.pop(i, None) + if self.DS302: + self.DS302.pop(i, None) + if self.Profile: + self.Profile.pop(i, None) + if not self.Profile: + self.ProfileName = "None" + + # -------------------------------------------------------------------------- + # Validator # -------------------------------------------------------------------------- def Validate(self, fix=False): - ''' Verify any inconsistencies when loading an OD. The function will + """ Verify any inconsistencies when loading an OD. The function will attempt to fix the data if the correct flag is enabled. - ''' - def _warn(text): + """ + def _warn(text: str): name = self.GetEntryName(index) - log.warning("WARNING: 0x{0:04x} ({0}) '{1}': {2}".format(index, name, text)) + log.warning("WARNING: 0x%04x (%d) '%s': %s", index, index, name, text) # Iterate over all the values and user parameters - params = set(self.Dictionary.keys()) - params.update(self.ParamsDictionary.keys()) - for index in params: + for index in set(self.Dictionary) | set(self.ParamsDictionary): # # Test if ParamDictionary exists without Dictionary # if index not in self.Dictionary: - _warn("Parameter without any value") + _warn("Parameter value without any dictionary entry") if fix: del self.ParamsDictionary[index] _warn("FIX: Deleting ParamDictionary entry") continue base = self.GetEntryInfos(index) - assert base # For mypy is_var = base["struct"] in (OD.VAR, OD.NVAR) + # FIXME: This probably needs a revisit. Is this checking that the + # dimensions of Dictionary and ParamsDictionary match? + # # Test if ParamDictionary matches Dictionary # - dictlen = 1 if is_var else len(self.Dictionary.get(index, [])) + # Complile a list of all subindexes + dictlen = 1 if is_var else len(self.Dictionary.get(index, [])) # type: ignore[arg-type] params = { k: v + # This assumes that ParamsDictionary is always a dict with or without subindexes for k, v in self.ParamsDictionary.get(index, {}).items() - if isinstance(k, int) + if isinstance(k, int) # Any other key is not a subindex } excessive_params = {k for k in params if k > dictlen} if excessive_params: - log.debug("Excessive params: {}".format(excessive_params)) - _warn("Excessive user parameters ({}) or too few dictionary values ({})".format(len(excessive_params), dictlen)) + log.debug("Excessive params: %s", excessive_params) + _warn( + f"Excessive user parameters ({len(excessive_params)}) " + f"or too few dictionary values ({dictlen})" + ) if index in self.Dictionary: for idx in excessive_params: - del self.ParamsDictionary[index][idx] + del self.ParamsDictionary[index][idx] # type: ignore[typeddict-item,misc] del params[idx] - _warn("FIX: Deleting ParamDictionary entries {}".format(", ".join(str(k) for k in excessive_params))) + t_p = ", ".join(str(k) for k in excessive_params) + _warn(f"FIX: Deleting ParamDictionary entries {t_p}") # If params have been emptied because of this, remove it altogether if not params: @@ -1179,24 +1004,23 @@ def _warn(text): _warn("FIX: Deleting ParamDictionary entry") # Iterate over all user mappings - params = set(self.UserMapping.keys()) - for index in params: + for index in set(self.UserMapping): for idx, subvals in enumerate(self.UserMapping[index]['values']): # - # Test if subindex have a name + # Test that subindexi have a name # if not subvals["name"]: - _warn("Sub index {}: Missing name".format(idx)) + _warn(f"Sub index {idx}: Missing name") if fix: - subvals["name"] = "Subindex {}".format(idx) - _warn("FIX: Set name to '{}'".format(subvals["name"])) + subvals["name"] = f"Subindex {idx}" + _warn(f"FIX: Set name to '{subvals['name']}'") # -------------------------------------------------------------------------- - # Printing and output + # Printing and output # -------------------------------------------------------------------------- - def GetPrintLine(self, index, unused=False, compact=False): + def GetPrintLine(self, index: int, unused=False, compact=False): obj = self.GetEntryInfos(index) if not obj: @@ -1208,16 +1032,19 @@ def GetPrintLine(self, index, unused=False, compact=False): return '', {} # Replace flags for formatting - for i, flag in enumerate(flags): + for _, flag in enumerate(flags.copy()): if flag == 'Missing': - flags[i] = Fore.RED + ' *MISSING* ' + Style.RESET_ALL + flags.discard('Missing') + flags.add(Fore.RED + ' *MISSING* ' + Style.RESET_ALL) # Print formattings + t_flags = ', '.join(flags) + t_string = maps.ODStructTypes.to_string(obj['struct']) or '???' fmt = { - 'key': "{0}0x{1:04x} ({1}){2}".format(Fore.GREEN, index, Style.RESET_ALL), + 'key': f"{Fore.GREEN}0x{index:04x} ({index}){Style.RESET_ALL}", 'name': self.GetEntryName(index), - 'struct': maps.ODStructTypes.to_string(obj.get('struct'), '???').upper(), # type: ignore - 'flags': " {}{}{}".format(Fore.CYAN, ', '.join(flags), Style.RESET_ALL) if flags else '', + 'struct': t_string.upper(), + 'flags': f" {Fore.CYAN}{t_flags}{Style.RESET_ALL}" if flags else '', 'pre': ' ' if not compact else '', } @@ -1230,7 +1057,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve """ # Get the indexes to print and determine the order - keys = keys or self.GetAllParameters(sort=True) + keys = keys or self.GetAllIndices(sort=True) index_range = None for k in keys: @@ -1240,11 +1067,11 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve continue # Print the parameter range header - ir = GetIndexRange(k) + ir = maps.INDEX_RANGES.get_index_range(k) if index_range != ir: index_range = ir if not compact: - yield Fore.YELLOW + ir["description"] + Style.RESET_ALL + yield Fore.YELLOW + ir.description + Style.RESET_ALL # Yield the parameter header yield line.format(**fmt) @@ -1253,41 +1080,50 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve if short or k not in self.Dictionary: continue + values = self.GetEntry(k, aslist=True) + entries = self.GetParamsEntry(k, aslist=True) + # FIXME: Using aslist=True ensures that both are lists + assert isinstance(values, list) and isinstance(entries, list) + infos = [] - for info in self.GetAllSubentryInfos(k, compute=not raw): + for i, (value, entry) in enumerate(zip(values, entries)): # Prepare data for printing - - i = info['subindex'] + info = self.GetSubentryInfos(k, i) typename = self.GetTypeName(info['type']) - value = info['value'] # Special formatting on value if isinstance(value, str): value = '"' + value + '"' - elif i and index_range and index_range["name"] in ('rpdom', 'tpdom'): + elif i and index_range and index_range.name in ('rpdom', 'tpdom'): + # FIXME: In PDO mappings, the value is ints + assert isinstance(value, int) index, subindex, _ = self.GetMapIndex(value) - pdo = self.GetSubentryInfos(index, subindex) - suffix = '???' if value else '' - if pdo: - suffix = str(pdo["name"]) - value = "0x{:x} {}".format(value, suffix) + try: + pdo = self.GetSubentryInfos(index, subindex) + value = f"0x{value:x} {pdo['name']}" + except ValueError: + suffix = ' ???' if value else '' + value = f"0x{value:x}{suffix}" elif i and value and (k in (4120, ) or 'COB ID' in info["name"]): - value = "0x{:x}".format(value) + value = f"0x{value:x}" else: value = str(value) - comment = info['comment'] or '' + comment = entry['comment'] or '' if comment: - comment = '{}/* {} */{}'.format(Fore.LIGHTBLACK_EX, info.get('comment'), Style.RESET_ALL) + comment = f"{Fore.LIGHTBLACK_EX}/* {info.get('comment')} */{Style.RESET_ALL}" # Omit printing this subindex if: - if not verbose and i == 0 and fmt['struct'] in ('RECORD', 'NRECORD', 'ARRAY', 'NARRAY') and not comment: + if (not verbose and i == 0 + and fmt['struct'] in ('RECORD', 'NRECORD', 'ARRAY', 'NARRAY') + and not comment + ): continue # Print formatting infos.append({ - 'i': "{:02d}".format(i), + 'i': f"{i:02d}", 'access': info['access'], 'pdo': 'P' if info['pdo'] else ' ', 'name': info['name'], @@ -1307,13 +1143,14 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve } # Generate a format string based on the calculcated column widths + # Legitimate use of % as this is making a string containing format specifiers fmt = "{pre} {i:%ss} {access:%ss} {pdo:%ss} {name:%ss} {type:%ss} {value:%ss} {comment}" % ( - w["i"], w["access"], w["pdo"], w["name"], w["type"], w["value"] # noqa: E126, E241 + w["i"], w["access"], w["pdo"], w["name"], w["type"], w["value"] ) # Print each line using the generated format string - for info in infos: - yield fmt.format(**info) + for infoentry in infos: + yield fmt.format(**infoentry) if not compact and infos: yield "" diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 1c9d918..a3d3eef 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -1,3 +1,4 @@ +"""Module to manage a list of nodes for a CANOpen network.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -19,133 +20,144 @@ import errno import os +from pathlib import Path import shutil +from dataclasses import dataclass from objdictgen import eds_utils +from objdictgen.node import Node +from objdictgen.nodemanager import NodeManager +from objdictgen.typing import TODObj, TODSubObj, TPath # ------------------------------------------------------------------------------ # Definition of NodeList Object # ------------------------------------------------------------------------------ +@dataclass +class SlaveNode: + """SlaveNodes in NodeList.""" + Name: str + EDS: str + Node: Node + class NodeList: """ Class recording a node list for a CANOpen network. """ - def __init__(self, manager, netname=""): - self.Root = "" - self.Manager = manager - self.NetworkName = netname - self.SlaveNodes = {} - self.EDSNodes = {} - self.CurrentSelected = None + def __init__(self, manager: NodeManager, netname=""): + self.Root: Path = Path("") + self.Manager: NodeManager = manager + self.NetworkName: str = netname + self.SlaveNodes: dict[int, SlaveNode] = {} + self.EDSNodes: dict[str, Node] = {} + self.CurrentSelected: int|None = None self.Changed = False - def HasChanged(self): + def HasChanged(self) -> bool: return self.Changed or not self.Manager.CurrentIsSaved() - def GetEDSFolder(self, root_path=None): + def GetEDSFolder(self, root_path: TPath|None = None) -> Path: if root_path is None: root_path = self.Root - return os.path.join(root_path, "eds") + return Path(root_path, "eds") - def GetMasterNodeID(self): - return self.Manager.GetCurrentNodeID() + def GetMasterNodeID(self) -> int: + return self.Manager.current.ID - def GetSlaveName(self, idx): - return self.SlaveNodes[idx]["Name"] + def GetSlaveName(self, idx: int) -> str: + return self.SlaveNodes[idx].Name - def GetSlaveNames(self): + def GetSlaveNames(self) -> list[str]: return [ - "0x%2.2X %s" % (idx, self.SlaveNodes[idx]["Name"]) + f"0x{idx:02X} {self.SlaveNodes[idx].Name}" for idx in sorted(self.SlaveNodes) ] - def GetSlaveIDs(self): + def GetSlaveIDs(self) -> list[int]: return list(sorted(self.SlaveNodes)) - def LoadProject(self, root, netname=None): + def LoadProject(self, root: TPath, netname: str = ""): self.SlaveNodes = {} self.EDSNodes = {} - self.Root = root - if not os.path.exists(self.Root): + self.Root = Path(root) + if not self.Root.exists(): raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), self.Root) eds_folder = self.GetEDSFolder() - if not os.path.exists(eds_folder): - os.mkdir(eds_folder) - # raise ValueError("'%s' folder doesn't contain a 'eds' folder" % self.Root) - - files = os.listdir(eds_folder) - for file in files: - filepath = os.path.join(eds_folder, file) - if os.path.isfile(filepath) and os.path.splitext(filepath)[-1] == ".eds": + if not eds_folder.exists(): + eds_folder.mkdir(parents=True, exist_ok=True) + # raise ValueError(f"'{self.Root}' folder doesn't contain a 'eds' folder") + + for file in eds_folder.iterdir(): + if file.is_file() and file.suffix == ".eds": self.LoadEDS(file) self.LoadMasterNode(netname) self.LoadSlaveNodes(netname) self.NetworkName = netname - def SaveProject(self, netname=None): + def SaveProject(self, netname: str = ""): self.SaveMasterNode(netname) self.SaveNodeList(netname) - def GetEDSFilePath(self, edspath): - _, file = os.path.split(edspath) - eds_folder = self.GetEDSFolder() - return os.path.join(eds_folder, file) + def GetEDSFilePath(self, edspath: TPath) -> Path: + return self.GetEDSFolder() / Path(edspath).name - def ImportEDSFile(self, edspath): - _, file = os.path.split(edspath) - shutil.copy(edspath, self.GetEDSFolder()) - self.LoadEDS(file) + def ImportEDSFile(self, edspath: TPath): + edsfolder = self.GetEDSFolder() + shutil.copy(edspath, edsfolder) + self.LoadEDS(edsfolder / Path(edspath).name) - def LoadEDS(self, eds): - edspath = os.path.join(self.GetEDSFolder(), eds) - node = eds_utils.GenerateNode(edspath) - self.EDSNodes[eds] = node + def LoadEDS(self, eds: TPath): + eds = Path(eds) + node = eds_utils.generate_node(eds) + self.EDSNodes[eds.name] = node - def AddSlaveNode(self, nodename, nodeid, eds): + def AddSlaveNode(self, nodename: str, nodeid: int, eds: str): if eds not in self.EDSNodes: - raise ValueError("'%s' EDS file is not available" % eds) - slave = {"Name": nodename, "EDS": eds, "Node": self.EDSNodes[eds]} + raise ValueError(f"'{eds}' EDS file is not available") + slave = SlaveNode(Name=nodename, EDS=eds, Node=self.EDSNodes[eds]) self.SlaveNodes[nodeid] = slave self.Changed = True - def RemoveSlaveNode(self, index): + def RemoveSlaveNode(self, index: int): if index not in self.SlaveNodes: - raise ValueError("Node with '0x%2.2X' ID doesn't exist" % (index)) + raise ValueError(f"Node with '0x{index:02X}' ID doesn't exist") self.SlaveNodes.pop(index) self.Changed = True - def LoadMasterNode(self, netname=None): + def LoadMasterNode(self, netname: str = "") -> int: if netname: - masterpath = os.path.join(self.Root, "%s_master.od" % netname) + masterpath = self.Root / f"{netname}_master.od" else: - masterpath = os.path.join(self.Root, "master.od") - if os.path.isfile(masterpath): + masterpath = self.Root / "master.od" + if masterpath.is_file(): index = self.Manager.OpenFileInCurrent(masterpath) else: - index = self.Manager.CreateNewNode("MasterNode", 0x00, "master", "", "None", "", "Heartbeat", ["DS302"]) + index = self.Manager.CreateNewNode( + name="MasterNode", id=0x00, type="master", description="", + profile="None", filepath="", nmt="Heartbeat", options=["DS302"], + ) return index - def SaveMasterNode(self, netname=None): + def SaveMasterNode(self, netname: str = ""): if netname: - masterpath = os.path.join(self.Root, "%s_master.od" % netname) + masterpath = self.Root / f"{netname}_master.od" else: - masterpath = os.path.join(self.Root, "master.od") + masterpath = self.Root / "master.od" try: self.Manager.SaveCurrentInFile(masterpath) except Exception as exc: # pylint: disable=broad-except - raise ValueError("Fail to save master node in '%s'" % (masterpath, )) from exc + raise ValueError(f"Fail to save master node in '{masterpath}'") from exc - def LoadSlaveNodes(self, netname=None): - cpjpath = os.path.join(self.Root, "nodelist.cpj") - if os.path.isfile(cpjpath): + def LoadSlaveNodes(self, netname: str = ""): + cpjpath = self.Root / "nodelist.cpj" + if cpjpath.is_file(): try: - networks = eds_utils.ParseCPJFile(cpjpath) + networks = eds_utils.parse_cpj_file(cpjpath) network = None if netname: for net in networks: @@ -161,93 +173,92 @@ def LoadSlaveNodes(self, netname=None): self.AddSlaveNode(node["Name"], nodeid, node["DCFName"]) self.Changed = False except Exception as exc: # pylint: disable=broad-except - raise ValueError("Unable to load CPJ file '%s'" % (cpjpath, )) from exc + raise ValueError(f"Unable to load CPJ file '{cpjpath}'") from exc - def SaveNodeList(self, netname=None): - cpjpath = '' # For linting + def SaveNodeList(self, netname: str = ""): + cpjpath = self.Root / "nodelist.cpj" try: - cpjpath = os.path.join(self.Root, "nodelist.cpj") - content = eds_utils.GenerateCPJContent(self) + content = eds_utils.generate_cpj_content(self) if netname: mode = "a" else: mode = "w" - with open(cpjpath, mode=mode) as f: + with open(cpjpath, mode=mode, encoding="utf-8") as f: f.write(content) self.Changed = False except Exception as exc: # pylint: disable=broad-except - raise ValueError("Fail to save node list in '%s'" % (cpjpath)) from exc + raise ValueError(f"Fail to save node list in '{cpjpath}'") from exc - def GetOrderNumber(self, nodeid): + def GetOrderNumber(self, nodeid: int) -> int: nodeindexes = list(sorted(self.SlaveNodes)) return nodeindexes.index(nodeid) + 1 - def IsCurrentEntry(self, index): + def IsCurrentEntry(self, index: int) -> bool: if self.CurrentSelected is not None: if self.CurrentSelected == 0: - return self.Manager.IsCurrentEntry(index) - node = self.SlaveNodes[self.CurrentSelected]["Node"] + return self.Manager.current.IsEntry(index) + node = self.SlaveNodes[self.CurrentSelected].Node if node: node.ID = self.CurrentSelected return node.IsEntry(index) - return False + raise ValueError("Can't find node") + raise ValueError("No Node selected") - def GetEntryInfos(self, index): + def GetEntryInfos(self, index: int) -> TODObj: if self.CurrentSelected is not None: if self.CurrentSelected == 0: - return self.Manager.GetEntryInfos(index) - node = self.SlaveNodes[self.CurrentSelected]["Node"] + return self.Manager.current.GetEntryInfos(index) + node = self.SlaveNodes[self.CurrentSelected].Node if node: node.ID = self.CurrentSelected return node.GetEntryInfos(index) - return None + raise ValueError("Can't find node") + raise ValueError("No Node selected") - def GetSubentryInfos(self, index, subindex): + def GetSubentryInfos(self, index: int, subindex: int) -> TODSubObj: if self.CurrentSelected is not None: if self.CurrentSelected == 0: - return self.Manager.GetSubentryInfos(index, subindex) - node = self.SlaveNodes[self.CurrentSelected]["Node"] + return self.Manager.current.GetSubentryInfos(index, subindex) + node = self.SlaveNodes[self.CurrentSelected].Node if node: node.ID = self.CurrentSelected return node.GetSubentryInfos(index, subindex) - return None + raise ValueError("Can't find node") + raise ValueError("No Node selected") - def GetCurrentValidIndexes(self, min_, max_): + def GetCurrentValidIndexes(self, minval: int, maxval: int) -> list[tuple[str, int]]: if self.CurrentSelected is not None: if self.CurrentSelected == 0: - return self.Manager.GetCurrentValidIndexes(min_, max_) - node = self.SlaveNodes[self.CurrentSelected]["Node"] + return self.Manager.GetCurrentValidIndexes(minval, maxval) + node = self.SlaveNodes[self.CurrentSelected].Node if node: node.ID = self.CurrentSelected return [ (node.GetEntryName(index), index) - for index in node.GetIndexes() - if min_ <= index <= max_ + for index in node + if minval <= index <= maxval ] raise ValueError("Can't find node") - return [] + raise ValueError("No Node selected") - def GetCurrentEntryValues(self, index): + def GetCurrentEntryValues(self, index: int): if self.CurrentSelected is not None: - node = self.SlaveNodes[self.CurrentSelected]["Node"] + node = self.SlaveNodes[self.CurrentSelected].Node if node: node.ID = self.CurrentSelected return self.Manager.GetNodeEntryValues(node, index) raise ValueError("Can't find node") - return [], [] + raise ValueError("No Node selected") - def AddToMasterDCF(self, node_id, index, subindex, size, value): + def AddToMasterDCF(self, node_id: int, index: int, subindex: int, size: int, value: int): # Adding DCF entry into Master node - if not self.Manager.IsCurrentEntry(0x1F22): + if not self.Manager.current.IsEntry(0x1F22): self.Manager.ManageEntriesOfCurrent([0x1F22], []) self.Manager.AddSubentriesToCurrent(0x1F22, 127) - self.Manager.AddToDCF(node_id, index, subindex, size, value) def main(projectdir): - # pylint: disable=import-outside-toplevel - from .nodemanager import NodeManager manager = NodeManager() @@ -260,8 +271,8 @@ def main(projectdir): for line in node.GetPrintParams(raw=True): print(line) print() - for nodeid, node in nodelist.SlaveNodes.items(): - print("SlaveNode name=%s id=0x%2.2X :" % (node["Name"], nodeid)) - for line in node["Node"].GetPrintParams(): + for nodeid, nodeinfo in nodelist.SlaveNodes.items(): + print(f"SlaveNode name={nodeinfo.Name} id=0x{nodeid:02X} :") + for line in nodeinfo.Node.GetPrintParams(): print(line) print() diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index c3a9647..c3ea9dd 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -1,3 +1,4 @@ +"""Manage the node and the undo buffer.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -19,14 +20,18 @@ import codecs import logging -import os import re +from pathlib import Path +from typing import Container, Generic, TypeVar, cast import colorama from objdictgen import maps -from objdictgen.maps import MAPPING_DICTIONARY, OD -from objdictgen.node import BE_to_LE, Find, ImportProfile, LE_to_BE, Node +from objdictgen.maps import OD, ODMapping +from objdictgen.node import Node +from objdictgen.typing import TODSubObj, TPath + +T = TypeVar("T") log = logging.getLogger('objdictgen') @@ -43,45 +48,42 @@ # Returns a new id -def GetNewId(): +def get_new_id(): global CURRENTID # pylint: disable=global-statement CURRENTID += 1 return CURRENTID -class UndoBuffer: +class UndoBuffer(Generic[T]): """ Class implementing a buffer of changes made on the current editing Object Dictionary """ - def __init__(self, currentstate, issaved=False): + def __init__(self, state: T|None = None, issaved: bool = False): """ Constructor initialising buffer """ - self.Buffer = [] - self.CurrentIndex = -1 - self.MinIndex = -1 - self.MaxIndex = -1 + self.Buffer: list[T|None] = [state if not i else None for i in range(UNDO_BUFFER_LENGTH)] + self.CurrentIndex: int = -1 + self.MinIndex: int = -1 + self.MaxIndex: int = -1 # if current state is defined - if currentstate: + if state is not None: self.CurrentIndex = 0 self.MinIndex = 0 self.MaxIndex = 0 - # Initialising buffer with currentstate at the first place - for i in range(UNDO_BUFFER_LENGTH): - self.Buffer.append(currentstate if not i else None) # Initialising index of state saved if issaved: self.LastSave = 0 else: self.LastSave = -1 - def Buffering(self, currentstate): + def Add(self, state: T): """ Add a new state in buffer """ self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH - self.Buffer[self.CurrentIndex] = currentstate + self.Buffer[self.CurrentIndex] = state # Actualising buffer limits self.MaxIndex = self.CurrentIndex if self.MinIndex == self.CurrentIndex: @@ -91,37 +93,48 @@ def Buffering(self, currentstate): self.MinIndex = (self.MinIndex + 1) % UNDO_BUFFER_LENGTH self.MinIndex = max(self.MinIndex, 0) - def Current(self): + def Current(self) -> T: """ Return current state of buffer """ - return self.Buffer[self.CurrentIndex] - - def Previous(self): + if self.CurrentIndex == -1: + raise AttributeError("No current state in buffer") + current = self.Buffer[self.CurrentIndex] + # FIXME: By design the buffer should not contain None values + assert current is not None + return current + + def Previous(self) -> T: """ Change current state to previous in buffer and return new current state """ - if self.CurrentIndex != self.MinIndex: + if self.CurrentIndex != -1 and self.CurrentIndex != self.MinIndex: self.CurrentIndex = (self.CurrentIndex - 1) % UNDO_BUFFER_LENGTH - return self.Buffer[self.CurrentIndex] - return None + current = self.Buffer[self.CurrentIndex] + # FIXME: By design the buffer should not contain None values + assert current is not None + return current + raise AttributeError("No previous buffer available") - def Next(self): + def Next(self) -> T: """ Change current state to next in buffer and return new current state """ - if self.CurrentIndex != self.MaxIndex: + if self.CurrentIndex != -1 and self.CurrentIndex != self.MaxIndex: self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH - return self.Buffer[self.CurrentIndex] - return None + current = self.Buffer[self.CurrentIndex] + # FIXME: By design the buffer should not contain None values + assert current is not None + return current + raise AttributeError("No next buffer available") - def IsFirst(self): + def IsFirst(self) -> bool: """ Return True if current state is the first in buffer """ return self.CurrentIndex == self.MinIndex - def IsLast(self): + def IsLast(self) -> bool: """ Return True if current state is the last in buffer """ @@ -133,7 +146,7 @@ def CurrentSaved(self): """ self.LastSave = self.CurrentIndex - def IsCurrentSaved(self): + def IsCurrentSaved(self) -> bool: """ Return True if current state is saved """ @@ -150,37 +163,43 @@ def __init__(self): Constructor """ self.LastNewIndex = 0 - self.FilePaths = {} - self.FileNames = {} - self.NodeIndex = None - self.CurrentNode = None - self.UndoBuffers = {} + self.FilePaths: dict[int, Path|None] = {} + self.FileNames: dict[int, str] = {} + self.CurrentNodeIndex: int|None = None + self.CurrentNode: Node|None = None + self.UndoBuffers: dict[int, UndoBuffer[Node]] = {} # -------------------------------------------------------------------------- - # Type and Map Variable Lists + # Properties # -------------------------------------------------------------------------- - def GetCurrentTypeList(self): - """ - Return the list of types defined for the current node - """ - if self.CurrentNode: - return self.CurrentNode.GetTypeList() - return "" - - def GetCurrentMapList(self): - """ - Return the list of variables that can be mapped for the current node - """ - if self.CurrentNode: - return self.CurrentNode.GetMapList() - return "" + @property + def nodeindex(self) -> int: + """The current node index.""" + if self.CurrentNodeIndex is None: + raise AttributeError("No node is currently selected") + return self.CurrentNodeIndex + + @property + def current(self) -> Node: + """The current selected node. It will raise an error if no node is selected.""" + if not self.CurrentNode: + raise AttributeError("No node is currently selected") + return self.CurrentNode + + @property + def current_default(self) -> Node: + """Return the current node or the default node if no current node is selected.""" + return self.CurrentNode if self.CurrentNode else Node() # -------------------------------------------------------------------------- # Create Load and Save Functions # -------------------------------------------------------------------------- - def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, options): # pylint: disable=redefined-builtin, invalid-name + # FIXME: Change to not mask the builtins + def CreateNewNode(self, name: str, id: int, type: str, description: str, + profile: str, filepath: TPath, nmt: str, + options: Container[str]): """ Create a new node and add a new buffer for storing it """ @@ -189,54 +208,54 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt # Load profile given if profile != "None": # Import profile - mapping, menuentries = ImportProfile(filepath) + mapping, menuentries = maps.import_profile(filepath) node.ProfileName = profile node.Profile = mapping node.SpecificMenu = menuentries else: # Default profile node.ProfileName = "None" - node.Profile = {} + node.Profile = ODMapping() node.SpecificMenu = [] # Initialising node self.CurrentNode = node - self.CurrentNode.Name = name - self.CurrentNode.ID = id - self.CurrentNode.Type = type - self.CurrentNode.Description = description - addindexlist = self.GetMandatoryIndexes() + node.Name = name + node.ID = id + node.Type = type + node.Description = description + addindexlist = node.GetMandatoryIndexes() addsubindexlist = [] if nmt == "NodeGuarding": addindexlist.extend([0x100C, 0x100D]) elif nmt == "Heartbeat": addindexlist.append(0x1017) - for option in options: - if option == "DS302": - # Import profile - mapping, menuentries = ImportProfile("DS-302") - self.CurrentNode.DS302 = mapping - self.CurrentNode.SpecificMenu.extend(menuentries) - elif option == "GenSYNC": - addindexlist.extend([0x1005, 0x1006]) - elif option == "Emergency": - addindexlist.append(0x1014) - elif option == "SaveConfig": - addindexlist.extend([0x1010, 0x1011, 0x1020]) - elif option == "StoreEDS": - addindexlist.extend([0x1021, 0x1022]) + if "DS302" in options: + # Import profile + mapping, menuentries = maps.import_profile("DS-302") + node.DS302 = mapping + node.SpecificMenu.extend(menuentries) + if "GenSYNC" in options: + addindexlist.extend([0x1005, 0x1006]) + if "Emergency" in options: + addindexlist.append(0x1014) + if "SaveConfig" in options: + addindexlist.extend([0x1010, 0x1011, 0x1020]) + if "StoreEDS" in options: + addindexlist.extend([0x1021, 0x1022]) if type == "slave": # add default SDO server addindexlist.append(0x1200) # add default 4 receive and 4 transmit PDO - for comm, mapping in [(0x1400, 0x1600), (0x1800, 0x1A00)]: - firstparamindex = self.GetLineFromIndex(comm) - firstmappingindex = self.GetLineFromIndex(mapping) + for paramindex, mapindex in [(0x1400, 0x1600), (0x1800, 0x1A00)]: + firstparamindex = self.GetLineFromIndex(paramindex) + firstmappingindex = self.GetLineFromIndex(mapindex) addindexlist.extend(list(range(firstparamindex, firstparamindex + 4))) for idx in range(firstmappingindex, firstmappingindex + 4): addindexlist.append(idx) addsubindexlist.append((idx, 8)) + # Add a new buffer - index = self.AddNodeBuffer(self.CurrentNode.Copy(), False) + index = self.AddNodeBuffer(node.copy(), False) self.SetCurrentFilePath(None) # Add Mandatory indexes self.ManageEntriesOfCurrent(addindexlist, []) @@ -244,20 +263,20 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt self.AddSubentriesToCurrent(idx, num) return index - def OpenFileInCurrent(self, filepath, load=True): + def OpenFileInCurrent(self, filepath: TPath, load=True) -> int: """ Open a file and store it in a new buffer """ node = Node.LoadFile(filepath) self.CurrentNode = node - self.CurrentNode.ID = 0 + node.ID = 0 - index = self.AddNodeBuffer(self.CurrentNode.Copy(), load) + index = self.AddNodeBuffer(node.copy(), load) self.SetCurrentFilePath(filepath if load else None) return index - def SaveCurrentInFile(self, filepath=None, filetype='', **kwargs): + def SaveCurrentInFile(self, filepath: TPath|None = None, filetype='', **kwargs) -> bool: """ Save current node in a file """ @@ -272,38 +291,39 @@ def SaveCurrentInFile(self, filepath=None, filetype='', **kwargs): return False # Save node in file - ext = os.path.splitext(filepath.lower())[1].lstrip('.') - if filetype: - ext = filetype.lower() + filepath = Path(filepath) + ext = filepath.suffix.lstrip('.').lower() # Save the data node.DumpFile(filepath, filetype=ext, **kwargs) # Update saved state in buffer - if ext not in ('c', 'eds'): - self.UndoBuffers[self.NodeIndex].CurrentSaved() + if ext not in ('c', 'eds') and self.nodeindex in self.UndoBuffers: + self.UndoBuffers[self.nodeindex].CurrentSaved() return True - def CloseCurrent(self, ignore=False): + def CloseCurrent(self, ignore=False) -> bool: """ Close current state """ # Verify if it's not forced that the current node is saved before closing it - if self.NodeIndex in self.UndoBuffers and (self.UndoBuffers[self.NodeIndex].IsCurrentSaved() or ignore): - self.RemoveNodeBuffer(self.NodeIndex) + if (self.CurrentNodeIndex in self.UndoBuffers + and (self.UndoBuffers[self.CurrentNodeIndex].IsCurrentSaved() or ignore) + ): + self.RemoveNodeBuffer(self.CurrentNodeIndex) if len(self.UndoBuffers) > 0: - previousindexes = [idx for idx in self.UndoBuffers if idx < self.NodeIndex] - nextindexes = [idx for idx in self.UndoBuffers if idx > self.NodeIndex] + previousindexes = [idx for idx in self.UndoBuffers if idx < self.CurrentNodeIndex] + nextindexes = [idx for idx in self.UndoBuffers if idx > self.CurrentNodeIndex] if len(previousindexes) > 0: previousindexes.sort() - self.NodeIndex = previousindexes[-1] + self.CurrentNodeIndex = previousindexes[-1] elif len(nextindexes) > 0: nextindexes.sort() - self.NodeIndex = nextindexes[0] + self.CurrentNodeIndex = nextindexes[0] else: - self.NodeIndex = None + self.CurrentNodeIndex = None else: - self.NodeIndex = None + self.CurrentNodeIndex = None return True return False @@ -311,24 +331,25 @@ def CloseCurrent(self, ignore=False): # Add Entries to Current Functions # -------------------------------------------------------------------------- - def AddSubentriesToCurrent(self, index, number, node=None): + def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = None): """ Add the specified number of subentry for the given entry. Verify that total number of subentry (except 0) doesn't exceed nbmax defined """ disable_buffer = node is not None if node is None: - node = self.CurrentNode - assert node # For mypy + node = self.current # Informations about entry length = node.GetEntry(index, 0) + # FIXME: This code assumes that subindex 0 is the length of the entry + assert isinstance(length, int) infos = node.GetEntryInfos(index) subentry_infos = node.GetSubentryInfos(index, 1) # Get default value for subindex if "default" in subentry_infos: default = subentry_infos["default"] else: - default = self.GetTypeDefaultValue(subentry_infos["type"]) + default = node.GetTypeDefaultValue(subentry_infos["type"]) # First case entry is array if infos["struct"] & OD.IdenticalSubindexes: for i in range(1, min(number, subentry_infos["nbmax"] - length) + 1): @@ -338,27 +359,30 @@ def AddSubentriesToCurrent(self, index, number, node=None): return # Second case entry is record (and array), only possible for manufacturer specific if infos["struct"] & OD.MultipleSubindexes and 0x2000 <= index <= 0x5FFF: - values = {"name": "Undefined", "type": 5, "access": "rw", "pdo": True} + values: TODSubObj = {"name": "Undefined", "type": 5, "access": "rw", "pdo": True} for i in range(1, min(number, 0xFE - length) + 1): - node.AddMappingEntry(index, length + i, values=values.copy()) + node.AddMappingSubEntry(index, length + i, values=values.copy()) node.AddEntry(index, length + i, 0) if not disable_buffer: self.BufferCurrentNode() - def RemoveSubentriesFromCurrent(self, index, number): + def RemoveSubentriesFromCurrent(self, index: int, number: int): """ Remove the specified number of subentry for the given entry. Verify that total number of subentry (except 0) isn't less than 1 """ # Informations about entry - infos = self.GetEntryInfos(index) - length = self.CurrentNode.GetEntry(index, 0) - if "nbmin" in infos: - nbmin = infos["nbmin"] - else: - nbmin = 1 + node = self.current + infos = node.GetEntryInfos(index) + length = node.GetEntry(index, 0) + # FIXME: This code assumes that subindex 0 is the length of the entry + assert isinstance(length, int) + nbmin = infos.get("nbmin", 1) # Entry is an array, or is an array/record of manufacturer specific - if infos["struct"] & OD.IdenticalSubindexes or 0x2000 <= index <= 0x5FFF and infos["struct"] & OD.MultipleSubindexes: + # FIXME: What is the intended order of the conditions? or-and on same level + if (infos["struct"] & OD.IdenticalSubindexes or 0x2000 <= index <= 0x5FFF + and infos["struct"] & OD.MultipleSubindexes + ): for i in range(min(number, length - nbmin)): self.RemoveCurrentVariable(index, length - i) self.BufferCurrentNode() @@ -368,7 +392,7 @@ def AddSDOServerToCurrent(self): Add a SDO Server to current node """ # An SDO Server is already defined at index 0x1200 - if self.CurrentNode.IsEntry(0x1200): + if self.current.IsEntry(0x1200): indexlist = [self.GetLineFromIndex(0x1201)] if None not in indexlist: self.ManageEntriesOfCurrent(indexlist, []) @@ -400,12 +424,13 @@ def AddPDOReceiveToCurrent(self): if None not in indexlist: self.ManageEntriesOfCurrent(indexlist, []) - def AddSpecificEntryToCurrent(self, menuitem): + def AddSpecificEntryToCurrent(self, menuitem: str): """ Add a list of entries defined in profile for menu item selected to current node """ - indexlist = [] - for menu, indexes in self.CurrentNode.SpecificMenu: + node = self.current + indexlist: list[int] = [] + for menu, indexes in node.SpecificMenu: if menuitem == menu: indexlist.extend( self.GetLineFromIndex(index) @@ -414,40 +439,41 @@ def AddSpecificEntryToCurrent(self, menuitem): if None not in indexlist: self.ManageEntriesOfCurrent(indexlist, []) - def GetLineFromIndex(self, base_index): + def GetLineFromIndex(self, base_index: int) -> int: """ Search the first index available for a pluri entry from base_index """ + node = self.current found = False index = base_index - infos = self.GetEntryInfos(base_index) + infos = node.GetEntryInfos(base_index) while index < base_index + infos["incr"] * infos["nbmax"] and not found: - if not self.CurrentNode.IsEntry(index): + if not node.IsEntry(index): found = True else: index += infos["incr"] if found: return index - return None + raise ValueError(f"No available index found for 0x{base_index:04X}") - def ManageEntriesOfCurrent(self, addinglist, removinglist, node=None): + def ManageEntriesOfCurrent(self, addinglist: list[int], removinglist: list[int], node: Node|None = None): """ Add entries specified in addinglist and remove entries specified in removinglist """ disable_buffer = node is not None if node is None: - node = self.CurrentNode + node = self.current # Add all the entries in addinglist for index in addinglist: - infos = self.GetEntryInfos(index) + infos = node.GetEntryInfos(index) if infos["struct"] & OD.MultipleSubindexes: # First case entry is an array if infos["struct"] & OD.IdenticalSubindexes: - subentry_infos = self.GetSubentryInfos(index, 1) + subentry_infos = node.GetSubentryInfos(index, 1) if "default" in subentry_infos: default = subentry_infos["default"] else: - default = self.GetTypeDefaultValue(subentry_infos["type"]) + default = node.GetTypeDefaultValue(subentry_infos["type"]) node.AddEntry(index, value=[]) if "nbmin" in subentry_infos: for i in range(subentry_infos["nbmin"]): @@ -456,23 +482,21 @@ def ManageEntriesOfCurrent(self, addinglist, removinglist, node=None): node.AddEntry(index, 1, default) # Second case entry is a record else: - i = 1 - subentry_infos = self.GetSubentryInfos(index, i) - while subentry_infos: + # FIXME: How to retrieve the number of subentries? + for i in range(1, node.GetSubentryLength(index) + 1): + subentry_infos = node.GetSubentryInfos(index, i) if "default" in subentry_infos: default = subentry_infos["default"] else: - default = self.GetTypeDefaultValue(subentry_infos["type"]) + default = node.GetTypeDefaultValue(subentry_infos["type"]) node.AddEntry(index, i, default) - i += 1 - subentry_infos = self.GetSubentryInfos(index, i) # Third case entry is a var else: - subentry_infos = self.GetSubentryInfos(index, 0) + subentry_infos = node.GetSubentryInfos(index, 0) if "default" in subentry_infos: default = subentry_infos["default"] else: - default = self.GetTypeDefaultValue(subentry_infos["type"]) + default = node.GetTypeDefaultValue(subentry_infos["type"]) node.AddEntry(index, 0, default) # Remove all the entries in removinglist for index in removinglist: @@ -480,158 +504,187 @@ def ManageEntriesOfCurrent(self, addinglist, removinglist, node=None): if not disable_buffer: self.BufferCurrentNode() - def SetCurrentEntryToDefault(self, index, subindex, node=None): + def SetCurrentEntryToDefault(self, index: int, subindex:int, node: Node|None = None): """ Reset an subentry from current node to its default value """ disable_buffer = node is not None if node is None: - node = self.CurrentNode + node = self.current if node.IsEntry(index, subindex): - subentry_infos = self.GetSubentryInfos(index, subindex) + subentry_infos = node.GetSubentryInfos(index, subindex) if "default" in subentry_infos: default = subentry_infos["default"] else: - default = self.GetTypeDefaultValue(subentry_infos["type"]) + default = node.GetTypeDefaultValue(subentry_infos["type"]) node.SetEntry(index, subindex, default) if not disable_buffer: self.BufferCurrentNode() - def RemoveCurrentVariable(self, index, subindex=None): + def RemoveCurrentVariable(self, index: int, subindex: int|None = None): """ Remove an entry from current node. Analize the index to perform the correct method """ - assert self.CurrentNode # For mypy - mappings = self.CurrentNode.GetMappings() + node = self.current + mappings = node.GetMappings() if index < 0x1000 and subindex is None: - type_ = self.CurrentNode.GetEntry(index, 1) - for i in mappings[-1]: + entrytype = node.GetEntry(index, 1) + # FIXME: By design the type of index 1 is the object type in int + assert isinstance(entrytype, int) + for i in mappings[-1]: # FIXME: Hard code to access last (UserMapping)? for value in mappings[-1][i]["values"]: if value["type"] == index: - value["type"] = type_ - self.CurrentNode.RemoveMappingEntry(index) - self.CurrentNode.RemoveEntry(index) + value["type"] = entrytype + node.RemoveMappingEntry(index) + node.RemoveEntry(index) elif index == 0x1200 and subindex is None: - self.CurrentNode.RemoveEntry(0x1200) + node.RemoveEntry(0x1200) elif 0x1201 <= index <= 0x127F and subindex is None: - self.CurrentNode.RemoveLine(index, 0x127F) + node.RemoveLine(index, 0x127F) elif 0x1280 <= index <= 0x12FF and subindex is None: - self.CurrentNode.RemoveLine(index, 0x12FF) + node.RemoveLine(index, 0x12FF) elif 0x1400 <= index <= 0x15FF or 0x1600 <= index <= 0x17FF and subindex is None: if 0x1600 <= index <= 0x17FF and subindex is None: index -= 0x200 - self.CurrentNode.RemoveLine(index, 0x15FF) - self.CurrentNode.RemoveLine(index + 0x200, 0x17FF) + node.RemoveLine(index, 0x15FF) + node.RemoveLine(index + 0x200, 0x17FF) elif 0x1800 <= index <= 0x19FF or 0x1A00 <= index <= 0x1BFF and subindex is None: if 0x1A00 <= index <= 0x1BFF: index -= 0x200 - self.CurrentNode.RemoveLine(index, 0x19FF) - self.CurrentNode.RemoveLine(index + 0x200, 0x1BFF) + node.RemoveLine(index, 0x19FF) + node.RemoveLine(index + 0x200, 0x1BFF) else: found = False - for _, list_ in self.CurrentNode.SpecificMenu: - for i in list_: - iinfos = self.GetEntryInfos(i) + for _, menulist in node.SpecificMenu: + for i in menulist: + iinfos = node.GetEntryInfos(i) indexes = [i + incr * iinfos["incr"] for incr in range(iinfos["nbmax"])] if index in indexes: found = True diff = index - i - for j in list_: - jinfos = self.GetEntryInfos(j) - self.CurrentNode.RemoveLine(j + diff, j + jinfos["incr"] * jinfos["nbmax"], jinfos["incr"]) - self.CurrentNode.RemoveMapVariable(index, subindex) + for j in menulist: + jinfos = node.GetEntryInfos(j) + node.RemoveLine( + j + diff, j + jinfos["incr"] * jinfos["nbmax"], jinfos["incr"] + ) + node.RemoveMapVariable(index, subindex or 0) if not found: - infos = self.GetEntryInfos(index) - if not infos["need"]: - self.CurrentNode.RemoveEntry(index, subindex) + infos = node.GetEntryInfos(index) + if not infos.get("need"): + node.RemoveEntry(index, subindex) if index in mappings[-1]: - self.CurrentNode.RemoveMappingEntry(index, subindex) + node.RemoveMappingEntry(index, subindex) - def AddMapVariableToCurrent(self, index, name, struct, number, node=None): + def AddMapVariableToCurrent(self, index: int, name: str, struct: int, number: int, node: Node|None = None): if 0x2000 <= index <= 0x5FFF: disable_buffer = node is not None if node is None: - node = self.CurrentNode - assert node # For mypy + node = self.current if node.IsEntry(index): - raise ValueError("Index 0x%04X already defined!" % index) - node.AddMappingEntry(index, name=name, struct=struct) + raise ValueError(f"Index 0x{index:04X} already defined!") + node.AddMappingEntry(index, entry={"name": name, "struct": struct}) if struct == OD.VAR: - values = {"name": name, "type": 0x05, "access": "rw", "pdo": True} - node.AddMappingEntry(index, 0, values=values) + values: TODSubObj = {"name": name, "type": 0x05, "access": "rw", "pdo": True} + node.AddMappingSubEntry(index, 0, values=values) node.AddEntry(index, 0, 0) else: values = {"name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False} - node.AddMappingEntry(index, 0, values=values) + node.AddMappingSubEntry(index, 0, values=values) if struct == OD.ARRAY: - values = {"name": name + " %d[(sub)]", "type": 0x05, "access": "rw", "pdo": True, "nbmax": 0xFE} - node.AddMappingEntry(index, 1, values=values) + values = { + "name": name + " %d[(sub)]", "type": 0x05, + "access": "rw", "pdo": True, "nbmax": 0xFE, + } + node.AddMappingSubEntry(index, 1, values=values) for i in range(number): node.AddEntry(index, i + 1, 0) else: for i in range(number): values = {"name": "Undefined", "type": 0x05, "access": "rw", "pdo": True} - node.AddMappingEntry(index, i + 1, values=values) + node.AddMappingSubEntry(index, i + 1, values=values) node.AddEntry(index, i + 1, 0) if not disable_buffer: self.BufferCurrentNode() return - raise ValueError("Index 0x%04X isn't a valid index for Map Variable!" % index) + raise ValueError(f"Index 0x{index:04X} isn't a valid index for Map Variable!") - def AddUserTypeToCurrent(self, type_, min_, max_, length): - assert self.CurrentNode # For mypy + def AddUserTypeToCurrent(self, objtype: int, minval: int, maxval: int, length: int): + node = self.current index = 0xA0 - while index < 0x100 and self.CurrentNode.IsEntry(index): + while index < 0x100 and node.IsEntry(index): index += 1 if index >= 0x100: raise ValueError("Too many User Types have already been defined!") - customisabletypes = self.GetCustomisableTypes() - name, valuetype = customisabletypes[type_] - size = self.GetEntryInfos(type_)["size"] - default = self.GetTypeDefaultValue(type_) + customisabletypes = node.GetCustomisableTypes() + name, valuetype = customisabletypes[objtype] + size = node.GetEntryInfos(objtype)["size"] + default = node.GetTypeDefaultValue(objtype) if valuetype == 0: - self.CurrentNode.AddMappingEntry(index, name="%s[%d-%d]" % (name, min_, max_), struct=OD.RECORD, size=size, default=default) - self.CurrentNode.AddMappingEntry(index, 0, values={"name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 1, values={"name": "Type", "type": 0x05, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 2, values={"name": "Minimum Value", "type": type_, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 3, values={"name": "Maximum Value", "type": type_, "access": "ro", "pdo": False}) - self.CurrentNode.AddEntry(index, 1, type_) - self.CurrentNode.AddEntry(index, 2, min_) - self.CurrentNode.AddEntry(index, 3, max_) + node.AddMappingEntry(index, entry={ + "name": f"{name}[{minval}-{maxval}]", "struct": OD.RECORD, + "size": size, "default": default, + }) + node.AddMappingSubEntry(index, 0, values={ + "name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 1, values={ + "name": "Type", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 2, values={ + "name": "Minimum Value", "type": objtype, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 3, values={ + "name": "Maximum Value", "type": objtype, "access": "ro", "pdo": False, + }) + node.AddEntry(index, 1, objtype) + node.AddEntry(index, 2, minval) + node.AddEntry(index, 3, maxval) elif valuetype == 1: - self.CurrentNode.AddMappingEntry(index, name="%s%d" % (name, length), struct=OD.RECORD, size=length * size, default=default) - self.CurrentNode.AddMappingEntry(index, 0, values={"name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 1, values={"name": "Type", "type": 0x05, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 2, values={"name": "Length", "type": 0x05, "access": "ro", "pdo": False}) - self.CurrentNode.AddEntry(index, 1, type_) - self.CurrentNode.AddEntry(index, 2, length) + node.AddMappingEntry(index, entry={ + "name": f"{name}{length}", "struct": OD.RECORD, + "size": length * size, "default": default, + }) + node.AddMappingSubEntry(index, 0, values={ + "name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 1, values={ + "name": "Type", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 2, values={ + "name": "Length", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddEntry(index, 1, objtype) + node.AddEntry(index, 2, length) self.BufferCurrentNode() # -------------------------------------------------------------------------- # Modify Entry and Mapping Functions # -------------------------------------------------------------------------- - def SetCurrentEntryCallbacks(self, index, value): - if self.CurrentNode and self.CurrentNode.IsEntry(index): - entry_infos = self.GetEntryInfos(index) + def SetCurrentEntryCallbacks(self, index: int, value: bool): + node = self.current + if node.IsEntry(index): + entry_infos = node.GetEntryInfos(index) if "callback" not in entry_infos: - self.CurrentNode.SetParamsEntry(index, None, callback=value) + # FIXME: This operation adds params directly to the entry + # regardless if the index object has subindexes. It should be + # investigated if this is the indended behavior. + node.SetParamsEntry(index, params={"callback": value}) self.BufferCurrentNode() - def SetCurrentEntry(self, index, subindex, value, name, editor, node=None): + def SetCurrentEntry(self, index: int, subindex: int, value: str, name: str, editor: str, node: Node|None = None): disable_buffer = node is not None if node is None: - node = self.CurrentNode + node = self.current if node and node.IsEntry(index): if name == "value": if editor == "map": - value = node.GetMapValue(value) - if value is not None: - node.SetEntry(index, subindex, value) + nvalue = node.GetMapValue(value) + node.SetEntry(index, subindex, nvalue) elif editor == "bool": - value = value == "True" - node.SetEntry(index, subindex, value) + nvalue = value == "True" + node.SetEntry(index, subindex, nvalue) elif editor == "time": node.SetEntry(index, subindex, value) elif editor == "number": @@ -644,93 +697,106 @@ def SetCurrentEntry(self, index, subindex, value, name, editor, node=None): # Might fail with binascii.Error if hex is malformed if len(value) % 2 != 0: value = "0" + value - value = codecs.decode(value, 'hex_codec') - node.SetEntry(index, subindex, value) + bvalue = codecs.decode(value, 'hex_codec').decode() + node.SetEntry(index, subindex, bvalue) elif editor == "dcf": node.SetEntry(index, subindex, value) else: - subentry_infos = self.GetSubentryInfos(index, subindex) - type_ = subentry_infos["type"] + subentry_infos = node.GetSubentryInfos(index, subindex) + objtype = subentry_infos["type"] dic = dict(maps.CUSTOMISABLE_TYPES) - if type_ not in dic: - type_ = node.GetEntry(type_)[1] - if dic[type_] == 0: + if objtype not in dic: + # FIXME: Subobj 1 is the objtype, which should be int by design + objtype = cast(int, node.GetEntry(objtype)[1]) # type: ignore[index] + # FIXME: If objtype is not in dic, this will raise a KeyError + if dic[objtype] == 0: # Might fail if number is malformed + ivalue: int|str if value.startswith("$NODEID"): - value = '"%s"' % value + ivalue = f'"{value}"' elif value.startswith("0x"): - value = int(value, 16) + ivalue = int(value, 16) else: - value = int(value) - node.SetEntry(index, subindex, value) + ivalue = int(value) + node.SetEntry(index, subindex, ivalue) else: node.SetEntry(index, subindex, value) elif name in ["comment", "save", "buffer_size"]: - if editor == "option": - value = value == "Yes" if name == "save": - node.SetParamsEntry(index, subindex, save=value) + node.SetParamsEntry(index, subindex, params={"save": value == "Yes"}) elif name == "comment": - node.SetParamsEntry(index, subindex, comment=value) + node.SetParamsEntry(index, subindex, params={"comment": value}) elif name == "buffer_size": # Might fail with ValueError if number is malformed - value = int(value) - if value <= 0: + nvalue = int(value) + if nvalue <= 0: raise ValueError("Number must be positive") - node.SetParamsEntry(index, subindex, buffer_size=value) + node.SetParamsEntry(index, subindex, params={"buffer_size": nvalue}) else: + nvalue: str|int = value if editor == "type": - value = self.GetTypeIndex(value) - size = self.GetEntryInfos(value)["size"] + nvalue = node.GetTypeIndex(value) + # All type object shall have size + size = node.GetEntryInfos(nvalue)["size"] node.UpdateMapVariable(index, subindex, size) elif editor in ["access", "raccess"]: - dic = { + nvalue = { # type: ignore[assignment] access: abbrev for abbrev, access in maps.ACCESS_TYPE.items() - } - value = dic[value] + }[value] if editor == "raccess" and not node.IsMappingEntry(index): - entry_infos = self.GetEntryInfos(index) - subindex0_infos = self.GetSubentryInfos(index, 0, False).copy() - subindex1_infos = self.GetSubentryInfos(index, 1, False).copy() - node.AddMappingEntry(index, name=entry_infos["name"], struct=OD.ARRAY) - node.AddMappingEntry(index, 0, values=subindex0_infos) - node.AddMappingEntry(index, 1, values=subindex1_infos) - node.SetMappingEntry(index, subindex, values={name: value}) + entry_infos = node.GetEntryInfos(index) + subindex0_infos = node.GetSubentryInfos(index, 0, False).copy() + subindex1_infos = node.GetSubentryInfos(index, 1, False).copy() + node.AddMappingEntry(index, entry={"name": entry_infos["name"], "struct": OD.ARRAY}) + node.AddMappingSubEntry(index, 0, values=subindex0_infos) + node.AddMappingSubEntry(index, 1, values=subindex1_infos) + node.SetMappingSubEntry(index, subindex, values={name: nvalue}) # type: ignore[misc] if not disable_buffer: self.BufferCurrentNode() - def SetCurrentEntryName(self, index, name): - self.CurrentNode.SetMappingEntry(index, name=name) + def SetCurrentEntryName(self, index: int, name: str): + self.current.SetMappingEntry(index, entry={"name": name}) self.BufferCurrentNode() - def SetCurrentUserType(self, index, type_, min_, max_, length): - assert self.CurrentNode # For mypy - customisabletypes = self.GetCustomisableTypes() - _, valuetype = self.GetCustomisedTypeValues(index) - name, new_valuetype = customisabletypes[type_] - size = self.GetEntryInfos(type_)["size"] - default = self.GetTypeDefaultValue(type_) + def SetCurrentUserType(self, index: int, objtype: int, minval: int, maxval: int, length: int): + node = self.current + customisabletypes = node.GetCustomisableTypes() + _, valuetype = node.GetCustomisedTypeValues(index) + name, new_valuetype = customisabletypes[objtype] + size = node.GetEntryInfos(objtype)["size"] + default = node.GetTypeDefaultValue(objtype) if new_valuetype == 0: - self.CurrentNode.SetMappingEntry(index, name="%s[%d-%d]" % (name, min_, max_), struct=OD.RECORD, size=size, default=default) + node.SetMappingEntry(index, entry={ + "name": f"{name}[{minval}-{maxval}]", "struct": OD.RECORD, + "size": size, "default": default, + }) if valuetype == 1: - self.CurrentNode.SetMappingEntry(index, 2, values={"name": "Minimum Value", "type": type_, "access": "ro", "pdo": False}) - self.CurrentNode.AddMappingEntry(index, 3, values={"name": "Maximum Value", "type": type_, "access": "ro", "pdo": False}) - self.CurrentNode.SetEntry(index, 1, type_) - self.CurrentNode.SetEntry(index, 2, min_) + node.SetMappingSubEntry(index, 2, values={ + "name": "Minimum Value", "type": objtype, "access": "ro", "pdo": False, + }) + node.AddMappingSubEntry(index, 3, values={ + "name": "Maximum Value", "type": objtype, "access": "ro", "pdo": False, + }) + node.SetEntry(index, 1, objtype) + node.SetEntry(index, 2, minval) if valuetype == 1: - self.CurrentNode.AddEntry(index, 3, max_) + node.AddEntry(index, 3, maxval) else: - self.CurrentNode.SetEntry(index, 3, max_) + node.SetEntry(index, 3, maxval) elif new_valuetype == 1: - self.CurrentNode.SetMappingEntry(index, name="%s%d" % (name, length), struct=OD.RECORD, size=size, default=default) + node.SetMappingEntry(index, entry={ + "name": f"{name}{length}", "struct": OD.RECORD, "size": size, "default": default, + }) if valuetype == 0: - self.CurrentNode.SetMappingEntry(index, 2, values={"name": "Length", "type": 0x02, "access": "ro", "pdo": False}) - self.CurrentNode.RemoveMappingEntry(index, 3) - self.CurrentNode.SetEntry(index, 1, type_) - self.CurrentNode.SetEntry(index, 2, length) + node.SetMappingSubEntry(index, 2, values={ + "name": "Length", "type": 0x02, "access": "ro", "pdo": False, + }) + node.RemoveMappingEntry(index, 3) + node.SetEntry(index, 1, objtype) + node.SetEntry(index, 2, length) if valuetype == 0: - self.CurrentNode.RemoveEntry(index, 3) + node.RemoveEntry(index, 3) self.BufferCurrentNode() # -------------------------------------------------------------------------- @@ -738,249 +804,200 @@ def SetCurrentUserType(self, index, type_, min_, max_, length): # -------------------------------------------------------------------------- def BufferCurrentNode(self): - self.UndoBuffers[self.NodeIndex].Buffering(self.CurrentNode.Copy()) + self.UndoBuffers[self.nodeindex].Add(self.current.copy()) - def CurrentIsSaved(self): - return self.UndoBuffers[self.NodeIndex].IsCurrentSaved() + def CurrentIsSaved(self) -> bool: + return self.UndoBuffers[self.nodeindex].IsCurrentSaved() - def OneFileHasChanged(self): + def OneFileHasChanged(self) -> bool: return any( not buffer for buffer in self.UndoBuffers.values() ) - def GetBufferNumber(self): + def GetBufferNumber(self) -> int: return len(self.UndoBuffers) - def GetBufferIndexes(self): + def GetBufferIndexes(self) -> list[int]: return list(self.UndoBuffers) def LoadCurrentPrevious(self): - self.CurrentNode = self.UndoBuffers[self.NodeIndex].Previous().Copy() + self.CurrentNode = self.UndoBuffers[self.nodeindex].Previous().copy() def LoadCurrentNext(self): - self.CurrentNode = self.UndoBuffers[self.NodeIndex].Next().Copy() + self.CurrentNode = self.UndoBuffers[self.nodeindex].Next().copy() - def AddNodeBuffer(self, currentstate=None, issaved=False): - self.NodeIndex = GetNewId() - self.UndoBuffers[self.NodeIndex] = UndoBuffer(currentstate, issaved) - self.FilePaths[self.NodeIndex] = "" - self.FileNames[self.NodeIndex] = "" - return self.NodeIndex + def AddNodeBuffer(self, currentstate: Node|None = None, issaved=False) -> int: + nodeindex = get_new_id() + self.CurrentNodeIndex = nodeindex + self.UndoBuffers[nodeindex] = UndoBuffer(currentstate, issaved) + self.FilePaths[nodeindex] = None + self.FileNames[nodeindex] = "" + return nodeindex - def ChangeCurrentNode(self, index): + def ChangeCurrentNode(self, index: int): if index in self.UndoBuffers: - self.NodeIndex = index - self.CurrentNode = self.UndoBuffers[self.NodeIndex].Current().Copy() + self.CurrentNodeIndex = index + self.CurrentNode = self.UndoBuffers[index].Current().copy() - def RemoveNodeBuffer(self, index): + def RemoveNodeBuffer(self, index: int): self.UndoBuffers.pop(index) self.FilePaths.pop(index) self.FileNames.pop(index) - def GetCurrentFilename(self): - return self.GetFilename(self.NodeIndex) + def GetCurrentFilename(self) -> str: + return self.GetFilename(self.nodeindex) - def GetAllFilenames(self): + def GetAllFilenames(self) -> list[str]: return [ self.GetFilename(idx) for idx in sorted(self.UndoBuffers) ] - def GetFilename(self, index): + def GetFilename(self, index: int) -> str: if self.UndoBuffers[index].IsCurrentSaved(): return self.FileNames[index] - return "~%s~" % self.FileNames[index] + return f"~{self.FileNames[index]}~" - def SetCurrentFilePath(self, filepath): - self.FilePaths[self.NodeIndex] = filepath + def SetCurrentFilePath(self, filepath: TPath|None): + nodeindex = self.nodeindex if filepath: - self.FileNames[self.NodeIndex] = os.path.splitext(os.path.basename(filepath))[0] + path = Path(filepath) + self.FilePaths[nodeindex] = path + self.FileNames[nodeindex] = path.stem else: self.LastNewIndex += 1 - self.FileNames[self.NodeIndex] = "Unnamed%d" % self.LastNewIndex + self.FilePaths[nodeindex] = None + self.FileNames[nodeindex] = f"Unnamed{self.LastNewIndex}" - def GetCurrentFilePath(self): + def GetCurrentFilePath(self) -> Path|None: if len(self.FilePaths) > 0: - return self.FilePaths[self.NodeIndex] + return self.FilePaths[self.nodeindex] return None - def GetCurrentBufferState(self): - first = self.UndoBuffers[self.NodeIndex].IsFirst() - last = self.UndoBuffers[self.NodeIndex].IsLast() + def GetCurrentBufferState(self) -> tuple[bool, bool]: + first = self.UndoBuffers[self.nodeindex].IsFirst() + last = self.UndoBuffers[self.nodeindex].IsLast() return not first, not last # -------------------------------------------------------------------------- # Profiles Management Functions # -------------------------------------------------------------------------- - def GetCurrentCommunicationLists(self): - list_ = [] - for index in MAPPING_DICTIONARY: + def GetCurrentCommunicationLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: + commlist = [] + for index in maps.MAPPING_DICTIONARY: if 0x1000 <= index < 0x1200: - list_.append(index) - return self.GetProfileLists(MAPPING_DICTIONARY, list_) + commlist.append(index) + return self.GetProfileLists(maps.MAPPING_DICTIONARY, commlist) - def GetCurrentDS302Lists(self): - return self.GetSpecificProfileLists(self.CurrentNode.DS302) + def GetCurrentDS302Lists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: + return self.GetSpecificProfileLists(self.current.DS302) - def GetCurrentProfileLists(self): - return self.GetSpecificProfileLists(self.CurrentNode.Profile) + def GetCurrentProfileLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: + return self.GetSpecificProfileLists(self.current.Profile) - def GetSpecificProfileLists(self, mappingdictionary): + def GetSpecificProfileLists(self, mappingdictionary: ODMapping) -> tuple[dict[int, tuple[str, bool]], list[int]]: validlist = [] exclusionlist = [] - for _, list_ in self.CurrentNode.SpecificMenu: - exclusionlist.extend(list_) + for _, menulist in self.current.SpecificMenu: + exclusionlist.extend(menulist) for index in mappingdictionary: if index not in exclusionlist: validlist.append(index) return self.GetProfileLists(mappingdictionary, validlist) - def GetProfileLists(self, mappingdictionary, list_): - dictionary = {} - current = [] - for index in list_: + def GetProfileLists(self, mappingdictionary: ODMapping, + profilelist: list[int]) -> tuple[dict[int, tuple[str, bool]], list[int]]: + dictionary: dict[int, tuple[str, bool]] = {} + current: list[int] = [] + node = self.current + for index in profilelist: dictionary[index] = (mappingdictionary[index]["name"], mappingdictionary[index]["need"]) - if self.CurrentNode.IsEntry(index): + if node.IsEntry(index): current.append(index) return dictionary, current - def GetCurrentNextMapIndex(self): - if self.CurrentNode: - index = 0x2000 - while self.CurrentNode.IsEntry(index) and index < 0x5FFF: - index += 1 - if index < 0x6000: + def GetCurrentNextMapIndex(self) -> int: + node = self.current + for index in range(0x2000, 0x5FFF): + if not node.IsEntry(index): return index - return None - - def CurrentDS302Defined(self): - if self.CurrentNode: - return len(self.CurrentNode.DS302) > 0 - return False - - def GetUnusedParameters(self): - node = self.CurrentNode - if not node: - raise ValueError("No node loaded") - return node.GetUnusedParameters() - - def RemoveParams(self, remove): - node = self.CurrentNode - if not node: - raise ValueError("No node loaded") - - for index in remove: - node.RemoveIndex(index) - + raise ValueError("No more free index available in the range 0x2000-0x5FFF") # -------------------------------------------------------------------------- # Node State and Values Functions # -------------------------------------------------------------------------- - def GetCurrentNodeName(self): - if self.CurrentNode: - return self.CurrentNode.Name - return "" - - def GetCurrentNodeID(self, node=None): # pylint: disable=unused-argument - if self.CurrentNode: - return self.CurrentNode.ID - return None - - def GetCurrentNodeInfos(self): - name = self.CurrentNode.Name - id_ = self.CurrentNode.ID - type_ = self.CurrentNode.Type - description = self.CurrentNode.Description or "" - return name, id_, type_, description - - def SetCurrentNodeInfos(self, name, id_, type_, description): - self.CurrentNode.Name = name - self.CurrentNode.ID = id_ - self.CurrentNode.Type = type_ - self.CurrentNode.Description = description + def GetCurrentNodeInfos(self) -> tuple[str, int, str, str]: + node = self.current + name = node.Name + nodeid = node.ID + nodetype = node.Type + description = node.Description + return name, nodeid, nodetype, description + + def SetCurrentNodeInfos(self, name: str, nodeid: int, nodetype: str, description: str): + node = self.current + node.Name = name + node.ID = nodeid + node.Type = nodetype + node.Description = description self.BufferCurrentNode() - def GetCurrentNodeDefaultStringSize(self): - if self.CurrentNode: - return self.CurrentNode.DefaultStringSize - return Node.DefaultStringSize - - def SetCurrentNodeDefaultStringSize(self, size): - if self.CurrentNode: - self.CurrentNode.DefaultStringSize = size - else: - Node.DefaultStringSize = size - - def GetCurrentProfileName(self): - if self.CurrentNode: - return self.CurrentNode.ProfileName - return "" - - def IsCurrentEntry(self, index): - if self.CurrentNode: - return self.CurrentNode.IsEntry(index) - return False - - def GetCurrentValidIndexes(self, min_, max_): + def GetCurrentValidIndexes(self, minval: int, maxval: int) -> list[tuple[str, int]]: + node = self.current return [ - (self.GetEntryName(index), index) - for index in self.CurrentNode.GetIndexes() - if min_ <= index <= max_ + (node.GetEntryName(index), index) + for index in node + if minval <= index <= maxval ] - def GetCurrentValidChoices(self, min_, max_): - validchoices = [] + def GetCurrentValidChoices(self, minval: int, maxval: int) -> list[tuple[str, int|None],]: + node = self.current + validchoices: list[tuple[str, int|None]] = [] exclusionlist = [] - for menu, indexes in self.CurrentNode.SpecificMenu: + for menu, indexes in node.SpecificMenu: exclusionlist.extend(indexes) good = True for index in indexes: - good &= min_ <= index <= max_ + good &= minval <= index <= maxval if good: + # FIXME: What does the "None" here mean for the index? validchoices.append((menu, None)) - list_ = [index for index in MAPPING_DICTIONARY if index >= 0x1000] - profiles = self.CurrentNode.GetMappings(False) + indices = [index for index in maps.MAPPING_DICTIONARY if index >= 0x1000] + profiles = node.GetMappings(False) for profile in profiles: - list_.extend(list(profile)) - for index in sorted(list_): - if min_ <= index <= max_ and not self.CurrentNode.IsEntry(index) and index not in exclusionlist: - validchoices.append((self.GetEntryName(index), index)) + indices.extend(list(profile)) + for index in sorted(indices): + if (minval <= index <= maxval and not node.IsEntry(index) + and index not in exclusionlist + ): + validchoices.append((node.GetEntryName(index), index)) return validchoices - def HasCurrentEntryCallbacks(self, index): - if self.CurrentNode: - return self.CurrentNode.HasEntryCallbacks(index) - return False - - def GetCurrentEntryValues(self, index): - if self.CurrentNode: - return self.GetNodeEntryValues(self.CurrentNode, index) - return None - - def GetNodeEntryValues(self, node, index): + def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[dict]]|None: if node and node.IsEntry(index): entry_infos = node.GetEntryInfos(index) - data = [] - editors = [] + # FIXME: data and editors must be described better as they are returned from this function + data: list[dict] = [] + editors: list[dict] = [] values = node.GetEntry(index, compute=False) params = node.GetParamsEntry(index) if isinstance(values, list): for i, value in enumerate(values): data.append({"value": value}) - data[-1].update(params[i]) + data[-1].update(params[i]) # type: ignore[literal-required] else: data.append({"value": values}) - data[-1].update(params) + data[-1].update(params) # type: ignore[arg-type] for i, dic in enumerate(data): dic["comment"] = dic["comment"] or "" dic["buffer_size"] = dic["buffer_size"] or "" infos = node.GetSubentryInfos(index, i) if infos["name"] == "Number of Entries": dic["buffer_size"] = "" - dic["subindex"] = "0x%02X" % i + dic["subindex"] = f"0x{i:02X}" dic["name"] = infos["name"] dic["type"] = node.GetTypeName(infos["type"]) if dic["type"] is None: @@ -999,6 +1016,7 @@ def GetNodeEntryValues(self, node, index): if 0x1600 <= index <= 0x17FF or 0x1A00 <= index <= 0x1C00: editor["access"] = "raccess" else: + # FIXME: Currently node.GetSubentryInfos(index, i) incorrectly adds this if infos["user_defined"]: if entry_infos["struct"] & OD.IdenticalSubindexes: if i == 1: @@ -1016,7 +1034,11 @@ def GetNodeEntryValues(self, node, index): editor["value"] = "map" dic["value"] = node.GetMapName(dic["value"]) else: - if dic["type"].startswith("VISIBLE_STRING") or dic["type"].startswith("OCTET_STRING"): + # FIXME: dic["type"] is a string by design + assert isinstance(dic["type"], str) + if (dic["type"].startswith("VISIBLE_STRING") + or dic["type"].startswith("OCTET_STRING") + ): editor["value"] = "string" elif dic["type"] in ["TIME_OF_DAY", "TIME_DIFFERENCE"]: editor["value"] = "time" @@ -1025,118 +1047,59 @@ def GetNodeEntryValues(self, node, index): editor["value"] = "dcf" else: editor["value"] = "domain" - dic["value"] = codecs.encode(dic["value"].encode(), 'hex_codec') + dic["value"] = codecs.encode(dic["value"].encode(), 'hex_codec').decode() elif dic["type"] == "BOOLEAN": editor["value"] = "bool" dic["value"] = maps.BOOL_TYPE[dic["value"]] dic["buffer_size"] = "" result = type_model.match(dic["type"]) if result: - values = result.groups() - if values[0] == "UNSIGNED": + if result[1] == "UNSIGNED": dic["buffer_size"] = "" try: - fmt = "0x%0" + str(int(values[1]) // 4) + "X" + fmt = "0x{:0" + str(int(result[2]) // 4) + "X}" except ValueError as exc: - log.debug("ValueError: '%s': %s" % (values[1], exc)) + log.debug("ValueError: '%s': %s", result[2], exc) raise # FIXME: Originial code swallows exception try: - dic["value"] = fmt % dic["value"] - except TypeError as exc: - log.debug("ValueError: '%s': %s" % (dic["value"], exc)) - # FIXME: dict["value"] can contain $NODEID for PDOs which is not an int i.e. $NODEID+0x200 + dic["value"] = fmt.format(dic["value"]) + except ValueError as exc: + log.debug("ValueError: '%s': %s", dic["value"], exc) + # FIXME: dict["value"] can contain $NODEID for PDOs i.e. $NODEID+0x200 editor["value"] = "string" - if values[0] == "INTEGER": + if result[1] == "INTEGER": editor["value"] = "number" dic["buffer_size"] = "" - elif values[0] == "REAL": + elif result[1] == "REAL": editor["value"] = "float" dic["buffer_size"] = "" - elif values[0] in ["VISIBLE_STRING", "OCTET_STRING"]: - editor["length"] = values[0] + elif result[1] in ["VISIBLE_STRING", "OCTET_STRING"]: + editor["length"] = result[1] result = range_model.match(dic["type"]) if result: - values = result.groups() - if values[0] in ["UNSIGNED", "INTEGER", "REAL"]: - editor["min"] = values[2] - editor["max"] = values[3] + if result[1] in ["UNSIGNED", "INTEGER", "REAL"]: + editor["min"] = result[3] + editor["max"] = result[4] dic["buffer_size"] = "" editors.append(editor) return data, editors return None - def AddToDCF(self, node_id, index, subindex, size, value): - if self.CurrentNode.IsEntry(0x1F22, node_id): - dcf_value = self.CurrentNode.GetEntry(0x1F22, node_id) + def AddToDCF(self, node_id: int, index: int, subindex: int, size: int, value: int): + node = self.current + if node.IsEntry(0x1F22, node_id): + dcf_value = node.GetEntry(0x1F22, node_id) + # FIXME: This code assumes that the DCF value is a list + assert isinstance(dcf_value, list) if dcf_value: - nbparams = BE_to_LE(dcf_value[:4]) + nbparams = maps.be_to_le(dcf_value[:4]) else: nbparams = 0 - new_value = LE_to_BE(nbparams + 1, 4) + dcf_value[4:] - new_value += LE_to_BE(index, 2) + LE_to_BE(subindex, 1) + LE_to_BE(size, 4) + LE_to_BE(value, size) - self.CurrentNode.SetEntry(0x1F22, node_id, new_value) - - # -------------------------------------------------------------------------- - # Node Informations Functions - # -------------------------------------------------------------------------- - - def GetCustomisedTypeValues(self, index): - if self.CurrentNode: - values = self.CurrentNode.GetEntry(index) - customisabletypes = self.GetCustomisableTypes() - return values, customisabletypes[values[1]][1] - return None, None - - def GetEntryName(self, index, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetEntryName(index, compute) - return Find.EntryName(index, MAPPING_DICTIONARY, compute) - - def GetEntryInfos(self, index, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetEntryInfos(index, compute) - return Find.EntryInfos(index, MAPPING_DICTIONARY, compute) - - def GetSubentryInfos(self, index, subindex, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetSubentryInfos(index, subindex, compute) - result = Find.SubentryInfos(index, subindex, MAPPING_DICTIONARY, compute) - if result: - result["user_defined"] = False - return result - - def GetTypeIndex(self, typename): - if self.CurrentNode: - return self.CurrentNode.GetTypeIndex(typename) - return Find.TypeIndex(typename, MAPPING_DICTIONARY) - - def GetTypeName(self, typeindex): - if self.CurrentNode: - return self.CurrentNode.GetTypeName(typeindex) - return Find.TypeName(typeindex, MAPPING_DICTIONARY) - - def GetTypeDefaultValue(self, typeindex): - if self.CurrentNode: - return self.CurrentNode.GetTypeDefaultValue(typeindex) - return Find.TypeDefaultValue(typeindex, MAPPING_DICTIONARY) - - def GetMapVariableList(self, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetMapVariableList(compute) - return [] - - def GetMandatoryIndexes(self): - if self.CurrentNode: - return self.CurrentNode.GetMandatoryIndexes() - return Find.MandatoryIndexes(MAPPING_DICTIONARY) - - def GetCustomisableTypes(self): - return { - index: [self.GetTypeName(index), valuetype] - for index, valuetype in maps.CUSTOMISABLE_TYPES - } - - def GetCurrentSpecificMenu(self): - if self.CurrentNode: - return self.CurrentNode.SpecificMenu - return [] + new_value = maps.le_to_be(nbparams + 1, 4) + dcf_value[4:] + new_value += ( + maps.le_to_be(index, 2) + + maps.le_to_be(subindex, 1) + + maps.le_to_be(size, 4) + + maps.le_to_be(value, size) + ) + node.SetEntry(0x1F22, node_id, new_value) diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index 33a8245..5f597e9 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -23,12 +23,20 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import ast +import io import logging import re import sys -from io import StringIO +from collections import UserDict, UserList +from typing import TYPE_CHECKING, Any, Container, TypeVar from xml.dom import minidom +if TYPE_CHECKING: + from _typeshed import SupportsRead + +T = TypeVar("T") + log = logging.getLogger('objdictgen.nosis') @@ -36,52 +44,96 @@ class _EmptyClass: """ Do-nohting empty class """ +# Define how the values for built-ins are stored in the XML file. If True, +# the value is stored in the body of the tag, otherwise it's stored in the +# value= attribute. TYPE_IN_BODY = { - int: 0, - float: 0, - complex: 0, - str: 1, + int: False, + float: False, + complex: False, + str: False, +} + +# +# This doesn't fit in any one place particularly well, but +# it needs to be documented somewhere. The following are the family +# types currently defined: +# +# obj - thing with attributes and possibly coredata +# +# uniq - unique thing, its type gives its value, and vice versa +# +# map - thing that maps objects to other objects +# +# seq - thing that holds a series of objects +# +# Note - Py2.3 maybe the new 'Set' type should go here? +# +# atom - non-unique thing without attributes (e.g. only coredata) +# +# lang - thing that likely has meaning only in the +# host language (functions, classes). +# +# [Note that in Gnosis-1.0.6 and earlier, these were +# mistakenly placed under 'uniq'. Those encodings are +# still accepted by the parsers for compatibility.] +# + +# Encodings for builtin types. +TYPE_NAMES = { + 'None': 'none', + 'dict': 'map', + 'list': 'seq', + 'tuple': 'seq', + 'numeric': 'atom', + 'string': 'atom', + 'bytes': 'atom', + # 'PyObject': 'obj', + # 'function': 'lang', + # 'class': 'lang', + 'True': 'uniq', + 'False': 'uniq', } -def getInBody(typename): - return TYPE_IN_BODY.get(typename) or 0 - -# pylint: disable=invalid-name -pat_fl = r'[-+]?(((((\d+)?[.]\d+|\d+[.])|\d+)[eE][+-]?\d+)|((\d+)?[.]\d+|\d+[.]))' -re_float = re.compile(pat_fl + r'$') -re_zero = r'[+-]?0$' -pat_int = r'[-+]?[1-9]\d*' -re_int = re.compile(pat_int + r'$') -pat_flint = r'(%s|%s)' % (pat_fl, pat_int) # float or int -re_long = re.compile(r'[-+]?\d+[lL]' + r'$') -re_hex = re.compile(r'([-+]?)(0[xX])([0-9a-fA-F]+)' + r'$') -re_oct = re.compile(r'([-+]?)(0)([0-7]+)' + r'$') -pat_complex = r'(%s)?[-+]%s[jJ]' % (pat_flint, pat_flint) -re_complex = re.compile(pat_complex + r'$') -pat_complex2 = r'(%s):(%s)' % (pat_flint, pat_flint) -re_complex2 = re.compile(pat_complex2 + r'$') - - -def aton(s): +# Regexp patterns +PAT_FL = r'[-+]?(((((\d+)?[.]\d+|\d+[.])|\d+)[eE][+-]?\d+)|((\d+)?[.]\d+|\d+[.]))' +PAT_INT = r'[-+]?[1-9]\d*' +PAT_FLINT = f'({PAT_FL}|{PAT_INT})' # float or int +PAT_COMPLEX = f'({PAT_FLINT})?[-+]{PAT_FLINT}[jJ]' +PAT_COMPLEX2 = f'({PAT_FLINT}):({PAT_FLINT})' + +# Regexps for parsing numbers +RE_FLOAT = re.compile(PAT_FL + r'$') +RE_ZERO = re.compile(r'[+-]?0$') +RE_INT = re.compile(PAT_INT + r'$') +RE_LONG = re.compile(r'[-+]?\d+[lL]$') +RE_HEX = re.compile(r'([-+]?)(0[xX])([0-9a-fA-F]+)$') +RE_OCT = re.compile(r'([-+]?)(0)([0-7]+)$') +RE_COMPLEX = re.compile(PAT_COMPLEX + r'$') +RE_COMPLEX2 = re.compile(PAT_COMPLEX2 + r'$') + + +def aton(s: str) -> int|float|complex: + """Convert a string to a number""" # -- massage the string slightly s = s.strip() while s[0] == '(' and s[-1] == ')': # remove optional parens s = s[1:-1] # -- test for cases - if re.match(re_zero, s): + if RE_ZERO.match(s): return 0 - if re.match(re_float, s): + if RE_FLOAT.match(s): return float(s) - if re.match(re_long, s): + if RE_LONG.match(s): return int(s.rstrip('lL')) - if re.match(re_int, s): + if RE_INT.match(s): return int(s) - m = re.match(re_hex, s) + m = RE_HEX.match(s) if m: n = int(m.group(3), 16) if n < sys.maxsize: @@ -90,7 +142,7 @@ def aton(s): n = n * (-1) return n - m = re.match(re_oct, s) + m = RE_OCT.match(s) if m: n = int(m.group(3), 8) if n < sys.maxsize: @@ -99,33 +151,35 @@ def aton(s): n = n * (-1) return n - if re.match(re_complex, s): + if RE_COMPLEX.match(s): return complex(s) - if re.match(re_complex2, s): + if RE_COMPLEX2.match(s): r, i = s.split(':') return complex(float(r), float(i)) - raise ValueError("Invalid string '%s' passed to to_number()'d" % s) + raise ValueError(f"Invalid string '{s}'") # we use ntoa() instead of repr() to ensure we have a known output format def ntoa(num: int|float|complex) -> str: """Convert a number to a string without calling repr()""" if isinstance(num, int): - s = "%d" % num - elif isinstance(num, float): - s = "%.17g" % num + return str(num) + + if isinstance(num, float): + s = f"{num:.17g}" # ensure a '.', adding if needed (unless in scientific notation) if '.' not in s and 'e' not in s: s = s + '.' - elif isinstance(num, complex): + return s + + if isinstance(num, complex): # these are always used as doubles, so it doesn't # matter if the '.' shows up - s = "%.17g+%.17gj" % (num.real, num.imag) - else: - raise ValueError("Unknown numeric type: %s" % repr(num)) - return s + return f"{num.real:.17g}+{num.imag:.17g}j" + + raise ValueError(f"Unknown numeric type: {repr(num)}") XML_QUOTES = ( @@ -137,222 +191,74 @@ def ntoa(num: int|float|complex) -> str: ) -def safe_string(s): - # markup XML entities - s = s.replace('&', '&') - s = s.replace('<', '<') - s = s.replace('>', '>') - s = s.replace('"', '"') - s = s.replace("'", ''') - # for others, use Python style escapes - s = repr(s) - return s[1:-1] # without the extra single-quotes - - -def unsafe_string(s): - # for Python escapes, exec the string - # (niggle w/ literalizing apostrophe) - _s = s = s.replace("'", "\\x27") - # log.debug("EXEC in unsafe_string(): '%s'" % ("s='" + s + "'",)) - exec("s='" + s + "'") - if s != _s: - log.debug("EXEC in unsafe_string(): '%s' -> '%s'" % (_s, s)) - # XML entities (DOM does it for us) - return s - - -def safe_content(s): - """Markup XML entities and strings so they're XML & unicode-safe""" - s = s.replace('&', '&') - s = s.replace('<', '<') - s = s.replace('>', '>') +def safe_string(s: str, isattr: bool = True) -> str: + """Quote XML entries""" + for repl in XML_QUOTES: + s = s.replace(repl[0], repl[1]) - return s # To be able to be used with py3 + if isattr: + # for others, use Python style escapes + return repr(s)[1:-1] # without the extra single-quotes - # # wrap "regular" python strings as unicode - # if isinstance(s, str): - # s = u"\xbb\xbb%s\xab\xab" % s + return s - # return s.encode('utf-8') +def unsafe_string(s: str, isattr: bool = True) -> str: + """Recreate the original string from the string returned by safe_string()""" + # Unqoute XML entries + for repl in XML_QUOTES: + s = s.replace(repl[1], repl[0]) -def unsafe_content(s): - """Take the string returned by safe_content() and recreate the - original string.""" - # don't have to "unescape" XML entities (parser does it for us) + if isattr: + s = s.replace("'", "\\x27") # Need this to not interfere with ast - # # unwrap python strings from unicode wrapper - # if s[:2] == chr(187) * 2 and s[-2:] == chr(171) * 2: - # s = s[2:-2].encode('us-ascii') + tree = ast.parse("'" + s + "'", mode='eval') + if not isinstance(tree.body, ast.Constant): + raise ValueError(f"Invalid string '{s}' passed to unsafe_string()") + return tree.body.s return s # Maintain list of object identities for multiple and cyclical references # (also to keep temporary objects alive) -VISITED = {} - - -# entry point expected by XML_Pickle -def thing_from_dom(filehandle): - global VISITED # pylint: disable=global-statement - VISITED = {} - return _thing_from_dom(minidom.parse(filehandle), None) +VISITED: dict[int, Any] = {} -def _save_obj_with_id(node, obj): +def _save_obj_with_id(node: minidom.Element, py_obj: Any) -> None: objid = node.getAttribute('id') - - if len(objid): # might be None, or empty - shouldn't use as key - VISITED[objid] = obj + if objid: # might be None, or empty - shouldn't use as key + VISITED[int(objid)] = py_obj # Store the objects that can be pickled -CLASS_STORE = {} +CLASS_STORE: dict[str, type[Any]] = {} -def add_class_to_store(classname='', klass=None): - """Put the class in the store (as 'classname'), return CLASS_STORE""" +def add_class_to_store(classname: str, klass: type[T]) -> None: + """Put the class in the store (as 'classname')""" if classname and klass: CLASS_STORE[classname] = klass - return CLASS_STORE - - -def get_class_from_name(classname): - """Given a classname, optional module name, return a ClassType, - of type module.classname, obeying the PARANOIA rules.""" - klass = CLASS_STORE.get(classname, None) - if klass: - return klass - raise ValueError("Cannot create class '%s'" % classname) - -def obj_from_node(node): - """Given a node, return an object of that type. - __init__ is NOT called on the new object, since the caller may want - to do some additional work first. - """ - classname = node.getAttribute('class') - # allow nodes w/out module name - # (possibly handwritten XML, XML containing "from-air" classes, - # or classes placed in the CLASS_STORE) - klass = get_class_from_name(classname) - return klass.__new__(klass) - - -def get_node_valuetext(node): - """Get text from node, whether in value=, or in element body.""" - - # we know where the text is, based on whether there is - # a value= attribute. ie. pickler can place it in either - # place (based on user preference) and unpickler doesn't care - - if 'value' in node._attrs: - # text in tag - ttext = node.getAttribute('value') - return unsafe_string(ttext) - - # text in body - node.normalize() - if node.childNodes: - return unsafe_content(node.childNodes[0].nodeValue) - return '' - - -# a multipurpose list-like object. it is nicer conceptually for us -# to pass lists around at the lower levels, yet we'd also like to be -# able to do things like write to a file without the overhead of building -# a huge list in memory first. this class handles that, yet drops in (for -# our purposes) in place of a list. -# -# (it's not based on UserList so that (a) we don't have to pull in UserList, -# and (b) it will break if someone accesses StreamWriter in an unexpected way -# rather than failing silently for some cases) -class StreamWriter: - """A multipurpose stream object. Four styles: - - - write an uncompressed file - - write a compressed file - - create an uncompressed memory stream - - create a compressed memory stream - """ - def __init__(self, iohandle=None, compress=None): - - if iohandle: - self.iohandle = iohandle - else: - self.iohandle = self.sio = StringIO() - - if compress == 1: # maybe we'll add more types someday - import gzip # pylint: disable=import-outside-toplevel - self.iohandle = gzip.GzipFile(None, 'wb', 9, self.iohandle) - - def append(self, item): - if isinstance(item, (list, tuple)): - item = ''.join(item) - self.iohandle.write(item) - - def getvalue(self): - "Returns memory stream as a single string, or None for file objs" - if hasattr(self, 'sio'): - if self.iohandle != self.sio: - # if iohandle is a GzipFile, we need to close it to flush - # remaining bits, write the CRC, etc. However, if iohandle is - # the sio, we CAN'T close it (getvalue() wouldn't work) - self.iohandle.close() - return self.sio.getvalue() - - # don't raise an exception - want getvalue() unconditionally - return None - - -# split off for future expansion of compression types, etc. -def StreamReader(stream): - """stream can be either a filehandle or string, and can - be compressed/uncompressed. Will return either a fileobj - appropriate for reading the stream.""" - - # turn strings into stream - if isinstance(stream, str): - stream = StringIO(stream) - - # determine if we have a gzipped stream by checking magic - # number in stream header - pos = stream.tell() - magic = stream.read(2) - stream.seek(pos) - if magic == '\037\213': - import gzip # pylint: disable=import-outside-toplevel - stream = gzip.GzipFile(None, 'rb', None, stream) - - return stream - - -def xmldump(iohandle=None, obj=None, binary=0, deepcopy=None, omit=None): +def xmldump(filehandle: io.TextIOWrapper|None, py_obj: object, + omit: Container[str]|None = None) -> str|None: """Create the XML representation as a string.""" - if deepcopy is None: - deepcopy = 0 - return _pickle_toplevel_obj(StreamWriter(iohandle, binary), obj, deepcopy, omit) - - -def xmlload(filehandle): - """Load pickled object from file fh.""" - return thing_from_dom(StreamReader(filehandle)) - - -# -- support functions + fh: io.TextIOWrapper + if filehandle is None: + fh = sio = io.StringIO() + else: + fh = filehandle -def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): - """handle the top object -- add XML header, etc.""" + omit = omit or () # Store the ref id to the pickling object (if not deepcopying) global VISITED # pylint: disable=global-statement - VISITED = {} - if not deepcopy: - id_ = id(py_obj) - VISITED[id_] = py_obj + objid = id(py_obj) + VISITED = { + objid: py_obj + } # note -- setting family="obj" lets us know that a mutator was used on # the object. Otherwise, it's tricky to unpickle both @@ -364,50 +270,21 @@ def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): # module= that we need to read before unmutating (i.e. the mutator # mutated into a PyObject) - famtype = '' # unless we have to, don't add family= and type= - klass = py_obj.__class__ klass_tag = klass.__name__ # Generate the XML string # if klass not in CLASS_STORE.values(): module = klass.__module__.replace('objdictgen.', '') # Workaround to be backwards compatible - extra = '%smodule="%s" class="%s"' % (famtype, module, klass_tag) - # else: - # extra = '%s class="%s"' % (famtype, klass_tag) - - xml_list.append('\n' - + '\n') - - if deepcopy: - xml_list.append('\n' % (extra)) - elif id_ is not None: - xml_list.append('\n' % (extra, id_)) - else: - xml_list.append('\n' % (extra)) - - pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy, omit=omit) - xml_list.append('\n') - - # returns None if xml_list is a fileobj, but caller should - # know that (or not care) - return xml_list.getvalue() + id_tag = f' id="{objid}"' if objid is not None else "" -def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): - """Pickle the given object into a + fh.write(f""" + + +""") - Add XML tags to list. Level is indentation (for aesthetic reasons) - """ - # concept: to pickle an object: - # - # 1. the object attributes (the "stuff") - # - # There is a twist to this -- instead of always putting the "stuff" - # into a container, we can make the elements of "stuff" first-level attributes, - # which gives a more natural-looking XML representation of the object. - - stuff = obj.__dict__ + stuff = py_obj.__dict__ # decide how to save the "stuff", depending on whether we need # to later grab it back as a single object @@ -417,184 +294,94 @@ def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): for key, val in stuff.items(): if omit and key in omit: continue - list_.append(_attr_tag(key, val, level, deepcopy)) + fh.write(_attr_tag(key, val, 0)) else: - raise ValueError("'%s.__dict__' is not a dict" % (obj)) - - -def unpickle_instance(node): - """Take a or <.. type="PyObject"> DOM node and unpickle the object.""" + raise ValueError(f"'{py_obj}.__dict__' is not a dict") - # we must first create an empty obj of the correct type and place - # it in VISITED{} (so we can handle self-refs within the object) - pyobj = obj_from_node(node) - _save_obj_with_id(node, pyobj) + fh.write('\n') - # slurp raw thing into a an empty object - raw = _thing_from_dom(node, _EmptyClass()) + if filehandle is None: + sio.flush() + return sio.getvalue() + return None - # code below has same ordering as pickle.py - stuff = raw.__dict__ +def xmlload(filehandle: "SupportsRead[str|bytes]|bytes|str") -> Any: + """Load pickled object from file fh.""" - # finally, decide how to get the stuff into pyobj - if isinstance(stuff, dict): - for k, v in stuff.items(): - setattr(pyobj, k, v) + fh: "SupportsRead[str|bytes]" = filehandle # type: ignore[assignment] + if isinstance(filehandle, str): + fh = io.StringIO(filehandle) + elif isinstance(filehandle, bytes): + fh = io.BytesIO(filehandle) - else: - # subtle -- this can happen either because the class really - # does violate the pickle protocol - raise ValueError("Non-dict violates pickle protocol") + global VISITED # pylint: disable=global-statement + VISITED = {} # Reset the visited collection - return pyobj + return _thing_from_dom(minidom.parse(fh), None) -# --- Functions to create XML output tags --- -def _attr_tag(name, thing, level=0, deepcopy=0): - start_tag = ' ' * level + ('\n' - return _tag_completer(start_tag, thing, close_tag, level, deepcopy) + return _tag_completer(start_tag, thing, close_tag, level) -def _item_tag(thing, level=0, deepcopy=0): +def _item_tag(thing, level=0): start_tag = ' ' * level + '\n' - return _tag_completer(start_tag, thing, close_tag, level, deepcopy) + return _tag_completer(start_tag, thing, close_tag, level) -def _entry_tag(key, val, level=0, deepcopy=0): +def _entry_tag(key, val, level=0): start_tag = ' ' * level + '\n' close_tag = ' ' * level + '\n' start_key = ' ' * level + ' \n' - key_block = _tag_completer(start_key, key, close_key, level + 1, deepcopy) + key_block = _tag_completer(start_key, key, close_key, level + 1) start_val = ' ' * level + ' \n' - val_block = _tag_completer(start_val, val, close_val, level + 1, deepcopy) + val_block = _tag_completer(start_val, val, close_val, level + 1) return start_tag + key_block + val_block + close_tag -def _tag_compound(start_tag, family_type, thing, deepcopy, extra=''): - """Make a start tag for a compound object, handling deepcopy & refs. +def _tag_compound(start_tag: str, family_type: str, thing: Any) -> tuple[str, int]: + """Make a start tag for a compound object, handling refs. Returns (start_tag,do_copy), with do_copy indicating whether a copy of the data is needed. """ - if deepcopy: - # don't need ids in a deepcopied file (looks neater) - start_tag = start_tag + '%s %s>\n' % (family_type, extra) - return (start_tag, 1) - - if VISITED.get(id(thing)): - start_tag = start_tag + '%s refid="%s" />\n' % (family_type, id(thing)) + idt = id(thing) + if VISITED.get(idt): + start_tag = f'{start_tag}{family_type} refid="{idt}" />\n' return (start_tag, 0) - start_tag = start_tag + '%s id="%s" %s>\n' % (family_type, id(thing), extra) + start_tag = f'{start_tag}{family_type} id="{idt}">\n' return (start_tag, 1) -# -# This doesn't fit in any one place particularly well, but -# it needs to be documented somewhere. The following are the family -# types currently defined: -# -# obj - thing with attributes and possibly coredata -# -# uniq - unique thing, its type gives its value, and vice versa -# -# map - thing that maps objects to other objects -# -# seq - thing that holds a series of objects -# -# Note - Py2.3 maybe the new 'Set' type should go here? -# -# atom - non-unique thing without attributes (e.g. only coredata) -# -# lang - thing that likely has meaning only in the -# host language (functions, classes). -# -# [Note that in Gnosis-1.0.6 and earlier, these were -# mistakenly placed under 'uniq'. Those encodings are -# still accepted by the parsers for compatibility.] -# - -def _family_type(family, typename, mtype, mextra): - """Create a type= string for an object, including family= if necessary. - typename is the builtin type, mtype is the mutated type (or None for - non-mutants). mextra is mutant-specific data, or None.""" - if mtype is None: - # family tags are technically only necessary for mutated types. - # we can intuit family for builtin types. - return 'type="%s"' % typename - - if mtype and len(mtype): - if mextra: - mextra = 'extra="%s"' % mextra - else: - mextra = '' - return 'family="%s" type="%s" %s' % (family, mtype, mextra) - return 'family="%s" type="%s"' % (family, typename) - - -def _fix_family(family, typename): - """ - If family is None or empty, guess family based on typename. - (We can only guess for builtins, of course.) - """ - if family and len(family): - return family # sometimes it's None, sometimes it's empty ... - - if typename == 'None': - return 'none' - if typename == 'dict': - return 'map' - if typename == 'list': - return 'seq' - if typename == 'tuple': - return 'seq' - if typename == 'numeric': - return 'atom' - if typename == 'string': - return 'atom' - if typename == 'PyObject': - return 'obj' - if typename == 'function': - return 'lang' - if typename == 'class': - return 'lang' - if typename == 'True': - return 'uniq' - if typename == 'False': - return 'uniq' - raise ValueError("family= must be given for unknown type '%s'" % typename) - - -def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): +def _tag_completer(start_tag: str, orig_thing, close_tag: str, level: int) -> str: tag_body = [] - (mtag, thing, in_body, mextra) = (None, orig_thing, getInBody(type(orig_thing)), None) + thing = orig_thing + in_body = TYPE_IN_BODY.get(type(orig_thing), 0) if thing is None: - start_tag = start_tag + "%s />\n" % (_family_type('none', 'None', None, None)) + start_tag = f'{start_tag}type="None" />\n' close_tag = '' + # bool cannot be used as a base class (see sanity check above) so if thing # is a bool it will always be BooleanType, and either True or False elif isinstance(thing, bool): - if thing is True: - typestr = 'True' - else: # must be False - typestr = 'False' + typestr = 'True' if thing is True else 'False' if in_body: - start_tag = start_tag + '%s>%s' % ( - _family_type('uniq', typestr, mtag, mextra), '') + start_tag = f'{start_tag}type="{typestr}">' close_tag = close_tag.lstrip() else: - start_tag = start_tag + '%s value="%s" />\n' % ( - _family_type('uniq', typestr, mtag, mextra), '') + start_tag = f'{start_tag}type="{typestr}" value="" />\n' close_tag = '' + elif isinstance(thing, (int, float, complex)): - # thing_str = repr(thing) thing_str = ntoa(thing) if in_body: @@ -602,22 +389,20 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): # contain special XML chars. # the unpickler can either call unsafe_content() or not, # it won't matter - start_tag = start_tag + '%s>%s' % ( - _family_type('atom', 'numeric', mtag, mextra), thing_str) + start_tag = f'{start_tag}type="numeric">{thing_str}' close_tag = close_tag.lstrip() else: - start_tag = start_tag + '%s value="%s" />\n' % ( - _family_type('atom', 'numeric', mtag, mextra), thing_str) + start_tag = f'{start_tag}type="numeric" value="{thing_str}" />\n' close_tag = '' + elif isinstance(thing, str): if in_body: - start_tag = start_tag + '%s>%s' % ( - _family_type('atom', 'string', mtag, mextra), safe_content(thing)) + start_tag = f'{start_tag}type="string">{safe_string(thing, isattr=False)}' close_tag = close_tag.lstrip() else: - start_tag = start_tag + '%s value="%s" />\n' % ( - _family_type('atom', 'string', mtag, mextra), safe_string(thing)) + start_tag = f'{start_tag}type="string" value="{safe_string(thing, isattr=True)}" />\n' close_tag = '' + # General notes: # 1. When we make references, set type to referenced object # type -- we don't need type when unpickling, but it may be useful @@ -627,89 +412,116 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): # (we CANNOT just move the visited{} update to the top of this # function, since that would screw up every _family_type() call) elif isinstance(thing, tuple): - start_tag, do_copy = _tag_compound( - start_tag, _family_type('seq', 'tuple', mtag, mextra), - orig_thing, deepcopy) + start_tag, do_copy = _tag_compound(start_tag, 'type="tuple"', orig_thing) if do_copy: for item in thing: - tag_body.append(_item_tag(item, level + 1, deepcopy)) + tag_body.append(_item_tag(item, level + 1)) else: close_tag = '' - elif isinstance(thing, list): - start_tag, do_copy = _tag_compound( - start_tag, _family_type('seq', 'list', mtag, mextra), - orig_thing, deepcopy) + + elif isinstance(thing, (list, UserList)): + start_tag, do_copy = _tag_compound(start_tag, 'type="list"', orig_thing) # need to remember we've seen container before pickling subitems VISITED[id(orig_thing)] = orig_thing if do_copy: for item in thing: - tag_body.append(_item_tag(item, level + 1, deepcopy)) + tag_body.append(_item_tag(item, level + 1)) else: close_tag = '' - elif isinstance(thing, dict): - start_tag, do_copy = _tag_compound( - start_tag, _family_type('map', 'dict', mtag, mextra), - orig_thing, deepcopy) + + elif isinstance(thing, (dict, UserDict)): + start_tag, do_copy = _tag_compound(start_tag, 'type="dict"', orig_thing) # need to remember we've seen container before pickling subitems VISITED[id(orig_thing)] = orig_thing if do_copy: for key, val in thing.items(): - tag_body.append(_entry_tag(key, val, level + 1, deepcopy)) + tag_body.append(_entry_tag(key, val, level + 1)) else: close_tag = '' + else: - raise ValueError("Non-handled type %s" % type(thing)) + raise ValueError(f"Non-handled type {type(thing)}") # need to keep a ref to the object for two reasons - # 1. we can ref it later instead of copying it into the XML stream # 2. need to keep temporary objects around so their ids don't get reused - - # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we - # never look at them - if not deepcopy: - VISITED[id(orig_thing)] = orig_thing + VISITED[id(orig_thing)] = orig_thing return start_tag + ''.join(tag_body) + close_tag -def _thing_from_dom(dom_node, container=None): +def _thing_from_dom(dom_node: minidom.Element|minidom.Document, container: Any = None) -> Any: """Converts an [xml_pickle] DOM tree to a 'native' Python object""" + node: minidom.Element for node in dom_node.childNodes: if not hasattr(node, '_attrs') or not node.nodeName != '#text': continue if node.nodeName == "PyObject": - container = unpickle_instance(node) - try: - id_ = node.getAttribute('id') - VISITED[id_] = container - except KeyError: - pass # Accepable, not having id only affects caching + + # Given a node, return an object of that type. + # __init__ is NOT called on the new object, since the caller may want + # to do some additional work first. + classname = node.getAttribute('class') + # allow nodes w/out module name + # (possibly handwritten XML, XML containing "from-air" classes, + # or classes placed in the CLASS_STORE) + klass = CLASS_STORE.get(classname) + if klass is None: + raise ValueError(f"Cannot create class '{classname}'") + container = klass.__new__(klass) # type: ignore[call-overload] + + _save_obj_with_id(node, container) + + # slurp raw thing into a an empty object + raw = _thing_from_dom(node, _EmptyClass()) + + # Copy attributes into the new container object + for k, v in raw.__dict__.items(): + setattr(container, k, v) elif node.nodeName in ['attr', 'item', 'key', 'val']: node_family = node.getAttribute('family') - node_type = node.getAttribute('type') + node_type: str = node.getAttribute('type') node_name = node.getAttribute('name') # check refid first (if present, type is type of referenced object) ref_id = node.getAttribute('refid') - if len(ref_id): # might be empty or None + if ref_id: # might be empty or None if node.nodeName == 'attr': - setattr(container, node_name, VISITED[ref_id]) + setattr(container, node_name, VISITED[int(ref_id)]) else: - container.append(VISITED[ref_id]) + container.append(VISITED[int(ref_id)]) # done, skip rest of block continue # if we didn't find a family tag, guess (do after refid check -- - # old pickles will set type="ref" which _fix_family can't handle) - node_family = _fix_family(node_family, node_type) - - node_valuetext = get_node_valuetext(node) + # old pickles will set type="ref" which this code can't handle) + # If family is None or empty, guess family based on typename. + if not node_family: + if node_type not in TYPE_NAMES: + raise ValueError(f"Unknown type {node_type}") + node_family = TYPE_NAMES[node_type] + + # Get text from node, whether in value=, or in element body. + # we know where the text is, based on whether there is + # a value= attribute. ie. pickler can place it in either + # place (based on user preference) and unpickler doesn't care + node_valuetext = "" + if 'value' in node._attrs: # type: ignore[attr-defined] # pylint: disable=protected-access + # text in tag + ttext = node.getAttribute('value') + node_valuetext = unsafe_string(ttext, isattr=True) + else: + # text in body + node.normalize() + if node.childNodes: + node_valuetext = unsafe_string(node.childNodes[0].nodeValue, isattr=False) # step 1 - set node_val to basic thing + node_val: Any if node_family == 'none': node_val = None elif node_family == 'atom': @@ -717,13 +529,13 @@ def _thing_from_dom(dom_node, container=None): elif node_family == 'seq': # seq must exist in VISITED{} before we unpickle subitems, # in order to handle self-references - seq = [] + seq: list[Any] = [] _save_obj_with_id(node, seq) node_val = _thing_from_dom(node, seq) elif node_family == 'map': # map must exist in VISITED{} before we unpickle subitems, # in order to handle self-references - mapping = {} + mapping: dict[Any, Any] = {} _save_obj_with_id(node, mapping) node_val = _thing_from_dom(node, mapping) elif node_family == 'uniq': @@ -734,9 +546,9 @@ def _thing_from_dom(dom_node, container=None): elif node_type == 'False': node_val = False else: - raise ValueError("Unknown uniq type %s" % node_type) + raise ValueError(f"Unknown uniq type {node_type}") else: - raise ValueError("Unknown family %s,%s,%s" % (node_family, node_type, node_name)) + raise ValueError(f"Unknown family {node_family},{node_type},{node_name}") # step 2 - take basic thing and make exact thing # Note there are several NOPs here since node_val has been decided @@ -748,7 +560,6 @@ def _thing_from_dom(dom_node, container=None): if node_type == 'None': node_val = None elif node_type == 'numeric': - # node_val = safe_eval(node_val) node_val = aton(node_val) elif node_type == 'string': node_val = node_val @@ -768,7 +579,7 @@ def _thing_from_dom(dom_node, container=None): elif node_type == 'False': node_val = node_val else: - raise ValueError("Unknown type %s,%s" % (node, node_type)) + raise ValueError(f"Unknown type {node},{node_type}") if node.nodeName == 'attr': setattr(container, node_name, node_val) @@ -784,6 +595,6 @@ def _thing_from_dom(dom_node, container=None): # has no id for refchecking else: - raise ValueError("Element %s is not in PyObjects.dtd" % node.nodeName) + raise ValueError(f"Element {node.nodeName} is not in PyObjects.dtd") return container diff --git a/src/objdictgen/py.typed b/src/objdictgen/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/objdictgen/schema/od.schema.json b/src/objdictgen/schema/od.schema.json index 3abb0dc..9277d45 100644 --- a/src/objdictgen/schema/od.schema.json +++ b/src/objdictgen/schema/od.schema.json @@ -111,7 +111,7 @@ "profile_callback": { "$ref": "#callback" }, "callback": { "$ref": "#callback" }, "unused": { "$ref": "#unused" }, - "default": { "$ref": "#default" }, + "default": { "$ref": "#value" }, "size": { "$ref": "#size" }, "incr": { "$ref": "#incr" }, "nbmax": { "$ref": "#nbmax" }, @@ -137,7 +137,7 @@ "properties": { "name": { "$ref": "#name" }, "comment": { "$ref": "#comment" }, - "buffer_size": { "$ref": "#buffersize" }, + "buffer_size": { "$ref": "#buffer_size" }, "type": { "$ref": "#type" }, "access": { "$ref": "#access" }, "pdo": { "$ref": "#pdo" }, @@ -159,13 +159,13 @@ } }, - "subitem_base": { + "subitem_repeat": { "$id": "#subitem_repeat", "description": "Sub object item in repeated parameter", "type": "object", "properties": { "comment": { "$ref": "#comment" }, - "buffer_size": { "$ref": "#buffersize" }, + "buffer_size": { "$ref": "#buffer_size" }, "save": { "$ref": "#save" }, "value": { "$ref": "#value" } }, @@ -216,7 +216,7 @@ }, "buffer_size": { - "$id": "#buffersize", + "$id": "#buffer_size", "description": "Buffer size (for strings)", "anyOf": [ { @@ -239,11 +239,6 @@ "description": "Free-text comment" }, - "default": { - "$id": "#default", - "type": "integer" - }, - "group": { "$id": "#group", "description": "Object group membership", @@ -404,6 +399,9 @@ }, { "type": "boolean" + }, + { + "type": "number" } ] } diff --git a/src/objdictgen/typing.py b/src/objdictgen/typing.py new file mode 100644 index 0000000..9ccc9dd --- /dev/null +++ b/src/objdictgen/typing.py @@ -0,0 +1,271 @@ +"""Typing stubs for the objdictgen module.""" +# +# Copyright (C) 2024 Svein Seldal, Laerdal Medical AS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + +import os +from typing import TYPE_CHECKING, Iterator, Protocol, TypedDict + +import deepdiff.model # type: ignore[import] # Due to missing typing stubs for deepdiff + +if TYPE_CHECKING: + from objdictgen.maps import ODMapping + + +# MAPS +# ====== + +TODValue = bool|int|float|str +"""Type for storing the value of an object dictionary entry.""" + +TODSubObj = TypedDict('TODSubObj', { + "name": str, + "type": int, + "access": str, + "pdo": bool, + "nbmin": int, + "nbmax": int, + "default": TODValue, +}, total=False) +"""Dict-like type for the sub-object dictionary mappings.""" + +TODObj = TypedDict('TODObj', { + "name": str, + "struct": int, + "size": int, + "default": TODValue, + "values": list[TODSubObj], + "need": bool, + "callback": bool, + "incr": int, + "nbmin": int, + "nbmax": int, +}, total=False) +"""Dict-like type for the object dictionary mappings.""" + +# See maps.import_profile +TProfileMenu = list[tuple[str, list[int]]] +"""Type for the profile menu entries.""" + + +# NODE +# ====== + +TPath = os.PathLike[str] | str +"""Type for a file path.""" + +TParamEntry = TypedDict('TParamEntry', { + "comment": str, + "buffer_size": int, + "save": bool, + # "callback": bool, # It can exist in the ParamsDictionary dict, but not as subenties +}, total=False) +"""Type for storing a object dictionary parameter entry.""" + +TIndexEntry = TypedDict('TIndexEntry', { + 'index': int, + 'dictionary': TODValue|list[TODValue], + 'params': TParamEntry|dict[int, TParamEntry], + 'object': TODObj, + 'base': int, + 'basestruct': int, + 'groups': list[str], +}, total=False) +"""Type representing the full entry of an object dictionary index.""" + + +class NodeProtocol(Protocol): + """Protocol for the Node class.""" + + # pylint: disable=unnecessary-ellipsis + + Name: str + """Name of the node.""" + + Type: str + """Type of the node. Either "master" or "slave".""" + + ID: int + """Node ID.""" + + Description: str + """Node description.""" + + ProfileName: str + """Name of any loaded profiles. "None" if no profile is loaded.""" + + Profile: "ODMapping" + """Mapping containing the object definitions for the profile.""" + + DefaultStringSize: int + """Setting for the default string size.""" + + def __iter__(self) -> Iterator[int]: + """Iterate over the entries of the node.""" + ... + + def GetEntryName(self, index: int, compute: bool = True) -> str: + """Get the name of the entry with the given index.""" + ... + + def GetEntry(self, index:int, subindex: int|None = None, + compute: bool = True, aslist: bool = False) -> list[TODValue]|TODValue: + """Get the value of the entry with the given index and subindex.""" + ... + + def GetTypeName(self, index: int) -> str: + """Get the type name of the entry with the given index.""" + ... + + def GetEntryInfos(self, index: int, compute: bool = True) -> TODObj: + """Get the dictionary of the entry with the given index.""" + ... + + def GetParamsEntry(self, index: int, subindex: int|None = None, + aslist: bool = False) -> TParamEntry|list[TParamEntry]: + """Get the parameters of the entry with the given index.""" + ... + + def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> TODSubObj: + """Get the dictionary of the subentry with the given index and subindex.""" + ... + + def GetIndexes(self) -> list[int]: + """ Return a sorted list of indexes in Object Dictionary """ + ... + + def GetNodeName(self) -> str: + """Get the name of the node.""" + ... + + def GetNodeID(self) -> int: + """Get the ID of the node.""" + ... + + def GetNodeType(self) -> str: + """Get the type of the node.""" + ... + + def GetNodeDescription(self) -> str: + """Get the description of the node.""" + ... + + def GetDefaultStringSize(self) -> int: + """Get the default string size setting.""" + ... + + +# JSON +# ====== +# Keep the TOD*Json types in sync with the JSON schema + +# Corresponds to #subitem and #subitem_repeat in json schema +TODSubObjJson = TypedDict('TODSubObjJson', { + "name": str, + "comment": str, + "buffer_size": int|str, + "type": int|str, + "access": str, + "pdo": bool, + "default": TODValue, + "save": bool, + "value": TODValue, + + # Convenience fields + "__name": str, + "__type": str, +}) +"""JSON object dictionary sub-object type definition.""" + +# Corresponds to "#each" in json schema +TODEachJson = TypedDict('TODEachJson', { + "name": str, + "type": int|str, + "access": str, + "pdo": bool, + "nbmin": int, + "nbmax": int, + "default": TODValue, +}) +"""JSON object dictionary "each" type definition.""" + +# Corresponds to "#object" in json schema +TODObjJson = TypedDict('TODObjJson', { + "index": int|str, + "name": str, + "struct": int|str, + "group": str|None, # FIXME: Don't really want None here + "mandatory": bool, + "unused": bool, + "default": TODValue, + "size": int, + "incr": int, + "nbmax": int, + "repeat": bool, + "profile_callback": bool, + "callback": bool, + "each": TODEachJson, + "sub": list[TODSubObjJson], + + # Convenience fields + "__name": str, +}, total=False) +"""JSON object dictionary object type definition.""" + +TODJson = TypedDict('TODJson', { + "$id": str, + "$version": int|str, + "$description": str, + "$tool": str, + "$date": str, + "name": str, + "description": str, + "type": str, + "id": int, + "profile": str, + "default_string_size": int, + "dictionary": list[TODObjJson], +}) +"""JSON file type definition""" + +TDiffEntries = list[tuple[str, deepdiff.model.DiffLevel, str]] +"""Type for the diff entries retunred by diff_nodes""" + +TDiffNodes = dict[int|str, TDiffEntries] +"""Type returned from the diff_nodes function.""" + + +# EDS +# ===== + +class TEntry(TypedDict): + """Type definition for ENTRY_TYPES in the EDS file.""" + name: str + require: list[str] + optional: list[str] + + + +# COMMONDIALOGS +# =============== + +TGetValues = TypedDict("TGetValues", { + "slaveName": str, + "slaveNodeID": int, + "edsFile": str +}, total=False) +"""Type for the return value of the AddSlaveDialog.GetValues method.""" diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index ec90610..8f8b311 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -1,3 +1,4 @@ +"""Shared dialog classes for the Object Dictionary Editor.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -24,8 +25,11 @@ import wx.grid import objdictgen +from objdictgen import maps from objdictgen.maps import OD -from objdictgen.node import BE_to_LE, LE_to_BE +from objdictgen.typing import TGetValues +from objdictgen.ui.exception import (display_error_dialog, + display_exception_dialog) log = logging.getLogger('objdictgen') @@ -40,15 +44,22 @@ ID_COMMUNICATIONDIALOGCURRENTINDEXES, ID_COMMUNICATIONDIALOGSELECT, ID_COMMUNICATIONDIALOGUNSELECT, ID_COMMUNICATIONDIALOGSTATICTEXT1, ID_COMMUNICATIONDIALOGSTATICTEXT2 -] = [wx.NewId() for _init_ctrls in range(7)] +] = [wx.NewId() for _ in range(7)] class CommunicationDialog(wx.Dialog): + """Edit Communication Profile Dialog.""" # pylint: disable=attribute-defined-outside-init + IndexDictionary: dict[int, tuple[str, bool]] + CurrentList: list[int] + AllList: list[int] + def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -109,9 +120,9 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_COMMUNICATIONDIALOG, - name='CommunicationDialog', parent=prnt, pos=wx.Point(234, 216), + name='CommunicationDialog', parent=parent, pos=wx.Point(234, 216), size=wx.Size(726, 437), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, title='Edit Communication Profile') self.SetClientSize(wx.Size(726, 437)) @@ -162,15 +173,15 @@ def __init__(self, parent): self.CurrentList = [] self.IndexDictionary = {} - def SetIndexDictionary(self, dictionary): + def SetIndexDictionary(self, dictionary: dict[int, tuple[str, bool]]): self.IndexDictionary = dictionary - def SetCurrentList(self, list_): + def SetCurrentList(self, currentlist: list[int]): self.CurrentList = [] - self.CurrentList.extend(list_) + self.CurrentList.extend(currentlist) self.CurrentList.sort() - def GetCurrentList(self): + def GetCurrentList(self) -> list[int]: return self.CurrentList def RefreshLists(self): @@ -182,10 +193,10 @@ def RefreshLists(self): self.AllList.append(index) self.AllList.sort() for index in self.AllList: - self.PossibleIndexes.Append("0x%04X %s" % (index, self.IndexDictionary[index][0])) + self.PossibleIndexes.Append(f"0x{index:04X} {self.IndexDictionary[index][0]}") for index in self.CurrentList: if index in self.IndexDictionary: - self.CurrentIndexes.Append("0x%04X %s" % (index, self.IndexDictionary[index][0])) + self.CurrentIndexes.Append(f"0x{index:04X} {self.IndexDictionary[index][0]}") def OnPossibleIndexesDClick(self, event): self.SelectPossible() @@ -232,15 +243,18 @@ def UnselectCurrent(self): ID_MAPVARIABLEDIALOGRADIOBUTTON3, ID_MAPVARIABLEDIALOGSTATICTEXT1, ID_MAPVARIABLEDIALOGSTATICTEXT2, ID_MAPVARIABLEDIALOGSTATICTEXT3, ID_MAPVARIABLEDIALOGSTATICTEXT4, -] = [wx.NewId() for _init_ctrls in range(13)] +] = [wx.NewId() for _ in range(13)] class MapVariableDialog(wx.Dialog): + """Create Map Variable Dialog.""" # pylint: disable=attribute-defined-outside-init def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -275,69 +289,85 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_MAPVARIABLEDIALOG, - name='CommunicationDialog', parent=prnt, pos=wx.Point(376, 223), - size=wx.Size(444, 186), style=wx.DEFAULT_DIALOG_STYLE, - title='Add Map Variable') + name='CommunicationDialog', parent=parent, pos=wx.Point(376, 223), + size=wx.Size(444, 186), style=wx.DEFAULT_DIALOG_STYLE, + title='Add Map Variable', + ) self.SetClientSize(wx.Size(444, 186)) self.staticText1 = wx.StaticText(id=ID_MAPVARIABLEDIALOGSTATICTEXT1, - label='Index:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Index:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText2 = wx.StaticText(id=ID_MAPVARIABLEDIALOGSTATICTEXT2, - label='Type:', name='staticText2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Type:', name='staticText2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText3 = wx.StaticText(id=ID_MAPVARIABLEDIALOGSTATICTEXT3, - label='Name:', name='staticText3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Name:', name='staticText3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText4 = wx.StaticText(id=ID_MAPVARIABLEDIALOGSTATICTEXT4, - label='Number:', name='staticText4', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 16), style=0) + label='Number:', name='staticText4', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 16), style=0, + ) self.radioButton1 = wx.RadioButton(id=ID_MAPVARIABLEDIALOGRADIOBUTTON1, - label='VAR', name='radioButton1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 24), style=wx.RB_GROUP) + label='VAR', name='radioButton1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 24), style=wx.RB_GROUP, + ) self.radioButton1.SetValue(True) self.radioButton1.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton1Click, - id=ID_MAPVARIABLEDIALOGRADIOBUTTON1) + id=ID_MAPVARIABLEDIALOGRADIOBUTTON1, + ) self.radioButton2 = wx.RadioButton(id=ID_MAPVARIABLEDIALOGRADIOBUTTON2, - label='ARRAY', name='radioButton2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 24), style=0) + label='ARRAY', name='radioButton2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 24), style=0, + ) self.radioButton2.SetValue(False) self.radioButton2.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton2Click, - id=ID_MAPVARIABLEDIALOGRADIOBUTTON2) + id=ID_MAPVARIABLEDIALOGRADIOBUTTON2, + ) self.radioButton3 = wx.RadioButton(id=ID_MAPVARIABLEDIALOGRADIOBUTTON3, - label='RECORD', name='radioButton3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 24), style=0) + label='RECORD', name='radioButton3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 24), style=0, + ) self.radioButton3.SetValue(False) self.radioButton3.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton3Click, - id=ID_MAPVARIABLEDIALOGRADIOBUTTON3) + id=ID_MAPVARIABLEDIALOGRADIOBUTTON3, + ) self.Index = wx.TextCtrl(id=ID_MAPVARIABLEDIALOGINDEX, name='Index', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 25), - style=0, value='0x2000') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 25), + style=0, value='0x2000', + ) self.IndexName = wx.TextCtrl(id=ID_MAPVARIABLEDIALOGINDEXNAME, - name='IndexName', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=0, value='Undefined') + name='IndexName', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=0, value='Undefined', + ) self.Number = wx.TextCtrl(id=ID_MAPVARIABLEDIALOGNUMBER, - name='Number', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=wx.TE_RIGHT, value='1') + name='Number', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_RIGHT, value='1', + ) self.Spacer = wx.Panel(id=ID_MAPVARIABLEDIALOGSPACER, - name='Spacer', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + name='Spacer', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL, + ) self.Spacer2 = wx.Panel(id=ID_MAPVARIABLEDIALOGSPACER2, - name='Spacer2', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + name='Spacer2', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL, + ) self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL) self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK) @@ -350,14 +380,14 @@ def __init__(self, parent): self.Number.Enable(False) def SetIndex(self, index): - self.Index.SetValue("0x%04X" % index) + self.Index.SetValue(f"0x{index:04X}") def OnOK(self, event): # pylint: disable=unused-argument error = [] try: int(self.Index.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.Index.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.Index.GetValue(), exc) error.append("Index") if self.radioButton2.GetValue() or self.radioButton3.GetValue(): try: @@ -365,7 +395,7 @@ def OnOK(self, event): # pylint: disable=unused-argument if int(self.Number.GetValue()) < 1: raise ValueError("Number out of range, must be >0") except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.Index.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.Index.GetValue(), exc) error.append("Number") if len(error) > 0: text = "" @@ -373,12 +403,10 @@ def OnOK(self, event): # pylint: disable=unused-argument if i == 0: text += item elif i == len(error) - 1: - text += (" and %s") % item + " must be integers!" + text += f" and {item} must be integers!" else: - text += ", %s" % item + " must be integer!" - message = wx.MessageDialog(self, "Form isn't valid. %s" % text, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + text += f", {item} must be integer!" + display_error_dialog(self, f"Form isn't valid. {text}") else: self.EndModal(wx.ID_OK) @@ -425,15 +453,24 @@ def EnableNumberTyping(self, enable): ID_USERTYPEDIALOGSTATICBOX1, ID_USERTYPEDIALOGSTATICTEXT1, ID_USERTYPEDIALOGSTATICTEXT2, ID_USERTYPEDIALOGSTATICTEXT3, ID_USERTYPEDIALOGSTATICTEXT4, -] = [wx.NewId() for _init_ctrls in range(11)] +] = [wx.NewId() for _ in range(11)] class UserTypeDialog(wx.Dialog): + """Create User Type Dialog.""" # pylint: disable=attribute-defined-outside-init + # Helpers for typing + RightBoxSizer: wx.StaticBoxSizer + RightBoxGridSizer: wx.FlexGridSizer + TypeDictionary: dict[str, tuple[int, int]] + # Index: wx.TextCtrl + def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -484,53 +521,65 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_USERTYPEDIALOG, name='UserTypeDialog', - parent=prnt, pos=wx.Point(376, 223), size=wx.Size(444, 210), - style=wx.DEFAULT_DIALOG_STYLE, title='Add User Type') + parent=parent, pos=wx.Point(376, 223), size=wx.Size(444, 210), + style=wx.DEFAULT_DIALOG_STYLE, title='Add User Type', + ) self.SetClientSize(wx.Size(444, 210)) self.staticText1 = wx.StaticText(id=ID_USERTYPEDIALOGSTATICTEXT1, - label='Type:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Type:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.Type = wx.ComboBox(choices=[], id=ID_USERTYPEDIALOGTYPE, - name='Type', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 28), style=wx.CB_READONLY) + name='Type', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 28), style=wx.CB_READONLY, + ) self.Type.Bind(wx.EVT_COMBOBOX, self.OnTypeChoice, - id=ID_USERTYPEDIALOGTYPE) + id=ID_USERTYPEDIALOGTYPE, + ) self.Spacer = wx.Panel(id=ID_MAPVARIABLEDIALOGSPACER, - name='Spacer', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + name='Spacer', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL, + ) self.staticBox1 = wx.StaticBox(id=ID_USERTYPEDIALOGSTATICBOX1, - label='Values', name='staticBox1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 0), style=0) + label='Values', name='staticBox1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 0), style=0, + ) self.staticText2 = wx.StaticText(id=ID_USERTYPEDIALOGSTATICTEXT2, - label='Minimum:', name='staticText2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0) + label='Minimum:', name='staticText2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0, + ) self.Min = wx.TextCtrl(id=ID_USERTYPEDIALOGMIN, name='Min', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=wx.TE_RIGHT, value='0') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=wx.TE_RIGHT, value='0', + ) self.staticText3 = wx.StaticText(id=ID_USERTYPEDIALOGSTATICTEXT3, - label='Maximum:', name='staticText3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0) + label='Maximum:', name='staticText3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0, + ) self.Max = wx.TextCtrl(id=ID_USERTYPEDIALOGMAX, name='Max', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=wx.TE_RIGHT, value='0') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=wx.TE_RIGHT, value='0', + ) self.staticText4 = wx.StaticText(id=ID_USERTYPEDIALOGSTATICTEXT4, - label='Length:', name='staticText4', parent=self, - pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0) + label='Length:', name='staticText4', parent=self, + pos=wx.Point(0, 0), size=wx.Size(80, 17), style=0, + ) self.Length = wx.TextCtrl(id=ID_USERTYPEDIALOGLENGTH, name='Length', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=wx.TE_RIGHT, value='0') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=wx.TE_RIGHT, value='0', + ) self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL) self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK) @@ -540,7 +589,7 @@ def _init_ctrls(self, prnt): def __init__(self, parent): self._init_ctrls(parent) - self.TypeDictionary = {} + self.TypeDictionary: dict[str, tuple[int, int]] = {} def OnOK(self, event): # pylint: disable=unused-argument error = [] @@ -552,18 +601,18 @@ def OnOK(self, event): # pylint: disable=unused-argument try: int(self.Min.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.Index.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.Index.GetValue(), exc) # FIXME: What is self.Index? error.append("Minimum") try: int(self.Max.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.Index.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.Index.GetValue(), exc) error.append("Maximum") elif valuetype == 1: try: int(self.Length.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.Index.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.Index.GetValue(), exc) error.append("Length") if len(error) > 0: message = "" @@ -571,19 +620,17 @@ def OnOK(self, event): # pylint: disable=unused-argument if i == 0: message += item elif i == len(error) - 1: - message += " and %s" % item + " must be integers!" + message += f" and {item} must be integers!" else: - message += ", %s" % item + " must be integer!" + message += f", {item} must be integer!" else: message = "A type must be selected!" if message is not None: - message = wx.MessageDialog(self, "Form isn't valid. %s" % (message,), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, f"Form isn't valid. {message}") else: self.EndModal(wx.ID_OK) - def SetValues(self, min=None, max=None, length=None): # pylint: disable=redefined-builtin + def SetValues(self, min=None, max=None, length=None): if min is not None: self.Min.SetValue(str(min)) if max is not None: @@ -591,16 +638,16 @@ def SetValues(self, min=None, max=None, length=None): # pylint: disable=redefin if length is not None: self.Length.SetValue(str(length)) - def SetTypeList(self, typedic, type_=None): + def SetTypeList(self, typedic, objtype=None): self.Type.Clear() - list_ = [] + typelist = [] for index, (name, valuetype) in typedic.items(): self.TypeDictionary[name] = (index, valuetype) - list_.append((index, name)) - for index, name in sorted(list_): + typelist.append((index, name)) + for index, name in sorted(typelist): self.Type.Append(name) - if type_ is not None: - self.Type.SetStringSelection(typedic[type_][0]) + if objtype is not None: + self.Type.SetStringSelection(typedic[objtype][0]) self.RefreshValues() def OnTypeChoice(self, event): @@ -635,11 +682,11 @@ def RefreshValues(self): def GetValues(self): name = self.Type.GetStringSelection() - type_ = self.TypeDictionary[name][0] - min_ = int(self.Min.GetValue()) - max_ = int(self.Max.GetValue()) + objtype = self.TypeDictionary[name][0] + minval = int(self.Min.GetValue()) + maxval = int(self.Max.GetValue()) length = int(self.Length.GetValue()) - return type_, min_, max_, length + return objtype, minval, maxval, length # ------------------------------------------------------------------------------ @@ -654,7 +701,7 @@ def GetValues(self): ID_NODEINFOSDIALOGSTATICTEXT1, ID_NODEINFOSDIALOGSTATICTEXT2, ID_NODEINFOSDIALOGSTATICTEXT3, ID_NODEINFOSDIALOGSTATICTEXT4, ID_NODEINFOSDIALOGSTATICTEXT5, -] = [wx.NewId() for _init_ctrls in range(11)] +] = [wx.NewId() for _ in range(11)] NODE_TYPES = ["master", "slave"] @@ -662,11 +709,14 @@ def GetValues(self): class NodeInfosDialog(wx.Dialog): + """Dialog for editing node infos.""" # pylint: disable=attribute-defined-outside-init def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -698,52 +748,63 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_NODEINFOSDIALOG, - name='NodeInfosDialog', parent=prnt, pos=wx.Point(376, 223), - size=wx.Size(300, 280), style=wx.DEFAULT_DIALOG_STYLE, - title='Node infos') + name='NodeInfosDialog', parent=parent, pos=wx.Point(376, 223), + size=wx.Size(300, 280), style=wx.DEFAULT_DIALOG_STYLE, + title='Node infos', + ) self.SetClientSize(wx.Size(300, 280)) self.staticText1 = wx.StaticText(id=ID_NODEINFOSDIALOGSTATICTEXT1, - label='Name:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Name:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.NodeName = wx.TextCtrl(id=ID_NODEINFOSDIALOGNAME, name='NodeName', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=0, value='') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=0, value='', + ) self.staticText2 = wx.StaticText(id=ID_NODEINFOSDIALOGSTATICTEXT2, - label='Node ID:', name='staticText2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Node ID:', name='staticText2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.NodeID = wx.TextCtrl(id=ID_NODEINFOSDIALOGNODEID, name='NodeID', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 25), - style=wx.TE_RIGHT, value='') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 25), + style=wx.TE_RIGHT, value='', + ) self.staticText3 = wx.StaticText(id=ID_NODEINFOSDIALOGSTATICTEXT3, - label='Type:', name='staticText3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Type:', name='staticText3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.Type = wx.ComboBox(choices=[], id=ID_NODEINFOSDIALOGTYPE, - name='Type', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 28), style=wx.CB_READONLY) + name='Type', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 28), style=wx.CB_READONLY, + ) self.staticText4 = wx.StaticText(id=ID_NODEINFOSDIALOGSTATICTEXT4, - label='Default String Size:', name='staticText4', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Default String Size:', name='staticText4', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.DefaultStringSize = wx.SpinCtrl(id=ID_NODEINFOSDIALOGDEFAULTSTRINGSIZE, - name='DefaultStringSize', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 25), style=wx.TE_RIGHT) + name='DefaultStringSize', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 25), style=wx.TE_RIGHT, + ) self.staticText5 = wx.StaticText(id=ID_NODEINFOSDIALOGSTATICTEXT5, - label='Description:', name='staticText5', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Description:', name='staticText5', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.Description = wx.TextCtrl(id=ID_NODEINFOSDIALOGDESCRIPTION, - name='Description', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=0, value='') + name='Description', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=0, value='', + ) self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL) self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK) @@ -767,35 +828,36 @@ def OnOK(self, event): # pylint: disable=unused-argument for item in name.split("_"): good &= item.isalnum() if not good: - message = "Node name can't be undefined or start with a digit and must be composed of alphanumerical characters or underscore!" + message = ( + "Node name can't be undefined or start with a digit and " + "must be composed of alphanumerical characters or underscore!" + ) if message: try: _ = int(self.NodeID.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.NodeID.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.NodeID.GetValue(), exc) message = "Node ID must be integer!" if message: - message = wx.MessageDialog(self, message, "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, message) self.NodeName.SetFocus() else: self.EndModal(wx.ID_OK) - def SetValues(self, name, id_, type_, description, defaultstringsize): + def SetValues(self, name, nodeid, nodetype, description, defaultstringsize): self.NodeName.SetValue(name) - self.NodeID.SetValue("0x%02X" % id_) - self.Type.SetStringSelection(type_) + self.NodeID.SetValue(f"0x{nodeid:02X}") + self.Type.SetStringSelection(nodetype) self.Description.SetValue(description) self.DefaultStringSize.SetValue(defaultstringsize) def GetValues(self): name = self.NodeName.GetValue() nodeid = int(self.NodeID.GetValue(), 16) - type_ = NODE_TYPES_DICT[self.Type.GetStringSelection()] + nodetype = NODE_TYPES_DICT[self.Type.GetStringSelection()] description = self.Description.GetValue() defaultstringsize = self.DefaultStringSize.GetValue() - return name, nodeid, type_, description, defaultstringsize + return name, nodeid, nodetype, description, defaultstringsize # ------------------------------------------------------------------------------ @@ -815,15 +877,18 @@ def GetValues(self): ID_CREATENODEDIALOGSTATICTEXT6, ID_CREATENODEDIALOGSTATICTEXT7, ID_CREATENODEDIALOGSTOREEDS, ID_CREATENODEDIALOGDESCRIPTION, ID_CREATENODEDIALOGTYPE, -] = [wx.NewId() for _init_ctrls in range(21)] +] = [wx.NewId() for _ in range(21)] class CreateNodeDialog(wx.Dialog): + """Dialog for creating new node.""" # pylint: disable=attribute-defined-outside-init def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -904,109 +969,132 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt, buttons): + def _init_ctrls(self, parent, buttons): wx.Dialog.__init__(self, id=ID_CREATENODEDIALOG, - name='CreateNodeDialog', parent=prnt, pos=wx.Point(376, 223), - size=wx.Size(450, 350), style=wx.DEFAULT_DIALOG_STYLE, - title='Create a new Node') + name='CreateNodeDialog', parent=parent, pos=wx.Point(376, 223), + size=wx.Size(450, 350), style=wx.DEFAULT_DIALOG_STYLE, + title='Create a new Node', + ) self.SetClientSize(wx.Size(450, 350)) self.staticText1 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT1, - label='Type:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Type:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText2 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT2, - label='Name:', name='staticText2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Name:', name='staticText2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText3 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT3, - label='Node ID:', name='staticText3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Node ID:', name='staticText3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.staticText4 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT4, - label='Profile:', name='staticText4', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Profile:', name='staticText4', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.Type = wx.ComboBox(choices=[], id=ID_CREATENODEDIALOGTYPE, - name='Type', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 28), style=wx.CB_READONLY) + name='Type', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 28), style=wx.CB_READONLY, + ) self.NodeName = wx.TextCtrl(id=ID_CREATENODEDIALOGNAME, name='NodeName', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=0, value='') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=0, value='', + ) self.NodeID = wx.TextCtrl(id=ID_CREATENODEDIALOGNODEID, name='NodeID', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), - style=wx.TE_RIGHT, value='') + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), + style=wx.TE_RIGHT, value='', + ) self.Profile = wx.ComboBox(choices=[], id=ID_CREATENODEDIALOGPROFILE, - name='Profile', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 28), style=wx.CB_READONLY) + name='Profile', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 28), style=wx.CB_READONLY, + ) self.Profile.Bind(wx.EVT_COMBOBOX, self.OnProfileChoice, - id=ID_CREATENODEDIALOGPROFILE) + id=ID_CREATENODEDIALOGPROFILE, + ) self.staticText5 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT5, - label='Network Management:', name='staticText5', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Network Management:', name='staticText5', + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.NMT_None = wx.RadioButton(id=ID_CREATENODEDIALOGNMT_NONE, - label='None', name='NMT_None', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=wx.RB_GROUP) + label='None', name='NMT_None', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=wx.RB_GROUP, + ) self.NMT_None.SetValue(True) self.NMT_NodeGuarding = wx.RadioButton(id=ID_CREATENODEDIALOGNMT_NODEGUARDING, - label='Node Guarding', name='NMT_NodeGuarding', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Node Guarding', name='NMT_NodeGuarding', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.NMT_NodeGuarding.SetValue(False) self.NMT_Heartbeat = wx.RadioButton(id=ID_CREATENODEDIALOGNMT_HEARTBEAT, - label='Heartbeat', name='NMT_Heartbeat', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Heartbeat', name='NMT_Heartbeat', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.NMT_Heartbeat.SetValue(False) self.staticText6 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT6, - label='Options:', name='staticText6', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Options:', name='staticText6', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.DS302 = wx.CheckBox(id=ID_CREATENODEDIALOGGENSYNC, - label='DS-302 Profile', name='DS302', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='DS-302 Profile', name='DS302', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.DS302.SetValue(False) # self.DS302.Enable(False) self.GenSYNC = wx.CheckBox(id=ID_CREATENODEDIALOGGENSYNC, - label='Generate SYNC', name='GenSYNC', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Generate SYNC', name='GenSYNC', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.GenSYNC.SetValue(False) self.Emergency = wx.CheckBox(id=ID_CREATENODEDIALOGEMERGENCY, - label='Emergency support', name='Emergency', - parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Emergency support', name='Emergency', + parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.Emergency.SetValue(False) self.SaveConfig = wx.CheckBox(id=ID_CREATENODEDIALOGSAVECONFIG, - label='Save Configuration', name='SaveConfig', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Save Configuration', name='SaveConfig', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.SaveConfig.SetValue(False) self.SaveConfig.Enable(False) self.StoreEDS = wx.CheckBox(id=ID_CREATENODEDIALOGSTOREEDS, - label='Store EDS', name='StoreEDS', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0) + label='Store EDS', name='StoreEDS', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 24), style=0, + ) self.StoreEDS.SetValue(False) self.StoreEDS.Hide() self.staticText7 = wx.StaticText(id=ID_CREATENODEDIALOGSTATICTEXT7, - label='Description:', name='staticText7', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Description:', name='staticText7', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.Description = wx.TextCtrl(id=ID_CREATENODEDIALOGDESCRIPTION, - name='Description', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=0, value='') + name='Description', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=0, value='', + ) self.Spacer = wx.Panel(id=ID_CREATENODEDIALOGSPACER, - name='Spacer', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + name='Spacer', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL, + ) self.ButtonSizer = self.CreateButtonSizer(buttons) self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK) @@ -1026,13 +1114,10 @@ def __init__(self, parent, buttons=wx.OK | wx.CANCEL): self.Description.SetValue("") self.ListProfile = {"None": ""} self.Profile.Append("None") - self.Directory = objdictgen.PROFILE_DIRECTORIES[-1] - for pdir in objdictgen.PROFILE_DIRECTORIES: - for item in sorted(os.listdir(pdir)): - name, extend = os.path.splitext(item) - if os.path.isfile(os.path.join(self.Directory, item)) and extend == ".prf" and name != "DS-302": - self.ListProfile[name] = os.path.join(self.Directory, item) - self.Profile.Append(name) + self.Directory = str(objdictgen.PROFILE_DIRECTORIES[-1]) + for p in objdictgen.PROFILES: + self.ListProfile[p.stem] = str(p) + self.Profile.Append(p.stem) self.Profile.Append("Other") self.Profile.SetStringSelection("None") self.NodeName.SetFocus() @@ -1041,18 +1126,21 @@ def OnOK(self, event): # pylint: disable=unused-argument name = self.NodeName.GetValue() message = "" if name: - if not ((not name[0].isdigit()) and all(item.isalnum() for item in name.split("_"))): - message = "Node name can't be undefined or start with a digit and must be composed of alphanumerical characters or underscore!" + if not ((not name[0].isdigit()) + and all(item.isalnum() for item in name.split("_")) + ): + message = ( + "Node name can't be undefined or start with a digit " + "and must be composed of alphanumerical characters or underscore!" + ) if message: try: _ = int(self.NodeID.GetValue(), 16) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.NodeID.GetValue(), exc)) + log.debug("ValueError: '%s': %s", self.NodeID.GetValue(), exc) message = "Node ID must be integer!" if message: - message = wx.MessageDialog(self, message, "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, message) self.NodeName.SetFocus() else: self.EndModal(wx.ID_OK) @@ -1062,25 +1150,25 @@ def GetValues(self): nodeid = 0 if self.NodeID.GetValue(): nodeid = int(self.NodeID.GetValue(), 16) - type_ = NODE_TYPES_DICT[self.Type.GetStringSelection()] + nodetype = NODE_TYPES_DICT[self.Type.GetStringSelection()] description = self.Description.GetValue() - return name, nodeid, type_, description + return name, nodeid, nodetype, description def GetProfile(self): name = self.Profile.GetStringSelection() return name, self.ListProfile[name] - def GetNMTManagement(self): + def GetNMTManagement(self) -> str: if self.NMT_None.GetValue(): return "None" if self.NMT_NodeGuarding.GetValue(): return "NodeGuarding" if self.NMT_Heartbeat.GetValue(): return "Heartbeat" - return None + return "" - def GetOptions(self): - options = [] + def GetOptions(self) -> list[str]: + options: list[str] = [] if self.DS302.GetValue(): options.append("DS302") if self.GenSYNC.GetValue(): @@ -1095,10 +1183,14 @@ def GetOptions(self): def OnProfileChoice(self, event): if self.Profile.GetStringSelection() == "Other": - dialog = wx.FileDialog(self, "Choose a file", self.Directory, "", "OD Profile files (*.prf)|*.prf|All files|*.*", style="") - dialog.ShowModal() - filepath = dialog.GetPath() - dialog.Destroy() + with wx.FileDialog( + self, "Choose a file", self.Directory, "", + "OD Profile files (*.prf)|*.prf|All files|*.*", + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return + filepath = dialog.GetPath() + if os.path.isfile(filepath): name = os.path.splitext(os.path.basename(filepath))[0] self.ListProfile[name] = filepath @@ -1119,15 +1211,18 @@ def OnProfileChoice(self, event): ID_ADDSLAVEDIALOGSLAVENODEID, ID_ADDSLAVEDIALOGEDSFILE, ID_ADDSLAVEDIALOGIMPORTEDS, ID_ADDSLAVEDIALOGSTATICTEXT1, ID_ADDSLAVEDIALOGSTATICTEXT2, ID_ADDSLAVEDIALOGSTATICTEXT3, -] = [wx.NewId() for _init_ctrls in range(8)] +] = [wx.NewId() for _ in range(8)] class AddSlaveDialog(wx.Dialog): + """UI for adding a slave to the nodelist.""" # pylint: disable=attribute-defined-outside-init def _init_coll_flexGridSizer1_Items(self, parent): - parent.Add(self.MainSizer, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.MainSizer, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_flexGridSizer1_Growables(self, parent): parent.AddGrowableCol(0) @@ -1162,42 +1257,51 @@ def _init_sizers(self): self.SetSizer(self.flexGridSizer1) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_ADDSLAVEDIALOG, - name='AddSlaveDialog', parent=prnt, pos=wx.Point(376, 223), - size=wx.Size(300, 250), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, - title='Add a slave to nodelist') + name='AddSlaveDialog', parent=parent, pos=wx.Point(376, 223), + size=wx.Size(300, 250), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, + title='Add a slave to nodelist', + ) self.SetClientSize(wx.Size(300, 250)) self.staticText1 = wx.StaticText(id=ID_ADDSLAVEDIALOGSTATICTEXT1, - label='Slave Name:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Slave Name:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.SlaveName = wx.TextCtrl(id=ID_ADDSLAVEDIALOGSLAVENAME, - name='SlaveName', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=0) + name='SlaveName', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=0, + ) self.staticText2 = wx.StaticText(id=ID_ADDSLAVEDIALOGSTATICTEXT2, - label='Slave Node ID:', name='staticText2', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='Slave Node ID:', name='staticText2', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.SlaveNodeID = wx.TextCtrl(id=ID_ADDSLAVEDIALOGSLAVENODEID, - name='SlaveName', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 24), style=wx.ALIGN_RIGHT) + name='SlaveName', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.ALIGN_RIGHT + ) self.staticText3 = wx.StaticText(id=ID_ADDSLAVEDIALOGSTATICTEXT3, - label='EDS File:', name='staticText3', parent=self, - pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0) + label='EDS File:', name='staticText3', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 17), style=0, + ) self.EDSFile = wx.ComboBox(id=ID_ADDSLAVEDIALOGEDSFILE, - name='EDSFile', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 28), style=wx.CB_READONLY) + name='EDSFile', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 28), style=wx.CB_READONLY, + ) self.ImportEDS = wx.Button(id=ID_ADDSLAVEDIALOGIMPORTEDS, label='Import EDS', - name='ImportEDS', parent=self, pos=wx.Point(0, 0), - size=wx.Size(100, 32), style=0) + name='ImportEDS', parent=self, pos=wx.Point(0, 0), + size=wx.Size(100, 32), style=0, + ) self.ImportEDS.Bind(wx.EVT_BUTTON, self.OnImportEDSButton, - id=ID_ADDSLAVEDIALOGIMPORTEDS) + id=ID_ADDSLAVEDIALOGIMPORTEDS, + ) self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE) self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK) @@ -1223,59 +1327,50 @@ def OnOK(self, event): # pylint: disable=unused-argument if i == 0: text += item elif i == len(error) - 1: - text += " and %s" % item + text += f" and {item}" else: - text += ", %s" % item - message = wx.MessageDialog(self, "Form isn't complete. %s must be filled!" % text, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + text += f", {item}" + display_error_dialog(self, f"Form isn't complete. {text} must be filled!") else: try: - nodeid = self.SlaveNodeID.GetValue() - if "x" in nodeid: - nodeid = int(nodeid, 16) + nodestr = self.SlaveNodeID.GetValue() + if "x" in nodestr: + nodeid = int(nodestr, 16) else: - nodeid = int(nodeid) + nodeid = int(nodestr) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.SlaveNodeID.GetValue(), exc)) - message = wx.MessageDialog(self, "Slave Node ID must be a value in decimal or hexadecimal!", "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + log.debug("ValueError: '%s': %s", self.SlaveNodeID.GetValue(), exc) + display_error_dialog(self, "Slave Node ID must be a value in decimal or hexadecimal!") return if not 0 <= nodeid <= 127: - message = wx.MessageDialog(self, "Slave Node ID must be between 0 and 127!", "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, "Slave Node ID must be between 0 and 127!") elif nodeid == self.NodeList.GetMasterNodeID() or nodeid in self.NodeList.GetSlaveIDs(): - message = wx.MessageDialog(self, "A Node with this ID already exist in the network!", "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, "A Node with this ID already exist in the network!") else: self.EndModal(wx.ID_OK) def OnImportEDSButton(self, event): - dialog = wx.FileDialog(self, - "Choose an EDS file", - os.path.expanduser("~"), - "", - "EDS files (*.eds)|*.eds|All files|*.*") - if dialog.ShowModal() == wx.ID_OK: + with wx.FileDialog( + self, "Choose an EDS file", os.path.expanduser("~"), "", + "EDS files (*.eds)|*.eds|All files|*.*", + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return filepath = dialog.GetPath() - else: - filepath = "" - dialog.Destroy() + if os.path.isfile(filepath): edsfile = self.NodeList.GetEDSFilePath(filepath) if os.path.isfile(edsfile): - dialog = wx.MessageDialog(self, "EDS file already imported\nWould you like to replace it ?", "Question", wx.YES_NO | wx.ICON_QUESTION) - if dialog.ShowModal() == wx.ID_YES: - try: - self.NodeList.ImportEDSFile(filepath) - except Exception as exc: # pylint: disable=broad-except - dialog = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - dialog.ShowModal() - dialog.Destroy() - dialog.Destroy() + with wx.MessageDialog(self, + "EDS file already imported\nWould you like to replace it ?", + "Question", wx.YES_NO | wx.ICON_QUESTION, + ) as dialog: + if dialog.ShowModal() == wx.ID_YES: + try: + self.NodeList.ImportEDSFile(filepath) + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) + self.RefreshEDSFile() event.Skip() @@ -1291,14 +1386,14 @@ def SetNodeList(self, nodelist): self.NodeList = nodelist self.RefreshEDSFile() - def GetValues(self): - values = {} + def GetValues(self) -> TGetValues: + values: TGetValues = {} values["slaveName"] = self.SlaveName.GetValue() - nodeid = self.SlaveNodeID.GetValue() - if "x" in nodeid: - values["slaveNodeID"] = int(nodeid, 16) + nodestr = self.SlaveNodeID.GetValue() + if "x" in nodestr: + values["slaveNodeID"] = int(nodestr, 16) else: - values["slaveNodeID"] = int(nodeid) + values["slaveNodeID"] = int(nodestr) values["edsFile"] = self.EDSFile.GetStringSelection() return values @@ -1316,7 +1411,7 @@ class DCFEntryValuesTable(wx.grid.GridTableBase): """ A custom wxGrid Table using user supplied data """ - def __init__(self, parent, data, colnames): + def __init__(self, parent: "DCFEntryValuesDialog", data, colnames): # The base class must be initialized *first* wx.grid.GridTableBase.__init__(self) self.data = data @@ -1365,8 +1460,13 @@ def ResetView(self, grid): """ grid.BeginBatch() for current, new, delmsg, addmsg in [ - (self._rows, self.GetNumberRows(), wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED), - (self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED), + ( + self._rows, self.GetNumberRows(), + wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED + ),( + self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, + wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED + ), ]: if new < current: msg = wx.grid.GridTableMessage(self, delmsg, new, current - new) @@ -1389,6 +1489,7 @@ def ResetView(self, grid): def UpdateValues(self, grid): """Update all displayed values""" # This sends an event to the grid table to update all of the values + # FIXME: This symbol has probably been removed from wx. Needs more investigation msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) @@ -1424,24 +1525,22 @@ def Empty(self): ID_DCFENTRYVALUESDIALOGADDBUTTON, ID_DCFENTRYVALUESDIALOGDELETEBUTTON, ID_DCFENTRYVALUESDIALOGUPBUTTON, ID_DCFENTRYVALUESDIALOGDOWNBUTTON, ID_VARIABLEEDITORPANELSTATICTEXT1, -] = [wx.NewId() for _init_ctrls in range(7)] +] = [wx.NewId() for _ in range(7)] class DCFEntryValuesDialog(wx.Dialog): + """Dialog to edit DCF Entry values.""" # pylint: disable=attribute-defined-outside-init - if wx.VERSION < (2, 6, 0): - def Bind(self, event, function, id=None): # pylint: disable=invalid-name, redefined-builtin - if id is not None: - event(self, id, function) - else: - event(self, function) - def _init_coll_MainSizer_Items(self, parent): - parent.Add(self.staticText1, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ValuesGrid, 0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonPanelSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT) - parent.Add(self.ButtonSizer, 0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) + parent.Add(self.staticText1, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ValuesGrid, 0, border=20, + flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonPanelSizer, 0, + border=20, flag=wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT) + parent.Add(self.ButtonSizer, 0, border=20, + flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT) def _init_coll_MainSizer_Growables(self, parent): parent.AddGrowableCol(0) @@ -1463,52 +1562,55 @@ def _init_sizers(self): self.SetSizer(self.MainSizer) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Dialog.__init__(self, id=ID_DCFENTRYVALUESDIALOG, - name='DCFEntryValuesDialog', parent=prnt, pos=wx.Point(376, 223), - size=wx.Size(400, 300), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, - title='Edit DCF Entry Values') + name='DCFEntryValuesDialog', parent=parent, pos=wx.Point(376, 223), + size=wx.Size(400, 300), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, + title='Edit DCF Entry Values', + ) self.SetClientSize(wx.Size(400, 300)) self.staticText1 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT1, - label='Entry Values:', name='staticText1', parent=self, - pos=wx.Point(0, 0), size=wx.Size(95, 17), style=0) + label='Entry Values:', name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.Size(95, 17), style=0, + ) self.ValuesGrid = wx.grid.Grid(id=ID_DCFENTRYVALUESDIALOGVALUESGRID, - name='ValuesGrid', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 150), style=wx.VSCROLL) + name='ValuesGrid', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 150), style=wx.VSCROLL, + ) self.ValuesGrid.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL, False, - 'Sans')) + 'Sans')) self.ValuesGrid.SetLabelFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL, - False, 'Sans')) + False, 'Sans')) self.ValuesGrid.SetRowLabelSize(0) self.ValuesGrid.SetSelectionBackground(wx.WHITE) self.ValuesGrid.SetSelectionForeground(wx.BLACK) - if wx.VERSION >= (2, 6, 0): - self.ValuesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, self.OnValuesGridCellChange) - self.ValuesGrid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnValuesGridSelectCell) - else: - wx.grid.EVT_GRID_CELL_CHANGING(self.ValuesGrid, self.OnValuesGridCellChange) - wx.grid.EVT_GRID_SELECT_CELL(self.ValuesGrid, self.OnValuesGridSelectCell) + self.ValuesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, self.OnValuesGridCellChange) + self.ValuesGrid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnValuesGridSelectCell) self.AddButton = wx.Button(id=ID_DCFENTRYVALUESDIALOGADDBUTTON, label='Add', - name='AddButton', parent=self, pos=wx.Point(0, 0), - size=wx.Size(72, 32), style=0) + name='AddButton', parent=self, pos=wx.Point(0, 0), + size=wx.Size(72, 32), style=0, + ) self.Bind(wx.EVT_BUTTON, self.OnAddButton, id=ID_DCFENTRYVALUESDIALOGADDBUTTON) self.DeleteButton = wx.Button(id=ID_DCFENTRYVALUESDIALOGDELETEBUTTON, label='Delete', - name='DeleteButton', parent=self, pos=wx.Point(0, 0), - size=wx.Size(72, 32), style=0) + name='DeleteButton', parent=self, pos=wx.Point(0, 0), + size=wx.Size(72, 32), style=0, + ) self.Bind(wx.EVT_BUTTON, self.OnDeleteButton, id=ID_DCFENTRYVALUESDIALOGDELETEBUTTON) self.UpButton = wx.Button(id=ID_DCFENTRYVALUESDIALOGUPBUTTON, label='^', - name='UpButton', parent=self, pos=wx.Point(0, 0), - size=wx.Size(32, 32), style=0) + name='UpButton', parent=self, pos=wx.Point(0, 0), + size=wx.Size(32, 32), style=0, + ) self.Bind(wx.EVT_BUTTON, self.OnUpButton, id=ID_DCFENTRYVALUESDIALOGUPBUTTON) self.DownButton = wx.Button(id=ID_DCFENTRYVALUESDIALOGDOWNBUTTON, label='v', - name='DownButton', parent=self, pos=wx.Point(0, 0), - size=wx.Size(32, 32), style=0) + name='DownButton', parent=self, pos=wx.Point(0, 0), + size=wx.Size(32, 32), style=0, + ) self.Bind(wx.EVT_BUTTON, self.OnDownButton, id=ID_DCFENTRYVALUESDIALOGDOWNBUTTON) self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE) @@ -1530,11 +1632,9 @@ def OnValuesGridCellChange(self, event): colname = self.Table.GetColLabelValue(col) value = self.Table.GetValue(row, col) try: - self.Values[row][colname] = int(value, 16) + self.Values[row][colname] = int(value, 16) # pyright: ignore[reportArgumentType] except ValueError: - message = wx.MessageDialog(self, "'%s' is not a valid value!" % value, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, f"'{value}' is not a valid value!") wx.CallAfter(self.RefreshValues) event.Skip() @@ -1582,30 +1682,32 @@ def MoveValue(self, value_index, move): self.RefreshValues() def SetValues(self, values): + # FIXME: THis function needs rewrite, as the map.be_to_le is not ported properly to py3 self.Values = [] if values: data = values[4:] current = 0 - for _ in range(BE_to_LE(values[:4])): + for _ in range(maps.be_to_le(values[:4])): value = {} - value["Index"] = BE_to_LE(data[current:current + 2]) - value["Subindex"] = BE_to_LE(data[current + 2:current + 3]) - size = BE_to_LE(data[current + 3:current + 7]) + value["Index"] = maps.be_to_le(data[current:current + 2]) + value["Subindex"] = maps.be_to_le(data[current + 2:current + 3]) + size = maps.be_to_le(data[current + 3:current + 7]) value["Size"] = size - value["Value"] = BE_to_LE(data[current + 7:current + 7 + size]) + value["Value"] = maps.be_to_le(data[current + 7:current + 7 + size]) current += 7 + size self.Values.append(value) self.RefreshValues() def GetValues(self): + # FIXME: THis function needs rewrite, as the map.be_to_le is not ported properly to py3 if len(self.Values) <= 0: return "" - value = LE_to_BE(len(self.Values), 4) + value = maps.le_to_be(len(self.Values), 4) for row in self.Values: - value += LE_to_BE(row["Index"], 2) - value += LE_to_BE(row["Subindex"], 1) - value += LE_to_BE(row["Size"], 4) - value += LE_to_BE(row["Value"], row["Size"]) + value += maps.le_to_be(row["Index"], 2) + value += maps.le_to_be(row["Subindex"], 1) + value += maps.le_to_be(row["Size"], 4) + value += maps.le_to_be(row["Value"], row["Size"]) return value def RefreshValues(self): @@ -1614,10 +1716,10 @@ def RefreshValues(self): data = [] for value in self.Values: row = {} - row["Index"] = "%04X" % value["Index"] - row["Subindex"] = "%02X" % value["Subindex"] - row["Size"] = "%08X" % value["Size"] - row["Value"] = ("%0" + "%d" % (value["Size"] * 2) + "X") % value["Value"] + row["Index"] = f"{value['Index']:04X}" + row["Subindex"] = f"{value['Subindex']:02X}" + row["Size"] = f"{value['Size']:08X}" + row["Value"] = ("{:0" + str(value['Size'] * 2) + "X}").format(value["Value"]) data.append(row) self.Table.SetData(data) self.Table.ResetView(self.ValuesGrid) diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index b4bd8c7..88d3467 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -1,3 +1,4 @@ +"""UI Exception module.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -22,6 +23,7 @@ import sys import time import traceback +from pathlib import Path import wx @@ -29,7 +31,7 @@ # Exception Handler # ------------------------------------------------------------------------------ -def Display_Exception_Dialog(e_type, e_value, e_tb): +def _display_exception_dialog(e_type, e_value, e_tb, parent=None): trcbck_lst = [] for i, line in enumerate(traceback.extract_tb(e_tb)): trcbck = " " + str(i + 1) + ". " @@ -45,7 +47,7 @@ def Display_Exception_Dialog(e_type, e_value, e_tb): if cap: cap.ReleaseMouse() - dlg = wx.SingleChoiceDialog(None, + with wx.SingleChoiceDialog(parent, (""" An error has occured. Click on OK for saving an error report. @@ -54,19 +56,21 @@ def Display_Exception_Dialog(e_type, e_value, e_tb): """ + str(e_type) + " : " + str(e_value)), "Error", - trcbck_lst) - try: - res = (dlg.ShowModal() == wx.ID_OK) - finally: - dlg.Destroy() + trcbck_lst + ) as dlg: + res = dlg.ShowModal() == wx.ID_OK return res -def Display_Error_Dialog(e_value): - message = wx.MessageDialog(None, str(e_value), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() +def display_exception_dialog(parent): + e_type, e_value, e_tb = sys.exc_info() + handle_exception(e_type, e_value, e_tb, parent) + + +def display_error_dialog(parent, message, caption="Error"): + with wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_ERROR) as dialog: + return dialog.ShowModal() def get_last_traceback(tb): @@ -76,47 +80,57 @@ def get_last_traceback(tb): def format_namespace(dic, indent=' '): - return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in dic.items()]) - - -IGNORED_EXCEPTIONS = [] # a problem with a line in a module is only reported once per session - - -def AddExceptHook(path, app_version='[No version]'): # , ignored_exceptions=[]): - - def handle_exception(e_type, e_value, e_traceback): - traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func - last_tb = get_last_traceback(e_traceback) - ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno) - if str(e_value).startswith("!!!"): # FIXME: Special exception handling - Display_Error_Dialog(e_value) - elif ex not in IGNORED_EXCEPTIONS: - IGNORED_EXCEPTIONS.append(ex) - result = Display_Exception_Dialog(e_type, e_value, e_traceback) - if result: - info = { - 'app-title': wx.GetApp().GetAppName(), # app_title - 'app-version': app_version, - 'wx-version': wx.VERSION_STRING, - 'wx-platform': wx.Platform, - 'python-version': platform.python_version(), # sys.version.split()[0], - 'platform': platform.platform(), - 'e-type': e_type, - 'e-value': e_value, - 'date': time.ctime(), - 'cwd': os.getcwd(), - } - if e_traceback: - info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value) - last_tb = get_last_traceback(e_traceback) - exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred - info['locals'] = format_namespace(exception_locals) - if 'self' in exception_locals: - info['self'] = format_namespace(exception_locals['self'].__dict__) - - with open(path + os.sep + "bug_report_" + info['date'].replace(':', '-').replace(' ', '_') + ".txt", 'w') as output: - for a in sorted(info): - output.write(a + ":\n" + str(info[a]) + "\n\n") + return '\n'.join(f"{indent}{k}: {repr(v)[:10000]}" for k, v in dic.items()) + + +# a problem with a line in a module is only reported once per session +IGNORED_EXCEPTIONS = [] + + +def handle_exception(e_type, e_value, e_traceback, parent=None): + + # Import here to prevent circular import + from objdictgen import \ + __version__ # pylint: disable=import-outside-toplevel + + # this is very helpful when there's an exception in the rest of this func + traceback.print_exception(e_type, e_value, e_traceback) + last_tb = get_last_traceback(e_traceback) + ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno) + if str(e_value).startswith("!!!"): # FIXME: Special exception handling + display_error_dialog(parent, str(e_value)) + if ex in IGNORED_EXCEPTIONS: + return + IGNORED_EXCEPTIONS.append(ex) + result = _display_exception_dialog(e_type, e_value, e_traceback, parent) + if result: + info = { + 'app-title': wx.GetApp().GetAppName(), # app_title + 'app-version': __version__, + 'wx-version': wx.VERSION_STRING, # FIXME: Missing from wx + 'wx-platform': wx.Platform, + 'python-version': platform.python_version(), # sys.version.split()[0], + 'platform': platform.platform(), + 'e-type': e_type, + 'e-value': e_value, + 'date': time.ctime(), + 'cwd': os.getcwd(), + } + if e_traceback: + info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + f'{e_type}: {e_value}' + # the locals at the level of the stack trace where the exception actually occurred + exception_locals = last_tb.tb_frame.f_locals + info['locals'] = format_namespace(exception_locals) + if 'self' in exception_locals: + info['self'] = format_namespace(exception_locals['self'].__dict__) + + f_date = info['date'].replace(':', '-').replace(' ', '_') + with open(Path.cwd() / f"bug_report_{f_date}.txt", 'w', encoding="utf-8") as fp: + for a, t in info.items(): + fp.write(f"{a}:\n{t}\n\n") + + +def add_except_hook(): # sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args) sys.excepthook = handle_exception diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 85b9e4e..09cef71 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -1,3 +1,4 @@ +"""Network Editor.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -25,9 +26,9 @@ import wx import objdictgen -from objdictgen.nodelist import NodeList +import objdictgen.nodelist as nl # Because NodeList is also an attr in NetworkEdit from objdictgen.nodemanager import NodeManager -from objdictgen.ui.exception import AddExceptHook +from objdictgen.ui.exception import add_except_hook, display_exception_dialog from objdictgen.ui.networkeditortemplate import NetworkEditorTemplate log = logging.getLogger('objdictgen') @@ -35,33 +36,40 @@ def usage(): print("\nUsage of networkedit.py :") - print("\n %s [Projectpath]\n" % sys.argv[0]) + print(f"\n {sys.argv[0]} [Projectpath]\n") [ ID_NETWORKEDIT, ID_NETWORKEDITNETWORKNODES, ID_NETWORKEDITHELPBAR, -] = [wx.NewId() for _init_ctrls in range(3)] +] = [wx.NewId() for _ in range(3)] +# AddMenu_Items [ ID_NETWORKEDITNETWORKMENUBUILDMASTER, -] = [wx.NewId() for _init_coll_AddMenu_Items in range(1)] +] = [wx.NewId() for _ in range(1)] +# EditMenu_Items [ ID_NETWORKEDITEDITMENUNODEINFOS, ID_NETWORKEDITEDITMENUDS301PROFILE, ID_NETWORKEDITEDITMENUDS302PROFILE, ID_NETWORKEDITEDITMENUOTHERPROFILE, -] = [wx.NewId() for _init_coll_EditMenu_Items in range(4)] +] = [wx.NewId() for _ in range(4)] +# AddMenu_Items [ ID_NETWORKEDITADDMENUSDOSERVER, ID_NETWORKEDITADDMENUSDOCLIENT, ID_NETWORKEDITADDMENUPDOTRANSMIT, ID_NETWORKEDITADDMENUPDORECEIVE, ID_NETWORKEDITADDMENUMAPVARIABLE, ID_NETWORKEDITADDMENUUSERTYPE, -] = [wx.NewId() for _init_coll_AddMenu_Items in range(6)] +] = [wx.NewId() for _ in range(6)] -class NetworkEdit(wx.Frame, NetworkEditorTemplate): +class NetworkEdit(NetworkEditorTemplate): + """Network Editor UI.""" # pylint: disable=attribute-defined-outside-init + # Type helpers + NodeList: nl.NodeList + EDITMENU_ID = ID_NETWORKEDITEDITMENUOTHERPROFILE def _init_coll_MenuBar_Menus(self, parent): @@ -73,17 +81,17 @@ def _init_coll_MenuBar_Menus(self, parent): def _init_coll_FileMenu_Items(self, parent): parent.Append(helpString='', id=wx.ID_NEW, - kind=wx.ITEM_NORMAL, item='New\tCTRL+N') + kind=wx.ITEM_NORMAL, item='New\tCTRL+N') parent.Append(helpString='', id=wx.ID_OPEN, - kind=wx.ITEM_NORMAL, item='Open\tCTRL+O') + kind=wx.ITEM_NORMAL, item='Open\tCTRL+O') parent.Append(helpString='', id=wx.ID_CLOSE, - kind=wx.ITEM_NORMAL, item='Close\tCTRL+W') + kind=wx.ITEM_NORMAL, item='Close\tCTRL+W') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_SAVE, - kind=wx.ITEM_NORMAL, item='Save\tCTRL+S') + kind=wx.ITEM_NORMAL, item='Save\tCTRL+S') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_EXIT, - kind=wx.ITEM_NORMAL, item='Exit') + kind=wx.ITEM_NORMAL, item='Exit') self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW) self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN) self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE) @@ -92,12 +100,12 @@ def _init_coll_FileMenu_Items(self, parent): def _init_coll_NetworkMenu_Items(self, parent): parent.Append(helpString='', id=wx.ID_ADD, - kind=wx.ITEM_NORMAL, item='Add Slave Node') + kind=wx.ITEM_NORMAL, item='Add Slave Node') parent.Append(helpString='', id=wx.ID_DELETE, - kind=wx.ITEM_NORMAL, item='Remove Slave Node') + kind=wx.ITEM_NORMAL, item='Remove Slave Node') parent.AppendSeparator() parent.Append(helpString='', id=ID_NETWORKEDITNETWORKMENUBUILDMASTER, - kind=wx.ITEM_NORMAL, item='Build Master Dictionary') + kind=wx.ITEM_NORMAL, item='Build Master Dictionary') self.Bind(wx.EVT_MENU, self.OnAddSlaveMenu, id=wx.ID_ADD) self.Bind(wx.EVT_MENU, self.OnRemoveSlaveMenu, id=wx.ID_DELETE) # self.Bind(wx.EVT_MENU, self.OnBuildMasterMenu, @@ -105,58 +113,58 @@ def _init_coll_NetworkMenu_Items(self, parent): def _init_coll_EditMenu_Items(self, parent): parent.Append(helpString='', id=wx.ID_REFRESH, - kind=wx.ITEM_NORMAL, item='Refresh\tCTRL+R') + kind=wx.ITEM_NORMAL, item='Refresh\tCTRL+R') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_UNDO, - kind=wx.ITEM_NORMAL, item='Undo\tCTRL+Z') + kind=wx.ITEM_NORMAL, item='Undo\tCTRL+Z') parent.Append(helpString='', id=wx.ID_REDO, - kind=wx.ITEM_NORMAL, item='Redo\tCTRL+Y') + kind=wx.ITEM_NORMAL, item='Redo\tCTRL+Y') parent.AppendSeparator() parent.Append(helpString='', id=ID_NETWORKEDITEDITMENUNODEINFOS, - kind=wx.ITEM_NORMAL, item='Node infos') + kind=wx.ITEM_NORMAL, item='Node infos') parent.Append(helpString='', id=ID_NETWORKEDITEDITMENUDS301PROFILE, - kind=wx.ITEM_NORMAL, item='DS-301 Profile') + kind=wx.ITEM_NORMAL, item='DS-301 Profile') parent.Append(helpString='', id=ID_NETWORKEDITEDITMENUDS302PROFILE, - kind=wx.ITEM_NORMAL, item='DS-302 Profile') + kind=wx.ITEM_NORMAL, item='DS-302 Profile') parent.Append(helpString='', id=ID_NETWORKEDITEDITMENUOTHERPROFILE, - kind=wx.ITEM_NORMAL, item='Other Profile') + kind=wx.ITEM_NORMAL, item='Other Profile') self.Bind(wx.EVT_MENU, self.OnRefreshMenu, id=wx.ID_REFRESH) self.Bind(wx.EVT_MENU, self.OnUndoMenu, id=wx.ID_UNDO) self.Bind(wx.EVT_MENU, self.OnRedoMenu, id=wx.ID_REDO) self.Bind(wx.EVT_MENU, self.OnNodeInfosMenu, - id=ID_NETWORKEDITEDITMENUNODEINFOS) + id=ID_NETWORKEDITEDITMENUNODEINFOS) self.Bind(wx.EVT_MENU, self.OnCommunicationMenu, - id=ID_NETWORKEDITEDITMENUDS301PROFILE) + id=ID_NETWORKEDITEDITMENUDS301PROFILE) self.Bind(wx.EVT_MENU, self.OnOtherCommunicationMenu, - id=ID_NETWORKEDITEDITMENUDS302PROFILE) + id=ID_NETWORKEDITEDITMENUDS302PROFILE) self.Bind(wx.EVT_MENU, self.OnEditProfileMenu, - id=ID_NETWORKEDITEDITMENUOTHERPROFILE) + id=ID_NETWORKEDITEDITMENUOTHERPROFILE) def _init_coll_AddMenu_Items(self, parent): parent.Append(helpString='', id=ID_NETWORKEDITADDMENUSDOSERVER, - kind=wx.ITEM_NORMAL, item='SDO Server') + kind=wx.ITEM_NORMAL, item='SDO Server') parent.Append(helpString='', id=ID_NETWORKEDITADDMENUSDOCLIENT, - kind=wx.ITEM_NORMAL, item='SDO Client') + kind=wx.ITEM_NORMAL, item='SDO Client') parent.Append(helpString='', id=ID_NETWORKEDITADDMENUPDOTRANSMIT, - kind=wx.ITEM_NORMAL, item='PDO Transmit') + kind=wx.ITEM_NORMAL, item='PDO Transmit') parent.Append(helpString='', id=ID_NETWORKEDITADDMENUPDORECEIVE, - kind=wx.ITEM_NORMAL, item='PDO Receive') + kind=wx.ITEM_NORMAL, item='PDO Receive') parent.Append(helpString='', id=ID_NETWORKEDITADDMENUMAPVARIABLE, - kind=wx.ITEM_NORMAL, item='Map Variable') + kind=wx.ITEM_NORMAL, item='Map Variable') parent.Append(helpString='', id=ID_NETWORKEDITADDMENUUSERTYPE, - kind=wx.ITEM_NORMAL, item='User Type') + kind=wx.ITEM_NORMAL, item='User Type') self.Bind(wx.EVT_MENU, self.OnAddSDOServerMenu, - id=ID_NETWORKEDITADDMENUSDOSERVER) + id=ID_NETWORKEDITADDMENUSDOSERVER) self.Bind(wx.EVT_MENU, self.OnAddSDOClientMenu, - id=ID_NETWORKEDITADDMENUSDOCLIENT) + id=ID_NETWORKEDITADDMENUSDOCLIENT) self.Bind(wx.EVT_MENU, self.OnAddPDOTransmitMenu, - id=ID_NETWORKEDITADDMENUPDOTRANSMIT) + id=ID_NETWORKEDITADDMENUPDOTRANSMIT) self.Bind(wx.EVT_MENU, self.OnAddPDOReceiveMenu, - id=ID_NETWORKEDITADDMENUPDORECEIVE) + id=ID_NETWORKEDITADDMENUPDORECEIVE) self.Bind(wx.EVT_MENU, self.OnAddMapVariableMenu, - id=ID_NETWORKEDITADDMENUMAPVARIABLE) + id=ID_NETWORKEDITADDMENUMAPVARIABLE) self.Bind(wx.EVT_MENU, self.OnAddUserTypeMenu, - id=ID_NETWORKEDITADDMENUUSERTYPE) + id=ID_NETWORKEDITADDMENUUSERTYPE) def _init_coll_HelpBar_Fields(self, parent): parent.SetFieldsCount(3) @@ -176,8 +184,6 @@ def _init_utils(self): self.NetworkMenu = wx.Menu(title='') self.EditMenu = wx.Menu(title='') self.AddMenu = wx.Menu(title='') - # FIXME: Unused. Delete this? - # self.HelpMenu = wx.Menu(title='') self._init_coll_MenuBar_Menus(self.MenuBar) if self.ModeSolo: @@ -186,10 +192,12 @@ def _init_utils(self): self._init_coll_EditMenu_Items(self.EditMenu) self._init_coll_AddMenu_Items(self.AddMenu) - def _init_ctrls(self, prnt): - wx.Frame.__init__(self, id=ID_NETWORKEDIT, name='networkedit', - parent=prnt, pos=wx.Point(149, 178), size=wx.Size(1000, 700), - style=wx.DEFAULT_FRAME_STYLE, title='Networkedit') + def _init_ctrls(self, parent): + wx.Frame.__init__( + self, id=ID_NETWORKEDIT, name='networkedit', + parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700), + style=wx.DEFAULT_FRAME_STYLE, title='Networkedit', + ) self._init_utils() self.SetClientSize(wx.Size(1000, 700)) self.SetMenuBar(self.MenuBar) @@ -201,21 +209,24 @@ def _init_ctrls(self, prnt): NetworkEditorTemplate._init_ctrls(self, self) - self.HelpBar = wx.StatusBar(id=ID_NETWORKEDITHELPBAR, name='HelpBar', - parent=self, style=wx.STB_SIZEGRIP) + self.HelpBar = wx.StatusBar( + id=ID_NETWORKEDITHELPBAR, name='HelpBar', + parent=self, style=wx.STB_SIZEGRIP, + ) self._init_coll_HelpBar_Fields(self.HelpBar) self.SetStatusBar(self.HelpBar) - def __init__(self, parent, nodelist=None, projectOpen=None): + def __init__(self, parent, nodelist: nl.NodeList|None = None, projectOpen=None): if nodelist is None: - NetworkEditorTemplate.__init__(self, NodeList(NodeManager()), self, True) + NetworkEditorTemplate.__init__(self, nl.NodeList(NodeManager()), True) else: - NetworkEditorTemplate.__init__(self, nodelist, self, False) + NetworkEditorTemplate.__init__(self, nodelist, False) self._init_ctrls(parent) - # FIXME: Unused. Delete this? - # self.HtmlFrameOpened = [] - icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "ui", "networkedit.ico"), wx.BITMAP_TYPE_ICO) + icon = wx.Icon( + str(objdictgen.SCRIPT_DIRECTORY / "img" / "networkedit.ico"), + wx.BITMAP_TYPE_ICO, + ) self.SetIcon(icon) if self.ModeSolo: @@ -226,10 +237,10 @@ def __init__(self, parent, nodelist=None, projectOpen=None): self.RefreshNetworkNodes() self.RefreshProfileMenu() except Exception as exc: - log.debug("Exception: %s" % exc) + log.debug("Exception: %s", exc) raise # FIXME: Temporary. Orginal code swallows exception else: - self.NodeList = None + self.NodeList = None # FIXME: Why is this needed? else: self.NodeList.CurrentSelected = 0 self.RefreshNetworkNodes() @@ -242,114 +253,113 @@ def __init__(self, parent, nodelist=None, projectOpen=None): def OnCloseFrame(self, event): self.Closing = True - if not self.ModeSolo and getattr(self, "_onclose", None) is not None: - self._onclose() event.Skip() def OnQuitMenu(self, event): # pylint: disable=unused-argument self.Close() -# ------------------------------------------------------------------------------ -# Load and Save Funtions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Load and Save Funtions + # -------------------------------------------------------------------------- def OnNewProjectMenu(self, event): # pylint: disable=unused-argument if self.NodeList: defaultpath = os.path.dirname(self.NodeList.Root) else: defaultpath = os.getcwd() - dialog = wx.DirDialog(self, "Choose a project", defaultpath, wx.DD_NEW_DIR_BUTTON) - if dialog.ShowModal() == wx.ID_OK: + + with wx.DirDialog( + self, "Choose a project", defaultpath, wx.DD_NEW_DIR_BUTTON + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return projectpath = dialog.GetPath() - if os.path.isdir(projectpath) and len(os.listdir(projectpath)) == 0: - manager = NodeManager() - nodelist = NodeList(manager) - try: - nodelist.LoadProject(projectpath) - self.Manager = manager - self.NodeList = nodelist - self.NodeList.CurrentSelected = 0 + if os.path.isdir(projectpath) and len(os.listdir(projectpath)) == 0: + manager = NodeManager() + nodelist = nl.NodeList(manager) + try: + nodelist.LoadProject(projectpath) - self.RefreshNetworkNodes() - self.RefreshBufferState() - self.RefreshTitle() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + self.Manager = manager + self.NodeList = nodelist + self.NodeList.CurrentSelected = 0 + + self.RefreshNetworkNodes() + self.RefreshBufferState() + self.RefreshTitle() + self.RefreshProfileMenu() + self.RefreshMainMenu() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnOpenProjectMenu(self, event): # pylint: disable=unused-argument if self.NodeList: defaultpath = os.path.dirname(self.NodeList.Root) else: defaultpath = os.getcwd() - dialog = wx.DirDialog(self, "Choose a project", defaultpath, 0) - if dialog.ShowModal() == wx.ID_OK: + + projectpath = "" + with wx.DirDialog(self, "Choose a project", defaultpath, 0) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return projectpath = dialog.GetPath() - if os.path.isdir(projectpath): - manager = NodeManager() - nodelist = NodeList(manager) - try: - nodelist.LoadProject(projectpath) - self.Manager = manager - self.NodeList = nodelist - self.NodeList.CurrentSelected = 0 + if os.path.isdir(projectpath): + manager = NodeManager() + nodelist = nl.NodeList(manager) + try: + nodelist.LoadProject(projectpath) - self.RefreshNetworkNodes() - self.RefreshBufferState() - self.RefreshTitle() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + self.Manager = manager + self.NodeList = nodelist + self.NodeList.CurrentSelected = 0 + + self.RefreshNetworkNodes() + self.RefreshBufferState() + self.RefreshTitle() + self.RefreshProfileMenu() + self.RefreshMainMenu() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnSaveProjectMenu(self, event): # pylint: disable=unused-argument - if not self.ModeSolo and getattr(self, "_onsave", None) is not None: - self._onsave() - else: - try: - self.NodeList.SaveProject() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + try: + self.NodeList.SaveProject() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnCloseProjectMenu(self, event): # pylint: disable=unused-argument if self.NodeList: if self.NodeList.HasChanged(): - dialog = wx.MessageDialog(self, "There are changes, do you want to save?", "Close Project", wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION) - answer = dialog.ShowModal() - dialog.Destroy() + with wx.MessageDialog( + self, "There are changes, do you want to save?", "Close Project", + wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION, + ) as dialog: + answer = dialog.ShowModal() + if answer == wx.ID_YES: try: self.NodeList.SaveProject() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) elif answer == wx.ID_NO: self.NodeList.Changed = False + if not self.NodeList.HasChanged(): - self.Manager = None - self.NodeList = None + self.Manager = None # FIXME: Why is this needed? + self.NodeList = None # FIXME: Why is this needed? self.RefreshNetworkNodes() self.RefreshTitle() self.RefreshMainMenu() -# ------------------------------------------------------------------------------ -# Refresh Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Refresh Functions + # -------------------------------------------------------------------------- def RefreshTitle(self): if self.NodeList is not None: - self.SetTitle("Networkedit - %s" % self.NodeList.NetworkName) + self.SetTitle(f"Networkedit - {self.NodeList.NetworkName}") else: self.SetTitle("Networkedit") @@ -357,7 +367,7 @@ def RefreshStatusBar(self): selected = self.NetworkNodes.GetSelection() if self.HelpBar and selected >= 0: window = self.NetworkNodes.GetPage(selected) - self.SetStatusBarText(window.GetSelection(), self.NodeList) + self.SetStatusBarText(window.GetSelection(), self.NodeList.Manager.current) def RefreshMainMenu(self): self.NetworkMenu.Enable(ID_NETWORKEDITNETWORKMENUBUILDMASTER, False) @@ -394,9 +404,9 @@ def RefreshMainMenu(self): self.MenuBar.EnableTop(1, False) self.MenuBar.EnableTop(2, False) -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): NetworkEditorTemplate.RefreshBufferState(self) @@ -410,7 +420,7 @@ def uimain(project): wx.InitAllImageHandlers() # Install a exception handle for bug reports - AddExceptHook(os.getcwd(), objdictgen.ODG_VERSION) + add_except_hook() frame = NetworkEdit(None, projectOpen=project) diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 57f6d1d..325d739 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -1,3 +1,4 @@ +"""Network Editor Template.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -17,30 +18,40 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +from typing import cast + import wx -from objdictgen.ui import commondialogs as cdia -from objdictgen.ui import nodeeditortemplate as net -from objdictgen.ui import subindextable as sit +from objdictgen.nodelist import NodeList +from objdictgen.ui.commondialogs import AddSlaveDialog +from objdictgen.ui.exception import display_exception_dialog +from objdictgen.ui.nodeeditortemplate import NodeEditorTemplate +from objdictgen.ui.subindextable import EditingPanel, EditingPanelNotebook [ ID_NETWORKEDITNETWORKNODES, -] = [wx.NewId() for _init_ctrls in range(1)] +] = [wx.NewId() for _ in range(1)] -class NetworkEditorTemplate(net.NodeEditorTemplate): +class NetworkEditorTemplate(NodeEditorTemplate): + """Network Editor Template.""" # pylint: disable=attribute-defined-outside-init - def _init_ctrls(self, prnt): - self.NetworkNodes = wx.Notebook(id=ID_NETWORKEDITNETWORKNODES, - name='NetworkNodes', parent=prnt, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.NB_LEFT) - self.NetworkNodes.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, - self.OnNodeSelectedChanged, id=ID_NETWORKEDITNETWORKNODES) - - def __init__(self, manager, frame, mode_solo): - self.NodeList = manager - net.NodeEditorTemplate.__init__(self, self.NodeList.Manager, frame, mode_solo) + def _init_ctrls(self, parent): + # FIXME: This cast is to define right type hints of attributes for this specific instance + self.NetworkNodes = cast(EditingPanelNotebook, wx.Notebook( + id=ID_NETWORKEDITNETWORKNODES, + name='NetworkNodes', parent=parent, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.NB_LEFT, + )) + self.NetworkNodes.Bind( + wx.EVT_NOTEBOOK_PAGE_CHANGED, + self.OnNodeSelectedChanged, id=ID_NETWORKEDITNETWORKNODES + ) + + def __init__(self, nodelist: NodeList, mode_solo: bool): + self.NodeList = nodelist + NodeEditorTemplate.__init__(self, self.NodeList.Manager, mode_solo) def GetCurrentNodeId(self): selected = self.NetworkNodes.GetSelection() @@ -60,11 +71,12 @@ def RefreshNetworkNodes(self): if self.NetworkNodes.GetPageCount() > 0: self.NetworkNodes.DeleteAllPages() if self.NodeList: - new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.Manager) - new_editingpanel.SetIndex(self.Manager.GetCurrentNodeID()) + new_editingpanel = EditingPanel(self.NetworkNodes, self, self.Manager) + new_editingpanel.SetIndex(self.Manager.current.ID) self.NetworkNodes.AddPage(new_editingpanel, "") for idx in self.NodeList.GetSlaveIDs(): - new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) + # FIXME: Why is NodeList used where NodeManager is expected? + new_editingpanel = EditingPanel(self.NetworkNodes, self, self.NodeList, False) new_editingpanel.SetIndex(idx) self.NetworkNodes.AddPage(new_editingpanel, "") @@ -75,68 +87,77 @@ def OnNodeSelectedChanged(self, event): if selected >= 0: window = self.NetworkNodes.GetPage(selected) self.NodeList.CurrentSelected = window.GetIndex() - wx.CallAfter(self.RefreshMainMenu) # FIXME: Missing symbol. From where? - wx.CallAfter(self.RefreshStatusBar) + raise NotImplementedError("Unimplemented symbol.") + # FIXME: Missing symbol in the original code. Delete? + # wx.CallAfter(self.RefreshMainMenu) + # wx.CallAfter(self.RefreshStatusBar) event.Skip() -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): if self.NodeList is not None: - nodeid = self.Manager.GetCurrentNodeID() + nodeid = self.Manager.current.ID if nodeid is not None: - nodename = "0x%2.2X %s" % (nodeid, self.Manager.GetCurrentNodeName()) + nodename = f"0x{nodeid:02X} {self.Manager.current.Name}" else: - nodename = self.Manager.GetCurrentNodeName() + nodename = self.Manager.current.Name self.NetworkNodes.SetPageText(0, nodename) for idx, name in enumerate(self.NodeList.GetSlaveNames()): self.NetworkNodes.SetPageText(idx + 1, name) -# ------------------------------------------------------------------------------ -# Slave Nodes Management -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Slave Nodes Management + # -------------------------------------------------------------------------- def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument - dialog = cdia.AddSlaveDialog(self.Frame) - dialog.SetNodeList(self.NodeList) - if dialog.ShowModal() == wx.ID_OK: + with AddSlaveDialog(self.Frame) as dialog: + dialog.SetNodeList(self.NodeList) + if dialog.ShowModal() != wx.ID_OK: + return values = dialog.GetValues() - try: - self.NodeList.AddSlaveNode(values["slaveName"], values["slaveNodeID"], values["edsFile"]) - new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) - new_editingpanel.SetIndex(values["slaveNodeID"]) - idx = self.NodeList.GetOrderNumber(values["slaveNodeID"]) - self.NetworkNodes.InsertPage(idx, new_editingpanel, "") - self.NodeList.CurrentSelected = idx - self.NetworkNodes.SetSelection(idx) - self.RefreshBufferState() - except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) - dialog.Destroy() + + try: + self.NodeList.AddSlaveNode( + values["slaveName"], values["slaveNodeID"], values["edsFile"], + ) + # FIXME: Why is NodeList used where NodeManager is expected? + new_editingpanel = EditingPanel(self.NetworkNodes, self, self.NodeList, False) + new_editingpanel.SetIndex(values["slaveNodeID"]) + idx = self.NodeList.GetOrderNumber(values["slaveNodeID"]) + self.NetworkNodes.InsertPage(idx, new_editingpanel, "") + self.NodeList.CurrentSelected = idx + self.NetworkNodes.SetSelection(idx) + self.RefreshBufferState() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self.Frame) def OnRemoveSlaveMenu(self, event): # pylint: disable=unused-argument slavenames = self.NodeList.GetSlaveNames() slaveids = self.NodeList.GetSlaveIDs() - dialog = wx.SingleChoiceDialog(self.Frame, "Choose a slave to remove", "Remove slave", slavenames) - if dialog.ShowModal() == wx.ID_OK: + with wx.SingleChoiceDialog( + self.Frame, "Choose a slave to remove", "Remove slave", slavenames, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return choice = dialog.GetSelection() - try: - self.NodeList.RemoveSlaveNode(slaveids[choice]) - slaveids.pop(choice) - current = self.NetworkNodes.GetSelection() - self.NetworkNodes.DeletePage(choice + 1) - if self.NetworkNodes.GetPageCount() > 0: - new_selection = min(current, self.NetworkNodes.GetPageCount() - 1) - self.NetworkNodes.SetSelection(new_selection) - if new_selection > 0: - self.NodeList.CurrentSelected = slaveids[new_selection - 1] - self.RefreshBufferState() - except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) - dialog.Destroy() - - def OpenMasterDCFDialog(self, node_id): + + try: + self.NodeList.RemoveSlaveNode(slaveids[choice]) + slaveids.pop(choice) + current = self.NetworkNodes.GetSelection() + self.NetworkNodes.DeletePage(choice + 1) + if self.NetworkNodes.GetPageCount() > 0: + new_selection = min(current, self.NetworkNodes.GetPageCount() - 1) + self.NetworkNodes.SetSelection(new_selection) + if new_selection > 0: + self.NodeList.CurrentSelected = slaveids[new_selection - 1] + self.RefreshBufferState() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self.Frame) + + def OpenMasterDCFDialog(self, node_id: int): self.NetworkNodes.SetSelection(0) self.NetworkNodes.GetPage(0).OpenDCFDialog(node_id) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 64c0912..d358df0 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -1,3 +1,4 @@ +"""Template for the NodeEditor class.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -20,27 +21,30 @@ import wx from objdictgen.maps import OD -from objdictgen.ui import commondialogs as cdia +from objdictgen.node import Node +from objdictgen.nodemanager import NodeManager +from objdictgen.ui import commondialogs as common +from objdictgen.ui.exception import (display_error_dialog, + display_exception_dialog) -class NodeEditorTemplate: +class NodeEditorTemplate(wx.Frame): + """Template for the NodeEditor class.""" - EDITMENU_ID = None + EDITMENU_ID: int|None = None - def __init__(self, manager, frame, mode_solo): - self.Manager = manager - self.Frame = frame - self.ModeSolo = mode_solo + # Hints for typing + HelpBar: wx.StatusBar + EditMenu: wx.Menu + AddMenu: wx.Menu - self.BusId = None + def __init__(self, manager: NodeManager, mode_solo: bool): + self.Manager: NodeManager = manager + self.Frame = self + self.ModeSolo = mode_solo + self.BusId = None # FIXME: Is this used? EditingPanel.OnSubindexGridCellLeftClick can seem to indicate it is iterable self.Closing = False - def GetBusId(self): - return self.BusId - - def IsClosing(self): - return self.Closing - def OnAddSDOServerMenu(self, event): # pylint: disable=unused-argument self.Manager.AddSDOServerToCurrent() self.RefreshBufferState() @@ -76,26 +80,26 @@ def RefreshCurrentIndexList(self): def RefreshStatusBar(self): pass - def SetStatusBarText(self, selection, manager): + def SetStatusBarText(self, selection, node: Node): if selection: index, subindex = selection - if manager.IsCurrentEntry(index): - self.Frame.HelpBar.SetStatusText("Index: 0x%04X" % index, 0) - self.Frame.HelpBar.SetStatusText("Subindex: 0x%02X" % subindex, 1) - entryinfos = manager.GetEntryInfos(index) + if node.IsEntry(index): + self.Frame.HelpBar.SetStatusText(f"Index: 0x{index:04X}", 0) + self.Frame.HelpBar.SetStatusText(f"Subindex: 0x{subindex:02X}", 1) + entryinfos = node.GetEntryInfos(index) name = entryinfos["name"] category = "Optional" - if entryinfos["need"]: + if entryinfos.get("need"): category = "Mandatory" struct = "VAR" number = "" if entryinfos["struct"] & OD.IdenticalIndexes: - number = " possibly defined %d times" % entryinfos["nbmax"] + number = f" possibly defined {entryinfos['nbmax']} times" if entryinfos["struct"] & OD.IdenticalSubindexes: struct = "ARRAY" elif entryinfos["struct"] & OD.MultipleSubindexes: struct = "RECORD" - text = "%s: %s entry of struct %s%s." % (name, category, struct, number) + text = f"{name}: {category} entry of struct {struct}{number}." self.Frame.HelpBar.SetStatusText(text, 2) else: for i in range(3): @@ -105,8 +109,9 @@ def SetStatusBarText(self, selection, manager): self.Frame.HelpBar.SetStatusText("", i) def RefreshProfileMenu(self): + node = self.Manager.current_default # Need a default to start UI if self.EDITMENU_ID is not None: - profile = self.Manager.GetCurrentProfileName() + profile = node.ProfileName edititem = self.Frame.EditMenu.FindItemById(self.EDITMENU_ID) if edititem: length = self.Frame.AddMenu.GetMenuItemCount() @@ -114,20 +119,22 @@ def RefreshProfileMenu(self): additem = self.Frame.AddMenu.FindItemByPosition(6) self.Frame.AddMenu.Delete(additem.GetId()) if profile not in ("None", "DS-301"): - edititem.SetItemLabel("%s Profile" % profile) + edititem.SetItemLabel(f"{profile} Profile") edititem.Enable(True) self.Frame.AddMenu.AppendSeparator() - for text, _ in self.Manager.GetCurrentSpecificMenu(): + for text, _ in node.SpecificMenu: new_id = wx.NewId() - self.Frame.AddMenu.Append(helpString='', id=new_id, kind=wx.ITEM_NORMAL, item=text) + self.Frame.AddMenu.Append( + helpString='', id=new_id,kind=wx.ITEM_NORMAL, item=text, + ) self.Frame.Bind(wx.EVT_MENU, self.GetProfileCallBack(text), id=new_id) else: edititem.SetItemLabel("Other Profile") edititem.Enable(False) -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): pass @@ -142,9 +149,9 @@ def OnRedoMenu(self, event): # pylint: disable=unused-argument self.RefreshCurrentIndexList() self.RefreshBufferState() -# ------------------------------------------------------------------------------ -# Editing Profiles functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Editing Profiles functions + # -------------------------------------------------------------------------- def OnCommunicationMenu(self, event): # pylint: disable=unused-argument dictionary, current = self.Manager.GetCurrentCommunicationLists() @@ -155,89 +162,80 @@ def OnOtherCommunicationMenu(self, event): # pylint: disable=unused-argument self.EditProfile("Edit DS-302 Profile", dictionary, current) def OnEditProfileMenu(self, event): # pylint: disable=unused-argument - title = "Edit %s Profile" % self.Manager.GetCurrentProfileName() + title = f"Edit {self.Manager.current.ProfileName} Profile" dictionary, current = self.Manager.GetCurrentProfileLists() self.EditProfile(title, dictionary, current) - def EditProfile(self, title, dictionary, current): - dialog = cdia.CommunicationDialog(self.Frame) - dialog.SetTitle(title) - dialog.SetIndexDictionary(dictionary) - dialog.SetCurrentList(current) - dialog.RefreshLists() - if dialog.ShowModal() == wx.ID_OK: - new_profile = dialog.GetCurrentList() - addinglist = [] - removinglist = [] - for index in new_profile: - if index not in current: - addinglist.append(index) - for index in current: - if index not in new_profile: - removinglist.append(index) - self.Manager.ManageEntriesOfCurrent(addinglist, removinglist) - self.Manager.BufferCurrentNode() - self.RefreshBufferState() - - dialog.Destroy() + def EditProfile(self, title: str, dictionary: dict[int, tuple[str, bool]], current: list[int]): + with common.CommunicationDialog(self.Frame) as dialog: + dialog.SetTitle(title) + dialog.SetIndexDictionary(dictionary) + dialog.SetCurrentList(current) + dialog.RefreshLists() + if dialog.ShowModal() == wx.ID_OK: + new_profile = dialog.GetCurrentList() + addinglist = [] + removinglist = [] + for index in new_profile: + if index not in current: + addinglist.append(index) + for index in current: + if index not in new_profile: + removinglist.append(index) + self.Manager.ManageEntriesOfCurrent(addinglist, removinglist) + self.Manager.BufferCurrentNode() + self.RefreshBufferState() def GetProfileCallBack(self, text): - def ProfileCallBack(event): # pylint: disable=unused-argument + def profile_cb(event): # pylint: disable=unused-argument self.Manager.AddSpecificEntryToCurrent(text) self.RefreshBufferState() self.RefreshCurrentIndexList() - return ProfileCallBack + return profile_cb -# ------------------------------------------------------------------------------ -# Edit Node informations function -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Edit Node informations function + # -------------------------------------------------------------------------- def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument - dialog = cdia.NodeInfosDialog(self.Frame) - name, id_, type_, description = self.Manager.GetCurrentNodeInfos() - defaultstringsize = self.Manager.GetCurrentNodeDefaultStringSize() - dialog.SetValues(name, id_, type_, description, defaultstringsize) + dialog = common.NodeInfosDialog(self.Frame) + name, nodeid, nodetype, description = self.Manager.GetCurrentNodeInfos() + defaultstringsize = self.Manager.current.DefaultStringSize + dialog.SetValues(name, nodeid, nodetype, description, defaultstringsize) if dialog.ShowModal() == wx.ID_OK: - name, id_, type_, description, defaultstringsize = dialog.GetValues() - self.Manager.SetCurrentNodeInfos(name, id_, type_, description) - self.Manager.SetCurrentNodeDefaultStringSize(defaultstringsize) + name, nodeid, nodetype, description, defaultstringsize = dialog.GetValues() + self.Manager.SetCurrentNodeInfos(name, nodeid, nodetype, description) + self.Manager.current.DefaultStringSize = defaultstringsize self.RefreshBufferState() self.RefreshCurrentIndexList() self.RefreshProfileMenu() -# ------------------------------------------------------------------------------ -# Add User Types and Variables -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Add User Types and Variables + # -------------------------------------------------------------------------- def AddMapVariable(self): index = self.Manager.GetCurrentNextMapIndex() if index: - dialog = cdia.MapVariableDialog(self.Frame) - dialog.SetIndex(index) + with common.MapVariableDialog(self.Frame) as dialog: + dialog.SetIndex(index) + if dialog.ShowModal() == wx.ID_OK: + try: + self.Manager.AddMapVariableToCurrent(*dialog.GetValues()) + self.RefreshBufferState() + self.RefreshCurrentIndexList() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self.Frame) + else: + display_error_dialog(self.Frame, "No map variable index left!") + + def AddUserType(self): + with common.UserTypeDialog(self) as dialog: + dialog.SetTypeList(self.Manager.current.GetCustomisableTypes()) if dialog.ShowModal() == wx.ID_OK: try: - self.Manager.AddMapVariableToCurrent(*dialog.GetValues()) + self.Manager.AddUserTypeToCurrent(*dialog.GetValues()) self.RefreshBufferState() self.RefreshCurrentIndexList() - except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) - dialog.Destroy() - else: - self.ShowErrorMessage("No map variable index left!") - - def AddUserType(self): - dialog = cdia.UserTypeDialog(self) - dialog.SetTypeList(self.Manager.GetCustomisableTypes()) - if dialog.ShowModal() == wx.ID_OK: - try: - self.Manager.AddUserTypeToCurrent(*dialog.GetValues()) - self.RefreshBufferState() - self.RefreshCurrentIndexList() - except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(str(exc)) - dialog.Destroy() - - def ShowErrorMessage(self, message): - message = wx.MessageDialog(self.Frame, message, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self.Frame) diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 1204847..c92a34a 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -1,3 +1,4 @@ +"""Objdictedit is a tool to edit and generate object dictionary files for CANopen devices.""" # # Copyright (C) 2022-2024 Svein Seldaleldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -21,46 +22,54 @@ import logging import os import sys +from typing import cast import wx import objdictgen -from objdictgen.ui import commondialogs as cdia -from objdictgen.ui import nodeeditortemplate as net -from objdictgen.ui import subindextable as sit -from objdictgen.ui.exception import AddExceptHook +from objdictgen.nodemanager import NodeManager +from objdictgen.typing import TPath +from objdictgen.ui.commondialogs import CreateNodeDialog +from objdictgen.ui.exception import (add_except_hook, display_error_dialog, + display_exception_dialog) +from objdictgen.ui.nodeeditortemplate import NodeEditorTemplate +from objdictgen.ui.subindextable import EditingPanel, EditingPanelNotebook log = logging.getLogger('objdictgen') def usage(): print("\nUsage of objdictedit :") - print("\n %s [Filepath, ...]\n" % sys.argv[0]) + print(f"\n {sys.argv[0]} [Filepath, ...]\n") [ ID_OBJDICTEDIT, ID_OBJDICTEDITFILEOPENED, ID_OBJDICTEDITHELPBAR, -] = [wx.NewId() for _init_ctrls in range(3)] +] = [wx.NewId() for _ in range(3)] +# FileMenu_Items [ ID_OBJDICTEDITFILEMENUIMPORTEDS, ID_OBJDICTEDITFILEMENUEXPORTEDS, ID_OBJDICTEDITFILEMENUEXPORTC, -] = [wx.NewId() for _init_coll_FileMenu_Items in range(3)] +] = [wx.NewId() for _ in range(3)] +# EditMenu_Items [ ID_OBJDICTEDITEDITMENUNODEINFOS, ID_OBJDICTEDITEDITMENUDS301PROFILE, ID_OBJDICTEDITEDITMENUDS302PROFILE, ID_OBJDICTEDITEDITMENUOTHERPROFILE, -] = [wx.NewId() for _init_coll_EditMenu_Items in range(4)] +] = [wx.NewId() for _ in range(4)] +# AddMenu_Items [ ID_OBJDICTEDITADDMENUSDOSERVER, ID_OBJDICTEDITADDMENUSDOCLIENT, ID_OBJDICTEDITADDMENUPDOTRANSMIT, ID_OBJDICTEDITADDMENUPDORECEIVE, ID_OBJDICTEDITADDMENUMAPVARIABLE, ID_OBJDICTEDITADDMENUUSERTYPE, -] = [wx.NewId() for _init_coll_AddMenu_Items in range(6)] +] = [wx.NewId() for _ in range(6)] -class ObjdictEdit(wx.Frame, net.NodeEditorTemplate): +class ObjdictEdit(NodeEditorTemplate): + """Main frame for the object dictionary editor.""" # pylint: disable=attribute-defined-outside-init EDITMENU_ID = ID_OBJDICTEDITEDITMENUOTHERPROFILE @@ -73,93 +82,93 @@ def _init_coll_MenuBar_Menus(self, parent): def _init_coll_FileMenu_Items(self, parent): parent.Append(helpString='', id=wx.ID_NEW, - kind=wx.ITEM_NORMAL, item='New\tCTRL+N') + kind=wx.ITEM_NORMAL, item='New\tCTRL+N') parent.Append(helpString='', id=wx.ID_OPEN, - kind=wx.ITEM_NORMAL, item='Open\tCTRL+O') + kind=wx.ITEM_NORMAL, item='Open\tCTRL+O') parent.Append(helpString='', id=wx.ID_CLOSE, - kind=wx.ITEM_NORMAL, item='Close\tCTRL+W') + kind=wx.ITEM_NORMAL, item='Close\tCTRL+W') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_SAVE, - kind=wx.ITEM_NORMAL, item='Save\tCTRL+S') + kind=wx.ITEM_NORMAL, item='Save\tCTRL+S') parent.Append(helpString='', id=wx.ID_SAVEAS, - kind=wx.ITEM_NORMAL, item='Save As...\tALT+S') + kind=wx.ITEM_NORMAL, item='Save As...\tALT+S') parent.AppendSeparator() parent.Append(helpString='', id=ID_OBJDICTEDITFILEMENUIMPORTEDS, - kind=wx.ITEM_NORMAL, item='Import EDS file') + kind=wx.ITEM_NORMAL, item='Import EDS file') parent.Append(helpString='', id=ID_OBJDICTEDITFILEMENUEXPORTEDS, - kind=wx.ITEM_NORMAL, item='Export to EDS file') + kind=wx.ITEM_NORMAL, item='Export to EDS file') parent.Append(helpString='', id=ID_OBJDICTEDITFILEMENUEXPORTC, - kind=wx.ITEM_NORMAL, item='Build Dictionary\tCTRL+B') + kind=wx.ITEM_NORMAL, item='Build Dictionary\tCTRL+B') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_EXIT, - kind=wx.ITEM_NORMAL, item='Exit') + kind=wx.ITEM_NORMAL, item='Exit') self.Bind(wx.EVT_MENU, self.OnNewMenu, id=wx.ID_NEW) self.Bind(wx.EVT_MENU, self.OnOpenMenu, id=wx.ID_OPEN) self.Bind(wx.EVT_MENU, self.OnCloseMenu, id=wx.ID_CLOSE) self.Bind(wx.EVT_MENU, self.OnSaveMenu, id=wx.ID_SAVE) self.Bind(wx.EVT_MENU, self.OnSaveAsMenu, id=wx.ID_SAVEAS) self.Bind(wx.EVT_MENU, self.OnImportEDSMenu, - id=ID_OBJDICTEDITFILEMENUIMPORTEDS) + id=ID_OBJDICTEDITFILEMENUIMPORTEDS) self.Bind(wx.EVT_MENU, self.OnExportEDSMenu, - id=ID_OBJDICTEDITFILEMENUEXPORTEDS) + id=ID_OBJDICTEDITFILEMENUEXPORTEDS) self.Bind(wx.EVT_MENU, self.OnExportCMenu, - id=ID_OBJDICTEDITFILEMENUEXPORTC) + id=ID_OBJDICTEDITFILEMENUEXPORTC) self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT) def _init_coll_EditMenu_Items(self, parent): parent.Append(helpString='', id=wx.ID_REFRESH, - kind=wx.ITEM_NORMAL, item='Refresh\tCTRL+R') + kind=wx.ITEM_NORMAL, item='Refresh\tCTRL+R') parent.AppendSeparator() parent.Append(helpString='', id=wx.ID_UNDO, - kind=wx.ITEM_NORMAL, item='Undo\tCTRL+Z') + kind=wx.ITEM_NORMAL, item='Undo\tCTRL+Z') parent.Append(helpString='', id=wx.ID_REDO, - kind=wx.ITEM_NORMAL, item='Redo\tCTRL+Y') + kind=wx.ITEM_NORMAL, item='Redo\tCTRL+Y') parent.AppendSeparator() parent.Append(helpString='', id=ID_OBJDICTEDITEDITMENUNODEINFOS, - kind=wx.ITEM_NORMAL, item='Node infos') + kind=wx.ITEM_NORMAL, item='Node infos') parent.Append(helpString='', id=ID_OBJDICTEDITEDITMENUDS301PROFILE, - kind=wx.ITEM_NORMAL, item='DS-301 Profile') + kind=wx.ITEM_NORMAL, item='DS-301 Profile') parent.Append(helpString='', id=ID_OBJDICTEDITEDITMENUDS302PROFILE, - kind=wx.ITEM_NORMAL, item='DS-302 Profile') + kind=wx.ITEM_NORMAL, item='DS-302 Profile') parent.Append(helpString='', id=ID_OBJDICTEDITEDITMENUOTHERPROFILE, - kind=wx.ITEM_NORMAL, item='Other Profile') + kind=wx.ITEM_NORMAL, item='Other Profile') self.Bind(wx.EVT_MENU, self.OnRefreshMenu, id=wx.ID_REFRESH) self.Bind(wx.EVT_MENU, self.OnUndoMenu, id=wx.ID_UNDO) self.Bind(wx.EVT_MENU, self.OnRedoMenu, id=wx.ID_REDO) self.Bind(wx.EVT_MENU, self.OnNodeInfosMenu, - id=ID_OBJDICTEDITEDITMENUNODEINFOS) + id=ID_OBJDICTEDITEDITMENUNODEINFOS) self.Bind(wx.EVT_MENU, self.OnCommunicationMenu, - id=ID_OBJDICTEDITEDITMENUDS301PROFILE) + id=ID_OBJDICTEDITEDITMENUDS301PROFILE) self.Bind(wx.EVT_MENU, self.OnOtherCommunicationMenu, - id=ID_OBJDICTEDITEDITMENUDS302PROFILE) + id=ID_OBJDICTEDITEDITMENUDS302PROFILE) self.Bind(wx.EVT_MENU, self.OnEditProfileMenu, - id=ID_OBJDICTEDITEDITMENUOTHERPROFILE) + id=ID_OBJDICTEDITEDITMENUOTHERPROFILE) def _init_coll_AddMenu_Items(self, parent): parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUSDOSERVER, - kind=wx.ITEM_NORMAL, item='SDO Server') + kind=wx.ITEM_NORMAL, item='SDO Server') parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUSDOCLIENT, - kind=wx.ITEM_NORMAL, item='SDO Client') + kind=wx.ITEM_NORMAL, item='SDO Client') parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUPDOTRANSMIT, - kind=wx.ITEM_NORMAL, item='PDO Transmit') + kind=wx.ITEM_NORMAL, item='PDO Transmit') parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUPDORECEIVE, - kind=wx.ITEM_NORMAL, item='PDO Receive') + kind=wx.ITEM_NORMAL, item='PDO Receive') parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUMAPVARIABLE, - kind=wx.ITEM_NORMAL, item='Map Variable') + kind=wx.ITEM_NORMAL, item='Map Variable') parent.Append(helpString='', id=ID_OBJDICTEDITADDMENUUSERTYPE, - kind=wx.ITEM_NORMAL, item='User Type') + kind=wx.ITEM_NORMAL, item='User Type') self.Bind(wx.EVT_MENU, self.OnAddSDOServerMenu, - id=ID_OBJDICTEDITADDMENUSDOSERVER) + id=ID_OBJDICTEDITADDMENUSDOSERVER) self.Bind(wx.EVT_MENU, self.OnAddSDOClientMenu, - id=ID_OBJDICTEDITADDMENUSDOCLIENT) + id=ID_OBJDICTEDITADDMENUSDOCLIENT) self.Bind(wx.EVT_MENU, self.OnAddPDOTransmitMenu, - id=ID_OBJDICTEDITADDMENUPDOTRANSMIT) + id=ID_OBJDICTEDITADDMENUPDOTRANSMIT) self.Bind(wx.EVT_MENU, self.OnAddPDOReceiveMenu, - id=ID_OBJDICTEDITADDMENUPDORECEIVE) + id=ID_OBJDICTEDITADDMENUPDORECEIVE) self.Bind(wx.EVT_MENU, self.OnAddMapVariableMenu, - id=ID_OBJDICTEDITADDMENUMAPVARIABLE) + id=ID_OBJDICTEDITADDMENUMAPVARIABLE) self.Bind(wx.EVT_MENU, self.OnAddUserTypeMenu, - id=ID_OBJDICTEDITADDMENUUSERTYPE) + id=ID_OBJDICTEDITADDMENUUSERTYPE) def _init_coll_HelpBar_Fields(self, parent): parent.SetFieldsCount(3) @@ -185,10 +194,12 @@ def _init_utils(self): self._init_coll_EditMenu_Items(self.EditMenu) self._init_coll_AddMenu_Items(self.AddMenu) - def _init_ctrls(self, prnt): - wx.Frame.__init__(self, id=ID_OBJDICTEDIT, name='objdictedit', - parent=prnt, pos=wx.Point(149, 178), size=wx.Size(1000, 700), - style=wx.DEFAULT_FRAME_STYLE, title='Objdictedit') + def _init_ctrls(self, parent): + wx.Frame.__init__( + self, id=ID_OBJDICTEDIT, name='objdictedit', + parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700), + style=wx.DEFAULT_FRAME_STYLE, title='Objdictedit', + ) self._init_utils() self.SetClientSize(wx.Size(1000, 700)) self.SetMenuBar(self.MenuBar) @@ -198,41 +209,51 @@ def _init_ctrls(self, prnt): accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL, 83, wx.ID_SAVE)]) self.SetAcceleratorTable(accel) - self.FileOpened = wx.Notebook(id=ID_OBJDICTEDITFILEOPENED, - name='FileOpened', parent=self, pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=0) - self.FileOpened.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, - self.OnFileSelectedChanged, id=ID_OBJDICTEDITFILEOPENED) - - self.HelpBar = wx.StatusBar(id=ID_OBJDICTEDITHELPBAR, name='HelpBar', - parent=self, style=wx.STB_SIZEGRIP) + # FIXME: This cast is to define right type hints of attributes for this specific instance + self.FileOpened = cast(EditingPanelNotebook, wx.Notebook( + id=ID_OBJDICTEDITFILEOPENED, + name='FileOpened', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=0, + )) + self.FileOpened.Bind( + wx.EVT_NOTEBOOK_PAGE_CHANGED, + self.OnFileSelectedChanged, id=ID_OBJDICTEDITFILEOPENED, + ) + + self.HelpBar = wx.StatusBar( + id=ID_OBJDICTEDITHELPBAR, name='HelpBar', + parent=self, style=wx.STB_SIZEGRIP, + ) self._init_coll_HelpBar_Fields(self.HelpBar) self.SetStatusBar(self.HelpBar) - def __init__(self, parent, manager=None, filesopen=None): + def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPath]|None = None): filesopen = filesopen or [] if manager is None: - net.NodeEditorTemplate.__init__(self, objdictgen.NodeManager(), self, True) + NodeEditorTemplate.__init__(self, NodeManager(), True) else: - net.NodeEditorTemplate.__init__(self, manager, self, False) + NodeEditorTemplate.__init__(self, manager, False) self._init_ctrls(parent) - icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "ui", "networkedit.ico"), wx.BITMAP_TYPE_ICO) + icon = wx.Icon( + str(objdictgen.SCRIPT_DIRECTORY / "img" / "networkedit.ico"), + wx.BITMAP_TYPE_ICO, + ) self.SetIcon(icon) if self.ModeSolo: for filepath in filesopen: try: index = self.Manager.OpenFileInCurrent(os.path.abspath(filepath)) - new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) + new_editingpanel = EditingPanel(self.FileOpened, self, self.Manager) new_editingpanel.SetIndex(index) self.FileOpened.AddPage(new_editingpanel, "") except Exception as exc: # Need this broad exception? - log.debug("Swallowed Exception: %s" % (exc, )) + log.warning("Swallowed Exception: %s", exc) raise # FIXME: Originial code swallows exception else: for index in self.Manager.GetBufferIndexes(): - new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) + new_editingpanel = EditingPanel(self.FileOpened, self, self.Manager) new_editingpanel.SetIndex(index) self.FileOpened.AddPage(new_editingpanel, "") @@ -242,10 +263,11 @@ def __init__(self, parent, manager=None, filesopen=None): self.Manager.ChangeCurrentNode(window.GetIndex()) self.FileOpened.SetSelection(0) - if self.Manager.CurrentDS302Defined(): + if self.Manager.current_default.DS302: self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) else: self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) + self.RefreshEditMenu() self.RefreshBufferState() self.RefreshProfileMenu() @@ -271,13 +293,14 @@ def OnQuitMenu(self, event): # pylint: disable=unused-argument def OnCloseFrame(self, event): self.Closing = True if not self.ModeSolo: - if getattr(self, "_onclose", None) is not None: - self._onclose() event.Skip() elif self.Manager.OneFileHasChanged(): - dialog = wx.MessageDialog(self, "There are changes, do you want to save?", "Close Application", wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION) - answer = dialog.ShowModal() - dialog.Destroy() + with wx.MessageDialog( + self, "There are changes, do you want to save?", "Close Application", + wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION, + ) as dialog: + answer = dialog.ShowModal() + if answer == wx.ID_YES: for _ in range(self.Manager.GetBufferNumber()): if self.Manager.CurrentIsSaved(): @@ -293,13 +316,13 @@ def OnCloseFrame(self, event): else: event.Skip() -# ------------------------------------------------------------------------------ -# Refresh Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Refresh Functions + # -------------------------------------------------------------------------- def RefreshTitle(self): if self.FileOpened.GetPageCount() > 0: - self.SetTitle("Objdictedit - %s" % self.Manager.GetCurrentFilename()) + self.SetTitle(f"Objdictedit - {self.Manager.GetCurrentFilename()}") else: self.SetTitle("Objdictedit") @@ -312,7 +335,7 @@ def RefreshStatusBar(self): selected = self.FileOpened.GetSelection() if selected >= 0: window = self.FileOpened.GetPage(selected) - self.SetStatusBarText(window.GetSelection(), self.Manager) + self.SetStatusBarText(window.GetSelection(), self.Manager.current) def RefreshMainMenu(self): if self.FileOpened.GetPageCount() > 0: @@ -349,9 +372,9 @@ def RefreshEditMenu(self): self.EditMenu.Enable(wx.ID_UNDO, False) self.EditMenu.Enable(wx.ID_REDO, False) -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): fileopened = self.Manager.GetAllFilenames() @@ -360,36 +383,37 @@ def RefreshBufferState(self): self.RefreshEditMenu() self.RefreshTitle() -# ------------------------------------------------------------------------------ -# Load and Save Funtions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Load and Save Funtions + # -------------------------------------------------------------------------- def OnNewMenu(self, event): # pylint: disable=unused-argument - # FIXME: Unused. Delete this? # self.FilePath = "" - dialog = cdia.CreateNodeDialog(self) - if dialog.ShowModal() == wx.ID_OK: - name, id_, nodetype, description = dialog.GetValues() + with CreateNodeDialog(self) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return + name, nodeid, nodetype, description = dialog.GetValues() profile, filepath = dialog.GetProfile() nmt = dialog.GetNMTManagement() options = dialog.GetOptions() - try: - index = self.Manager.CreateNewNode(name, id_, nodetype, description, profile, filepath, nmt, options) - new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) - new_editingpanel.SetIndex(index) - self.FileOpened.AddPage(new_editingpanel, "") - self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) - self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) - if "DS302" in options: - self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) - self.RefreshBufferState() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + + try: + index = self.Manager.CreateNewNode( + name=name, id=nodeid, type=nodetype, description=description, + profile=profile, filepath=filepath, nmt=nmt, options=options, + ) + new_editingpanel = EditingPanel(self.FileOpened, self, self.Manager) + new_editingpanel.SetIndex(index) + self.FileOpened.AddPage(new_editingpanel, "") + self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) + self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) + if "DS302" in options: + self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) + self.RefreshBufferState() + self.RefreshProfileMenu() + self.RefreshMainMenu() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnOpenMenu(self, event): # pylint: disable=unused-argument filepath = self.Manager.GetCurrentFilePath() @@ -397,36 +421,36 @@ def OnOpenMenu(self, event): # pylint: disable=unused-argument directory = os.path.dirname(filepath) else: directory = os.getcwd() - dialog = wx.FileDialog(self, "Choose a file", directory, "", "OD files (*.json;*.od;*.eds)|*.json;*.od;*.eds|All files|*.*", style=wx.FD_OPEN | wx.FD_CHANGE_DIR) - if dialog.ShowModal() == wx.ID_OK: + + with wx.FileDialog( + self, "Choose a file", directory, "", + "OD files (*.json;*.od;*.eds)|*.json;*.od;*.eds|All files|*.*", + style=wx.FD_OPEN | wx.FD_CHANGE_DIR, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return filepath = dialog.GetPath() - if os.path.isfile(filepath): - try: - index = self.Manager.OpenFileInCurrent(filepath) - new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) - new_editingpanel.SetIndex(index) - self.FileOpened.AddPage(new_editingpanel, "") - self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) - if self.Manager.CurrentDS302Defined(): - self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) - else: - self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) - self.RefreshEditMenu() - self.RefreshBufferState() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + + if os.path.isfile(filepath): + try: + index = self.Manager.OpenFileInCurrent(filepath) + new_editingpanel = EditingPanel(self.FileOpened, self, self.Manager) + new_editingpanel.SetIndex(index) + self.FileOpened.AddPage(new_editingpanel, "") + self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) + if self.Manager.current.DS302: + self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) + else: + self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) + self.RefreshEditMenu() + self.RefreshBufferState() + self.RefreshProfileMenu() + self.RefreshMainMenu() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnSaveMenu(self, event): # pylint: disable=unused-argument - if not self.ModeSolo and getattr(self, "_onsave", None) is not None: - self._onsave() - self.RefreshBufferState() - else: - self.Save() + self.Save() def OnSaveAsMenu(self, event): # pylint: disable=unused-argument self.SaveAs() @@ -438,54 +462,56 @@ def Save(self): self.SaveAs() else: self.RefreshBufferState() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def SaveAs(self): filepath = self.Manager.GetCurrentFilePath() if filepath: directory, filename = os.path.split(filepath) else: - directory, filename = os.getcwd(), "%s" % self.Manager.GetCurrentNodeInfos()[0] - - with wx.FileDialog(self, "Choose a file", directory, filename, wildcard="OD JSON file (*.json)|*.json;|OD file (*.od)|*.od;|EDS file (*.eds)|*.eds", - style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) as dialog: - if dialog.ShowModal() == wx.ID_CANCEL: + directory, filename = os.getcwd(), str(self.Manager.GetCurrentNodeInfos()[0]) + + with wx.FileDialog( + self, "Choose a file", directory, filename, + wildcard="OD JSON file (*.json)|*.json;|OD file (*.od)|*.od;|EDS file (*.eds)|*.eds", + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: return - + log.debug(filepath) filepath = dialog.GetPath() - if not os.path.isdir(os.path.dirname(filepath)): - message = wx.MessageDialog(self, "%s is not a valid folder!" % os.path.dirname(filepath), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - else: - try: - #Try and save the file and then update the filepath if successfull - if self.Manager.SaveCurrentInFile(filepath): - self.Manager.SetCurrentFilePath(filepath) - self.RefreshBufferState() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + if not os.path.isdir(os.path.dirname(filepath)): + display_error_dialog(self, f"'{os.path.dirname(filepath)}' is not a valid folder!") + else: + try: + # Try and save the file and then update the filepath if successfull + if self.Manager.SaveCurrentInFile(filepath): + self.Manager.SetCurrentFilePath(filepath) + self.RefreshBufferState() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnCloseMenu(self, event): answer = wx.ID_YES result = self.Manager.CloseCurrent() + if not result: - dialog = wx.MessageDialog(self, "There are changes, do you want to save?", "Close File", wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION) - answer = dialog.ShowModal() - dialog.Destroy() + with wx.MessageDialog( + self, "There are changes, do you want to save?", "Close File", + wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION, + ) as dialog: + answer = dialog.ShowModal() + if answer == wx.ID_YES: self.OnSaveMenu(event) if self.Manager.CurrentIsSaved(): self.Manager.CloseCurrent() elif answer == wx.ID_NO: self.Manager.CloseCurrent(True) + if self.FileOpened.GetPageCount() > self.Manager.GetBufferNumber(): current = self.FileOpened.GetSelection() self.FileOpened.DeletePage(current) @@ -499,78 +525,84 @@ def OnCloseMenu(self, event): # -------------------------------------------------------------------------- def OnImportEDSMenu(self, event): # pylint: disable=unused-argument - dialog = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "EDS files (*.eds)|*.eds|All files|*.*", style=wx.FD_OPEN | wx.FD_CHANGE_DIR) - if dialog.ShowModal() == wx.ID_OK: + with wx.FileDialog( + self, "Choose a file", os.getcwd(), "", "EDS files (*.eds)|*.eds|All files|*.*", + style=wx.FD_OPEN | wx.FD_CHANGE_DIR, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return filepath = dialog.GetPath() - if os.path.isfile(filepath): - try: - index = self.Manager.OpenFileInCurrent(filepath, load=False) - new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) - new_editingpanel.SetIndex(index) - self.FileOpened.AddPage(new_editingpanel, "") - self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) - self.RefreshBufferState() - self.RefreshCurrentIndexList() - self.RefreshProfileMenu() - self.RefreshMainMenu() - message = wx.MessageDialog(self, "Import successful", "Information", wx.OK | wx.ICON_INFORMATION) - message.ShowModal() - message.Destroy() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) + + if os.path.isfile(filepath): + try: + index = self.Manager.OpenFileInCurrent(filepath, load=False) + new_editingpanel = EditingPanel(self.FileOpened, self, self.Manager) + new_editingpanel.SetIndex(index) + self.FileOpened.AddPage(new_editingpanel, "") + self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) + self.RefreshBufferState() + self.RefreshCurrentIndexList() + self.RefreshProfileMenu() + self.RefreshMainMenu() + with wx.MessageDialog( + self, "Import successful", "Information", wx.OK | wx.ICON_INFORMATION, + ) as message: message.ShowModal() - message.Destroy() - else: - message = wx.MessageDialog(self, "'%s' is not a valid file!" % filepath, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) + else: + display_error_dialog(self, f"'{filepath}' is not a valid file!") def OnExportEDSMenu(self, event): # pylint: disable=unused-argument - dialog = wx.FileDialog(self, "Choose a file", os.getcwd(), self.Manager.GetCurrentNodeInfos()[0], "EDS files (*.eds)|*.eds|All files|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) - if dialog.ShowModal() == wx.ID_OK: + with wx.FileDialog( + self, "Choose a file", os.getcwd(), self.Manager.GetCurrentNodeInfos()[0], + "EDS files (*.eds)|*.eds|All files|*.*", + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return filepath = dialog.GetPath() - if not os.path.isdir(os.path.dirname(filepath)): - message = wx.MessageDialog(self, "'%s' is not a valid folder!" % os.path.dirname(filepath), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - else: - path, extend = os.path.splitext(filepath) - if extend in ("", "."): - filepath = path + ".eds" - try: - self.Manager.SaveCurrentInFile(filepath, filetype='eds') - message = wx.MessageDialog(self, "Export successful", "Information", wx.OK | wx.ICON_INFORMATION) - message.ShowModal() - message.Destroy() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) + + if not os.path.isdir(os.path.dirname(filepath)): + display_error_dialog(self, f"'{os.path.dirname(filepath)}' is not a valid folder!") + else: + path, extend = os.path.splitext(filepath) + if extend in ("", "."): + filepath = path + ".eds" + try: + self.Manager.SaveCurrentInFile(filepath, filetype='eds') + with wx.MessageDialog( + self, "Export successful", "Information", + wx.OK | wx.ICON_INFORMATION, + ) as message: message.ShowModal() - message.Destroy() - dialog.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def OnExportCMenu(self, event): # pylint: disable=unused-argument - dialog = wx.FileDialog(self, "Choose a file", os.getcwd(), self.Manager.GetCurrentNodeInfos()[0], "CANFestival C files (*.c)|*.c|All files|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) - if dialog.ShowModal() == wx.ID_OK: + with wx.FileDialog( + self, "Choose a file", os.getcwd(), self.Manager.GetCurrentNodeInfos()[0], + "CANFestival C files (*.c)|*.c|All files|*.*", + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR, + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return filepath = dialog.GetPath() - if not os.path.isdir(os.path.dirname(filepath)): - message = wx.MessageDialog(self, "'%s' is not a valid folder!" % os.path.dirname(filepath), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - else: - path, extend = os.path.splitext(filepath) - if extend in ("", "."): - filepath = path + ".c" - try: - self.Manager.SaveCurrentInFile(filepath, filetype='c') - message = wx.MessageDialog(self, "Export successful", "Information", wx.OK | wx.ICON_INFORMATION) - message.ShowModal() - message.Destroy() - except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) + + if not os.path.isdir(os.path.dirname(filepath)): + display_error_dialog(self, f"'{os.path.dirname(filepath)}' is not a valid folder!") + else: + path, extend = os.path.splitext(filepath) + if extend in ("", "."): + filepath = path + ".c" + try: + self.Manager.SaveCurrentInFile(filepath, filetype='c') + with wx.MessageDialog( + self, "Export successful", "Information", wx.OK | wx.ICON_INFORMATION, + ) as message: message.ShowModal() - message.Destroy() - dialog.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def uimain(args): @@ -579,7 +611,7 @@ def uimain(args): wx.InitAllImageHandlers() # Install a exception handle for bug reports - AddExceptHook(os.getcwd(), objdictgen.ODG_VERSION) + add_except_hook() frame = ObjdictEdit(None, filesopen=args) diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 17486b3..dfbbfdd 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -1,3 +1,4 @@ +"""Subindex table and editing panel for the Object Dictionary Editor.""" # # Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD @@ -25,10 +26,16 @@ from objdictgen import maps from objdictgen.maps import OD -from objdictgen.ui import commondialogs as cdia +from objdictgen.nodemanager import NodeManager +from objdictgen.ui import commondialogs as common +from objdictgen.ui.exception import display_error_dialog +from objdictgen.ui.nodeeditortemplate import NodeEditorTemplate COL_SIZES = [75, 250, 150, 125, 100, 60, 250, 60] -COL_ALIGNMENTS = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_CENTER, wx.ALIGN_RIGHT, wx.ALIGN_CENTER, wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT] +COL_ALIGNMENTS = [ + wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_CENTER, wx.ALIGN_RIGHT, + wx.ALIGN_CENTER, wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT +] RW = ["Read Only", "Write Only", "Read/Write"] RO = ["Read Only", "Read/Write"] @@ -51,7 +58,10 @@ PDO_TRANSMIT: ("PDO Transmit", 1, "AddPDOTransmitToCurrent"), MAP_VARIABLE: ("Map Variable", 0, "AddMapVariable") } -INDEXCHOICE_OPTIONS_DICT = {translation: option for option, (translation, object, function) in INDEXCHOICE_OPTIONS.items()} +INDEXCHOICE_OPTIONS_DICT = { + translation: option + for option, (translation, object, function) in INDEXCHOICE_OPTIONS.items() +} INDEXCHOICE_SECTIONS = { 0: [USER_TYPE], @@ -63,7 +73,9 @@ 8: [MAP_VARIABLE], } -SUBINDEX_TABLE_COLNAMES = ["subindex", "name", "type", "value", "access", "save", "comment", "buffer_size"] +SUBINDEX_TABLE_COLNAMES = [ + "subindex", "name", "type", "value", "access", "save", "comment", "buffer_size" +] IEC_TYPE_CONVERSION = { "BOOLEAN": "BOOL", @@ -90,15 +102,20 @@ "UNSIGNED56": "ULINT", "UNSIGNED64": "ULINT", } -SIZE_CONVERSION = {1: "X", 8: "B", 16: "W", 24: "D", 32: "D", 40: "L", 48: "L", 56: "L", 64: "L"} +SIZE_CONVERSION = { + 1: "X", 8: "B", 16: "W", 24: "D", 32: "D", 40: "L", 48: "L", 56: "L", 64: "L" +} class SubindexTable(wx.grid.GridTableBase): - """ A custom wxGrid Table using user supplied data """ - def __init__(self, parent, data, editors, colnames): + + # Typing definitions + CurrentIndex: int + + def __init__(self, parent: "EditingPanel", data, editors, colnames): # The base class must be initialized *first* wx.grid.GridTableBase.__init__(self) self.data = data @@ -130,12 +147,12 @@ def GetColLabelValue(self, col, translate=True): # pylint: disable=unused-argum return self.colnames[col] return None - def GetValue(self, row, col, translate=True): # pylint: disable=unused-argument + def GetValue(self, row, col, translate=True) -> str: # pylint: disable=unused-argument if row < self.GetNumberRows(): colname = self.GetColLabelValue(col, False) value = str(self.data[row].get(colname, "")) return value - return None + return "" def GetEditor(self, row, col): if row < self.GetNumberRows(): @@ -173,8 +190,13 @@ def ResetView(self, grid): """ grid.BeginBatch() for current, new, delmsg, addmsg in [ - (self._rows, self.GetNumberRows(), wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED), - (self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED), + ( + self._rows, self.GetNumberRows(), + wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, + ),( + self._cols, self.GetNumberCols(), + wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, + ), ]: if new < current: msg = wx.grid.GridTableMessage(self, delmsg, new, current - new) @@ -194,13 +216,14 @@ def ResetView(self, grid): grid.AdjustScrollbars() grid.ForceRefresh() - def UpdateValues(self, grid): + def UpdateValues(self, grid: wx.grid.Grid): """Update all displayed values""" # This sends an event to the grid table to update all of the values + # FIXME: This symbols is not defined in wx no more. Investigate. msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) - def _updateColAttrs(self, grid): + def _updateColAttrs(self, grid: wx.grid.Grid): """ wx.grid.Grid -> update the column attributes to add the appropriate renderer given the column name. @@ -219,13 +242,13 @@ def _updateColAttrs(self, grid): maplist = None for row in range(self.GetNumberRows()): editors = self.editors[row] - if wx.Platform == '__WXMSW__': + if wx.Platform == '__WXMSW__': # FIXME: Missing from wxtyping? grid.SetRowMinimalHeight(row, 20) else: grid.SetRowMinimalHeight(row, 28) grid.AutoSizeRow(row, False) for col in range(self.GetNumberCols()): - editor = None + editor: wx.grid.GridCellTextEditor|wx.grid.GridCellChoiceEditor|None = None renderer = None colname = self.GetColLabelValue(col, False) @@ -244,7 +267,7 @@ def _updateColAttrs(self, grid): editor = wx.grid.GridCellNumberEditor() renderer = wx.grid.GridCellNumberRenderer() if colname == "value" and "min" in editors and "max" in editors: - editor.SetParameters("%s,%s" % (editors["min"], editors["max"])) + editor.SetParameters(f"{editors['min']},{editors['max']}") elif editortype == "float": editor = wx.grid.GridCellTextEditor() renderer = wx.grid.GridCellStringRenderer() @@ -258,11 +281,11 @@ def _updateColAttrs(self, grid): editor = wx.grid.GridCellChoiceEditor(OPTION_LIST) elif editortype == "type": if typelist is None: - typelist = self.Parent.Manager.GetCurrentTypeList() + typelist = self.Parent.Manager.current.GetTypeList() editor = wx.grid.GridCellChoiceEditor(typelist) elif editortype == "map": if maplist is None: - maplist = self.Parent.Manager.GetCurrentMapList() + maplist = self.Parent.Manager.current.GetMapList() editor = wx.grid.GridCellChoiceEditor(maplist) elif editortype == "time": editor = wx.grid.GridCellTextEditor() @@ -286,7 +309,7 @@ def SetEditors(self, editors): def GetCurrentIndex(self): return self.CurrentIndex - def SetCurrentIndex(self, index): + def SetCurrentIndex(self, index: int): self.CurrentIndex = index def Empty(self): @@ -299,22 +322,28 @@ def Empty(self): ID_EDITINGPANELINDEXLIST, ID_EDITINGPANELINDEXLISTPANEL, ID_EDITINGPANELPARTLIST, ID_EDITINGPANELSECONDSPLITTER, ID_EDITINGPANELSUBINDEXGRID, ID_EDITINGPANELSUBINDEXGRIDPANEL, ID_EDITINGPANELCALLBACKCHECK, -] = [wx.NewId() for _init_ctrls in range(10)] +] = [wx.NewId() for _ in range(10)] +# IndexListMenu_Items [ ID_EDITINGPANELINDEXLISTMENUITEMS0, ID_EDITINGPANELINDEXLISTMENUITEMS1, ID_EDITINGPANELINDEXLISTMENUITEMS2, -] = [wx.NewId() for _init_coll_IndexListMenu_Items in range(3)] +] = [wx.NewId() for _ in range(3)] +# SubindexGridMenu_Items [ ID_EDITINGPANELMENU1ITEMS0, ID_EDITINGPANELMENU1ITEMS1, ID_EDITINGPANELMENU1ITEMS3, ID_EDITINGPANELMENU1ITEMS4, -] = [wx.NewId() for _init_coll_SubindexGridMenu_Items in range(4)] +] = [wx.NewId() for _ in range(4)] class EditingPanel(wx.SplitterWindow): + """UI for the Object Dictionary Editor.""" # pylint: disable=attribute-defined-outside-init + # Typing definitions + Manager: NodeManager + def _init_coll_AddToListSizer_Items(self, parent): parent.Add(self.AddButton, 0, border=0, flag=0) parent.Add(self.IndexChoice, 0, border=0, flag=wx.GROW) @@ -340,38 +369,38 @@ def _init_coll_IndexListSizer_Growables(self, parent): def _init_coll_SubindexGridMenu_Items(self, parent): parent.Append(helpString='', id=ID_EDITINGPANELMENU1ITEMS0, - kind=wx.ITEM_NORMAL, item='Add subindexes') + kind=wx.ITEM_NORMAL, item='Add subindexes') parent.Append(helpString='', id=ID_EDITINGPANELMENU1ITEMS1, - kind=wx.ITEM_NORMAL, item='Delete subindexes') + kind=wx.ITEM_NORMAL, item='Delete subindexes') parent.AppendSeparator() parent.Append(helpString='', id=ID_EDITINGPANELMENU1ITEMS3, - kind=wx.ITEM_NORMAL, item='Default value') + kind=wx.ITEM_NORMAL, item='Default value') if not self.Editable: parent.Append(helpString='', id=ID_EDITINGPANELMENU1ITEMS4, - kind=wx.ITEM_NORMAL, item='Add to DCF') + kind=wx.ITEM_NORMAL, item='Add to DCF') self.Bind(wx.EVT_MENU, self.OnAddSubindexMenu, - id=ID_EDITINGPANELMENU1ITEMS0) + id=ID_EDITINGPANELMENU1ITEMS0) self.Bind(wx.EVT_MENU, self.OnDeleteSubindexMenu, - id=ID_EDITINGPANELMENU1ITEMS1) + id=ID_EDITINGPANELMENU1ITEMS1) self.Bind(wx.EVT_MENU, self.OnDefaultValueSubindexMenu, - id=ID_EDITINGPANELMENU1ITEMS3) + id=ID_EDITINGPANELMENU1ITEMS3) if not self.Editable: self.Bind(wx.EVT_MENU, self.OnAddToDCFSubindexMenu, - id=ID_EDITINGPANELMENU1ITEMS4) + id=ID_EDITINGPANELMENU1ITEMS4) def _init_coll_IndexListMenu_Items(self, parent): parent.Append(helpString='', id=ID_EDITINGPANELINDEXLISTMENUITEMS0, - kind=wx.ITEM_NORMAL, item='Rename') + kind=wx.ITEM_NORMAL, item='Rename') parent.Append(helpString='', id=ID_EDITINGPANELINDEXLISTMENUITEMS2, - kind=wx.ITEM_NORMAL, item='Modify') + kind=wx.ITEM_NORMAL, item='Modify') parent.Append(helpString='', id=ID_EDITINGPANELINDEXLISTMENUITEMS1, - kind=wx.ITEM_NORMAL, item='Delete') + kind=wx.ITEM_NORMAL, item='Delete') self.Bind(wx.EVT_MENU, self.OnRenameIndexMenu, - id=ID_EDITINGPANELINDEXLISTMENUITEMS0) + id=ID_EDITINGPANELINDEXLISTMENUITEMS0) self.Bind(wx.EVT_MENU, self.OnDeleteIndexMenu, - id=ID_EDITINGPANELINDEXLISTMENUITEMS1) + id=ID_EDITINGPANELINDEXLISTMENUITEMS1) self.Bind(wx.EVT_MENU, self.OnModifyIndexMenu, - id=ID_EDITINGPANELINDEXLISTMENUITEMS2) + id=ID_EDITINGPANELINDEXLISTMENUITEMS2) def _init_utils(self): self.IndexListMenu = wx.Menu(title='') @@ -395,90 +424,90 @@ def _init_sizers(self): self.SubindexGridPanel.SetSizer(self.SubindexGridSizer) self.IndexListPanel.SetSizer(self.IndexListSizer) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.SplitterWindow.__init__(self, id=ID_EDITINGPANEL, - name='MainSplitter', parent=prnt, pos=wx.Point(0, 0), - size=wx.Size(-1, -1), style=wx.SP_3D) + name='MainSplitter', parent=parent, pos=wx.Point(0, 0), + size=wx.Size(-1, -1), style=wx.SP_3D) self._init_utils() self.PartList = wx.ListBox(choices=[], id=ID_EDITINGPANELPARTLIST, - name='PartList', parent=self, pos=wx.Point(0, 0), - size=wx.Size(-1, -1), style=0) + name='PartList', parent=self, pos=wx.Point(0, 0), + size=wx.Size(-1, 180), style=0) self.PartList.Bind(wx.EVT_LISTBOX, self.OnPartListBoxClick, - id=ID_EDITINGPANELPARTLIST) + id=ID_EDITINGPANELPARTLIST) self.SecondSplitter = wx.SplitterWindow(id=ID_EDITINGPANELSECONDSPLITTER, - name='SecondSplitter', parent=self, pos=wx.Point(0, 0), - size=wx.Size(-1, -1), style=wx.SP_3D) + name='SecondSplitter', parent=self, pos=wx.Point(0, 0), + size=wx.Size(-1, -1), style=wx.SP_3D) self.SplitHorizontally(self.PartList, self.SecondSplitter, 110) self.SetMinimumPaneSize(1) self.SubindexGridPanel = wx.Panel(id=ID_EDITINGPANELSUBINDEXGRIDPANEL, - name='SubindexGridPanel', parent=self.SecondSplitter, - pos=wx.Point(0, 0), size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL) + name='SubindexGridPanel', parent=self.SecondSplitter, + pos=wx.Point(0, 0), size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL) self.IndexListPanel = wx.Panel(id=ID_EDITINGPANELINDEXLISTPANEL, - name='IndexListPanel', parent=self.SecondSplitter, - pos=wx.Point(0, 0), size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL) + name='IndexListPanel', parent=self.SecondSplitter, + pos=wx.Point(0, 0), size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL) self.SecondSplitter.SplitVertically(self.IndexListPanel, self.SubindexGridPanel, 280) self.SecondSplitter.SetMinimumPaneSize(1) self.SubindexGrid = wx.grid.Grid(id=ID_EDITINGPANELSUBINDEXGRID, - name='SubindexGrid', parent=self.SubindexGridPanel, pos=wx.Point(0, - 0), size=wx.Size(-1, -1), style=0) - self.SubindexGrid.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, - 'Sans')) - self.SubindexGrid.SetLabelFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, - False, 'Sans')) + name='SubindexGrid', parent=self.SubindexGridPanel, pos=wx.Point(0, + 0), size=wx.Size(-1, -1), style=0) + self.SubindexGrid.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, + wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Sans')) + self.SubindexGrid.SetLabelFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, + wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Sans')) self.SubindexGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, - self.OnSubindexGridCellChange) + self.OnSubindexGridCellChange) self.SubindexGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, - self.OnSubindexGridRightClick) + self.OnSubindexGridRightClick) self.SubindexGrid.Bind(wx.grid.EVT_GRID_SELECT_CELL, - self.OnSubindexGridSelectCell) + self.OnSubindexGridSelectCell) self.SubindexGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, - self.OnSubindexGridCellLeftClick) + self.OnSubindexGridCellLeftClick) self.SubindexGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, - self.OnSubindexGridEditorShown) + self.OnSubindexGridEditorShown) self.CallbackCheck = wx.CheckBox(id=ID_EDITINGPANELCALLBACKCHECK, - label='Have Callbacks', name='CallbackCheck', - parent=self.SubindexGridPanel, pos=wx.Point(0, 0), size=wx.Size(152, - 24), style=0) + label='Have Callbacks', name='CallbackCheck', + parent=self.SubindexGridPanel, pos=wx.Point(0, 0), size=wx.Size(152, + 24), style=0) self.CallbackCheck.Bind(wx.EVT_CHECKBOX, self.OnCallbackCheck, - id=ID_EDITINGPANELCALLBACKCHECK) + id=ID_EDITINGPANELCALLBACKCHECK) self.IndexList = wx.ListBox(choices=[], id=ID_EDITINGPANELINDEXLIST, - name='IndexList', parent=self.IndexListPanel, pos=wx.Point(0, 0), - size=wx.Size(-1, -1), style=0) + name='IndexList', parent=self.IndexListPanel, pos=wx.Point(0, 0), + size=wx.Size(-1, -1), style=0) self.IndexList.Bind(wx.EVT_LISTBOX, self.OnIndexListClick, - id=ID_EDITINGPANELINDEXLIST) + id=ID_EDITINGPANELINDEXLIST) self.IndexList.Bind(wx.EVT_RIGHT_UP, self.OnIndexListRightUp) self.AddButton = wx.Button(id=ID_EDITINGPANELADDBUTTON, label='Add', - name='AddButton', parent=self.IndexListPanel, pos=wx.Point(0, 0), - size=wx.DefaultSize, style=0) + name='AddButton', parent=self.IndexListPanel, pos=wx.Point(0, 0), + size=wx.DefaultSize, style=0) self.AddButton.Bind(wx.EVT_BUTTON, self.OnAddButtonClick, - id=ID_EDITINGPANELADDBUTTON) + id=ID_EDITINGPANELADDBUTTON) self.IndexChoice = wx.ComboBox(choices=[], id=ID_EDITINGPANELINDEXCHOICE, - name='IndexChoice', parent=self.IndexListPanel, pos=wx.Point(50, - 0), size=wx.Size(-1, 30), style=wx.CB_READONLY) + name='IndexChoice', parent=self.IndexListPanel, pos=wx.Point(50, + 0), size=wx.Size(-1, 30), style=wx.CB_READONLY) self._init_sizers() - def __init__(self, parent, window, manager, editable=True): + def __init__(self, parent, window: NodeEditorTemplate, manager: NodeManager, editable=True): self.Editable = editable self._init_ctrls(parent) self.ParentWindow = window self.Manager = manager - self.ListIndex = [] - self.ChoiceIndex = [] + self.ListIndex: list[int] = [] + self.ChoiceIndex: list[int] = [] self.FirstCall = False - self.Index = None + self.Index: int = 0 for values in maps.INDEX_RANGES: - text = " 0x%04X-0x%04X %s" % (values["min"], values["max"], values["description"]) + text = f" 0x{values.min:04X}-0x{values.max:04X} {values.description}" self.PartList.Append(text) self.Table = SubindexTable(self, [], [], SUBINDEX_TABLE_COLNAMES) self.SubindexGrid.SetTable(self.Table) @@ -517,42 +546,49 @@ def OnSubindexGridCellLeftClick(self, event): if selected != wx.NOT_FOUND: index = self.ListIndex[selected] subindex = event.GetRow() - entry_infos = self.Manager.GetEntryInfos(index) + entry_infos = self.Manager.current.GetEntryInfos(index) if not entry_infos["struct"] & OD.MultipleSubindexes or subindex != 0: - subentry_infos = self.Manager.GetSubentryInfos(index, subindex) - typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) + subentry_infos = self.Manager.current.GetSubentryInfos(index, subindex) + typeinfos = self.Manager.current.GetEntryInfos(subentry_infos["type"]) if typeinfos: - bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) - var_name = "%s_%04x_%02x" % (self.Manager.GetCurrentNodeName(), index, subindex) + # FIXME: What is bus_id? It is never set anywhere + bus_id = ".".join(str(k) for k in self.ParentWindow.BusId) + var_name = f"{self.Manager.current.Name}_{index:04x}_{subindex:02x}" size = typeinfos["size"] - data = wx.TextDataObject(str( - ("%s%s.%d.%d" % (SIZE_CONVERSION[size], bus_id, index, subindex), - "location", - IEC_TYPE_CONVERSION.get(typeinfos["name"]), - var_name, ""))) + data = wx.TextDataObject(str(( + f"{SIZE_CONVERSION[size]}{bus_id}.{index}.{subindex}", + "location", + IEC_TYPE_CONVERSION.get(typeinfos["name"]), + var_name, "")) + ) dragsource = wx.DropSource(self.SubindexGrid) dragsource.SetData(data) dragsource.DoDragDrop() return elif col == 0: selected = self.IndexList.GetSelection() + # FIXME: When used in node editor context, this method doesn't exist. + # It exists in NetworkEditorTemplate. What's ths use here? node_id = self.ParentWindow.GetCurrentNodeId() if selected != wx.NOT_FOUND and node_id is not None: index = self.ListIndex[selected] subindex = event.GetRow() - entry_infos = self.Manager.GetEntryInfos(index) + entry_infos = self.Manager.current.GetEntryInfos(index) if not entry_infos["struct"] & OD.MultipleSubindexes or subindex != 0: - subentry_infos = self.Manager.GetSubentryInfos(index, subindex) - typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) + subentry_infos = self.Manager.current.GetSubentryInfos(index, subindex) + typeinfos = self.Manager.current.GetEntryInfos(subentry_infos["type"]) if subentry_infos["pdo"] and typeinfos: - bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) - var_name = "%s_%04x_%02x" % (self.Manager.GetSlaveName(node_id), index, subindex) + # FIXME: What is bus_id? It is never set anywhere + bus_id = ".".join(str(k) for k in self.ParentWindow.BusId) + # FIXME: Exists in NodeList, not in NodeManager + var_name = f"{self.Manager.GetSlaveName(node_id)}_{index:04x}_{subindex:02x}" size = typeinfos["size"] - data = wx.TextDataObject(str( - ("%s%s.%d.%d.%d" % (SIZE_CONVERSION[size], bus_id, node_id, index, subindex), - "location", - IEC_TYPE_CONVERSION.get(typeinfos["name"]), - var_name, ""))) + data = wx.TextDataObject(str(( + f"{SIZE_CONVERSION[size]}{bus_id}.{node_id}.{index}.{subindex}", + "location", + IEC_TYPE_CONVERSION.get(typeinfos["name"]), + var_name, "")) + ) dragsource = wx.DropSource(self.SubindexGrid) dragsource.SetData(data) dragsource.DoDragDrop() @@ -570,7 +606,7 @@ def OnAddButtonClick(self, event): getattr(self.ParentWindow, INDEXCHOICE_OPTIONS[choice][2])() elif INDEXCHOICE_OPTIONS[choice][1] == 1: getattr(self.Manager, INDEXCHOICE_OPTIONS[choice][2])() - elif selected in [menu for menu, indexes in self.Manager.GetCurrentSpecificMenu()]: + elif selected in [menu for menu, indexes in self.Manager.current.SpecificMenu]: self.Manager.AddSpecificEntryToCurrent(selected) else: index = self.ChoiceIndex[self.IndexChoice.GetSelection()] @@ -580,25 +616,25 @@ def OnAddButtonClick(self, event): event.Skip() def OnPartListBoxClick(self, event): - if not self.ParentWindow.IsClosing(): + if not self.ParentWindow.Closing: self.SubindexGrid.SetGridCursor(0, 0) self.RefreshIndexList() event.Skip() def OnIndexListClick(self, event): - if not self.ParentWindow.IsClosing(): + if not self.ParentWindow.Closing: self.SubindexGrid.SetGridCursor(0, 0) self.RefreshTable() event.Skip() def OnSubindexGridSelectCell(self, event): - if not self.ParentWindow.IsClosing(): + if not self.ParentWindow.Closing: wx.CallAfter(self.ParentWindow.RefreshStatusBar) event.Skip() -# ------------------------------------------------------------------------------ -# Refresh Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Refresh Functions + # -------------------------------------------------------------------------- def RefreshIndexList(self): selected = self.IndexList.GetSelection() @@ -612,8 +648,8 @@ def RefreshIndexList(self): if i < len(maps.INDEX_RANGES): values = maps.INDEX_RANGES[i] self.ListIndex = [] - for name, index in self.Manager.GetCurrentValidIndexes(values["min"], values["max"]): - self.IndexList.Append("0x%04X %s" % (index, name)) + for name, index in self.Manager.GetCurrentValidIndexes(values.min, values.max): + self.IndexList.Append(f"0x{index:04X} {name}") self.ListIndex.append(index) if self.Editable: self.ChoiceIndex = [] @@ -627,18 +663,24 @@ def RefreshIndexList(self): else: self.IndexChoice.SetSelection(0) else: - for name, index in self.Manager.GetCurrentValidChoices(values["min"], values["max"]): - if index: - self.IndexChoice.Append("0x%04X %s" % (index, name)) + for name, cindex in self.Manager.GetCurrentValidChoices(values.min, values.max): + if cindex: + self.IndexChoice.Append(f"0x{cindex:04X} {name}") else: self.IndexChoice.Append(name) - self.ChoiceIndex.append(index) - if choiceindex != wx.NOT_FOUND and choiceindex < self.IndexChoice.GetCount() and choice == self.IndexChoice.GetString(choiceindex): + self.ChoiceIndex.append(cindex or 0) + if (choiceindex != wx.NOT_FOUND + and choiceindex < self.IndexChoice.GetCount() + and choice == self.IndexChoice.GetString(choiceindex) + ): self.IndexChoice.SetStringSelection(choice) if self.Editable: self.IndexChoice.Enable(self.IndexChoice.GetCount() != 0) self.AddButton.Enable(self.IndexChoice.GetCount() != 0) - if selected == wx.NOT_FOUND or selected >= len(self.ListIndex) or selectedindex != self.ListIndex[selected]: + if (selected == wx.NOT_FOUND + or selected >= len(self.ListIndex) + or selectedindex != self.ListIndex[selected] + ): self.Table.Empty() self.CallbackCheck.SetValue(False) self.CallbackCheck.Disable() @@ -654,8 +696,8 @@ def RefreshTable(self): index = self.ListIndex[selected] if index > 0x260 and self.Editable: self.CallbackCheck.Enable() - self.CallbackCheck.SetValue(self.Manager.HasCurrentEntryCallbacks(index)) - result = self.Manager.GetCurrentEntryValues(index) + self.CallbackCheck.SetValue(self.Manager.current.HasEntryCallbacks(index)) + result = self.Manager.GetNodeEntryValues(self.Manager.current, index) if result is not None: self.Table.SetCurrentIndex(index) data, editors = result @@ -664,9 +706,9 @@ def RefreshTable(self): self.Table.ResetView(self.SubindexGrid) self.ParentWindow.RefreshStatusBar() -# ------------------------------------------------------------------------------ -# Editing Table value function -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Editing Table value function + # -------------------------------------------------------------------------- def OnSubindexGridEditorShown(self, event): row, col = event.GetRow(), event.GetCol() @@ -677,16 +719,20 @@ def OnSubindexGridEditorShown(self, event): event.Skip() def ShowDCFEntryDialog(self, row, col): + # FIXME: Exists in NetworkEditorTemplate, not in NodeEditorTemplate if self.Editable or self.ParentWindow.GetCurrentNodeId() is None: selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): - dialog = cdia.DCFEntryValuesDialog(self, self.Editable) + if self.Manager.current.IsEntry(index): + dialog = common.DCFEntryValuesDialog(self, self.Editable) dialog.SetValues(codecs.decode(self.Table.GetValue(row, col), "hex_codec")) if dialog.ShowModal() == wx.ID_OK and self.Editable: value = dialog.GetValues() - self.Manager.SetCurrentEntry(index, row, value, "value", "dcf") + try: + self.Manager.SetCurrentEntry(index, row, value, "value", "dcf") + except Exception as e: # pylint: disable=broad-except + display_error_dialog(self, f"Failed to set value: {e}", "Failed to set value") self.ParentWindow.RefreshBufferState() wx.CallAfter(self.RefreshTable) @@ -698,7 +744,10 @@ def OnSubindexGridCellChange(self, event): name = self.Table.GetColLabelValue(col, False) value = self.Table.GetValue(subindex, col, False) editor = self.Table.GetEditor(subindex, col) - self.Manager.SetCurrentEntry(index, subindex, value, name, editor) + try: + self.Manager.SetCurrentEntry(index, subindex, value, name, editor) + except Exception as e: # pylint: disable=broad-except + display_error_dialog(self, f"Failed to set value: {e}", "Failed to set value") self.ParentWindow.RefreshBufferState() wx.CallAfter(self.RefreshTable) event.Skip() @@ -711,9 +760,9 @@ def OnCallbackCheck(self, event): wx.CallAfter(self.RefreshTable) event.Skip() -# ------------------------------------------------------------------------------ -# Contextual Menu functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Contextual Menu functions + # -------------------------------------------------------------------------- def OnIndexListRightUp(self, event): if self.Editable: @@ -748,10 +797,14 @@ def OnSubindexGridRightClick(self, event): selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): + if self.Manager.current.IsEntry(index): showpopup = False - infos = self.Manager.GetEntryInfos(index) - if 0x2000 <= index <= 0x5FFF and infos["struct"] & OD.MultipleSubindexes or infos["struct"] & OD.IdenticalSubindexes: + infos = self.Manager.current.GetEntryInfos(index) + # FIXME: And and or combined in the same condition + if (0x2000 <= index <= 0x5FFF + and infos["struct"] & OD.MultipleSubindexes + or infos["struct"] & OD.IdenticalSubindexes + ): showpopup = True self.SubindexGridMenu.FindItemByPosition(0).Enable(True) self.SubindexGridMenu.FindItemByPosition(1).Enable(True) @@ -765,12 +818,15 @@ def OnSubindexGridRightClick(self, event): self.SubindexGridMenu.FindItemByPosition(3).Enable(False) if showpopup: self.PopupMenu(self.SubindexGridMenu) - elif self.Table.GetColLabelValue(event.GetCol(), False) == "value" and self.ParentWindow.GetCurrentNodeId() is not None: + # FIXME: Exists in NetworkEditorTemplate, not in NodeEditorTemplate + elif (self.Table.GetColLabelValue(event.GetCol(), False) == "value" + and self.ParentWindow.GetCurrentNodeId() is not None + ): selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): - infos = self.Manager.GetEntryInfos(index) + if self.Manager.current.IsEntry(index): + infos = self.Manager.current.GetEntryInfos(index) if not infos["struct"] & OD.MultipleSubindexes or event.GetRow() > 0: self.SubindexGridMenu.FindItemByPosition(0).Enable(False) self.SubindexGridMenu.FindItemByPosition(1).Enable(False) @@ -785,11 +841,12 @@ def OnAddToDCFSubindexMenu(self, event): # pylint: disable=unused-argument if selected != wx.NOT_FOUND: index = self.ListIndex[selected] subindex = self.SubindexGrid.GetGridCursorRow() - entry_infos = self.Manager.GetEntryInfos(index) + entry_infos = self.Manager.current.GetEntryInfos(index) if not entry_infos["struct"] & OD.MultipleSubindexes or subindex != 0: - subentry_infos = self.Manager.GetSubentryInfos(index, subindex) - typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) + subentry_infos = self.Manager.current.GetSubentryInfos(index, subindex) + typeinfos = self.Manager.current.GetEntryInfos(subentry_infos["type"]) if typeinfos: + # FIXME: Exists in NetworkEditorTemplate, not in NodeEditorTemplate node_id = self.ParentWindow.GetCurrentNodeId() value = self.Table.GetValueByName(subindex, "value") if value == "True": @@ -802,10 +859,14 @@ def OnAddToDCFSubindexMenu(self, event): # pylint: disable=unused-argument value = int(value, 16) else: value = int(value, 16) - self.Manager.AddToMasterDCF(node_id, index, subindex, max(1, typeinfos["size"] // 8), value) + # FIXME: Exists in NodeList, not in NodeManager + self.Manager.AddToMasterDCF( + node_id, index, subindex, max(1, typeinfos["size"] // 8), value + ) + # FIXME: Exists in NetworkEditorTemplate, not in NodeEditorTemplate self.ParentWindow.OpenMasterDCFDialog(node_id) - def OpenDCFDialog(self, node_id): + def OpenDCFDialog(self, node_id: int): self.PartList.SetSelection(7) self.RefreshIndexList() self.IndexList.SetSelection(self.ListIndex.index(0x1F22)) @@ -818,32 +879,33 @@ def OnRenameIndexMenu(self, event): # pylint: disable=unused-argument selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): - infos = self.Manager.GetEntryInfos(index) - dialog = wx.TextEntryDialog(self, "Give a new name for index 0x%04X" % index, - "Rename an index", infos["name"], wx.OK | wx.CANCEL) - if dialog.ShowModal() == wx.ID_OK: - self.Manager.SetCurrentEntryName(index, dialog.GetValue()) - self.ParentWindow.RefreshBufferState() - self.RefreshIndexList() - dialog.Destroy() + if self.Manager.current.IsEntry(index): + infos = self.Manager.current.GetEntryInfos(index) + with wx.TextEntryDialog( + self, f"Give a new name for index 0x{index:04X}", + "Rename an index", infos["name"], wx.OK | wx.CANCEL, + ) as dialog: + if dialog.ShowModal() == wx.ID_OK: + self.Manager.SetCurrentEntryName(index, dialog.GetValue()) + self.ParentWindow.RefreshBufferState() + self.RefreshIndexList() def OnModifyIndexMenu(self, event): # pylint: disable=unused-argument if self.Editable: selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index) and index < 0x260: - values, valuetype = self.Manager.GetCustomisedTypeValues(index) - dialog = cdia.UserTypeDialog(self) - dialog.SetTypeList(self.Manager.GetCustomisableTypes(), values[1]) + if self.Manager.current.IsEntry(index) and index < 0x260: + values, valuetype = self.Manager.current.GetCustomisedTypeValues(index) + dialog = common.UserTypeDialog(self) + dialog.SetTypeList(self.Manager.current.GetCustomisableTypes(), values[1]) if valuetype == 0: dialog.SetValues(min=values[2], max=values[3]) elif valuetype == 1: dialog.SetValues(length=values[2]) if dialog.ShowModal() == wx.ID_OK: - type_, min_, max_, length = dialog.GetValues() - self.Manager.SetCurrentUserType(index, type_, min_, max_, length) + otype, omin, omax, olength = dialog.GetValues() + self.Manager.SetCurrentUserType(index, otype, omin, omax, olength) self.ParentWindow.RefreshBufferState() self.RefreshIndexList() @@ -852,7 +914,7 @@ def OnDeleteIndexMenu(self, event): # pylint: disable=unused-argument selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): + if self.Manager.current.IsEntry(index): self.Manager.ManageEntriesOfCurrent([], [index]) self.ParentWindow.RefreshBufferState() self.RefreshIndexList() @@ -862,48 +924,52 @@ def OnAddSubindexMenu(self, event): # pylint: disable=unused-argument selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): - dialog = wx.TextEntryDialog(self, "Number of subindexes to add:", - "Add subindexes", "1", wx.OK | wx.CANCEL) - if dialog.ShowModal() == wx.ID_OK: - try: - number = int(dialog.GetValue()) - self.Manager.AddSubentriesToCurrent(index, number) - self.ParentWindow.RefreshBufferState() - self.RefreshIndexList() - except ValueError: - message = wx.MessageDialog(self, "An integer is required!", "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + if self.Manager.current.IsEntry(index): + with wx.TextEntryDialog( + self, "Number of subindexes to add:", + "Add subindexes", "1", wx.OK | wx.CANCEL, + ) as dialog: + if dialog.ShowModal() == wx.ID_OK: + try: + number = int(dialog.GetValue()) + self.Manager.AddSubentriesToCurrent(index, number) + self.ParentWindow.RefreshBufferState() + self.RefreshIndexList() + except ValueError: + display_error_dialog(self, "An integer is required!") def OnDeleteSubindexMenu(self, event): # pylint: disable=unused-argument if self.Editable: selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): - dialog = wx.TextEntryDialog(self, "Number of subindexes to delete:", - "Delete subindexes", "1", wx.OK | wx.CANCEL) - if dialog.ShowModal() == wx.ID_OK: - try: - number = int(dialog.GetValue()) - self.Manager.RemoveSubentriesFromCurrent(index, number) - self.ParentWindow.RefreshBufferState() - self.RefreshIndexList() - except ValueError: - message = wx.MessageDialog(self, "An integer is required!", "ERROR", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - dialog.Destroy() + if self.Manager.current.IsEntry(index): + with wx.TextEntryDialog( + self, "Number of subindexes to delete:", + "Delete subindexes", "1", wx.OK | wx.CANCEL, + ) as dialog: + if dialog.ShowModal() == wx.ID_OK: + try: + number = int(dialog.GetValue()) + self.Manager.RemoveSubentriesFromCurrent(index, number) + self.ParentWindow.RefreshBufferState() + self.RefreshIndexList() + except ValueError: + display_error_dialog(self, "An integer is required!") def OnDefaultValueSubindexMenu(self, event): # pylint: disable=unused-argument if self.Editable: selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): + if self.Manager.current.IsEntry(index): row = self.SubindexGrid.GetGridCursorRow() self.Manager.SetCurrentEntryToDefault(index, row) self.ParentWindow.RefreshBufferState() self.RefreshIndexList() + + +# This class essentially only exists to provide type hints +class EditingPanelNotebook(wx.Notebook): + """Type override for wx.Notebook.""" + def GetPage(self, page) -> EditingPanel: ... # type: ignore[empty-body] diff --git a/tests/conftest.py b/tests/conftest.py index d6fba68..204e108 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,67 +1,287 @@ +""" Pytest configuration file for the objdictgen package """ +import shutil +from typing import Generator import os -import glob +import sys import difflib +from dataclasses import dataclass +import subprocess +from pathlib import Path +import pickle import pytest -import attr import objdictgen import objdictgen.node -HERE = os.path.split(__file__)[0] +# The path to this directory +HERE = Path(__file__).parent + +# Where are pytest started from? +CWD = Path(os.getcwd()) # Location of the test OD files -ODDIR = os.path.join(HERE, 'od') +ODDIR = HERE / 'od' + +# Default OD test directories +ODTESTDIRS = [ + ODDIR, +] + +# Files to exclude from py2 legacy testing +PY2_OD_EXCLUDE = [ + ODDIR / "unicode.json", + ODDIR / "unicode.od", +] + +# Files to exclude in EDS testing +PY2_EDS_EXCLUDE = [ + ODDIR / "legacy-strings.od", # The legacy tool silently crash on this input +] + +# Equivalent files that should compare as equal +COMPARE_EQUIVS = [ + ('alltypes', 'legacy-alltypes'), + ('master', 'legacy-master'), + ('slave', 'legacy-slave'), + #( "profile-test", "legacy-profile-test"), + ( "profile-ds302", "legacy-profile-ds302"), + ( "profile-ds401", "legacy-profile-ds401"), + ( "profile-ds302-ds401", "legacy-profile-ds302-ds401"), + #( "profile-ds302-test", "legacy-profile-ds302-test"), + ( "slave-ds302", "legacy-slave-ds302"), + ( "slave-emcy", "legacy-slave-emcy"), + ( "slave-heartbeat", "legacy-slave-heartbeat"), + ( "slave-nodeguarding", "legacy-slave-nodeguarding"), + ( "slave-sync", "legacy-slave-sync"), + ( "strings", "legacy-strings"), + ( "domain", "legacy-domain"), +] + -# Make a list of all .od files in tests/od -ODFILES = list(glob.glob(os.path.join(ODDIR, 'legacy-compare', '*.od'))) -ODFILES.extend(glob.glob(os.path.join(ODDIR, 'extra-compare', '*.od'))) +class ODPath(type(Path())): + """ Overload on Path to add OD specific methods """ + @classmethod + def nfactory(cls, iterable): + return [cls(p.absolute()) for p in iterable] -@attr.s -class ODFile(object): - filename = attr.ib() - def __str__(self): - return self.filename def __add__(self, other): - return self.filename + other - @property - def name(self): - return os.path.split(self.filename)[1] - @property - def relpath(self): - return os.path.relpath(self.filename, ODDIR) + return ODPath(self.parent / (self.name + other)) + + def __truediv__(self, other): + return ODPath(Path.__truediv__(self, other)) + + def rel_to_odpath(self): + return self.relative_to(ODDIR.absolute()) + + def rel_to_wd(self): + return self.relative_to(CWD) + + @classmethod + def n(cls, *args, **kwargs): + return cls(*args, **kwargs) + + +class Fn: + """ Helper class for testing functions """ + + @staticmethod + def diff(a, b, predicate=None, postprocess=None, **kw): + """ Diff two files """ + if predicate is None: + predicate = lambda x: True + with open(a, 'r', encoding="utf-8") as f: + da = [n.rstrip() for n in f if predicate(n)] + with open(b, 'r', encoding="utf-8") as f: + db = [n.rstrip() for n in f if predicate(n)] + out = list(d.rstrip() for d in difflib.unified_diff(da, db, **kw)) + if out and postprocess: + out = list(postprocess(out)) + if out: + print('\n'.join(out)) + pytest.fail(f"Files {a} and {b} differ") + return not out + + +@dataclass +class Py2: + """ Class for calling python2 """ + py2: Path | None + objdictgen: Path | None + + PIPE = subprocess.PIPE + STDOUT = subprocess.STDOUT + + def run(self, script=None, *, cmd='-', **kwargs): + + if not self.py2: + pytest.skip("--py2 configuration option not set") + if not self.py2.exists(): + pytest.fail(f"--py2 executable {self.py2} cannot be found") + if not self.objdictgen: + pytest.skip("--objdictgen configuation option not set") + if not self.objdictgen.exists(): + pytest.fail(f"--objdictgen directory {self.objdictgen} cannot be found") + + env = os.environ.copy() + env['PYTHONPATH'] = str(self.objdictgen) + + if script is not None: + indata = script.encode('ascii', 'backslashreplace') + else: + indata = None + + args = kwargs.pop('args', []) + kw = { + 'input': indata, + 'env': env, + 'text': False, + } + kw.update(**kwargs) + + return subprocess.run([self.py2, cmd] + args, executable=self.py2, **kw) + + def stdout(self, proc): + if not proc.stdout: + return '' + return proc.stdout.decode('utf-8') + + def stderr(self, proc): + if not proc.stderr: + return '' + return proc.stderr.decode('utf-8') + + def check(self, proc): + if proc.returncode: + raise subprocess.CalledProcessError(proc.returncode, proc.args, self.stdout(proc)) + return proc + + +def pytest_addoption(parser): + """ Add options to the pytest command line """ + parser.addoption( + "--py2", action="store", default=None, type=Path, help="Path to python2 executable", + ) + parser.addoption( + "--objdictgen", action="store", default=None, type=Path, help="Path to legacy objdictgen directory", + ) + parser.addoption( + "--oddir", action="append", default = None, type=Path, help="Path to OD test directories", + ) + parser.addoption( + "--extra", action="store_true", default=False, help=f"Run extra tests in {(ODDIR / 'extra-compare').relative_to(CWD)}" + ) -ODFILES = [ODFile(os.path.abspath(x.replace('.od', ''))) for x in ODFILES] def pytest_generate_tests(metafunc): - ''' Special fixture generators ''' + """ Special fixture generators """ + + # Collect the list of od test directories + oddirs = metafunc.config.getoption("oddir") + if not oddirs: + oddirs = list(ODTESTDIRS) + + # Add the extra directory if requested + extra = metafunc.config.getoption("extra") + if extra: + oddirs.append(ODDIR / 'extra-compare') + + # Add "_suffix" fixture + if "_suffix" in metafunc.fixturenames: + metafunc.parametrize( + "_suffix", ['od', 'json', 'eds'], indirect=False, scope="session" + ) + + # Make a list of all .od files in tests/od + odfiles = [] + for d in oddirs: + odfiles += ODPath.nfactory(d.glob('*.od')) + + jsonfiles = [] + for d in oddirs: + jsonfiles += ODPath.nfactory(d.glob('*.json')) + + edsfiles = [] + for d in oddirs: + edsfiles += ODPath.nfactory(d.glob('*.eds')) + + def odids(odlist): + return [str(o.relative_to(ODDIR).as_posix()) for o in odlist] + + # Add "odfile" fixture if "odfile" in metafunc.fixturenames: - metafunc.parametrize("odfile", ODFILES, ids=[ - o.relpath for o in ODFILES - ], indirect=True) + data = sorted(odfiles) + metafunc.parametrize( + "odfile", data, ids=odids(data), indirect=False, scope="session" + ) + # Add "odjson" fixture + if "odjson" in metafunc.fixturenames: + data = sorted(odfiles + jsonfiles) + metafunc.parametrize( + "odjson", data, ids=odids(data), indirect=False, scope="session" + ) -@pytest.fixture -def oddir(): - """ Fixture returning the path for the od test directory """ - return os.path.abspath(os.path.join(ODDIR)) + # Add "odjsoneds" fixture + if "odjsoneds" in metafunc.fixturenames: + data = sorted(odfiles + jsonfiles + edsfiles) + metafunc.parametrize( + "odjsoneds", data, ids=odids(data), indirect=False, scope="session" + ) + + # Add "py2" fixture + if "py2" in metafunc.fixturenames: + py2_path = metafunc.config.getoption("py2") + objdictgen_dir = metafunc.config.getoption("objdictgen") + + if py2_path: + py2_path = py2_path.absolute() + if objdictgen_dir: + objdictgen_dir = objdictgen_dir.absolute() + + metafunc.parametrize("py2", [Py2(py2_path, objdictgen_dir)], + indirect=False, scope="session") + # Add "equiv_files" fixture + if "equiv_files" in metafunc.fixturenames: + metafunc.parametrize("equiv_files", COMPARE_EQUIVS, ids=(e[0] for e in COMPARE_EQUIVS), + indirect=False, scope="session") + + +def pytest_collection_modifyitems(items): + """Modifies test items in place to ensure test modules run in a given order.""" + # Somewhat of a hack to run test cases ub in sorted order + items[:] = list(sorted(items, key=lambda k: (k.module.__name__, k.name))) + + +# +# FIXTURES +# ======================================== +# @pytest.fixture def basepath(): """ Fixture returning the base of the project """ - return os.path.abspath(os.path.join(HERE, '..')) + return (HERE / '..').resolve() @pytest.fixture -def wd(tmp_path): - """ Fixture that changes the working directory to a temp location """ - cwd = os.getcwd() - os.chdir(str(tmp_path)) - # print("PATH: %s" % os.getcwd()) - yield os.getcwd() - os.chdir(str(cwd)) +def fn(): + """ Fixture providing a helper class for testing functions """ + return Fn() + + +@pytest.fixture(scope="session") +def odfile(request) -> Generator[ODPath, None, None]: + """ Fixture for each of the od files in the test directory """ + return request.param + + +@pytest.fixture +def odpath(): + """ Fixture returning the path for the od test directory """ + return ODPath(ODDIR.absolute()) @pytest.fixture @@ -73,35 +293,130 @@ def profile(monkeypatch): newdirs.extend(objdictgen.PROFILE_DIRECTORIES) newdirs.append(ODDIR) monkeypatch.setattr(objdictgen, 'PROFILE_DIRECTORIES', newdirs) - yield None + return None @pytest.fixture -def odfile(request, profile): +def py2(request) -> Generator[Py2, None, None]: """ Fixture for each of the od files in the test directory """ - yield request.param + return request.param -def diff(a, b, predicate=None, **kw): - if predicate is None: - predicate = lambda x: True - print(a, b) - with open(a, 'r') as f: - da = [n.rstrip() for n in f if predicate(n)] - with open(b, 'r') as f: - db = [n.rstrip() for n in f if predicate(n)] - out = tuple(difflib.unified_diff(da, db, **kw)) - if out: - print('\n'.join(o.rstrip() for o in out)) - return not out +@pytest.fixture(scope="session") +def py2_cfile(odfile, py2, wd_session): + """Fixture for making the cfiles generated by python2 objdictgen""" + if not odfile.exists(): + pytest.skip(f"File not found: {odfile.rel_to_wd()}") -class Fn: - @staticmethod - def diff(*a, **kw): - return diff(*a, **kw) + if odfile in PY2_OD_EXCLUDE: + pytest.skip(f"File {odfile.rel_to_wd()} is excluded from py2 testing") + + tmpod = odfile.stem + + shutil.copy(odfile, tmpod + '.od') + + pyapp = f""" +from nodemanager import * +import eds_utils, gen_cfile +eds_utils._ = lambda x: x +gen_cfile._ = lambda x: x +manager = NodeManager() +manager.OpenFileInCurrent(r'{tmpod}.od') +manager.ExportCurrentToCFile(r'{tmpod}.c') +""" + cmd = py2.run(script=pyapp, stderr=py2.PIPE) + stderr = py2.stderr(cmd) + print(stderr, file=sys.stderr) + if cmd.returncode: + lines = stderr.splitlines() + pytest.xfail(f"Py2 failed: {lines[-1]}") + + return odfile, ODPath(tmpod).absolute() + + +@pytest.fixture(scope="session") +def py2_edsfile(odfile, py2, wd_session): + """Fixture for making the cfiles generated by python2 objdictgen""" + + if not odfile.exists(): + pytest.skip(f"File not found: {odfile.rel_to_wd()}") + + if odfile in PY2_EDS_EXCLUDE: + pytest.skip(f"File {odfile.rel_to_wd()} is excluded from py2 testing") + + tmpod = odfile.stem + + shutil.copy(odfile, tmpod + '.od') + + pyapp = f""" +from nodemanager import * +import eds_utils, gen_cfile +eds_utils._ = lambda x: x +gen_cfile._ = lambda x: x +manager = NodeManager() +manager.OpenFileInCurrent(r'{tmpod}.od') +if manager.CurrentNode.GetEntry(0x1018, 1) is None: + raise Exception("Missing ID 0x1018 which won't work with EDS") +manager.ExportCurrentToEDSFile(r'{tmpod}.eds') +""" + cmd = py2.run(script=pyapp, stderr=py2.PIPE) + stderr = py2.stderr(cmd) + print(stderr, file=sys.stderr) + if cmd.returncode: + lines = stderr.splitlines() + pytest.xfail(f"Py2 failed: {lines[-1]}") + + return odfile, ODPath(tmpod).absolute() + + +@pytest.fixture(scope="session") +def py2_pickle(odfile, py2, wd_session): + """Fixture for making the cfiles generated by python2 objdictgen""" + + if not odfile.exists(): + pytest.skip(f"File not found: {odfile.rel_to_wd()}") + + tmpod = odfile.stem + + shutil.copy(odfile, tmpod + '.od') + + pyapp = f""" +import pickle +from nodemanager import * +manager = NodeManager() +manager.OpenFileInCurrent(r'{tmpod}.od') +with open(r'{tmpod}.pickle', 'wb') as f: + pickle.dump(manager.CurrentNode.__dict__, f, protocol=1) +""" + cmd = py2.run(script=pyapp, stderr=py2.PIPE) + stderr = py2.stderr(cmd) + print(stderr, file=sys.stderr) + if cmd.returncode: + lines = stderr.splitlines() + pytest.xfail(f"Py2 failed: {lines[-1]}") + + # Load the pickled data + with open(tmpod + '.pickle', 'rb') as f: + data = pickle.load(f, encoding='utf-8') + + return odfile, data @pytest.fixture -def fn(): - return Fn +def wd(tmp_path): + """ Fixture that changes the working directory to a temp location """ + cwd = os.getcwd() + os.chdir(str(tmp_path)) + yield Path(os.getcwd()) + os.chdir(str(cwd)) + + +@pytest.fixture(scope="session") +def wd_session(tmp_path_factory): + """ Fixture that changes the working directory to a temp location """ + cwd = os.getcwd() + tmp_path = tmp_path_factory.mktemp("session") + os.chdir(str(tmp_path)) + yield Path(os.getcwd()) + os.chdir(str(cwd)) diff --git a/tests/od/README.md b/tests/od/README.md new file mode 100644 index 0000000..c835c67 --- /dev/null +++ b/tests/od/README.md @@ -0,0 +1,4 @@ +# ODs for testing + +This directory contains the ODs for testing the various aspects of objdictgen. +The `legacy-*` is ODs which are generated with the legacy python2 tool. diff --git a/tests/od/alltypes.json b/tests/od/alltypes.json index 23ec081..f1e0a89 100644 --- a/tests/od/alltypes.json +++ b/tests/od/alltypes.json @@ -2,86 +2,42 @@ "$id": "od data", "$version": "1", "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-08-08T21:02:03.703619", + "$tool": "odg 3.4", + "$date": "2024-02-23T16:02:27.420347", "name": "Alltypes", - "description": "All types", + "description": "OD that implements all available object types", "type": "master", "id": 0, "profile": "None", + "default_string_size": 10, "dictionary": [ { - "index": "0x1000", // 4096 - "name": "Device Type", + "index": "0x2008", // 8200 + "name": "REAL32", "struct": "var", - "group": "built-in", - "mandatory": true, + "mandatory": false, "sub": [ { - "name": "Device Type", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 + "name": "REAL32", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "value": 1.2 } ] }, { - "index": "0x1001", // 4097 - "name": "Error Register", + "index": "0x2009", // 8201 + "name": "VISIBLE_STRING", "struct": "var", - "group": "built-in", - "mandatory": true, + "mandatory": false, "sub": [ { - "name": "Error Register", - "type": "UNSIGNED8", // 5 - "access": "ro", + "name": "VISIBLE_STRING", + "type": "VISIBLE_STRING", // 9 + "access": "rw", "pdo": true, - "value": 0 - } - ] - }, - { - "index": "0x1018", // 4120 - "name": "Identity", - "struct": "record", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Vendor ID", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Product Code", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Revision Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Serial Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 + "value": "ABCD" } ] }, @@ -111,7 +67,7 @@ "type": "INTEGER8", // 2 "access": "rw", "pdo": true, - "value": 0 + "value": 12 } ] }, @@ -126,7 +82,7 @@ "type": "INTEGER16", // 3 "access": "rw", "pdo": true, - "value": 0 + "value": 34 } ] }, @@ -141,7 +97,7 @@ "type": "INTEGER32", // 4 "access": "rw", "pdo": true, - "value": 0 + "value": 45 } ] }, @@ -156,7 +112,7 @@ "type": "UNSIGNED8", // 5 "access": "rw", "pdo": true, - "value": 0 + "value": 56 } ] }, @@ -171,7 +127,7 @@ "type": "UNSIGNED16", // 6 "access": "rw", "pdo": true, - "value": 0 + "value": 8198 } ] }, @@ -186,97 +142,97 @@ "type": "UNSIGNED32", // 7 "access": "rw", "pdo": true, - "value": 0 + "value": 537337864 } ] }, { - "index": "0x2008", // 8200 - "name": "REAL32", + "index": "0x201B", // 8219 + "name": "UNSIGNED64", "struct": "var", "mandatory": false, "sub": [ { - "name": "REAL32", - "type": "REAL32", // 8 + "name": "UNSIGNED64", + "type": "UNSIGNED64", // 27 "access": "rw", "pdo": true, - "value": 0 + "value": 64 } ] }, { - "index": "0x2009", // 8201 - "name": "VISIBLE_STRING", + "index": "0x201A", // 8218 + "name": "UNSIGNED56", "struct": "var", "mandatory": false, "sub": [ { - "name": "VISIBLE_STRING", - "type": "VISIBLE_STRING", // 9 + "name": "UNSIGNED56", + "type": "UNSIGNED56", // 26 "access": "rw", "pdo": true, - "value": "" + "value": 56 } ] }, { - "index": "0x200A", // 8202 - "name": "OCTET_STRING", + "index": "0x2016", // 8214 + "name": "UNSIGNED24", "struct": "var", "mandatory": false, "sub": [ { - "name": "OCTET_STRING", - "type": "OCTET_STRING", // 10 + "name": "UNSIGNED24", + "type": "UNSIGNED24", // 22 "access": "rw", "pdo": true, - "value": "" + "value": 24 } ] }, { - "index": "0x200F", // 8207 - "name": "DOMAIN", + "index": "0x2015", // 8213 + "name": "INTEGER64", "struct": "var", "mandatory": false, "sub": [ { - "name": "DOMAIN", - "type": "DOMAIN", // 15 + "name": "INTEGER64", + "type": "INTEGER64", // 21 "access": "rw", "pdo": true, - "value": "@ABCD" + "value": -64 } ] }, { - "index": "0x2010", // 8208 - "name": "INTEGER24", + "index": "0x2014", // 8212 + "name": "INTEGER56", "struct": "var", "mandatory": false, "sub": [ { - "name": "INTEGER24", - "type": "INTEGER24", // 16 + "name": "INTEGER56", + "type": "INTEGER56", // 20 "access": "rw", "pdo": true, - "value": 0 + "value": -56 } ] }, { - "index": "0x2011", // 8209 - "name": "REAL64", + "index": "0x2013", // 8211 + "name": "INTEGER48", "struct": "var", "mandatory": false, "sub": [ { - "name": "REAL64", - "type": "REAL64", // 17 + "name": "INTEGER48", + "type": "INTEGER48", // 19 "access": "rw", "pdo": true, - "value": 0 + "value": -48 } ] }, @@ -291,67 +247,52 @@ "type": "INTEGER40", // 18 "access": "rw", "pdo": true, - "value": 0 + "value": -40 } ] }, { - "index": "0x2013", // 8211 - "name": "INTEGER48", - "struct": "var", - "mandatory": false, - "sub": [ - { - "name": "INTEGER48", - "type": "INTEGER48", // 19 - "access": "rw", - "pdo": true, - "value": 0 - } - ] - }, - { - "index": "0x2014", // 8212 - "name": "INTEGER56", + "index": "0x2011", // 8209 + "name": "REAL64", "struct": "var", "mandatory": false, "sub": [ { - "name": "INTEGER56", - "type": "INTEGER56", // 20 + "name": "REAL64", + "type": "REAL64", // 17 "access": "rw", "pdo": true, - "value": 0 + "value": 1.6 } ] }, { - "index": "0x2015", // 8213 - "name": "INTEGER64", + "index": "0x2010", // 8208 + "name": "INTEGER24", "struct": "var", "mandatory": false, "sub": [ { - "name": "INTEGER64", - "type": "INTEGER64", // 21 + "name": "INTEGER24", + "type": "INTEGER24", // 16 "access": "rw", "pdo": true, - "value": 0 + "value": -1 } ] }, { - "index": "0x2016", // 8214 - "name": "UNSIGNED24", + "index": "0x2019", // 8217 + "name": "UNSIGNED48", "struct": "var", "mandatory": false, "sub": [ { - "name": "UNSIGNED24", - "type": "UNSIGNED24", // 22 + "name": "UNSIGNED48", + "type": "UNSIGNED48", // 25 "access": "rw", "pdo": true, - "value": 0 + "value": 48 } ] }, @@ -366,52 +307,37 @@ "type": "UNSIGNED40", // 24 "access": "rw", "pdo": true, - "value": 0 + "value": 40 } ] }, { - "index": "0x2019", // 8217 - "name": "UNSIGNED48", - "struct": "var", - "mandatory": false, - "sub": [ - { - "name": "UNSIGNED48", - "type": "UNSIGNED48", // 25 - "access": "rw", - "pdo": true, - "value": 0 - } - ] - }, - { - "index": "0x201A", // 8218 - "name": "UNSIGNED56", + "index": "0x200A", // 8202 + "name": "OCTET_STRING", "struct": "var", "mandatory": false, "sub": [ { - "name": "UNSIGNED56", - "type": "UNSIGNED56", // 26 + "name": "OCTET_STRING", + "type": "OCTET_STRING", // 10 "access": "rw", "pdo": true, - "value": 0 + "value": "ABCD" } ] }, { - "index": "0x201B", // 8219 - "name": "UNSIGNED64", + "index": "0x200F", // 8207 + "name": "DOMAIN", "struct": "var", "mandatory": false, "sub": [ { - "name": "UNSIGNED64", - "type": "UNSIGNED64", // 27 + "name": "DOMAIN", + "type": "DOMAIN", // 15 "access": "rw", "pdo": true, - "value": 0 + "value": "@ABCD" } ] } diff --git a/tests/od/alltypes.od b/tests/od/alltypes.od index 31b7c14..3ef9d28 100644 --- a/tests/od/alltypes.od +++ b/tests/od/alltypes.od @@ -1,53 +1,49 @@ - - + + - - - - - - + + - + - + - + - + - + - + - + - + - + @@ -55,78 +51,65 @@ - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - + - + - + - + - + - - + + @@ -158,15 +141,15 @@ - + - - + + @@ -198,15 +181,15 @@ - + - - + + @@ -238,15 +221,15 @@ - + - - + + @@ -278,15 +261,15 @@ - + - - + + @@ -318,15 +301,15 @@ - + - - + + @@ -358,15 +341,15 @@ - + - - + + @@ -398,15 +381,15 @@ - + - - + + @@ -438,15 +421,15 @@ - + - - + + @@ -478,15 +461,15 @@ - + - - + + @@ -518,15 +501,15 @@ - + - - + + @@ -558,15 +541,15 @@ - + - - + + @@ -598,15 +581,15 @@ - + - - + + @@ -638,15 +621,15 @@ - + - - + + @@ -678,15 +661,15 @@ - + - - + + @@ -718,15 +701,15 @@ - + - - + + @@ -758,15 +741,15 @@ - + - - + + @@ -798,15 +781,15 @@ - + - - + + @@ -838,15 +821,15 @@ - + - - + + @@ -878,15 +861,15 @@ - + - - + + @@ -918,15 +901,15 @@ - + - - + + @@ -958,15 +941,15 @@ - + - - + + @@ -997,10 +980,11 @@ - + + diff --git a/tests/od/domain.json b/tests/od/domain.json new file mode 100644 index 0000000..701ff82 --- /dev/null +++ b/tests/od/domain.json @@ -0,0 +1,30 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-04-10T22:48:13.333406", + "name": "domain", + "description": "", + "type": "master", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x2000", // 8192 + "name": "Domain", + "struct": "var", + "group": "user", + "mandatory": false, + "sub": [ + { + "name": "Domain", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": true, + "value": "\u0002@ABC\u0001" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/domain.od b/tests/od/domain.od new file mode 100644 index 0000000..7d4e54b --- /dev/null +++ b/tests/od/domain.od @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/generate_json.py b/tests/od/generate_json.py deleted file mode 100644 index a58e028..0000000 --- a/tests/od/generate_json.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import glob -import sys -import argparse -import shutil -import logging - -from objdictgen import Node - -parser = argparse.ArgumentParser() -parser.add_argument("od_dir", help="Directory to ODs") -parser.add_argument("out_dir", nargs="?", help="Output directory. Use od_dir if omitted.") -opts = parser.parse_args() - -if opts.out_dir: - out_dir = os.path.abspath(opts.out_dir) -else: - out_dir = os.path.abspath(opts.od_dir) - -# Initalize the python logger to simply output to stdout -log = logging.getLogger() -log.setLevel(logging.DEBUG) -log.addHandler(logging.StreamHandler(sys.stdout)) - - -def convert(fname): - base = fname.replace('.od', '') - - print("Reading %s" % fname) - node = Node.LoadFile(base + '.od') - - node.Validate(fix=True) - - print(" Writing json") - node.DumpFile(base + '.json', filetype='json', sort=True) - - -for root, dirs, files in os.walk(opts.od_dir): - for fname in files: - if not fname.endswith('.od'): - continue - - src = os.path.abspath(os.path.join(root, fname)) - dst = os.path.join(out_dir, fname) - if src != dst: - shutil.copyfile(src, dst) - - try: - convert(dst) - except Exception as err: - print("FAILED: %s" %(err,)) diff --git a/tests/od/legacy-alltypes.od b/tests/od/legacy-alltypes.od index 10e5cc1..9035bbf 100644 --- a/tests/od/legacy-alltypes.od +++ b/tests/od/legacy-alltypes.od @@ -1,53 +1,49 @@ - - + + - - - - - - +OD that implements all available object types + - + - + - + - + - + - + - + - + ABCD - + ABCD @@ -55,78 +51,66 @@ - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - + - + - + + - + - + - - + + @@ -158,15 +142,15 @@ - + - - + + @@ -198,15 +182,15 @@ - + - - + + @@ -238,15 +222,15 @@ - + - - + + @@ -278,15 +262,15 @@ - + - - + + @@ -318,15 +302,15 @@ - + - - + + @@ -358,15 +342,15 @@ - + - - + + @@ -398,15 +382,15 @@ - + - - + + @@ -438,15 +422,15 @@ - + - - + + @@ -478,15 +462,15 @@ - + - - + + @@ -518,15 +502,15 @@ - + - - + + @@ -558,15 +542,15 @@ - + - - + + @@ -598,15 +582,15 @@ - + - - + + @@ -638,15 +622,15 @@ - + - - + + @@ -678,15 +662,15 @@ - + - - + + @@ -718,15 +702,15 @@ - + - - + + @@ -758,15 +742,15 @@ - + - - + + @@ -798,15 +782,15 @@ - + - - + + @@ -838,15 +822,15 @@ - + - - + + @@ -878,15 +862,15 @@ - + - - + + @@ -918,15 +902,15 @@ - + - - + + @@ -958,15 +942,15 @@ - + - - + + @@ -997,10 +981,10 @@ - + - +Alltypes diff --git a/tests/od/legacy-compare/README.md b/tests/od/legacy-compare/README.md deleted file mode 100644 index 93ef111..0000000 --- a/tests/od/legacy-compare/README.md +++ /dev/null @@ -1,4 +0,0 @@ - -This directory contains .od files that have been converted to .c/.h and .eds -files with the legacy tool, and .json that have been converted with the new -tool. diff --git a/tests/od/legacy-compare/jsontest.c b/tests/od/legacy-compare/jsontest.c deleted file mode 100644 index 3243a83..0000000 --- a/tests/od/legacy-compare/jsontest.c +++ /dev/null @@ -1,532 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#include "jsontest.h" - -/**************************************************************************/ -/* Declaration of mapped variables */ -/**************************************************************************/ -UNS8 VAR = 0x0; /* Mapped at index 0x2000, subindex 0x00 */ -INTEGER8 ARRAY[] = /* Mapped at index 0x2001, subindex 0x01 - 0x02 */ - { - 0x1, /* 1 */ - 0x2 /* 2 */ - }; -UNS8 RECORD_RECORD_1 = 0x7; /* Mapped at index 0x2002, subindex 0x01 */ -INTEGER16 RECORD_RECORD_2 = 0x2A; /* Mapped at index 0x2002, subindex 0x02 */ -UNS8 Global_Interrupt_Enable_Digital_Sure = 0x0; /* Mapped at index 0x6000, subindex 0x00 */ -INTEGER32 RECORD_Software_position_limit_Minimal_position_limit = 0x1; /* Mapped at index 0x6100, subindex 0x01 */ -INTEGER32 RECORD_Software_position_limit_Maximal_position_limit = 0x2; /* Mapped at index 0x6100, subindex 0x02 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_1 = 0x1; /* Mapped at index 0x6180, subindex 0x01 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_2 = 0x2; /* Mapped at index 0x6180, subindex 0x02 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_3 = 0x3; /* Mapped at index 0x6180, subindex 0x03 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_4 = 0x4; /* Mapped at index 0x6180, subindex 0x04 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_5 = 0x5; /* Mapped at index 0x6180, subindex 0x05 */ -INTEGER16 RECORD_AL_Action_AL_1_Action_6 = 0x6; /* Mapped at index 0x6180, subindex 0x06 */ -INTEGER16 ARRAY_Acceleration_Value[] = /* Mapped at index 0x6200, subindex 0x01 - 0x02 */ - { - 0x1, /* 1 */ - 0x10 /* 16 */ - }; -UNS32 Device_Type_1_and_0 = 0x1; /* Mapped at index 0x6300, subindex 0x00 */ -UNS32 Device_Type_2_and_0 = 0xC; /* Mapped at index 0x6302, subindex 0x00 */ -INTEGER32 NARRAY_CAM1_Low_Limit[] = /* Mapped at index 0x6400, subindex 0x01 - 0x02 */ - { - 0x1, /* 1 */ - 0x2 /* 2 */ - }; -INTEGER32 NARRAY_CAM2_Low_Limit[] = /* Mapped at index 0x6402, subindex 0x01 - 0x00 */ - { - }; -UNS32 NRECORD_Receive_PDO_1_Parameter_COB_ID_used_by_PDO = 0x1; /* Mapped at index 0x6500, subindex 0x01 */ -UNS8 NRECORD_Receive_PDO_1_Parameter_Transmission_Type = 0x2; /* Mapped at index 0x6500, subindex 0x02 */ -UNS16 NRECORD_Receive_PDO_1_Parameter_Inhibit_Time = 0x3; /* Mapped at index 0x6500, subindex 0x03 */ -UNS8 NRECORD_Receive_PDO_1_Parameter_Compatibility_Entry = 0x4; /* Mapped at index 0x6500, subindex 0x04 */ -UNS16 NRECORD_Receive_PDO_1_Parameter_Event_Timer = 0x5; /* Mapped at index 0x6500, subindex 0x05 */ -UNS8 NRECORD_Receive_PDO_1_Parameter_SYNC_start_value = 0x6; /* Mapped at index 0x6500, subindex 0x06 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_1 = 0x1; /* Mapped at index 0x6580, subindex 0x01 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_2 = 0x2; /* Mapped at index 0x6580, subindex 0x02 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_3 = 0x3; /* Mapped at index 0x6580, subindex 0x03 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_4 = 0x4; /* Mapped at index 0x6580, subindex 0x04 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_5 = 0x5; /* Mapped at index 0x6580, subindex 0x05 */ -UNS32 NRECORD_AL_1_Action_AL_1_Action_6 = 0x6; /* Mapped at index 0x6580, subindex 0x06 */ -UNS16 Producer_Heartbeat_Time = 0x1; /* Mapped at index 0x6600, subindex 0x00 */ - -/**************************************************************************/ -/* Declaration of value range types */ -/**************************************************************************/ - -#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */ -#define valueRange_1 0xA0 /* Type UNS32, 100 < value < 200 */ -UNS32 jsontest_valueRangeTest (UNS8 typeValue, void * value) -{ - switch (typeValue) { - case valueRange_EMC: - if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break; - case valueRange_1: - if (*(UNS32*)value < (UNS32)100) return OD_VALUE_TOO_LOW; - if (*(UNS32*)value > (UNS32)200) return OD_VALUE_TOO_HIGH; - break; - } - return 0; -} - -/**************************************************************************/ -/* The node id */ -/**************************************************************************/ -/* node_id default value.*/ -UNS8 jsontest_bDeviceNodeId = 0x00; - -/**************************************************************************/ -/* Array of message processing information */ - -const UNS8 jsontest_iam_a_slave = 0; - -TIMER_HANDLE jsontest_heartBeatTimers[1]; - -/* -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - - OBJECT DICTIONARY - -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -*/ - -/* index 0x1000 : Device Type. */ - UNS32 jsontest_obj1000 = 0x0; /* 0 */ - subindex jsontest_Index1000[] = - { - { RO|TO_BE_SAVE, uint32, sizeof (UNS32), (void*)&jsontest_obj1000, NULL } - }; - -/* index 0x1001 : Error Register. */ - UNS8 jsontest_obj1001 = 0x0; /* 0 */ - subindex jsontest_Index1001[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_obj1001, NULL } - }; - -/* index 0x1003 : Pre-defined Error Field */ - UNS8 jsontest_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ - UNS32 jsontest_obj1003[] = - { - 0x0 /* 0 */ - }; - subindex jsontest_Index1003[] = - { - { RW, valueRange_EMC, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1003, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&jsontest_obj1003[0], NULL } - }; - -/* index 0x1005 : SYNC COB ID */ - UNS32 jsontest_obj1005 = 0x0; /* 0 */ - -/* index 0x1006 : Communication / Cycle Period */ - UNS32 jsontest_obj1006 = 0x0; /* 0 */ - -/* index 0x100C : Guard Time */ - UNS16 jsontest_obj100C = 0x0; /* 0 */ - -/* index 0x100D : Life Time Factor */ - UNS8 jsontest_obj100D = 0x0; /* 0 */ - -/* index 0x1014 : Emergency COB ID */ - UNS32 jsontest_obj1014 = 0x80 + 0x00; /* 128 + NodeID */ - -/* index 0x1016 : Consumer Heartbeat Time */ - UNS8 jsontest_highestSubIndex_obj1016 = 0; - UNS32 jsontest_obj1016[]={0}; - -/* index 0x1017 : Producer Heartbeat Time */ - UNS16 jsontest_obj1017 = 0x0; /* 0 */ - -/* index 0x1018 : Identity. */ - UNS8 jsontest_highestSubIndex_obj1018 = 4; /* number of subindex - 1*/ - UNS32 jsontest_obj1018_Vendor_ID = 0x0; /* 0 */ - UNS32 jsontest_obj1018_Product_Code = 0x0; /* 0 */ - UNS32 jsontest_obj1018_Revision_Number = 0x0; /* 0 */ - UNS32 jsontest_obj1018_Serial_Number = 0x0; /* 0 */ - subindex jsontest_Index1018[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1018, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&jsontest_obj1018_Vendor_ID, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&jsontest_obj1018_Product_Code, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&jsontest_obj1018_Revision_Number, NULL }, - { RO|TO_BE_SAVE, uint32, sizeof (UNS32), (void*)&jsontest_obj1018_Serial_Number, NULL } - }; - -/* index 0x1280 : Client SDO 1 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1280 = 3; /* number of subindex - 1*/ - UNS32 jsontest_obj1280_COB_ID_Client_to_Server_Transmit_SDO = 0x0; /* 0 */ - UNS32 jsontest_obj1280_COB_ID_Server_to_Client_Receive_SDO = 0x0; /* 0 */ - UNS8 jsontest_obj1280_Node_ID_of_the_SDO_Server = 0x0; /* 0 */ - subindex jsontest_Index1280[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1280, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1280_COB_ID_Client_to_Server_Transmit_SDO, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1280_COB_ID_Server_to_Client_Receive_SDO, NULL }, - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_obj1280_Node_ID_of_the_SDO_Server, NULL } - }; - -/* index 0x1281 : Client SDO 2 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1281 = 3; /* number of subindex - 1*/ - UNS32 jsontest_obj1281_COB_ID_Client_to_Server_Transmit_SDO = 0x0; /* 0 */ - UNS32 jsontest_obj1281_COB_ID_Server_to_Client_Receive_SDO = 0x0; /* 0 */ - UNS8 jsontest_obj1281_Node_ID_of_the_SDO_Server = 0x0; /* 0 */ - subindex jsontest_Index1281[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1281, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1281_COB_ID_Client_to_Server_Transmit_SDO, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1281_COB_ID_Server_to_Client_Receive_SDO, NULL }, - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_obj1281_Node_ID_of_the_SDO_Server, NULL } - }; - -/* index 0x1282 : Client SDO 3 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1282 = 3; /* number of subindex - 1*/ - UNS32 jsontest_obj1282_COB_ID_Client_to_Server_Transmit_SDO = 0x0; /* 0 */ - UNS32 jsontest_obj1282_COB_ID_Server_to_Client_Receive_SDO = 0x0; /* 0 */ - UNS8 jsontest_obj1282_Node_ID_of_the_SDO_Server = 0x0; /* 0 */ - subindex jsontest_Index1282[] = - { - { RO, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1282, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1282_COB_ID_Client_to_Server_Transmit_SDO, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1282_COB_ID_Server_to_Client_Receive_SDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1282_Node_ID_of_the_SDO_Server, NULL } - }; - -/* index 0x1400 : Receive PDO 1 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1400 = 6; /* number of subindex - 1*/ - UNS32 jsontest_obj1400_COB_ID_used_by_PDO = 0x200; /* 512 */ - UNS8 jsontest_obj1400_Transmission_Type = 0x0; /* 0 */ - UNS16 jsontest_obj1400_Inhibit_Time = 0x0; /* 0 */ - UNS8 jsontest_obj1400_Compatibility_Entry = 0x0; /* 0 */ - UNS16 jsontest_obj1400_Event_Timer = 0x0; /* 0 */ - UNS8 jsontest_obj1400_SYNC_start_value = 0x0; /* 0 */ - subindex jsontest_Index1400[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1400, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1400_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1400_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1400_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1400_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1400_Event_Timer, NULL }, - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_obj1400_SYNC_start_value, NULL } - }; - -/* index 0x1401 : Receive PDO 2 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1401 = 6; /* number of subindex - 1*/ - UNS32 jsontest_obj1401_COB_ID_used_by_PDO = 0x300; /* 768 */ - UNS8 jsontest_obj1401_Transmission_Type = 0x0; /* 0 */ - UNS16 jsontest_obj1401_Inhibit_Time = 0x0; /* 0 */ - UNS8 jsontest_obj1401_Compatibility_Entry = 0x0; /* 0 */ - UNS16 jsontest_obj1401_Event_Timer = 0x0; /* 0 */ - UNS8 jsontest_obj1401_SYNC_start_value = 0x0; /* 0 */ - subindex jsontest_Index1401[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1401, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1401_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1401_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1401_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1401_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1401_Event_Timer, NULL }, - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_obj1401_SYNC_start_value, NULL } - }; - -/* index 0x1402 : Receive PDO 3 Parameter. */ - UNS8 jsontest_highestSubIndex_obj1402 = 6; /* number of subindex - 1*/ - UNS32 jsontest_obj1402_COB_ID_used_by_PDO = 0x400; /* 1024 */ - UNS8 jsontest_obj1402_Transmission_Type = 0x0; /* 0 */ - UNS16 jsontest_obj1402_Inhibit_Time = 0x0; /* 0 */ - UNS8 jsontest_obj1402_Compatibility_Entry = 0x0; /* 0 */ - UNS16 jsontest_obj1402_Event_Timer = 0x0; /* 0 */ - UNS8 jsontest_obj1402_SYNC_start_value = 0x0; /* 0 */ - subindex jsontest_Index1402[] = - { - { RO, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1402, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&jsontest_obj1402_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1402_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1402_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1402_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&jsontest_obj1402_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&jsontest_obj1402_SYNC_start_value, NULL } - }; - -/* index 0x1600 : Receive PDO 1 Mapping. */ - UNS8 jsontest_highestSubIndex_obj1600 = 0; /* number of subindex - 1*/ - UNS32 jsontest_obj1600[] = - { - }; - subindex jsontest_Index1600[] = - { - { RW, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1600, NULL } - }; - -/* index 0x1601 : Receive PDO 2 Mapping. */ - UNS8 jsontest_highestSubIndex_obj1601 = 0; /* number of subindex - 1*/ - UNS32 jsontest_obj1601[] = - { - }; - subindex jsontest_Index1601[] = - { - { RW, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1601, NULL } - }; - -/* index 0x1602 : Receive PDO 3 Mapping. */ - UNS8 jsontest_highestSubIndex_obj1602 = 0; /* number of subindex - 1*/ - UNS32 jsontest_obj1602[] = - { - }; - subindex jsontest_Index1602[] = - { - { RW, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1602, NULL } - }; - -/* index 0x1F20 : Store DCF. */ - UNS8 jsontest_highestSubIndex_obj1F20 = 2; /* number of subindex - 1*/ - UNS8* jsontest_obj1F20[] = - { - "", - "" - }; - subindex jsontest_Index1F20[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj1F20, NULL }, - { RW|TO_BE_SAVE, domain, 0, (void*)&jsontest_obj1F20[0], NULL }, - { RW|TO_BE_SAVE, domain, 0, (void*)&jsontest_obj1F20[1], NULL } - }; - -/* index 0x2000 : Mapped variable VAR */ - subindex jsontest_Index2000[] = - { - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&VAR, NULL } - }; - -/* index 0x2001 : Mapped variable ARRAY */ - UNS8 jsontest_highestSubIndex_obj2001 = 2; /* number of subindex - 1*/ - subindex jsontest_Index2001[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj2001, NULL }, - { RO, int8, sizeof (INTEGER8), (void*)&ARRAY[0], NULL }, - { RO, int8, sizeof (INTEGER8), (void*)&ARRAY[1], NULL } - }; - -/* index 0x2002 : Mapped variable RECORD */ - UNS8 jsontest_highestSubIndex_obj2002 = 2; /* number of subindex - 1*/ - subindex jsontest_Index2002[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj2002, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&RECORD_RECORD_1, NULL }, - { RW|TO_BE_SAVE, int16, sizeof (INTEGER16), (void*)&RECORD_RECORD_2, NULL } - }; - -/* index 0x6000 : Mapped variable VAR: Global Interrupt Enable Digital */ - subindex jsontest_Index6000[] = - { - { RW|TO_BE_SAVE, boolean, sizeof (UNS8), (void*)&Global_Interrupt_Enable_Digital_Sure, NULL } - }; - -/* index 0x6100 : Mapped variable RECORD: Software position limit */ - UNS8 jsontest_highestSubIndex_obj6100 = 2; /* number of subindex - 1*/ - subindex jsontest_Index6100[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6100, NULL }, - { RW, int32, sizeof (INTEGER32), (void*)&RECORD_Software_position_limit_Minimal_position_limit, NULL }, - { RW|TO_BE_SAVE, int32, sizeof (INTEGER32), (void*)&RECORD_Software_position_limit_Maximal_position_limit, NULL } - }; - -/* index 0x6180 : Mapped variable RECORD: AL Action */ - UNS8 jsontest_highestSubIndex_obj6180 = 6; /* number of subindex - 1*/ - subindex jsontest_Index6180[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6180, NULL }, - { RW, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_1, NULL }, - { RW, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_2, NULL }, - { RW, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_3, NULL }, - { RW, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_4, NULL }, - { RW, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_5, NULL }, - { RW|TO_BE_SAVE, int16, sizeof (INTEGER16), (void*)&RECORD_AL_Action_AL_1_Action_6, NULL } - }; - -/* index 0x6200 : Mapped variable ARRAY: Acceleration Value */ - UNS8 jsontest_highestSubIndex_obj6200 = 2; /* number of subindex - 1*/ - subindex jsontest_Index6200[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6200, NULL }, - { RO, int16, sizeof (INTEGER16), (void*)&ARRAY_Acceleration_Value[0], NULL }, - { RO|TO_BE_SAVE, int16, sizeof (INTEGER16), (void*)&ARRAY_Acceleration_Value[1], NULL } - }; - -/* index 0x6300 : Mapped variable NVAR: Test profile 1 */ - subindex jsontest_Index6300[] = - { - { RO|TO_BE_SAVE, uint32, sizeof (UNS32), (void*)&Device_Type_1_and_0, NULL } - }; - -/* index 0x6302 : Mapped variable NVAR: Test profile 2 */ - subindex jsontest_Index6302[] = - { - { RO, uint32, sizeof (UNS32), (void*)&Device_Type_2_and_0, NULL } - }; - -/* index 0x6400 : Mapped variable NARRAY: CAM1 Low Limit */ - UNS8 jsontest_highestSubIndex_obj6400 = 2; /* number of subindex - 1*/ - subindex jsontest_Index6400[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6400, NULL }, - { RW, int32, sizeof (INTEGER32), (void*)&NARRAY_CAM1_Low_Limit[0], NULL }, - { RW|TO_BE_SAVE, int32, sizeof (INTEGER32), (void*)&NARRAY_CAM1_Low_Limit[1], NULL } - }; - -/* index 0x6402 : Mapped variable NARRAY: CAM2 Low Limit */ - UNS8 jsontest_highestSubIndex_obj6402 = 0; /* number of subindex - 1*/ - subindex jsontest_Index6402[] = - { - { RO, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6402, NULL } - }; - -/* index 0x6500 : Mapped variable NRECORD: Receive PDO 1 Parameter */ - UNS8 jsontest_highestSubIndex_obj6500 = 6; /* number of subindex - 1*/ - subindex jsontest_Index6500[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6500, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&NRECORD_Receive_PDO_1_Parameter_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&NRECORD_Receive_PDO_1_Parameter_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&NRECORD_Receive_PDO_1_Parameter_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&NRECORD_Receive_PDO_1_Parameter_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&NRECORD_Receive_PDO_1_Parameter_Event_Timer, NULL }, - { RW|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&NRECORD_Receive_PDO_1_Parameter_SYNC_start_value, NULL } - }; - -/* index 0x6502 : Mapped variable NRECORD: Receive PDO 2 Parameter */ - UNS8 jsontest_highestSubIndex_obj6502 = 0; /* number of subindex - 1*/ - subindex jsontest_Index6502[] = - { - { RO, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6502, NULL } - }; - -/* index 0x6580 : Mapped variable NRECORD: AL 1 Action */ - UNS8 jsontest_highestSubIndex_obj6580 = 6; /* number of subindex - 1*/ - subindex jsontest_Index6580[] = - { - { RO|TO_BE_SAVE, uint8, sizeof (UNS8), (void*)&jsontest_highestSubIndex_obj6580, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_1, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_2, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_3, NULL }, - { RW|TO_BE_SAVE, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_4, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_5, NULL }, - { RW|TO_BE_SAVE, uint32, sizeof (UNS32), (void*)&NRECORD_AL_1_Action_AL_1_Action_6, NULL } - }; - -/* index 0x6600 : Mapped variable Producer Heartbeat Time */ - subindex jsontest_Index6600[] = - { - { RW|TO_BE_SAVE, uint16, sizeof (UNS16), (void*)&Producer_Heartbeat_Time, NULL } - }; - -/**************************************************************************/ -/* Declaration of pointed variables */ -/**************************************************************************/ - -const indextable jsontest_objdict[] = -{ - { (subindex*)jsontest_Index1000,sizeof(jsontest_Index1000)/sizeof(jsontest_Index1000[0]), 0x1000}, - { (subindex*)jsontest_Index1001,sizeof(jsontest_Index1001)/sizeof(jsontest_Index1001[0]), 0x1001}, - { (subindex*)jsontest_Index1018,sizeof(jsontest_Index1018)/sizeof(jsontest_Index1018[0]), 0x1018}, - { (subindex*)jsontest_Index1280,sizeof(jsontest_Index1280)/sizeof(jsontest_Index1280[0]), 0x1280}, - { (subindex*)jsontest_Index1281,sizeof(jsontest_Index1281)/sizeof(jsontest_Index1281[0]), 0x1281}, - { (subindex*)jsontest_Index1282,sizeof(jsontest_Index1282)/sizeof(jsontest_Index1282[0]), 0x1282}, - { (subindex*)jsontest_Index1400,sizeof(jsontest_Index1400)/sizeof(jsontest_Index1400[0]), 0x1400}, - { (subindex*)jsontest_Index1401,sizeof(jsontest_Index1401)/sizeof(jsontest_Index1401[0]), 0x1401}, - { (subindex*)jsontest_Index1402,sizeof(jsontest_Index1402)/sizeof(jsontest_Index1402[0]), 0x1402}, - { (subindex*)jsontest_Index1600,sizeof(jsontest_Index1600)/sizeof(jsontest_Index1600[0]), 0x1600}, - { (subindex*)jsontest_Index1601,sizeof(jsontest_Index1601)/sizeof(jsontest_Index1601[0]), 0x1601}, - { (subindex*)jsontest_Index1602,sizeof(jsontest_Index1602)/sizeof(jsontest_Index1602[0]), 0x1602}, - { (subindex*)jsontest_Index1F20,sizeof(jsontest_Index1F20)/sizeof(jsontest_Index1F20[0]), 0x1F20}, - { (subindex*)jsontest_Index2000,sizeof(jsontest_Index2000)/sizeof(jsontest_Index2000[0]), 0x2000}, - { (subindex*)jsontest_Index2001,sizeof(jsontest_Index2001)/sizeof(jsontest_Index2001[0]), 0x2001}, - { (subindex*)jsontest_Index2002,sizeof(jsontest_Index2002)/sizeof(jsontest_Index2002[0]), 0x2002}, - { (subindex*)jsontest_Index6000,sizeof(jsontest_Index6000)/sizeof(jsontest_Index6000[0]), 0x6000}, - { (subindex*)jsontest_Index6100,sizeof(jsontest_Index6100)/sizeof(jsontest_Index6100[0]), 0x6100}, - { (subindex*)jsontest_Index6180,sizeof(jsontest_Index6180)/sizeof(jsontest_Index6180[0]), 0x6180}, - { (subindex*)jsontest_Index6200,sizeof(jsontest_Index6200)/sizeof(jsontest_Index6200[0]), 0x6200}, - { (subindex*)jsontest_Index6300,sizeof(jsontest_Index6300)/sizeof(jsontest_Index6300[0]), 0x6300}, - { (subindex*)jsontest_Index6302,sizeof(jsontest_Index6302)/sizeof(jsontest_Index6302[0]), 0x6302}, - { (subindex*)jsontest_Index6400,sizeof(jsontest_Index6400)/sizeof(jsontest_Index6400[0]), 0x6400}, - { (subindex*)jsontest_Index6402,sizeof(jsontest_Index6402)/sizeof(jsontest_Index6402[0]), 0x6402}, - { (subindex*)jsontest_Index6500,sizeof(jsontest_Index6500)/sizeof(jsontest_Index6500[0]), 0x6500}, - { (subindex*)jsontest_Index6502,sizeof(jsontest_Index6502)/sizeof(jsontest_Index6502[0]), 0x6502}, - { (subindex*)jsontest_Index6580,sizeof(jsontest_Index6580)/sizeof(jsontest_Index6580[0]), 0x6580}, - { (subindex*)jsontest_Index6600,sizeof(jsontest_Index6600)/sizeof(jsontest_Index6600[0]), 0x6600}, -}; - -const indextable * jsontest_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) -{ - int i; - (void)d; /* unused parameter */ - switch(wIndex){ - case 0x1000: i = 0;break; - case 0x1001: i = 1;break; - case 0x1018: i = 2;break; - case 0x1280: i = 3;break; - case 0x1281: i = 4;break; - case 0x1282: i = 5;break; - case 0x1400: i = 6;break; - case 0x1401: i = 7;break; - case 0x1402: i = 8;break; - case 0x1600: i = 9;break; - case 0x1601: i = 10;break; - case 0x1602: i = 11;break; - case 0x1F20: i = 12;break; - case 0x2000: i = 13;break; - case 0x2001: i = 14;break; - case 0x2002: i = 15;break; - case 0x6000: i = 16;break; - case 0x6100: i = 17;break; - case 0x6180: i = 18;break; - case 0x6200: i = 19;break; - case 0x6300: i = 20;break; - case 0x6302: i = 21;break; - case 0x6400: i = 22;break; - case 0x6402: i = 23;break; - case 0x6500: i = 24;break; - case 0x6502: i = 25;break; - case 0x6580: i = 26;break; - case 0x6600: i = 27;break; - default: - *errorCode = OD_NO_SUCH_OBJECT; - return NULL; - } - *errorCode = OD_SUCCESSFUL; - return &jsontest_objdict[i]; -} - -/* - * To count at which received SYNC a PDO must be sent. - * Even if no pdoTransmit are defined, at least one entry is computed - * for compilations issues. - */ -s_PDO_status jsontest_PDO_status[1] = {s_PDO_status_Initializer}; - -const quick_index jsontest_firstIndex = { - 0, /* SDO_SVR */ - 3, /* SDO_CLT */ - 6, /* PDO_RCV */ - 9, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const quick_index jsontest_lastIndex = { - 0, /* SDO_SVR */ - 5, /* SDO_CLT */ - 8, /* PDO_RCV */ - 11, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const UNS16 jsontest_ObjdictSize = sizeof(jsontest_objdict)/sizeof(jsontest_objdict[0]); - -CO_Data jsontest_Data = CANOPEN_NODE_DATA_INITIALIZER(jsontest); - diff --git a/tests/od/legacy-compare/jsontest.eds b/tests/od/legacy-compare/jsontest.eds deleted file mode 100644 index 91fbb33..0000000 --- a/tests/od/legacy-compare/jsontest.eds +++ /dev/null @@ -1,908 +0,0 @@ -[FileInfo] -FileName=jsontest.eds -FileVersion=1 -FileRevision=1 -EDSVersion=4.0 -Description=Full JSON test -CreationTime=09:46PM -CreationDate=11-11-2022 -CreatedBy=CANFestival -ModificationTime=09:46PM -ModificationDate=11-11-2022 -ModifiedBy=CANFestival - -[DeviceInfo] -VendorName=CANFestival -VendorNumber=0x00000000 -ProductName=jsontest -ProductNumber=0x00000000 -RevisionNumber=0x00000000 -BaudRate_10=1 -BaudRate_20=1 -BaudRate_50=1 -BaudRate_125=1 -BaudRate_250=1 -BaudRate_500=1 -BaudRate_800=1 -BaudRate_1000=1 -SimpleBootUpMaster=1 -SimpleBootUpSlave=0 -Granularity=8 -DynamicChannelsSupported=0 -CompactPDO=0 -GroupMessaging=0 -NrOfRXPDO=3 -NrOfTXPDO=0 -LSS_Supported=0 - -[DummyUsage] -Dummy0001=0 -Dummy0002=1 -Dummy0003=1 -Dummy0004=1 -Dummy0005=1 -Dummy0006=1 -Dummy0007=1 - -[Comments] -Lines=0 - -[MandatoryObjects] -SupportedObjects=3 -1=0x1000 -2=0x1001 -3=0x1018 - -[1000] -ParameterName=Device Type -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1001] -ParameterName=Error Register -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=0 -PDOMapping=1 - -[1018] -ParameterName=Identity -ObjectType=0x9 -SubNumber=5 - -[1018sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=4 -PDOMapping=0 - -[1018sub1] -ParameterName=Vendor ID -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub2] -ParameterName=Product Code -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub3] -ParameterName=Revision Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub4] -ParameterName=Serial Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[OptionalObjects] -SupportedObjects=23 -1=0x00A0 -2=0x1280 -3=0x1281 -4=0x1282 -5=0x1400 -6=0x1401 -7=0x1402 -8=0x1600 -9=0x1601 -10=0x1602 -11=0x1F20 -12=0x6000 -13=0x6100 -14=0x6180 -15=0x6200 -16=0x6300 -17=0x6302 -18=0x6400 -19=0x6402 -20=0x6500 -21=0x6502 -22=0x6580 -23=0x6600 - -[A0] -ParameterName=UNSIGNED32[100-200] -ObjectType=0x9 -SubNumber=4 - -[A0sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=3 -PDOMapping=0 - -[A0sub1] -ParameterName=Type -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=7 -PDOMapping=0 - -[A0sub2] -ParameterName=Minimum Value -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=100 -PDOMapping=0 - -[A0sub3] -ParameterName=Maximum Value -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=200 -PDOMapping=0 - -[1280] -ParameterName=Client SDO 1 Parameter -ObjectType=0x9 -SubNumber=4 - -[1280sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=3 -PDOMapping=0 - -[1280sub1] -ParameterName=COB ID Client to Server (Transmit SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1280sub2] -ParameterName=COB ID Server to Client (Receive SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1280sub3] -ParameterName=Node ID of the SDO Server -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1281] -ParameterName=Client SDO 2 Parameter -ObjectType=0x9 -SubNumber=4 - -[1281sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=3 -PDOMapping=0 - -[1281sub1] -ParameterName=COB ID Client to Server (Transmit SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1281sub2] -ParameterName=COB ID Server to Client (Receive SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1281sub3] -ParameterName=Node ID of the SDO Server -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1282] -ParameterName=Client SDO 3 Parameter -ObjectType=0x9 -SubNumber=4 - -[1282sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=3 -PDOMapping=0 - -[1282sub1] -ParameterName=COB ID Client to Server (Transmit SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1282sub2] -ParameterName=COB ID Server to Client (Receive SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1282sub3] -ParameterName=Node ID of the SDO Server -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400] -ParameterName=Receive PDO 1 Parameter -ObjectType=0x9 -SubNumber=6 - -[1400sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1400sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x200 -PDOMapping=0 - -[1400sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401] -ParameterName=Receive PDO 2 Parameter -ObjectType=0x9 -SubNumber=6 - -[1401sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1401sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x300 -PDOMapping=0 - -[1401sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402] -ParameterName=Receive PDO 3 Parameter -ObjectType=0x9 -SubNumber=6 - -[1402sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1402sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x400 -PDOMapping=0 - -[1402sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600] -ParameterName=Receive PDO 1 Mapping -ObjectType=0x8 -SubNumber=1 - -[1600sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601] -ParameterName=Receive PDO 2 Mapping -ObjectType=0x8 -SubNumber=1 - -[1601sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602] -ParameterName=Receive PDO 3 Mapping -ObjectType=0x8 -SubNumber=1 - -[1602sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1F20] -ParameterName=Store DCF -ObjectType=0x8 -SubNumber=3 - -[1F20sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[1F20sub1] -ParameterName=Store DCF for node 1 -ObjectType=0x7 -DataType=0x000F -AccessType=rw -DefaultValue= -PDOMapping=0 - -[1F20sub2] -ParameterName=Store DCF for node 2 -ObjectType=0x7 -DataType=0x000F -AccessType=rw -DefaultValue= -PDOMapping=0 - -[6000] -ParameterName=Global Interrupt Enable Digital Sure -ObjectType=0x7 -DataType=0x0001 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[6100] -ParameterName=RECORD: Software position limit -ObjectType=0x9 -SubNumber=3 - -[6100sub0] -ParameterName=Number of things -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[6100sub1] -ParameterName=Minimal position limit -ObjectType=0x7 -DataType=0x0004 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[6100sub2] -ParameterName=Maximal position limit -ObjectType=0x7 -DataType=0x0004 -AccessType=rw -DefaultValue=2 -PDOMapping=0 - -[6180] -ParameterName=RECORD: AL Action -ObjectType=0x9 -SubNumber=7 - -[6180sub0] -ParameterName=Number of subs -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[6180sub1] -ParameterName=AL 1 Action 1 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[6180sub2] -ParameterName=AL 1 Action 2 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=2 -PDOMapping=0 - -[6180sub3] -ParameterName=AL 1 Action 3 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=3 -PDOMapping=0 - -[6180sub4] -ParameterName=AL 1 Action 4 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=4 -PDOMapping=0 - -[6180sub5] -ParameterName=AL 1 Action 5 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=5 -PDOMapping=0 - -[6180sub6] -ParameterName=AL 1 Action 6 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=6 -PDOMapping=0 - -[6200] -ParameterName=ARRAY: Acceleration Value -ObjectType=0x8 -SubNumber=3 - -[6200sub0] -ParameterName=Number of Available Channels -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[6200sub1] -ParameterName=Acceleration Value Channel 1 -ObjectType=0x7 -DataType=0x0003 -AccessType=ro -DefaultValue=1 -PDOMapping=1 - -[6200sub2] -ParameterName=Acceleration Value Channel 2 -ObjectType=0x7 -DataType=0x0003 -AccessType=ro -DefaultValue=16 -PDOMapping=1 - -[6300] -ParameterName=Device Type 1 and 0 -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=1 -PDOMapping=1 - -[6302] -ParameterName=Device Type 2 and 0 -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=12 -PDOMapping=1 - -[6400] -ParameterName=NARRAY: CAM1 Low Limit -ObjectType=0x8 -SubNumber=3 - -[6400sub0] -ParameterName=Number of Available Channels -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[6400sub1] -ParameterName=CAM1 Low Limit Channel 1 -ObjectType=0x7 -DataType=0x0004 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[6400sub2] -ParameterName=CAM1 Low Limit Channel 2 -ObjectType=0x7 -DataType=0x0004 -AccessType=rw -DefaultValue=2 -PDOMapping=0 - -[6402] -ParameterName=NARRAY: CAM2 Low Limit -ObjectType=0x8 -SubNumber=1 - -[6402sub0] -ParameterName=Number of Available Channels -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[6500] -ParameterName=NRECORD: Receive PDO 1 Parameter -ObjectType=0x9 -SubNumber=6 - -[6500sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[6500sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[6500sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=2 -PDOMapping=0 - -[6500sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=3 -PDOMapping=0 - -[6500sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=5 -PDOMapping=0 - -[6500sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=6 -PDOMapping=0 - -[6502] -ParameterName=NRECORD: Receive PDO 2 Parameter -ObjectType=0x9 -SubNumber=1 - -[6502sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[6580] -ParameterName=NRECORD: AL 1 Action -ObjectType=0x9 -SubNumber=7 - -[6580sub0] -ParameterName=Number of Actions -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[6580sub1] -ParameterName=AL 1 Action 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[6580sub2] -ParameterName=AL 1 Action 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=2 -PDOMapping=0 - -[6580sub3] -ParameterName=AL 1 Action 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=3 -PDOMapping=0 - -[6580sub4] -ParameterName=AL 1 Action 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=4 -PDOMapping=0 - -[6580sub5] -ParameterName=AL 1 Action 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=5 -PDOMapping=0 - -[6580sub6] -ParameterName=AL 1 Action 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=6 -PDOMapping=0 - -[6600] -ParameterName=Producer Heartbeat Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=1 -PDOMapping=0 - -[ManufacturerObjects] -SupportedObjects=3 -1=0x2000 -2=0x2001 -3=0x2002 - -[2000] -ParameterName=VAR -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=1 - -[2001] -ParameterName=ARRAY -ObjectType=0x8 -SubNumber=3 - -[2001sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[2001sub1] -ParameterName=ARRAY 1 -ObjectType=0x7 -DataType=0x0002 -AccessType=ro -DefaultValue=1 -PDOMapping=1 - -[2001sub2] -ParameterName=ARRAY 2 -ObjectType=0x7 -DataType=0x0002 -AccessType=ro -DefaultValue=2 -PDOMapping=1 - -[2002] -ParameterName=RECORD -ObjectType=0x9 -SubNumber=3 - -[2002sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[2002sub1] -ParameterName=RECORD 1 -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=7 -PDOMapping=1 - -[2002sub2] -ParameterName=RECORD 2 -ObjectType=0x7 -DataType=0x0003 -AccessType=rw -DefaultValue=42 -PDOMapping=1 diff --git a/tests/od/legacy-compare/jsontest.h b/tests/od/legacy-compare/jsontest.h deleted file mode 100644 index 59e0eed..0000000 --- a/tests/od/legacy-compare/jsontest.h +++ /dev/null @@ -1,47 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef JSONTEST_H -#define JSONTEST_H - -#include "data.h" - -/* Prototypes of function provided by object dictionnary */ -UNS32 jsontest_valueRangeTest (UNS8 typeValue, void * value); -const indextable * jsontest_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); - -/* Master node data struct */ -extern CO_Data jsontest_Data; -extern UNS8 VAR; /* Mapped at index 0x2000, subindex 0x00*/ -extern INTEGER8 ARRAY[2]; /* Mapped at index 0x2001, subindex 0x01 - 0x02 */ -extern UNS8 RECORD_RECORD_1; /* Mapped at index 0x2002, subindex 0x01 */ -extern INTEGER16 RECORD_RECORD_2; /* Mapped at index 0x2002, subindex 0x02 */ -extern UNS8 Global_Interrupt_Enable_Digital_Sure; /* Mapped at index 0x6000, subindex 0x00*/ -extern INTEGER32 RECORD_Software_position_limit_Minimal_position_limit; /* Mapped at index 0x6100, subindex 0x01 */ -extern INTEGER32 RECORD_Software_position_limit_Maximal_position_limit; /* Mapped at index 0x6100, subindex 0x02 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_1; /* Mapped at index 0x6180, subindex 0x01 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_2; /* Mapped at index 0x6180, subindex 0x02 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_3; /* Mapped at index 0x6180, subindex 0x03 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_4; /* Mapped at index 0x6180, subindex 0x04 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_5; /* Mapped at index 0x6180, subindex 0x05 */ -extern INTEGER16 RECORD_AL_Action_AL_1_Action_6; /* Mapped at index 0x6180, subindex 0x06 */ -extern INTEGER16 ARRAY_Acceleration_Value[2]; /* Mapped at index 0x6200, subindex 0x01 - 0x02 */ -extern UNS32 Device_Type_1_and_0; /* Mapped at index 0x6300, subindex 0x00*/ -extern UNS32 Device_Type_2_and_0; /* Mapped at index 0x6302, subindex 0x00*/ -extern INTEGER32 NARRAY_CAM1_Low_Limit[2]; /* Mapped at index 0x6400, subindex 0x01 - 0x02 */ -extern INTEGER32 NARRAY_CAM2_Low_Limit[0]; /* Mapped at index 0x6402, subindex 0x01 - 0x00 */ -extern UNS32 NRECORD_Receive_PDO_1_Parameter_COB_ID_used_by_PDO; /* Mapped at index 0x6500, subindex 0x01 */ -extern UNS8 NRECORD_Receive_PDO_1_Parameter_Transmission_Type; /* Mapped at index 0x6500, subindex 0x02 */ -extern UNS16 NRECORD_Receive_PDO_1_Parameter_Inhibit_Time; /* Mapped at index 0x6500, subindex 0x03 */ -extern UNS8 NRECORD_Receive_PDO_1_Parameter_Compatibility_Entry; /* Mapped at index 0x6500, subindex 0x04 */ -extern UNS16 NRECORD_Receive_PDO_1_Parameter_Event_Timer; /* Mapped at index 0x6500, subindex 0x05 */ -extern UNS8 NRECORD_Receive_PDO_1_Parameter_SYNC_start_value; /* Mapped at index 0x6500, subindex 0x06 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_1; /* Mapped at index 0x6580, subindex 0x01 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_2; /* Mapped at index 0x6580, subindex 0x02 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_3; /* Mapped at index 0x6580, subindex 0x03 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_4; /* Mapped at index 0x6580, subindex 0x04 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_5; /* Mapped at index 0x6580, subindex 0x05 */ -extern UNS32 NRECORD_AL_1_Action_AL_1_Action_6; /* Mapped at index 0x6580, subindex 0x06 */ -extern UNS16 Producer_Heartbeat_Time; /* Mapped at index 0x6600, subindex 0x00*/ - -#endif // JSONTEST_H diff --git a/tests/od/legacy-compare/jsontest_objectdefines.h b/tests/od/legacy-compare/jsontest_objectdefines.h deleted file mode 100644 index e81b2e3..0000000 --- a/tests/od/legacy-compare/jsontest_objectdefines.h +++ /dev/null @@ -1,161 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef JSONTEST_OBJECTDEFINES_H -#define JSONTEST_OBJECTDEFINES_H - -/* - Object defines naming convention: - General: - * All characters in object names that does not match [a-zA-Z0-9_] will be replaced by '_'. - * Case of object dictionary names will be kept as is. - Index : Node object dictionary name +_+ index name +_+ Idx - SubIndex : Node object dictionary name +_+ index name +_+ subIndex name +_+ sIdx -*/ - -#define jsontest_Device_Type_Idx 0x1000 -#define jsontest_Device_Type_Device_Type_sIdx 0x00 /* Device type */ - -#define jsontest_Error_Register_Idx 0x1001 -#define jsontest_Error_Register_Error_Register_sIdx 0x00 /* Err register */ - -#define jsontest_Identity_Idx 0x1018 -#define jsontest_Identity_Number_of_Entries_sIdx 0x00 /* R0 */ -#define jsontest_Identity_Vendor_ID_sIdx 0x01 /* R1 */ -#define jsontest_Identity_Product_Code_sIdx 0x02 /* R2 */ -#define jsontest_Identity_Revision_Number_sIdx 0x03 /* R3 */ -#define jsontest_Identity_Serial_Number_sIdx 0x04 /* R4 */ - -#define jsontest_Client_SDO_1_Parameter_Idx 0x1280 -#define jsontest_Client_SDO_1_Parameter_Number_of_Entries_sIdx 0x00 /* SDO0 */ -#define jsontest_Client_SDO_1_Parameter_COB_ID_Client_to_Server__Transmit_SDO__sIdx 0x01 /* SDO1 */ -#define jsontest_Client_SDO_1_Parameter_COB_ID_Server_to_Client__Receive_SDO__sIdx 0x02 /* SDO2 */ -#define jsontest_Client_SDO_1_Parameter_Node_ID_of_the_SDO_Server_sIdx 0x03 /* SDO3 */ - -#define jsontest_Client_SDO_2_Parameter_Idx 0x1281 -#define jsontest_Client_SDO_2_Parameter_Number_of_Entries_sIdx 0x00 /* client0 */ -#define jsontest_Client_SDO_2_Parameter_COB_ID_Client_to_Server__Transmit_SDO__sIdx 0x01 /* client1 */ -#define jsontest_Client_SDO_2_Parameter_COB_ID_Server_to_Client__Receive_SDO__sIdx 0x02 /* client2 */ -#define jsontest_Client_SDO_2_Parameter_Node_ID_of_the_SDO_Server_sIdx 0x03 /* client3 */ - -#define jsontest_Client_SDO_3_Parameter_Idx 0x1282 -#define jsontest_Client_SDO_3_Parameter_Number_of_Entries_sIdx 0x00 -#define jsontest_Client_SDO_3_Parameter_COB_ID_Client_to_Server__Transmit_SDO__sIdx 0x01 -#define jsontest_Client_SDO_3_Parameter_COB_ID_Server_to_Client__Receive_SDO__sIdx 0x02 -#define jsontest_Client_SDO_3_Parameter_Node_ID_of_the_SDO_Server_sIdx 0x03 - -#define jsontest_Receive_PDO_1_Parameter_Idx 0x1400 -#define jsontest_Receive_PDO_1_Parameter_Highest_SubIndex_Supported_sIdx 0x00 /* rpdo0 */ -#define jsontest_Receive_PDO_1_Parameter_COB_ID_used_by_PDO_sIdx 0x01 /* rpdo1 */ -#define jsontest_Receive_PDO_1_Parameter_Transmission_Type_sIdx 0x02 /* rpdo2 */ -#define jsontest_Receive_PDO_1_Parameter_Inhibit_Time_sIdx 0x03 /* rpdo3 */ -#define jsontest_Receive_PDO_1_Parameter_Compatibility_Entry_sIdx 0x04 /* rpdo4 */ -#define jsontest_Receive_PDO_1_Parameter_Event_Timer_sIdx 0x05 /* rpdo5 */ -#define jsontest_Receive_PDO_1_Parameter_SYNC_start_value_sIdx 0x06 /* rpdo6 */ - -#define jsontest_Receive_PDO_2_Parameter_Idx 0x1401 -#define jsontest_Receive_PDO_2_Parameter_Highest_SubIndex_Supported_sIdx 0x00 /* c0 */ -#define jsontest_Receive_PDO_2_Parameter_COB_ID_used_by_PDO_sIdx 0x01 /* c1 */ -#define jsontest_Receive_PDO_2_Parameter_Transmission_Type_sIdx 0x02 /* c2 */ -#define jsontest_Receive_PDO_2_Parameter_Inhibit_Time_sIdx 0x03 /* c3 */ -#define jsontest_Receive_PDO_2_Parameter_Compatibility_Entry_sIdx 0x04 /* c4 */ -#define jsontest_Receive_PDO_2_Parameter_Event_Timer_sIdx 0x05 /* c5 */ -#define jsontest_Receive_PDO_2_Parameter_SYNC_start_value_sIdx 0x06 /* c6 */ - -#define jsontest_Receive_PDO_3_Parameter_Idx 0x1402 -#define jsontest_Receive_PDO_3_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define jsontest_Receive_PDO_3_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define jsontest_Receive_PDO_3_Parameter_Transmission_Type_sIdx 0x02 -#define jsontest_Receive_PDO_3_Parameter_Inhibit_Time_sIdx 0x03 -#define jsontest_Receive_PDO_3_Parameter_Compatibility_Entry_sIdx 0x04 -#define jsontest_Receive_PDO_3_Parameter_Event_Timer_sIdx 0x05 -#define jsontest_Receive_PDO_3_Parameter_SYNC_start_value_sIdx 0x06 - -#define jsontest_Receive_PDO_1_Mapping_Idx 0x1600 -#define jsontest_Receive_PDO_1_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_Receive_PDO_2_Mapping_Idx 0x1601 -#define jsontest_Receive_PDO_2_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_Receive_PDO_3_Mapping_Idx 0x1602 -#define jsontest_Receive_PDO_3_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_Store_DCF_Idx 0x1f20 -#define jsontest_Store_DCF_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_VAR_Idx 0x2000 -#define jsontest_VAR_VAR_sIdx 0x00 /* VAR */ - -#define jsontest_ARRAY_Idx 0x2001 -#define jsontest_ARRAY_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_RECORD_Idx 0x2002 -#define jsontest_RECORD_Number_of_Entries_sIdx 0x00 /* R0 */ -#define jsontest_RECORD_RECORD_1_sIdx 0x01 /* R1 */ -#define jsontest_RECORD_RECORD_2_sIdx 0x02 /* R2 */ - -#define jsontest_VAR__Global_Interrupt_Enable_Digital_Idx 0x6000 -#define jsontest_VAR__Global_Interrupt_Enable_Digital_Global_Interrupt_Enable_Digital_Sure_sIdx 0x00 /* Nope */ - -#define jsontest_RECORD__Software_position_limit_Idx 0x6100 -#define jsontest_RECORD__Software_position_limit_Number_of_things_sIdx 0x00 /* Rec0 */ -#define jsontest_RECORD__Software_position_limit_Minimal_position_limit_sIdx 0x01 /* Rec1 */ -#define jsontest_RECORD__Software_position_limit_Maximal_position_limit_sIdx 0x02 /* Rec2 */ - -#define jsontest_RECORD__AL_Action_Idx 0x6180 -#define jsontest_RECORD__AL_Action_Number_of_subs_sIdx 0x00 /* r0 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_1_sIdx 0x01 /* r1 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_2_sIdx 0x02 /* r2 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_3_sIdx 0x03 /* r3 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_4_sIdx 0x04 /* r4 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_5_sIdx 0x05 /* r5 */ -#define jsontest_RECORD__AL_Action_AL_1_Action_6_sIdx 0x06 /* r6 */ - -#define jsontest_ARRAY__Acceleration_Value_Idx 0x6200 -#define jsontest_ARRAY__Acceleration_Value_Number_of_Available_Channels_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_NVAR__Test_profile_1_Idx 0x6300 -#define jsontest_NVAR__Test_profile_1_Device_Type_1_and_0_sIdx 0x00 /* dt10 */ - -#define jsontest_NVAR__Test_profile_2_Idx 0x6302 -#define jsontest_NVAR__Test_profile_2_Device_Type_2_and_0_sIdx 0x00 - -#define jsontest_NARRAY__CAM1_Low_Limit_Idx 0x6400 -#define jsontest_NARRAY__CAM1_Low_Limit_Number_of_Available_Channels_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_NARRAY__CAM2_Low_Limit_Idx 0x6402 -#define jsontest_NARRAY__CAM2_Low_Limit_Number_of_Available_Channels_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Idx 0x6500 -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Highest_SubIndex_Supported_sIdx 0x00 /* nr0 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_COB_ID_used_by_PDO_sIdx 0x01 /* nr1 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Transmission_Type_sIdx 0x02 /* nr2 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Inhibit_Time_sIdx 0x03 /* nr3 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Compatibility_Entry_sIdx 0x04 /* nr4 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_Event_Timer_sIdx 0x05 /* nr5 */ -#define jsontest_NRECORD__Receive_PDO_1_Parameter_SYNC_start_value_sIdx 0x06 /* nr6 */ - -#define jsontest_NRECORD__Receive_PDO_2_Parameter_Idx 0x6502 -#define jsontest_NRECORD__Receive_PDO_2_Parameter_Highest_SubIndex_Supported_sIdx 0x00 - -#define jsontest_NRECORD__AL_1_Action_Idx 0x6580 -#define jsontest_NRECORD__AL_1_Action_Number_of_Actions_sIdx 0x00 /* com0 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_1_sIdx 0x01 /* com1 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_2_sIdx 0x02 /* com2 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_3_sIdx 0x03 /* com3 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_4_sIdx 0x04 /* com4 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_5_sIdx 0x05 /* com5 */ -#define jsontest_NRECORD__AL_1_Action_AL_1_Action_6_sIdx 0x06 /* com6 */ - -#define jsontest_Producer_Heartbeat_Time_Idx 0x6600 -#define jsontest_Producer_Heartbeat_Time_Producer_Heartbeat_Time_sIdx 0x00 /* Comment for it */ - -#endif /* JSONTEST_OBJECTDEFINES_H */ diff --git a/tests/od/legacy-compare/master.c b/tests/od/legacy-compare/master.c deleted file mode 100644 index dbe583d..0000000 --- a/tests/od/legacy-compare/master.c +++ /dev/null @@ -1,164 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#include "master.h" - -/**************************************************************************/ -/* Declaration of mapped variables */ -/**************************************************************************/ - -/**************************************************************************/ -/* Declaration of value range types */ -/**************************************************************************/ - -#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */ -UNS32 Master_valueRangeTest (UNS8 typeValue, void * value) -{ - switch (typeValue) { - case valueRange_EMC: - if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break; - } - return 0; -} - -/**************************************************************************/ -/* The node id */ -/**************************************************************************/ -/* node_id default value.*/ -UNS8 Master_bDeviceNodeId = 0x00; - -/**************************************************************************/ -/* Array of message processing information */ - -const UNS8 Master_iam_a_slave = 0; - -TIMER_HANDLE Master_heartBeatTimers[1]; - -/* -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - - OBJECT DICTIONARY - -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -*/ - -/* index 0x1000 : Device Type. */ - UNS32 Master_obj1000 = 0x0; /* 0 */ - subindex Master_Index1000[] = - { - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1000, NULL } - }; - -/* index 0x1001 : Error Register. */ - UNS8 Master_obj1001 = 0x0; /* 0 */ - subindex Master_Index1001[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Master_obj1001, NULL } - }; - -/* index 0x1003 : Pre-defined Error Field */ - UNS8 Master_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ - UNS32 Master_obj1003[] = - { - 0x0 /* 0 */ - }; - subindex Master_Index1003[] = - { - { RW, valueRange_EMC, sizeof (UNS8), (void*)&Master_highestSubIndex_obj1003, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1003[0], NULL } - }; - -/* index 0x1005 : SYNC COB ID */ - UNS32 Master_obj1005 = 0x0; /* 0 */ - -/* index 0x1006 : Communication / Cycle Period */ - UNS32 Master_obj1006 = 0x0; /* 0 */ - -/* index 0x100C : Guard Time */ - UNS16 Master_obj100C = 0x0; /* 0 */ - -/* index 0x100D : Life Time Factor */ - UNS8 Master_obj100D = 0x0; /* 0 */ - -/* index 0x1014 : Emergency COB ID */ - UNS32 Master_obj1014 = 0x80 + 0x00; /* 128 + NodeID */ - -/* index 0x1016 : Consumer Heartbeat Time */ - UNS8 Master_highestSubIndex_obj1016 = 0; - UNS32 Master_obj1016[]={0}; - -/* index 0x1017 : Producer Heartbeat Time */ - UNS16 Master_obj1017 = 0x0; /* 0 */ - -/* index 0x1018 : Identity. */ - UNS8 Master_highestSubIndex_obj1018 = 4; /* number of subindex - 1*/ - UNS32 Master_obj1018_Vendor_ID = 0x0; /* 0 */ - UNS32 Master_obj1018_Product_Code = 0x0; /* 0 */ - UNS32 Master_obj1018_Revision_Number = 0x0; /* 0 */ - UNS32 Master_obj1018_Serial_Number = 0x0; /* 0 */ - subindex Master_Index1018[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Master_highestSubIndex_obj1018, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1018_Vendor_ID, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1018_Product_Code, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1018_Revision_Number, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Master_obj1018_Serial_Number, NULL } - }; - -/**************************************************************************/ -/* Declaration of pointed variables */ -/**************************************************************************/ - -const indextable Master_objdict[] = -{ - { (subindex*)Master_Index1000,sizeof(Master_Index1000)/sizeof(Master_Index1000[0]), 0x1000}, - { (subindex*)Master_Index1001,sizeof(Master_Index1001)/sizeof(Master_Index1001[0]), 0x1001}, - { (subindex*)Master_Index1018,sizeof(Master_Index1018)/sizeof(Master_Index1018[0]), 0x1018}, -}; - -const indextable * Master_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) -{ - int i; - (void)d; /* unused parameter */ - switch(wIndex){ - case 0x1000: i = 0;break; - case 0x1001: i = 1;break; - case 0x1018: i = 2;break; - default: - *errorCode = OD_NO_SUCH_OBJECT; - return NULL; - } - *errorCode = OD_SUCCESSFUL; - return &Master_objdict[i]; -} - -/* - * To count at which received SYNC a PDO must be sent. - * Even if no pdoTransmit are defined, at least one entry is computed - * for compilations issues. - */ -s_PDO_status Master_PDO_status[1] = {s_PDO_status_Initializer}; - -const quick_index Master_firstIndex = { - 0, /* SDO_SVR */ - 0, /* SDO_CLT */ - 0, /* PDO_RCV */ - 0, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const quick_index Master_lastIndex = { - 0, /* SDO_SVR */ - 0, /* SDO_CLT */ - 0, /* PDO_RCV */ - 0, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const UNS16 Master_ObjdictSize = sizeof(Master_objdict)/sizeof(Master_objdict[0]); - -CO_Data Master_Data = CANOPEN_NODE_DATA_INITIALIZER(Master); - diff --git a/tests/od/legacy-compare/master.eds b/tests/od/legacy-compare/master.eds deleted file mode 100644 index e19157f..0000000 --- a/tests/od/legacy-compare/master.eds +++ /dev/null @@ -1,121 +0,0 @@ -[FileInfo] -FileName=master.eds -FileVersion=1 -FileRevision=1 -EDSVersion=4.0 -Description=Master created with objdictedit -CreationTime=09:46PM -CreationDate=11-11-2022 -CreatedBy=CANFestival -ModificationTime=09:46PM -ModificationDate=11-11-2022 -ModifiedBy=CANFestival - -[DeviceInfo] -VendorName=CANFestival -VendorNumber=0x00000000 -ProductName=Master -ProductNumber=0x00000000 -RevisionNumber=0x00000000 -BaudRate_10=1 -BaudRate_20=1 -BaudRate_50=1 -BaudRate_125=1 -BaudRate_250=1 -BaudRate_500=1 -BaudRate_800=1 -BaudRate_1000=1 -SimpleBootUpMaster=1 -SimpleBootUpSlave=0 -Granularity=8 -DynamicChannelsSupported=0 -CompactPDO=0 -GroupMessaging=0 -NrOfRXPDO=0 -NrOfTXPDO=0 -LSS_Supported=0 - -[DummyUsage] -Dummy0001=0 -Dummy0002=1 -Dummy0003=1 -Dummy0004=1 -Dummy0005=1 -Dummy0006=1 -Dummy0007=1 - -[Comments] -Lines=0 - -[MandatoryObjects] -SupportedObjects=3 -1=0x1000 -2=0x1001 -3=0x1018 - -[1000] -ParameterName=Device Type -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1001] -ParameterName=Error Register -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=0 -PDOMapping=1 - -[1018] -ParameterName=Identity -ObjectType=0x9 -SubNumber=5 - -[1018sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=4 -PDOMapping=0 - -[1018sub1] -ParameterName=Vendor ID -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub2] -ParameterName=Product Code -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub3] -ParameterName=Revision Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub4] -ParameterName=Serial Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[OptionalObjects] -SupportedObjects=0 - -[ManufacturerObjects] -SupportedObjects=0 diff --git a/tests/od/legacy-compare/master.h b/tests/od/legacy-compare/master.h deleted file mode 100644 index eb1e1a8..0000000 --- a/tests/od/legacy-compare/master.h +++ /dev/null @@ -1,16 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef MASTER_H -#define MASTER_H - -#include "data.h" - -/* Prototypes of function provided by object dictionnary */ -UNS32 Master_valueRangeTest (UNS8 typeValue, void * value); -const indextable * Master_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); - -/* Master node data struct */ -extern CO_Data Master_Data; - -#endif // MASTER_H diff --git a/tests/od/legacy-compare/master.json b/tests/od/legacy-compare/master.json deleted file mode 100644 index 5834e0d..0000000 --- a/tests/od/legacy-compare/master.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "$id": "od data", - "$version": "1", - "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-11-11T21:47:10.129424", - "name": "Master", - "description": "Master created with objdictedit", - "type": "master", - "id": 0, - "profile": "None", - "dictionary": [ - { - "index": "0x1000", // 4096 - "name": "Device Type", - "struct": "var", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Device Type", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - }, - { - "index": "0x1001", // 4097 - "name": "Error Register", - "struct": "var", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Error Register", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": true, - "value": 0 - } - ] - }, - { - "index": "0x1018", // 4120 - "name": "Identity", - "struct": "record", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Vendor ID", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Product Code", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Revision Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Serial Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - } - ] -} \ No newline at end of file diff --git a/tests/od/legacy-compare/master.od b/tests/od/legacy-compare/master.od deleted file mode 100644 index bca5044..0000000 --- a/tests/od/legacy-compare/master.od +++ /dev/null @@ -1,38 +0,0 @@ - - - - - -Master created with objdictedit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Master - diff --git a/tests/od/legacy-compare/master_objectdefines.h b/tests/od/legacy-compare/master_objectdefines.h deleted file mode 100644 index 3942965..0000000 --- a/tests/od/legacy-compare/master_objectdefines.h +++ /dev/null @@ -1,29 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef MASTER_OBJECTDEFINES_H -#define MASTER_OBJECTDEFINES_H - -/* - Object defines naming convention: - General: - * All characters in object names that does not match [a-zA-Z0-9_] will be replaced by '_'. - * Case of object dictionary names will be kept as is. - Index : Node object dictionary name +_+ index name +_+ Idx - SubIndex : Node object dictionary name +_+ index name +_+ subIndex name +_+ sIdx -*/ - -#define Master_Device_Type_Idx 0x1000 -#define Master_Device_Type_Device_Type_sIdx 0x00 - -#define Master_Error_Register_Idx 0x1001 -#define Master_Error_Register_Error_Register_sIdx 0x00 - -#define Master_Identity_Idx 0x1018 -#define Master_Identity_Number_of_Entries_sIdx 0x00 -#define Master_Identity_Vendor_ID_sIdx 0x01 -#define Master_Identity_Product_Code_sIdx 0x02 -#define Master_Identity_Revision_Number_sIdx 0x03 -#define Master_Identity_Serial_Number_sIdx 0x04 - -#endif /* MASTER_OBJECTDEFINES_H */ diff --git a/tests/od/legacy-compare/minimal.c b/tests/od/legacy-compare/minimal.c deleted file mode 100644 index 21afe9a..0000000 --- a/tests/od/legacy-compare/minimal.c +++ /dev/null @@ -1,155 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#include "minimal.h" - -/**************************************************************************/ -/* Declaration of mapped variables */ -/**************************************************************************/ - -/**************************************************************************/ -/* Declaration of value range types */ -/**************************************************************************/ - -#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */ -UNS32 Null_valueRangeTest (UNS8 typeValue, void * value) -{ - switch (typeValue) { - case valueRange_EMC: - if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break; - } - return 0; -} - -/**************************************************************************/ -/* The node id */ -/**************************************************************************/ -/* node_id default value.*/ -UNS8 Null_bDeviceNodeId = 0x00; - -/**************************************************************************/ -/* Array of message processing information */ - -const UNS8 Null_iam_a_slave = 0; - -TIMER_HANDLE Null_heartBeatTimers[1]; - -/* -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - - OBJECT DICTIONARY - -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -*/ - -/* index 0x1000 : Device Type. */ - UNS32 Null_obj1000 = 0x0; /* 0 */ - subindex Null_Index1000[] = - { - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1000, NULL } - }; - -/* index 0x1003 : Pre-defined Error Field */ - UNS8 Null_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ - UNS32 Null_obj1003[] = - { - 0x0 /* 0 */ - }; - subindex Null_Index1003[] = - { - { RW, valueRange_EMC, sizeof (UNS8), (void*)&Null_highestSubIndex_obj1003, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1003[0], NULL } - }; - -/* index 0x1005 : SYNC COB ID */ - UNS32 Null_obj1005 = 0x0; /* 0 */ - -/* index 0x1006 : Communication / Cycle Period */ - UNS32 Null_obj1006 = 0x0; /* 0 */ - -/* index 0x100C : Guard Time */ - UNS16 Null_obj100C = 0x0; /* 0 */ - -/* index 0x100D : Life Time Factor */ - UNS8 Null_obj100D = 0x0; /* 0 */ - -/* index 0x1014 : Emergency COB ID */ - UNS32 Null_obj1014 = 0x80 + 0x00; /* 128 + NodeID */ - -/* index 0x1016 : Consumer Heartbeat Time */ - UNS8 Null_highestSubIndex_obj1016 = 0; - UNS32 Null_obj1016[]={0}; - -/* index 0x1017 : Producer Heartbeat Time */ - UNS16 Null_obj1017 = 0x0; /* 0 */ - -/* index 0x1018 : Identity. */ - UNS8 Null_highestSubIndex_obj1018 = 4; /* number of subindex - 1*/ - UNS32 Null_obj1018_Vendor_ID = 0x0; /* 0 */ - UNS32 Null_obj1018_Product_Code = 0x0; /* 0 */ - UNS32 Null_obj1018_Revision_Number = 0x0; /* 0 */ - UNS32 Null_obj1018_Serial_Number = 0x0; /* 0 */ - subindex Null_Index1018[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Null_highestSubIndex_obj1018, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1018_Vendor_ID, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1018_Product_Code, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1018_Revision_Number, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Null_obj1018_Serial_Number, NULL } - }; - -/**************************************************************************/ -/* Declaration of pointed variables */ -/**************************************************************************/ - -const indextable Null_objdict[] = -{ - { (subindex*)Null_Index1000,sizeof(Null_Index1000)/sizeof(Null_Index1000[0]), 0x1000}, - { (subindex*)Null_Index1018,sizeof(Null_Index1018)/sizeof(Null_Index1018[0]), 0x1018}, -}; - -const indextable * Null_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) -{ - int i; - (void)d; /* unused parameter */ - switch(wIndex){ - case 0x1000: i = 0;break; - case 0x1018: i = 1;break; - default: - *errorCode = OD_NO_SUCH_OBJECT; - return NULL; - } - *errorCode = OD_SUCCESSFUL; - return &Null_objdict[i]; -} - -/* - * To count at which received SYNC a PDO must be sent. - * Even if no pdoTransmit are defined, at least one entry is computed - * for compilations issues. - */ -s_PDO_status Null_PDO_status[1] = {s_PDO_status_Initializer}; - -const quick_index Null_firstIndex = { - 0, /* SDO_SVR */ - 0, /* SDO_CLT */ - 0, /* PDO_RCV */ - 0, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const quick_index Null_lastIndex = { - 0, /* SDO_SVR */ - 0, /* SDO_CLT */ - 0, /* PDO_RCV */ - 0, /* PDO_RCV_MAP */ - 0, /* PDO_TRS */ - 0 /* PDO_TRS_MAP */ -}; - -const UNS16 Null_ObjdictSize = sizeof(Null_objdict)/sizeof(Null_objdict[0]); - -CO_Data Null_Data = CANOPEN_NODE_DATA_INITIALIZER(Null); - diff --git a/tests/od/legacy-compare/minimal.eds b/tests/od/legacy-compare/minimal.eds deleted file mode 100644 index 9c94df6..0000000 --- a/tests/od/legacy-compare/minimal.eds +++ /dev/null @@ -1,112 +0,0 @@ -[FileInfo] -FileName=minimal.eds -FileVersion=1 -FileRevision=1 -EDSVersion=4.0 -Description= -CreationTime=09:46PM -CreationDate=11-11-2022 -CreatedBy=CANFestival -ModificationTime=09:46PM -ModificationDate=11-11-2022 -ModifiedBy=CANFestival - -[DeviceInfo] -VendorName=CANFestival -VendorNumber=0x00000000 -ProductName=Null -ProductNumber=0x00000000 -RevisionNumber=0x00000000 -BaudRate_10=1 -BaudRate_20=1 -BaudRate_50=1 -BaudRate_125=1 -BaudRate_250=1 -BaudRate_500=1 -BaudRate_800=1 -BaudRate_1000=1 -SimpleBootUpMaster=1 -SimpleBootUpSlave=0 -Granularity=8 -DynamicChannelsSupported=0 -CompactPDO=0 -GroupMessaging=0 -NrOfRXPDO=0 -NrOfTXPDO=0 -LSS_Supported=0 - -[DummyUsage] -Dummy0001=0 -Dummy0002=1 -Dummy0003=1 -Dummy0004=1 -Dummy0005=1 -Dummy0006=1 -Dummy0007=1 - -[Comments] -Lines=0 - -[MandatoryObjects] -SupportedObjects=2 -1=0x1000 -2=0x1018 - -[1000] -ParameterName=Device Type -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018] -ParameterName=Identity -ObjectType=0x9 -SubNumber=5 - -[1018sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=4 -PDOMapping=0 - -[1018sub1] -ParameterName=Vendor ID -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub2] -ParameterName=Product Code -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub3] -ParameterName=Revision Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub4] -ParameterName=Serial Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[OptionalObjects] -SupportedObjects=0 - -[ManufacturerObjects] -SupportedObjects=0 diff --git a/tests/od/legacy-compare/minimal.h b/tests/od/legacy-compare/minimal.h deleted file mode 100644 index 6b99194..0000000 --- a/tests/od/legacy-compare/minimal.h +++ /dev/null @@ -1,16 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef MINIMAL_H -#define MINIMAL_H - -#include "data.h" - -/* Prototypes of function provided by object dictionnary */ -UNS32 Null_valueRangeTest (UNS8 typeValue, void * value); -const indextable * Null_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); - -/* Master node data struct */ -extern CO_Data Null_Data; - -#endif // MINIMAL_H diff --git a/tests/od/legacy-compare/minimal.json b/tests/od/legacy-compare/minimal.json deleted file mode 100644 index 29f9a4f..0000000 --- a/tests/od/legacy-compare/minimal.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$id": "od data", - "$version": "1", - "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-11-11T21:47:10.131425", - "name": "Null", - "description": "", - "type": "master", - "id": 0, - "profile": "None", - "dictionary": [ - { - "index": "0x1000", // 4096 - "name": "Device Type", - "struct": "var", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Device Type", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - }, - { - "index": "0x1018", // 4120 - "name": "Identity", - "struct": "record", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Vendor ID", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Product Code", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Revision Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Serial Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - } - ] -} \ No newline at end of file diff --git a/tests/od/legacy-compare/minimal.od b/tests/od/legacy-compare/minimal.od deleted file mode 100644 index ad995c8..0000000 --- a/tests/od/legacy-compare/minimal.od +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -None - - -master - -Null - diff --git a/tests/od/legacy-compare/minimal_objectdefines.h b/tests/od/legacy-compare/minimal_objectdefines.h deleted file mode 100644 index 7b71fa4..0000000 --- a/tests/od/legacy-compare/minimal_objectdefines.h +++ /dev/null @@ -1,26 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef MINIMAL_OBJECTDEFINES_H -#define MINIMAL_OBJECTDEFINES_H - -/* - Object defines naming convention: - General: - * All characters in object names that does not match [a-zA-Z0-9_] will be replaced by '_'. - * Case of object dictionary names will be kept as is. - Index : Node object dictionary name +_+ index name +_+ Idx - SubIndex : Node object dictionary name +_+ index name +_+ subIndex name +_+ sIdx -*/ - -#define Null_Device_Type_Idx 0x1000 -#define Null_Device_Type_Device_Type_sIdx 0x00 - -#define Null_Identity_Idx 0x1018 -#define Null_Identity_Number_of_Entries_sIdx 0x00 -#define Null_Identity_Vendor_ID_sIdx 0x01 -#define Null_Identity_Product_Code_sIdx 0x02 -#define Null_Identity_Revision_Number_sIdx 0x03 -#define Null_Identity_Serial_Number_sIdx 0x04 - -#endif /* MINIMAL_OBJECTDEFINES_H */ diff --git a/tests/od/legacy-compare/slave.c b/tests/od/legacy-compare/slave.c deleted file mode 100644 index 358ce34..0000000 --- a/tests/od/legacy-compare/slave.c +++ /dev/null @@ -1,569 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#include "slave.h" - -/**************************************************************************/ -/* Declaration of mapped variables */ -/**************************************************************************/ - -/**************************************************************************/ -/* Declaration of value range types */ -/**************************************************************************/ - -#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */ -UNS32 Slave_valueRangeTest (UNS8 typeValue, void * value) -{ - switch (typeValue) { - case valueRange_EMC: - if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break; - } - return 0; -} - -/**************************************************************************/ -/* The node id */ -/**************************************************************************/ -/* node_id default value.*/ -UNS8 Slave_bDeviceNodeId = 0x00; - -/**************************************************************************/ -/* Array of message processing information */ - -const UNS8 Slave_iam_a_slave = 1; - -TIMER_HANDLE Slave_heartBeatTimers[1]; - -/* -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - - OBJECT DICTIONARY - -$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -*/ - -/* index 0x1000 : Device Type. */ - UNS32 Slave_obj1000 = 0x0; /* 0 */ - subindex Slave_Index1000[] = - { - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1000, NULL } - }; - -/* index 0x1001 : Error Register. */ - UNS8 Slave_obj1001 = 0x0; /* 0 */ - subindex Slave_Index1001[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_obj1001, NULL } - }; - -/* index 0x1003 : Pre-defined Error Field */ - UNS8 Slave_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ - UNS32 Slave_obj1003[] = - { - 0x0 /* 0 */ - }; - subindex Slave_Index1003[] = - { - { RW, valueRange_EMC, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1003, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1003[0], NULL } - }; - -/* index 0x1005 : SYNC COB ID */ - UNS32 Slave_obj1005 = 0x0; /* 0 */ - -/* index 0x1006 : Communication / Cycle Period */ - UNS32 Slave_obj1006 = 0x0; /* 0 */ - -/* index 0x100C : Guard Time */ - UNS16 Slave_obj100C = 0x0; /* 0 */ - -/* index 0x100D : Life Time Factor */ - UNS8 Slave_obj100D = 0x0; /* 0 */ - -/* index 0x1014 : Emergency COB ID */ - UNS32 Slave_obj1014 = 0x80 + 0x00; /* 128 + NodeID */ - -/* index 0x1016 : Consumer Heartbeat Time */ - UNS8 Slave_highestSubIndex_obj1016 = 0; - UNS32 Slave_obj1016[]={0}; - -/* index 0x1017 : Producer Heartbeat Time */ - UNS16 Slave_obj1017 = 0x0; /* 0 */ - -/* index 0x1018 : Identity. */ - UNS8 Slave_highestSubIndex_obj1018 = 4; /* number of subindex - 1*/ - UNS32 Slave_obj1018_Vendor_ID = 0x0; /* 0 */ - UNS32 Slave_obj1018_Product_Code = 0x0; /* 0 */ - UNS32 Slave_obj1018_Revision_Number = 0x0; /* 0 */ - UNS32 Slave_obj1018_Serial_Number = 0x0; /* 0 */ - subindex Slave_Index1018[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1018, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1018_Vendor_ID, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1018_Product_Code, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1018_Revision_Number, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1018_Serial_Number, NULL } - }; - -/* index 0x1200 : Server SDO Parameter. */ - UNS8 Slave_highestSubIndex_obj1200 = 2; /* number of subindex - 1*/ - UNS32 Slave_obj1200_COB_ID_Client_to_Server_Receive_SDO = 0x600; /* 1536 */ - UNS32 Slave_obj1200_COB_ID_Server_to_Client_Transmit_SDO = 0x580; /* 1408 */ - subindex Slave_Index1200[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1200, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1200_COB_ID_Client_to_Server_Receive_SDO, NULL }, - { RO, uint32, sizeof (UNS32), (void*)&Slave_obj1200_COB_ID_Server_to_Client_Transmit_SDO, NULL } - }; - -/* index 0x1400 : Receive PDO 1 Parameter. */ - UNS8 Slave_highestSubIndex_obj1400 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1400_COB_ID_used_by_PDO = 0x200; /* 512 */ - UNS8 Slave_obj1400_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1400_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1400_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1400_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1400_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1400[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1400, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1400_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1400_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1400_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1400_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1400_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1400_SYNC_start_value, NULL } - }; - -/* index 0x1401 : Receive PDO 2 Parameter. */ - UNS8 Slave_highestSubIndex_obj1401 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1401_COB_ID_used_by_PDO = 0x300; /* 768 */ - UNS8 Slave_obj1401_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1401_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1401_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1401_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1401_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1401[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1401, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1401_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1401_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1401_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1401_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1401_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1401_SYNC_start_value, NULL } - }; - -/* index 0x1402 : Receive PDO 3 Parameter. */ - UNS8 Slave_highestSubIndex_obj1402 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1402_COB_ID_used_by_PDO = 0x400; /* 1024 */ - UNS8 Slave_obj1402_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1402_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1402_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1402_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1402_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1402[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1402, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1402_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1402_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1402_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1402_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1402_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1402_SYNC_start_value, NULL } - }; - -/* index 0x1403 : Receive PDO 4 Parameter. */ - UNS8 Slave_highestSubIndex_obj1403 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1403_COB_ID_used_by_PDO = 0x500; /* 1280 */ - UNS8 Slave_obj1403_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1403_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1403_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1403_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1403_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1403[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1403, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1403_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1403_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1403_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1403_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1403_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1403_SYNC_start_value, NULL } - }; - -/* index 0x1600 : Receive PDO 1 Mapping. */ - UNS8 Slave_highestSubIndex_obj1600 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1600[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1600[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1600, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1600[7], NULL } - }; - -/* index 0x1601 : Receive PDO 2 Mapping. */ - UNS8 Slave_highestSubIndex_obj1601 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1601[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1601[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1601, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1601[7], NULL } - }; - -/* index 0x1602 : Receive PDO 3 Mapping. */ - UNS8 Slave_highestSubIndex_obj1602 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1602[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1602[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1602, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1602[7], NULL } - }; - -/* index 0x1603 : Receive PDO 4 Mapping. */ - UNS8 Slave_highestSubIndex_obj1603 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1603[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1603[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1603, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1603[7], NULL } - }; - -/* index 0x1800 : Transmit PDO 1 Parameter. */ - UNS8 Slave_highestSubIndex_obj1800 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1800_COB_ID_used_by_PDO = 0x180; /* 384 */ - UNS8 Slave_obj1800_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1800_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1800_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1800_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1800_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1800[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1800, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1800_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1800_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1800_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1800_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1800_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1800_SYNC_start_value, NULL } - }; - -/* index 0x1801 : Transmit PDO 2 Parameter. */ - UNS8 Slave_highestSubIndex_obj1801 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1801_COB_ID_used_by_PDO = 0x280; /* 640 */ - UNS8 Slave_obj1801_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1801_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1801_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1801_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1801_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1801[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1801, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1801_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1801_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1801_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1801_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1801_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1801_SYNC_start_value, NULL } - }; - -/* index 0x1802 : Transmit PDO 3 Parameter. */ - UNS8 Slave_highestSubIndex_obj1802 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1802_COB_ID_used_by_PDO = 0x380; /* 896 */ - UNS8 Slave_obj1802_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1802_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1802_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1802_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1802_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1802[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1802, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1802_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1802_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1802_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1802_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1802_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1802_SYNC_start_value, NULL } - }; - -/* index 0x1803 : Transmit PDO 4 Parameter. */ - UNS8 Slave_highestSubIndex_obj1803 = 6; /* number of subindex - 1*/ - UNS32 Slave_obj1803_COB_ID_used_by_PDO = 0x480; /* 1152 */ - UNS8 Slave_obj1803_Transmission_Type = 0x0; /* 0 */ - UNS16 Slave_obj1803_Inhibit_Time = 0x0; /* 0 */ - UNS8 Slave_obj1803_Compatibility_Entry = 0x0; /* 0 */ - UNS16 Slave_obj1803_Event_Timer = 0x0; /* 0 */ - UNS8 Slave_obj1803_SYNC_start_value = 0x0; /* 0 */ - subindex Slave_Index1803[] = - { - { RO, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1803, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1803_COB_ID_used_by_PDO, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1803_Transmission_Type, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1803_Inhibit_Time, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1803_Compatibility_Entry, NULL }, - { RW, uint16, sizeof (UNS16), (void*)&Slave_obj1803_Event_Timer, NULL }, - { RW, uint8, sizeof (UNS8), (void*)&Slave_obj1803_SYNC_start_value, NULL } - }; - -/* index 0x1A00 : Transmit PDO 1 Mapping. */ - UNS8 Slave_highestSubIndex_obj1A00 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1A00[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1A00[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1A00, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A00[7], NULL } - }; - -/* index 0x1A01 : Transmit PDO 2 Mapping. */ - UNS8 Slave_highestSubIndex_obj1A01 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1A01[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1A01[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1A01, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A01[7], NULL } - }; - -/* index 0x1A02 : Transmit PDO 3 Mapping. */ - UNS8 Slave_highestSubIndex_obj1A02 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1A02[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1A02[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1A02, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A02[7], NULL } - }; - -/* index 0x1A03 : Transmit PDO 4 Mapping. */ - UNS8 Slave_highestSubIndex_obj1A03 = 8; /* number of subindex - 1*/ - UNS32 Slave_obj1A03[] = - { - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0, /* 0 */ - 0x0 /* 0 */ - }; - subindex Slave_Index1A03[] = - { - { RW, uint8, sizeof (UNS8), (void*)&Slave_highestSubIndex_obj1A03, NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[0], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[1], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[2], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[3], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[4], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[5], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[6], NULL }, - { RW, uint32, sizeof (UNS32), (void*)&Slave_obj1A03[7], NULL } - }; - -/**************************************************************************/ -/* Declaration of pointed variables */ -/**************************************************************************/ - -const indextable Slave_objdict[] = -{ - { (subindex*)Slave_Index1000,sizeof(Slave_Index1000)/sizeof(Slave_Index1000[0]), 0x1000}, - { (subindex*)Slave_Index1001,sizeof(Slave_Index1001)/sizeof(Slave_Index1001[0]), 0x1001}, - { (subindex*)Slave_Index1018,sizeof(Slave_Index1018)/sizeof(Slave_Index1018[0]), 0x1018}, - { (subindex*)Slave_Index1200,sizeof(Slave_Index1200)/sizeof(Slave_Index1200[0]), 0x1200}, - { (subindex*)Slave_Index1400,sizeof(Slave_Index1400)/sizeof(Slave_Index1400[0]), 0x1400}, - { (subindex*)Slave_Index1401,sizeof(Slave_Index1401)/sizeof(Slave_Index1401[0]), 0x1401}, - { (subindex*)Slave_Index1402,sizeof(Slave_Index1402)/sizeof(Slave_Index1402[0]), 0x1402}, - { (subindex*)Slave_Index1403,sizeof(Slave_Index1403)/sizeof(Slave_Index1403[0]), 0x1403}, - { (subindex*)Slave_Index1600,sizeof(Slave_Index1600)/sizeof(Slave_Index1600[0]), 0x1600}, - { (subindex*)Slave_Index1601,sizeof(Slave_Index1601)/sizeof(Slave_Index1601[0]), 0x1601}, - { (subindex*)Slave_Index1602,sizeof(Slave_Index1602)/sizeof(Slave_Index1602[0]), 0x1602}, - { (subindex*)Slave_Index1603,sizeof(Slave_Index1603)/sizeof(Slave_Index1603[0]), 0x1603}, - { (subindex*)Slave_Index1800,sizeof(Slave_Index1800)/sizeof(Slave_Index1800[0]), 0x1800}, - { (subindex*)Slave_Index1801,sizeof(Slave_Index1801)/sizeof(Slave_Index1801[0]), 0x1801}, - { (subindex*)Slave_Index1802,sizeof(Slave_Index1802)/sizeof(Slave_Index1802[0]), 0x1802}, - { (subindex*)Slave_Index1803,sizeof(Slave_Index1803)/sizeof(Slave_Index1803[0]), 0x1803}, - { (subindex*)Slave_Index1A00,sizeof(Slave_Index1A00)/sizeof(Slave_Index1A00[0]), 0x1A00}, - { (subindex*)Slave_Index1A01,sizeof(Slave_Index1A01)/sizeof(Slave_Index1A01[0]), 0x1A01}, - { (subindex*)Slave_Index1A02,sizeof(Slave_Index1A02)/sizeof(Slave_Index1A02[0]), 0x1A02}, - { (subindex*)Slave_Index1A03,sizeof(Slave_Index1A03)/sizeof(Slave_Index1A03[0]), 0x1A03}, -}; - -const indextable * Slave_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode) -{ - int i; - (void)d; /* unused parameter */ - switch(wIndex){ - case 0x1000: i = 0;break; - case 0x1001: i = 1;break; - case 0x1018: i = 2;break; - case 0x1200: i = 3;break; - case 0x1400: i = 4;break; - case 0x1401: i = 5;break; - case 0x1402: i = 6;break; - case 0x1403: i = 7;break; - case 0x1600: i = 8;break; - case 0x1601: i = 9;break; - case 0x1602: i = 10;break; - case 0x1603: i = 11;break; - case 0x1800: i = 12;break; - case 0x1801: i = 13;break; - case 0x1802: i = 14;break; - case 0x1803: i = 15;break; - case 0x1A00: i = 16;break; - case 0x1A01: i = 17;break; - case 0x1A02: i = 18;break; - case 0x1A03: i = 19;break; - default: - *errorCode = OD_NO_SUCH_OBJECT; - return NULL; - } - *errorCode = OD_SUCCESSFUL; - return &Slave_objdict[i]; -} - -/* - * To count at which received SYNC a PDO must be sent. - * Even if no pdoTransmit are defined, at least one entry is computed - * for compilations issues. - */ -s_PDO_status Slave_PDO_status[4] = {s_PDO_status_Initializer,s_PDO_status_Initializer,s_PDO_status_Initializer,s_PDO_status_Initializer}; - -const quick_index Slave_firstIndex = { - 3, /* SDO_SVR */ - 0, /* SDO_CLT */ - 4, /* PDO_RCV */ - 8, /* PDO_RCV_MAP */ - 12, /* PDO_TRS */ - 16 /* PDO_TRS_MAP */ -}; - -const quick_index Slave_lastIndex = { - 3, /* SDO_SVR */ - 0, /* SDO_CLT */ - 7, /* PDO_RCV */ - 11, /* PDO_RCV_MAP */ - 15, /* PDO_TRS */ - 19 /* PDO_TRS_MAP */ -}; - -const UNS16 Slave_ObjdictSize = sizeof(Slave_objdict)/sizeof(Slave_objdict[0]); - -CO_Data Slave_Data = CANOPEN_NODE_DATA_INITIALIZER(Slave); - diff --git a/tests/od/legacy-compare/slave.eds b/tests/od/legacy-compare/slave.eds deleted file mode 100644 index 9534c75..0000000 --- a/tests/od/legacy-compare/slave.eds +++ /dev/null @@ -1,1207 +0,0 @@ -[FileInfo] -FileName=slave.eds -FileVersion=1 -FileRevision=1 -EDSVersion=4.0 -Description=Slave created with objdictedit -CreationTime=09:46PM -CreationDate=11-11-2022 -CreatedBy=CANFestival -ModificationTime=09:46PM -ModificationDate=11-11-2022 -ModifiedBy=CANFestival - -[DeviceInfo] -VendorName=CANFestival -VendorNumber=0x00000000 -ProductName=Slave -ProductNumber=0x00000000 -RevisionNumber=0x00000000 -BaudRate_10=1 -BaudRate_20=1 -BaudRate_50=1 -BaudRate_125=1 -BaudRate_250=1 -BaudRate_500=1 -BaudRate_800=1 -BaudRate_1000=1 -SimpleBootUpMaster=0 -SimpleBootUpSlave=1 -Granularity=8 -DynamicChannelsSupported=0 -CompactPDO=0 -GroupMessaging=0 -NrOfRXPDO=4 -NrOfTXPDO=4 -LSS_Supported=0 - -[DummyUsage] -Dummy0001=0 -Dummy0002=1 -Dummy0003=1 -Dummy0004=1 -Dummy0005=1 -Dummy0006=1 -Dummy0007=1 - -[Comments] -Lines=0 - -[MandatoryObjects] -SupportedObjects=3 -1=0x1000 -2=0x1001 -3=0x1018 - -[1000] -ParameterName=Device Type -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1001] -ParameterName=Error Register -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=0 -PDOMapping=1 - -[1018] -ParameterName=Identity -ObjectType=0x9 -SubNumber=5 - -[1018sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=4 -PDOMapping=0 - -[1018sub1] -ParameterName=Vendor ID -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub2] -ParameterName=Product Code -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub3] -ParameterName=Revision Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[1018sub4] -ParameterName=Serial Number -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=0 -PDOMapping=0 - -[OptionalObjects] -SupportedObjects=17 -1=0x1200 -2=0x1400 -3=0x1401 -4=0x1402 -5=0x1403 -6=0x1600 -7=0x1601 -8=0x1602 -9=0x1603 -10=0x1800 -11=0x1801 -12=0x1802 -13=0x1803 -14=0x1A00 -15=0x1A01 -16=0x1A02 -17=0x1A03 - -[1200] -ParameterName=Server SDO Parameter -ObjectType=0x9 -SubNumber=3 - -[1200sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=2 -PDOMapping=0 - -[1200sub1] -ParameterName=COB ID Client to Server (Receive SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=$NODEID+0x600 -PDOMapping=0 - -[1200sub2] -ParameterName=COB ID Server to Client (Transmit SDO) -ObjectType=0x7 -DataType=0x0007 -AccessType=ro -DefaultValue=$NODEID+0x580 -PDOMapping=0 - -[1400] -ParameterName=Receive PDO 1 Parameter -ObjectType=0x9 -SubNumber=6 - -[1400sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1400sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x200 -PDOMapping=0 - -[1400sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1400sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401] -ParameterName=Receive PDO 2 Parameter -ObjectType=0x9 -SubNumber=6 - -[1401sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1401sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x300 -PDOMapping=0 - -[1401sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1401sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402] -ParameterName=Receive PDO 3 Parameter -ObjectType=0x9 -SubNumber=6 - -[1402sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1402sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x400 -PDOMapping=0 - -[1402sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1402sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1403] -ParameterName=Receive PDO 4 Parameter -ObjectType=0x9 -SubNumber=6 - -[1403sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1403sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x500 -PDOMapping=0 - -[1403sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1403sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1403sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1403sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600] -ParameterName=Receive PDO 1 Mapping -ObjectType=0x8 -SubNumber=9 - -[1600sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1600sub1] -ParameterName=PDO 1 Mapping for an application object 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub2] -ParameterName=PDO 1 Mapping for an application object 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub3] -ParameterName=PDO 1 Mapping for an application object 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub4] -ParameterName=PDO 1 Mapping for an application object 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub5] -ParameterName=PDO 1 Mapping for an application object 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub6] -ParameterName=PDO 1 Mapping for an application object 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub7] -ParameterName=PDO 1 Mapping for an application object 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1600sub8] -ParameterName=PDO 1 Mapping for an application object 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601] -ParameterName=Receive PDO 2 Mapping -ObjectType=0x8 -SubNumber=9 - -[1601sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1601sub1] -ParameterName=PDO 2 Mapping for an application object 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub2] -ParameterName=PDO 2 Mapping for an application object 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub3] -ParameterName=PDO 2 Mapping for an application object 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub4] -ParameterName=PDO 2 Mapping for an application object 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub5] -ParameterName=PDO 2 Mapping for an application object 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub6] -ParameterName=PDO 2 Mapping for an application object 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub7] -ParameterName=PDO 2 Mapping for an application object 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1601sub8] -ParameterName=PDO 2 Mapping for an application object 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602] -ParameterName=Receive PDO 3 Mapping -ObjectType=0x8 -SubNumber=9 - -[1602sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1602sub1] -ParameterName=PDO 3 Mapping for an application object 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub2] -ParameterName=PDO 3 Mapping for an application object 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub3] -ParameterName=PDO 3 Mapping for an application object 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub4] -ParameterName=PDO 3 Mapping for an application object 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub5] -ParameterName=PDO 3 Mapping for an application object 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub6] -ParameterName=PDO 3 Mapping for an application object 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub7] -ParameterName=PDO 3 Mapping for an application object 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1602sub8] -ParameterName=PDO 3 Mapping for an application object 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603] -ParameterName=Receive PDO 4 Mapping -ObjectType=0x8 -SubNumber=9 - -[1603sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1603sub1] -ParameterName=PDO 4 Mapping for an application object 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub2] -ParameterName=PDO 4 Mapping for an application object 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub3] -ParameterName=PDO 4 Mapping for an application object 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub4] -ParameterName=PDO 4 Mapping for an application object 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub5] -ParameterName=PDO 4 Mapping for an application object 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub6] -ParameterName=PDO 4 Mapping for an application object 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub7] -ParameterName=PDO 4 Mapping for an application object 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1603sub8] -ParameterName=PDO 4 Mapping for an application object 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1800] -ParameterName=Transmit PDO 1 Parameter -ObjectType=0x9 -SubNumber=6 - -[1800sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1800sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x180 -PDOMapping=0 - -[1800sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1800sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1800sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1800sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1801] -ParameterName=Transmit PDO 2 Parameter -ObjectType=0x9 -SubNumber=6 - -[1801sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1801sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x280 -PDOMapping=0 - -[1801sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1801sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1801sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1801sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1802] -ParameterName=Transmit PDO 3 Parameter -ObjectType=0x9 -SubNumber=6 - -[1802sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1802sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x380 -PDOMapping=0 - -[1802sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1802sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1802sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1802sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1803] -ParameterName=Transmit PDO 4 Parameter -ObjectType=0x9 -SubNumber=6 - -[1803sub0] -ParameterName=Highest SubIndex Supported -ObjectType=0x7 -DataType=0x0005 -AccessType=ro -DefaultValue=6 -PDOMapping=0 - -[1803sub1] -ParameterName=COB ID used by PDO -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=$NODEID+0x480 -PDOMapping=0 - -[1803sub2] -ParameterName=Transmission Type -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1803sub3] -ParameterName=Inhibit Time -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1803sub5] -ParameterName=Event Timer -ObjectType=0x7 -DataType=0x0006 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1803sub6] -ParameterName=SYNC start value -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00] -ParameterName=Transmit PDO 1 Mapping -ObjectType=0x8 -SubNumber=9 - -[1A00sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1A00sub1] -ParameterName=PDO 1 Mapping for a process data variable 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub2] -ParameterName=PDO 1 Mapping for a process data variable 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub3] -ParameterName=PDO 1 Mapping for a process data variable 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub4] -ParameterName=PDO 1 Mapping for a process data variable 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub5] -ParameterName=PDO 1 Mapping for a process data variable 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub6] -ParameterName=PDO 1 Mapping for a process data variable 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub7] -ParameterName=PDO 1 Mapping for a process data variable 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A00sub8] -ParameterName=PDO 1 Mapping for a process data variable 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01] -ParameterName=Transmit PDO 2 Mapping -ObjectType=0x8 -SubNumber=9 - -[1A01sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1A01sub1] -ParameterName=PDO 2 Mapping for a process data variable 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub2] -ParameterName=PDO 2 Mapping for a process data variable 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub3] -ParameterName=PDO 2 Mapping for a process data variable 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub4] -ParameterName=PDO 2 Mapping for a process data variable 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub5] -ParameterName=PDO 2 Mapping for a process data variable 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub6] -ParameterName=PDO 2 Mapping for a process data variable 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub7] -ParameterName=PDO 2 Mapping for a process data variable 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A01sub8] -ParameterName=PDO 2 Mapping for a process data variable 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02] -ParameterName=Transmit PDO 3 Mapping -ObjectType=0x8 -SubNumber=9 - -[1A02sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1A02sub1] -ParameterName=PDO 3 Mapping for a process data variable 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub2] -ParameterName=PDO 3 Mapping for a process data variable 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub3] -ParameterName=PDO 3 Mapping for a process data variable 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub4] -ParameterName=PDO 3 Mapping for a process data variable 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub5] -ParameterName=PDO 3 Mapping for a process data variable 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub6] -ParameterName=PDO 3 Mapping for a process data variable 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub7] -ParameterName=PDO 3 Mapping for a process data variable 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A02sub8] -ParameterName=PDO 3 Mapping for a process data variable 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03] -ParameterName=Transmit PDO 4 Mapping -ObjectType=0x8 -SubNumber=9 - -[1A03sub0] -ParameterName=Number of Entries -ObjectType=0x7 -DataType=0x0005 -AccessType=rw -DefaultValue=8 -PDOMapping=0 - -[1A03sub1] -ParameterName=PDO 4 Mapping for a process data variable 1 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub2] -ParameterName=PDO 4 Mapping for a process data variable 2 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub3] -ParameterName=PDO 4 Mapping for a process data variable 3 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub4] -ParameterName=PDO 4 Mapping for a process data variable 4 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub5] -ParameterName=PDO 4 Mapping for a process data variable 5 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub6] -ParameterName=PDO 4 Mapping for a process data variable 6 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub7] -ParameterName=PDO 4 Mapping for a process data variable 7 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[1A03sub8] -ParameterName=PDO 4 Mapping for a process data variable 8 -ObjectType=0x7 -DataType=0x0007 -AccessType=rw -DefaultValue=0 -PDOMapping=0 - -[ManufacturerObjects] -SupportedObjects=0 diff --git a/tests/od/legacy-compare/slave.h b/tests/od/legacy-compare/slave.h deleted file mode 100644 index 9f13c30..0000000 --- a/tests/od/legacy-compare/slave.h +++ /dev/null @@ -1,16 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef SLAVE_H -#define SLAVE_H - -#include "data.h" - -/* Prototypes of function provided by object dictionnary */ -UNS32 Slave_valueRangeTest (UNS8 typeValue, void * value); -const indextable * Slave_scanIndexOD (CO_Data *d, UNS16 wIndex, UNS32 * errorCode); - -/* Master node data struct */ -extern CO_Data Slave_Data; - -#endif // SLAVE_H diff --git a/tests/od/legacy-compare/slave_objectdefines.h b/tests/od/legacy-compare/slave_objectdefines.h deleted file mode 100644 index 9157741..0000000 --- a/tests/od/legacy-compare/slave_objectdefines.h +++ /dev/null @@ -1,138 +0,0 @@ - -/* File generated by gen_cfile.py. Should not be modified. */ - -#ifndef SLAVE_OBJECTDEFINES_H -#define SLAVE_OBJECTDEFINES_H - -/* - Object defines naming convention: - General: - * All characters in object names that does not match [a-zA-Z0-9_] will be replaced by '_'. - * Case of object dictionary names will be kept as is. - Index : Node object dictionary name +_+ index name +_+ Idx - SubIndex : Node object dictionary name +_+ index name +_+ subIndex name +_+ sIdx -*/ - -#define Slave_Device_Type_Idx 0x1000 -#define Slave_Device_Type_Device_Type_sIdx 0x00 - -#define Slave_Error_Register_Idx 0x1001 -#define Slave_Error_Register_Error_Register_sIdx 0x00 - -#define Slave_Identity_Idx 0x1018 -#define Slave_Identity_Number_of_Entries_sIdx 0x00 -#define Slave_Identity_Vendor_ID_sIdx 0x01 -#define Slave_Identity_Product_Code_sIdx 0x02 -#define Slave_Identity_Revision_Number_sIdx 0x03 -#define Slave_Identity_Serial_Number_sIdx 0x04 - -#define Slave_Server_SDO_Parameter_Idx 0x1200 -#define Slave_Server_SDO_Parameter_Number_of_Entries_sIdx 0x00 -#define Slave_Server_SDO_Parameter_COB_ID_Client_to_Server__Receive_SDO__sIdx 0x01 -#define Slave_Server_SDO_Parameter_COB_ID_Server_to_Client__Transmit_SDO__sIdx 0x02 - -#define Slave_Receive_PDO_1_Parameter_Idx 0x1400 -#define Slave_Receive_PDO_1_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Receive_PDO_1_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Receive_PDO_1_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Receive_PDO_1_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Receive_PDO_1_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Receive_PDO_1_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Receive_PDO_1_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Receive_PDO_2_Parameter_Idx 0x1401 -#define Slave_Receive_PDO_2_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Receive_PDO_2_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Receive_PDO_2_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Receive_PDO_2_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Receive_PDO_2_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Receive_PDO_2_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Receive_PDO_2_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Receive_PDO_3_Parameter_Idx 0x1402 -#define Slave_Receive_PDO_3_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Receive_PDO_3_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Receive_PDO_3_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Receive_PDO_3_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Receive_PDO_3_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Receive_PDO_3_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Receive_PDO_3_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Receive_PDO_4_Parameter_Idx 0x1403 -#define Slave_Receive_PDO_4_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Receive_PDO_4_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Receive_PDO_4_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Receive_PDO_4_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Receive_PDO_4_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Receive_PDO_4_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Receive_PDO_4_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Receive_PDO_1_Mapping_Idx 0x1600 -#define Slave_Receive_PDO_1_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Receive_PDO_2_Mapping_Idx 0x1601 -#define Slave_Receive_PDO_2_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Receive_PDO_3_Mapping_Idx 0x1602 -#define Slave_Receive_PDO_3_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Receive_PDO_4_Mapping_Idx 0x1603 -#define Slave_Receive_PDO_4_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Transmit_PDO_1_Parameter_Idx 0x1800 -#define Slave_Transmit_PDO_1_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Transmit_PDO_1_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Transmit_PDO_1_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Transmit_PDO_1_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Transmit_PDO_1_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Transmit_PDO_1_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Transmit_PDO_1_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Transmit_PDO_2_Parameter_Idx 0x1801 -#define Slave_Transmit_PDO_2_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Transmit_PDO_2_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Transmit_PDO_2_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Transmit_PDO_2_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Transmit_PDO_2_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Transmit_PDO_2_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Transmit_PDO_2_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Transmit_PDO_3_Parameter_Idx 0x1802 -#define Slave_Transmit_PDO_3_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Transmit_PDO_3_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Transmit_PDO_3_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Transmit_PDO_3_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Transmit_PDO_3_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Transmit_PDO_3_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Transmit_PDO_3_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Transmit_PDO_4_Parameter_Idx 0x1803 -#define Slave_Transmit_PDO_4_Parameter_Highest_SubIndex_Supported_sIdx 0x00 -#define Slave_Transmit_PDO_4_Parameter_COB_ID_used_by_PDO_sIdx 0x01 -#define Slave_Transmit_PDO_4_Parameter_Transmission_Type_sIdx 0x02 -#define Slave_Transmit_PDO_4_Parameter_Inhibit_Time_sIdx 0x03 -#define Slave_Transmit_PDO_4_Parameter_Compatibility_Entry_sIdx 0x04 -#define Slave_Transmit_PDO_4_Parameter_Event_Timer_sIdx 0x05 -#define Slave_Transmit_PDO_4_Parameter_SYNC_start_value_sIdx 0x06 - -#define Slave_Transmit_PDO_1_Mapping_Idx 0x1a00 -#define Slave_Transmit_PDO_1_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Transmit_PDO_2_Mapping_Idx 0x1a01 -#define Slave_Transmit_PDO_2_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Transmit_PDO_3_Mapping_Idx 0x1a02 -#define Slave_Transmit_PDO_3_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#define Slave_Transmit_PDO_4_Mapping_Idx 0x1a03 -#define Slave_Transmit_PDO_4_Mapping_Number_of_Entries_sIdx 0x00 -/* subindex define not generated for array objects */ - -#endif /* SLAVE_OBJECTDEFINES_H */ diff --git a/tests/od/legacy-domain.od b/tests/od/legacy-domain.od new file mode 100644 index 0000000..74f2482 --- /dev/null +++ b/tests/od/legacy-domain.od @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Domain + + + + + + + Domain + + + + + + + + + + + + + +domain + diff --git a/tests/od/legacy-master.od b/tests/od/legacy-master.od index fcd1dfb..be304ee 100644 --- a/tests/od/legacy-master.od +++ b/tests/od/legacy-master.od @@ -1,17 +1,17 @@ - - + + -Master generated with legacy objdictedit - +Empty master OD + - + @@ -23,16 +23,16 @@ - + - + - + - + -Master +master diff --git a/tests/od/master-ds302-ds401.od b/tests/od/legacy-profile-ds302-ds401.od similarity index 91% rename from tests/od/master-ds302-ds401.od rename to tests/od/legacy-profile-ds302-ds401.od index 5b88405..3aaf2fb 100644 --- a/tests/od/master-ds302-ds401.od +++ b/tests/od/legacy-profile-ds302-ds401.od @@ -1,18 +1,18 @@ - - + + - + - - + + @@ -30,7 +30,7 @@ - + @@ -66,15 +66,15 @@ - + - - + + @@ -92,7 +92,7 @@ - + @@ -128,15 +128,15 @@ - + - - + + @@ -154,7 +154,7 @@ - + @@ -190,15 +190,15 @@ - + - - + + @@ -216,7 +216,7 @@ - + @@ -252,15 +252,15 @@ - + - - + + @@ -278,7 +278,7 @@ - + @@ -314,15 +314,15 @@ - + - - + + @@ -354,15 +354,15 @@ - + - - + + @@ -380,7 +380,7 @@ - + @@ -416,15 +416,15 @@ - + - - + + @@ -442,7 +442,7 @@ - + @@ -478,15 +478,15 @@ - + - - + + @@ -504,7 +504,7 @@ - + @@ -540,15 +540,15 @@ - + - - + + @@ -566,7 +566,7 @@ - + @@ -602,15 +602,15 @@ - + - - + + @@ -628,7 +628,7 @@ - + @@ -664,15 +664,15 @@ - + - - + + @@ -690,7 +690,7 @@ - + @@ -726,15 +726,15 @@ - + - - + + @@ -752,7 +752,7 @@ - + @@ -788,15 +788,15 @@ - + - - + + @@ -814,7 +814,7 @@ - + @@ -850,15 +850,15 @@ - + - - + + @@ -876,7 +876,7 @@ - + @@ -912,15 +912,15 @@ - + - - + + @@ -938,7 +938,7 @@ - + @@ -974,15 +974,15 @@ - + - - + + @@ -1000,7 +1000,7 @@ - + @@ -1036,15 +1036,15 @@ - + - - + + @@ -1062,7 +1062,7 @@ - + @@ -1098,7 +1098,7 @@ - + @@ -1113,8 +1113,8 @@ - - + + @@ -1132,7 +1132,7 @@ - + @@ -1168,15 +1168,15 @@ - + - - + + @@ -1194,7 +1194,7 @@ - + @@ -1230,15 +1230,15 @@ - + - - + + @@ -1256,7 +1256,7 @@ - + @@ -1292,15 +1292,15 @@ - + - - + + @@ -1318,7 +1318,7 @@ - + @@ -1354,15 +1354,15 @@ - + - - + + @@ -1380,7 +1380,7 @@ - + @@ -1416,15 +1416,15 @@ - + - - + + @@ -1442,7 +1442,7 @@ - + @@ -1478,15 +1478,15 @@ - + - - + + @@ -1504,7 +1504,7 @@ - + @@ -1540,15 +1540,15 @@ - + - - + + @@ -1566,7 +1566,7 @@ - + @@ -1602,15 +1602,15 @@ - + - - + + @@ -1628,7 +1628,7 @@ - + @@ -1664,15 +1664,15 @@ - + - - + + @@ -1690,7 +1690,7 @@ - + @@ -1726,15 +1726,15 @@ - + - - + + @@ -1752,7 +1752,7 @@ - + @@ -1788,15 +1788,15 @@ - + - - + + @@ -1814,7 +1814,7 @@ - + @@ -1850,15 +1850,15 @@ - + - - + + @@ -1876,7 +1876,7 @@ - + @@ -1912,15 +1912,15 @@ - + - - + + @@ -1938,7 +1938,7 @@ - + @@ -1974,15 +1974,15 @@ - + - - + + @@ -2000,7 +2000,7 @@ - + @@ -2036,15 +2036,15 @@ - + - - + + @@ -2062,7 +2062,7 @@ - + @@ -2098,7 +2098,7 @@ - + @@ -2113,8 +2113,8 @@ - - + + @@ -2132,7 +2132,7 @@ - + @@ -2168,15 +2168,15 @@ - + - - + + @@ -2194,7 +2194,7 @@ - + @@ -2230,15 +2230,15 @@ - + - - + + @@ -2256,7 +2256,7 @@ - + @@ -2292,15 +2292,15 @@ - + - - + + @@ -2318,7 +2318,7 @@ - + @@ -2354,7 +2354,7 @@ - + @@ -2369,8 +2369,8 @@ - - + + @@ -2388,7 +2388,7 @@ - + @@ -2424,15 +2424,15 @@ - + - - + + @@ -2450,7 +2450,7 @@ - + @@ -2486,7 +2486,7 @@ - + @@ -2501,8 +2501,8 @@ - - + + @@ -2520,7 +2520,7 @@ - + @@ -2556,15 +2556,15 @@ - + - - + + @@ -2582,7 +2582,7 @@ - + @@ -2618,15 +2618,15 @@ - + - - + + @@ -2644,7 +2644,7 @@ - + @@ -2680,7 +2680,7 @@ - + @@ -2695,8 +2695,8 @@ - - + + @@ -2714,7 +2714,7 @@ - + @@ -2750,15 +2750,15 @@ - + - - + + @@ -2776,7 +2776,7 @@ - + @@ -2812,15 +2812,15 @@ - + - - + + @@ -2838,7 +2838,7 @@ - + @@ -2874,15 +2874,15 @@ - + - - + + @@ -2900,7 +2900,7 @@ - + @@ -2936,15 +2936,15 @@ - + - - + + @@ -2962,7 +2962,7 @@ - + @@ -2998,15 +2998,15 @@ - + - - + + @@ -3024,7 +3024,7 @@ - + @@ -3060,15 +3060,15 @@ - + - - + + @@ -3086,7 +3086,7 @@ - + @@ -3122,15 +3122,15 @@ - + - - + + @@ -3148,7 +3148,7 @@ - + @@ -3184,15 +3184,15 @@ - + - - + + @@ -3210,7 +3210,7 @@ - + @@ -3246,15 +3246,15 @@ - + - - + + @@ -3272,7 +3272,7 @@ - + @@ -3308,15 +3308,15 @@ - + - - + + @@ -3334,7 +3334,7 @@ - + @@ -3370,15 +3370,15 @@ - + - - + + @@ -3396,7 +3396,7 @@ - + @@ -3432,15 +3432,15 @@ - + - - + + @@ -3458,7 +3458,7 @@ - + @@ -3494,15 +3494,15 @@ - + - - + + @@ -3534,15 +3534,15 @@ - + - - + + @@ -3560,7 +3560,7 @@ - + @@ -3596,7 +3596,7 @@ - + @@ -3611,8 +3611,8 @@ - - + + @@ -3630,7 +3630,7 @@ - + @@ -3666,15 +3666,15 @@ - + - - + + @@ -3692,7 +3692,7 @@ - + @@ -3728,7 +3728,7 @@ - + @@ -3743,8 +3743,8 @@ - - + + @@ -3762,7 +3762,7 @@ - + @@ -3798,15 +3798,15 @@ - + - - + + @@ -3824,7 +3824,7 @@ - + @@ -3860,15 +3860,15 @@ - + - - + + @@ -3886,7 +3886,7 @@ - + @@ -3922,15 +3922,15 @@ - + - - + + @@ -3948,7 +3948,7 @@ - + @@ -3984,7 +3984,7 @@ - + @@ -3999,8 +3999,8 @@ - - + + @@ -4018,7 +4018,7 @@ - + @@ -4054,7 +4054,7 @@ - + @@ -4069,8 +4069,8 @@ - - + + @@ -4088,7 +4088,7 @@ - + @@ -4124,15 +4124,15 @@ - + - - + + @@ -4150,7 +4150,7 @@ - + @@ -4186,15 +4186,15 @@ - + - - + + @@ -4212,7 +4212,7 @@ - + @@ -4248,15 +4248,15 @@ - + - - + + @@ -4274,7 +4274,7 @@ - + @@ -4310,15 +4310,15 @@ - + - - + + @@ -4336,7 +4336,7 @@ - + @@ -4372,15 +4372,15 @@ - + - - + + @@ -4398,7 +4398,7 @@ - + @@ -4434,15 +4434,15 @@ - + - - + + @@ -4460,7 +4460,7 @@ - + @@ -4496,15 +4496,15 @@ - + - - + + @@ -4522,7 +4522,7 @@ - + @@ -4558,15 +4558,15 @@ - + - - + + @@ -4584,7 +4584,7 @@ - + @@ -4620,15 +4620,15 @@ - + - - + + @@ -4646,7 +4646,7 @@ - + @@ -4682,7 +4682,7 @@ - + @@ -4697,8 +4697,8 @@ - - + + @@ -4716,7 +4716,7 @@ - + @@ -4752,15 +4752,15 @@ - + - - + + @@ -4778,7 +4778,7 @@ - + @@ -4814,15 +4814,15 @@ - + - - + + @@ -4840,7 +4840,7 @@ - + @@ -4876,15 +4876,15 @@ - + - - + + @@ -4902,7 +4902,7 @@ - + @@ -4938,15 +4938,15 @@ - + - - + + @@ -4964,7 +4964,7 @@ - + @@ -5000,7 +5000,7 @@ - + @@ -5015,8 +5015,8 @@ - - + + @@ -5034,7 +5034,7 @@ - + @@ -5069,15 +5069,15 @@ -Node generated with objdictedit. DS-302+DS-401 Profile - +Test DS-302 and DS-401 profile + - + @@ -5089,24 +5089,24 @@ - + - + - + - + - + - - + + @@ -5124,7 +5124,7 @@ - + @@ -5160,15 +5160,15 @@ - + - - + + @@ -5186,7 +5186,7 @@ - + @@ -5222,15 +5222,15 @@ - + - - + + @@ -5248,7 +5248,7 @@ - + @@ -5284,15 +5284,15 @@ - + - - + + @@ -5310,7 +5310,7 @@ - + @@ -5346,15 +5346,15 @@ - + - - + + @@ -5372,7 +5372,7 @@ - + @@ -5408,15 +5408,15 @@ - + - - + + @@ -5434,7 +5434,7 @@ - + @@ -5452,7 +5452,7 @@ - + @@ -5484,15 +5484,15 @@ - + - - + + @@ -5510,7 +5510,7 @@ - + @@ -5546,15 +5546,15 @@ - + - - + + @@ -5572,7 +5572,7 @@ - + @@ -5610,5 +5610,5 @@ DS-401 -Master +profile_ds302_ds401 diff --git a/tests/od/legacy-profile-ds302-test.od b/tests/od/legacy-profile-ds302-test.od new file mode 100644 index 0000000..a9ac2a6 --- /dev/null +++ b/tests/od/legacy-profile-ds302-test.od @@ -0,0 +1,1113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Test DS-302 and custom profile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Test + + +profile_ds302_test + diff --git a/tests/od/master-ds302.od b/tests/od/legacy-profile-ds302.od similarity index 89% rename from tests/od/master-ds302.od rename to tests/od/legacy-profile-ds302.od index 364ce04..c089047 100644 --- a/tests/od/master-ds302.od +++ b/tests/od/legacy-profile-ds302.od @@ -1,17 +1,17 @@ - - + + -Node generated with objdictedit. DS-302 Profile - +Test DS-302 profile + - + @@ -23,24 +23,24 @@ - + - + - + - + - + - - + + @@ -58,7 +58,7 @@ - + @@ -94,15 +94,15 @@ - + - - + + @@ -120,7 +120,7 @@ - + @@ -156,15 +156,15 @@ - + - - + + @@ -182,7 +182,7 @@ - + @@ -218,15 +218,15 @@ - + - - + + @@ -244,7 +244,7 @@ - + @@ -280,15 +280,15 @@ - + - - + + @@ -306,7 +306,7 @@ - + @@ -342,15 +342,15 @@ - + - - + + @@ -368,7 +368,7 @@ - + @@ -386,7 +386,7 @@ - + @@ -418,15 +418,15 @@ - + - - + + @@ -444,7 +444,7 @@ - + @@ -480,15 +480,15 @@ - + - - + + @@ -506,7 +506,7 @@ - + @@ -544,5 +544,5 @@ -Master +profile_ds302 diff --git a/tests/od/legacy-master-ds401.od b/tests/od/legacy-profile-ds401.od similarity index 91% rename from tests/od/legacy-master-ds401.od rename to tests/od/legacy-profile-ds401.od index 0977bd6..0b6b680 100644 --- a/tests/od/legacy-master-ds401.od +++ b/tests/od/legacy-profile-ds401.od @@ -1,18 +1,18 @@ - - + + - + - - + + @@ -30,7 +30,7 @@ - + @@ -66,15 +66,15 @@ - + - - + + @@ -92,7 +92,7 @@ - + @@ -128,15 +128,15 @@ - + - - + + @@ -154,7 +154,7 @@ - + @@ -190,15 +190,15 @@ - + - - + + @@ -216,7 +216,7 @@ - + @@ -252,15 +252,15 @@ - + - - + + @@ -278,7 +278,7 @@ - + @@ -314,15 +314,15 @@ - + - - + + @@ -354,15 +354,15 @@ - + - - + + @@ -380,7 +380,7 @@ - + @@ -416,15 +416,15 @@ - + - - + + @@ -442,7 +442,7 @@ - + @@ -478,15 +478,15 @@ - + - - + + @@ -504,7 +504,7 @@ - + @@ -540,15 +540,15 @@ - + - - + + @@ -566,7 +566,7 @@ - + @@ -602,15 +602,15 @@ - + - - + + @@ -628,7 +628,7 @@ - + @@ -664,15 +664,15 @@ - + - - + + @@ -690,7 +690,7 @@ - + @@ -726,15 +726,15 @@ - + - - + + @@ -752,7 +752,7 @@ - + @@ -788,15 +788,15 @@ - + - - + + @@ -814,7 +814,7 @@ - + @@ -850,15 +850,15 @@ - + - - + + @@ -876,7 +876,7 @@ - + @@ -912,15 +912,15 @@ - + - - + + @@ -938,7 +938,7 @@ - + @@ -974,15 +974,15 @@ - + - - + + @@ -1000,7 +1000,7 @@ - + @@ -1036,15 +1036,15 @@ - + - - + + @@ -1062,7 +1062,7 @@ - + @@ -1098,7 +1098,7 @@ - + @@ -1113,8 +1113,8 @@ - - + + @@ -1132,7 +1132,7 @@ - + @@ -1168,15 +1168,15 @@ - + - - + + @@ -1194,7 +1194,7 @@ - + @@ -1230,15 +1230,15 @@ - + - - + + @@ -1256,7 +1256,7 @@ - + @@ -1292,15 +1292,15 @@ - + - - + + @@ -1318,7 +1318,7 @@ - + @@ -1354,15 +1354,15 @@ - + - - + + @@ -1380,7 +1380,7 @@ - + @@ -1416,15 +1416,15 @@ - + - - + + @@ -1442,7 +1442,7 @@ - + @@ -1478,15 +1478,15 @@ - + - - + + @@ -1504,7 +1504,7 @@ - + @@ -1540,15 +1540,15 @@ - + - - + + @@ -1566,7 +1566,7 @@ - + @@ -1602,15 +1602,15 @@ - + - - + + @@ -1628,7 +1628,7 @@ - + @@ -1664,15 +1664,15 @@ - + - - + + @@ -1690,7 +1690,7 @@ - + @@ -1726,15 +1726,15 @@ - + - - + + @@ -1752,7 +1752,7 @@ - + @@ -1788,15 +1788,15 @@ - + - - + + @@ -1814,7 +1814,7 @@ - + @@ -1850,15 +1850,15 @@ - + - - + + @@ -1876,7 +1876,7 @@ - + @@ -1912,15 +1912,15 @@ - + - - + + @@ -1938,7 +1938,7 @@ - + @@ -1974,15 +1974,15 @@ - + - - + + @@ -2000,7 +2000,7 @@ - + @@ -2036,15 +2036,15 @@ - + - - + + @@ -2062,7 +2062,7 @@ - + @@ -2098,7 +2098,7 @@ - + @@ -2113,8 +2113,8 @@ - - + + @@ -2132,7 +2132,7 @@ - + @@ -2168,15 +2168,15 @@ - + - - + + @@ -2194,7 +2194,7 @@ - + @@ -2230,15 +2230,15 @@ - + - - + + @@ -2256,7 +2256,7 @@ - + @@ -2292,15 +2292,15 @@ - + - - + + @@ -2318,7 +2318,7 @@ - + @@ -2354,7 +2354,7 @@ - + @@ -2369,8 +2369,8 @@ - - + + @@ -2388,7 +2388,7 @@ - + @@ -2424,15 +2424,15 @@ - + - - + + @@ -2450,7 +2450,7 @@ - + @@ -2486,7 +2486,7 @@ - + @@ -2501,8 +2501,8 @@ - - + + @@ -2520,7 +2520,7 @@ - + @@ -2556,15 +2556,15 @@ - + - - + + @@ -2582,7 +2582,7 @@ - + @@ -2618,15 +2618,15 @@ - + - - + + @@ -2644,7 +2644,7 @@ - + @@ -2680,7 +2680,7 @@ - + @@ -2695,8 +2695,8 @@ - - + + @@ -2714,7 +2714,7 @@ - + @@ -2750,15 +2750,15 @@ - + - - + + @@ -2776,7 +2776,7 @@ - + @@ -2812,15 +2812,15 @@ - + - - + + @@ -2838,7 +2838,7 @@ - + @@ -2874,15 +2874,15 @@ - + - - + + @@ -2900,7 +2900,7 @@ - + @@ -2936,15 +2936,15 @@ - + - - + + @@ -2962,7 +2962,7 @@ - + @@ -2998,15 +2998,15 @@ - + - - + + @@ -3024,7 +3024,7 @@ - + @@ -3060,15 +3060,15 @@ - + - - + + @@ -3086,7 +3086,7 @@ - + @@ -3122,15 +3122,15 @@ - + - - + + @@ -3148,7 +3148,7 @@ - + @@ -3184,15 +3184,15 @@ - + - - + + @@ -3210,7 +3210,7 @@ - + @@ -3246,15 +3246,15 @@ - + - - + + @@ -3272,7 +3272,7 @@ - + @@ -3308,15 +3308,15 @@ - + - - + + @@ -3334,7 +3334,7 @@ - + @@ -3370,15 +3370,15 @@ - + - - + + @@ -3396,7 +3396,7 @@ - + @@ -3432,15 +3432,15 @@ - + - - + + @@ -3458,7 +3458,7 @@ - + @@ -3494,15 +3494,15 @@ - + - - + + @@ -3534,15 +3534,15 @@ - + - - + + @@ -3560,7 +3560,7 @@ - + @@ -3596,7 +3596,7 @@ - + @@ -3611,8 +3611,8 @@ - - + + @@ -3630,7 +3630,7 @@ - + @@ -3666,15 +3666,15 @@ - + - - + + @@ -3692,7 +3692,7 @@ - + @@ -3728,7 +3728,7 @@ - + @@ -3743,8 +3743,8 @@ - - + + @@ -3762,7 +3762,7 @@ - + @@ -3798,15 +3798,15 @@ - + - - + + @@ -3824,7 +3824,7 @@ - + @@ -3860,15 +3860,15 @@ - + - - + + @@ -3886,7 +3886,7 @@ - + @@ -3922,15 +3922,15 @@ - + - - + + @@ -3948,7 +3948,7 @@ - + @@ -3984,7 +3984,7 @@ - + @@ -3999,8 +3999,8 @@ - - + + @@ -4018,7 +4018,7 @@ - + @@ -4054,7 +4054,7 @@ - + @@ -4069,8 +4069,8 @@ - - + + @@ -4088,7 +4088,7 @@ - + @@ -4124,15 +4124,15 @@ - + - - + + @@ -4150,7 +4150,7 @@ - + @@ -4186,15 +4186,15 @@ - + - - + + @@ -4212,7 +4212,7 @@ - + @@ -4248,15 +4248,15 @@ - + - - + + @@ -4274,7 +4274,7 @@ - + @@ -4310,15 +4310,15 @@ - + - - + + @@ -4336,7 +4336,7 @@ - + @@ -4372,15 +4372,15 @@ - + - - + + @@ -4398,7 +4398,7 @@ - + @@ -4434,15 +4434,15 @@ - + - - + + @@ -4460,7 +4460,7 @@ - + @@ -4496,15 +4496,15 @@ - + - - + + @@ -4522,7 +4522,7 @@ - + @@ -4558,15 +4558,15 @@ - + - - + + @@ -4584,7 +4584,7 @@ - + @@ -4620,15 +4620,15 @@ - + - - + + @@ -4646,7 +4646,7 @@ - + @@ -4682,7 +4682,7 @@ - + @@ -4697,8 +4697,8 @@ - - + + @@ -4716,7 +4716,7 @@ - + @@ -4752,15 +4752,15 @@ - + - - + + @@ -4778,7 +4778,7 @@ - + @@ -4814,15 +4814,15 @@ - + - - + + @@ -4840,7 +4840,7 @@ - + @@ -4876,15 +4876,15 @@ - + - - + + @@ -4902,7 +4902,7 @@ - + @@ -4938,15 +4938,15 @@ - + - - + + @@ -4964,7 +4964,7 @@ - + @@ -5000,7 +5000,7 @@ - + @@ -5015,8 +5015,8 @@ - - + + @@ -5034,7 +5034,7 @@ - + @@ -5069,15 +5069,15 @@ -Master generated with legacy objdictedt. DS-401 - +Test DS-401 profile + - + @@ -5089,16 +5089,16 @@ - + - + - + - + DS-401 -Master +profile_ds401 diff --git a/tests/od/legacy-profile-test.od b/tests/od/legacy-profile-test.od new file mode 100644 index 0000000..9476490 --- /dev/null +++ b/tests/od/legacy-profile-test.od @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Test custom profile + + + + + + + + + + + + + + + + + + + + + + + + + + + +Test + + +profile_test + diff --git a/tests/od/legacy-slave-ds302.od b/tests/od/legacy-slave-ds302.od index a3ec29f..b74c9c2 100644 --- a/tests/od/legacy-slave-ds302.od +++ b/tests/od/legacy-slave-ds302.od @@ -3,7 +3,7 @@ -Slave generated with legacy objdictedit. DS-302 +Slave with DS-302 diff --git a/tests/od/legacy-slave-emcy.od b/tests/od/legacy-slave-emcy.od index 02f81c3..2cd859c 100644 --- a/tests/od/legacy-slave-emcy.od +++ b/tests/od/legacy-slave-emcy.od @@ -3,7 +3,7 @@ -Slave generated with legacy objdictedit. Emergency support +Slave with emergency support diff --git a/tests/od/legacy-slave-heartbeat.od b/tests/od/legacy-slave-heartbeat.od index 544aa5c..aa638e7 100644 --- a/tests/od/legacy-slave-heartbeat.od +++ b/tests/od/legacy-slave-heartbeat.od @@ -3,7 +3,7 @@ -Slave generated with legacy objdictedit. Heartbeat +Slave with heartbeat diff --git a/tests/od/legacy-slave-nodeguarding.od b/tests/od/legacy-slave-nodeguarding.od index 9eb856a..22af21b 100644 --- a/tests/od/legacy-slave-nodeguarding.od +++ b/tests/od/legacy-slave-nodeguarding.od @@ -3,7 +3,7 @@ -Slave generated with legacy objdictedit. Node Guarding +Slave with Node Guarding diff --git a/tests/od/legacy-slave-sync.od b/tests/od/legacy-slave-sync.od index 7bdf3db..5455d0a 100644 --- a/tests/od/legacy-slave-sync.od +++ b/tests/od/legacy-slave-sync.od @@ -3,7 +3,7 @@ -Slave generated with legacy objdictedit. With SYNC +Slave with SYNC diff --git a/tests/od/legacy-slave.od b/tests/od/legacy-slave.od index e5cc258..d2a33b8 100644 --- a/tests/od/legacy-slave.od +++ b/tests/od/legacy-slave.od @@ -1,17 +1,17 @@ - - + + -Slave generated with legacy objdictedit - +Slave OD + - + @@ -22,7 +22,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -44,7 +44,7 @@ - + @@ -55,7 +55,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -79,7 +79,7 @@ - + @@ -90,7 +90,7 @@ - + @@ -103,7 +103,7 @@ - + @@ -116,7 +116,7 @@ - + @@ -129,7 +129,7 @@ - + @@ -140,7 +140,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -166,7 +166,7 @@ - + @@ -179,7 +179,7 @@ - + @@ -190,7 +190,7 @@ - + @@ -203,7 +203,7 @@ - + @@ -216,22 +216,22 @@ - + - + - + - + - + -Slave +slave diff --git a/tests/od/legacy-strings.od b/tests/od/legacy-strings.od new file mode 100644 index 0000000..b5fc561 --- /dev/null +++ b/tests/od/legacy-strings.od @@ -0,0 +1,535 @@ + + + + + +Complicated strings with unicode + + + + + + + + + + + + + abcd + abcø + abc✓ + + + + + + + + + + + + + + + + + + abcd + abcø + abc✓ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + brod + + + + + + + brod + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + brød + + + + + + + brød + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Plain + + + + + + + + + + + + + + + + + + Latin1 + + + + + + + + + + + + + + + + + + Unicode + + + + + + + OCTET_STRING + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Plain + + + + + + + + + + + + + + + + + + Latin1 + + + + + + + + + + + + + + + + + + Unicode + + + + + + + DOMAIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Plain + + + + + + + + + + + + + + + + + + Latin1 + + + + + + + + + + + + + + + + + + Unicode + + + + + + + UNICODE_STRING + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Plain + + + + + + + + + + + + + + + + + + Latin1 + + + + + + + + + + + + + + + + + + Unicode + + + + + + + VISIBLE_STRING + + + + + + + + + + + + + +a + diff --git a/tests/od/master.json b/tests/od/master.json index 2c9833d..7117413 100644 --- a/tests/od/master.json +++ b/tests/od/master.json @@ -2,10 +2,10 @@ "$id": "od data", "$version": "1", "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-08-08T21:02:00.390850", - "name": "Master", - "description": "Master created with objdictedit", + "$tool": "odg 3.4", + "$date": "2024-02-27T00:27:21.144432", + "name": "master", + "description": "Empty master OD", "type": "master", "id": 0, "profile": "None", diff --git a/tests/od/master.od b/tests/od/master.od index bca5044..f0cdd5d 100644 --- a/tests/od/master.od +++ b/tests/od/master.od @@ -1,38 +1,38 @@ - - + + + + + + + + + -Master created with objdictedit - + + + + + - + - - - - - + - + - + - - - - - -Master diff --git a/tests/od/profile-ds302-ds401.json b/tests/od/profile-ds302-ds401.json new file mode 100644 index 0000000..59a9600 --- /dev/null +++ b/tests/od/profile-ds302-ds401.json @@ -0,0 +1,2149 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-27T19:26:46.575697", + "name": "profile_ds302_ds401", + "description": "Test DS-302 and DS-401 profile", + "type": "master", + "id": 0, + "profile": "DS-401", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x6000", // 24576 + "name": "Read Inputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Inputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6002", // 24578 + "name": "Polarity Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6003", // 24579 + "name": "Filter Constant Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Constant Input 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6005", // 24581 + "name": "Global Interrupt Enable Digital", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Global Interrupt Enable Digital", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x6006", // 24582 + "name": "Interrupt Mask Any Change 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6007", // 24583 + "name": "Interrupt Mask Low to High 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6008", // 24584 + "name": "Interrupt Mask High to Low 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6020", // 24608 + "name": "Read Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Read Single Input 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6030", // 24624 + "name": "Polarity Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Polarity Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6038", // 24632 + "name": "Filter Constant Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Filter Constant Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6050", // 24656 + "name": "Interrupt Mask Input Any Change Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6060", // 24672 + "name": "Interrupt Mask Input Low to High Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6070", // 24688 + "name": "Interrupt Mask Input High to Low Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6100", // 24832 + "name": "Read Inputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Inputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6102", // 24834 + "name": "Polarity Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6103", // 24835 + "name": "Filter Constant Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Constant Input 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6106", // 24838 + "name": "Interrupt Mask Any Change 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6107", // 24839 + "name": "Interrupt Mask Low to High 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6108", // 24840 + "name": "Interrupt Mask High to Low 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6120", // 24864 + "name": "Read Input 4 Byte", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6122", // 24866 + "name": "Polarity Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6123", // 24867 + "name": "Filter Constant Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6126", // 24870 + "name": "Interrupt Mask Input Any Change 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6127", // 24871 + "name": "Interrupt Mask Input Low to High 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6128", // 24872 + "name": "Interrupt Mask Input High to Low 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6200", // 25088 + "name": "Write Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6202", // 25090 + "name": "Change Polarity Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Change Polarity Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6206", // 25094 + "name": "Error Mode Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6207", // 25095 + "name": "Error Value Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6208", // 25096 + "name": "Filter Mask Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6220", // 25120 + "name": "Write Outputs Bit %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Write Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6240", // 25152 + "name": "Change Polarity Outputs Bit %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Change Polarity Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6250", // 25168 + "name": "Error Mode Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Error Mode Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6260", // 25184 + "name": "Error Value Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Error Value Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6270", // 25200 + "name": "Filter Constant Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Filter Constant Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6300", // 25344 + "name": "Write Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6302", // 25346 + "name": "Change Polarity Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Change Polarity Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6306", // 25350 + "name": "Error Mode Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6307", // 25351 + "name": "Error Value Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6308", // 25352 + "name": "Filter Mask Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6320", // 25376 + "name": "Write Output 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6322", // 25378 + "name": "Change Polarity Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6326", // 25382 + "name": "Error Mode Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6327", // 25383 + "name": "Error Value Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6328", // 25384 + "name": "Filter Mask Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6400", // 25600 + "name": "Read Analogue Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6401", // 25601 + "name": "Read Analogue Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6402", // 25602 + "name": "Read Analogue Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6403", // 25603 + "name": "Read Analogue Input Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input Float", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6404", // 25604 + "name": "Read Manufacturer specific Analogue Input", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL64", // 17 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6410", // 25616 + "name": "Write Analogue Output 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6411", // 25617 + "name": "Write Analogue Output 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6412", // 25618 + "name": "Write Analogue Output 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6413", // 25619 + "name": "Write Analogue Output Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs Float", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6414", // 25620 + "name": "Write Manufacturer specific Analogue Output", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL64", // 17 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6421", // 25633 + "name": "Interrupt Trigger Selection", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analog Inputs 0x%X[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6422", // 25634 + "name": "Analogue Input Interrupt Source", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Source Bank 0x%X[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Interrupt Source Bank", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6423", // 25635 + "name": "Analogue Input Global Interrupt Enable", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Analogue Input Global Interrupt Enable", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true + } + ] + }, + { + "index": "0x6424", // 25636 + "name": "Analogue Input Interrupt Upper Limit Interger", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6425", // 25637 + "name": "Analogue Input Interrupt Lower Limit Interger", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6426", // 25638 + "name": "Analogue Input Interrupt Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6427", // 25639 + "name": "Analogue Input Interrupt Negative Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6428", // 25640 + "name": "Analogue Input Interrupt Positive Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6429", // 25641 + "name": "Analogue Input Interrupt Upper Limit Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642A", // 25642 + "name": "Analogue Input Interrupt Lower Limit Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642B", // 25643 + "name": "Analogue Input Interrupt Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642C", // 25644 + "name": "Analogue Input Interrupt Negative Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642D", // 25645 + "name": "Analogue Input Interrupt Positive Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642E", // 25646 + "name": "Analogue Input Offset Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642F", // 25647 + "name": "Analogue Input Scaling Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6430", // 25648 + "name": "Analogue Input SI unit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6431", // 25649 + "name": "Analogue Input Offset Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6432", // 25650 + "name": "Analogue Input Scaling Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6441", // 25665 + "name": "Analogue Output Offset Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6442", // 25666 + "name": "Analogue Output Scaling Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6443", // 25667 + "name": "Analogue Output Error Mode", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Analogue Output %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6444", // 25668 + "name": "Analogue Output Error Value Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6445", // 25669 + "name": "Analogue Output Error Value Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6446", // 25670 + "name": "Analogue Output Offset Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6447", // 25671 + "name": "Analogue Output Scaling Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6450", // 25680 + "name": "Analogue Output SI Unit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F20", // 7968 + "name": "Store DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Store DCF for node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F21", // 7969 + "name": "Storage Format", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Storage Format for Node %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F22", // 7970 + "name": "Concise DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Concise DCF for Node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F50", // 8016 + "name": "Download Program Data", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs supported on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F51", // 8017 + "name": "Program Control", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F52", // 8018 + "name": "Verify Application Software", + "struct": "record", + "group": "ds302", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Application software date", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + }, + { + "name": "Application sofware time", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x1F53", // 8019 + "name": "Expected Application SW Date", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F55", // 8021 + "name": "Expected Application SW Time", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/legacy-master-ds302-ds401.od b/tests/od/profile-ds302-ds401.od similarity index 90% rename from tests/od/legacy-master-ds302-ds401.od rename to tests/od/profile-ds302-ds401.od index 7718328..cac54cc 100644 --- a/tests/od/legacy-master-ds302-ds401.od +++ b/tests/od/profile-ds302-ds401.od @@ -1,18 +1,39 @@ - - + + + + + + + - + + + + + + + + + - - + + + + + + + + + + @@ -21,16 +42,16 @@ + + - - + + - - + + - - @@ -39,14 +60,6 @@ - - - - - - - - @@ -54,27 +67,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -83,32 +104,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -116,27 +129,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -145,16 +166,16 @@ + + - - + + - - + + - - @@ -163,14 +184,6 @@ - - - - - - - - @@ -178,89 +191,75 @@ + + + + + - + - + - - - - - - - - - - - + + - - + + - - - - - + - - - - - - - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -269,32 +268,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -302,67 +293,97 @@ + + + + + - + - - - - - - - + + + + + + + + + + - + + + + + + + - + - - + + + + + + + + + + + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -371,16 +392,16 @@ + + + + + + - - - - - - @@ -389,14 +410,6 @@ - - - - - - - - @@ -404,27 +417,43 @@ + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -433,16 +462,16 @@ + + - - + + - - + + - - @@ -451,42 +480,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -495,16 +532,16 @@ + + - - + + - - + + - - @@ -513,42 +550,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -557,16 +602,16 @@ + + - - + + - - + + - - @@ -575,42 +620,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -619,16 +672,16 @@ + + - - + + - - + + - - @@ -637,42 +690,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -681,16 +742,16 @@ + + - - + + - - + + - - @@ -699,42 +760,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -743,16 +812,16 @@ + + - - + + - - + + - - @@ -761,42 +830,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -805,32 +874,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -838,27 +899,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -867,16 +936,16 @@ + + - - + + - - + + - - @@ -885,14 +954,6 @@ - - - - - - - - @@ -900,27 +961,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -929,16 +998,16 @@ + + - - + + - - + + - - @@ -947,14 +1016,6 @@ - - - - - - - - @@ -962,27 +1023,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -991,32 +1060,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -1024,27 +1085,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1053,16 +1122,16 @@ + + - - + + - - + + - - @@ -1071,14 +1140,6 @@ - - - - - - - - @@ -1086,35 +1147,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -1123,60 +1184,60 @@ + + - - + + - - + + - - - - - - - - - - - - + + - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -1185,16 +1246,16 @@ + + - - + + - - + + - - @@ -1203,42 +1264,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -1247,16 +1308,16 @@ + + - - + + - - + + - - @@ -1265,14 +1326,6 @@ - - - - - - - - @@ -1280,27 +1333,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1309,16 +1370,16 @@ + + - - + + - - + + - - @@ -1327,14 +1388,6 @@ - - - - - - - - @@ -1342,27 +1395,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1371,16 +1432,16 @@ + + - - + + - - + + - - @@ -1389,14 +1450,6 @@ - - - - - - - - @@ -1404,27 +1457,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1433,16 +1494,16 @@ + + - - + + - - + + - - @@ -1451,14 +1512,6 @@ - - - - - - - - @@ -1466,27 +1519,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1495,16 +1556,16 @@ + + - - + + - - + + - - @@ -1513,14 +1574,6 @@ - - - - - - - - @@ -1528,27 +1581,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1557,16 +1618,16 @@ - - - - + + - + + + + + - - @@ -1575,14 +1636,6 @@ - - - - - - - - @@ -1590,27 +1643,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1619,16 +1680,16 @@ + + - - + + - - + + - - @@ -1637,14 +1698,6 @@ - - - - - - - - @@ -1652,27 +1705,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1681,16 +1742,16 @@ + + - - + + - - + + - - @@ -1699,14 +1760,6 @@ - - - - - - - - @@ -1714,27 +1767,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1743,16 +1804,16 @@ + + - - + + - - + + - - @@ -1761,14 +1822,6 @@ - - - - - - - - @@ -1776,27 +1829,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1805,16 +1866,16 @@ + + - - + + - - + + - - @@ -1823,14 +1884,6 @@ - - - - - - - - @@ -1838,27 +1891,43 @@ + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1867,16 +1936,16 @@ + + - - + + - - + + - - @@ -1885,42 +1954,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1929,16 +2006,16 @@ + + - - + + - - + + - - @@ -1947,42 +2024,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1991,16 +2076,16 @@ + + - - + + - - + + - - @@ -2009,42 +2094,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -2053,16 +2146,16 @@ + + - - + + - - + + - - @@ -2071,41 +2164,33 @@ - - - - - - - - - + + + + + + - + - + - - - - - - - + + @@ -2113,8 +2198,16 @@ - - + + + + + + + + + + @@ -2123,16 +2216,16 @@ + + - - + + - - + + - - @@ -2141,14 +2234,6 @@ - - - - - - - - @@ -2156,27 +2241,35 @@ + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2185,16 +2278,16 @@ + + - - + + - - + + - - @@ -2203,14 +2296,6 @@ - - - - - - - - @@ -2218,27 +2303,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2247,16 +2340,16 @@ + + - - + + - - + + - - @@ -2265,14 +2358,6 @@ - - - - - - - - @@ -2280,27 +2365,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2309,16 +2402,16 @@ + + - - + + - - + + - - @@ -2327,14 +2420,6 @@ - - - - - - - - @@ -2342,35 +2427,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2379,16 +2464,16 @@ + + - - + + - - + + - - @@ -2397,42 +2482,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2441,16 +2526,16 @@ + + - - + + - - + + - - @@ -2459,14 +2544,6 @@ - - - - - - - - @@ -2474,35 +2551,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2511,16 +2588,16 @@ + + - - + + - - + + - - @@ -2529,42 +2606,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2573,16 +2650,16 @@ + + - - + + - - + + - - @@ -2591,14 +2668,6 @@ - - - - - - - - @@ -2606,27 +2675,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2635,16 +2712,16 @@ + + - - + + - - + + - - @@ -2653,14 +2730,6 @@ - - - - - - - - @@ -2668,35 +2737,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2705,16 +2774,16 @@ + + - - + + - - + + - - @@ -2723,42 +2792,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2767,16 +2836,16 @@ + + - - + + - - + + - - @@ -2785,42 +2854,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -2829,32 +2898,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2862,27 +2923,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2891,32 +2960,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2924,27 +2985,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2953,32 +3022,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2986,27 +3047,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3015,32 +3084,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3048,27 +3109,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3077,32 +3146,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3110,27 +3171,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3139,16 +3208,16 @@ + + - - + + - - + + - - @@ -3157,14 +3226,6 @@ - - - - - - - - @@ -3172,27 +3233,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3201,32 +3270,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3234,27 +3295,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3263,16 +3332,16 @@ + + - - + + - - + + - - @@ -3281,14 +3350,6 @@ - - - - - - - - @@ -3296,27 +3357,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3325,16 +3394,16 @@ + + - - + + - - + + - - @@ -3343,14 +3412,6 @@ - - - - - - - - @@ -3358,27 +3419,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3387,16 +3456,16 @@ + + - - + + - - + + - - @@ -3405,14 +3474,6 @@ - - - - - - - - @@ -3420,27 +3481,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3449,16 +3518,16 @@ + + - - + + - - + + - - @@ -3467,14 +3536,6 @@ - - - - - - - - @@ -3482,46 +3543,73 @@ + + + + + - + - - - - - - - + + + + + + + + + + - + - + + + + + + + - + - - + + + + + + + + + + + + + + + @@ -3530,89 +3618,62 @@ - - - - - - - - - - - - - - - + + - - + + - - + + - - - + - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -3621,16 +3682,16 @@ + + - - + + - - + + - - @@ -3639,42 +3700,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -3683,32 +3744,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3716,35 +3769,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -3753,16 +3806,16 @@ + + - - + + - - + + - - @@ -3771,42 +3824,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -3815,16 +3868,16 @@ + + - - + + - - + + - - @@ -3833,14 +3886,6 @@ - - - - - - - - @@ -3848,27 +3893,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3877,16 +3930,16 @@ + + - - + + - - + + - - @@ -3895,14 +3948,6 @@ - - - - - - - - @@ -3910,27 +3955,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3939,16 +3992,16 @@ + + - - + + - - + + - - @@ -3957,14 +4010,6 @@ - - - - - - - - @@ -3972,35 +4017,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4009,16 +4054,16 @@ + + - - + + - - + + - - @@ -4027,50 +4072,42 @@ - - - - - - - - - + - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4079,16 +4116,16 @@ + + - - + + - - + + - - @@ -4097,42 +4134,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -4141,32 +4178,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -4174,27 +4203,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4203,16 +4240,16 @@ + + - - + + - - + + - - @@ -4221,14 +4258,6 @@ - - - - - - - - @@ -4236,27 +4265,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4265,16 +4302,16 @@ + + - - + + - - + + - - @@ -4283,14 +4320,6 @@ - - - - - - - - @@ -4298,27 +4327,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4327,16 +4364,16 @@ + + - - + + - - + + - - @@ -4345,14 +4382,6 @@ - - - - - - - - @@ -4360,27 +4389,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4389,16 +4426,16 @@ + + - - + + - - + + - - @@ -4407,14 +4444,6 @@ - - - - - - - - @@ -4422,27 +4451,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4451,16 +4488,16 @@ + + - - + + - - + + - - @@ -4469,14 +4506,6 @@ - - - - - - - - @@ -4484,27 +4513,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4513,16 +4550,16 @@ + + - - + + - - + + - - @@ -4531,14 +4568,6 @@ - - - - - - - - @@ -4546,27 +4575,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4575,16 +4612,16 @@ + + - - + + - - + + - - @@ -4593,14 +4630,6 @@ - - - - - - - - @@ -4608,27 +4637,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4637,16 +4674,16 @@ + + - - + + - - + + - - @@ -4655,14 +4692,6 @@ - - - - - - - - @@ -4670,35 +4699,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4707,16 +4736,16 @@ + + - - + + - - + + - - @@ -4725,42 +4754,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -4769,16 +4798,16 @@ + + - - + + - - + + - - @@ -4787,14 +4816,6 @@ - - - - - - - - @@ -4802,27 +4823,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4831,16 +4860,16 @@ + + - - + + - - + + - - @@ -4849,14 +4878,6 @@ - - - - - - - - @@ -4864,27 +4885,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4893,16 +4922,16 @@ + + - - + + - - + + - - @@ -4911,14 +4940,6 @@ - - - - - - - - @@ -4926,27 +4947,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4955,16 +4984,16 @@ + + - - + + - - + + - - @@ -4973,14 +5002,6 @@ - - - - - - - - @@ -4988,35 +5009,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -5025,16 +5046,16 @@ + + - - + + - - + + - - @@ -5043,70 +5064,67 @@ - - - - - - - - - + - - - - - - - - -Master generated with legacy objdictedt. DS-302 + DS-401 - + + + + + + + - + - - - - - - - + - - - + - + + + + + + + + + - - + + + + + + + + + + @@ -5115,16 +5133,16 @@ + + - - + + - - + + - - @@ -5133,14 +5151,6 @@ - - - - - - - - @@ -5148,60 +5158,60 @@ + + + + + - + - - - - - - - - - - - + + - - + + - - - - - - - + + + + + + + - - + + + + + + @@ -5210,27 +5220,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -5239,16 +5257,16 @@ + + - - + + - - + + - - @@ -5257,14 +5275,6 @@ - - - - - - - - @@ -5272,27 +5282,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -5301,16 +5319,16 @@ + + - - + + - - + + - - @@ -5319,14 +5337,6 @@ - - - - - - - - @@ -5334,27 +5344,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -5363,16 +5381,16 @@ + + - - + + - - + + - - @@ -5381,14 +5399,6 @@ - - - - - - - - @@ -5396,27 +5406,35 @@ + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -5425,16 +5443,16 @@ + + - - + + - - + + - - @@ -5443,16 +5461,16 @@ + + - - + + - - + + - - @@ -5461,38 +5479,38 @@ - - - - - - - - + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -5501,16 +5519,16 @@ + + - - + + - - + + - - @@ -5519,14 +5537,6 @@ - - - - - - - - @@ -5534,27 +5544,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -5563,16 +5581,16 @@ + + - - + + - - + + - - @@ -5581,14 +5599,6 @@ - - - - - - - - @@ -5596,19 +5606,9 @@ - - - - - - - - -DS-401 - - -Master + + diff --git a/tests/od/legacy-compare/jsontest.json b/tests/od/profile-ds302-test.json similarity index 50% rename from tests/od/legacy-compare/jsontest.json rename to tests/od/profile-ds302-test.json index 2c15950..6a89cf6 100644 --- a/tests/od/legacy-compare/jsontest.json +++ b/tests/od/profile-ds302-test.json @@ -2,72 +2,26 @@ "$id": "od data", "$version": "1", "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-08-08T21:01:43.205000", - "name": "jsontest", - "description": "Full JSON test", + "$tool": "odg 3.4", + "$date": "2024-02-27T18:20:27.355891", + "name": "profile_ds302_test", + "description": "Test DS302 and test profile", "type": "master", "id": 0, "profile": "Test", "dictionary": [ - { - "index": "0x00A0", // 160 - "name": "UNSIGNED32[100-200]", - "struct": "record", - "mandatory": false, - "default": 0, - "size": 32, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "save": true, - "comment": "T0" - }, - { - "name": "Type", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "comment": "T1", - "value": 7 - }, - { - "name": "Minimum Value", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "comment": "T2", - "value": 100 - }, - { - "name": "Maximum Value", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "save": true, - "comment": "T3", - "value": 200 - } - ] - }, { "index": "0x1000", // 4096 "name": "Device Type", "struct": "var", "group": "built-in", "mandatory": true, - "callback": true, "sub": [ { "name": "Device Type", "type": "UNSIGNED32", // 7 "access": "ro", "pdo": false, - "save": true, - "comment": "Device type", "value": 0 } ] @@ -84,8 +38,6 @@ "type": "UNSIGNED8", // 5 "access": "ro", "pdo": true, - "save": true, - "comment": "Err register", "value": 0 } ] @@ -96,22 +48,18 @@ "struct": "record", "group": "built-in", "mandatory": true, - "callback": true, "sub": [ { "name": "Number of Entries", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "R0" + "pdo": false }, { "name": "Vendor ID", "type": "UNSIGNED32", // 7 "access": "ro", "pdo": false, - "comment": "R1", "value": 0 }, { @@ -119,7 +67,6 @@ "type": "UNSIGNED32", // 7 "access": "ro", "pdo": false, - "comment": "R2", "value": 0 }, { @@ -127,7 +74,6 @@ "type": "UNSIGNED32", // 7 "access": "ro", "pdo": false, - "comment": "R3", "value": 0 }, { @@ -135,835 +81,228 @@ "type": "UNSIGNED32", // 7 "access": "ro", "pdo": false, - "save": true, - "comment": "R4", "value": 0 } ] }, { - "index": "0x1280", // 4736 - "name": "Client SDO %d Parameter[(idx)]", - "struct": "nrecord", - "group": "built-in", + "index": "0x5000", // 20480 + "name": "VAR: Global Interrupt Enable Digital", + "struct": "var", + "group": "profile", "mandatory": false, - "callback": true, - "incr": 1, - "nbmax": 256, + "unused": true, "sub": [ { - "name": "Number of Entries", + "name": "Global Interrupt Enable Digital Sure", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": false, + "default": true + } + ] + }, + { + "index": "0x5100", // 20736 + "name": "RECORD: Software position limit", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of things", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "SDO0" + "pdo": false }, { - "name": "COB ID Client to Server (Transmit SDO)", - "type": "UNSIGNED32", // 7 + "name": "Minimal position limit", + "type": "INTEGER32", // 4 "access": "rw", "pdo": false, - "comment": "SDO1", - "value": 0 + "default": 16 }, { - "name": "COB ID Server to Client (Receive SDO)", - "type": "UNSIGNED32", // 7 + "name": "Maximal position limit", + "type": "INTEGER32", // 4 "access": "rw", "pdo": false, - "comment": "SDO2", - "value": 0 - }, + "default": 23 + } + ] + }, + { + "index": "0x5180", // 20864 + "name": "RECORD: AL Action", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "AL %d Action %d[(idx,sub)]", + "type": "INTEGER16", // 3 + "access": "rw", + "pdo": false, + "nbmax": 6, + "default": 16 + }, + "sub": [ { - "name": "Node ID of the SDO Server", + "name": "Number of subs", "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false, - "save": true, - "comment": "SDO3", - "value": 0 + "access": "ro", + "pdo": false } ] }, { - "index": "0x1281", // 4737 - // "name": "Client SDO 2 Parameter" - "repeat": true, - "struct": "nrecord", - "callback": true, + "index": "0x5200", // 20992 + "name": "ARRAY: Acceleration Value", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Acceleration Value Channel %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "ro", + "pdo": true, + "nbmax": 4, + "default": 16 + }, "sub": [ { - // "name": "Number of Entries" - // "type": "UNSIGNED8" // 5 - "save": true, - "comment": "client0" - }, - { - // "name": "COB ID Client to Server (Transmit SDO)" - // "type": "UNSIGNED32" // 7 - "comment": "client1", - "value": 0 - }, - { - // "name": "COB ID Server to Client (Receive SDO)" - // "type": "UNSIGNED32" // 7 - "comment": "client2", - "value": 0 - }, - { - // "name": "Node ID of the SDO Server" - // "type": "UNSIGNED8" // 5 - "save": true, - "comment": "client3", - "value": 0 + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false } ] }, { - "index": "0x1282", // 4738 - // "name": "Client SDO 3 Parameter" - "repeat": true, - "struct": "nrecord", + "index": "0x5300", // 21248 + "name": "NVAR: Test profile %d[(idx)]", + "struct": "nvar", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, "sub": [ { - // "name": "Number of Entries" - // "type": "UNSIGNED8" // 5 - }, - { - // "name": "COB ID Client to Server (Transmit SDO)" - // "type": "UNSIGNED32" // 7 - "value": 0 - }, - { - // "name": "COB ID Server to Client (Receive SDO)" - // "type": "UNSIGNED32" // 7 - "value": 0 - }, + "name": "Device Type %d and %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": true, + "default": 16 + } + ] + }, + { + "index": "0x5400", // 21504 + "name": "NARRAY: CAM%d Low Limit[(idx)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "each": { + "name": "CAM%d Low Limit Channel %d[(idx,sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "nbmax": 4, + "default": 16 + }, + "sub": [ { - // "name": "Node ID of the SDO Server" - // "type": "UNSIGNED8" // 5 - "value": 0 + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false } ] }, { - "index": "0x1400", // 5120 - "name": "Receive PDO %d Parameter[(idx)]", + "index": "0x5500", // 21760 + "name": "NRECORD: Receive PDO %d Parameter[(idx)]", "struct": "nrecord", - "group": "built-in", + "group": "profile", "mandatory": false, - "callback": true, - "incr": 1, - "nbmax": 512, + "unused": true, + "incr": 2, + "nbmax": 8, "sub": [ { "name": "Highest SubIndex Supported", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "rpdo0" + "pdo": false }, { "name": "COB ID used by PDO", "type": "UNSIGNED32", // 7 "access": "rw", "pdo": false, - "comment": "rpdo1", - "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]", - "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + "default": 12 }, { "name": "Transmission Type", "type": "UNSIGNED8", // 5 "access": "rw", - "pdo": false, - "comment": "rpdo2", - "value": 0 + "pdo": false }, { "name": "Inhibit Time", "type": "UNSIGNED16", // 6 "access": "rw", - "pdo": false, - "comment": "rpdo3", - "value": 0 + "pdo": false }, { "name": "Compatibility Entry", "type": "UNSIGNED8", // 5 "access": "rw", - "pdo": false, - "comment": "rpdo4", - "value": 0 + "pdo": false }, { "name": "Event Timer", "type": "UNSIGNED16", // 6 "access": "rw", - "pdo": false, - "comment": "rpdo5", - "value": 0 + "pdo": false }, { "name": "SYNC start value", "type": "UNSIGNED8", // 5 "access": "rw", "pdo": false, - "save": true, - "comment": "rpdo6", - "value": 0 - } - ] - }, - { - "index": "0x1401", // 5121 - // "name": "Receive PDO 2 Parameter" - "repeat": true, - "struct": "nrecord", - "callback": true, - "sub": [ - { - // "name": "Highest SubIndex Supported" - // "type": "UNSIGNED8" // 5 - "save": true, - "comment": "c0" - }, - { - // "name": "COB ID used by PDO" - // "type": "UNSIGNED32" // 7 - "comment": "c1", - "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" - }, - { - // "name": "Transmission Type" - // "type": "UNSIGNED8" // 5 - "comment": "c2", - "value": 0 - }, - { - // "name": "Inhibit Time" - // "type": "UNSIGNED16" // 6 - "comment": "c3", - "value": 0 - }, - { - // "name": "Compatibility Entry" - // "type": "UNSIGNED8" // 5 - "comment": "c4", - "value": 0 - }, - { - // "name": "Event Timer" - // "type": "UNSIGNED16" // 6 - "comment": "c5", - "value": 0 - }, - { - // "name": "SYNC start value" - // "type": "UNSIGNED8" // 5 - "save": true, - "comment": "c6", - "value": 0 + "default": 16 } ] }, { - "index": "0x1402", // 5122 - // "name": "Receive PDO 3 Parameter" - "repeat": true, + "index": "0x5580", // 21888 + "name": "NRECORD: AL %d Action[(idx)]", "struct": "nrecord", - "sub": [ - { - // "name": "Highest SubIndex Supported" - // "type": "UNSIGNED8" // 5 - }, - { - // "name": "COB ID used by PDO" - // "type": "UNSIGNED32" // 7 - "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" - }, - { - // "name": "Transmission Type" - // "type": "UNSIGNED8" // 5 - "value": 0 - }, - { - // "name": "Inhibit Time" - // "type": "UNSIGNED16" // 6 - "value": 0 - }, - { - // "name": "Compatibility Entry" - // "type": "UNSIGNED8" // 5 - "value": 0 - }, - { - // "name": "Event Timer" - // "type": "UNSIGNED16" // 6 - "value": 0 - }, - { - // "name": "SYNC start value" - // "type": "UNSIGNED8" // 5 - "value": 0 - } - ] - }, - { - "index": "0x1600", // 5632 - "name": "Receive PDO %d Mapping[(idx)]", - "struct": "narray", - "group": "built-in", + "group": "profile", "mandatory": false, - "incr": 1, - "nbmax": 512, + "unused": true, + "incr": 2, + "nbmax": 16, "each": { - "name": "PDO %d Mapping for an application object %d[(idx,sub)]", + "name": "AL %d Action %d[(idx,sub)]", "type": "UNSIGNED32", // 7 "access": "rw", "pdo": false, - "nbmin": 0, - "nbmax": 64 + "nbmax": 3, + "default": 16 }, "sub": [ { - "name": "Number of Entries", + "name": "Number of Actions", "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false - } - ] - }, - { - "index": "0x1601", // 5633 - // "name": "Receive PDO 2 Mapping" - "repeat": true, - "struct": "narray", - "sub": [] - }, - { - "index": "0x1602", // 5634 - // "name": "Receive PDO 3 Mapping" - "repeat": true, - "struct": "narray", - "sub": [] - }, - { - "index": "0x1F20", // 7968 - "name": "Store DCF", - "struct": "array", - "group": "ds302", - "mandatory": false, - "callback": true, - "each": { - "name": "Store DCF for node %d[(sub)]", - "type": "DOMAIN", // 15 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "save": true, - "comment": "DCF0", - "buffer_size": 3 - }, - { - // "name": "Store DCF for node 1" - // "type": "DOMAIN" // 15 - "save": true, - "comment": "DCF1", - "value": "", - "buffer_size": 12 - }, - { - // "name": "Store DCF for node 2" - // "type": "DOMAIN" // 15 - "save": true, - "comment": "DCF2", - "value": "", - "buffer_size": 14 - } - ] - }, - { - "index": "0x1F21", // 7969 - "name": "Storage Format", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Storage Format for Node %d[(sub)]", - "type": "INTEGER8", // 2 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x1F22", // 7970 - "name": "Concise DCF", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Concise DCF for Node %d[(sub)]", - "type": "DOMAIN", // 15 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x1F50", // 8016 - "name": "Download Program Data", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Program Number %d[(sub)]", - "type": "DOMAIN", // 15 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of different programs supported on the node", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x1F51", // 8017 - "name": "Program Control", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Program Number %d[(sub)]", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of different programs on the node", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x1F52", // 8018 - "name": "Verify Application Software", - "struct": "record", - "group": "ds302", - "mandatory": false, - "unused": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Application software date", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false - }, - { - "name": "Application sofware time", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false - } - ] - }, - { - "index": "0x1F53", // 8019 - "name": "Expected Application SW Date", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Program number %d[(sub)]", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of different programs on the node", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x1F55", // 8021 - "name": "Expected Application SW Time", - "struct": "array", - "group": "ds302", - "mandatory": false, - "unused": true, - "each": { - "name": "Program number %d[(sub)]", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false, - "nbmax": 127 - }, - "sub": [ - { - "name": "Number of different programs on the node", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x2000", // 8192 - "name": "VAR", - "struct": "var", - "mandatory": false, - "callback": true, - "sub": [ - { - "name": "VAR", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": true, - "save": true, - "comment": "VAR", - "value": 0 - } - ] - }, - { - "index": "0x2001", // 8193 - "name": "ARRAY", - "struct": "array", - "mandatory": false, - "callback": true, - "each": { - "name": "ARRAY %d[(sub)]", - "type": "INTEGER8", // 2 - "access": "ro", - "pdo": true, - "nbmax": 254 - }, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "save": true, - "comment": "A0" - }, - { - // "name": "ARRAY 1" - // "type": "INTEGER8" // 2 - "comment": "A1", - "value": 1 - }, - { - // "name": "ARRAY 2" - // "type": "INTEGER8" // 2 - "comment": "A2", - "value": 2 - } - ] - }, - { - "index": "0x2002", // 8194 - "name": "RECORD", - "struct": "record", - "mandatory": false, - "callback": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "save": true, - "comment": "R0" - }, - { - "name": "RECORD 1", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": true, - "comment": "R1", - "value": 7 - }, - { - "name": "RECORD 2", - "type": "INTEGER16", // 3 - "access": "rw", - "pdo": true, - "save": true, - "comment": "R2", - "value": 42 - } - ] - }, - { - "index": "0x5000", // 20480 - "name": "VAR: Global Interrupt Enable Digital", - "struct": "var", - "group": "profile", - "mandatory": false, - "unused": true, - "sub": [ - { - "name": "Global Interrupt Enable Digital Sure", - "type": "BOOLEAN", // 1 - "access": "rw", - "pdo": false, - "default": true - } - ] - }, - { - "index": "0x5100", // 20736 - "name": "RECORD: Software position limit", - "struct": "record", - "group": "profile", - "mandatory": false, - "unused": true, - "sub": [ - { - "name": "Number of things", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Minimal position limit", - "type": "INTEGER32", // 4 - "access": "rw", - "pdo": false, - "default": 16 - }, - { - "name": "Maximal position limit", - "type": "INTEGER32", // 4 - "access": "rw", - "pdo": false, - "default": 23 - } - ] - }, - { - "index": "0x5180", // 20864 - "name": "RECORD: AL Action", - "struct": "record", - "group": "profile", - "mandatory": false, - "unused": true, - "each": { - "name": "AL %d Action %d[(idx,sub)]", - "type": "INTEGER16", // 3 - "access": "rw", - "pdo": false, - "nbmax": 6, - "default": 16 - }, - "sub": [ - { - "name": "Number of subs", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x5200", // 20992 - "name": "ARRAY: Acceleration Value", - "struct": "array", - "group": "profile", - "mandatory": false, - "unused": true, - "each": { - "name": "Acceleration Value Channel %d[(sub)]", - "type": "INTEGER16", // 3 - "access": "ro", - "pdo": true, - "nbmax": 254, - "default": 16 - }, - "sub": [ - { - "name": "Number of Available Channels", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x5300", // 21248 - "name": "NVAR: Test profile %d[(idx)]", - "struct": "nvar", - "group": "profile", - "mandatory": false, - "unused": true, - "incr": 2, - "nbmax": 8, - "sub": [ - { - "name": "Device Type %d and %d[(idx,sub)]", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": true, - "default": 16 - } - ] - }, - { - "index": "0x5400", // 21504 - "name": "NARRAY: CAM%d Low Limit[(idx)]", - "struct": "narray", - "group": "profile", - "mandatory": false, - "unused": true, - "incr": 2, - "nbmax": 8, - "each": { - "name": "CAM%d Low Limit Channel %d[(idx,sub)]", - "type": "INTEGER32", // 4 - "access": "rw", - "pdo": false, - "nbmax": 254, - "default": 16 - }, - "sub": [ - { - "name": "Number of Available Channels", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - } - ] - }, - { - "index": "0x5500", // 21760 - "name": "NRECORD: Receive PDO %d Parameter[(idx)]", - "struct": "nrecord", - "group": "profile", - "mandatory": false, - "unused": true, - "incr": 2, - "nbmax": 8, - "sub": [ - { - "name": "Highest SubIndex Supported", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "COB ID used by PDO", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false, - "default": 12 - }, - { - "name": "Transmission Type", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false - }, - { - "name": "Inhibit Time", - "type": "UNSIGNED16", // 6 - "access": "rw", - "pdo": false - }, - { - "name": "Compatibility Entry", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false - }, - { - "name": "Event Timer", - "type": "UNSIGNED16", // 6 - "access": "rw", - "pdo": false - }, - { - "name": "SYNC start value", - "type": "UNSIGNED8", // 5 - "access": "rw", - "pdo": false, - "default": 16 - } - ] - }, - { - "index": "0x5580", // 21888 - "name": "NRECORD: AL %d Action[(idx)]", - "struct": "nrecord", - "group": "profile", - "mandatory": false, - "unused": true, - "incr": 2, - "nbmax": 16, - "each": { - "name": "AL %d Action %d[(idx,sub)]", - "type": "UNSIGNED32", // 7 - "access": "rw", - "pdo": false, - "nbmax": 3, - "default": 16 - }, - "sub": [ - { - "name": "Number of Actions", - "type": "UNSIGNED8", // 5 - "access": "ro", + "access": "ro", "pdo": false } ] @@ -991,17 +330,14 @@ "struct": "var", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "sub": [ { "name": "Global Interrupt Enable Digital Sure", "type": "BOOLEAN", // 1 "access": "rw", "pdo": false, - "save": true, - "comment": "Nope", - "default": true, - "value": false + "default": true } ] }, @@ -1011,34 +347,27 @@ "struct": "record", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "sub": [ { "name": "Number of things", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "Rec0" + "pdo": false }, { "name": "Minimal position limit", "type": "INTEGER32", // 4 "access": "rw", "pdo": false, - "comment": "Rec1", - "default": 16, - "value": 1 + "default": 16 }, { "name": "Maximal position limit", "type": "INTEGER32", // 4 "access": "rw", "pdo": false, - "save": true, - "comment": "Rec2", - "default": 23, - "value": 2 + "default": 23 } ] }, @@ -1048,7 +377,7 @@ "struct": "record", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "each": { "name": "AL %d Action %d[(idx,sub)]", "type": "INTEGER16", // 3 @@ -1057,51 +386,12 @@ "nbmax": 6, "default": 16 }, - "sub": [ - { - "name": "Number of subs", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false, - "save": true, - "comment": "r0" - }, - { - // "name": "AL 1 Action 1" - // "type": "INTEGER16" // 3 - "comment": "r1", - "value": 1 - }, - { - // "name": "AL 1 Action 2" - // "type": "INTEGER16" // 3 - "comment": "r2", - "value": 2 - }, - { - // "name": "AL 1 Action 3" - // "type": "INTEGER16" // 3 - "comment": "r3", - "value": 3 - }, - { - // "name": "AL 1 Action 4" - // "type": "INTEGER16" // 3 - "comment": "r4", - "value": 4 - }, - { - // "name": "AL 1 Action 5" - // "type": "INTEGER16" // 3 - "comment": "r5", - "value": 5 - }, + "sub": [ { - // "name": "AL 1 Action 6" - // "type": "INTEGER16" // 3 - "save": true, - "comment": "r6", - "value": 6 + "name": "Number of subs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false } ] }, @@ -1111,13 +401,13 @@ "struct": "array", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "each": { "name": "Acceleration Value Channel %d[(sub)]", "type": "INTEGER16", // 3 "access": "ro", "pdo": true, - "nbmax": 254, + "nbmax": 4, "default": 16 }, "sub": [ @@ -1125,22 +415,7 @@ "name": "Number of Available Channels", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "A0" - }, - { - // "name": "Acceleration Value Channel 1" - // "type": "INTEGER16" // 3 - "comment": "A1", - "value": 1 - }, - { - // "name": "Acceleration Value Channel 2" - // "type": "INTEGER16" // 3 - "save": true, - "comment": "A2", - "value": 16 + "pdo": false } ] }, @@ -1150,7 +425,7 @@ "struct": "nvar", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "incr": 2, "nbmax": 8, "sub": [ @@ -1159,23 +434,7 @@ "type": "UNSIGNED32", // 7 "access": "ro", "pdo": true, - "save": true, - "comment": "dt10", - "default": 16, - "value": 1 - } - ] - }, - { - "index": "0x6302", // 25346 - // "name": "NVAR: Test profile 2" - "repeat": true, - "struct": "nvar", - "sub": [ - { - // "name": "Device Type 2 and 0" - // "type": "UNSIGNED32" // 7 - "value": 12 + "default": 16 } ] }, @@ -1185,7 +444,7 @@ "struct": "narray", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "incr": 2, "nbmax": 8, "each": { @@ -1193,7 +452,7 @@ "type": "INTEGER32", // 4 "access": "rw", "pdo": false, - "nbmax": 254, + "nbmax": 4, "default": 16 }, "sub": [ @@ -1201,39 +460,17 @@ "name": "Number of Available Channels", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "n0" - }, - { - // "name": "CAM1 Low Limit Channel 1" - // "type": "INTEGER32" // 4 - "comment": "n1", - "value": 1 - }, - { - // "name": "CAM1 Low Limit Channel 2" - // "type": "INTEGER32" // 4 - "save": true, - "comment": "n2", - "value": 2 + "pdo": false } ] }, - { - "index": "0x6402", // 25602 - // "name": "NARRAY: CAM2 Low Limit" - "repeat": true, - "struct": "narray", - "sub": [] - }, { "index": "0x6500", // 25856 "name": "NRECORD: Receive PDO %d Parameter[(idx)]", "struct": "nrecord", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "incr": 2, "nbmax": 8, "sub": [ @@ -1241,77 +478,55 @@ "name": "Highest SubIndex Supported", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "nr0" + "pdo": false }, { "name": "COB ID used by PDO", "type": "UNSIGNED32", // 7 "access": "rw", "pdo": false, - "comment": "nr1", - "default": 12, - "value": 1 + "default": 12 }, { "name": "Transmission Type", "type": "UNSIGNED8", // 5 "access": "rw", - "pdo": false, - "comment": "nr2", - "value": 2 + "pdo": false }, { "name": "Inhibit Time", "type": "UNSIGNED16", // 6 "access": "rw", - "pdo": false, - "comment": "nr3", - "value": 3 + "pdo": false }, { "name": "Compatibility Entry", "type": "UNSIGNED8", // 5 "access": "rw", - "pdo": false, - "comment": "nr4", - "value": 4 + "pdo": false }, { "name": "Event Timer", "type": "UNSIGNED16", // 6 "access": "rw", - "pdo": false, - "comment": "nr5", - "value": 5 + "pdo": false }, { "name": "SYNC start value", "type": "UNSIGNED8", // 5 "access": "rw", "pdo": false, - "save": true, - "comment": "nr6", - "default": 16, - "value": 6 + "default": 16 } ] }, - { - "index": "0x6502", // 25858 - // "name": "NRECORD: Receive PDO 2 Parameter" - "repeat": true, - "struct": "nrecord", - "sub": [] - }, { "index": "0x6580", // 25984 "name": "NRECORD: AL %d Action[(idx)]", "struct": "nrecord", "group": "profile", "mandatory": false, - "callback": true, + "unused": true, "incr": 2, "nbmax": 16, "each": { @@ -1319,7 +534,7 @@ "type": "UNSIGNED32", // 7 "access": "rw", "pdo": false, - "nbmax": 6, + "nbmax": 3, "default": 16 }, "sub": [ @@ -1327,47 +542,7 @@ "name": "Number of Actions", "type": "UNSIGNED8", // 5 "access": "ro", - "pdo": false, - "save": true, - "comment": "com0" - }, - { - // "name": "AL 1 Action 1" - // "type": "UNSIGNED32" // 7 - "comment": "com1", - "value": 1 - }, - { - // "name": "AL 1 Action 2" - // "type": "UNSIGNED32" // 7 - "comment": "com2", - "value": 2 - }, - { - // "name": "AL 1 Action 3" - // "type": "UNSIGNED32" // 7 - "comment": "com3", - "value": 3 - }, - { - // "name": "AL 1 Action 4" - // "type": "UNSIGNED32" // 7 - "save": true, - "comment": "com4", - "value": 4 - }, - { - // "name": "AL 1 Action 5" - // "type": "UNSIGNED32" // 7 - "comment": "com5", - "value": 5 - }, - { - // "name": "AL 1 Action 6" - // "type": "UNSIGNED32" // 7 - "save": true, - "comment": "com6", - "value": 6 + "pdo": false } ] }, @@ -1378,15 +553,202 @@ "group": "profile", "mandatory": false, "profile_callback": true, + "unused": true, "sub": [ { "name": "Producer Heartbeat Time", "type": "UNSIGNED16", // 6 "access": "rw", - "pdo": false, - "save": true, - "comment": "Comment for it", - "value": 1 + "pdo": false + } + ] + }, + { + "index": "0x1F20", // 7968 + "name": "Store DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Store DCF for node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F21", // 7969 + "name": "Storage Format", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Storage Format for Node %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F22", // 7970 + "name": "Concise DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Concise DCF for Node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F50", // 8016 + "name": "Download Program Data", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs supported on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F51", // 8017 + "name": "Program Control", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F52", // 8018 + "name": "Verify Application Software", + "struct": "record", + "group": "ds302", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Application software date", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + }, + { + "name": "Application sofware time", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x1F53", // 8019 + "name": "Expected Application SW Date", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F55", // 8021 + "name": "Expected Application SW Time", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false } ] } diff --git a/tests/od/legacy-compare/jsontest.od b/tests/od/profile-ds302-test.od similarity index 53% rename from tests/od/legacy-compare/jsontest.od rename to tests/od/profile-ds302-test.od index 7e30a3c..45ce58e 100644 --- a/tests/od/legacy-compare/jsontest.od +++ b/tests/od/profile-ds302-test.od @@ -1,18 +1,39 @@ - - + + + + + + + - + + + + + + + + + - - + + + + + + + + + + @@ -25,38 +46,38 @@ - - - - - - - - + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -65,16 +86,16 @@ + + - - + + - - + + - - @@ -87,16 +108,16 @@ + + - - + + - - + + - - @@ -109,38 +130,38 @@ - - - - - - - - + + + + + - + - - - - - - - + + + + + + + + + + @@ -149,24 +170,24 @@ + + + + + + - + - - + + - - - - - - @@ -175,38 +196,38 @@ - - - - - - - - + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -215,72 +236,72 @@ + + + + + + - + - - + + - - - - - - - + - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + - - + + + + + + + + + + @@ -293,120 +314,120 @@ - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + - - - - - - + + - - + + - - + + - - + + - - - - - - + + - + + + + + + + + + - - - - + + + + + - - - - - - - - - - + + + + + + + + + - - + + + + + + + + + + @@ -415,16 +436,16 @@ + + - - + + - - + + - - @@ -437,16 +458,16 @@ + + - - + + - - + + - - @@ -455,16 +476,16 @@ + + - - + + - - + + - - @@ -473,16 +494,16 @@ + + - - + + - - + + - - @@ -491,16 +512,16 @@ + + - - + + - - + + - - @@ -509,16 +530,16 @@ + + - - + + - - + + - - @@ -531,46 +552,46 @@ - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + - - + + + + + + + + + + @@ -579,24 +600,24 @@ + + + + + + - + - - + + - - - - - - @@ -605,30 +626,22 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + @@ -639,2233 +652,774 @@ - - - - - - + + - - + + - - + + + + + + + + + + + - + - - - - - - - - - - - + + - - + + - - + + + + + + + + + + + - + - + - - - - - - - - - - - + + - - - - - - + + + + + + - - + + - - - - - - + + + + + + - - + + - - + + - - - + + + + + + + + + + + + - + - - - - - - - - - - - + + - - - - - - + + + + + + - - - - - - + + - - + + + + + + + + + + + + + + + - + - + - - - - - - - - - - - + + - - - - - - + + + + - + + + - - - - - - + + - - + + + + + + + + + + - - - - - - - - - + - - + + + + + + + + + + - - - - - - + + - - + + - - + + + + + + - - - - - - - - - + - - + + + + + + + + + + - - - - - - + + - - - - - - + + + + + + - - - - - - + + - + + + + + + + + + - - - - - - - - - + - - + + + + + + + + + + - - - - - - + + - - - - - - + + + + + + - - + + - - - - - - - - + + + + - - + + - - + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + - - + + - + - - + + - - + + - - + + - + - - + + - - + + - - - - - - - - - - - - + + + + + + + - - + + - - + + - - + + - - - - - + + + + - - - - - - + + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - + - + - - - + - - - - - + - - + + - - + + + + + + + - + - + - - - - - + + + + - - - - - - + + - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - - - - - + + - - - - - - - + + + + + + + - - + + + + + + @@ -2874,60 +1428,60 @@ + + + + + - + - - - - - - - - - - - + + - - + + - - - - - - - + + + + + + + - - + + + + + + @@ -2936,27 +1490,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2965,16 +1527,16 @@ + + - - + + - - + + - - @@ -2983,14 +1545,6 @@ - - - - - - - - @@ -2998,27 +1552,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3027,16 +1589,16 @@ + + - - + + - - + + - - @@ -3045,14 +1607,6 @@ - - - - - - - - @@ -3060,27 +1614,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3089,16 +1651,16 @@ + + - - + + - - + + - - @@ -3107,14 +1669,6 @@ - - - - - - - - @@ -3122,27 +1676,35 @@ + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -3151,16 +1713,16 @@ + + - - + + - - + + - - @@ -3169,16 +1731,16 @@ + + - - + + - - + + - - @@ -3187,38 +1749,38 @@ - - - - - - - - + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -3227,16 +1789,16 @@ + + - - + + - - + + - - @@ -3245,14 +1807,6 @@ - - - - - - - - @@ -3260,27 +1814,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3289,16 +1851,16 @@ + + - - + + - - + + - - @@ -3307,14 +1869,6 @@ - - - - - - - - @@ -3322,19 +1876,9 @@ - - - - - - - - - - - - + + diff --git a/tests/od/profile-ds302.json b/tests/od/profile-ds302.json new file mode 100644 index 0000000..acafdc3 --- /dev/null +++ b/tests/od/profile-ds302.json @@ -0,0 +1,278 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-27T18:18:29.388619", + "name": "profile_ds302", + "description": "Test DS-302 profile", + "type": "master", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1F20", // 7968 + "name": "Store DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Store DCF for node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F21", // 7969 + "name": "Storage Format", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Storage Format for Node %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F22", // 7970 + "name": "Concise DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Concise DCF for Node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F50", // 8016 + "name": "Download Program Data", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs supported on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F51", // 8017 + "name": "Program Control", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F52", // 8018 + "name": "Verify Application Software", + "struct": "record", + "group": "ds302", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Application software date", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + }, + { + "name": "Application sofware time", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x1F53", // 8019 + "name": "Expected Application SW Date", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F55", // 8021 + "name": "Expected Application SW Time", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/legacy-master-ds302.od b/tests/od/profile-ds302.od similarity index 88% rename from tests/od/legacy-master-ds302.od rename to tests/od/profile-ds302.od index 36e13ba..6600972 100644 --- a/tests/od/legacy-master-ds302.od +++ b/tests/od/profile-ds302.od @@ -1,46 +1,64 @@ - - + + + + + + + -Master generated with legacy objdictedit. DS-302 Profile - + + + + + + + - + - - - - - - - - - + - + - + + + + + + + + + - - + + + + + + + + + + @@ -49,16 +67,16 @@ + + - - + + - - + + - - @@ -67,14 +85,6 @@ - - - - - - - - @@ -82,27 +92,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -111,16 +129,16 @@ + + - - + + - - + + - - @@ -129,14 +147,6 @@ - - - - - - - - @@ -144,27 +154,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -173,16 +191,16 @@ + + - - + + - - + + - - @@ -191,14 +209,6 @@ - - - - - - - - @@ -206,27 +216,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -235,16 +253,16 @@ + + - - + + - - + + - - @@ -253,14 +271,6 @@ - - - - - - - - @@ -268,27 +278,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -297,16 +315,16 @@ + + - - + + - - + + - - @@ -315,14 +333,6 @@ - - - - - - - - @@ -330,27 +340,35 @@ + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -359,16 +377,16 @@ + + - - + + - - + + - - @@ -377,16 +395,16 @@ + + - - + + - - + + - - @@ -395,38 +413,38 @@ - - - - - - - - + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -435,16 +453,16 @@ + + - - + + - - + + - - @@ -453,14 +471,6 @@ - - - - - - - - @@ -468,27 +478,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -497,16 +515,16 @@ + + - - + + - - + + - - @@ -515,14 +533,6 @@ - - - - - - - - @@ -530,19 +540,9 @@ - - - - - - - - - - - -Master + + diff --git a/tests/od/profile-ds401.json b/tests/od/profile-ds401.json new file mode 100644 index 0000000..901180f --- /dev/null +++ b/tests/od/profile-ds401.json @@ -0,0 +1,1960 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-27T19:25:31.741401", + "name": "profile_ds401", + "description": "Test DS-401 profile", + "type": "master", + "id": 0, + "profile": "DS-401", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x6000", // 24576 + "name": "Read Inputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Inputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6002", // 24578 + "name": "Polarity Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6003", // 24579 + "name": "Filter Constant Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Constant Input 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6005", // 24581 + "name": "Global Interrupt Enable Digital", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Global Interrupt Enable Digital", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x6006", // 24582 + "name": "Interrupt Mask Any Change 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6007", // 24583 + "name": "Interrupt Mask Low to High 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6008", // 24584 + "name": "Interrupt Mask High to Low 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 8 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6020", // 24608 + "name": "Read Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Read Single Input 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6030", // 24624 + "name": "Polarity Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Polarity Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6038", // 24632 + "name": "Filter Constant Input Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Filter Constant Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6050", // 24656 + "name": "Interrupt Mask Input Any Change Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6060", // 24672 + "name": "Interrupt Mask Input Low to High Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6070", // 24688 + "name": "Interrupt Mask Input High to Low Bit 0x%X to 0x%X[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Interrupt Mask Any Change Input bit 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 1 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6100", // 24832 + "name": "Read Inputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Inputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6102", // 24834 + "name": "Polarity Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6103", // 24835 + "name": "Filter Constant Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Constant Input 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6106", // 24838 + "name": "Interrupt Mask Any Change 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6107", // 24839 + "name": "Interrupt Mask Low to High 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6108", // 24840 + "name": "Interrupt Mask High to Low 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 16 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6120", // 24864 + "name": "Read Input 4 Byte", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Read Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6122", // 24866 + "name": "Polarity Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6123", // 24867 + "name": "Filter Constant Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6126", // 24870 + "name": "Interrupt Mask Input Any Change 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Any Change Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6127", // 24871 + "name": "Interrupt Mask Input Low to High 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Low to High Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6128", // 24872 + "name": "Interrupt Mask Input High to Low 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt High to Low Input 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Input 32 bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6200", // 25088 + "name": "Write Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6202", // 25090 + "name": "Change Polarity Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Change Polarity Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6206", // 25094 + "name": "Error Mode Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6207", // 25095 + "name": "Error Value Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6208", // 25096 + "name": "Filter Mask Outputs 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*8-7,sub*8)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6220", // 25120 + "name": "Write Outputs Bit %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Write Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6240", // 25152 + "name": "Change Polarity Outputs Bit %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Change Polarity Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6250", // 25168 + "name": "Error Mode Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Error Mode Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6260", // 25184 + "name": "Error Value Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Error Value Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6270", // 25200 + "name": "Filter Constant Outputs Lines %d to %d[(idx*128-127,idx*128)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 1, + "nbmax": 8, + "each": { + "name": "Filter Constant Outputs 0x%X[((idx-1)*128+sub)]", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true, + "nbmax": 128 + }, + "sub": [ + { + "name": "Number of Output 1 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6300", // 25344 + "name": "Write Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6302", // 25346 + "name": "Change Polarity Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Change Polarity Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6306", // 25350 + "name": "Error Mode Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6307", // 25351 + "name": "Error Value Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6308", // 25352 + "name": "Filter Mask Outputs 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*16-15,sub*16)]", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6320", // 25376 + "name": "Write Output 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Write Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6322", // 25378 + "name": "Change Polarity Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Polarity Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6326", // 25382 + "name": "Error Mode Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6327", // 25383 + "name": "Error Value Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Value Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6328", // 25384 + "name": "Filter Mask Outputs 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Filter Mask Outputs 0x%X to 0x%X[(sub*32-31,sub*32)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Output 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6400", // 25600 + "name": "Read Analogue Input 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6401", // 25601 + "name": "Read Analogue Input 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6402", // 25602 + "name": "Read Analogue Input 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6403", // 25603 + "name": "Read Analogue Input Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input Float", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6404", // 25604 + "name": "Read Manufacturer specific Analogue Input", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL64", // 17 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6410", // 25616 + "name": "Write Analogue Output 8 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 8 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6411", // 25617 + "name": "Write Analogue Output 16 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Input 16 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6412", // 25618 + "name": "Write Analogue Output 32 Bit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs 32 Bit", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6413", // 25619 + "name": "Write Analogue Output Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs Float", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6414", // 25620 + "name": "Write Manufacturer specific Analogue Output", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL64", // 17 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6421", // 25633 + "name": "Interrupt Trigger Selection", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analog Inputs 0x%X[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6422", // 25634 + "name": "Analogue Input Interrupt Source", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Interrupt Source Bank 0x%X[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Interrupt Source Bank", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6423", // 25635 + "name": "Analogue Input Global Interrupt Enable", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Analogue Input Global Interrupt Enable", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": true + } + ] + }, + { + "index": "0x6424", // 25636 + "name": "Analogue Input Interrupt Upper Limit Interger", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6425", // 25637 + "name": "Analogue Input Interrupt Lower Limit Interger", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6426", // 25638 + "name": "Analogue Input Interrupt Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6427", // 25639 + "name": "Analogue Input Interrupt Negative Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6428", // 25640 + "name": "Analogue Input Interrupt Positive Delta Unsigned", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6429", // 25641 + "name": "Analogue Input Interrupt Upper Limit Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642A", // 25642 + "name": "Analogue Input Interrupt Lower Limit Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642B", // 25643 + "name": "Analogue Input Interrupt Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642C", // 25644 + "name": "Analogue Input Interrupt Negative Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642D", // 25645 + "name": "Analogue Input Interrupt Positive Delta Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642E", // 25646 + "name": "Analogue Input Offset Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x642F", // 25647 + "name": "Analogue Input Scaling Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6430", // 25648 + "name": "Analogue Input SI unit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6431", // 25649 + "name": "Analogue Input Offset Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6432", // 25650 + "name": "Analogue Input Scaling Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Input %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Inputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6441", // 25665 + "name": "Analogue Output Offset Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6442", // 25666 + "name": "Analogue Output Scaling Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6443", // 25667 + "name": "Analogue Output Error Mode", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Error Mode Analogue Output %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6444", // 25668 + "name": "Analogue Output Error Value Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6445", // 25669 + "name": "Analogue Output Error Value Float", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "REAL32", // 8 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6446", // 25670 + "name": "Analogue Output Offset Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6447", // 25671 + "name": "Analogue Output Scaling Integer", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6450", // 25680 + "name": "Analogue Output SI Unit", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Analogue Output %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": true, + "nbmax": 254 + }, + "sub": [ + { + "name": "Number of Analogue Outputs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/master-ds401.od b/tests/od/profile-ds401.od similarity index 90% rename from tests/od/master-ds401.od rename to tests/od/profile-ds401.od index 47eb143..6586530 100644 --- a/tests/od/master-ds401.od +++ b/tests/od/profile-ds401.od @@ -1,18 +1,39 @@ - - + + + + + + + - + + + + + + + + + - - + + + + + + + + + + @@ -21,16 +42,16 @@ + + - - + + - - + + - - @@ -39,14 +60,6 @@ - - - - - - - - @@ -54,27 +67,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -83,32 +104,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -116,27 +129,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -145,16 +166,16 @@ + + - - + + - - + + - - @@ -163,14 +184,6 @@ - - - - - - - - @@ -178,89 +191,75 @@ + + + + + - + - + - - - - - - - - - - - + + - - + + - - - - - + - - - - - - - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -269,32 +268,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -302,67 +293,97 @@ + + + + + - + - - - - - - - + + + + + + + + + + - + + + + + + + - + - - + + + + + + + + + + + + + + + - + - + - - - - - - - + + + + + + + + + + @@ -371,16 +392,16 @@ + + + + + + - - - - - - @@ -389,14 +410,6 @@ - - - - - - - - @@ -404,27 +417,43 @@ + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -433,16 +462,16 @@ + + - - + + - - + + - - @@ -451,42 +480,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -495,16 +532,16 @@ + + - - + + - - + + - - @@ -513,42 +550,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -557,16 +602,16 @@ + + - - + + - - + + - - @@ -575,42 +620,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -619,16 +672,16 @@ + + - - + + - - + + - - @@ -637,42 +690,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -681,16 +742,16 @@ + + - - + + - - + + - - @@ -699,42 +760,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -743,16 +812,16 @@ + + - - + + - - + + - - @@ -761,42 +830,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -805,32 +874,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -838,27 +899,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -867,16 +936,16 @@ + + - - + + - - + + - - @@ -885,14 +954,6 @@ - - - - - - - - @@ -900,27 +961,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -929,16 +998,16 @@ + + - - + + - - + + - - @@ -947,14 +1016,6 @@ - - - - - - - - @@ -962,27 +1023,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -991,32 +1060,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -1024,27 +1085,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1053,16 +1122,16 @@ + + - - + + - - + + - - @@ -1071,14 +1140,6 @@ - - - - - - - - @@ -1086,35 +1147,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -1123,16 +1184,16 @@ + + - - + + - - + + - - @@ -1141,42 +1202,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -1185,16 +1246,16 @@ + + - - + + - - + + - - @@ -1203,42 +1264,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -1247,16 +1308,16 @@ + + - - + + - - + + - - @@ -1265,14 +1326,6 @@ - - - - - - - - @@ -1280,27 +1333,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1309,16 +1370,16 @@ + + - - + + - - + + - - @@ -1327,14 +1388,6 @@ - - - - - - - - @@ -1342,27 +1395,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1371,16 +1432,16 @@ + + - - + + - - + + - - @@ -1389,14 +1450,6 @@ - - - - - - - - @@ -1404,27 +1457,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1433,16 +1494,16 @@ + + - - + + - - + + - - @@ -1451,14 +1512,6 @@ - - - - - - - - @@ -1466,27 +1519,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1495,16 +1556,16 @@ + + - - + + - - + + - - @@ -1513,14 +1574,6 @@ - - - - - - - - @@ -1528,45 +1581,53 @@ + + + + + - + - - - - - - - + + - - + + + + + + + + + + + + - - + + - - + + - - @@ -1575,14 +1636,6 @@ - - - - - - - - @@ -1590,27 +1643,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1619,16 +1680,16 @@ + + - - + + - - + + - - @@ -1637,14 +1698,6 @@ - - - - - - - - @@ -1652,27 +1705,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1681,16 +1742,16 @@ + + - - + + - - + + - - @@ -1699,14 +1760,6 @@ - - - - - - - - @@ -1714,27 +1767,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1743,16 +1804,16 @@ + + - - + + - - + + - - @@ -1761,14 +1822,6 @@ - - - - - - - - @@ -1776,27 +1829,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -1805,16 +1866,16 @@ + + - - + + - - + + - - @@ -1823,14 +1884,6 @@ - - - - - - - - @@ -1838,27 +1891,43 @@ + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1867,16 +1936,16 @@ + + - - + + - - + + - - @@ -1885,42 +1954,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1929,16 +2006,16 @@ + + - - + + - - + + - - @@ -1947,42 +2024,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -1991,16 +2076,16 @@ + + - - + + - - + + - - @@ -2009,42 +2094,50 @@ - - - - - - - - - + + + + + + - + - + + + + + - - - - - + + + + - - + + + + + + + + + + @@ -2053,16 +2146,16 @@ + + - - + + - - + + - - @@ -2071,41 +2164,33 @@ - - - - - - - - - + + + + + + - + - + - - - - - - - + + @@ -2113,8 +2198,16 @@ - - + + + + + + + + + + @@ -2123,16 +2216,16 @@ + + - - + + - - + + - - @@ -2141,14 +2234,6 @@ - - - - - - - - @@ -2156,27 +2241,35 @@ + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2185,16 +2278,16 @@ + + - - + + - - + + - - @@ -2203,14 +2296,6 @@ - - - - - - - - @@ -2218,27 +2303,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2247,16 +2340,16 @@ + + - - + + - - + + - - @@ -2265,14 +2358,6 @@ - - - - - - - - @@ -2280,27 +2365,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2309,16 +2402,16 @@ + + - - + + - - + + - - @@ -2327,14 +2420,6 @@ - - - - - - - - @@ -2342,35 +2427,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2379,16 +2464,16 @@ + + - - + + - - + + - - @@ -2397,42 +2482,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2441,16 +2526,16 @@ + + - - + + - - + + - - @@ -2459,14 +2544,6 @@ - - - - - - - - @@ -2474,35 +2551,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2511,16 +2588,16 @@ + + - - + + - - + + - - @@ -2529,42 +2606,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2573,16 +2650,16 @@ + + - - + + - - + + - - @@ -2591,14 +2668,6 @@ - - - - - - - - @@ -2606,27 +2675,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2635,16 +2712,16 @@ + + - - + + - - + + - - @@ -2653,14 +2730,6 @@ - - - - - - - - @@ -2668,35 +2737,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -2705,16 +2774,16 @@ + + - - + + - - + + - - @@ -2723,42 +2792,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -2767,16 +2836,16 @@ + + - - + + - - + + - - @@ -2785,42 +2854,42 @@ - - - - - - - - - + + + + + + - + - - - - - - - + + + + + + + + + + @@ -2829,32 +2898,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2862,27 +2923,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2891,32 +2960,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2924,27 +2985,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -2953,32 +3022,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -2986,27 +3047,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3015,32 +3084,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3048,27 +3109,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3077,32 +3146,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3110,27 +3171,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3139,16 +3208,16 @@ + + - - + + - - + + - - @@ -3157,14 +3226,6 @@ - - - - - - - - @@ -3172,27 +3233,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3201,32 +3270,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3234,27 +3295,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3263,16 +3332,16 @@ + + - - + + - - + + - - @@ -3281,14 +3350,6 @@ - - - - - - - - @@ -3296,27 +3357,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3325,16 +3394,16 @@ + + - - + + - - + + - - @@ -3343,14 +3412,6 @@ - - - - - - - - @@ -3358,27 +3419,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3387,16 +3456,16 @@ + + - - + + - - + + - - @@ -3405,14 +3474,6 @@ - - - - - - - - @@ -3420,27 +3481,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3449,16 +3518,16 @@ + + - - + + - - + + - - @@ -3467,14 +3536,6 @@ - - - - - - - - @@ -3482,46 +3543,73 @@ + + + + + - + - - - - - - - + + + + + + + + + + - + - + + + + + + + - + - - + + + + + + + + + + + + + + + @@ -3530,89 +3618,62 @@ - - - - - - - + + - - - - - - + + - - - - - + - - - + - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -3621,16 +3682,16 @@ + + - - + + - - + + - - @@ -3639,42 +3700,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -3683,32 +3744,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -3716,35 +3769,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -3753,16 +3806,16 @@ + + - - + + - - + + - - @@ -3771,42 +3824,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -3815,31 +3868,23 @@ + + - - + + - - + + - - - - - - - - - - - - + + @@ -3848,27 +3893,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3877,16 +3930,16 @@ + + - - + + - - + + - - @@ -3895,14 +3948,6 @@ - - - - - - - - @@ -3910,27 +3955,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -3939,16 +3992,16 @@ + + - - + + - - + + - - @@ -3957,14 +4010,6 @@ - - - - - - - - @@ -3972,35 +4017,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4009,16 +4054,16 @@ + + - - + + - - + + - - @@ -4027,50 +4072,42 @@ - - - - - - - - - + - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4079,16 +4116,16 @@ + + - - + + - - + + - - @@ -4097,42 +4134,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -4141,32 +4178,24 @@ + + - - + + - - + + - - - + - - - - - - - - @@ -4174,27 +4203,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4203,16 +4240,16 @@ + + - - + + - - + + - - @@ -4221,14 +4258,6 @@ - - - - - - - - @@ -4236,27 +4265,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4265,16 +4302,16 @@ + + - - + + - - + + - - @@ -4283,14 +4320,6 @@ - - - - - - - - @@ -4298,27 +4327,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4327,16 +4364,16 @@ + + - - + + - - + + - - @@ -4345,14 +4382,6 @@ - - - - - - - - @@ -4360,27 +4389,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4389,16 +4426,16 @@ + + - - + + - - + + - - @@ -4407,14 +4444,6 @@ - - - - - - - - @@ -4422,27 +4451,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4451,16 +4488,16 @@ + + - - + + - - + + - - @@ -4469,14 +4506,6 @@ - - - - - - - - @@ -4484,27 +4513,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4513,16 +4550,16 @@ + + - - + + - - + + - - @@ -4531,14 +4568,6 @@ - - - - - - - - @@ -4546,27 +4575,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4575,16 +4612,16 @@ + + - - + + - - + + - - @@ -4593,14 +4630,6 @@ - - - - - - - - @@ -4608,27 +4637,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4637,16 +4674,16 @@ + + - - + + - - + + - - @@ -4655,14 +4692,6 @@ - - - - - - - - @@ -4670,35 +4699,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -4707,16 +4736,16 @@ + + - - + + - - + + - - @@ -4725,42 +4754,42 @@ - - - - - - - - - + + + + + + - - + + - - + + - - - - - - - + + + + + + + + + + @@ -4769,16 +4798,16 @@ + + - - + + - - + + - - @@ -4787,14 +4816,6 @@ - - - - - - - - @@ -4802,27 +4823,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4831,16 +4860,16 @@ + + - - + + - - + + - - @@ -4849,14 +4878,6 @@ - - - - - - - - @@ -4864,27 +4885,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4893,16 +4922,16 @@ + + - - + + - - + + - - @@ -4911,14 +4940,6 @@ - - - - - - - - @@ -4926,27 +4947,35 @@ + + + + + - + - - - - - - - + + + + + + + + + + @@ -4955,16 +4984,16 @@ + + - - + + - - + + - - @@ -4973,14 +5002,6 @@ - - - - - - - - @@ -4988,35 +5009,35 @@ - - - - - - - - - - + + - - + + - + - - + + - - + + + + + + + + + + @@ -5025,16 +5046,16 @@ + + - - + + - - + + - - @@ -5043,62 +5064,41 @@ - - - - - - - - - + - - - - - - - - -Node generated with objdictedit. DS-401 Profile - + + + + + + + - + - - - - - - - + - + - + -DS-401 - - -Master diff --git a/tests/od/profile-test.json b/tests/od/profile-test.json new file mode 100644 index 0000000..b6127d1 --- /dev/null +++ b/tests/od/profile-test.json @@ -0,0 +1,567 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-27T18:19:39.536308", + "name": "profile_test", + "description": "Custom test profile", + "type": "master", + "id": 0, + "profile": "Test", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x5000", // 20480 + "name": "VAR: Global Interrupt Enable Digital", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Global Interrupt Enable Digital Sure", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": false, + "default": true + } + ] + }, + { + "index": "0x5100", // 20736 + "name": "RECORD: Software position limit", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of things", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Minimal position limit", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "default": 16 + }, + { + "name": "Maximal position limit", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "default": 23 + } + ] + }, + { + "index": "0x5180", // 20864 + "name": "RECORD: AL Action", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "AL %d Action %d[(idx,sub)]", + "type": "INTEGER16", // 3 + "access": "rw", + "pdo": false, + "nbmax": 6, + "default": 16 + }, + "sub": [ + { + "name": "Number of subs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x5200", // 20992 + "name": "ARRAY: Acceleration Value", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Acceleration Value Channel %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "ro", + "pdo": true, + "nbmax": 4, + "default": 16 + }, + "sub": [ + { + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x5300", // 21248 + "name": "NVAR: Test profile %d[(idx)]", + "struct": "nvar", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "sub": [ + { + "name": "Device Type %d and %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": true, + "default": 16 + } + ] + }, + { + "index": "0x5400", // 21504 + "name": "NARRAY: CAM%d Low Limit[(idx)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "each": { + "name": "CAM%d Low Limit Channel %d[(idx,sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "nbmax": 4, + "default": 16 + }, + "sub": [ + { + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x5500", // 21760 + "name": "NRECORD: Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": 12 + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "default": 16 + } + ] + }, + { + "index": "0x5580", // 21888 + "name": "NRECORD: AL %d Action[(idx)]", + "struct": "nrecord", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 16, + "each": { + "name": "AL %d Action %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 3, + "default": 16 + }, + "sub": [ + { + "name": "Number of Actions", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x5600", // 22016 + "name": "Producer Heartbeat Time", + "struct": "var", + "group": "profile", + "mandatory": false, + "profile_callback": true, + "unused": true, + "sub": [ + { + "name": "Producer Heartbeat Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x6000", // 24576 + "name": "VAR: Global Interrupt Enable Digital", + "struct": "var", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Global Interrupt Enable Digital Sure", + "type": "BOOLEAN", // 1 + "access": "rw", + "pdo": false, + "default": true + } + ] + }, + { + "index": "0x6100", // 24832 + "name": "RECORD: Software position limit", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of things", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Minimal position limit", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "default": 16 + }, + { + "name": "Maximal position limit", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "default": 23 + } + ] + }, + { + "index": "0x6180", // 24960 + "name": "RECORD: AL Action", + "struct": "record", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "AL %d Action %d[(idx,sub)]", + "type": "INTEGER16", // 3 + "access": "rw", + "pdo": false, + "nbmax": 6, + "default": 16 + }, + "sub": [ + { + "name": "Number of subs", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6200", // 25088 + "name": "ARRAY: Acceleration Value", + "struct": "array", + "group": "profile", + "mandatory": false, + "unused": true, + "each": { + "name": "Acceleration Value Channel %d[(sub)]", + "type": "INTEGER16", // 3 + "access": "ro", + "pdo": true, + "nbmax": 4, + "default": 16 + }, + "sub": [ + { + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6300", // 25344 + "name": "NVAR: Test profile %d[(idx)]", + "struct": "nvar", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "sub": [ + { + "name": "Device Type %d and %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": true, + "default": 16 + } + ] + }, + { + "index": "0x6400", // 25600 + "name": "NARRAY: CAM%d Low Limit[(idx)]", + "struct": "narray", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "each": { + "name": "CAM%d Low Limit Channel %d[(idx,sub)]", + "type": "INTEGER32", // 4 + "access": "rw", + "pdo": false, + "nbmax": 4, + "default": 16 + }, + "sub": [ + { + "name": "Number of Available Channels", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6500", // 25856 + "name": "NRECORD: Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 8, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": 12 + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "default": 16 + } + ] + }, + { + "index": "0x6580", // 25984 + "name": "NRECORD: AL %d Action[(idx)]", + "struct": "nrecord", + "group": "profile", + "mandatory": false, + "unused": true, + "incr": 2, + "nbmax": 16, + "each": { + "name": "AL %d Action %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 3, + "default": 16 + }, + "sub": [ + { + "name": "Number of Actions", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x6600", // 26112 + "name": "Producer Heartbeat Time", + "struct": "var", + "group": "profile", + "mandatory": false, + "profile_callback": true, + "unused": true, + "sub": [ + { + "name": "Producer Heartbeat Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/profile-test.od b/tests/od/profile-test.od new file mode 100644 index 0000000..24599d9 --- /dev/null +++ b/tests/od/profile-test.od @@ -0,0 +1,1374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/slave-ds302.json b/tests/od/slave-ds302.json new file mode 100644 index 0000000..376e583 --- /dev/null +++ b/tests/od/slave-ds302.json @@ -0,0 +1,1124 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-28T00:45:00.339681", + "name": "Slave", + "description": "Slave with DS-302", + "type": "slave", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1200", // 4608 + "name": "Server SDO Parameter", + "struct": "record", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID Client to Server (Receive SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x600\"", + "value": "\"$NODEID+0x600\"" + }, + { + "name": "COB ID Server to Client (Transmit SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x580\"", + "value": "\"$NODEID+0x580\"" + } + ] + }, + { + "index": "0x1400", // 5120 + "name": "Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1401", // 5121 + // "name": "Receive PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1402", // 5122 + // "name": "Receive PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1403", // 5123 + // "name": "Receive PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1600", // 5632 + "name": "Receive PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for an application object %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1601", // 5633 + // "name": "Receive PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1602", // 5634 + // "name": "Receive PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1603", // 5635 + // "name": "Receive PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1800", // 6144 + "name": "Transmit PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1801", // 6145 + // "name": "Transmit PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1802", // 6146 + // "name": "Transmit PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1803", // 6147 + // "name": "Transmit PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1A00", // 6656 + "name": "Transmit PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A01", // 6657 + // "name": "Transmit PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A02", // 6658 + // "name": "Transmit PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A03", // 6659 + // "name": "Transmit PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1F20", // 7968 + "name": "Store DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Store DCF for node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F21", // 7969 + "name": "Storage Format", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Storage Format for Node %d[(sub)]", + "type": "INTEGER8", // 2 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F22", // 7970 + "name": "Concise DCF", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Concise DCF for Node %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F50", // 8016 + "name": "Download Program Data", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs supported on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F51", // 8017 + "name": "Program Control", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program Number %d[(sub)]", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F52", // 8018 + "name": "Verify Application Software", + "struct": "record", + "group": "ds302", + "mandatory": false, + "unused": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Application software date", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + }, + { + "name": "Application sofware time", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false + } + ] + }, + { + "index": "0x1F53", // 8019 + "name": "Expected Application SW Date", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + }, + { + "index": "0x1F55", // 8021 + "name": "Expected Application SW Time", + "struct": "array", + "group": "ds302", + "mandatory": false, + "unused": true, + "each": { + "name": "Program number %d[(sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmax": 127 + }, + "sub": [ + { + "name": "Number of different programs on the node", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/slave-ds302.od b/tests/od/slave-ds302.od new file mode 100644 index 0000000..8d2a830 --- /dev/null +++ b/tests/od/slave-ds302.od @@ -0,0 +1,747 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/slave-emcy.json b/tests/od/slave-emcy.json new file mode 100644 index 0000000..6470513 --- /dev/null +++ b/tests/od/slave-emcy.json @@ -0,0 +1,952 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-28T00:45:42.455357", + "name": "Slave", + "description": "Slave with emergency support", + "type": "slave", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1014", // 4116 + "name": "Emergency COB ID", + "struct": "var", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Emergency COB ID", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "\"$NODEID+0x80\"", + "value": "\"$NODEID+0x80\"" + } + ] + }, + { + "index": "0x1200", // 4608 + "name": "Server SDO Parameter", + "struct": "record", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID Client to Server (Receive SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x600\"", + "value": "\"$NODEID+0x600\"" + }, + { + "name": "COB ID Server to Client (Transmit SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x580\"", + "value": "\"$NODEID+0x580\"" + } + ] + }, + { + "index": "0x1400", // 5120 + "name": "Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1401", // 5121 + // "name": "Receive PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1402", // 5122 + // "name": "Receive PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1403", // 5123 + // "name": "Receive PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1600", // 5632 + "name": "Receive PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for an application object %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1601", // 5633 + // "name": "Receive PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1602", // 5634 + // "name": "Receive PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1603", // 5635 + // "name": "Receive PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1800", // 6144 + "name": "Transmit PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1801", // 6145 + // "name": "Transmit PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1802", // 6146 + // "name": "Transmit PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1803", // 6147 + // "name": "Transmit PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1A00", // 6656 + "name": "Transmit PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A01", // 6657 + // "name": "Transmit PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A02", // 6658 + // "name": "Transmit PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A03", // 6659 + // "name": "Transmit PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/slave-emcy.od b/tests/od/slave-emcy.od new file mode 100644 index 0000000..6591915 --- /dev/null +++ b/tests/od/slave-emcy.od @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/legacy-compare/slave.json b/tests/od/slave-heartbeat.json similarity index 97% rename from tests/od/legacy-compare/slave.json rename to tests/od/slave-heartbeat.json index b3d22be..8da43aa 100644 --- a/tests/od/legacy-compare/slave.json +++ b/tests/od/slave-heartbeat.json @@ -2,10 +2,10 @@ "$id": "od data", "$version": "1", "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-11-11T21:47:10.137423", + "$tool": "odg 3.4", + "$date": "2024-02-28T00:46:18.111927", "name": "Slave", - "description": "Slave created with objdictedit", + "description": "Slave with heartbeat", "type": "slave", "id": 0, "profile": "None", @@ -85,6 +85,23 @@ } ] }, + { + "index": "0x1017", // 4119 + "name": "Producer Heartbeat Time", + "struct": "var", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "sub": [ + { + "name": "Producer Heartbeat Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, { "index": "0x1200", // 4608 "name": "Server SDO Parameter", diff --git a/tests/od/legacy-compare/slave.od b/tests/od/slave-heartbeat.od similarity index 83% rename from tests/od/legacy-compare/slave.od rename to tests/od/slave-heartbeat.od index bf9126d..0958efa 100644 --- a/tests/od/legacy-compare/slave.od +++ b/tests/od/slave-heartbeat.od @@ -1,17 +1,27 @@ - - + + + + + + + + + -Slave created with objdictedit - + + + + + - + @@ -19,19 +29,19 @@ - + - + - + @@ -42,7 +52,7 @@ - + @@ -53,7 +63,7 @@ - + @@ -64,7 +74,7 @@ - + @@ -75,7 +85,7 @@ - + @@ -88,7 +98,7 @@ - + @@ -101,7 +111,7 @@ - + @@ -114,7 +124,7 @@ - + @@ -127,7 +137,7 @@ - + @@ -138,7 +148,7 @@ - + @@ -149,7 +159,7 @@ - + @@ -160,7 +170,7 @@ - + @@ -171,7 +181,7 @@ - + @@ -184,7 +194,7 @@ - + @@ -197,7 +207,7 @@ - + @@ -210,7 +220,7 @@ - + @@ -222,16 +232,10 @@ - - - + - + - + - - - -Slave diff --git a/tests/od/slave-nodeguarding.json b/tests/od/slave-nodeguarding.json new file mode 100644 index 0000000..353b5bf --- /dev/null +++ b/tests/od/slave-nodeguarding.json @@ -0,0 +1,967 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-28T00:46:53.202929", + "name": "Slave", + "description": "Slave with Node Guarding", + "type": "slave", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x100C", // 4108 + "name": "Guard Time", + "struct": "var", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Guard Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x100D", // 4109 + "name": "Life Time Factor", + "struct": "var", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Life Time Factor", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1200", // 4608 + "name": "Server SDO Parameter", + "struct": "record", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID Client to Server (Receive SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x600\"", + "value": "\"$NODEID+0x600\"" + }, + { + "name": "COB ID Server to Client (Transmit SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x580\"", + "value": "\"$NODEID+0x580\"" + } + ] + }, + { + "index": "0x1400", // 5120 + "name": "Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1401", // 5121 + // "name": "Receive PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1402", // 5122 + // "name": "Receive PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1403", // 5123 + // "name": "Receive PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1600", // 5632 + "name": "Receive PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for an application object %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1601", // 5633 + // "name": "Receive PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1602", // 5634 + // "name": "Receive PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1603", // 5635 + // "name": "Receive PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1800", // 6144 + "name": "Transmit PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1801", // 6145 + // "name": "Transmit PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1802", // 6146 + // "name": "Transmit PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1803", // 6147 + // "name": "Transmit PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1A00", // 6656 + "name": "Transmit PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A01", // 6657 + // "name": "Transmit PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A02", // 6658 + // "name": "Transmit PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A03", // 6659 + // "name": "Transmit PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/slave-nodeguarding.od b/tests/od/slave-nodeguarding.od new file mode 100644 index 0000000..92beb67 --- /dev/null +++ b/tests/od/slave-nodeguarding.od @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/slave-sync.json b/tests/od/slave-sync.json new file mode 100644 index 0000000..904f867 --- /dev/null +++ b/tests/od/slave-sync.json @@ -0,0 +1,969 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-28T00:47:25.014478", + "name": "Slave", + "description": "Slave with SYNC", + "type": "slave", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1005", // 4101 + "name": "SYNC COB ID", + "struct": "var", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "sub": [ + { + "name": "SYNC COB ID", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1006", // 4102 + "name": "Communication / Cycle Period", + "struct": "var", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "sub": [ + { + "name": "Communication Cycle Period", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1200", // 4608 + "name": "Server SDO Parameter", + "struct": "record", + "group": "built-in", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID Client to Server (Receive SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x600\"", + "value": "\"$NODEID+0x600\"" + }, + { + "name": "COB ID Server to Client (Transmit SDO)", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "default": "\"$NODEID+0x580\"", + "value": "\"$NODEID+0x580\"" + } + ] + }, + { + "index": "0x1400", // 5120 + "name": "Receive PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1401", // 5121 + // "name": "Receive PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1402", // 5122 + // "name": "Receive PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1403", // 5123 + // "name": "Receive PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X00\"%(base+2),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1600", // 5632 + "name": "Receive PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for an application object %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1601", // 5633 + // "name": "Receive PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1602", // 5634 + // "name": "Receive PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1603", // 5635 + // "name": "Receive PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for an application object 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for an application object 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1800", // 6144 + "name": "Transmit PDO %d Parameter[(idx)]", + "struct": "nrecord", + "group": "built-in", + "mandatory": false, + "profile_callback": true, + "incr": 1, + "nbmax": 512, + "sub": [ + { + "name": "Highest SubIndex Supported", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "COB ID used by PDO", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "default": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]", + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + "name": "Transmission Type", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Inhibit Time", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Compatibility Entry", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "Event Timer", + "type": "UNSIGNED16", // 6 + "access": "rw", + "pdo": false, + "value": 0 + }, + { + "name": "SYNC start value", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1801", // 6145 + // "name": "Transmit PDO 2 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1802", // 6146 + // "name": "Transmit PDO 3 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1803", // 6147 + // "name": "Transmit PDO 4 Parameter" + "repeat": true, + "struct": "nrecord", + "sub": [ + { + // "name": "Highest SubIndex Supported" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "COB ID used by PDO" + // "type": "UNSIGNED32" // 7 + "value": "{True:\"$NODEID+0x%X80\"%(base+1),False:0x80000000}[base<4]" + }, + { + // "name": "Transmission Type" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Inhibit Time" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "Compatibility Entry" + // "type": "UNSIGNED8" // 5 + "value": 0 + }, + { + // "name": "Event Timer" + // "type": "UNSIGNED16" // 6 + "value": 0 + }, + { + // "name": "SYNC start value" + // "type": "UNSIGNED8" // 5 + "value": 0 + } + ] + }, + { + "index": "0x1A00", // 6656 + "name": "Transmit PDO %d Mapping[(idx)]", + "struct": "narray", + "group": "built-in", + "mandatory": false, + "incr": 1, + "nbmax": 512, + "each": { + "name": "PDO %d Mapping for a process data variable %d[(idx,sub)]", + "type": "UNSIGNED32", // 7 + "access": "rw", + "pdo": false, + "nbmin": 0, + "nbmax": 64 + }, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": false + }, + { + // "name": "PDO 1 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 1 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A01", // 6657 + // "name": "Transmit PDO 2 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 2 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 2 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A02", // 6658 + // "name": "Transmit PDO 3 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 3 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 3 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + }, + { + "index": "0x1A03", // 6659 + // "name": "Transmit PDO 4 Mapping" + "repeat": true, + "struct": "narray", + "sub": [ + { + // "name": "Number of Entries" + // "type": "UNSIGNED8" // 5 + }, + { + // "name": "PDO 4 Mapping for a process data variable 1" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 2" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 3" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 4" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 5" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 6" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 7" + // "type": "UNSIGNED32" // 7 + "value": 0 + }, + { + // "name": "PDO 4 Mapping for a process data variable 8" + // "type": "UNSIGNED32" // 7 + "value": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/slave-sync.od b/tests/od/slave-sync.od new file mode 100644 index 0000000..b8d0c83 --- /dev/null +++ b/tests/od/slave-sync.od @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/od/slave.json b/tests/od/slave.json index 88a99de..b8e8ab7 100644 --- a/tests/od/slave.json +++ b/tests/od/slave.json @@ -2,10 +2,10 @@ "$id": "od data", "$version": "1", "$description": "Canfestival object dictionary data", - "$tool": "odg 3.2", - "$date": "2022-08-08T21:01:56.260539", - "name": "Slave", - "description": "Slave created with objdictedit", + "$tool": "odg 3.4", + "$date": "2024-02-27T00:53:43.165540", + "name": "slave", + "description": "Slave OD", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave.od b/tests/od/slave.od index bf9126d..49e95ca 100644 --- a/tests/od/slave.od +++ b/tests/od/slave.od @@ -1,37 +1,43 @@ - - + + + + + + + + + -Slave created with objdictedit - + + + + + - + - - - - - + - + @@ -42,7 +48,7 @@ - + @@ -53,7 +59,7 @@ - + @@ -64,7 +70,7 @@ - + @@ -75,7 +81,7 @@ - + @@ -88,7 +94,7 @@ - + @@ -101,7 +107,7 @@ - + @@ -114,7 +120,7 @@ - + @@ -127,7 +133,7 @@ - + @@ -138,7 +144,7 @@ - + @@ -149,7 +155,7 @@ - + @@ -160,7 +166,7 @@ - + @@ -171,7 +177,7 @@ - + @@ -184,7 +190,7 @@ - + @@ -197,7 +203,7 @@ - + @@ -210,7 +216,7 @@ - + @@ -222,16 +228,10 @@ - + - + - + - - - - - -Slave diff --git a/tests/od/strings.json b/tests/od/strings.json new file mode 100644 index 0000000..45393f9 --- /dev/null +++ b/tests/od/strings.json @@ -0,0 +1,260 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$date": "2024-02-28T01:14:57.869658", + "name": "a", + "description": "Complicated strings with unicode", + "type": "master", + "id": 0, + "profile": "None", + "default_string_size": 10, + "dictionary": [ + { + "index": "0x2000", // 8192 + "name": "brod", + "struct": "var", + "mandatory": false, + "sub": [ + { + "name": "brod", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "value": 1 + } + ] + }, + { + "index": "0x2001", // 8193 + "name": "br\u00f8d", + "struct": "var", + "mandatory": false, + "sub": [ + { + "name": "br\u00f8d", + "type": "UNSIGNED8", // 5 + "access": "rw", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x2002", // 8194 + "name": "OCTET_STRING", + "struct": "record", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Plain", + "type": "OCTET_STRING", // 10 + "access": "rw", + "pdo": true, + "value": "abcd" + }, + { + "name": "Latin1", + "type": "OCTET_STRING", // 10 + "access": "rw", + "pdo": true, + "value": "abc\u00f8" + }, + { + "name": "Unicode", + "type": "OCTET_STRING", // 10 + "access": "rw", + "pdo": true, + "value": "abc\u2713" + } + ] + }, + { + "index": "0x2003", // 8195 + "name": "DOMAIN", + "struct": "record", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Plain", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": true, + "value": "abcd" + }, + { + "name": "Latin1", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": true, + "value": "abc\u00f8" + }, + { + "name": "Unicode", + "type": "DOMAIN", // 15 + "access": "rw", + "pdo": true, + "value": "abc\u2713" + } + ] + }, + { + "index": "0x2004", // 8196 + "name": "UNICODE_STRING", + "struct": "record", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Plain", + "type": "UNICODE_STRING", // 11 + "access": "rw", + "pdo": true, + "value": "abcd" + }, + { + "name": "Latin1", + "type": "UNICODE_STRING", // 11 + "access": "rw", + "pdo": true, + "value": "abc\u00f8" + }, + { + "name": "Unicode", + "type": "UNICODE_STRING", // 11 + "access": "rw", + "pdo": true, + "value": "abc\u2713" + } + ] + }, + { + "index": "0x2006", // 8198 + "name": "VISIBLE_STRING", + "struct": "record", + "mandatory": false, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Plain", + "type": "VISIBLE_STRING", // 9 + "access": "rw", + "pdo": true, + "value": "abcd" + }, + { + "name": "Latin1", + "type": "VISIBLE_STRING", // 9 + "access": "rw", + "pdo": true, + "value": "abc\u00f8" + }, + { + "name": "Unicode", + "type": "VISIBLE_STRING", // 9 + "access": "rw", + "pdo": true, + "value": "abc\u2713" + } + ] + }, + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/od/unicode.json b/tests/od/unicode.json index a09c5ae..5960f76 100644 --- a/tests/od/unicode.json +++ b/tests/od/unicode.json @@ -21,82 +21,7 @@ "type": "UNICODE_STRING", // 11 "access": "rw", "pdo": true, - "value": "Hæ?" - } - ] - }, - { - "index": "0x1000", // 4096 - "name": "Device Type", - "struct": "var", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Device Type", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - }, - { - "index": "0x1018", // 4120 - "name": "Identity", - "struct": "record", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Number of Entries", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": false - }, - { - "name": "Vendor ID", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Product Code", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Revision Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - }, - { - "name": "Serial Number", - "type": "UNSIGNED32", // 7 - "access": "ro", - "pdo": false, - "value": 0 - } - ] - }, - { - "index": "0x1001", // 4097 - "name": "Error Register", - "struct": "var", - "group": "built-in", - "mandatory": true, - "sub": [ - { - "name": "Error Register", - "type": "UNSIGNED8", // 5 - "access": "ro", - "pdo": true, - "value": 0 + "value": "OK✓" } ] } diff --git a/tests/od/unicode.od b/tests/od/unicode.od index b5726a5..fa17f27 100644 --- a/tests/od/unicode.od +++ b/tests/od/unicode.od @@ -1,76 +1,59 @@ - -Unicode -master + + + -Unicode test -None - + + + - + - + - Hæ? - - - - - - - - - - - - - - - - - + - + - + - + - + - name - Unicode + + - need - + + - struct - + + - values - - + + + - name - Unicode + + - type + - access - rw + + - pdo + diff --git a/tests/test_imports.py b/tests/test_imports.py new file mode 100644 index 0000000..d4a0623 --- /dev/null +++ b/tests/test_imports.py @@ -0,0 +1,45 @@ + +def test_import_eds_utils(): + import objdictgen.eds_utils + +def test_import_gen_cfile(): + import objdictgen.gen_cfile + +def test_import_jsonod(): + import objdictgen.jsonod + +def test_import_maps(): + import objdictgen.maps + +def test_import_node(): + import objdictgen.node + +def test_import_nodelist(): + import objdictgen.nodelist + +def test_import_nodemanager(): + import objdictgen.nodemanager + +def test_import_nosis(): + import objdictgen.nosis + +def test_import_typing(): + import objdictgen.typing + +def test_import_ui_commondialogs(): + import objdictgen.ui.commondialogs + +def test_import_ui_exception(): + import objdictgen.ui.exception + +def test_import_ui_networkedit(): + import objdictgen.ui.networkedit + +def test_import_ui_networkeditortemplate(): + import objdictgen.ui.networkeditortemplate + +def test_import_ui_objdictedit(): + import objdictgen.ui.objdictedit + +def test_import_ui_subindextable(): + import objdictgen.ui.subindextable diff --git a/tests/test_jsonod.py b/tests/test_jsonod.py new file mode 100644 index 0000000..02ae50a --- /dev/null +++ b/tests/test_jsonod.py @@ -0,0 +1,63 @@ +import pytest +from pprint import pprint +from objdictgen import Node +from objdictgen.jsonod import generate_jsonc, generate_node +from .test_odcompare import shave_equal + +def test_jsonod_roundtrip(odjsoneds): + """ Test that the file can be exported to json and that the loaded file + is equal to the first. + """ + od = odjsoneds + + m1 = Node.LoadFile(od) + + out = generate_jsonc(m1, compact=False, sort=False, internal=False, validate=True) + + m2 = generate_node(out) + + a, b = shave_equal(m1, m2, ignore=["IndexOrder", "DefaultStringSize"]) + try: + # pprint(out) + pprint(a) + pprint(b) + # pprint(a.keys()) + # pprint(b.keys()) + # pprint(a.keys() == b.keys()) + # pprint(a["UserMapping"][8193]) + # pprint(b["UserMapping"][8193]) + except KeyError: + pass + assert a == b + + +def test_jsonod_roundtrip_compact(odjsoneds): + """ Test that the file can be exported to json and that the loaded file + is equal to the first. + """ + od = odjsoneds + + m1 = Node.LoadFile(od) + + out = generate_jsonc(m1, compact=True, sort=False, internal=False, validate=True) + + m2 = generate_node(out) + + a, b = shave_equal(m1, m2, ignore=["IndexOrder", "DefaultStringSize"]) + assert a == b + + +def test_jsonod_roundtrip_internal(odjsoneds): + """ Test that the file can be exported to json and that the loaded file + is equal to the first. + """ + od = odjsoneds + + m1 = Node.LoadFile(od) + + out = generate_jsonc(m1, compact=False, sort=False, internal=True, validate=True) + + m2 = generate_node(out) + + a, b = shave_equal(m1, m2, ignore=["IndexOrder", "DefaultStringSize"]) + assert a == b diff --git a/tests/test_knownfailures.py b/tests/test_knownfailures.py index cb5bab6..ba44f76 100644 --- a/tests/test_knownfailures.py +++ b/tests/test_knownfailures.py @@ -1,32 +1,31 @@ -import os import pytest from objdictgen import Node -@pytest.mark.parametrize("suffix", ['.od', '.json']) -def test_edsfail_null(wd, oddir, suffix): - ''' EDS export of null.od fails because it contains no +@pytest.mark.parametrize("suffix", ['od', 'json']) +def test_edsfail_null(wd, odpath, suffix): + """ EDS export of null.od fails because it contains no data. This is possibly a bug, or EDS does not support empty EDS. - ''' + """ - fa = os.path.join(oddir, 'null') + fa = odpath / 'null' - m0 = Node.LoadFile(fa + suffix) + m0 = Node.LoadFile(fa + '.' + suffix) with pytest.raises(KeyError) as exc: m0.DumpFile(fa + '.eds', filetype='eds') assert "Index 0x1018 does not exist" in str(exc.value) -@pytest.mark.parametrize("suffix", ['.od', '.json']) -def test_cexportfail_unicode(wd, oddir, suffix): - ''' C-export does not support UNICODE yet. ''' +@pytest.mark.parametrize("suffix", ['od', 'json']) +def test_cexportfail_unicode(wd, odpath, suffix): + """ C-export does not support UNICODE yet. """ - fa = os.path.join(oddir, 'unicode') + fa = odpath / 'unicode' - m0 = Node.LoadFile(fa + suffix) + m0 = Node.LoadFile(fa + '.' + suffix) with pytest.raises(ValueError) as exc: m0.DumpFile(fa + '.c', filetype='c') diff --git a/tests/test_node.py b/tests/test_node.py index 2135924..9ee9d35 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,47 +1,130 @@ import pytest -from objdictgen.node import StringFormat, EvaluateExpression +from objdictgen import maps -def test_string_format(): - assert StringFormat('Additional Server SDO %d Parameter[(idx)]', 5, 0) == 'Additional Server SDO 5 Parameter' - assert StringFormat('Restore Manufacturer Defined Default Parameters %d[(sub - 3)]', 1, 5) == 'Restore Manufacturer Defined Default Parameters 2' - assert StringFormat('This doesn\'t match the regex', 1, 2) == 'This doesn\'t match the regex' - assert StringFormat('%s %.3f[(idx,sub)]', 1, 2) == '1 2.000' - assert StringFormat('%s %.3f[( idx , sub )]', 1, 2) == '1 2.000' +def test_node_eval_value(): - with pytest.raises(TypeError): - StringFormat('What are these %s[("tests")]', 0, 1) + dut = maps.eval_value + + assert dut("1234", 0, 0, True) == "1234" + assert dut("'$NODEID+1'", 0, 0, False) == "$NODEID+1" + assert dut("'$NODEID+1'", 0, 1000, True) == 1001 + + assert dut('{True:"$NODEID+0x%X00"%(base+2),False:0x80000000}[base<4]', 0, 0, False) == "$NODEID+0x200" + assert dut('{True:"$NODEID+0x%X00"%(base+2),False:0x80000000}[base<4]', 0, 0x1000, True) == 0x1200 + assert dut('{True:"$NODEID+0x%X00"%(base+2),False:0x80000000}[base<4]', 4, 0x1000, True) == 0x80000000 + + +def test_node_eval_name(): + + dut = maps.eval_name + + assert dut("Additional Server SDO %d Parameter[(idx)]", 5, 0) == "Additional Server SDO 5 Parameter" + assert dut("Restore Manufacturer Defined Default Parameters %d[(sub - 3)]", 1, 5) == "Restore Manufacturer Defined Default Parameters 2" + assert dut("This doesn't match the regex", 1, 2) == "This doesn't match the regex" + + assert dut("%s %.3f[(idx,sub)]", 1, 2) == "1 2.000" + assert dut("%s %.3f[( idx , sub )]", 1, 2) == "1 2.000" + + assert dut("This is a %s[(sub*8-7)]", 1, 2) == "This is a 9" + + # with pytest.raises(ValueError): + assert dut("What are these %s[('tests')]", 0, 1) == "What are these tests" + assert dut('What are these %s[("tests")]', 0, 1) == "What are these tests" + + with pytest.raises(NameError): + dut("What are these %s[(tests)]", 0, 1) with pytest.raises(TypeError): - StringFormat('There is nothing to format[(idx, sub)]', 1, 2) + dut("There is nothing to format[(idx, sub)]", 1, 2) with pytest.raises(Exception): - StringFormat('Unhandled arithmatic[(idx*sub)]', 2, 4) + dut("Unhandled arithmatic[(idx*sub)]", 2, 4) -def test_evaluate_expression(): +def test_node_evaluate_expression(): - assert EvaluateExpression('4+3') == 7 - assert EvaluateExpression('4-3') == 1 - assert EvaluateExpression('11') == 11 - assert EvaluateExpression('4+3+2') == 9 - assert EvaluateExpression('4+3-2') == 5 + dut = maps.evaluate_expression + # BinOp + assert dut("4+3") == 7 + assert dut("4-3") == 1 + assert dut("4*3") == 12 + assert dut("10%8") == 2 + with pytest.raises(SyntaxError): + dut("1/2") + + assert dut("4+3+2") == 9 + assert dut("4+3-2") == 5 + + # Compare + assert dut("1<2") == True + with pytest.raises(SyntaxError): + dut("1<2<3") + with pytest.raises(SyntaxError): + dut("1==2") + + # Subscript + assert dut("'abc'[1]") == 'b' + + # Constant + assert dut("11") == 11 + assert dut("1.1") == 1.1 + assert dut("1+2j") == 1+2j + assert dut("'foobar'") == "foobar" + assert dut('"foobar"') == "foobar" + assert dut("False") is False + assert dut("True") is True + with pytest.raises(TypeError): + dut("b'abc'") with pytest.raises(TypeError): - EvaluateExpression('3-"tests"') + dut("None") + with pytest.raises(TypeError): + dut("...") + + # Name + assert dut("foobar", {"foobar": 42}) == 42 + with pytest.raises(NameError): + dut("foo") + # Call + assert dut("f(1)", {"f": lambda x: x + 1}) == 2 + with pytest.raises(NameError): + dut("f()") + with pytest.raises(TypeError): + dut("f()", {"f": lambda x: x}) with pytest.raises(TypeError): - EvaluateExpression('3-"tests"') + assert dut("f(a=2)", {"f": lambda x: x}) + with pytest.raises(TypeError): + assert dut("f(1, a=2)", {"f": lambda x: x}) + with pytest.raises(TypeError): + dut("foo()", {"foo": 42}) + + # Tuple + assert dut("()") == tuple() + assert dut("(1,2)") == (1, 2) + # Dict + assert dut("{}") == {} + assert dut("{1: 2, 3: 4}") == {1: 2, 3: 4} with pytest.raises(TypeError): - EvaluateExpression('not 5') + dut("{None: 1}") + # List with pytest.raises(TypeError): - EvaluateExpression('str') + dut("[]") + # UnaryOp with pytest.raises(TypeError): - EvaluateExpression('"str"') + dut("not 5") + # General with pytest.raises(SyntaxError): - EvaluateExpression('$NODEID+12') \ No newline at end of file + dut("1;2") + with pytest.raises(SyntaxError): + dut("$NODEID+12") + with pytest.raises(TypeError): + dut('3-"tests"') + with pytest.raises(TypeError): + dut("3-'tests'") diff --git a/tests/test_nodelist.py b/tests/test_nodelist.py index 14063c2..c273e34 100644 --- a/tests/test_nodelist.py +++ b/tests/test_nodelist.py @@ -1,5 +1,3 @@ -import os -import shutil from objdictgen.nodemanager import NodeManager from objdictgen.nodelist import NodeList diff --git a/tests/test_nodemanager.py b/tests/test_nodemanager.py index 656af88..a5f999c 100644 --- a/tests/test_nodemanager.py +++ b/tests/test_nodemanager.py @@ -1,23 +1,30 @@ -import os from objdictgen.nodemanager import NodeManager -def test_create_master(): +def test_nodemanager_create_master(): m1 = NodeManager() - m1.CreateNewNode("TestMaster", 0x00, "master", "Longer description", "None", None, "Heartbeat", ["DS302", "GenSYNC", "Emergency"]) + m1.CreateNewNode( + name="TestMaster", id=0x00, type="master", description="Longer description", + profile="None", filepath=None, nmt="Heartbeat", + options=["DS302", "GenSYNC", "Emergency"] + ) m1.CloseCurrent() -def test_create_slave(): +def test_nodemanager_create_slave(): m1 = NodeManager() - m1.CreateNewNode("TestSlave", 0x01, "slave", "Longer description", "None", None, "Heartbeat", ["DS302", "GenSYNC", "Emergency"]) + m1.CreateNewNode( + name="TestSlave", id=0x01, type="slave", description="Longer description", + profile="None", filepath=None, nmt="Heartbeat", + options=["DS302", "GenSYNC", "Emergency"] + ) m1.CloseCurrent() -def test_load(basepath): +def test_nodemanager_load(odpath): m1 = NodeManager() - m1.OpenFileInCurrent(os.path.join(basepath, 'tests', 'od', 'master.od')) + m1.OpenFileInCurrent(odpath / 'master.od') diff --git a/tests/test_nosis.py b/tests/test_nosis.py index b804328..88b5503 100644 --- a/tests/test_nosis.py +++ b/tests/test_nosis.py @@ -1,10 +1,11 @@ from dataclasses import dataclass +import pickle import pytest from objdictgen import nosis -def test_aton(): +def test_nosis_aton(): aton = nosis.aton assert aton("(1)") == 1 @@ -23,7 +24,7 @@ def test_aton(): aton("1.2.3") -def test_ntoa(): +def test_nosis_ntoa(): ntoa = nosis.ntoa assert ntoa(1) == "1" @@ -35,7 +36,7 @@ def test_ntoa(): ntoa("foo") -SAFE_TESTS = [ +TESTS = [ ("", ""), ("&", "&"), ("<", "<"), @@ -49,33 +50,24 @@ def test_ntoa(): ("fu - - -hello -""" - data = nosis.xmlload(xml) - assert data.s == "hello" +def test_nosis_datatypes(types_dut): + """Test dump and load of all datatypes""" - # Attribute in tag - xml = """ - - - -""" + xml = nosis.xmldump(None, types_dut) + # print(xml) + nosis.add_class_to_store('TypesDut', TypesDut) data = nosis.xmlload(xml) - assert data.s == "world" + # print(data) + assert types_dut == data -def test_nosis_all_datatypes(): - '''Test all datatypes''' - # @dataclass - # class C: - # s: str +def test_nosis_py2_datatypes_load(py2, wd, types_dut): + """Test that py2 gnosis is able to load a py3 nosis generated XML""" - @dataclass - class Dut: - s: str - i: int - l: list - d: dict - t: tuple - n: None - f: float - c: complex - T: bool - F: bool - # o: C - # b: bytes + nosis.add_class_to_store('TypesDut', TypesDut) - nosis.add_class_to_store('Dut', Dut) - # nosis.add_class_to_store('C', C) + xml = nosis.xmldump(None, types_dut) + + # Import the XML using the old py2 gnosis and pickle it + pyapp=f""" +from gnosis.xml.pickle import * +import pickle, sys +a = loads('''{xml}''') +with open("dump.pickle", "wb") as f: + pickle.dump(a.__dict__, f, protocol=0) +""" + cmd = py2.run(pyapp, stdout=py2.PIPE) + out = py2.stdout(cmd) + print(out) + py2.check(cmd) + + # Load the pickled data and compare + with open("dump.pickle", "rb") as f: + data = pickle.load(f) + print(data) + + assert types_dut.__dict__ == data + + +def xtest_nosis_py2_datatypes_dump(py2, wd, types_dut): + """Test that py3 nosis is able to read py2 generated XML""" + + # Import the XML using the old py2 gnosis and pickle it + pyapp=""" +from gnosis.xml.pickle import * +class TypesDut: + def __init__(self): + self._str = "foo" + self._int = 1 + self._float = 1.5 + self._complex = 1+2j + self._none = None + self._true = True + self._false = False + self._list = [1, 2, 3] + self._tuple = (1, 2, 3) + self._dict = {'a': 1, 'b': 2} +a = TypesDutLegacy() +xml = dumps(a) +with open("dump.xml", "wb") as f: + f.write(xml) +""" + cmd = py2.run(pyapp, stdout=py2.PIPE) + out = py2.stdout(cmd) + print(out) + py2.check(cmd) + + # Load the pickled data and compare + with open("dump.xml", "rb") as f: + xml = f.read().decode() + + nosis.add_class_to_store('TypesDut', TypesDut) + data = nosis.xmlload(xml) + print(data) - cmp_xml(Dut( - s="foo", i=1, l=[1, 2, 3], d={'a': 1, 'b': 2}, t=(1, 2, 3), - n=None, f=1.5, c=1+2j, T=True, F=False #, o=C("foo"), b=b'\x00\x01\x02' - )) + assert types_dut == data diff --git a/tests/test_objdictgen.py b/tests/test_objdictgen.py index c6a4373..9876855 100644 --- a/tests/test_objdictgen.py +++ b/tests/test_objdictgen.py @@ -1,28 +1,59 @@ -import os +import pytest import objdictgen.__main__ -# def test_odsetup(odfile, fn): -# """ Test that we have the same od files present in DUT as in REF """ -# reffile = odfile.replace(fn.DUT, fn.REF) -# d = list(fn.diff(reffile + '.od', odfile + '.od')) -# assert not d +def test_objdictgen_run(odjsoneds, mocker, wd): + """Test that we're able to run objdictgen on our od files""" + od = odjsoneds + tmpod = od.stem -def test_objdictgen(wd, mocker, odfile, fn): - """ Test that objdictgen generates equal output as reference """ - od = odfile.name + if tmpod in ('legacy-strings', 'strings', 'unicode'): + pytest.xfail("UNICODE_STRINGS is not supported in C") mocker.patch("sys.argv", [ "objdictgen", - odfile + '.od', - od + '.c', + str(od), + od.stem + '.c', ]) objdictgen.__main__.main_objdictgen() - if os.path.exists(odfile + '.c'): - assert fn.diff(odfile + '.c', od + '.c', n=0) - assert fn.diff(odfile + '.h', od + '.h', n=0) - assert fn.diff(odfile + '_objectdefines.h', od + '_objectdefines.h', n=0) + +def test_objdictgen_py2_compare(py2_cfile, mocker, wd, fn): + """Test that comparing objectdictgen output from python2 and python3 is the same.""" + + # Extract the path to the OD and the path to the python2 c file + od, py2od = py2_cfile + tmpod = od.stem + + if tmpod in ('legacy-strings'): + pytest.xfail("UNICODE_STRINGS is not supported in C") + + mocker.patch("sys.argv", [ + "objdictgen", + str(od), + tmpod + '.c', + ]) + + objdictgen.__main__.main_objdictgen() + + def accept_known_py2_bugs(lines): + """ Python2 outputs floats differently than python3, but the + change is expected. This function attempts to find if the diff + output only contains these expected differences.""" + for line in lines: + if line in ("---", "+++"): + continue + if any(line.startswith(s) for s in ( + "@@ ", "-REAL32 ", "+REAL32 ", "-REAL64 ", "+REAL64 ", + )): + continue + # No match, so there is some other difference. Report as error + return lines + pytest.xfail("Py2 prints floats differently than py3 which is expected") + + assert fn.diff(tmpod + '.c', py2od + '.c', n=0, postprocess=accept_known_py2_bugs) + assert fn.diff(tmpod + '.h', py2od + '.h', n=0) + assert fn.diff(tmpod + '_objectdefines.h', py2od + '_objectdefines.h', n=0) diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index 9a71a7d..2823f61 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -8,6 +8,9 @@ def shave_dict(a, b): + """ Recursively remove equal elements from two dictionaries. + Note: This function modifies the input dictionaries. + """ if isinstance(a, dict) and isinstance(b, dict): for k in set(a.keys()) | set(b.keys()): if k in a and k in b: @@ -18,7 +21,9 @@ def shave_dict(a, b): return a, b -def shave_equal(a, b, ignore=None): +def shave_equal(a, b, ignore=None, preprocess=None): + """ Remove equal elements from two objects and return the remaining elements. + """ a = copy.deepcopy(a.__dict__) b = copy.deepcopy(b.__dict__) @@ -26,337 +31,371 @@ def shave_equal(a, b, ignore=None): a.pop(n, None) b.pop(n, None) - return shave_dict(a, b) - - -# TIPS: -# -# Printing of diffs: -# # from objdictgen.__main__ import print_diffs -# # from objdictgen import jsonod -# # diffs = jsonod.diff_nodes(m0, m1, as_dict=False, validate=True) -# # print_diffs(diffs) -# -# Saving for debug -# # m1.SaveCurrentInFile('/_tmp.err.json', sort=True, internal=True, validate=False) - + if preprocess: + preprocess(a, b) + return shave_dict(a, b) -# def dictify(d): -# if isinstance(d, dict): -# return { -# k: dictify(v) -# for k, v in d.items() -# } -# elif isinstance(d, list): -# return [ -# dictify(v) -# for v in d -# ] -# return d +def test_load_compare(odjsoneds): + """ Tests that the file can be loaded twice without failure and no + difference. + """ + od = odjsoneds -# def del_IndexOrder(obj): -# if hasattr(obj, 'IndexOrder'): -# delattr(obj, 'IndexOrder') + # Load the OD two times + m1 = Node.LoadFile(od) + m2 = Node.LoadFile(od) + a, b = shave_equal(m1, m2) + assert a == b -@pytest.mark.parametrize("suffix", ['od', 'json', 'eds']) -def test_load_compare(odfile, suffix): - ''' Tests that the file can be loaded twice without different. - L(od) == L(od) - ''' - if not os.path.exists(odfile + '.' + suffix): - pytest.skip("File not found") +def test_odload_py2_compare(py2_pickle, wd): + """ Load the OD and compare it with the python2 loaded OD to ensure that + the OD is loaded equally. This is particular important when comparing + string encoding. + """ + od, py2data = py2_pickle # Load the OD - m1 = Node.LoadFile(odfile + '.' + suffix) - m2 = Node.LoadFile(odfile + '.' + suffix) + m1 = Node.LoadFile(od) - assert m1.__dict__ == m2.__dict__ + a, b = shave_dict(py2data, m1.__dict__) + assert a == b -def test_odexport(wd, odfile, fn): - ''' Test that the od file can be exported to od and that the loaded file - is equal to the first. - L(od) -> S(od2), od == L(od2) - ''' - od = odfile.name +def test_odexport(odjsoneds, wd, fn): + """ Test that the od file can be exported to od and that the loaded file + is equal to the original. + """ + od = odjsoneds + tmpod = od.stem - m0 = Node.LoadFile(odfile + '.od') - m1 = Node.LoadFile(odfile + '.od') + m0 = Node.LoadFile(od) + m1 = Node.LoadFile(od) # Save the OD - m1.DumpFile(od + '.od', filetype='od') + m1.DumpFile(tmpod + '.od', filetype='od') # Assert that the object is unmodified by the export - assert m0.__dict__ == m1.__dict__ + a, b = shave_equal(m0, m1) + assert a == b # Modify the od files to remove unique elements # .od.orig is the original .od file # .od is the generated .od file - RE_ID = re.compile(r'(id|module)="\w+"') - with open(odfile + '.od', 'r') as fi: - with open(od + '.od.orig', 'w') as fo: + re_id = re.compile(b'(id|module)="\\w+"') + with open(od, 'rb') as fi: + with open(f'{od.name}.orig', 'wb') as fo: for line in fi: - fo.write(RE_ID.sub('', line)) - shutil.move(od + '.od', od + '.tmp') - with open(od + '.tmp', 'r') as fi: - with open(od + '.od', 'w') as fo: + fo.write(re_id.sub(b'', line)) + shutil.move(tmpod + '.od', tmpod + '.tmp') + with open(tmpod + '.tmp', 'rb') as fi: + with open(tmpod + '.od', 'wb') as fo: for line in fi: - fo.write(RE_ID.sub('', line)) - os.remove(od + '.tmp') + fo.write(re_id.sub(b'', line)) + os.remove(tmpod + '.tmp') # Load the saved OD - m2 = Node.LoadFile(od + '.od') + m2 = Node.LoadFile(tmpod + '.od') - # Compare the OD master and the OD2 objects - if m1.__dict__ != m2.__dict__: - a, b = shave_equal(m1, m2) - assert a == b - assert m1.__dict__ == m2.__dict__ + # OD format never contains IndexOrder, so its ok to ignore it + a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) + assert a == b -def test_jsonexport(wd, odfile): - ''' Test that the file can be exported to json and that the loaded file +def test_jsonexport(odjsoneds, wd): + """ Test that the file can be exported to json and that the loaded file is equal to the first. - L(od) -> fix -> S(json), L(od) == od - ''' - od = odfile.name + """ + od = odjsoneds + tmpod = od.stem - m0 = Node.LoadFile(odfile + '.od') - m1 = Node.LoadFile(odfile + '.od') + m0 = Node.LoadFile(od) + m1 = Node.LoadFile(od) - # Need this to fix any incorrect ODs which cause import error - m0.Validate(fix=True) - m1.Validate(fix=True) - - m1.DumpFile(od + '.json', filetype='json') + m1.DumpFile(tmpod + '.json', filetype='json') # Assert that the object is unmodified by the export - assert m0.__dict__ == m1.__dict__ + a, b = shave_equal(m0, m1) + assert a == b - m2 = Node.LoadFile(odfile + '.od') - # To verify that the export doesn't clobber the object - equal = m1.__dict__ == m2.__dict__ +def test_cexport(odjsoneds, wd, fn): + """ Test that the file can be exported to c + """ + od = odjsoneds + tmpod = od.stem - # If this isn't equal, then it could be the fix option above, so let's attempt - # to modify m2 with the same change - if not equal: - m2.Validate(fix=True) + if tmpod in ('strings', 'legacy-strings', 'unicode'): + pytest.xfail("UNICODE_STRINGS is not supported in C") - assert m1.__dict__ == m2.__dict__ + m0 = Node.LoadFile(od) + m1 = Node.LoadFile(od) + m1.DumpFile(tmpod + '.c', filetype='c') -def test_cexport(wd, odfile, fn): - ''' Test that the file can be exported to c and that the loaded file - is equal to the stored template (if present). - L(od) -> S(c), diff(c) - ''' - od = odfile.name + # Assert that the object is unmodified by the export + a, b = shave_equal(m0, m1) + assert a == b - m0 = Node.LoadFile(odfile + '.od') - m1 = Node.LoadFile(odfile + '.od') - m1.DumpFile(od + '.c', filetype='c') +def test_cexport_py2_compare(py2_cfile, wd, fn): + """ Test that the exported c files match the ones generated by python2 + """ - # Assert that the object is unmodified by the export - assert m0.__dict__ == m1.__dict__ + # Extract the path to the OD and the path to the python2 c file + od, py2od = py2_cfile + tmpod = od.stem - # FIXME: If files doesn't exist, this leaves this test half-done. Better way? - if os.path.exists(odfile + '.c'): - assert fn.diff(odfile + '.c', od + '.c', n=0) - assert fn.diff(odfile + '.h', od + '.h', n=0) - assert fn.diff(odfile + '_objectdefines.h', od + '_objectdefines.h', n=0) + if tmpod in ('strings', 'legacy-strings'): + pytest.xfail("UNICODE_STRINGS is not supported in C") + m0 = Node.LoadFile(od) -def test_edsexport(wd, odfile, fn): - ''' Test that the file can be exported to eds and that the loaded file - is equal to the stored template (if present) - L(od) -> S(eds), diff(eds) - ''' - od = odfile.name + m0.DumpFile(tmpod + '.c', filetype='c') - if od == 'null': - pytest.skip("Won't work for null") + def accept_known_py2_bugs(lines): + """ Python2 outputs floats differently than python3, but the + change is expected. This function attempts to find if the diff + output only contains these expected differences.""" + for line in lines: + if line in ("---", "+++"): + continue + if any(line.startswith(s) for s in ( + "@@ ", "-REAL32 ", "+REAL32 ", "-REAL64 ", "+REAL64 ", + )): + continue + # No match, so there is some other difference. Report as error + return lines + pytest.xfail("Py2 prints floats differently than py3 which is expected") - m0 = Node.LoadFile(odfile + '.od') - m1 = Node.LoadFile(odfile + '.od') + # Compare the generated c files + assert fn.diff(tmpod + '.c', py2od + '.c', n=0, postprocess=accept_known_py2_bugs) + assert fn.diff(tmpod + '.h', py2od + '.h', n=0) + assert fn.diff(tmpod + '_objectdefines.h', py2od + '_objectdefines.h', n=0) - m1.DumpFile(od + '.eds', filetype='eds') - # Assert that the object is unmodified by the export - assert m0.__dict__ == m1.__dict__ +def test_edsexport(odjsoneds, wd, fn): + """ Test that the file can be exported to eds """ - def predicate(line): - for m in ('CreationDate', 'CreationTime', 'ModificationDate', 'ModificationTime'): - if m in line: - return False - return True + od = odjsoneds + tmpod = od.stem - # FIXME: If file doesn't exist, this leaves this test half-done. Better way? - if os.path.exists(odfile + '.eds'): - assert fn.diff(odfile + '.eds', od + '.eds', predicate=predicate) + m0 = Node.LoadFile(od) + m1 = Node.LoadFile(od) + try: + m1.DumpFile(tmpod + '.eds', filetype='eds') -def test_edsimport(wd, odfile): - ''' Test that EDS files can be exported and imported again. - L(od) -> S(eds), L(eds) - ''' - od = odfile.name + except KeyError as e: + if str(e) in "KeyError: 'Index 0x1018 does not exist'": + pytest.xfail("Index 0x1018 does not exist, so EDS export will fail") + raise - if od == 'null': - pytest.skip("Won't work for null") + # Assert that the object is unmodified by the export + a, b = shave_equal(m0, m1) + assert a == b - m1 = Node.LoadFile(odfile + '.od') - # Need this to fix any incorrect ODs which cause EDS import error - #m1.Validate(correct=True) +def test_edsexport_py2_compare(py2_edsfile, wd, fn): + """ Test that the exported c files match the ones generated by python2 + """ + + # Extract the path to the OD and the path to the python2 eds file + od, py2od = py2_edsfile + tmpod = od.stem + + m0 = Node.LoadFile(od) + + m0.DumpFile(tmpod + '.eds', filetype='eds') + + assert fn.diff(tmpod + '.eds', py2od + '.eds') + + +def test_edsimport(odjsoneds, wd): + """ Test that EDS files can be exported and imported again. + """ + od = odjsoneds + tmpod = od.stem + + m1 = Node.LoadFile(od) + + try: + m1.DumpFile(tmpod + '.eds', filetype='eds') + except KeyError as e: + if str(e) in "KeyError: 'Index 0x1018 does not exist'": + pytest.xfail("Index 0x1018 does not exist, so EDS export will fail") + raise + + m2 = Node.LoadFile(tmpod + '.eds') + + def accept_known_eds_limitation(a, b): + """ This function mitigates a known limitation in the EDS file format + to make the rest of the file comparison possible. + + In the EDS the Dictionary element contains dynamic code, such + as "'{True:"$NODEID+0x%X00"%(base+2),False:0x80000000}[base<4]'", + while in the EDS this is compiled to values such as'"$NODEID+0x500"' + + The fix is to replace the dynamic code with the calculated value + which is the same as is done in the EDS file generation. + """ + def num(x): + if isinstance(x, list): + return x[1:] + return x + a['Dictionary'] = { + i: num(m1.GetEntry(i, compute=False)) + for i in m1 + } + b['Dictionary'] = { + i: num(m2.GetEntry(i, compute=False)) + for i in m2 + } + + # EDS files are does not contain the complete information as OD and JSON + # files does. + a, b = shave_equal( + m1, m2, preprocess=accept_known_eds_limitation, + ignore=( + "IndexOrder", "Profile", "ProfileName", "DS302", "UserMapping", + "ParamsDictionary", "DefaultStringSize" + ) + ) + assert a == b - m1.DumpFile(od + '.eds', filetype='eds') - m2 = Node.LoadFile(od + '.eds') +def test_jsonimport(odjsoneds, wd): + """ Test that JSON files can be exported and read back. It will be + compared with orginal contents. + """ + od = odjsoneds + tmpod = od.stem - # FIXME: EDS isn't complete enough to compare with an OD-loaded file - # a, b = shave_equal(m1, m2, ignore=('IndexOrder', 'Description')) - # assert a == b + m1 = Node.LoadFile(od) + m1.DumpFile(tmpod + '.json', filetype='json') -def test_jsonimport(wd, odfile): - ''' Test that JSON files can be exported and read back. It will be - compared with orginal contents. - L(od) -> fix -> S(json), od == L(json) - ''' - od = odfile.name + m2 = Node.LoadFile(tmpod + '.json') - m1 = Node.LoadFile(odfile + '.od') + # Only include IndexOrder when comparing json files + ignore = ('IndexOrder',) if od.suffix != '.json' else None + a, b = shave_equal(m1, m2, ignore=ignore) + assert a == b - # Need this to fix any incorrect ODs which cause import error - m1.Validate(fix=True) - m1.DumpFile(od + '.json', filetype='json') - m1.DumpFile(od + '.json2', filetype='json', compact=True) +def test_jsonimport_compact(odjsoneds, wd): + """ Test that JSON files can be exported and read back. It will be + compared with orginal contents. + """ + od = odjsoneds + tmpod = od.stem - m2 = Node.LoadFile(od + '.json') + m1 = Node.LoadFile(od) - a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) - assert a == b + m1.DumpFile(tmpod + '.json', filetype='json', compact=True) - m3 = Node.LoadFile(od + '.json2') + m2 = Node.LoadFile(tmpod + '.json') - a, b = shave_equal(m1, m3, ignore=('IndexOrder',)) + # Only include IndexOrder when comparing json files + ignore = ('IndexOrder',) if od.suffix != '.json' else None + a, b = shave_equal(m1, m2, ignore=ignore) assert a == b def test_od_json_compare(odfile): - ''' Test reading the od and compare it with the corresponding json file - L(od) == L(json) - ''' + """ Test reading and comparing the od and json with the same filename + """ - if not os.path.exists(odfile + '.json'): - raise pytest.skip("No .json file for '%s'" %(odfile + '.od')) + odjson = odfile.with_suffix('.json') - m1 = Node.LoadFile(odfile + '.od') - m2 = Node.LoadFile(odfile + '.json') + if not odjson.exists(): + pytest.skip(f"No .json file next to '{odfile.rel_to_wd()}'") - # To verify that the export doesn't clobber the object - a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) - equal = a == b - - # If this isn't equal, then it could be the fix option above, so let's attempt - # to modify m1 with the fix - if not equal: - m1.Validate(fix=True) + m1 = Node.LoadFile(odfile) + m2 = Node.LoadFile(odjson) + # IndexOrder doesn't make sense in OD file so ignore it a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) assert a == b PROFILE_ODS = [ - "test-profile", - "test-profile-use", - "master-ds302", - "master-ds401", - "master-ds302-ds401", - "legacy-test-profile", - "legacy-test-profile-use", - "legacy-master-ds302", - "legacy-master-ds401", - "legacy-master-ds302-ds401", - "legacy-slave-ds302", - "legacy-slave-emcy", - "legacy-slave-heartbeat", - "legacy-slave-nodeguarding", - "legacy-slave-sync", + "profile-test", + "profile-ds302", + "profile-ds401", + "profile-ds302-ds401", + "profile-ds302-test", + "legacy-profile-test", + "legacy-profile-ds302", + "legacy-profile-ds401", + "legacy-profile-ds302-ds401", + "legacy-profile-ds302-test", ] + @pytest.mark.parametrize("oddut", PROFILE_ODS) @pytest.mark.parametrize("suffix", ['od', 'json']) -def test_save_wo_profile(oddir, oddut, suffix, wd): - ''' Test that saving a od that contains a profile creates identical +def test_save_wo_profile(odpath, oddut, suffix, wd): + """ Test that saving a od that contains a profile creates identical results as the original. This test has no access to the profile dir - ''' + """ - fa = os.path.join(oddir, oddut) + fa = odpath / oddut + fb = oddut + '.' + suffix m1 = Node.LoadFile(fa + '.od') - m1.DumpFile(oddut + '.' + suffix, filetype=suffix) + m1.DumpFile(fb, filetype=suffix) - m2 = Node.LoadFile(oddut + '.' + suffix) + m2 = Node.LoadFile(fb) - a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) + # Ignore the IndexOrder when working with json files + ignore = ('IndexOrder',) if suffix == 'json' else None + a, b = shave_equal(m1, m2, ignore=ignore) assert a == b @pytest.mark.parametrize("oddut", PROFILE_ODS) @pytest.mark.parametrize("suffix", ['od', 'json']) -def test_save_with_profile(oddir, oddut, suffix, wd, profile): - ''' Test that saving a od that contains a profile creates identical +def test_save_with_profile(odpath, oddut, suffix, wd, profile): + """ Test that saving a od that contains a profile creates identical results as the original. This test have access to the profile dir - ''' + """ - fa = os.path.join(oddir, oddut) + # FIXME: Does this work? The test succeeds even if the profile is missing + + fa = odpath / oddut + fb = oddut + '.' + suffix m1 = Node.LoadFile(fa + '.od') - m1.DumpFile(oddut + '.' + suffix, filetype=suffix) + m1.DumpFile(fb, filetype=suffix) - m2 = Node.LoadFile(oddut + '.' + suffix) + m2 = Node.LoadFile(fb) - a, b = shave_equal(m1, m2, ignore=('IndexOrder',)) + # Ignore the IndexOrder when working with json files + ignore = ('IndexOrder',) if suffix == 'json' else None + a, b = shave_equal(m1, m2, ignore=ignore) assert a == b -@pytest.mark.parametrize("equivs", [ - ('minimal.od', 'legacy-minimal.od'), - ('minimal.json', 'legacy-minimal.od'), - ('master.od', 'legacy-master.od'), - ('master.json', 'legacy-master.od'), - ('slave.od', 'legacy-slave.od'), - ('slave.json', 'legacy-slave.od'), - ('alltypes.od', 'legacy-alltypes.od'), - ('alltypes.json', 'legacy-alltypes.od'), - ('test-profile.od', 'legacy-test-profile.od'), - #('test-profile.json', 'legacy-test-profile.od'), - ('test-profile-use.od', 'legacy-test-profile-use.od'), - #('test-profile-use.json', 'legacy-test-profile-use.od'), - ('master-ds302.od', 'legacy-master-ds302.od'), - #('master-ds302.json', 'legacy-master-ds302.od'), - ('master-ds401.od', 'legacy-master-ds401.od'), - #('master-ds401.json', 'legacy-master-ds401.od'), - ('master-ds302-ds401.od', 'legacy-master-ds302-ds401.od'), - #('master-ds302-ds401.json', 'legacy-master-ds302-ds401.od'), -]) -def test_legacy_compare(oddir, equivs): - ''' Test reading the od and compare it with the corresponding json file - ''' - a, b = equivs - fa = os.path.join(oddir, a) - fb = os.path.join(oddir, b) - - m1 = Node.LoadFile(fa) - m2 = Node.LoadFile(fb) + + +@pytest.mark.parametrize("suffix", ['od', 'json']) +def test_equiv_compare(odpath, equiv_files, suffix): + """ Test reading the od and compare it with the corresponding json file + """ + a, b = equiv_files + + oda = (odpath / a) + '.' + suffix + odb = (odpath / b) + '.od' + + if not oda.exists(): + pytest.skip(f"No {oda.rel_to_wd()} file") + + m1 = Node.LoadFile(oda) + m2 = Node.LoadFile(odb) a, b = shave_equal(m1, m2, ignore=('Description', 'IndexOrder')) assert a == b diff --git a/tests/test_odg.py b/tests/test_odg.py index abb8a76..8cec23b 100644 --- a/tests/test_odg.py +++ b/tests/test_odg.py @@ -1,30 +1,42 @@ -import os import pytest from objdictgen.__main__ import main -@pytest.mark.parametrize("suffix", ['.od', '.json', '.eds']) -def test_odg_list_woprofile(odfile, suffix): +def test_odg_list_woprofile(odjsoneds): - fname = odfile + suffix - if not os.path.exists(fname): - pytest.skip("File not found") + od = odjsoneds main(( - 'list', - fname + 'list', '-D', + str(od) )) -@pytest.mark.parametrize("suffix", ['.od', '.json', '.eds']) -def test_odg_list_wprofile(odfile, suffix, profile): +def test_odg_list_wprofile(odjsoneds, profile): - fname = odfile + suffix - if not os.path.exists(fname): - pytest.skip("File not found") + od = odjsoneds main(( - 'list', - fname + 'list', '-D', + str(od) + )) + + +@pytest.mark.parametrize("suffix", ['od', 'json']) +def test_odg_compare(odpath, equiv_files, suffix): + """ Test reading the od and compare it with the corresponding json file + """ + a, b = equiv_files + + oda = (odpath / a) + '.' + suffix + odb = (odpath / b) + '.od' + + if not oda.exists(): + pytest.skip(f"No {oda.rel_to_wd()} file") + + main(( + 'compare', '-D', + str(oda), + str(odb), ))