-
Notifications
You must be signed in to change notification settings - Fork 358
eds: Fix signed integer limit parsing and export for all SIGNED_TYPES #658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
bf972c1
f65a1fb
4ed45dd
4319d4a
2382b96
28c7d26
3d1d2d4
aa5257a
cb00c3b
417ace5
2b900a9
e6d1074
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -204,15 +204,12 @@ def import_from_node(node_id: int, network: canopen.network.Network): | |||||||
| return od | ||||||||
|
|
||||||||
|
|
||||||||
| def _calc_bit_length(data_type): | ||||||||
| if data_type == datatypes.INTEGER8: | ||||||||
| return 8 | ||||||||
| elif data_type == datatypes.INTEGER16: | ||||||||
| return 16 | ||||||||
| elif data_type == datatypes.INTEGER32: | ||||||||
| return 32 | ||||||||
| elif data_type == datatypes.INTEGER64: | ||||||||
| return 64 | ||||||||
| def _calc_bit_length(data_type: int) -> int: | ||||||||
| if data_type in datatypes.SIGNED_TYPES: | ||||||||
| st = ODVariable.STRUCT_TYPES[data_type] | ||||||||
| if isinstance(st, datatypes.IntegerN): | ||||||||
| return st.width | ||||||||
| return st.size * 8 | ||||||||
| else: | ||||||||
| raise ValueError( | ||||||||
| f"Invalid data_type '{data_type}', expecting a signed integer data_type." | ||||||||
|
|
@@ -221,11 +218,25 @@ def _calc_bit_length(data_type): | |||||||
|
|
||||||||
| def _signed_int_from_hex(hex_str, bit_length): | ||||||||
| number = int(hex_str, 0) | ||||||||
| max_value = (1 << (bit_length - 1)) - 1 | ||||||||
|
|
||||||||
| if number > max_value: | ||||||||
| return number - (1 << bit_length) | ||||||||
| min_signed = -(1 << (bit_length - 1)) | ||||||||
| max_signed = (1 << (bit_length - 1)) - 1 | ||||||||
| max_unsigned = (1 << bit_length) - 1 | ||||||||
|
|
||||||||
| if number < 0: | ||||||||
| # Negative decimal literal (e.g. LowLimit=-32768) | ||||||||
| if number < min_signed: | ||||||||
| raise ValueError( | ||||||||
| f"Value {hex_str!r} is out of range for a {bit_length}-bit signed integer" | ||||||||
| ) | ||||||||
| return number | ||||||||
| else: | ||||||||
| # Unsigned hex literal, two's-complement (e.g. LowLimit=0xFFFF → -1 for INTEGER16) | ||||||||
| if number > max_unsigned: | ||||||||
| raise ValueError( | ||||||||
| f"Value {hex_str!r} is out of range for a {bit_length}-bit signed integer" | ||||||||
| ) | ||||||||
| if number > max_signed: | ||||||||
| return number - (1 << bit_length) | ||||||||
| return number | ||||||||
|
|
||||||||
|
|
||||||||
|
|
@@ -245,6 +256,18 @@ def _convert_variable(node_id, var_type, value): | |||||||
| return int(value, 0) | ||||||||
|
|
||||||||
|
|
||||||||
| def _int_to_hex(data_type: int, value: int) -> str: | ||||||||
| """Format an integer as EDS hex string. | ||||||||
|
|
||||||||
| Signed types with a negative value are written as two's-complement hex | ||||||||
| (e.g. INTEGER8 -1 → 0xFF) so the output is a valid EDS literal. | ||||||||
|
friederschueler marked this conversation as resolved.
Outdated
|
||||||||
| """ | ||||||||
| if data_type in datatypes.SIGNED_TYPES and value < 0: | ||||||||
| bit_length = _calc_bit_length(data_type) | ||||||||
| return f"0x{value + (1 << bit_length):0{bit_length // 4}X}" | ||||||||
| return f"0x{value:02X}" | ||||||||
|
|
||||||||
|
|
||||||||
| def _revert_variable(var_type, value): | ||||||||
| if value is None: | ||||||||
| return None | ||||||||
|
|
@@ -255,7 +278,7 @@ def _revert_variable(var_type, value): | |||||||
| elif var_type in datatypes.FLOAT_TYPES: | ||||||||
| return value | ||||||||
| else: | ||||||||
| return f"0x{value:02X}" | ||||||||
| return _int_to_hex(var_type, value) | ||||||||
|
|
||||||||
|
|
||||||||
| def build_variable( | ||||||||
|
|
@@ -309,7 +332,10 @@ def build_variable( | |||||||
| else: | ||||||||
| var.min = int(min_string, 0) | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| logger.warning( | ||||||||
| "Invalid LowLimit %r for %s (0x%X), ignoring", | ||||||||
| eds.get(section, "LowLimit"), var.name, var.index, | ||||||||
|
Comment on lines
+324
to
+325
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ) | ||||||||
| if eds.has_option(section, "HighLimit"): | ||||||||
| try: | ||||||||
| max_string = eds.get(section, "HighLimit") | ||||||||
|
|
@@ -318,38 +344,44 @@ def build_variable( | |||||||
| else: | ||||||||
| var.max = int(max_string, 0) | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| logger.warning( | ||||||||
| "Invalid HighLimit %r for %s (0x%X), ignoring", | ||||||||
| eds.get(section, "HighLimit"), var.name, var.index, | ||||||||
|
Comment on lines
+336
to
+337
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ) | ||||||||
| if eds.has_option(section, "DefaultValue"): | ||||||||
| try: | ||||||||
| var.default_raw = eds.get(section, "DefaultValue") | ||||||||
| if '$NODEID' in var.default_raw: | ||||||||
| var.relative = True | ||||||||
| var.default = _convert_variable(node_id, var.data_type, var.default_raw) | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| logger.warning( | ||||||||
| "Invalid DefaultValue %r for %s (0x%X), ignoring", | ||||||||
| eds.get(section, "DefaultValue"), var.name, var.index, | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ) | ||||||||
| if eds.has_option(section, "ParameterValue"): | ||||||||
| try: | ||||||||
| var.value_raw = eds.get(section, "ParameterValue") | ||||||||
| var.value = _convert_variable(node_id, var.data_type, var.value_raw) | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| logger.warning( | ||||||||
| "Invalid ParameterValue %r for %s (0x%X), ignoring", | ||||||||
| eds.get(section, "ParameterValue"), var.name, var.index, | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| ) | ||||||||
| # Factor, Description and Unit are not standard according to the CANopen specifications, but | ||||||||
| # they are implemented in the python canopen package, so we can at least try to use them | ||||||||
| if eds.has_option(section, "Factor"): | ||||||||
| try: | ||||||||
| var.factor = float(eds.get(section, "Factor")) | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| logger.warning( | ||||||||
| "Invalid Factor %r for %s (0x%X), ignoring", | ||||||||
| eds.get(section, "Factor"), var.name, var.index, | ||||||||
| ) | ||||||||
| if eds.has_option(section, "Description"): | ||||||||
| try: | ||||||||
| var.description = eds.get(section, "Description") | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| var.description = eds.get(section, "Description") | ||||||||
| if eds.has_option(section, "Unit"): | ||||||||
| try: | ||||||||
| var.unit = eds.get(section, "Unit") | ||||||||
| except ValueError: | ||||||||
| pass | ||||||||
| var.unit = eds.get(section, "Unit") | ||||||||
| return var | ||||||||
|
|
||||||||
|
|
||||||||
|
|
@@ -414,9 +446,9 @@ def export_variable(var, eds): | |||||||
| eds.set(section, "PDOMapping", hex(var.pdo_mappable)) | ||||||||
|
|
||||||||
| if getattr(var, 'min', None) is not None: | ||||||||
| eds.set(section, "LowLimit", var.min) | ||||||||
| eds.set(section, "LowLimit", _int_to_hex(var.data_type, var.min)) | ||||||||
| if getattr(var, 'max', None) is not None: | ||||||||
| eds.set(section, "HighLimit", var.max) | ||||||||
| eds.set(section, "HighLimit", _int_to_hex(var.data_type, var.max)) | ||||||||
|
|
||||||||
| if getattr(var, 'description', '') != '': | ||||||||
| eds.set(section, "Description", var.description) | ||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.