Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions canopen/objectdictionary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,16 @@ def __init__(self, name: str, index: int):
self.name = name
#: Storage location of index
self.storage_location: Optional[str] = None
#: CiA 306 ObjFlags bitfield
self.obj_flags: int = 0
#: CiA 306 Denotation string (DCF only)
self.denotation: str = ""
self.subindices: dict[int, ODVariable] = {}
self.names: dict[str, ODVariable] = {}

def __repr__(self) -> str:
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}>"
flags = f" flags=0x{self.obj_flags:X}" if self.obj_flags else ""
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}{flags}>"

def __getitem__(self, subindex: Union[int, str]) -> ODVariable:
item = self.names.get(subindex) or self.subindices.get(subindex)
Expand Down Expand Up @@ -269,11 +274,16 @@ def __init__(self, name: str, index: int):
self.name = name
#: Storage location of index
self.storage_location: Optional[str] = None
#: CiA 306 ObjFlags bitfield
self.obj_flags: int = 0
#: CiA 306 Denotation string (DCF only)
self.denotation: str = ""
self.subindices: dict[int, ODVariable] = {}
self.names: dict[str, ODVariable] = {}

def __repr__(self) -> str:
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}>"
flags = f" flags=0x{self.obj_flags:X}" if self.obj_flags else ""
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}{flags}>"

def __getitem__(self, subindex: Union[int, str]) -> ODVariable:
var = self.names.get(subindex) or self.subindices.get(subindex)
Expand Down Expand Up @@ -379,12 +389,17 @@ def __init__(self, name: str, index: int, subindex: int = 0):
self.bit_definitions: dict[str, list[int]] = {}
#: Storage location of index
self.storage_location: Optional[str] = None
#: CiA 306 ObjFlags bitfield
self.obj_flags: int = 0
#: CiA 306 Denotation string (DCF only)
self.denotation: str = ""
Comment thread
bizfsc marked this conversation as resolved.
#: Can this variable be mapped to a PDO
self.pdo_mappable = False

def __repr__(self) -> str:
subindex = self.subindex if isinstance(self.parent, (ODRecord, ODArray)) else None
return f"<{type(self).__qualname__} {self.qualname!r} at {pretty_index(self.index, subindex)}>"
flags = f" flags=0x{self.obj_flags:X}" if self.obj_flags else ""
return f"<{type(self).__qualname__} {self.qualname!r} at {pretty_index(self.index, subindex)}{flags}>"

@property
def qualname(self) -> str:
Expand Down
78 changes: 55 additions & 23 deletions canopen/objectdictionary/eds.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import copy
import logging
import re
import sys
from configparser import NoOptionError, NoSectionError, RawConfigParser
from datetime import datetime as dt
from typing import TYPE_CHECKING

from canopen.objectdictionary import (
Expand All @@ -24,7 +26,8 @@

def import_eds(source, node_id):
eds = RawConfigParser(inline_comment_prefixes=(';',))
eds.optionxform = str
eds.optionxform = str # type: ignore[assignment]
fp = None
opened_here = False
try:
if hasattr(source, "read"):
Expand All @@ -35,7 +38,7 @@ def import_eds(source, node_id):
eds.read_file(fp)
finally:
# Only close object if opened in this fn
if opened_here:
if opened_here and fp is not None:
fp.close()

od = ObjectDictionary()
Expand Down Expand Up @@ -139,14 +142,20 @@ def import_eds(source, node_id):
arr.add_member(last_subindex)
arr.add_member(build_variable(eds, section, node_id, object_type, index, 1))
arr.storage_location = storage_location
arr.obj_flags = _get_obj_flags(eds, section)
arr.denotation = eds.get(section, "Denotation") if eds.has_option(section, "Denotation") else ""
od.add_object(arr)
elif object_type == objectcodes.ARRAY:
arr = ODArray(name, index)
arr.storage_location = storage_location
arr.obj_flags = _get_obj_flags(eds, section)
arr.denotation = eds.get(section, "Denotation") if eds.has_option(section, "Denotation") else ""
od.add_object(arr)
elif object_type == objectcodes.RECORD:
record = ODRecord(name, index)
record.storage_location = storage_location
record.obj_flags = _get_obj_flags(eds, section)
record.denotation = eds.get(section, "Denotation") if eds.has_option(section, "Denotation") else ""
od.add_object(record)

continue
Expand All @@ -171,8 +180,10 @@ def import_eds(source, node_id):
index = int(match.group(1), 16)
num_of_entries = int(eds.get(section, "NrOfEntries"))
entry = od[index]
if not isinstance(entry, (ODRecord, ODArray)):
continue
# For CompactSubObj index 1 is were we find the variable
src_var = od[index][1]
src_var = entry[1]
for subindex in range(1, num_of_entries + 1):
var = copy_variable(eds, section, subindex, src_var)
if var is not None:
Expand Down Expand Up @@ -258,6 +269,15 @@ def _revert_variable(var_type, value):
return f"0x{value:02X}"


def _get_obj_flags(eds, section):
if eds.has_option(section, "ObjFlags"):
try:
return int(eds.get(section, "ObjFlags"), 0)
except ValueError:
pass
return 0


def build_variable(
eds: RawConfigParser,
section: str,
Expand Down Expand Up @@ -321,16 +341,16 @@ def build_variable(
pass
if eds.has_option(section, "DefaultValue"):
try:
var.default_raw = eds.get(section, "DefaultValue")
if '$NODEID' in var.default_raw:
var.default_raw = eds.get(section, "DefaultValue") # type: ignore[attr-defined]
if '$NODEID' in var.default_raw: # type: ignore[attr-defined]
var.relative = True
var.default = _convert_variable(node_id, var.data_type, var.default_raw)
var.default = _convert_variable(node_id, var.data_type, var.default_raw) # type: ignore[assignment,attr-defined]
except ValueError:
pass
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)
var.value_raw = eds.get(section, "ParameterValue") # type: ignore[attr-defined]
var.value = _convert_variable(node_id, var.data_type, var.value_raw) # type: ignore[assignment,attr-defined]
except ValueError:
pass
# Factor, Description and Unit are not standard according to the CANopen specifications, but
Expand All @@ -350,6 +370,9 @@ def build_variable(
var.unit = eds.get(section, "Unit")
except ValueError:
pass
var.obj_flags = _get_obj_flags(eds, section)
if eds.has_option(section, "Denotation"):
var.denotation = eds.get(section, "Denotation")
return var


Expand Down Expand Up @@ -425,23 +448,31 @@ def export_variable(var, eds):
if getattr(var, 'unit', '') != '':
eds.set(section, "Unit", var.unit)

if getattr(var, 'obj_flags', 0) != 0:
eds.set(section, "ObjFlags", f"0x{var.obj_flags:X}")
if device_commisioning and getattr(var, 'denotation', '') != '':
eds.set(section, "Denotation", var.denotation)

def export_record(var, eds):
section = f"{var.index:04X}"
export_common(var, eds, section)
eds.set(section, "SubNumber", f"0x{len(var.subindices):X}")
ot = objectcodes.RECORD if isinstance(var, ODRecord) else objectcodes.ARRAY
eds.set(section, "ObjectType", f"0x{ot:X}")
if getattr(var, 'obj_flags', 0) != 0:
eds.set(section, "ObjFlags", f"0x{var.obj_flags:X}")
if device_commisioning and getattr(var, 'denotation', '') != '':
eds.set(section, "Denotation", var.denotation)
for i in var:
export_variable(var[i], eds)

export_array = export_record

eds = RawConfigParser()
# both disables lowercasing, and allows int keys
eds.optionxform = str
eds.optionxform = str # type: ignore[assignment]

from datetime import datetime as dt
defmtime = dt.utcnow()
defmtime = dt.now()

try:
# only if eds was loaded by us
Expand All @@ -450,12 +481,12 @@ def export_record(var, eds):
origFileInfo = {
# just set some defaults
"CreationDate": defmtime.strftime("%m-%d-%Y"),
"CreationTime": defmtime.strftime("%I:%m%p"),
"CreationTime": defmtime.strftime("%I:%M%p"),
"EdsVersion": 4.2,
}

file_info.setdefault("ModificationDate", defmtime.strftime("%m-%d-%Y"))
file_info.setdefault("ModificationTime", defmtime.strftime("%I:%m%p"))
file_info.setdefault("ModificationTime", defmtime.strftime("%I:%M%p"))
for k, v in origFileInfo.items():
file_info.setdefault(k, v)

Expand Down Expand Up @@ -485,34 +516,36 @@ def export_record(var, eds):
continue
elif isinstance(val, str):
eds.set("DeviceInfo", eprop, val)
elif isinstance(val, (int, bool)):
eds.set("DeviceInfo", eprop, int(val))
elif isinstance(val, bool):
eds.set("DeviceInfo", eprop, str(int(val)))
elif isinstance(val, int):
eds.set("DeviceInfo", eprop, str(val))

# we are also adding out of spec baudrates here.
for rate in od.device_information.allowed_baudrates.union(
{10e3, 20e3, 50e3, 125e3, 250e3, 500e3, 800e3, 1000e3}):
eds.set(
"DeviceInfo", f"BaudRate_{int(rate//1000)}",
int(rate in od.device_information.allowed_baudrates))
str(int(rate in od.device_information.allowed_baudrates)))

if device_commisioning and (od.bitrate or od.node_id):
eds.add_section("DeviceComissioning")
if od.bitrate:
eds.set("DeviceComissioning", "Baudrate", int(od.bitrate / 1000))
eds.set("DeviceComissioning", "Baudrate", str(od.bitrate // 1000))
if od.node_id:
eds.set("DeviceComissioning", "NodeID", int(od.node_id))
eds.set("DeviceComissioning", "NodeID", str(od.node_id))

eds.add_section("Comments")
i = 0
for line in od.comments.splitlines():
i += 1
eds.set("Comments", f"Line{i}", line)
eds.set("Comments", "Lines", i)
eds.set("Comments", "Lines", str(i))

eds.add_section("DummyUsage")
for i in range(1, 8):
key = f"Dummy{i:04d}"
eds.set("DummyUsage", key, 1 if (key in od) else 0)
eds.set("DummyUsage", key, str(1 if (key in od) else 0))

def mandatory_indices(x):
return x in {0x1000, 0x1001, 0x1018}
Expand All @@ -533,9 +566,9 @@ def optional_indices(x):

def add_list(section, list):
eds.add_section(section)
eds.set(section, "SupportedObjects", len(list))
eds.set(section, "SupportedObjects", str(len(list)))
for i in range(0, len(list)):
eds.set(section, (i + 1), f"0x{list[i]:04X}")
eds.set(section, str(i + 1), f"0x{list[i]:04X}")
for index in list:
export_object(od[index], eds)

Expand All @@ -544,7 +577,6 @@ def add_list(section, list):
add_list("ManufacturerObjects", supported_manufacturer_indices)

if not dest:
import sys
dest = sys.stdout

eds.write(dest, False)
29 changes: 29 additions & 0 deletions test/sample.eds
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,14 @@ DataType=0x0007
AccessType=rw
PDOMapping=0

[3060]
ParameterName=Object with ObjFlags
ObjectType=0x7
DataType=0x0007
AccessType=rw
PDOMapping=0
ObjFlags=0x1

[3064]
ParameterName=Record with DOMAIN sub-object
SubNumber=0x2
Expand All @@ -1044,3 +1052,24 @@ ObjectType=0x2
DataType=0x0007
AccessType=rw
PDOMapping=0

[3065]
ParameterName=Record with ObjFlags
ObjectType=0x9
ObjFlags=0x3
SubNumber=0x2

[3065sub0]
ParameterName=Highest sub-index supported
ObjectType=0x7
DataType=0x0005
AccessType=ro
DefaultValue=0x01
PDOMapping=0

[3065sub1]
ParameterName=Value
ObjectType=0x7
DataType=0x0007
AccessType=rw
PDOMapping=0
Loading