From 96977df277b87d21fcb23300400f9bdd663c17ea Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 12 Feb 2024 10:24:33 +0100 Subject: [PATCH 01/28] Fixup UI message dialogs and errors * Use display_error_dialog() instead of repeatedly calling wx.MessageDialog() * Fixup UI exception handling * Changed methods to snake_case --- src/objdictgen/ui/commondialogs.py | 41 +++------ src/objdictgen/ui/exception.py | 96 ++++++++++++---------- src/objdictgen/ui/networkedit.py | 20 ++--- src/objdictgen/ui/networkeditortemplate.py | 5 +- src/objdictgen/ui/nodeeditortemplate.py | 12 +-- src/objdictgen/ui/objdictedit.py | 48 +++-------- src/objdictgen/ui/subindextable.py | 9 +- 7 files changed, 92 insertions(+), 139 deletions(-) diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index ec90610..e9d71cc 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -26,6 +26,7 @@ import objdictgen from objdictgen.maps import OD from objdictgen.node import BE_to_LE, LE_to_BE +from objdictgen.ui.exception import display_error_dialog, display_exception_dialog log = logging.getLogger('objdictgen') @@ -376,9 +377,7 @@ def OnOK(self, event): # pylint: disable=unused-argument text += (" and %s") % 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() + display_error_dialog(self, "Form isn't valid. %s" % text) else: self.EndModal(wx.ID_OK) @@ -577,9 +576,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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, "Form isn't valid. %s" % message) else: self.EndModal(wx.ID_OK) @@ -775,9 +772,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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) @@ -1050,9 +1045,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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) @@ -1226,9 +1219,7 @@ def OnOK(self, event): # pylint: disable=unused-argument text += " and %s" % 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() + display_error_dialog(self, "Form isn't complete. %s must be filled!" % text) else: try: nodeid = self.SlaveNodeID.GetValue() @@ -1238,18 +1229,12 @@ def OnOK(self, event): # pylint: disable=unused-argument nodeid = int(nodeid) 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() + 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) @@ -1272,9 +1257,7 @@ def OnImportEDSButton(self, event): 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() + display_exception_dialog(self) dialog.Destroy() self.RefreshEDSFile() event.Skip() @@ -1532,9 +1515,7 @@ def OnValuesGridCellChange(self, event): try: self.Values[row][colname] = int(value, 16) 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, "'%s' is not a valid value!" % value) wx.CallAfter(self.RefreshValues) event.Skip() diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index b4bd8c7..18909bf 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -29,7 +29,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 +45,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,17 +54,19 @@ def Display_Exception_Dialog(e_type, e_value, e_tb): """ + str(e_type) + " : " + str(e_value)), "Error", - trcbck_lst) - try: + trcbck_lst) as dlg: res = (dlg.ShowModal() == wx.ID_OK) - finally: - dlg.Destroy() return res -def Display_Error_Dialog(e_value): - message = wx.MessageDialog(None, str(e_value), "Error", wx.OK | wx.ICON_ERROR) +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"): + message = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_ERROR) message.ShowModal() message.Destroy() @@ -76,47 +78,53 @@ 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()]) + return '\n'.join(f"{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") +def handle_exception(e_type, e_value, e_traceback, parent=None): + + # Import here to prevent circular import + from objdictgen import ODG_VERSION # pylint: disable=import-outside-toplevel + app_version = ODG_VERSION + + 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(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': 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)) + f'{e_type}: {e_value}' + 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(os.path.join(os.getcwd(), "bug_report_" + info['date'].replace(':', '-').replace(' ', '_') + ".txt"), 'w') 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..97c8673 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -27,7 +27,7 @@ import objdictgen from objdictgen.nodelist import NodeList 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') @@ -277,9 +277,7 @@ def OnNewProjectMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) def OnOpenProjectMenu(self, event): # pylint: disable=unused-argument if self.NodeList: @@ -305,9 +303,7 @@ def OnOpenProjectMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) dialog.Destroy() def OnSaveProjectMenu(self, event): # pylint: disable=unused-argument @@ -317,9 +313,7 @@ def OnSaveProjectMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) def OnCloseProjectMenu(self, event): # pylint: disable=unused-argument if self.NodeList: @@ -331,9 +325,7 @@ def OnCloseProjectMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) elif answer == wx.ID_NO: self.NodeList.Changed = False if not self.NodeList.HasChanged(): @@ -410,7 +402,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..3db4b8c 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -22,6 +22,7 @@ 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 display_exception_dialog [ ID_NETWORKEDITNETWORKNODES, @@ -113,7 +114,7 @@ def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument self.NetworkNodes.SetSelection(idx) self.RefreshBufferState() except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) + display_exception_dialog(self.Frame) dialog.Destroy() def OnRemoveSlaveMenu(self, event): # pylint: disable=unused-argument @@ -134,7 +135,7 @@ def OnRemoveSlaveMenu(self, event): # pylint: disable=unused-argument self.NodeList.CurrentSelected = slaveids[new_selection - 1] self.RefreshBufferState() except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) + display_exception_dialog(self.Frame) dialog.Destroy() def OpenMasterDCFDialog(self, node_id): diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 64c0912..7d62d34 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -21,6 +21,7 @@ from objdictgen.maps import OD from objdictgen.ui import commondialogs as cdia +from objdictgen.ui.exception import display_error_dialog, display_exception_dialog class NodeEditorTemplate: @@ -220,10 +221,10 @@ def AddMapVariable(self): self.RefreshBufferState() self.RefreshCurrentIndexList() except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(exc) + display_exception_dialog(self.Frame) dialog.Destroy() else: - self.ShowErrorMessage("No map variable index left!") + display_error_dialog(self.Frame, "No map variable index left!") def AddUserType(self): dialog = cdia.UserTypeDialog(self) @@ -234,10 +235,5 @@ def AddUserType(self): self.RefreshBufferState() self.RefreshCurrentIndexList() except Exception as exc: # pylint: disable=broad-except - self.ShowErrorMessage(str(exc)) + display_exception_dialog(self.Frame) dialog.Destroy() - - def ShowErrorMessage(self, message): - message = wx.MessageDialog(self.Frame, message, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 1204847..b50c06c 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -28,7 +28,7 @@ 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.ui.exception import add_except_hook, display_error_dialog, display_exception_dialog log = logging.getLogger('objdictgen') @@ -386,9 +386,7 @@ def OnNewMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) dialog.Destroy() def OnOpenMenu(self, event): # pylint: disable=unused-argument @@ -416,9 +414,7 @@ def OnOpenMenu(self, event): # pylint: disable=unused-argument 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() + display_exception_dialog(self) dialog.Destroy() def OnSaveMenu(self, event): # pylint: disable=unused-argument @@ -439,9 +435,7 @@ def Save(self): 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() + display_exception_dialog(self) def SaveAs(self): filepath = self.Manager.GetCurrentFilePath() @@ -459,9 +453,7 @@ def SaveAs(self): 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() + display_error_dialog(self, "'%s' is not a valid folder!" % os.path.dirname(filepath)) else: try: #Try and save the file and then update the filepath if successfull @@ -469,9 +461,7 @@ def SaveAs(self): 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() + display_exception_dialog(self) def OnCloseMenu(self, event): answer = wx.ID_YES @@ -517,13 +507,9 @@ def OnImportEDSMenu(self, event): # pylint: disable=unused-argument message.ShowModal() message.Destroy() except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_exception_dialog(self) else: - message = wx.MessageDialog(self, "'%s' is not a valid file!" % filepath, "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_error_dialog(self, "'%s' is not a valid file!" % filepath) dialog.Destroy() def OnExportEDSMenu(self, event): # pylint: disable=unused-argument @@ -531,9 +517,7 @@ def OnExportEDSMenu(self, event): # pylint: disable=unused-argument if dialog.ShowModal() == wx.ID_OK: 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() + display_error_dialog(self, "'%s' is not a valid folder!" % os.path.dirname(filepath)) else: path, extend = os.path.splitext(filepath) if extend in ("", "."): @@ -544,9 +528,7 @@ def OnExportEDSMenu(self, event): # pylint: disable=unused-argument message.ShowModal() message.Destroy() except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_exception_dialog(self) dialog.Destroy() def OnExportCMenu(self, event): # pylint: disable=unused-argument @@ -554,9 +536,7 @@ def OnExportCMenu(self, event): # pylint: disable=unused-argument if dialog.ShowModal() == wx.ID_OK: 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() + display_error_dialog(self, "'%s' is not a valid folder!" % os.path.dirname(filepath)) else: path, extend = os.path.splitext(filepath) if extend in ("", "."): @@ -567,9 +547,7 @@ def OnExportCMenu(self, event): # pylint: disable=unused-argument message.ShowModal() message.Destroy() except Exception as exc: # pylint: disable=broad-except - message = wx.MessageDialog(self, str(exc), "Error", wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + display_exception_dialog(self) dialog.Destroy() @@ -579,7 +557,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 27c769e..03a7b94 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -25,6 +25,7 @@ from objdictgen import maps from objdictgen.maps import OD from objdictgen.ui import commondialogs as cdia +from objdictgen.ui.exception import display_error_dialog 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] @@ -864,9 +865,7 @@ def OnAddSubindexMenu(self, event): # pylint: disable=unused-argument 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() + display_error_dialog(self, "An integer is required!") dialog.Destroy() def OnDeleteSubindexMenu(self, event): # pylint: disable=unused-argument @@ -884,9 +883,7 @@ def OnDeleteSubindexMenu(self, event): # pylint: disable=unused-argument 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() + display_error_dialog(self, "An integer is required!") dialog.Destroy() def OnDefaultValueSubindexMenu(self, event): # pylint: disable=unused-argument From babf8078806a87df374eb86a5d250283e8936c69 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 12 Feb 2024 21:23:51 +0100 Subject: [PATCH 02/28] Fixup imports --- src/objdictgen/__init__.py | 8 ++----- src/objdictgen/eds_utils.py | 6 +++--- src/objdictgen/jsonod.py | 6 +++--- src/objdictgen/node.py | 26 +++++++++++----------- src/objdictgen/nodemanager.py | 37 ++++++++++++++++---------------- src/objdictgen/ui/objdictedit.py | 3 ++- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/objdictgen/__init__.py b/src/objdictgen/__init__.py index 7714d1a..e6ec65b 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -19,8 +19,7 @@ import os -from objdictgen.maps import OD -from objdictgen.node import Find, ImportProfile, Node +from objdictgen.node import Node from objdictgen.nodemanager import NodeManager # Shortcuts @@ -40,11 +39,8 @@ JSON_SCHEMA = os.path.join(SCRIPT_DIRECTORY, 'schema', 'od.schema.json') __all__ = [ - "Node", - "ImportProfile", - "Find", "LoadFile", "LoadJson", + "Node", "NodeManager", - "OD", ] diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 84b056d..fa0ad1b 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -22,7 +22,7 @@ import re from time import localtime, strftime -import objdictgen +from objdictgen import node as nodelib from objdictgen.maps import OD # Regular expression for finding index section names @@ -653,7 +653,7 @@ def GenerateCPJContent(nodelist): # Function that generates Node from an EDS file def GenerateNode(filepath, nodeid=0): # 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) @@ -673,7 +673,7 @@ def GenerateNode(filepath, nodeid=0): try: # Import profile profilename = "DS-%d" % profilenb - mapping, menuentries = objdictgen.ImportProfile(profilename) + mapping, menuentries = nodelib.ImportProfile(profilename) node.ProfileName = profilename node.Profile = mapping node.SpecificMenu = menuentries diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 7cbb0a2..3da13c4 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -27,7 +27,7 @@ import jsonschema import objdictgen -from objdictgen import maps +from objdictgen import maps, node as nodelib from objdictgen.maps import OD log = logging.getLogger('objdictgen') @@ -299,7 +299,7 @@ def get_object_types(node=None, dictionary=None): def compare_profile(profilename, params, menu=None): try: - dsmap, menumap = objdictgen.ImportProfile(profilename) + dsmap, menumap = nodelib.ImportProfile(profilename) identical = all( k in dsmap and k in params and dsmap[k] == params[k] for k in set(dsmap) | set(params) @@ -852,7 +852,7 @@ def node_fromdict(jd, internal=False): jd.setdefault("profile", "None") # Create the node and fill the most basic data - node = objdictgen.Node( + node = nodelib.Node( name=jd["name"], type=jd["type"], id=jd["id"], description=jd["description"], profilename=jd["profile"], ) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 1a7aa0f..a0565ce 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -28,7 +28,7 @@ import objdictgen from objdictgen import eds_utils, gen_cfile, jsonod, maps, nosis -from objdictgen.maps import MAPPING_DICTIONARY, OD +from objdictgen.maps import OD log = logging.getLogger('objdictgen') @@ -844,7 +844,7 @@ def GetBaseIndex(self, index): result = Find.Index(index, mapping) if result: return result - return Find.Index(index, MAPPING_DICTIONARY) + return Find.Index(index, maps.MAPPING_DICTIONARY) def GetBaseIndexNumber(self, index): """ Return the index number from the base object """ @@ -852,9 +852,9 @@ def GetBaseIndexNumber(self, index): result = Find.Index(index, mapping) if result is not None: return (index - result) // mapping[result].get("incr", 1) - result = Find.Index(index, MAPPING_DICTIONARY) + result = Find.Index(index, maps.MAPPING_DICTIONARY) if result is not None: - return (index - result) // MAPPING_DICTIONARY[result].get("incr", 1) + return (index - result) // maps.MAPPING_DICTIONARY[result].get("incr", 1) return 0 def GetCustomisedTypeValues(self, index): @@ -870,7 +870,7 @@ def GetEntryName(self, index, compute=True): result = Find.EntryName(index, mappings[i], compute) i += 1 if result is None: - result = Find.EntryName(index, MAPPING_DICTIONARY, compute) + result = Find.EntryName(index, maps.MAPPING_DICTIONARY, compute) return result def GetEntryInfos(self, index, compute=True): @@ -880,7 +880,7 @@ def GetEntryInfos(self, index, compute=True): while not result and i < len(mappings): result = Find.EntryInfos(index, mappings[i], compute) i += 1 - r301 = Find.EntryInfos(index, MAPPING_DICTIONARY, compute) + r301 = Find.EntryInfos(index, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -896,7 +896,7 @@ def GetSubentryInfos(self, index, subindex, compute=True): if result: result["user_defined"] = i == len(mappings) - 1 and index >= 0x1000 i += 1 - r301 = Find.SubentryInfos(index, subindex, MAPPING_DICTIONARY, compute) + r301 = Find.SubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -950,7 +950,7 @@ def GetTypeIndex(self, typename): result = Find.TypeIndex(typename, mappings[i]) i += 1 if result is None: - result = Find.TypeIndex(typename, MAPPING_DICTIONARY) + result = Find.TypeIndex(typename, maps.MAPPING_DICTIONARY) return result def GetTypeName(self, typeindex): @@ -961,7 +961,7 @@ def GetTypeName(self, typeindex): result = Find.TypeName(typeindex, mappings[i]) i += 1 if result is None: - result = Find.TypeName(typeindex, MAPPING_DICTIONARY) + result = Find.TypeName(typeindex, maps.MAPPING_DICTIONARY) return result def GetTypeDefaultValue(self, typeindex): @@ -972,18 +972,18 @@ def GetTypeDefaultValue(self, typeindex): result = Find.TypeDefaultValue(typeindex, mappings[i]) i += 1 if result is None: - result = Find.TypeDefaultValue(typeindex, MAPPING_DICTIONARY) + result = Find.TypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) return result def GetMapVariableList(self, compute=True): - list_ = list(Find.MapVariableList(MAPPING_DICTIONARY, self, compute)) + list_ = list(Find.MapVariableList(maps.MAPPING_DICTIONARY, self, compute)) for mapping in self.GetMappings(): list_.extend(Find.MapVariableList(mapping, self, compute)) list_.sort() return list_ def GetMandatoryIndexes(self, node=None): # pylint: disable=unused-argument - list_ = Find.MandatoryIndexes(MAPPING_DICTIONARY) + list_ = Find.MandatoryIndexes(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): list_.extend(Find.MandatoryIndexes(mapping)) return list_ @@ -1021,7 +1021,7 @@ def IsRealType(self, index): # -------------------------------------------------------------------------- def GetTypeList(self): - list_ = Find.TypeList(MAPPING_DICTIONARY) + list_ = Find.TypeList(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): list_.extend(Find.TypeList(mapping)) return list_ diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index c3a9647..c79aad7 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -24,9 +24,8 @@ 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 import maps, node as nodelib +from objdictgen.maps import OD log = logging.getLogger('objdictgen') @@ -185,11 +184,11 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt Create a new node and add a new buffer for storing it """ # Create a new node - node = Node() + node = nodelib.Node() # Load profile given if profile != "None": # Import profile - mapping, menuentries = ImportProfile(filepath) + mapping, menuentries = nodelib.ImportProfile(filepath) node.ProfileName = profile node.Profile = mapping node.SpecificMenu = menuentries @@ -213,7 +212,7 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt for option in options: if option == "DS302": # Import profile - mapping, menuentries = ImportProfile("DS-302") + mapping, menuentries = nodelib.ImportProfile("DS-302") self.CurrentNode.DS302 = mapping self.CurrentNode.SpecificMenu.extend(menuentries) elif option == "GenSYNC": @@ -816,10 +815,10 @@ def GetCurrentBufferState(self): def GetCurrentCommunicationLists(self): list_ = [] - for index in MAPPING_DICTIONARY: + for index in maps.MAPPING_DICTIONARY: if 0x1000 <= index < 0x1200: list_.append(index) - return self.GetProfileLists(MAPPING_DICTIONARY, list_) + return self.GetProfileLists(maps.MAPPING_DICTIONARY, list_) def GetCurrentDS302Lists(self): return self.GetSpecificProfileLists(self.CurrentNode.DS302) @@ -941,7 +940,7 @@ def GetCurrentValidChoices(self, min_, max_): good &= min_ <= index <= max_ if good: validchoices.append((menu, None)) - list_ = [index for index in MAPPING_DICTIONARY if index >= 0x1000] + list_ = [index for index in maps.MAPPING_DICTIONARY if index >= 0x1000] profiles = self.CurrentNode.GetMappings(False) for profile in profiles: list_.extend(list(profile)) @@ -1069,11 +1068,11 @@ def AddToDCF(self, node_id, index, subindex, size, value): if self.CurrentNode.IsEntry(0x1F22, node_id): dcf_value = self.CurrentNode.GetEntry(0x1F22, node_id) if dcf_value: - nbparams = BE_to_LE(dcf_value[:4]) + nbparams = nodelib.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) + new_value = nodelib.LE_to_BE(nbparams + 1, 4) + dcf_value[4:] + new_value += nodelib.LE_to_BE(index, 2) + nodelib.LE_to_BE(subindex, 1) + nodelib.LE_to_BE(size, 4) + nodelib.LE_to_BE(value, size) self.CurrentNode.SetEntry(0x1F22, node_id, new_value) # -------------------------------------------------------------------------- @@ -1090,17 +1089,17 @@ def GetCustomisedTypeValues(self, index): def GetEntryName(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryName(index, compute) - return Find.EntryName(index, MAPPING_DICTIONARY, compute) + return nodelib.Find.EntryName(index, maps.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) + return nodelib.Find.EntryInfos(index, maps.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) + result = nodelib.Find.SubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if result: result["user_defined"] = False return result @@ -1108,17 +1107,17 @@ def GetSubentryInfos(self, index, subindex, compute=True): def GetTypeIndex(self, typename): if self.CurrentNode: return self.CurrentNode.GetTypeIndex(typename) - return Find.TypeIndex(typename, MAPPING_DICTIONARY) + return nodelib.Find.TypeIndex(typename, maps.MAPPING_DICTIONARY) def GetTypeName(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeName(typeindex) - return Find.TypeName(typeindex, MAPPING_DICTIONARY) + return nodelib.Find.TypeName(typeindex, maps.MAPPING_DICTIONARY) def GetTypeDefaultValue(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeDefaultValue(typeindex) - return Find.TypeDefaultValue(typeindex, MAPPING_DICTIONARY) + return nodelib.Find.TypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) def GetMapVariableList(self, compute=True): if self.CurrentNode: @@ -1128,7 +1127,7 @@ def GetMapVariableList(self, compute=True): def GetMandatoryIndexes(self): if self.CurrentNode: return self.CurrentNode.GetMandatoryIndexes() - return Find.MandatoryIndexes(MAPPING_DICTIONARY) + return nodelib.Find.MandatoryIndexes(maps.MAPPING_DICTIONARY) def GetCustomisableTypes(self): return { diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index b50c06c..32e291a 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -25,6 +25,7 @@ import wx import objdictgen +from objdictgen import nodemanager from objdictgen.ui import commondialogs as cdia from objdictgen.ui import nodeeditortemplate as net from objdictgen.ui import subindextable as sit @@ -212,7 +213,7 @@ def _init_ctrls(self, prnt): def __init__(self, parent, manager=None, filesopen=None): filesopen = filesopen or [] if manager is None: - net.NodeEditorTemplate.__init__(self, objdictgen.NodeManager(), self, True) + net.NodeEditorTemplate.__init__(self, nodemanager.NodeManager(), self, True) else: net.NodeEditorTemplate.__init__(self, manager, self, False) self._init_ctrls(parent) From 79f2d7b7d701a215d0f9d29ed4140a933b175937 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 12 Feb 2024 23:44:04 +0100 Subject: [PATCH 03/28] Cleanups pt 2 * Rename functions from CamelCase to snake_case * Restructure functions in node.py to be staticmethods in Node * Clean up confetest for pytest --- src/objdictgen/eds_utils.py | 36 +- src/objdictgen/gen_cfile.py | 63 +-- src/objdictgen/jsonod.py | 6 +- src/objdictgen/node.py | 738 ++++++++++++++--------------- src/objdictgen/nodelist.py | 6 +- src/objdictgen/nodemanager.py | 33 +- src/objdictgen/nosis.py | 2 +- src/objdictgen/ui/commondialogs.py | 22 +- tests/conftest.py | 74 +-- tests/test_node.py | 40 +- 10 files changed, 514 insertions(+), 506 deletions(-) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index fa0ad1b..945764d 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -87,7 +87,7 @@ # 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, index, subindex=None): infos = node.GetEntryInfos(index) if infos["struct"] & OD.MultipleSubindexes: # First case entry is a array @@ -124,7 +124,7 @@ def GetDefaultValue(node, index, subindex=None): # Function that extract sections from a file and returns a dictionary of the informations -def ExtractSections(file): +def extract_sections(file): return [ (blocktuple[0], # EntryName : Assignements dict blocktuple[-1].splitlines()) # all the lines @@ -136,14 +136,14 @@ def ExtractSections(file): # Function that parse an CPJ file and returns a dictionary of the informations -def ParseCPJFile(filepath): +def parse_cpj_file(filepath): networks = [] # Read file text with open(filepath, "r") as f: cpj_file = f.read() - sections = ExtractSections(cpj_file) + sections = extract_sections(cpj_file) # Parse assignments for each section for section_name, assignments in sections: @@ -250,14 +250,14 @@ def ParseCPJFile(filepath): # Function that parse an EDS file and returns a dictionary of the informations -def ParseEDSFile(filepath): +def parse_eds_file(filepath): eds_dict = {} # Read file text with open(filepath, 'r') 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: @@ -397,13 +397,13 @@ def ParseEDSFile(filepath): 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"])) - 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, section_name, param): uparam = param.upper() if uparam in values: try: @@ -420,7 +420,7 @@ def VerifyValue(values, section_name, param): # Function that generate the EDS file content for the current node in the manager -def GenerateFileContent(node, filepath): +def generate_eds_content(node, filepath): # Dictionary of each index contents indexcontents = {} @@ -626,15 +626,15 @@ def GenerateFileContent(node, filepath): # Function that generates EDS file from current node edited -def GenerateEDSFile(filepath, node): - content = GenerateFileContent(node, filepath) +def generate_eds_file(filepath, node): + content = generate_eds_content(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): nodes = nodelist.SlaveNodes filecontent = "[TOPOLOGY]\n" @@ -651,12 +651,12 @@ def GenerateCPJContent(nodelist): # Function that generates Node from an EDS file -def GenerateNode(filepath, nodeid=0): +def generate_node(filepath, nodeid=0): # Create a new node node = nodelib.Node(id=nodeid) # Parse file and extract dictionary of EDS entry - eds_dict = ParseEDSFile(filepath) + eds_dict = parse_eds_file(filepath) # Ensure we have the ODs we need missing = ["0x%04X" % i for i in ( @@ -673,7 +673,7 @@ def GenerateNode(filepath, nodeid=0): try: # Import profile profilename = "DS-%d" % profilenb - mapping, menuentries = nodelib.ImportProfile(profilename) + mapping, menuentries = nodelib.Node.ImportProfile(profilename) node.ProfileName = profilename node.Profile = mapping node.SpecificMenu = menuentries @@ -776,7 +776,7 @@ def GenerateNode(filepath, nodeid=0): value = values["DEFAULTVALUE"] # Find default value for value type of the entry else: - value = GetDefaultValue(node, entry) + 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]: @@ -794,7 +794,7 @@ def GenerateNode(filepath, nodeid=0): value = values["subindexes"][subindex]["DEFAULTVALUE"] # Find default value for value type of the subindex else: - value = GetDefaultValue(node, entry, subindex) + value = get_default_value(node, entry, subindex) node.AddEntry(entry, subindex, value) else: raise ValueError("Array or Record entry 0x%4.4X must have a 'SubNumber' attribute" % entry) diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 96f7a8d..7bb27dc 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -44,13 +44,13 @@ def __init__(self): # Format a string for making a C++ variable -def FormatName(name): +def format_name(name): wordlist = [word for word in RE_WORD.findall(name) if word] return "_".join(wordlist) # Extract the informations from a given type name -def GetValidTypeInfos(context, typename, items=None): +def get_valid_type_infos(context, typename, items=None): items = items or [] if typename in context.internal_types: return context.internal_types[typename] @@ -88,7 +88,7 @@ def GetValidTypeInfos(context, typename, items=None): return typeinfos -def ComputeValue(type_, value): +def compute_value(type_, value): if type_ == "visible_string": return '"%s"' % value, "" if type_ == "domain": @@ -101,7 +101,7 @@ def ComputeValue(type_, value): return "0x%X" % value, "\t/* %s */" % str(value) -def GetTypeName(node, typenumber): +def get_type_name(node, typenumber): typename = node.GetTypeName(typenumber) if typename is None: # FIXME: The !!! is for special UI handling @@ -109,7 +109,7 @@ def GetTypeName(node, typenumber): return typename -def GenerateFileContent(node, headerfilepath, pointers_dict=None): +def generate_file_content(node, headerfilepath, pointers_dict=None): """ pointers_dict = {(Idx,Sidx):"VariableName",...} """ @@ -157,7 +157,7 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): num += 1 typeindex = node.GetEntry(index, 1) typename = node.GetTypeName(typeindex) - typeinfos = GetValidTypeInfos(context, typename) + typeinfos = get_valid_type_infos(context, typename) context.internal_types[rangename] = (typeinfos[0], typeinfos[1], "valueRange_%d" % num) minvalue = node.GetEntry(index, 2) maxvalue = node.GetEntry(index, 3) @@ -200,8 +200,8 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): # Entry type is VAR if not isinstance(values, list): subentry_infos = node.GetSubentryInfos(index, 0) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, [values]) + typename = get_type_name(node, subentry_infos["type"]) + typeinfos = get_valid_type_infos(context, 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) @@ -213,9 +213,9 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): texts["suffixe"] = "[%d]" % typeinfos[1] else: texts["suffixe"] = "" - texts["value"], texts["comment"] = ComputeValue(typeinfos[2], values) + texts["value"], texts["comment"] = compute_value(typeinfos[2], values) if index in variablelist: - texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(subentry_infos["name"])) + texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(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 else: @@ -223,8 +223,8 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): values = [values] else: subentry_infos = node.GetSubentryInfos(index, 0) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename) + typename = get_type_name(node, subentry_infos["type"]) + typeinfos = get_valid_type_infos(context, typename) if index == 0x1003: texts["value"] = 0 else: @@ -236,7 +236,7 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): if entry_infos["struct"] & OD.IdenticalSubindexes: subentry_infos = node.GetSubentryInfos(index, 1) typename = node.GetTypeName(subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, values[1:]) + typeinfos = get_valid_type_infos(context, typename, values[1:]) texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: texts["suffixe"] = "[%d]" % typeinfos[1] @@ -246,7 +246,7 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): texts["type_suffixe"] = "" texts["length"] = values[0] if index in variablelist: - texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(entry_infos["name"])) + texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(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 @@ -255,7 +255,7 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): if subindex > 0: if subindex == len(values) - 1: sep = "" - value, comment = ComputeValue(typeinfos[2], value) + value, comment = compute_value(typeinfos[2], value) 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) @@ -267,20 +267,20 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): if subindex > 0: if subindex == len(values) - 1: sep = "" - value, comment = ComputeValue(typeinfos[2], value) + value, comment = compute_value(typeinfos[2], value) strindex += " %s%s%s\n" % (value, sep, comment) strindex += " };\n" else: - texts["parent"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', FormatName(entry_infos["name"])) + texts["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 params_infos = node.GetParamsEntry(index, subindex) if subindex > 0: subentry_infos = node.GetSubentryInfos(index, subindex) - typename = GetTypeName(node, subentry_infos["type"]) - typeinfos = GetValidTypeInfos(context, typename, [values[subindex]]) + typename = get_type_name(node, subentry_infos["type"]) + typeinfos = get_valid_type_infos(context, typename, [values[subindex]]) texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: if params_infos["buffer_size"]: @@ -289,8 +289,8 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): texts["suffixe"] = "[%d]" % typeinfos[1] else: texts["suffixe"] = "" - texts["value"], texts["comment"] = ComputeValue(typeinfos[2], value) - texts["name"] = FormatName(subentry_infos["name"]) + texts["value"], texts["comment"] = compute_value(typeinfos[2], value) + texts["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 @@ -317,28 +317,28 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): sep = "" typename = node.GetTypeName(subentry_infos["type"]) if entry_infos["struct"] & OD.IdenticalSubindexes: - typeinfos = GetValidTypeInfos(context, typename, values[1:]) + typeinfos = get_valid_type_infos(context, typename, values[1:]) else: - typeinfos = GetValidTypeInfos(context, typename, [values[subindex]]) + typeinfos = get_valid_type_infos(context, typename, [values[subindex]]) if subindex == 0: if index == 0x1003: - typeinfos = GetValidTypeInfos(context, "valueRange_EMC") + typeinfos = get_valid_type_infos(context, "valueRange_EMC") if entry_infos["struct"] & OD.MultipleSubindexes: name = "%(NodeName)s_highestSubIndex_obj%(index)04X" % texts 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("%s_obj%04X" % (texts["NodeName"], texts["index"])) elif entry_infos["struct"] & OD.IdenticalSubindexes: if index in variablelist: - name = "%s[%d]" % (FormatName(entry_infos["name"]), subindex - 1) + name = "%s[%d]" % (format_name(entry_infos["name"]), subindex - 1) else: name = "%s_obj%04X[%d]" % (texts["NodeName"], texts["index"], subindex - 1) else: if index in variablelist: - name = FormatName("%s_%s" % (entry_infos["name"], subentry_infos["name"])) + name = format_name("%s_%s" % (entry_infos["name"], subentry_infos["name"])) else: - name = "%s_obj%04X_%s" % (texts["NodeName"], texts["index"], FormatName(subentry_infos["name"])) + name = "%s_obj%04X_%s" % (texts["NodeName"], texts["index"], format_name(subentry_infos["name"])) if typeinfos[2] == "visible_string": if params_infos["buffer_size"]: sizeof = params_infos["buffer_size"] @@ -642,11 +642,12 @@ def GenerateFileContent(node, headerfilepath, pointers_dict=None): # Main Function # ------------------------------------------------------------------------------ -def GenerateFile(filepath, node, pointers_dict=None): +def generate_file(filepath, node, pointers_dict=None): + """Main function to generate the C file from a object dictionary node.""" 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) + content, header, header_defs = generate_file_content(node, os.path.basename(headerfilepath), pointers_dict) # Write main .c contents with open(filepath, "wb") as f: diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 3da13c4..fbc0c00 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -299,7 +299,7 @@ def get_object_types(node=None, dictionary=None): def compare_profile(profilename, params, menu=None): try: - dsmap, menumap = nodelib.ImportProfile(profilename) + dsmap, menumap = nodelib.Node.ImportProfile(profilename) identical = all( k in dsmap and k in params and dsmap[k] == params[k] for k in set(dsmap) | set(params) @@ -316,7 +316,7 @@ def compare_profile(profilename, params, menu=None): return False, False -def GenerateJson(node, compact=False, sort=False, internal=False, validate=True): +def generate_json(node, compact=False, sort=False, internal=False, validate=True): ''' Export a JSON string representation of the node ''' # Get the dict representation @@ -367,7 +367,7 @@ def _index_repl(m): return out -def GenerateNode(contents): +def generate_node(contents): ''' Import from JSON string or objects ''' jd = contents diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index a0565ce..8eb25ff 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -40,337 +40,6 @@ RE_NAME = re.compile(r'(.*)\[[(](.*)[)]\]') -# ------------------------------------------------------------------------------ -# Utils -# ------------------------------------------------------------------------------ -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 - """ - - # 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]) - - -# ------------------------------------------------------------------------------ -# 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 - - -# ------------------------------------------------------------------------------ -# Search in a Mapping Dictionary -# ------------------------------------------------------------------------------ - -class Find: - """ Collection of static methods for seaching in a mapping directory """ - - @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) - - @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 - - @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 - - @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 - - @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 - - @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 - - @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) - - @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"] - ] - - @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 - - # ------------------------------------------------------------------------------ # Definition of Node Object # ------------------------------------------------------------------------------ @@ -398,21 +67,32 @@ def __init__(self, name="", type="slave", id=0, description="", profilename="DS- self.IndexOrder = [] # -------------------------------------------------------------------------- - # Node Input/Output + # Node Input/Output # -------------------------------------------------------------------------- @staticmethod - def LoadFile(filepath): - # type: (str) -> Node + def isXml(filepath): + with open(filepath, 'r') as f: + header = f.read(5) + return header == " "Node": """ Open a file and create a new node """ - if isXml(filepath): + if Node.isXml(filepath): log.debug("Loading XML OD '%s'" % filepath) with open(filepath, "r") as f: return nosis.xmlload(f) # type: ignore - if isEds(filepath): + if Node.isEds(filepath): log.debug("Loading EDS '%s'" % filepath) - return eds_utils.GenerateNode(filepath) + return eds_utils.generate_node(filepath) log.debug("Loading JSON OD '%s'" % filepath) with open(filepath, "r") as f: @@ -421,7 +101,7 @@ def LoadFile(filepath): @staticmethod def LoadJson(contents): """ Import a new Node from a JSON string """ - return jsonod.GenerateNode(contents) + return jsonod.generate_node(contents) def DumpFile(self, filepath, filetype="json", **kwargs): """ Save node into file """ @@ -434,7 +114,7 @@ def DumpFile(self, filepath, filetype="json", **kwargs): if filetype == 'eds': log.debug("Writing EDS '%s'" % filepath) - eds_utils.GenerateEDSFile(filepath, self) + eds_utils.generate_eds_file(filepath, self) return if filetype == 'json': @@ -446,19 +126,19 @@ def DumpFile(self, filepath, filetype="json", **kwargs): if filetype == 'c': log.debug("Writing C files '%s'" % filepath) - gen_cfile.GenerateFile(filepath, self) + gen_cfile.generate_file(filepath, self) return raise ValueError("Unknown file suffix, unable to write file") def DumpJson(self, compact=False, sort=False, internal=False, validate=True): """ Dump the node into a JSON string """ - return jsonod.GenerateJson( + return jsonod.generate_json( self, compact=compact, sort=sort, internal=internal, validate=validate ) # -------------------------------------------------------------------------- - # Node Functions + # Node Functions # -------------------------------------------------------------------------- def GetMappings(self, userdefinedtoo=True): @@ -835,24 +515,24 @@ def CompileValue(self, value, index, compute=True): return value # -------------------------------------------------------------------------- - # Node Informations Functions + # Node Informations Functions # -------------------------------------------------------------------------- def GetBaseIndex(self, index): """ Return the index number of the base object """ for mapping in self.GetMappings(): - result = Find.Index(index, mapping) + result = self.FindIndex(index, mapping) if result: return result - return Find.Index(index, maps.MAPPING_DICTIONARY) + return self.FindIndex(index, maps.MAPPING_DICTIONARY) def GetBaseIndexNumber(self, index): """ Return the index number from the base object """ for mapping in self.GetMappings(): - result = Find.Index(index, mapping) + result = self.FindIndex(index, mapping) if result is not None: return (index - result) // mapping[result].get("incr", 1) - result = Find.Index(index, maps.MAPPING_DICTIONARY) + result = self.FindIndex(index, maps.MAPPING_DICTIONARY) if result is not None: return (index - result) // maps.MAPPING_DICTIONARY[result].get("incr", 1) return 0 @@ -867,10 +547,10 @@ def GetEntryName(self, index, compute=True): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.EntryName(index, mappings[i], compute) + result = self.FindEntryName(index, mappings[i], compute) i += 1 if result is None: - result = Find.EntryName(index, maps.MAPPING_DICTIONARY, compute) + result = self.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) return result def GetEntryInfos(self, index, compute=True): @@ -878,9 +558,9 @@ def GetEntryInfos(self, index, compute=True): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.EntryInfos(index, mappings[i], compute) + result = self.FindEntryInfos(index, mappings[i], compute) i += 1 - r301 = Find.EntryInfos(index, maps.MAPPING_DICTIONARY, compute) + r301 = self.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -892,11 +572,11 @@ def GetSubentryInfos(self, index, subindex, compute=True): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.SubentryInfos(index, subindex, mappings[i], compute) + result = self.FindSubentryInfos(index, subindex, mappings[i], compute) if result: result["user_defined"] = i == len(mappings) - 1 and index >= 0x1000 i += 1 - r301 = Find.SubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) + r301 = self.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -947,10 +627,10 @@ def GetTypeIndex(self, typename): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.TypeIndex(typename, mappings[i]) + result = self.FindTypeIndex(typename, mappings[i]) i += 1 if result is None: - result = Find.TypeIndex(typename, maps.MAPPING_DICTIONARY) + result = self.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) return result def GetTypeName(self, typeindex): @@ -958,10 +638,10 @@ def GetTypeName(self, typeindex): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.TypeName(typeindex, mappings[i]) + result = self.FindTypeName(typeindex, mappings[i]) i += 1 if result is None: - result = Find.TypeName(typeindex, maps.MAPPING_DICTIONARY) + result = self.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) return result def GetTypeDefaultValue(self, typeindex): @@ -969,23 +649,23 @@ def GetTypeDefaultValue(self, typeindex): mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = Find.TypeDefaultValue(typeindex, mappings[i]) + result = self.FindTypeDefaultValue(typeindex, mappings[i]) i += 1 if result is None: - result = Find.TypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) + result = self.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) return result def GetMapVariableList(self, compute=True): - list_ = list(Find.MapVariableList(maps.MAPPING_DICTIONARY, self, compute)) + list_ = list(self.FindMapVariableList(maps.MAPPING_DICTIONARY, self, compute)) for mapping in self.GetMappings(): - list_.extend(Find.MapVariableList(mapping, self, compute)) + list_.extend(self.FindMapVariableList(mapping, self, compute)) list_.sort() return list_ def GetMandatoryIndexes(self, node=None): # pylint: disable=unused-argument - list_ = Find.MandatoryIndexes(maps.MAPPING_DICTIONARY) + list_ = self.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): - list_.extend(Find.MandatoryIndexes(mapping)) + list_.extend(self.FindMandatoryIndexes(mapping)) return list_ def GetCustomisableTypes(self): @@ -995,7 +675,7 @@ def GetCustomisableTypes(self): } # -------------------------------------------------------------------------- - # Type helper functions + # Type helper functions # -------------------------------------------------------------------------- def IsStringType(self, index): @@ -1017,13 +697,13 @@ def IsRealType(self, index): return False # -------------------------------------------------------------------------- - # Type and Map Variable Lists + # Type and Map Variable Lists # -------------------------------------------------------------------------- def GetTypeList(self): - list_ = Find.TypeList(maps.MAPPING_DICTIONARY) + list_ = self.FindTypeList(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): - list_.extend(Find.TypeList(mapping)) + list_.extend(self.FindTypeList(mapping)) return list_ def GenerateMapName(self, name, index, subindex): # pylint: disable=unused-argument @@ -1123,7 +803,7 @@ def RemoveIndex(self, index): self.ProfileName = "None" # -------------------------------------------------------------------------- - # Validator + # Validator # -------------------------------------------------------------------------- def Validate(self, fix=False): @@ -1193,7 +873,7 @@ def _warn(text): _warn("FIX: Set name to '{}'".format(subvals["name"])) # -------------------------------------------------------------------------- - # Printing and output + # Printing and output # -------------------------------------------------------------------------- def GetPrintLine(self, index, unused=False, compact=False): @@ -1240,7 +920,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve continue # Print the parameter range header - ir = GetIndexRange(k) + ir = self.get_index_range(k) if index_range != ir: index_range = ir if not compact: @@ -1318,6 +998,324 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve if not compact and infos: yield "" + # -------------------------------------------------------------------------- + # Load mapping + # -------------------------------------------------------------------------- + + @staticmethod + def ImportProfile(profilename): + + # 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 + + # -------------------------------------------------------------------------- + # Utils + # -------------------------------------------------------------------------- + + @staticmethod + def string_format(text, idx, sub): # pylint: disable=unused-argument + """ + Format the text given with the index and subindex defined + """ + result = RE_NAME.match(text) + if result: + fmt = result.groups() + try: + args = fmt[1].split(',') + args = [a.replace('idx', str(idx)) for a in args] + args = [a.replace('sub', str(sub)) for a in args] + + # NOTE: Python2 type evaluations are baked into the maps.py + # and json format OD so cannot be removed currently + if len(args) == 1: + return fmt[0] % (Node.evaluate_expression(args[0].strip())) + elif len(args) == 2: + return fmt[0] % (Node.evaluate_expression(args[0].strip()), Node.evaluate_expression(args[1].strip())) + + return fmt[0] + except Exception as exc: + log.debug("PARSING FAILED: %s" % (exc, )) + raise + else: + return text + + @staticmethod + def evaluate_expression(expression: str): + """Parses a string expression and attempts to calculate the result + Supports: + - Addition (i.e. "3+4") + - Subraction (i.e. "7-4") + - Constants (i.e. "5") + This function will handle chained arithmatic i.e. "1+2+3" although operating order is not neccesarily preserved + + Parameters: + expression (str): string to parse + """ + tree = ast.parse(expression, mode="eval") + return Node.evaluate_node(tree.body) + + @staticmethod + def evaluate_node(node: ast.AST): + ''' + Recursively parses ast.Node objects to evaluate arithmatic expressions + ''' + if isinstance(node, ast.BinOp): + if isinstance(node.op, ast.Add): + return Node.evaluate_node(node.left) + Node.evaluate_node(node.right) + elif isinstance(node.op, ast.Sub): + return Node.evaluate_node(node.left) - Node.evaluate_node(node.right) + else: + raise SyntaxError("Unhandled arithmatic operation %s" % type(node.op)) + elif isinstance(node, ast.Constant): + if isinstance(node.value, int | float | complex): + return node.value + else: + raise TypeError("Cannot parse str type constant '%s'" % node.value) + elif isinstance(node, ast.AST): + raise TypeError("Unhandled ast node class %s" % type(node)) + else: + raise TypeError("Invalid argument type %s" % type(node) ) + + @staticmethod + def get_index_range(index): + for irange in maps.INDEX_RANGES: + if irange["min"] <= index <= irange["max"]: + return irange + raise ValueError("Cannot find index range for value '0x%x'" % index) + + @staticmethod + def be_to_le(value): + """ + Convert Big Endian to Little Endian + @param value: value expressed in Big Endian + @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 + 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) + + @staticmethod + 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]) + + # -------------------------------------------------------------------------- + # Search in a Mapping Dictionary + # -------------------------------------------------------------------------- + + @staticmethod + def FindTypeIndex(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) + + @staticmethod + def FindTypeName(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 + + @staticmethod + def FindTypeDefaultValue(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 + + @staticmethod + def FindTypeList(mappingdictionary): + """ + Return the list of types defined in mappingdictionary + """ + return [ + mappingdictionary[index]["name"] + for index in mappingdictionary + if index < 0x1000 + ] + + @staticmethod + def FindEntryName(index, mappingdictionary, compute=True): + """ + Return the name of an entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(index, mappingdictionary) + if base_index: + infos = mappingdictionary[base_index] + if infos["struct"] & OD.IdenticalIndexes and compute: + return Node.string_format(infos["name"], (index - base_index) // infos["incr"] + 1, 0) + return infos["name"] + return None + + @staticmethod + def FindEntryInfos(index, mappingdictionary, compute=True): + """ + Return the informations of one entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(index, mappingdictionary) + if base_index: + obj = mappingdictionary[base_index].copy() + if obj["struct"] & OD.IdenticalIndexes and compute: + obj["name"] = Node.string_format(obj["name"], (index - base_index) // obj["incr"] + 1, 0) + obj.pop("values") + return obj + return None + + @staticmethod + def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): + """ + Return the informations of one subentry of an entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(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"] = Node.string_format(infos["name"], (index - base_index) // incr + 1, subindex) + + return infos + return None + + @staticmethod + def FindMapVariableList(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 = Node.string_format(computed_name, 1, i + 1) + yield (index, i + 1, infos["size"], computed_name) + else: + computed_name = name + if compute: + computed_name = Node.string_format(computed_name, 1, subindex) + yield (index, subindex, infos["size"], computed_name) + + @staticmethod + def FindMandatoryIndexes(mappingdictionary): + """ + Return the list of mandatory indexes defined in mappingdictionary + """ + return [ + index + for index in mappingdictionary + if index >= 0x1000 and mappingdictionary[index]["need"] + ] + + @staticmethod + def FindIndex(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 + # Register node with gnosis nosis.add_class_to_store('Node', Node) diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 1c9d918..7dd7aba 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -104,7 +104,7 @@ def ImportEDSFile(self, edspath): def LoadEDS(self, eds): edspath = os.path.join(self.GetEDSFolder(), eds) - node = eds_utils.GenerateNode(edspath) + node = eds_utils.generate_node(edspath) self.EDSNodes[eds] = node def AddSlaveNode(self, nodename, nodeid, eds): @@ -145,7 +145,7 @@ def LoadSlaveNodes(self, netname=None): cpjpath = os.path.join(self.Root, "nodelist.cpj") if os.path.isfile(cpjpath): try: - networks = eds_utils.ParseCPJFile(cpjpath) + networks = eds_utils.parse_cpj_file(cpjpath) network = None if netname: for net in networks: @@ -167,7 +167,7 @@ def SaveNodeList(self, netname=None): cpjpath = '' # For linting 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: diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index c79aad7..2a718f9 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -42,7 +42,7 @@ # Returns a new id -def GetNewId(): +def get_new_id(): global CURRENTID # pylint: disable=global-statement CURRENTID += 1 return CURRENTID @@ -188,7 +188,7 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt # Load profile given if profile != "None": # Import profile - mapping, menuentries = nodelib.ImportProfile(filepath) + mapping, menuentries = nodelib.Node.ImportProfile(filepath) node.ProfileName = profile node.Profile = mapping node.SpecificMenu = menuentries @@ -212,7 +212,7 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt for option in options: if option == "DS302": # Import profile - mapping, menuentries = nodelib.ImportProfile("DS-302") + mapping, menuentries = nodelib.Node.ImportProfile("DS-302") self.CurrentNode.DS302 = mapping self.CurrentNode.SpecificMenu.extend(menuentries) elif option == "GenSYNC": @@ -247,7 +247,7 @@ def OpenFileInCurrent(self, filepath, load=True): """ Open a file and store it in a new buffer """ - node = Node.LoadFile(filepath) + node = nodelib.Node.LoadFile(filepath) self.CurrentNode = node self.CurrentNode.ID = 0 @@ -761,7 +761,7 @@ def LoadCurrentNext(self): self.CurrentNode = self.UndoBuffers[self.NodeIndex].Next().Copy() def AddNodeBuffer(self, currentstate=None, issaved=False): - self.NodeIndex = GetNewId() + self.NodeIndex = get_new_id() self.UndoBuffers[self.NodeIndex] = UndoBuffer(currentstate, issaved) self.FilePaths[self.NodeIndex] = "" self.FileNames[self.NodeIndex] = "" @@ -1068,11 +1068,14 @@ def AddToDCF(self, node_id, index, subindex, size, value): if self.CurrentNode.IsEntry(0x1F22, node_id): dcf_value = self.CurrentNode.GetEntry(0x1F22, node_id) if dcf_value: - nbparams = nodelib.BE_to_LE(dcf_value[:4]) + nbparams = nodelib.Node.be_to_le(dcf_value[:4]) else: nbparams = 0 - new_value = nodelib.LE_to_BE(nbparams + 1, 4) + dcf_value[4:] - new_value += nodelib.LE_to_BE(index, 2) + nodelib.LE_to_BE(subindex, 1) + nodelib.LE_to_BE(size, 4) + nodelib.LE_to_BE(value, size) + new_value = nodelib.Node.le_to_be(nbparams + 1, 4) + dcf_value[4:] + new_value += (nodelib.Node.le_to_be(index, 2) + + nodelib.Node.le_to_be(subindex, 1) + + nodelib.Node.le_to_be(size, 4) + + nodelib.Node.le_to_be(value, size)) self.CurrentNode.SetEntry(0x1F22, node_id, new_value) # -------------------------------------------------------------------------- @@ -1089,17 +1092,17 @@ def GetCustomisedTypeValues(self, index): def GetEntryName(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryName(index, compute) - return nodelib.Find.EntryName(index, maps.MAPPING_DICTIONARY, compute) + return nodelib.Node.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) def GetEntryInfos(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryInfos(index, compute) - return nodelib.Find.EntryInfos(index, maps.MAPPING_DICTIONARY, compute) + return nodelib.Node.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) def GetSubentryInfos(self, index, subindex, compute=True): if self.CurrentNode: return self.CurrentNode.GetSubentryInfos(index, subindex, compute) - result = nodelib.Find.SubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) + result = nodelib.Node.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if result: result["user_defined"] = False return result @@ -1107,17 +1110,17 @@ def GetSubentryInfos(self, index, subindex, compute=True): def GetTypeIndex(self, typename): if self.CurrentNode: return self.CurrentNode.GetTypeIndex(typename) - return nodelib.Find.TypeIndex(typename, maps.MAPPING_DICTIONARY) + return nodelib.Node.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) def GetTypeName(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeName(typeindex) - return nodelib.Find.TypeName(typeindex, maps.MAPPING_DICTIONARY) + return nodelib.Node.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) def GetTypeDefaultValue(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeDefaultValue(typeindex) - return nodelib.Find.TypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) + return nodelib.Node.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) def GetMapVariableList(self, compute=True): if self.CurrentNode: @@ -1127,7 +1130,7 @@ def GetMapVariableList(self, compute=True): def GetMandatoryIndexes(self): if self.CurrentNode: return self.CurrentNode.GetMandatoryIndexes() - return nodelib.Find.MandatoryIndexes(maps.MAPPING_DICTIONARY) + return nodelib.Node.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) def GetCustomisableTypes(self): return { diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index 33a8245..a97c122 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -294,7 +294,7 @@ def append(self, item): self.iohandle.write(item) def getvalue(self): - "Returns memory stream as a single string, or None for file objs" + """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 diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index e9d71cc..25d8e81 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -25,7 +25,7 @@ import objdictgen from objdictgen.maps import OD -from objdictgen.node import BE_to_LE, LE_to_BE +from objdictgen.node import Node from objdictgen.ui.exception import display_error_dialog, display_exception_dialog log = logging.getLogger('objdictgen') @@ -1567,13 +1567,13 @@ def SetValues(self, values): if values: data = values[4:] current = 0 - for _ in range(BE_to_LE(values[:4])): + for _ in range(Node.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"] = Node.be_to_le(data[current:current + 2]) + value["Subindex"] = Node.be_to_le(data[current + 2:current + 3]) + size = Node.be_to_le(data[current + 3:current + 7]) value["Size"] = size - value["Value"] = BE_to_LE(data[current + 7:current + 7 + size]) + value["Value"] = Node.be_to_le(data[current + 7:current + 7 + size]) current += 7 + size self.Values.append(value) self.RefreshValues() @@ -1581,12 +1581,12 @@ def SetValues(self, values): def GetValues(self): if len(self.Values) <= 0: return "" - value = LE_to_BE(len(self.Values), 4) + value = Node.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 += Node.le_to_be(row["Index"], 2) + value += Node.le_to_be(row["Subindex"], 1) + value += Node.le_to_be(row["Size"], 4) + value += Node.le_to_be(row["Value"], row["Size"]) return value def RefreshValues(self): diff --git a/tests/conftest.py b/tests/conftest.py index d6fba68..d25ed75 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ import os import glob import difflib +from dataclasses import dataclass import pytest -import attr import objdictgen import objdictgen.node @@ -14,25 +14,51 @@ ODDIR = os.path.join(HERE, 'od') # 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'))) +_ODFILES = list(glob.glob(os.path.join(ODDIR, 'legacy-compare', '*.od'))) +_ODFILES.extend(glob.glob(os.path.join(ODDIR, 'extra-compare', '*.od'))) -@attr.s -class ODFile(object): - filename = attr.ib() +@dataclass +class ODFile: + """ Class representing an OD file """ + filename: str + 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) -ODFILES = [ODFile(os.path.abspath(x.replace('.od', ''))) for x in ODFILES] + +class Fn: + """ Helper class for testing functions """ + + @staticmethod + def diff(a, b, predicate=None, **kw): + """ Diff two files """ + 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 + + +# List of all OD files +ODFILES = [ODFile(os.path.abspath(x.replace('.od', ''))) for x in _ODFILES] def pytest_generate_tests(metafunc): ''' Special fixture generators ''' @@ -42,28 +68,29 @@ def pytest_generate_tests(metafunc): ], indirect=True) +# +# FIXTURES +# ======================================== +# + @pytest.fixture def oddir(): """ Fixture returning the path for the od test directory """ return os.path.abspath(os.path.join(ODDIR)) - @pytest.fixture def basepath(): """ Fixture returning the base of the project """ return os.path.abspath(os.path.join(HERE, '..')) - @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)) - @pytest.fixture def profile(monkeypatch): """ Fixture that monkeypatches the profile load directory to include the OD directory @@ -75,33 +102,12 @@ def profile(monkeypatch): monkeypatch.setattr(objdictgen, 'PROFILE_DIRECTORIES', newdirs) yield None - @pytest.fixture def odfile(request, profile): """ Fixture for each of the od files in the test directory """ yield 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 - - -class Fn: - @staticmethod - def diff(*a, **kw): - return diff(*a, **kw) - - @pytest.fixture def fn(): - return Fn + """ Fixture providing a helper class for testing functions """ + return Fn() diff --git a/tests/test_node.py b/tests/test_node.py index 2135924..a8ffaa0 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,47 +1,47 @@ import pytest -from objdictgen.node import StringFormat, EvaluateExpression +from objdictgen.node import Node 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 Node.string_format('Additional Server SDO %d Parameter[(idx)]', 5, 0) == 'Additional Server SDO 5 Parameter' + assert Node.string_format('Restore Manufacturer Defined Default Parameters %d[(sub - 3)]', 1, 5) == 'Restore Manufacturer Defined Default Parameters 2' + assert Node.string_format('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' + assert Node.string_format('%s %.3f[(idx,sub)]', 1, 2) == '1 2.000' + assert Node.string_format('%s %.3f[( idx , sub )]', 1, 2) == '1 2.000' with pytest.raises(TypeError): - StringFormat('What are these %s[("tests")]', 0, 1) + Node.string_format('What are these %s[("tests")]', 0, 1) with pytest.raises(TypeError): - StringFormat('There is nothing to format[(idx, sub)]', 1, 2) + Node.string_format('There is nothing to format[(idx, sub)]', 1, 2) with pytest.raises(Exception): - StringFormat('Unhandled arithmatic[(idx*sub)]', 2, 4) + Node.string_format('Unhandled arithmatic[(idx*sub)]', 2, 4) def test_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 + assert Node.evaluate_expression('4+3') == 7 + assert Node.evaluate_expression('4-3') == 1 + assert Node.evaluate_expression('11') == 11 + assert Node.evaluate_expression('4+3+2') == 9 + assert Node.evaluate_expression('4+3-2') == 5 with pytest.raises(TypeError): - EvaluateExpression('3-"tests"') + Node.evaluate_expression('3-"tests"') with pytest.raises(TypeError): - EvaluateExpression('3-"tests"') + Node.evaluate_expression('3-"tests"') with pytest.raises(TypeError): - EvaluateExpression('not 5') + Node.evaluate_expression('not 5') with pytest.raises(TypeError): - EvaluateExpression('str') + Node.evaluate_expression('str') with pytest.raises(TypeError): - EvaluateExpression('"str"') + Node.evaluate_expression('"str"') with pytest.raises(SyntaxError): - EvaluateExpression('$NODEID+12') \ No newline at end of file + Node.evaluate_expression('$NODEID+12') \ No newline at end of file From fa7b950f94ba1b5eef679ff6a1a964a886448475 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 12 Feb 2024 23:51:41 +0100 Subject: [PATCH 04/28] Comment cleanup and negible UI code fixes --- src/objdictgen/__main__.py | 42 +++++++++++----------- src/objdictgen/jsonod.py | 28 +++++++-------- src/objdictgen/node.py | 10 +++--- src/objdictgen/ui/commondialogs.py | 21 ++++------- src/objdictgen/ui/networkedit.py | 11 +++--- src/objdictgen/ui/networkeditortemplate.py | 2 +- src/objdictgen/ui/objdictedit.py | 11 +++--- src/objdictgen/ui/subindextable.py | 8 +++-- 8 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index db73f6b..8e31047 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -41,7 +41,7 @@ @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): @@ -51,9 +51,9 @@ def set_debug(self, dbg): def debug_wrapper(): - ''' Wrapper to catch all exceptions and supress the output unless debug + """ Wrapper to catch all exceptions and supress the output unless debug is set - ''' + """ def decorator(fn): @functools.wraps(fn) def inner(*args, **kw): @@ -70,7 +70,7 @@ def inner(*args, **kw): def open_od(fname, validate=True, fix=False): - ''' Open and validate the OD file''' + """ Open and validate the OD file""" try: od = objdictgen.LoadFile(fname) @@ -117,7 +117,7 @@ def _printlines(entries): @debug_wrapper() def main(debugopts, args=None): - ''' Main command dispatcher ''' + """ Main command dispatcher """ parser = argparse.ArgumentParser( prog=objdictgen.ODG_PROGRAM, @@ -130,9 +130,9 @@ 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 -- @@ -143,15 +143,15 @@ def main(debugopts, args=None): parser.add_argument('-D', '--debug', **opt_debug) # -- 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) # -- CONVERT -- - subp = subparser.add_parser('convert', help=''' + subp = subparser.add_parser('convert', help=""" Generate - ''', aliases=['gen', 'conv']) + """, aliases=['gen', 'conv']) subp.add_argument('od', **opt_od) 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.") @@ -166,9 +166,9 @@ def main(debugopts, args=None): subp.add_argument('-D', '--debug', **opt_debug) # -- DIFF -- - subp = subparser.add_parser('diff', help=''' + subp = subparser.add_parser('diff', help=""" Compare OD files - ''', aliases=['compare']) + """, aliases=['compare']) subp.add_argument('od1', **opt_od) subp.add_argument('od2', **opt_od) subp.add_argument('--internal', action="store_true", help="Diff internal object") @@ -177,16 +177,16 @@ def main(debugopts, args=None): subp.add_argument('-D', '--debug', **opt_debug) # -- 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) # -- 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") @@ -199,16 +199,16 @@ def main(debugopts, args=None): subp.add_argument('-D', '--debug', **opt_debug) # -- 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) # -- 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) diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index fbc0c00..8cc24f1 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -37,7 +37,7 @@ class ValidationError(Exception): - ''' Validation failure ''' + """ Validation failure """ # JSON Version history/formats @@ -154,7 +154,7 @@ class ValidationError(Exception): def remove_jasonc(text): - ''' Remove jsonc annotations ''' + """ Remove jsonc annotations """ # Copied from https://github.com/NickolaiBeloguzov/jsonc-parser/blob/master/jsonc_parser/parser.py#L11-L39 def __re_sub(match): if match.group(2) is not None: @@ -224,12 +224,12 @@ def remove_underscore(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 + """ 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 @@ -258,7 +258,7 @@ def member_compare(a, must=None, optional=None, not_want=None, msg='', only_if=N def get_object_types(node=None, dictionary=None): - ''' Return two dicts with the object type mapping ''' + """ Return two dicts with the object type mapping """ groups = [maps.MAPPING_DICTIONARY] if node: @@ -317,7 +317,7 @@ def compare_profile(profilename, params, menu=None): def generate_json(node, compact=False, sort=False, internal=False, validate=True): - ''' Export a JSON string representation of the node ''' + """ Export a JSON string representation of the node """ # Get the dict representation jd, objtypes_s2i = node_todict( @@ -368,7 +368,7 @@ def _index_repl(m): def generate_node(contents): - ''' Import from JSON string or objects ''' + """ Import from JSON string or objects """ jd = contents if isinstance(contents, str): @@ -399,7 +399,7 @@ def generate_node(contents): def node_todict(node, sort=False, rich=True, internal=False, validate=True): - ''' + """ Convert a node to dict representation for serialization. sort: Set if the output dictionary should be sorted before output. @@ -414,7 +414,7 @@ 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() @@ -555,9 +555,9 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): def node_todict_parameter(obj, node, index): - ''' Modify obj from internal dict representation to generic dict structure + """ Modify obj from internal dict representation to generic dict structure which is suitable for serialization into JSON. - ''' + """ # Observations: # ============= @@ -835,7 +835,7 @@ def validate_nodeindex(node, index, obj): def node_fromdict(jd, internal=False): - ''' Convert a dict jd into a Node ''' + """ Convert a dict jd into a Node """ # Remove all underscore keys from the file jd = remove_underscore(jd) @@ -1021,9 +1021,9 @@ def node_fromdict_parameter(obj, objtypes_s2i): def validate_fromdict(jsonobj, objtypes_i2s=None, objtypes_s2i=None): - ''' Validate that jsonobj is a properly formatted dictionary that may + """ Validate that jsonobj is a properly formatted dictionary that may be imported to the internal OD-format - ''' + """ jd = jsonobj diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 8eb25ff..dcd542f 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -468,7 +468,7 @@ def GetDict(self): return copy.deepcopy(self.__dict__) def GetIndexDict(self, index): - ''' Return a dict representation of the index ''' + """ Return a dict representation of the index """ obj = {} if index in self.Dictionary: obj['dictionary'] = self.Dictionary[index] @@ -807,9 +807,9 @@ def RemoveIndex(self, index): # -------------------------------------------------------------------------- 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): name = self.GetEntryName(index) log.warning("WARNING: 0x{0:04x} ({0}) '{1}': {2}".format(index, name, text)) @@ -1083,9 +1083,9 @@ def evaluate_expression(expression: str): @staticmethod def evaluate_node(node: ast.AST): - ''' + """ Recursively parses ast.Node objects to evaluate arithmatic expressions - ''' + """ if isinstance(node, ast.BinOp): if isinstance(node.op, ast.Add): return Node.evaluate_node(node.left) + Node.evaluate_node(node.right) diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 25d8e81..4b054b8 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -41,7 +41,7 @@ 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): @@ -233,7 +233,7 @@ 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): @@ -424,7 +424,7 @@ 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): @@ -651,7 +651,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"] @@ -810,7 +810,7 @@ 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): @@ -1112,7 +1112,7 @@ 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): @@ -1407,19 +1407,12 @@ 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): # 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) diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 97c8673..4d80f01 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -41,22 +41,25 @@ def usage(): [ 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): diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 3db4b8c..3d651ab 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -26,7 +26,7 @@ [ ID_NETWORKEDITNETWORKNODES, -] = [wx.NewId() for _init_ctrls in range(1)] +] = [wx.NewId() for _ in range(1)] class NetworkEditorTemplate(net.NodeEditorTemplate): diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 32e291a..6b5b4b7 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -42,23 +42,26 @@ def usage(): [ 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): diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 03a7b94..782ecf3 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -292,17 +292,19 @@ 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): From 5fa31d2c8ff3b15d99ce566e289c101bdd2a43f0 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Fri, 16 Feb 2024 08:16:45 +0100 Subject: [PATCH 05/28] Convert strings to f-strings --- src/objdictgen/__main__.py | 43 +++--- src/objdictgen/eds_utils.py | 157 +++++++++++---------- src/objdictgen/gen_cfile.py | 109 ++++++-------- src/objdictgen/jsonod.py | 75 +++++----- src/objdictgen/node.py | 91 ++++++------ src/objdictgen/nodelist.py | 20 +-- src/objdictgen/nodemanager.py | 28 ++-- src/objdictgen/nosis.py | 85 ++++++----- src/objdictgen/ui/commondialogs.py | 52 +++---- src/objdictgen/ui/networkedit.py | 6 +- src/objdictgen/ui/networkeditortemplate.py | 2 +- src/objdictgen/ui/nodeeditortemplate.py | 12 +- src/objdictgen/ui/objdictedit.py | 16 +-- src/objdictgen/ui/subindextable.py | 18 +-- tests/od/generate_json.py | 2 +- 15 files changed, 353 insertions(+), 363 deletions(-) diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 8e31047..dbe7ba9 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -63,7 +63,7 @@ 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 @@ -80,7 +80,7 @@ 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 @@ -93,25 +93,25 @@ def _pprint(text): def _printlines(entries): 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]) @@ -231,7 +231,7 @@ def main(debugopts, args=None): for subparsers_action in (a for a in parser._actions if isinstance(a, argparse._SubParsersAction)): for choice, subparser in subparsers_action.choices.items(): - print("command '{}'".format(choice)) + print(f"command '{choice}'") for info in subparser.format_help().split('\n'): print(" " + info) @@ -287,10 +287,10 @@ def main(debugopts, args=None): 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) @@ -323,7 +323,7 @@ def main(debugopts, args=None): keys = tuple(k for k in keys if k in index) missing = ", ".join((str(k) for k in index if k not in keys)) if missing: - parser.error("Unknown index {}".format(missing)) + parser.error(f"Unknown index {missing}") profiles = [] if od.DS302: @@ -340,19 +340,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: @@ -383,7 +384,7 @@ def main(debugopts, args=None): else: - parser.error("Programming error: Uknown option '%s'" % (opts.command)) + parser.error(f"Programming error: Uknown option '{opts.command}'") def main_objdictgen(): @@ -391,7 +392,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"]) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 945764d..6c667a9 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -173,7 +173,7 @@ def parse_cpj_file(filepath): 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}' 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"): @@ -192,59 +192,59 @@ def parse_cpj_file(filepath): 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") nodeid = int(nodepresent_result.groups()[0]) 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") nodeid = int(nodename_result.groups()[0]) 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)) + raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") nodeid = int(nodedcfname_result.groups()[0]) 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 @@ -278,7 +278,7 @@ def parse_eds_file(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 @@ -291,7 +291,7 @@ def parse_eds_file(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 +304,14 @@ def parse_eds_file(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 @@ -331,15 +331,15 @@ def parse_eds_file(filepath): 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}' 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}' 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 +358,16 @@ def parse_eds_file(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: @@ -384,18 +384,20 @@ def parse_eds_file(filepath): 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 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 for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry") verify_value(values, section_name, "ParameterValue") verify_value(values, section_name, "DefaultValue") @@ -416,7 +418,7 @@ def verify_value(values, section_name, param): elif not isinstance(values[uparam], int) and "$NODEID" not in values[uparam].upper(): raise ValueError() # FIXME: Should this get something more specific? 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 @@ -440,36 +442,36 @@ def generate_eds_content(node, filepath): # Generate FileInfo section fileContent = "[FileInfo]\n" - fileContent += "FileName=%s\n" % os.path.split(filepath)[-1] + fileContent += f"FileName={os.path.split(filepath)[-1]}\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 +482,16 @@ def generate_eds_content(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" @@ -526,24 +528,24 @@ def generate_eds_content(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[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: @@ -558,20 +560,20 @@ def generate_eds_content(node, filepath): 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 += f"\n[{entry:X}sub{subentry:X}]\n" + subtext += f"ParameterName={subentry_infos['name']}\n" subtext += "ObjectType=0x7\n" - subtext += "DataType=0x%4.4X\n" % subentry_infos["type"] - subtext += "AccessType=%s\n" % subentry_infos["access"] + subtext += f"DataType=0x{subentry_infos['type']:04X}\n" + subtext += f"AccessType={subentry_infos['access']}\n" if subentry_infos["type"] == 1: - subtext += "DefaultValue=%s\n" % BOOL_TRANSLATE[value] + subtext += f"DefaultValue={BOOL_TRANSLATE[value]}\n" else: - subtext += "DefaultValue=%s\n" % value - subtext += "PDOMapping=%s\n" % BOOL_TRANSLATE[subentry_infos["pdo"]] + 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 @@ -596,27 +598,27 @@ def generate_eds_content(node, filepath): # Generate Definition of mandatory objects fileContent += "\n[MandatoryObjects]\n" - fileContent += "SupportedObjects=%d\n" % len(mandatories) + fileContent += f"SupportedObjects={len(mandatories)}\n" for idx, entry in enumerate(mandatories): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) + fileContent += f"{idx + 1}=0x{entry:04X}\n" # 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) + fileContent += f"SupportedObjects={len(optionals)}\n" for idx, entry in enumerate(optionals): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) + fileContent += f"{idx + 1}=0x{entry:04X}\n" # 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) + fileContent += f"SupportedObjects={len(manufacturers)}\n" for idx, entry in enumerate(manufacturers): - fileContent += "%d=0x%4.4X\n" % (idx + 1, entry) + fileContent += f"{idx + 1}=0x{entry:04X}\n" # Write manufacturer entries for entry in manufacturers: fileContent += indexcontents[entry] @@ -638,13 +640,13 @@ def generate_cpj_content(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 @@ -659,11 +661,12 @@ def generate_node(filepath, nodeid=0): eds_dict = parse_eds_file(filepath) # 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 profilenb = eds_dict[0x1000].get("DEFAULTVALUE", 0) & 0x0000ffff @@ -672,7 +675,7 @@ def generate_node(filepath, nodeid=0): # Compile Profile name and path to .prf file try: # Import profile - profilename = "DS-%d" % profilenb + profilename = f"DS-{profilenb}" mapping, menuentries = nodelib.Node.ImportProfile(profilename) node.ProfileName = profilename node.Profile = mapping @@ -697,7 +700,7 @@ def generate_node(filepath, nodeid=0): 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) + raise ValueError(f"Domain entry 0x{entry:04X} DataType must be 0xF(DOMAIN) if defined") # Add mapping for entry node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.VAR) # Add mapping for first subindex @@ -743,7 +746,7 @@ def generate_node(filepath, nodeid=0): # 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) + # raise ValueError(f"Error on entry 0x{entry:04X}: Subindex 0 must be defined for a RECORD entry") # # Add mapping for entry # node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.ARRAY) # # Add mapping for first subindex @@ -763,7 +766,7 @@ def generate_node(filepath, nodeid=0): # "nbmax": 0xFE, # }) # else: - # raise ValueError("Error on entry 0x%4.4X: A RECORD entry must have at least 2 subindexes" % entry) + # raise ValueError(f"Error on entry 0x{entry:04X}: A RECORD entry must have at least 2 subindexes") # Define entry for the new node @@ -797,5 +800,5 @@ def generate_node(filepath, nodeid=0): value = get_default_value(node, entry, subindex) node.AddEntry(entry, subindex, value) else: - raise ValueError("Array or Record entry 0x%4.4X must have a 'SubNumber' attribute" % entry) + 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 7bb27dc..5d14f86 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -58,11 +58,11 @@ def get_valid_type_infos(context, typename, items=None): 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) + typeinfos = (f"UNS{values[1]}", None, f"uint{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) + typeinfos = (f"INTEGER{values[1]}", None, f"int{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) + typeinfos = (f"{values[0]}{values[1]}", None, f"real{values[1]}", False) elif values[0] in ["VISIBLE_STRING", "OCTET_STRING"]: size = context.default_string_size for item in items: @@ -79,33 +79,34 @@ def get_valid_type_infos(context, typename, items=None): typeinfos = ("UNS8", None, "boolean", False) else: # FIXME: The !!! is for special UI handling - raise ValueError("!!! '%s' isn't a valid type for CanFestival." % typename) + raise ValueError(f"!!! '{typename}' isn't a valid type for CanFestival.") 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) + raise ValueError(f"!!! '{typename}' isn't a valid type for CanFestival.") return typeinfos def compute_value(type_, value): if type_ == "visible_string": - return '"%s"' % value, "" + return f'"{value}"', "" if type_ == "domain": - return '"%s"' % ''.join(["\\x%2.2x" % ord(char) for char in value]), "" + tp = ''.join([f"\\x{ord(char):02x}" for char in value]) + return f'"{tp}"', "" if type_.startswith("real"): - return "%f" % value, "" + return str(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) + return f"-0x{-value:X}", f"\t/* {value} */" + return f"0x{value:X}", f"\t/* {value} */" def get_type_name(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) + raise ValueError(f"!!! Datatype with value '0x{typenumber:04X}' isn't defined in CanFestival.") return typename @@ -158,16 +159,16 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): typeindex = node.GetEntry(index, 1) typename = node.GetTypeName(typeindex) typeinfos = get_valid_type_infos(context, typename) - context.internal_types[rangename] = (typeinfos[0], typeinfos[1], "valueRange_%d" % num) + context.internal_types[rangename] = (typeinfos[0], typeinfos[1], f"valueRange_{num}") 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) + strDefine += f"\n#define valueRange_{num} 0x{index:02X} /* Type {typeinfos[0]}, {minvalue} < value < {maxvalue} */" + strSwitch += f" case valueRange_{num}:\n" if typeinfos[3] 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[0]}*)value < ({typeinfos[0]}){minvalue}) return OD_VALUE_TOO_LOW;\n" + strSwitch += f" if (*({typeinfos[0]}*)value > ({typeinfos[0]}){maxvalue}) return OD_VALUE_TOO_HIGH;\n" strSwitch += " break;\n" valueRangeContent += strDefine @@ -204,13 +205,13 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): typeinfos = get_valid_type_infos(context, 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) + raise ValueError(f"Domain variable not initialized, index: 0x{index:04X}, subindex: 0x00") texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: if params_infos["buffer_size"]: - texts["suffixe"] = "[%s]" % params_infos["buffer_size"] + texts["suffixe"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = "[%d]" % typeinfos[1] + texts["suffixe"] = f"[{typeinfos[1]}]" else: texts["suffixe"] = "" texts["value"], texts["comment"] = compute_value(typeinfos[2], values) @@ -239,7 +240,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): typeinfos = get_valid_type_infos(context, typename, values[1:]) texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: - texts["suffixe"] = "[%d]" % typeinfos[1] + texts["suffixe"] = f"[{typeinfos[1]}]" texts["type_suffixe"] = "*" else: texts["suffixe"] = "" @@ -257,8 +258,8 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): sep = "" value, comment = compute_value(typeinfos[2], value) 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(f"Domain variable not initialized, 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 @@ -268,7 +269,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): if subindex == len(values) - 1: sep = "" value, comment = compute_value(typeinfos[2], value) - strindex += " %s%s%s\n" % (value, sep, comment) + strindex += f" {value}{sep}{comment}\n" strindex += " };\n" else: @@ -284,9 +285,9 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: if params_infos["buffer_size"]: - texts["suffixe"] = "[%s]" % params_infos["buffer_size"] + texts["suffixe"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = "[%d]" % typeinfos[1] + texts["suffixe"] = f"[{typeinfos[1]}]" else: texts["suffixe"] = "" texts["value"], texts["comment"] = compute_value(typeinfos[2], value) @@ -297,13 +298,8 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): 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") + f"\n#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_Idx {texts['index']:#04x}\n" + ) # Generating Dictionary C++ entry strindex += " subindex %(NodeName)s_Index%(index)04X[] = \n {\n" % texts @@ -328,17 +324,17 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): elif index in variablelist: name = format_name(subentry_infos["name"]) else: - name = format_name("%s_obj%04X" % (texts["NodeName"], texts["index"])) + name = format_name(f"{texts['NodeName']}_obj{texts['index']:04X}") elif entry_infos["struct"] & OD.IdenticalSubindexes: if index in variablelist: - name = "%s[%d]" % (format_name(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"{texts['NodeName']}_obj{texts['index']:04X}[{subindex - 1}]" else: if index in variablelist: - name = format_name("%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"], format_name(subentry_infos["name"])) + name = f"{texts['NodeName']}_obj{texts['index']:%04X}_{format_name(subentry_infos['name'])}" if typeinfos[2] == "visible_string": if params_infos["buffer_size"]: sizeof = params_infos["buffer_size"] @@ -347,27 +343,22 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): elif typeinfos[2] == "domain": sizeof = str(len(values[subindex])) else: - sizeof = "sizeof (%s)" % typeinfos[0] + sizeof = f"sizeof ({typeinfos[0]})" params = node.GetParamsEntry(index, subindex) 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" {{ {subentry_infos['access'].upper()}{save}, {typeinfos[2]}, {sizeof}, (void*)&{start_digit}, NULL }}{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[0]}* {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"))) + f"#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_{RE_NOTW.sub('_', subentry_infos['name'])}_sIdx {subindex:#04x}" + ) if params_infos["comment"]: headerObjectDefinitionContent += " /* " + params_infos["comment"] + " */\n" else: @@ -376,14 +367,8 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): 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") + f"#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_{RE_NOTW.sub('_', subentry_infos['name'])}_sIdx {subindex:#04x}\n" + ) headerObjectDefinitionContent += "/* subindex define not generated for array objects */\n" strindex += " };\n" indexContents[index] = strindex @@ -477,7 +462,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): 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) + 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 @@ -487,21 +472,21 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): maxPDOtransmit += 1 texts["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 {texts['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 # ------------------------------------------------------------------------------ - fileContent = FILE_HEADER + """ -#include "%s" -""" % (headerfilepath) + fileContent = FILE_HEADER + f""" +#include "{headerfilepath}" +""" fileContent += """ /**************************************************************************/ diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 8cc24f1..c609232 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -241,20 +241,20 @@ 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): @@ -288,9 +288,9 @@ def get_object_types(node=None, dictionary=None): 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 @@ -309,7 +309,7 @@ def compare_profile(profilename, params, menu=None): return True, identical except ValueError as exc: - log.debug("Loading profile failed: {}".format(exc)) + log.debug("Loading profile failed: %s", exc) # FIXME: Is this an error? # Test case test-profile.od -> test-profile.json without access to profile log.warning("WARNING: %s", exc) @@ -349,7 +349,7 @@ def _index_repl(m): 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_]+)",?$', @@ -455,7 +455,7 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): obj = node.GetIndexDict(index) # Add in the index (as dictionary is a list) - obj["index"] = "0x{:04X}".format(index) if rich else index + obj["index"] = f"0x{index:04X}" if rich else index # Don't wrangle further if the internal format is wanted if internal: @@ -527,7 +527,7 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): obj["each"] = copy_in_order(obj["each"], JSON_SUB_ORDER) 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: @@ -622,7 +622,7 @@ def node_todict_parameter(obj, node, index): # Baseobj should have been emptied if baseobj != {}: - raise ValidationError("Mapping data not empty. Programming error?. Contains: {}".format(baseobj)) + raise ValidationError(f"Mapping data not empty. Programming error?. Contains: {baseobj}") # -- STEP 2) -- # Migrate 'params' and 'dictionary' to common 'sub' @@ -677,7 +677,7 @@ def node_todict_parameter(obj, node, index): # 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. Programming error? Contains: {params}") return obj @@ -722,7 +722,8 @@ def validate_nodeindex(node, index, obj): # 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 = {} @@ -767,7 +768,7 @@ def validate_nodeindex(node, index, obj): # 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 @@ -789,7 +790,7 @@ 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)} @@ -826,12 +827,12 @@ 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): @@ -878,7 +879,7 @@ def node_fromdict(jd, internal=False): obj = node_fromdict_parameter(obj, objtypes_s2i) 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 @@ -899,10 +900,10 @@ def node_fromdict(jd, internal=False): diff = deepdiff.DeepDiff(baseobj, obj['built-in'], 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)) + log.debug(' %s', line) + raise ValidationError(f"Built-in parameter index 0x{index:04x} ({index}) does not match against system parameters") # 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 @@ -1047,13 +1048,11 @@ 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: @@ -1144,9 +1143,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): @@ -1182,21 +1181,21 @@ def _validate_dictionary(index, obj): # 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: @@ -1223,7 +1222,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 +1246,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 +1255,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 +1277,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,15 +1285,15 @@ 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)) + sindex = obj.get('index', f'item {num}') index = str_to_number(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 diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index dcd542f..90bae21 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -86,15 +86,15 @@ def isEds(filepath): def LoadFile(filepath: str) -> "Node": """ Open a file and create a new node """ if Node.isXml(filepath): - log.debug("Loading XML OD '%s'" % filepath) + log.debug("Loading XML OD '%s'", filepath) with open(filepath, "r") as f: return nosis.xmlload(f) # type: ignore if Node.isEds(filepath): - log.debug("Loading EDS '%s'" % filepath) + log.debug("Loading EDS '%s'", filepath) return eds_utils.generate_node(filepath) - log.debug("Loading JSON OD '%s'" % filepath) + log.debug("Loading JSON OD '%s'", filepath) with open(filepath, "r") as f: return Node.LoadJson(f.read()) @@ -106,26 +106,26 @@ def LoadJson(contents): def DumpFile(self, filepath, filetype="json", **kwargs): """ Save node into file """ if filetype == 'od': - log.debug("Writing XML OD '%s'" % filepath) + log.debug("Writing XML OD '%s'", filepath) with open(filepath, "w") 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) + log.debug("Writing EDS '%s'", filepath) eds_utils.generate_eds_file(filepath, self) 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: f.write(jdata) return if filetype == 'c': - log.debug("Writing C files '%s'" % filepath) + log.debug("Writing C files '%s'", filepath) gen_cfile.generate_file(filepath, self) return @@ -251,7 +251,7 @@ def GetEntry(self, index, subindex=None, compute=True, aslist=False): 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") if subindex is None: if isinstance(self.Dictionary[index], list): return [len(self.Dictionary[index])] + [ @@ -269,7 +269,7 @@ def GetEntry(self, index, subindex=None, compute=True, aslist=False): 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)) + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") def GetParamsEntry(self, index, subindex=None, aslist=False): """ @@ -277,7 +277,7 @@ def GetParamsEntry(self, index, subindex=None, aslist=False): 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") if subindex is None: if isinstance(self.Dictionary[index], list): if index in self.ParamsDictionary: @@ -306,7 +306,7 @@ def GetParamsEntry(self, index, subindex=None, aslist=False): 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)) + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") def HasEntryCallbacks(self, index): entry_infos = self.GetEntryInfos(index) @@ -497,11 +497,11 @@ def CompileValue(self, value, index, compute=True): # 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,)) + 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,)) + 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. @@ -509,8 +509,8 @@ def CompileValue(self, value, index, compute=True): # 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 + log.debug("EVAL FAILED: %s", exc) + raise ValueError(f"CompileValue failed for '{value}'") from exc else: return value @@ -707,7 +707,7 @@ def GetTypeList(self): return list_ def GenerateMapName(self, name, index, subindex): # pylint: disable=unused-argument - return "%s (0x%4.4X)" % (name, index) + return f"{name} (0x{index:04X})" def GetMapValue(self, mapname): if mapname == "None": @@ -812,7 +812,7 @@ def Validate(self, fix=False): """ def _warn(text): 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()) @@ -844,14 +844,15 @@ def _warn(text): } 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)}) or too few dictionary values ({dictlen})") if index in self.Dictionary: for idx in excessive_params: del self.ParamsDictionary[index][idx] 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: @@ -867,10 +868,10 @@ def _warn(text): # Test if subindex 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 @@ -893,11 +894,12 @@ def GetPrintLine(self, index, unused=False, compact=False): flags[i] = Fore.RED + ' *MISSING* ' + Style.RESET_ALL # Print formattings + t_flags = ', '.join(flags) 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 '', + 'flags': f" {Fore.CYAN}{t_flags}{Style.RESET_ALL}" if flags else '', 'pre': ' ' if not compact else '', } @@ -951,15 +953,15 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve suffix = '???' if value else '' if pdo: suffix = str(pdo["name"]) - value = "0x{:x} {}".format(value, suffix) + 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 '' 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: @@ -967,7 +969,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve # Print formatting infos.append({ - 'i': "{:02d}".format(i), + 'i': f"{i:02d}", 'access': info['access'], 'pdo': 'P' if info['pdo'] else ' ', 'name': info['name'], @@ -987,8 +989,9 @@ 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"] # noqa: E126, E241 ) # Print each line using the generated format string @@ -1010,7 +1013,7 @@ def ImportProfile(profilename): # The UI use full filenames, while all other uses use profile names profilepath = profilename if not os.path.exists(profilepath): - fname = "%s.prf" % profilename + fname = f"{profilename}.prf" try: profilepath = next( os.path.join(base, fname) @@ -1018,22 +1021,22 @@ def ImportProfile(profilename): 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 + 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") as f: - log.debug("EXECFILE %s" % (profilepath,)) + 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("EXECFILE FAILED: %s", exc) log.debug(traceback.format_exc()) - raise ValueError("Loading profile '%s' failed: %s" % (profilepath, exc)) from exc + raise ValueError(f"Loading profile '{profilepath}' failed: {exc}") from exc # -------------------------------------------------------------------------- # Utils @@ -1052,8 +1055,8 @@ def string_format(text, idx, sub): # pylint: disable=unused-argument args = [a.replace('idx', str(idx)) for a in args] args = [a.replace('sub', str(sub)) for a in args] - # NOTE: Python2 type evaluations are baked into the maps.py - # and json format OD so cannot be removed currently + # NOTE: Legacy Python2 type evaluations are baked into the OD + # and cannot be removed currently if len(args) == 1: return fmt[0] % (Node.evaluate_expression(args[0].strip())) elif len(args) == 2: @@ -1061,7 +1064,7 @@ def string_format(text, idx, sub): # pylint: disable=unused-argument return fmt[0] except Exception as exc: - log.debug("PARSING FAILED: %s" % (exc, )) + log.debug("PARSING FAILED: %s", exc) raise else: return text @@ -1092,23 +1095,23 @@ def evaluate_node(node: ast.AST): elif isinstance(node.op, ast.Sub): return Node.evaluate_node(node.left) - Node.evaluate_node(node.right) else: - raise SyntaxError("Unhandled arithmatic operation %s" % type(node.op)) + raise SyntaxError(f"Unhandled arithmatic operation {type(node.op)}") elif isinstance(node, ast.Constant): if isinstance(node.value, int | float | complex): return node.value else: - raise TypeError("Cannot parse str type constant '%s'" % node.value) + raise TypeError(f"Cannot parse str type constant '{node.value}'") elif isinstance(node, ast.AST): - raise TypeError("Unhandled ast node class %s" % type(node)) + raise TypeError(f"Unhandled ast node class {type(node)}") else: - raise TypeError("Invalid argument type %s" % type(node) ) + raise TypeError(f"Invalid argument type {type(node)}") @staticmethod def get_index_range(index): for irange in maps.INDEX_RANGES: if irange["min"] <= index <= irange["max"]: return irange - raise ValueError("Cannot find index range for value '0x%x'" % index) + raise ValueError(f"Cannot find index range for value '0x{index:x}'") @staticmethod def be_to_le(value): @@ -1126,7 +1129,7 @@ def be_to_le(value): # 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) + return int("".join([f"{ord(char):02X}" for char in reversed(value)]), 16) @staticmethod def le_to_be(value, size): diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 7dd7aba..b96916e 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -58,7 +58,7 @@ def GetSlaveName(self, idx): def GetSlaveNames(self): return [ - "0x%2.2X %s" % (idx, self.SlaveNodes[idx]["Name"]) + f"0x{idx:02X} {self.SlaveNodes[idx]['Name']}" for idx in sorted(self.SlaveNodes) ] @@ -76,7 +76,7 @@ def LoadProject(self, root, netname=None): 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) + # raise ValueError(f"'{self.Root}' folder doesn't contain a 'eds' folder") files = os.listdir(eds_folder) for file in files: @@ -109,20 +109,20 @@ def LoadEDS(self, eds): def AddSlaveNode(self, nodename, nodeid, eds): if eds not in self.EDSNodes: - raise ValueError("'%s' EDS file is not available" % eds) + raise ValueError(f"'{eds}' EDS file is not available") slave = {"Name": nodename, "EDS": eds, "Node": self.EDSNodes[eds]} self.SlaveNodes[nodeid] = slave self.Changed = True def RemoveSlaveNode(self, index): 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): if netname: - masterpath = os.path.join(self.Root, "%s_master.od" % netname) + masterpath = os.path.join(self.Root, f"{netname}_master.od") else: masterpath = os.path.join(self.Root, "master.od") if os.path.isfile(masterpath): @@ -133,13 +133,13 @@ def LoadMasterNode(self, netname=None): def SaveMasterNode(self, netname=None): if netname: - masterpath = os.path.join(self.Root, "%s_master.od" % netname) + masterpath = os.path.join(self.Root, f"{netname}_master.od") else: masterpath = os.path.join(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") @@ -161,7 +161,7 @@ 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 @@ -176,7 +176,7 @@ def SaveNodeList(self, netname=None): 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): nodeindexes = list(sorted(self.SlaveNodes)) @@ -261,7 +261,7 @@ def main(projectdir): print(line) print() for nodeid, node in nodelist.SlaveNodes.items(): - print("SlaveNode name=%s id=0x%2.2X :" % (node["Name"], nodeid)) + print(f"SlaveNode name={node['Name']} id=0x{nodeid:02X} :") for line in node["Node"].GetPrintParams(): print(line) print() diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index 2a718f9..9c60934 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -554,7 +554,7 @@ def AddMapVariableToCurrent(self, index, name, struct, number, node=None): node = self.CurrentNode assert node # For mypy if node.IsEntry(index): - raise ValueError("Index 0x%04X already defined!" % index) + raise ValueError(f"Index 0x{index:04X} already defined!") node.AddMappingEntry(index, name=name, struct=struct) if struct == OD.VAR: values = {"name": name, "type": 0x05, "access": "rw", "pdo": True} @@ -576,7 +576,7 @@ def AddMapVariableToCurrent(self, index, name, struct, number, node=None): 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 @@ -590,7 +590,7 @@ def AddUserTypeToCurrent(self, type_, min_, max_, length): size = self.GetEntryInfos(type_)["size"] default = self.GetTypeDefaultValue(type_) 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, name=f"{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}) @@ -599,7 +599,7 @@ def AddUserTypeToCurrent(self, type_, min_, max_, length): self.CurrentNode.AddEntry(index, 2, min_) self.CurrentNode.AddEntry(index, 3, max_) elif valuetype == 1: - self.CurrentNode.AddMappingEntry(index, name="%s%d" % (name, length), struct=OD.RECORD, size=length * size, default=default) + self.CurrentNode.AddMappingEntry(index, name=f"{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}) @@ -656,7 +656,7 @@ def SetCurrentEntry(self, index, subindex, value, name, editor, node=None): if dic[type_] == 0: # Might fail if number is malformed if value.startswith("$NODEID"): - value = '"%s"' % value + value = f'"{value}"' elif value.startswith("0x"): value = int(value, 16) else: @@ -711,7 +711,7 @@ def SetCurrentUserType(self, index, type_, min_, max_, length): size = self.GetEntryInfos(type_)["size"] default = self.GetTypeDefaultValue(type_) if new_valuetype == 0: - self.CurrentNode.SetMappingEntry(index, name="%s[%d-%d]" % (name, min_, max_), struct=OD.RECORD, size=size, default=default) + self.CurrentNode.SetMappingEntry(index, name=f"{name}[{min_}-{max_}]", 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}) @@ -722,7 +722,7 @@ def SetCurrentUserType(self, index, type_, min_, max_, length): else: self.CurrentNode.SetEntry(index, 3, max_) elif new_valuetype == 1: - self.CurrentNode.SetMappingEntry(index, name="%s%d" % (name, length), struct=OD.RECORD, size=size, default=default) + self.CurrentNode.SetMappingEntry(index, 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) @@ -789,7 +789,7 @@ def GetAllFilenames(self): def GetFilename(self, index): 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 @@ -797,7 +797,7 @@ def SetCurrentFilePath(self, filepath): self.FileNames[self.NodeIndex] = os.path.splitext(os.path.basename(filepath))[0] else: self.LastNewIndex += 1 - self.FileNames[self.NodeIndex] = "Unnamed%d" % self.LastNewIndex + self.FileNames[self.NodeIndex] = f"Unnamed{self.LastNewIndex}" def GetCurrentFilePath(self): if len(self.FilePaths) > 0: @@ -979,7 +979,7 @@ def GetNodeEntryValues(self, node, index): 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: @@ -1035,14 +1035,14 @@ def GetNodeEntryValues(self, node, index): if values[0] == "UNSIGNED": dic["buffer_size"] = "" try: - fmt = "0x%0" + str(int(values[1]) // 4) + "X" + fmt = "0x{:0" + str(int(values[1]) // 4) + "X}" except ValueError as exc: - log.debug("ValueError: '%s': %s" % (values[1], exc)) + log.debug("ValueError: '%s': %s", values[1], exc) raise # FIXME: Originial code swallows exception try: - dic["value"] = fmt % dic["value"] + dic["value"] = fmt.format(dic["value"]) except TypeError as exc: - log.debug("ValueError: '%s': %s" % (dic["value"], 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 editor["value"] = "string" if values[0] == "INTEGER": diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index a97c122..ede2171 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -52,13 +52,13 @@ def getInBody(typename): 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 +pat_flint = f'({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) +pat_complex = f'({pat_flint})?[-+]{pat_flint}[jJ]' re_complex = re.compile(pat_complex + r'$') -pat_complex2 = r'(%s):(%s)' % (pat_flint, pat_flint) +pat_complex2 = f'({pat_flint}):({pat_flint})' re_complex2 = re.compile(pat_complex2 + r'$') @@ -106,25 +106,25 @@ def aton(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 + s = str(num) elif isinstance(num, float): - s = "%.17g" % num + 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): # these are always used as doubles, so it doesn't # matter if the '.' shows up - s = "%.17g+%.17gj" % (num.real, num.imag) + s = f"{num.real:.17g}+{num.imag:.17g}j" else: - raise ValueError("Unknown numeric type: %s" % repr(num)) + raise ValueError(f"Unknown numeric type: {repr(num)}") return s @@ -225,7 +225,7 @@ def get_class_from_name(classname): klass = CLASS_STORE.get(classname, None) if klass: return klass - raise ValueError("Cannot create class '%s'" % classname) + raise ValueError(f"Cannot create class '{classname}'") def obj_from_node(node): @@ -372,19 +372,19 @@ def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): # 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) + extra = f'{famtype}module="{module}" class="{klass_tag}"' # else: - # extra = '%s class="%s"' % (famtype, klass_tag) + # extra = f'{famtype} class="{klass_tag}"' xml_list.append('\n' + '\n') if deepcopy: - xml_list.append('\n' % (extra)) + xml_list.append(f'\n') elif id_ is not None: - xml_list.append('\n' % (extra, id_)) + xml_list.append(f'\n') else: - xml_list.append('\n' % (extra)) + xml_list.append(f'\n') pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy, omit=omit) xml_list.append('\n') @@ -419,7 +419,7 @@ def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): continue list_.append(_attr_tag(key, val, level, deepcopy)) else: - raise ValueError("'%s.__dict__' is not a dict" % (obj)) + raise ValueError(f"'{obj}.__dict__' is not a dict") def unpickle_instance(node): @@ -452,7 +452,7 @@ def unpickle_instance(node): # --- 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) @@ -482,14 +482,15 @@ def _tag_compound(start_tag, family_type, thing, deepcopy, extra=''): """ if deepcopy: # don't need ids in a deepcopied file (looks neater) - start_tag = start_tag + '%s %s>\n' % (family_type, extra) + start_tag = f'{start_tag}{family_type} {extra}>\n' 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}" {extra}>\n' return (start_tag, 1) @@ -525,15 +526,15 @@ def _family_type(family, typename, mtype, mextra): if mtype is None: # family tags are technically only necessary for mutated types. # we can intuit family for builtin types. - return 'type="%s"' % typename + return f'type="{typename}"' if mtype and len(mtype): if mextra: - mextra = 'extra="%s"' % mextra + mextra = f'extra="{mextra}"' else: mextra = '' - return 'family="%s" type="%s" %s' % (family, mtype, mextra) - return 'family="%s" type="%s"' % (family, typename) + return f'family="{family}" type="{mtype}" {mextra}' + return f'family="{family}" type="{typename}"' def _fix_family(family, typename): @@ -566,7 +567,7 @@ def _fix_family(family, typename): return 'uniq' if typename == 'False': return 'uniq' - raise ValueError("family= must be given for unknown type '%s'" % typename) + raise ValueError(f"family= must be given for unknown type '{typename}'") def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): @@ -575,7 +576,8 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): (mtag, thing, in_body, mextra) = (None, orig_thing, getInBody(type(orig_thing)), None) if thing is None: - start_tag = start_tag + "%s />\n" % (_family_type('none', 'None', None, None)) + ft = _family_type('none', 'None', None, None) + start_tag = f"{start_tag}{ft} />\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 @@ -585,38 +587,35 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): else: # must be False typestr = 'False' + ft = _family_type('uniq', typestr, mtag, mextra) if in_body: - start_tag = start_tag + '%s>%s' % ( - _family_type('uniq', typestr, mtag, mextra), '') + start_tag = f"{start_tag}{ft}>" 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}{ft} value="" />\n' close_tag = '' elif isinstance(thing, (int, float, complex)): # thing_str = repr(thing) thing_str = ntoa(thing) + ft = _family_type("atom", "numeric", mtag, mextra) if in_body: # we don't call safe_content() here since numerics won't # 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}{ft}>{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}{ft} value="{thing_str}" />\n' close_tag = '' elif isinstance(thing, str): + ft = _family_type("atom", "string", mtag, mextra) if in_body: - start_tag = start_tag + '%s>%s' % ( - _family_type('atom', 'string', mtag, mextra), safe_content(thing)) + start_tag = f'{start_tag}{ft}>{safe_content(thing)}' 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}{ft} value="{safe_string(thing)}" />\n' close_tag = '' # General notes: # 1. When we make references, set type to referenced object @@ -658,7 +657,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): 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 @@ -734,9 +733,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 @@ -768,7 +767,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 +783,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/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 4b054b8..b56dbb4 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -183,10 +183,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() @@ -351,14 +351,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: @@ -366,7 +366,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 = "" @@ -374,10 +374,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!" - display_error_dialog(self, "Form isn't valid. %s" % text) + text += f", {item} must be integer!" + display_error_dialog(self, f"Form isn't valid. {text}") else: self.EndModal(wx.ID_OK) @@ -551,18 +551,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) 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 = "" @@ -570,13 +570,13 @@ 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: - display_error_dialog(self, "Form isn't valid. %s" % message) + display_error_dialog(self, f"Form isn't valid. {message}") else: self.EndModal(wx.ID_OK) @@ -769,7 +769,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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: display_error_dialog(self, message) @@ -779,7 +779,7 @@ def OnOK(self, event): # pylint: disable=unused-argument def SetValues(self, name, id_, type_, description, defaultstringsize): self.NodeName.SetValue(name) - self.NodeID.SetValue("0x%02X" % id_) + self.NodeID.SetValue(f"0x{id_:02X}") self.Type.SetStringSelection(type_) self.Description.SetValue(description) self.DefaultStringSize.SetValue(defaultstringsize) @@ -1042,7 +1042,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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: display_error_dialog(self, message) @@ -1216,10 +1216,10 @@ 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 - display_error_dialog(self, "Form isn't complete. %s must be filled!" % text) + text += f", {item}" + display_error_dialog(self, f"Form isn't complete. {text} must be filled!") else: try: nodeid = self.SlaveNodeID.GetValue() @@ -1228,7 +1228,7 @@ def OnOK(self, event): # pylint: disable=unused-argument else: nodeid = int(nodeid) except ValueError as exc: - log.debug("ValueError: '%s': %s" % (self.SlaveNodeID.GetValue(), exc)) + 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: @@ -1508,7 +1508,7 @@ def OnValuesGridCellChange(self, event): try: self.Values[row][colname] = int(value, 16) except ValueError: - display_error_dialog(self, "'%s' is not a valid value!" % value) + display_error_dialog(self, f"'{value}' is not a valid value!") wx.CallAfter(self.RefreshValues) event.Skip() @@ -1588,10 +1588,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/networkedit.py b/src/objdictgen/ui/networkedit.py index 4d80f01..0df1ea1 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -35,7 +35,7 @@ def usage(): print("\nUsage of networkedit.py :") - print("\n %s [Projectpath]\n" % sys.argv[0]) + print(f"\n {sys.argv[0]} [Projectpath]\n") [ @@ -229,7 +229,7 @@ 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 @@ -344,7 +344,7 @@ def OnCloseProjectMenu(self, event): # pylint: disable=unused-argument 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") diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 3d651ab..d4bf0e1 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -88,7 +88,7 @@ def RefreshBufferState(self): if self.NodeList is not None: nodeid = self.Manager.GetCurrentNodeID() if nodeid is not None: - nodename = "0x%2.2X %s" % (nodeid, self.Manager.GetCurrentNodeName()) + nodename = f"0x{nodeid:02X} {self.Manager.GetCurrentNodeName()}" else: nodename = self.Manager.GetCurrentNodeName() self.NetworkNodes.SetPageText(0, nodename) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 7d62d34..b2c6cb6 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -81,8 +81,8 @@ def SetStatusBarText(self, selection, manager): 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) + self.Frame.HelpBar.SetStatusText(f"Index: 0x{index:04X}", 0) + self.Frame.HelpBar.SetStatusText(f"Subindex: 0x{subindex:02X}", 1) entryinfos = manager.GetEntryInfos(index) name = entryinfos["name"] category = "Optional" @@ -91,12 +91,12 @@ def SetStatusBarText(self, selection, manager): 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): @@ -115,7 +115,7 @@ 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(): @@ -156,7 +156,7 @@ 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.GetCurrentProfileName()} Profile" dictionary, current = self.Manager.GetCurrentProfileLists() self.EditProfile(title, dictionary, current) diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 6b5b4b7..a93cf01 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -36,7 +36,7 @@ def usage(): print("\nUsage of objdictedit :") - print("\n %s [Filepath, ...]\n" % sys.argv[0]) + print(f"\n {sys.argv[0]} [Filepath, ...]\n") [ @@ -232,7 +232,7 @@ def __init__(self, parent, manager=None, filesopen=None): new_editingpanel.SetIndex(index) self.FileOpened.AddPage(new_editingpanel, "") except Exception as exc: # Need this broad exception? - log.debug("Swallowed Exception: %s" % (exc, )) + log.debug("Swallowed Exception: %s", exc) raise # FIXME: Originial code swallows exception else: for index in self.Manager.GetBufferIndexes(): @@ -303,7 +303,7 @@ def OnCloseFrame(self, event): 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") @@ -446,7 +446,7 @@ def SaveAs(self): if filepath: directory, filename = os.path.split(filepath) else: - directory, filename = os.getcwd(), "%s" % self.Manager.GetCurrentNodeInfos()[0] + 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: @@ -457,7 +457,7 @@ def SaveAs(self): filepath = dialog.GetPath() if not os.path.isdir(os.path.dirname(filepath)): - display_error_dialog(self, "'%s' is not a valid folder!" % 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 @@ -513,7 +513,7 @@ def OnImportEDSMenu(self, event): # pylint: disable=unused-argument except Exception as exc: # pylint: disable=broad-except display_exception_dialog(self) else: - display_error_dialog(self, "'%s' is not a valid file!" % filepath) + display_error_dialog(self, f"'{filepath}' is not a valid file!") dialog.Destroy() def OnExportEDSMenu(self, event): # pylint: disable=unused-argument @@ -521,7 +521,7 @@ def OnExportEDSMenu(self, event): # pylint: disable=unused-argument if dialog.ShowModal() == wx.ID_OK: filepath = dialog.GetPath() if not os.path.isdir(os.path.dirname(filepath)): - display_error_dialog(self, "'%s' is not a valid folder!" % 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 ("", "."): @@ -540,7 +540,7 @@ def OnExportCMenu(self, event): # pylint: disable=unused-argument if dialog.ShowModal() == wx.ID_OK: filepath = dialog.GetPath() if not os.path.isdir(os.path.dirname(filepath)): - display_error_dialog(self, "'%s' is not a valid folder!" % 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 ("", "."): diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 782ecf3..30ef63e 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -236,7 +236,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() @@ -473,7 +473,7 @@ def __init__(self, parent, window, manager, editable=True): self.Index = None 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) @@ -518,10 +518,10 @@ def OnSubindexGridCellLeftClick(self, event): typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) if typeinfos: bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) - var_name = "%s_%04x_%02x" % (self.Manager.GetCurrentNodeName(), index, subindex) + var_name = f"{self.Manager.GetCurrentNodeName()}_{index:04x}_{subindex:02x}" size = typeinfos["size"] data = wx.TextDataObject(str( - ("%s%s.%d.%d" % (SIZE_CONVERSION[size], bus_id, index, subindex), + (f"{SIZE_CONVERSION[size]}{bus_id}.{index}.{subindex}", "location", IEC_TYPE_CONVERSION.get(typeinfos["name"]), var_name, ""))) @@ -541,10 +541,10 @@ def OnSubindexGridCellLeftClick(self, event): typeinfos = self.Manager.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) + 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), + (f"{SIZE_CONVERSION[size]}{bus_id}.{node_id}.{index}.{subindex}", "location", IEC_TYPE_CONVERSION.get(typeinfos["name"]), var_name, ""))) @@ -608,7 +608,7 @@ def RefreshIndexList(self): 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)) + self.IndexList.Append(f"0x{index:04X} {name}") self.ListIndex.append(index) if self.Editable: self.ChoiceIndex = [] @@ -624,7 +624,7 @@ def RefreshIndexList(self): else: for name, index in self.Manager.GetCurrentValidChoices(values["min"], values["max"]): if index: - self.IndexChoice.Append("0x%04X %s" % (index, name)) + self.IndexChoice.Append(f"0x{index:04X} {name}") else: self.IndexChoice.Append(name) self.ChoiceIndex.append(index) @@ -815,7 +815,7 @@ def OnRenameIndexMenu(self, event): # pylint: disable=unused-argument 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, + dialog = wx.TextEntryDialog(self, f"Give a new name for index 0x{index:04X}", "Rename an index", infos["name"], wx.OK | wx.CANCEL) if dialog.ShowModal() == wx.ID_OK: self.Manager.SetCurrentEntryName(index, dialog.GetValue()) diff --git a/tests/od/generate_json.py b/tests/od/generate_json.py index a58e028..d09e171 100644 --- a/tests/od/generate_json.py +++ b/tests/od/generate_json.py @@ -26,7 +26,7 @@ def convert(fname): base = fname.replace('.od', '') - print("Reading %s" % fname) + print(f"Reading {fname}") node = Node.LoadFile(base + '.od') node.Validate(fix=True) From a1ae2b8c2e2f77d6620ddbd274221f9836a03fdf Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Fri, 16 Feb 2024 11:22:07 +0100 Subject: [PATCH 06/28] Change package setup and packaging --- .pylintrc | 587 --------------------- pyproject.toml | 554 ++++++++++++++++++- setup.cfg | 69 +++ setup.py | 209 -------- src/objdictgen/__init__.py | 3 +- src/objdictgen/__main__.py | 2 +- src/objdictgen/{ui => img}/networkedit.ico | Bin src/objdictgen/{ui => img}/networkedit.png | Bin src/objdictgen/jsonod.py | 2 +- src/objdictgen/ui/exception.py | 5 +- src/objdictgen/ui/networkedit.py | 2 +- src/objdictgen/ui/objdictedit.py | 2 +- 12 files changed, 627 insertions(+), 808 deletions(-) delete mode 100644 .pylintrc delete mode 100644 setup.py rename src/objdictgen/{ui => img}/networkedit.ico (100%) rename src/objdictgen/{ui => img}/networkedit.png (100%) 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/pyproject.toml b/pyproject.toml index 57c52aa..57a27fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,556 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel" -] +requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.vulture] ignore_names = ["object", "main_*"] # verbose = 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 = + +# 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 = 200 + +# Maximum number of lines in a module. +max-module-lines = 1000 + +# 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". +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"] + +# 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 = + +[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..aeed3bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,72 @@ +[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.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 + +[options] +package_dir = + = src +packages = find_namespace: +include_package_data = True +python_requires = >=3.6, <4 +install_requires = + jsonschema + colorama + deepdiff + wxPython + +[options.packages.find] +where = src + +[options.package_data] +objdictgen.config = *.prf +objdictgen.img = * +objdictgen.schema = *.json + +[options.extras_require] +dist = + build +dev = + pylint + flake8 + 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 + [flake8] max-line-length = 200 ignore = E128, W503, E303 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 e6ec65b..94ae836 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -22,12 +22,13 @@ 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] diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index dbe7ba9..68ca393 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -139,7 +139,7 @@ def main(debugopts, args=None): 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('--version', action='version', version='%(prog)s ' + objdictgen.__version__) parser.add_argument('-D', '--debug', **opt_debug) # -- HELP -- 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 c609232..db35c57 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -436,7 +436,7 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): '$id': JSON_ID, '$version': JSON_INTERNAL_VERSION if internal else JSON_VERSION, '$description': JSON_DESCRIPTION, - '$tool': str(objdictgen.ODG_PROGRAM) + ' ' + str(objdictgen.ODG_VERSION), + '$tool': str(objdictgen.ODG_PROGRAM) + ' ' + str(objdictgen.__version__), '$date': datetime.isoformat(datetime.now()), }) diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index 18909bf..2f43b72 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -87,8 +87,7 @@ def format_namespace(dic, indent=' '): def handle_exception(e_type, e_value, e_traceback, parent=None): # Import here to prevent circular import - from objdictgen import ODG_VERSION # pylint: disable=import-outside-toplevel - app_version = ODG_VERSION + from objdictgen import __version__ # pylint: disable=import-outside-toplevel 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) @@ -102,7 +101,7 @@ def handle_exception(e_type, e_value, e_traceback, parent=None): if result: info = { 'app-title': wx.GetApp().GetAppName(), # app_title - 'app-version': app_version, + 'app-version': __version__, 'wx-version': wx.VERSION_STRING, 'wx-platform': wx.Platform, 'python-version': platform.python_version(), # sys.version.split()[0], diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 0df1ea1..4174754 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -218,7 +218,7 @@ def __init__(self, parent, nodelist=None, projectOpen=None): # 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(os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), wx.BITMAP_TYPE_ICO) self.SetIcon(icon) if self.ModeSolo: diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index a93cf01..d2d2469 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -221,7 +221,7 @@ def __init__(self, parent, manager=None, filesopen=None): net.NodeEditorTemplate.__init__(self, manager, self, False) self._init_ctrls(parent) - icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "ui", "networkedit.ico"), wx.BITMAP_TYPE_ICO) + icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), wx.BITMAP_TYPE_ICO) self.SetIcon(icon) if self.ModeSolo: From ff5ecf8ebc616093a8a2f12af261bd17c806e058 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 19 Feb 2024 00:42:17 +0100 Subject: [PATCH 07/28] Project formatting cleanups * Restrict line length to 120. Break up long lines * Formatting changes * Add function docs * Change wx Dialogs to with contexts * Minor code changes --- pyproject.toml | 10 +- setup.cfg | 2 +- src/objdictgen/__main__.py | 33 +- src/objdictgen/eds_utils.py | 391 ++++++++------- src/objdictgen/gen_cfile.py | 171 +++++-- src/objdictgen/jsonod.py | 22 +- src/objdictgen/maps.py | 5 +- src/objdictgen/node.py | 108 +++-- src/objdictgen/nodelist.py | 5 +- src/objdictgen/nodemanager.py | 196 +++++--- src/objdictgen/nosis.py | 102 ++-- src/objdictgen/ui/commondialogs.py | 528 +++++++++++++-------- src/objdictgen/ui/exception.py | 24 +- src/objdictgen/ui/networkedit.py | 175 ++++--- src/objdictgen/ui/networkeditortemplate.py | 87 ++-- src/objdictgen/ui/nodeeditortemplate.py | 84 ++-- src/objdictgen/ui/objdictedit.py | 357 ++++++++------ src/objdictgen/ui/subindextable.py | 232 +++++---- 18 files changed, 1500 insertions(+), 1032 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 57a27fc..de6d3c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -312,10 +312,11 @@ indent-string = " " # Maximum number of characters on a single line. # objdictgen: was 100 # FIXME: Set to shorter -max-line-length = 200 +max-line-length = 120 # Maximum number of lines in a module. -max-module-lines = 1000 +# 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. @@ -382,7 +383,10 @@ confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFIN # --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", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero"] +# objdictgen: orig on first line +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 diff --git a/setup.cfg b/setup.cfg index aeed3bd..d6b6bc0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,7 +68,7 @@ console_scripts = objdictedit = objdictgen.__main__:main_objdictedit [flake8] -max-line-length = 200 +max-line-length = 120 ignore = E128, W503, E303 [tool:pytest] diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 68ca393..b8efbce 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 # @@ -45,6 +45,7 @@ class DebugOpts: show_debug: bool = field(default=False) def set_debug(self, dbg): + """Set debug level""" self.show_debug = dbg log.setLevel(logging.DEBUG) @@ -85,6 +86,7 @@ def open_od(fname, validate=True, fix=False): def print_diffs(diffs, show=False): + """ Print the differences between two object dictionaries""" def _pprint(text): for line in pformat(text).splitlines(): @@ -154,15 +156,20 @@ def main(debugopts, args=None): """, aliases=['gen', 'conv']) subp.add_argument('od', **opt_od) 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('--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) # -- DIFF -- @@ -172,7 +179,8 @@ def main(debugopts, args=None): subp.add_argument('od1', **opt_od) subp.add_argument('od2', **opt_od) 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) @@ -189,7 +197,8 @@ def main(debugopts, args=None): """) 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") @@ -228,8 +237,10 @@ 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(f"command '{choice}'") for info in subparser.format_help().split('\n'): diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 6c667a9..6cc814e 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -43,12 +43,14 @@ 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 # noqa: E731 +is_string = lambda x: isinstance(x, str) # pylint: disable=unnecessary-lambda-assignment # noqa: E731 +is_boolean = lambda x: x in (0, 1) # pylint: disable=unnecessary-lambda-assignment # noqa: E731 # Define checking of value for each attribute ENTRY_ATTRIBUTES = { @@ -75,7 +77,7 @@ 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 +87,9 @@ } -# Function that search into Node Mappings the informations about an index or a subindex -# and return the default value def get_default_value(node, index, subindex=None): + """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 +125,29 @@ def get_default_value(node, index, subindex=None): "STANDARDDATATYPES", "SUPPORTEDMODULES"] -# Function that extract sections from a file and returns a dictionary of the informations -def extract_sections(file): +def extract_sections(data): + """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 parse_cpj_file(filepath): + """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() + cpj_data = f.read() - sections = extract_sections(cpj_file) + sections = extract_sections(cpj_data) # Parse assignments for each section for section_name, assignments in sections: @@ -173,7 +177,10 @@ def parse_cpj_file(filepath): try: computed_value = int(value, 16) except ValueError: - raise ValueError(f"'{value}' is not a valid value for attribute '{keyname}' of section '[{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"): @@ -192,33 +199,51 @@ def parse_cpj_file(filepath): if keyname.upper() == "NETNAME": if not is_string(computed_value): - raise ValueError(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + 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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + 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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + 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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + "of section '[{section_name}]'" + ) nodeid = int(nodepresent_result.groups()[0]) 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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) nodeid = int(nodename_result.groups()[0]) 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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{section_name}]'") + raise ValueError( + f"Invalid value '{value}' for keyname '{keyname}' " + f"of section '[{section_name}]'" + ) nodeid = int(nodedcfname_result.groups()[0]) if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} @@ -249,8 +274,8 @@ def parse_cpj_file(filepath): return networks -# Function that parse an EDS file and returns a dictionary of the informations def parse_eds_file(filepath): + """Parse an EDS file and returns a dictionary of the informations""" eds_dict = {} # Read file text @@ -333,13 +358,19 @@ def parse_eds_file(filepath): _ = int(value.upper().replace("$NODEID+", ""), 16) computed_value = f'"{value}"' except ValueError: - raise ValueError(f"'{value}' is not a valid formula for attribute '{keyname}' of section '[{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(f"'{value}' is not a valid value for attribute '{keyname}' of section '[{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,10 +389,14 @@ def parse_eds_file(filepath): if is_entry: # Verify that keyname is a possible attribute if keyname.upper() not in ENTRY_ATTRIBUTES: - raise ValueError(f"Keyname '{keyname}' not recognised for section '[{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(f"Invalid value '{value}' for keyname '{keyname}' of section '[{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 @@ -377,8 +412,10 @@ def parse_eds_file(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): @@ -388,7 +425,10 @@ def parse_eds_file(filepath): attributes = f"Attributes {tp} are" else: attributes = f"Attribute '{missing.pop()}' is" - raise ValueError(f"Error on section '[{section_name}]': '{attributes}' required for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry") + 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) @@ -397,7 +437,10 @@ def parse_eds_file(filepath): attributes = f"Attributes {tp} are" else: attributes = f"Attribute '{unsupported.pop()}' is" - raise ValueError(f"Error on section '[{section_name}]': '{attributes}' unsupported for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry") + raise ValueError( + f"Error on section '[{section_name}]': '{attributes}' unsupported " + f"for a '{ENTRY_TYPES[values['OBJECTTYPE']]['name']}' entry" + ) verify_value(values, section_name, "ParameterValue") verify_value(values, section_name, "DefaultValue") @@ -406,6 +449,7 @@ def parse_eds_file(filepath): def verify_value(values, section_name, param): + """Verify that a value is compatible with the DataType of the entry""" uparam = param.upper() if uparam in values: try: @@ -421,8 +465,8 @@ def verify_value(values, section_name, param): 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 generate_eds_content(node, filepath): + """Generate the EDS file content for the current node in the manager.""" # Dictionary of each index contents indexcontents = {} @@ -591,52 +635,28 @@ def generate_eds_content(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 += f"SupportedObjects={len(mandatories)}\n" - for idx, entry in enumerate(mandatories): - fileContent += f"{idx + 1}=0x{entry:04X}\n" - # Write mandatory entries - for entry in mandatories: - fileContent += indexcontents[entry] - - # Generate Definition of optional objects - fileContent += "\n[OptionalObjects]\n" - fileContent += f"SupportedObjects={len(optionals)}\n" - for idx, entry in enumerate(optionals): - fileContent += f"{idx + 1}=0x{entry:04X}\n" - # Write optional entries - for entry in optionals: - fileContent += indexcontents[entry] - - # Generate Definition of manufacturer objects - fileContent += "\n[ManufacturerObjects]\n" - fileContent += f"SupportedObjects={len(manufacturers)}\n" - for idx, entry in enumerate(manufacturers): - fileContent += f"{idx + 1}=0x{entry:04X}\n" - # Write manufacturer entries - for entry in manufacturers: - fileContent += indexcontents[entry] + def generate_index_contents(name, entries): + """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 generate_eds_file(filepath, node): - content = generate_eds_content(node, filepath) - - with open(filepath, "w") as f: - f.write(content) - - -# Function that generate the CPJ file content for the nodelist def generate_cpj_content(nodelist): + """Generate the CPJ file content for the nodelist.""" nodes = nodelist.SlaveNodes filecontent = "[TOPOLOGY]\n" @@ -652,8 +672,8 @@ def generate_cpj_content(nodelist): return filecontent -# Function that generates Node from an EDS file def generate_node(filepath, nodeid=0): + """Generate a Node from an EDS file.""" # Create a new node node = nodelib.Node(id=nodeid) @@ -688,117 +708,124 @@ def generate_node(filepath, nodeid=0): 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(f"Domain entry 0x{entry:04X} DataType must be 0xF(DOMAIN) if defined") - # 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(f"Error on entry 0x{entry:04X}: Subindex 0 must be defined for a RECORD 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(f"Error on entry 0x{entry:04X}: A RECORD entry must have at least 2 subindexes") - - # Define entry for the new node + continue + + # 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]: - # 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 + 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, 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]: - # 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") + # 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( + # f"Error on entry 0x{entry:04X}: Subindex 0 must be defined for a RECORD 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( + # f"Error on entry 0x{entry:04X}: A RECORD entry must have at least 2 subindexes" + # ) + + # Define entry for the new node + + # 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 5d14f86..7f331ff 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -29,28 +29,33 @@ 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 = [ + ("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. */ +""" class CFileContext: + """Context for generating C file.""" def __init__(self): self.internal_types = {} self.default_string_size = 10 -# Format a string for making a C++ variable def format_name(name): + """Format a string for making a C++ variable.""" wordlist = [word for word in RE_WORD.findall(name) if word] return "_".join(wordlist) -# Extract the informations from a given type name def get_valid_type_infos(context, typename, items=None): + """Get valid type infos from a typename.""" items = items or [] if typename in context.internal_types: return context.internal_types[typename] @@ -89,6 +94,7 @@ def get_valid_type_infos(context, typename, items=None): def compute_value(type_, value): + """Compute value for C file.""" if type_ == "visible_string": return f'"{value}"', "" if type_ == "domain": @@ -103,6 +109,7 @@ def compute_value(type_, value): def get_type_name(node, typenumber): + """Get type name from a type number.""" typename = node.GetTypeName(typenumber) if typename is None: # FIXME: The !!! is for special UI handling @@ -145,10 +152,14 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): # ------------------------------------------------------------------------------ valueRangeContent = "" - strDefine = "\n#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */" + strDefine = ( + "\n#define valueRange_EMC 0x9F " + "/* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */" + ) strSwitch = """ case valueRange_EMC: if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; - break;\n""" + break; +""" context.internal_types["valueRange_EMC"] = ("UNS8", "", "valueRange_EMC", True) num = 0 for index in rangelist: @@ -162,13 +173,20 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): context.internal_types[rangename] = (typeinfos[0], typeinfos[1], f"valueRange_{num}") minvalue = node.GetEntry(index, 2) maxvalue = node.GetEntry(index, 3) - strDefine += f"\n#define valueRange_{num} 0x{index:02X} /* Type {typeinfos[0]}, {minvalue} < value < {maxvalue} */" + strDefine += ( + f"\n#define valueRange_{num} 0x{index:02X} " + f"/* Type {typeinfos[0]}, {minvalue} < value < {maxvalue} */" + ) strSwitch += f" case valueRange_{num}:\n" if typeinfos[3] and minvalue <= 0: strSwitch += " /* Negative or null low limit ignored because of unsigned type */;\n" else: - strSwitch += f" if (*({typeinfos[0]}*)value < ({typeinfos[0]}){minvalue}) return OD_VALUE_TOO_LOW;\n" - strSwitch += f" if (*({typeinfos[0]}*)value > ({typeinfos[0]}){maxvalue}) return OD_VALUE_TOO_HIGH;\n" + strSwitch += ( + f" if (*({typeinfos[0]}*)value < ({typeinfos[0]}){minvalue}) return OD_VALUE_TOO_LOW;\n" + ) + strSwitch += ( + f" if (*({typeinfos[0]}*)value > ({typeinfos[0]}){maxvalue}) return OD_VALUE_TOO_HIGH;\n" + ) strSwitch += " break;\n" valueRangeContent += strDefine @@ -209,18 +227,27 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: if params_infos["buffer_size"]: - texts["suffixe"] = f"[{params_infos['buffer_size']}]" + texts["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = f"[{typeinfos[1]}]" + texts["suffix"] = f"[{typeinfos[1]}]" else: - texts["suffixe"] = "" + texts["suffix"] = "" texts["value"], texts["comment"] = compute_value(typeinfos[2], values) if index in variablelist: texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(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 + strDeclareHeader += ( + "extern %(subIndexType)s %(name)s%(suffix)s;" + "\t\t/* Mapped at index 0x%(index)04X, subindex 0x00*/\n" + ) % texts + mappedVariableContent += ( + "%(subIndexType)s %(name)s%(suffix)s = %(value)s;" + "\t\t/* Mapped at index 0x%(index)04X, subindex 0x00 */\n" + ) % texts else: - strindex += " %(subIndexType)s %(NodeName)s_obj%(index)04X%(suffixe)s = %(value)s;%(comment)s\n" % texts + strindex += ( + " " + "%(subIndexType)s %(NodeName)s_obj%(index)04X%(suffix)s = %(value)s;%(comment)s\n" + ) % texts values = [values] else: subentry_infos = node.GetSubentryInfos(index, 0) @@ -231,7 +258,11 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): 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 + strindex += ( + " " + "%(subIndexType)s %(NodeName)s_highestSubIndex_obj%(index)04X = %(value)d; " + "/* number of subindex - 1*/\n" + ) % texts # Entry type is ARRAY if entry_infos["struct"] & OD.IdenticalSubindexes: @@ -240,17 +271,23 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): typeinfos = get_valid_type_infos(context, typename, values[1:]) texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: - texts["suffixe"] = f"[{typeinfos[1]}]" - texts["type_suffixe"] = "*" + texts["suffix"] = f"[{typeinfos[1]}]" + texts["type_suffix"] = "*" else: - texts["suffixe"] = "" - texts["type_suffixe"] = "" + texts["suffix"] = "" + texts["type_suffix"] = "" texts["length"] = values[0] if index in variablelist: texts["name"] = RE_STARTS_WITH_DIGIT.sub(r'_\1', format_name(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 + strDeclareHeader += ( + "extern %(subIndexType)s %(name)s[%(values_count)s]%(suffix)s;\t\t" + "/* Mapped at index 0x%(index)04X, subindex 0x01 - 0x%(length)02X */\n" + ) % texts + mappedVariableContent += ( + "%(subIndexType)s %(name)s[]%(suffix)s =\t\t" + "/* Mapped at index 0x%(index)04X, subindex 0x01 - 0x%(length)02X */\n {\n" + ) % texts for subindex, value in enumerate(values): sep = "," if subindex > 0: @@ -258,11 +295,18 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): sep = "" value, comment = compute_value(typeinfos[2], value) if len(value) == 2 and typename == "DOMAIN": - raise ValueError(f"Domain variable not initialized, index : 0x{index:04X}, subindex : 0x{subindex:02X}") + 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)s%(type_suffix)s %(NodeName)s_obj%(index)04X[] = \n" + " {\n" + ) % texts for subindex, value in enumerate(values): sep = "," if subindex > 0: @@ -285,24 +329,40 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): texts["subIndexType"] = typeinfos[0] if typeinfos[1] is not None: if params_infos["buffer_size"]: - texts["suffixe"] = f"[{params_infos['buffer_size']}]" + texts["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffixe"] = f"[{typeinfos[1]}]" + texts["suffix"] = f"[{typeinfos[1]}]" else: - texts["suffixe"] = "" + texts["suffix"] = "" texts["value"], texts["comment"] = compute_value(typeinfos[2], value) texts["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)s %(parent)s_%(name)s%(suffix)s;\t\t" + "/* Mapped at index 0x%(index)04X, subindex 0x%(subindex)02X */\n" + ) % texts + mappedVariableContent += ( + "%(subIndexType)s %(parent)s_%(name)s%(suffix)s = %(value)s;\t\t" + "/* Mapped at index 0x%(index)04X, subindex 0x%(subindex)02X */\n" + ) % texts else: - strindex += " %(subIndexType)s %(NodeName)s_obj%(index)04X_%(name)s%(suffixe)s = %(value)s;%(comment)s\n" % texts + strindex += ( + " " + "%(subIndexType)s %(NodeName)s" + "_obj%(index)04X_%(name)s%(suffix)s = " + "%(value)s;%(comment)s\n" + ) % texts headerObjectDefinitionContent += ( - f"\n#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_Idx {texts['index']:#04x}\n" + f"\n#define {RE_NOTW.sub('_', texts['NodeName'])}" + f"_{RE_NOTW.sub('_', texts['EntryName'])}_Idx {texts['index']:#04x}\n" ) # Generating Dictionary C++ entry - strindex += " subindex %(NodeName)s_Index%(index)04X[] = \n {\n" % texts + strindex += ( + " " + "subindex %(NodeName)s_Index%(index)04X[] = \n" + " {\n" + ) % texts generateSubIndexArrayComment = True for subindex, _ in enumerate(values): subentry_infos = node.GetSubentryInfos(index, subindex) @@ -334,7 +394,10 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): if index in variablelist: name = format_name(f"{entry_infos['name']}_{subentry_infos['name']}") else: - name = f"{texts['NodeName']}_obj{texts['index']:%04X}_{format_name(subentry_infos['name'])}" + name = ( + f"{texts['NodeName']}_obj{texts['index']:04X}_" + f"{format_name(subentry_infos['name'])}" + ) if typeinfos[2] == "visible_string": if params_infos["buffer_size"]: sizeof = params_infos["buffer_size"] @@ -350,14 +413,22 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): else: save = "" start_digit = RE_STARTS_WITH_DIGIT.sub(r'_\1', name) - strindex += f" {{ {subentry_infos['access'].upper()}{save}, {typeinfos[2]}, {sizeof}, (void*)&{start_digit}, NULL }}{sep}\n" + strindex += ( + f" {{ " + f"{subentry_infos['access'].upper()}{save}, " + f"{typeinfos[2]}, {sizeof}, (void*)&{start_digit}, NULL " + f"}}{sep}\n" + ) pointer_name = pointers_dict.get((index, subindex), None) if pointer_name is not None: pointedVariableContent += f"{typeinfos[0]}* {pointer_name} = &{name};\n" if not entry_infos["struct"] & OD.IdenticalSubindexes: generateSubIndexArrayComment = True headerObjectDefinitionContent += ( - f"#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_{RE_NOTW.sub('_', subentry_infos['name'])}_sIdx {subindex:#04x}" + f"#define {RE_NOTW.sub('_', texts['NodeName'])}" + f"_{RE_NOTW.sub('_', texts['EntryName'])}" + f"_{RE_NOTW.sub('_', subentry_infos['name'])}" + f"_sIdx {subindex:#04x}" ) if params_infos["comment"]: headerObjectDefinitionContent += " /* " + params_infos["comment"] + " */\n" @@ -365,9 +436,13 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): headerObjectDefinitionContent += "\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 + # Generate Number_of_Entries_sIdx define and write comment + # about not generating defines for the rest of the array objects headerObjectDefinitionContent += ( - f"#define {RE_NOTW.sub('_', texts['NodeName'])}_{RE_NOTW.sub('_', texts['EntryName'])}_{RE_NOTW.sub('_', subentry_infos['name'])}_sIdx {subindex:#04x}\n" + f"#define {RE_NOTW.sub('_', texts['NodeName'])}" + f"_{RE_NOTW.sub('_', texts['EntryName'])}" + f"_{RE_NOTW.sub('_', subentry_infos['name'])}" + f"_sIdx {subindex:#04x}\n" ) headerObjectDefinitionContent += "/* subindex define not generated for array objects */\n" strindex += " };\n" @@ -380,7 +455,8 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): if 0x1003 not in communicationlist: entry_infos = node.GetEntryInfos(0x1003) texts["EntryName"] = entry_infos["name"] - indexContents[0x1003] = """\n/* index 0x1003 : %(EntryName)s */ + indexContents[0x1003] = """ +/* index 0x1003 : %(EntryName)s */ UNS8 %(NodeName)s_highestSubIndex_obj1003 = 0; /* number of subindex - 1*/ UNS32 %(NodeName)s_obj1003[] = { @@ -461,7 +537,11 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): 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 + 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 += f" case 0x{index:04X}: i = {i};break;\n" for cat, idx_min, idx_max in CATEGORIES: if idx_min <= index <= idx_max: @@ -514,7 +594,9 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): """ % texts if texts["heartBeatTimers_number"] > 0: - declaration = "TIMER_HANDLE %(NodeName)s_heartBeatTimers[%(heartBeatTimers_number)d]" % texts + declaration = ( + "TIMER_HANDLE %(NodeName)s_heartBeatTimers[%(heartBeatTimers_number)d]" + ) % texts initializer = "{TIMER_NONE" + ",TIMER_NONE" * (texts["heartBeatTimers_number"] - 1) + "}" fileContent += declaration + " = " + initializer + ";\n" else: @@ -629,10 +711,11 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): def generate_file(filepath, node, pointers_dict=None): """Main function to generate the C file from a object dictionary node.""" - pointers_dict = pointers_dict or {} filebase = os.path.splitext(filepath)[0] headerfilepath = filebase + ".h" - content, header, header_defs = generate_file_content(node, os.path.basename(headerfilepath), pointers_dict) + content, header, header_defs = generate_file_content( + node, os.path.basename(headerfilepath), pointers_dict, + ) # Write main .c contents with open(filepath, "wb") as f: diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index db35c57..c94916d 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 # @@ -298,6 +298,7 @@ def get_object_types(node=None, dictionary=None): def compare_profile(profilename, params, menu=None): + """Compare a profile with a set of parameters and menu.""" try: dsmap, menumap = nodelib.Node.ImportProfile(profilename) identical = all( @@ -389,7 +390,7 @@ def generate_node(contents): # 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') as f: SCHEMA = json.loads(remove_jasonc(f.read())) if SCHEMA: @@ -544,7 +545,7 @@ def node_todict(node, sort=False, rich=True, internal=False, validate=True): # Cleanup of unwanted members # - NOTE: SpecificMenu is not used in dict representation for k in ('Dictionary', 'ParamsDictionary', 'Profile', 'SpecificMenu', - 'DS302', 'UserMapping', 'IndexOrder'): + 'DS302', 'UserMapping', 'IndexOrder'): jd.pop(k, None) # Cross check verification to see if we later can import the generated dict @@ -903,7 +904,10 @@ def node_fromdict(jd, internal=False): log.debug("Index 0x%04x (%s) Difference between built-in object and imported:", index, index) for line in diff.pretty().splitlines(): log.debug(' %s', line) - raise ValidationError(f"Built-in parameter index 0x{index:04x} ({index}) does not match against system parameters") + raise ValidationError( + f"Built-in parameter index 0x{index:04x} ({index}) " + "does not match against system parameters" + ) # 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 @@ -915,6 +919,7 @@ def node_fromdict(jd, internal=False): def node_fromdict_parameter(obj, objtypes_s2i): + """ Convert a dict obj into a Node parameter """ # -- STEP 1a) -- # Move 'definition' into individual mapping type category @@ -1048,11 +1053,15 @@ def validate_fromdict(jsonobj, objtypes_i2s=None, objtypes_s2i=None): # Validate "$id" (must) if jd.get('$id') != JSON_ID: - raise ValidationError(f"Unknown file format, expected '$id' to be '{JSON_ID}', found '{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(f"Unknown file version, expected '$version' to be '{JSON_VERSION}', found '{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: @@ -1298,6 +1307,7 @@ def _validate_dictionary(index, obj): def diff_nodes(node1, node2, as_dict=True, validate=True): + """Compare two nodes and return the differences.""" diffs = {} diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 60039f9..676c2b7 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 @@ -37,6 +37,7 @@ # ------------------------------------------------------------------------------ class ODStructTypes: + """Object Dictionary Structure Types""" # # Properties of entry structure in the Object Dictionary # @@ -73,11 +74,13 @@ class ODStructTypes: @classmethod def to_string(cls, val, default=''): + """Return the string representation of the structure value.""" # type: (type[ODStructTypes], int, str) -> str return cls.STRINGS.get(val, default) @classmethod def from_string(cls, val, default=None): + """Return the structure value from the string representation.""" # type: (type[ODStructTypes], str, int|None) -> int|None try: return next(k for k, v in cls.STRINGS.items() if v == val) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 90bae21..0b79dfa 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 @@ -35,8 +36,8 @@ 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'] +# 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'(.*)\[[(](.*)[)]\]') @@ -52,7 +53,8 @@ class Node: DefaultStringSize = 10 - def __init__(self, name="", type="slave", id=0, description="", profilename="DS-301", profile=None, specificmenu=None): # pylint: disable=redefined-builtin, invalid-name + 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 @@ -72,12 +74,14 @@ def __init__(self, name="", type="slave", id=0, description="", profilename="DS- @staticmethod def isXml(filepath): + """Check if the file is an XML file""" with open(filepath, 'r') as f: header = f.read(5) return header == " dictlen} if excessive_params: log.debug("Excessive params: %s", excessive_params) - _warn(f"Excessive user parameters ({len(excessive_params)}) or too few dictionary values ({dictlen})") + _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: @@ -860,7 +886,7 @@ def _warn(text): _warn("FIX: Deleting ParamDictionary entry") # Iterate over all user mappings - params = set(self.UserMapping.keys()) + params = set(self.UserMapping) for index in params: for idx, subvals in enumerate(self.UserMapping[index]['values']): @@ -964,7 +990,10 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve 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 @@ -1021,7 +1050,9 @@ def ImportProfile(profilename): if os.path.exists(os.path.join(base, fname)) ) except StopIteration: - raise ValueError(f"Unable to load profile '{profilename}': '{fname}': No such file or directory") from None + 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 @@ -1059,8 +1090,11 @@ def string_format(text, idx, sub): # pylint: disable=unused-argument # and cannot be removed currently if len(args) == 1: return fmt[0] % (Node.evaluate_expression(args[0].strip())) - elif len(args) == 2: - return fmt[0] % (Node.evaluate_expression(args[0].strip()), Node.evaluate_expression(args[1].strip())) + if len(args) == 2: + return fmt[0] % ( + Node.evaluate_expression(args[0].strip()), + Node.evaluate_expression(args[1].strip()), + ) return fmt[0] except Exception as exc: @@ -1076,7 +1110,8 @@ def evaluate_expression(expression: str): - Addition (i.e. "3+4") - Subraction (i.e. "7-4") - Constants (i.e. "5") - This function will handle chained arithmatic i.e. "1+2+3" although operating order is not neccesarily preserved + This function will handle chained arithmatic i.e. "1+2+3" although + operating order is not neccesarily preserved Parameters: expression (str): string to parse @@ -1092,19 +1127,16 @@ def evaluate_node(node: ast.AST): if isinstance(node, ast.BinOp): if isinstance(node.op, ast.Add): return Node.evaluate_node(node.left) + Node.evaluate_node(node.right) - elif isinstance(node.op, ast.Sub): + if isinstance(node.op, ast.Sub): return Node.evaluate_node(node.left) - Node.evaluate_node(node.right) - else: - raise SyntaxError(f"Unhandled arithmatic operation {type(node.op)}") - elif isinstance(node, ast.Constant): + raise SyntaxError(f"Unhandled arithmatic operation {type(node.op)}") + if isinstance(node, ast.Constant): if isinstance(node.value, int | float | complex): return node.value - else: - raise TypeError(f"Cannot parse str type constant '{node.value}'") - elif isinstance(node, ast.AST): + raise TypeError(f"Cannot parse str type constant '{node.value}'") + if isinstance(node, ast.AST): raise TypeError(f"Unhandled ast node class {type(node)}") - else: - raise TypeError(f"Invalid argument type {type(node)}") + raise TypeError(f"Invalid argument type {type(node)}") @staticmethod def get_index_range(index): diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index b96916e..1ecabcd 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 @@ -128,7 +129,9 @@ def LoadMasterNode(self, netname=None): if os.path.isfile(masterpath): index = self.Manager.OpenFileInCurrent(masterpath) else: - index = self.Manager.CreateNewNode("MasterNode", 0x00, "master", "", "None", "", "Heartbeat", ["DS302"]) + index = self.Manager.CreateNewNode( + "MasterNode", 0x00, "master", "", "None", "", "Heartbeat", ["DS302"] + ) return index def SaveMasterNode(self, netname=None): diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index 9c60934..a3e67e3 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 @@ -199,10 +200,10 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt node.SpecificMenu = [] # Initialising node self.CurrentNode = node - self.CurrentNode.Name = name - self.CurrentNode.ID = id - self.CurrentNode.Type = type - self.CurrentNode.Description = description + node.Name = name + node.ID = id + node.Type = type + node.Description = description addindexlist = self.GetMandatoryIndexes() addsubindexlist = [] if nmt == "NodeGuarding": @@ -213,8 +214,8 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt if option == "DS302": # Import profile mapping, menuentries = nodelib.Node.ImportProfile("DS-302") - self.CurrentNode.DS302 = mapping - self.CurrentNode.SpecificMenu.extend(menuentries) + node.DS302 = mapping + node.SpecificMenu.extend(menuentries) elif option == "GenSYNC": addindexlist.extend([0x1005, 0x1006]) elif option == "Emergency": @@ -235,7 +236,7 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt 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, []) @@ -250,9 +251,9 @@ def OpenFileInCurrent(self, filepath, load=True): node = nodelib.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 @@ -288,7 +289,9 @@ def CloseCurrent(self, ignore=False): 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): + if (self.NodeIndex in self.UndoBuffers + and (self.UndoBuffers[self.NodeIndex].IsCurrentSaved() or ignore) + ): self.RemoveNodeBuffer(self.NodeIndex) if len(self.UndoBuffers) > 0: previousindexes = [idx for idx in self.UndoBuffers if idx < self.NodeIndex] @@ -357,7 +360,10 @@ def RemoveSubentriesFromCurrent(self, index, number): else: 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() @@ -502,34 +508,35 @@ def RemoveCurrentVariable(self, index, subindex=None): method """ assert self.CurrentNode # For mypy - mappings = self.CurrentNode.GetMappings() + node = self.CurrentNode + mappings = node.GetMappings() if index < 0x1000 and subindex is None: - type_ = self.CurrentNode.GetEntry(index, 1) + type_ = node.GetEntry(index, 1) for i in mappings[-1]: for value in mappings[-1][i]["values"]: if value["type"] == index: value["type"] = type_ - self.CurrentNode.RemoveMappingEntry(index) - self.CurrentNode.RemoveEntry(index) + 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 _, list_ in node.SpecificMenu: for i in list_: iinfos = self.GetEntryInfos(i) indexes = [i + incr * iinfos["incr"] for incr in range(iinfos["nbmax"])] @@ -538,14 +545,16 @@ def RemoveCurrentVariable(self, index, subindex=None): 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) + node.RemoveLine( + j + diff, j + jinfos["incr"] * jinfos["nbmax"], jinfos["incr"] + ) + node.RemoveMapVariable(index, subindex) if not found: infos = self.GetEntryInfos(index) if not infos["need"]: - self.CurrentNode.RemoveEntry(index, subindex) + 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): if 0x2000 <= index <= 0x5FFF: @@ -564,7 +573,10 @@ def AddMapVariableToCurrent(self, index, name, struct, number, node=None): values = {"name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False} node.AddMappingEntry(index, 0, values=values) if struct == OD.ARRAY: - values = {"name": name + " %d[(sub)]", "type": 0x05, "access": "rw", "pdo": True, "nbmax": 0xFE} + values = { + "name": name + " %d[(sub)]", "type": 0x05, + "access": "rw", "pdo": True, "nbmax": 0xFE, + } node.AddMappingEntry(index, 1, values=values) for i in range(number): node.AddEntry(index, i + 1, 0) @@ -580,8 +592,9 @@ def AddMapVariableToCurrent(self, index, name, struct, number, node=None): def AddUserTypeToCurrent(self, type_, min_, max_, length): assert self.CurrentNode # For mypy + node = self.CurrentNode 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!") @@ -590,21 +603,39 @@ def AddUserTypeToCurrent(self, type_, min_, max_, length): size = self.GetEntryInfos(type_)["size"] default = self.GetTypeDefaultValue(type_) if valuetype == 0: - self.CurrentNode.AddMappingEntry(index, name=f"{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, + name=f"{name}[{min_}-{max_}]", struct=OD.RECORD, size=size, default=default + ) + node.AddMappingEntry(index, 0, values={ + "name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 1, values={ + "name": "Type", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 2, values={ + "name": "Minimum Value", "type": type_, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 3, values={ + "name": "Maximum Value", "type": type_, "access": "ro", "pdo": False, + }) + node.AddEntry(index, 1, type_) + node.AddEntry(index, 2, min_) + node.AddEntry(index, 3, max_) elif valuetype == 1: - self.CurrentNode.AddMappingEntry(index, name=f"{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, + name=f"{name}{length}", struct=OD.RECORD, size=length * size, default=default + ) + node.AddMappingEntry(index, 0, values={ + "name": "Number of Entries", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 1, values={ + "name": "Type", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 2, values={ + "name": "Length", "type": 0x05, "access": "ro", "pdo": False, + }) + node.AddEntry(index, 1, type_) + node.AddEntry(index, 2, length) self.BufferCurrentNode() # -------------------------------------------------------------------------- @@ -705,31 +736,42 @@ def SetCurrentEntryName(self, index, name): def SetCurrentUserType(self, index, type_, min_, max_, length): assert self.CurrentNode # For mypy + node = self.CurrentNode customisabletypes = self.GetCustomisableTypes() _, valuetype = self.GetCustomisedTypeValues(index) name, new_valuetype = customisabletypes[type_] size = self.GetEntryInfos(type_)["size"] default = self.GetTypeDefaultValue(type_) if new_valuetype == 0: - self.CurrentNode.SetMappingEntry(index, name=f"{name}[{min_}-{max_}]", struct=OD.RECORD, size=size, default=default) + node.SetMappingEntry(index, + name=f"{name}[{min_}-{max_}]", 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.SetMappingEntry(index, 2, values={ + "name": "Minimum Value", "type": type_, "access": "ro", "pdo": False, + }) + node.AddMappingEntry(index, 3, values={ + "name": "Maximum Value", "type": type_, "access": "ro", "pdo": False, + }) + node.SetEntry(index, 1, type_) + node.SetEntry(index, 2, min_) if valuetype == 1: - self.CurrentNode.AddEntry(index, 3, max_) + node.AddEntry(index, 3, max_) else: - self.CurrentNode.SetEntry(index, 3, max_) + node.SetEntry(index, 3, max_) elif new_valuetype == 1: - self.CurrentNode.SetMappingEntry(index, name=f"{name}{length}", struct=OD.RECORD, size=size, default=default) + node.SetMappingEntry(index, + 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.SetMappingEntry(index, 2, values={ + "name": "Length", "type": 0x02, "access": "ro", "pdo": False, + }) + node.RemoveMappingEntry(index, 3) + node.SetEntry(index, 1, type_) + node.SetEntry(index, 2, length) if valuetype == 0: - self.CurrentNode.RemoveEntry(index, 3) + node.RemoveEntry(index, 3) self.BufferCurrentNode() # -------------------------------------------------------------------------- @@ -889,29 +931,31 @@ def GetCurrentNodeID(self, node=None): # pylint: disable=unused-argument return None def GetCurrentNodeInfos(self): - name = self.CurrentNode.Name - id_ = self.CurrentNode.ID - type_ = self.CurrentNode.Type - description = self.CurrentNode.Description or "" + node = self.CurrentNode + name = node.Name + id_ = node.ID + type_ = node.Type + description = node.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 + node = self.CurrentNode + node.Name = name + node.ID = id_ + node.Type = type_ + node.Description = description self.BufferCurrentNode() def GetCurrentNodeDefaultStringSize(self): if self.CurrentNode: return self.CurrentNode.DefaultStringSize - return Node.DefaultStringSize + return nodelib.Node.DefaultStringSize def SetCurrentNodeDefaultStringSize(self, size): if self.CurrentNode: self.CurrentNode.DefaultStringSize = size else: - Node.DefaultStringSize = size + nodelib.Node.DefaultStringSize = size def GetCurrentProfileName(self): if self.CurrentNode: @@ -945,7 +989,9 @@ def GetCurrentValidChoices(self, min_, max_): 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: + if (min_ <= index <= max_ and not self.CurrentNode.IsEntry(index) + and index not in exclusionlist + ): validchoices.append((self.GetEntryName(index), index)) return validchoices @@ -1015,7 +1061,9 @@ 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"): + 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" @@ -1043,7 +1091,7 @@ def GetNodeEntryValues(self, node, index): dic["value"] = fmt.format(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 + # FIXME: dict["value"] can contain $NODEID for PDOs i.e. $NODEID+0x200 editor["value"] = "string" if values[0] == "INTEGER": editor["value"] = "number" @@ -1072,10 +1120,12 @@ def AddToDCF(self, node_id, index, subindex, size, value): else: nbparams = 0 new_value = nodelib.Node.le_to_be(nbparams + 1, 4) + dcf_value[4:] - new_value += (nodelib.Node.le_to_be(index, 2) - + nodelib.Node.le_to_be(subindex, 1) - + nodelib.Node.le_to_be(size, 4) - + nodelib.Node.le_to_be(value, size)) + new_value += ( + nodelib.Node.le_to_be(index, 2) + + nodelib.Node.le_to_be(subindex, 1) + + nodelib.Node.le_to_be(size, 4) + + nodelib.Node.le_to_be(value, size) + ) self.CurrentNode.SetEntry(0x1F22, node_id, new_value) # -------------------------------------------------------------------------- diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index ede2171..0db16fc 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -43,9 +43,6 @@ class _EmptyClass: str: 1, } -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'$') @@ -113,19 +110,21 @@ def aton(s): def ntoa(num: int|float|complex) -> str: """Convert a number to a string without calling repr()""" if isinstance(num, int): - s = str(num) - elif isinstance(num, float): + 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 = f"{num.real:.17g}+{num.imag:.17g}j" - else: - raise ValueError(f"Unknown numeric type: {repr(num)}") - return s + return f"{num.real:.17g}+{num.imag:.17g}j" + + raise ValueError(f"Unknown numeric type: {repr(num)}") XML_QUOTES = ( @@ -138,15 +137,12 @@ 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("'", ''') + """Quote XML entries""" + + for repl in XML_QUOTES: + s = s.replace(repl[0], repl[1]) # for others, use Python style escapes - s = repr(s) - return s[1:-1] # without the extra single-quotes + return repr(s)[1:-1] # without the extra single-quotes def unsafe_string(s): @@ -163,11 +159,10 @@ def unsafe_string(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('>', '>') - - return s # To be able to be used with py3 + # Quote XML entries + for repl in XML_QUOTES: + s = s.replace(repl[0], repl[1]) + return s # # wrap "regular" python strings as unicode # if isinstance(s, str): @@ -248,7 +243,7 @@ def get_node_valuetext(node): # 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: + if 'value' in node._attrs: # pylint: disable=protected-access # text in tag ttext = node.getAttribute('value') return unsafe_string(ttext) @@ -333,7 +328,9 @@ def xmldump(iohandle=None, obj=None, binary=0, deepcopy=None, omit=None): """Create the XML representation as a string.""" if deepcopy is None: deepcopy = 0 - return _pickle_toplevel_obj(StreamWriter(iohandle, binary), obj, deepcopy, omit) + return _pickle_toplevel_obj( + StreamWriter(iohandle, binary), obj, deepcopy, omit, + ) def xmlload(filehandle): @@ -404,8 +401,9 @@ def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): # 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. + # 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__ @@ -423,7 +421,8 @@ def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): def unpickle_instance(node): - """Take a or <.. type="PyObject"> DOM node and unpickle the object.""" + """Take a or <.. type="PyObject"> DOM node and unpickle + the object.""" # 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) @@ -537,6 +536,23 @@ def _family_type(family, typename, mtype, mextra): return f'family="{family}" type="{typename}"' +# Encodings for builtin types. +TYPENAMES = { + '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 _fix_family(family, typename): """ If family is None or empty, guess family based on typename. @@ -545,35 +561,19 @@ def _fix_family(family, typename): 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' + typename = TYPENAMES.get(typename) + if typename is not None: + return typename raise ValueError(f"family= must be given for unknown type '{typename}'") def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): tag_body = [] - (mtag, thing, in_body, mextra) = (None, orig_thing, getInBody(type(orig_thing)), None) + mtag = None + thing = orig_thing + in_body = TYPE_IN_BODY.get(type(orig_thing), 0) + mextra = None if thing is None: ft = _family_type('none', 'None', None, None) diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index b56dbb4..da39193 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 @@ -45,11 +46,14 @@ class CommunicationDialog(wx.Dialog): + """Edit Communication Profile 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) @@ -237,11 +241,14 @@ def UnselectCurrent(self): 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) @@ -278,67 +285,83 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=prnt, 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) @@ -428,11 +451,14 @@ def EnableNumberTyping(self, enable): class UserTypeDialog(wx.Dialog): + """Create User Type 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) @@ -485,51 +511,63 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=prnt, 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) @@ -659,11 +697,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) @@ -697,50 +738,61 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=prnt, 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) @@ -764,7 +816,10 @@ 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) @@ -814,11 +869,14 @@ def GetValues(self): 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) @@ -901,107 +959,130 @@ def _init_sizers(self): def _init_ctrls(self, prnt, 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=prnt, 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) @@ -1025,7 +1106,9 @@ def __init__(self, parent, buttons=wx.OK | wx.CANCEL): 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": + 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.Profile.Append("Other") @@ -1036,8 +1119,13 @@ 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) @@ -1088,10 +1176,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 @@ -1116,11 +1208,14 @@ def OnProfileChoice(self, event): 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) @@ -1157,40 +1252,49 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=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', + ) 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) @@ -1239,26 +1343,27 @@ def OnOK(self, event): # pylint: disable=unused-argument 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 - display_exception_dialog(self) - 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() @@ -1348,8 +1453,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) @@ -1411,13 +1521,18 @@ def Empty(self): class DCFEntryValuesDialog(wx.Dialog): + """Dialog to edit DCF Entry values.""" # pylint: disable=attribute-defined-outside-init 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) @@ -1441,50 +1556,53 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=prnt, 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) diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index 2f43b72..29e6a14 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 @@ -54,8 +56,9 @@ def _display_exception_dialog(e_type, e_value, e_tb, parent=None): """ + str(e_type) + " : " + str(e_value)), "Error", - trcbck_lst) as dlg: - res = (dlg.ShowModal() == wx.ID_OK) + trcbck_lst + ) as dlg: + res = dlg.ShowModal() == wx.ID_OK return res @@ -66,9 +69,8 @@ def display_exception_dialog(parent): def display_error_dialog(parent, message, caption="Error"): - message = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_ERROR) - message.ShowModal() - message.Destroy() + with wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_ERROR) as dialog: + return dialog.ShowModal() def get_last_traceback(tb): @@ -81,7 +83,8 @@ def format_namespace(dic, indent=' '): return '\n'.join(f"{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 +# 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): @@ -89,7 +92,8 @@ 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 - traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func + # 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 @@ -113,12 +117,14 @@ def handle_exception(e_type, e_value, e_traceback, parent=None): } if e_traceback: info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + f'{e_type}: {e_value}' - exception_locals = last_tb.tb_frame.f_locals # the locals at the level of the stack trace where the exception actually occurred + # 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__) - with open(os.path.join(os.getcwd(), "bug_report_" + info['date'].replace(':', '-').replace(' ', '_') + ".txt"), 'w') as fp: + f_date = info['date'].replace(':', '-').replace(' ', '_') + with open(Path.cwd() / f"bug_report_{f_date}.txt", 'w') as fp: for a, t in info.items(): fp.write(f"{a}:\n{t}\n\n") diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 4174754..b4dc8d1 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 @@ -63,6 +64,7 @@ def usage(): class NetworkEdit(wx.Frame, NetworkEditorTemplate): + """Network Editor UI.""" # pylint: disable=attribute-defined-outside-init EDITMENU_ID = ID_NETWORKEDITEDITMENUOTHERPROFILE @@ -76,17 +78,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) @@ -95,12 +97,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, @@ -108,58 +110,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) @@ -190,9 +192,11 @@ def _init_utils(self): 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') + 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', + ) self._init_utils() self.SetClientSize(wx.Size(1000, 700)) self.SetMenuBar(self.MenuBar) @@ -204,8 +208,10 @@ 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) @@ -218,7 +224,10 @@ def __init__(self, parent, nodelist=None, projectOpen=None): # FIXME: Unused. Delete this? # self.HtmlFrameOpened = [] - icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), wx.BITMAP_TYPE_ICO) + icon = wx.Icon( + os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), + wx.BITMAP_TYPE_ICO, + ) self.SetIcon(icon) if self.ModeSolo: @@ -261,53 +270,61 @@ def OnNewProjectMenu(self, event): # pylint: disable=unused-argument 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 = NodeList(manager) + try: + nodelist.LoadProject(projectpath) - self.RefreshNetworkNodes() - self.RefreshBufferState() - self.RefreshTitle() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - display_exception_dialog(self) + 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 = NodeList(manager) + try: + nodelist.LoadProject(projectpath) - self.RefreshNetworkNodes() - self.RefreshBufferState() - self.RefreshTitle() - self.RefreshProfileMenu() - self.RefreshMainMenu() - except Exception as exc: # pylint: disable=broad-except - display_exception_dialog(self) - 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: @@ -315,22 +332,26 @@ def OnSaveProjectMenu(self, event): # pylint: disable=unused-argument else: try: self.NodeList.SaveProject() - except Exception as exc: # pylint: disable=broad-except + 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 + 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 diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index d4bf0e1..038028f 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 @@ -30,14 +31,19 @@ class NetworkEditorTemplate(net.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) + 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 @@ -76,7 +82,8 @@ 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? + raise NotImplementedError("Missing unknown symbol. Please report this issue.") + # wx.CallAfter(self.RefreshMainMenu) # FIXME: Missing symbol. From where? wx.CallAfter(self.RefreshStatusBar) event.Skip() @@ -100,43 +107,49 @@ def RefreshBufferState(self): # ------------------------------------------------------------------------------ def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument - dialog = cdia.AddSlaveDialog(self.Frame) - dialog.SetNodeList(self.NodeList) - if dialog.ShowModal() == wx.ID_OK: + with cdia.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 - display_exception_dialog(self.Frame) - dialog.Destroy() + + 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: # 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 - display_exception_dialog(self.Frame) - dialog.Destroy() + + 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): self.NetworkNodes.SetSelection(0) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index b2c6cb6..206c1df 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 @@ -25,6 +26,7 @@ class NodeEditorTemplate: + """Template for the NodeEditor class.""" EDITMENU_ID = None @@ -120,7 +122,9 @@ def RefreshProfileMenu(self): self.Frame.AddMenu.AppendSeparator() for text, _ in self.Manager.GetCurrentSpecificMenu(): 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") @@ -161,33 +165,31 @@ def OnEditProfileMenu(self, event): # pylint: disable=unused-argument 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() + with cdia.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 @@ -213,27 +215,25 @@ def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument def AddMapVariable(self): index = self.Manager.GetCurrentNextMapIndex() if index: - dialog = cdia.MapVariableDialog(self.Frame) - dialog.SetIndex(index) + with cdia.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 cdia.UserTypeDialog(self) as dialog: + dialog.SetTypeList(self.Manager.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 + except Exception: # pylint: disable=broad-except display_exception_dialog(self.Frame) - dialog.Destroy() - else: - display_error_dialog(self.Frame, "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 - display_exception_dialog(self.Frame) - dialog.Destroy() diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index d2d2469..c5eea3c 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 @@ -65,6 +66,7 @@ def usage(): class ObjdictEdit(wx.Frame, net.NodeEditorTemplate): + """Main frame for the object dictionary editor.""" # pylint: disable=attribute-defined-outside-init EDITMENU_ID = ID_OBJDICTEDITEDITMENUOTHERPROFILE @@ -77,93 +79,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) @@ -190,9 +192,11 @@ def _init_utils(self): 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') + 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', + ) self._init_utils() self.SetClientSize(wx.Size(1000, 700)) self.SetMenuBar(self.MenuBar) @@ -202,14 +206,20 @@ 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) + 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, + ) self._init_coll_HelpBar_Fields(self.HelpBar) self.SetStatusBar(self.HelpBar) @@ -221,7 +231,10 @@ def __init__(self, parent, manager=None, filesopen=None): net.NodeEditorTemplate.__init__(self, manager, self, False) self._init_ctrls(parent) - icon = wx.Icon(os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), wx.BITMAP_TYPE_ICO) + icon = wx.Icon( + os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), + wx.BITMAP_TYPE_ICO, + ) self.SetIcon(icon) if self.ModeSolo: @@ -279,9 +292,12 @@ def OnCloseFrame(self, event): 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(): @@ -371,55 +387,64 @@ def RefreshBufferState(self): def OnNewMenu(self, event): # pylint: disable=unused-argument # FIXME: Unused. Delete this? # self.FilePath = "" - dialog = cdia.CreateNodeDialog(self) - if dialog.ShowModal() == wx.ID_OK: + with cdia.CreateNodeDialog(self) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return name, id_, 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: # pylint: disable=broad-except + display_exception_dialog(self) + + def OnOpenMenu(self, event): # pylint: disable=unused-argument + filepath = self.Manager.GetCurrentFilePath() + if filepath: + directory = os.path.dirname(filepath) + else: + directory = os.getcwd() + + 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.CreateNewNode(name, id_, nodetype, description, profile, filepath, nmt, options) + 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) - self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) - if "DS302" in options: + 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 + except Exception: # pylint: disable=broad-except display_exception_dialog(self) - dialog.Destroy() - - def OnOpenMenu(self, event): # pylint: disable=unused-argument - filepath = self.Manager.GetCurrentFilePath() - if filepath: - 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: - 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 - display_exception_dialog(self) - dialog.Destroy() def OnSaveMenu(self, event): # pylint: disable=unused-argument if not self.ModeSolo and getattr(self, "_onsave", None) is not None: @@ -438,7 +463,7 @@ def Save(self): self.SaveAs() else: self.RefreshBufferState() - except Exception as exc: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except display_exception_dialog(self) def SaveAs(self): @@ -448,38 +473,46 @@ def SaveAs(self): else: 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_CANCEL: + 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)): - 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 as exc: # pylint: disable=broad-except - display_exception_dialog(self) + 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) @@ -493,66 +526,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) + + 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() + with wx.MessageDialog( + self, "Import successful", "Information", wx.OK | wx.ICON_INFORMATION, + ) as message: message.ShowModal() - message.Destroy() - except Exception as exc: # pylint: disable=broad-except - display_exception_dialog(self) - else: - display_error_dialog(self, f"'{filepath}' is not a valid file!") - 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)): - 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') - message = wx.MessageDialog(self, "Export successful", "Information", wx.OK | wx.ICON_INFORMATION) + + 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() - except Exception as exc: # pylint: disable=broad-except - display_exception_dialog(self) - 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)): - 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') - message = wx.MessageDialog(self, "Export successful", "Information", wx.OK | wx.ICON_INFORMATION) + + 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() - except Exception as exc: # pylint: disable=broad-except - display_exception_dialog(self) - dialog.Destroy() + except Exception: # pylint: disable=broad-except + display_exception_dialog(self) def uimain(args): diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 30ef63e..0246d13 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 @@ -28,7 +29,10 @@ from objdictgen.ui.exception import display_error_dialog 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 +55,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 +70,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,7 +99,9 @@ "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): @@ -165,8 +176,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) @@ -308,6 +324,7 @@ def Empty(self): class EditingPanel(wx.SplitterWindow): + """UI for the Object Dictionary Editor.""" # pylint: disable=attribute-defined-outside-init def _init_coll_AddToListSizer_Items(self, parent): @@ -335,38 +352,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='') @@ -392,73 +409,73 @@ def _init_sizers(self): def _init_ctrls(self, prnt): 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=prnt, 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, -1), 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() @@ -520,11 +537,12 @@ def OnSubindexGridCellLeftClick(self, event): bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) var_name = f"{self.Manager.GetCurrentNodeName()}_{index:04x}_{subindex:02x}" size = typeinfos["size"] - data = wx.TextDataObject(str( - (f"{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() @@ -543,11 +561,12 @@ def OnSubindexGridCellLeftClick(self, event): bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) var_name = f"{self.Manager.GetSlaveName(node_id)}_{index:04x}_{subindex:02x}" size = typeinfos["size"] - data = wx.TextDataObject(str( - (f"{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() @@ -628,12 +647,18 @@ def RefreshIndexList(self): 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): + 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() @@ -746,7 +771,11 @@ def OnSubindexGridRightClick(self, event): if self.Manager.IsCurrentEntry(index): showpopup = False infos = self.Manager.GetEntryInfos(index) - if 0x2000 <= index <= 0x5FFF and infos["struct"] & OD.MultipleSubindexes or infos["struct"] & OD.IdenticalSubindexes: + # 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) @@ -760,7 +789,9 @@ 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: + 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] @@ -797,7 +828,9 @@ 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) + self.Manager.AddToMasterDCF( + node_id, index, subindex, max(1, typeinfos["size"] // 8), value + ) self.ParentWindow.OpenMasterDCFDialog(node_id) def OpenDCFDialog(self, node_id): @@ -815,13 +848,14 @@ def OnRenameIndexMenu(self, event): # pylint: disable=unused-argument index = self.ListIndex[selected] if self.Manager.IsCurrentEntry(index): infos = self.Manager.GetEntryInfos(index) - dialog = wx.TextEntryDialog(self, f"Give a new name for index 0x{index:04X}", - "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() + 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: @@ -858,17 +892,18 @@ def OnAddSubindexMenu(self, event): # pylint: disable=unused-argument 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: - display_error_dialog(self, "An integer is required!") - dialog.Destroy() + 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: @@ -876,17 +911,18 @@ def OnDeleteSubindexMenu(self, event): # pylint: disable=unused-argument 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: - display_error_dialog(self, "An integer is required!") - dialog.Destroy() + 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: From e72ae739b24a74863d4bc75ca733fe32e4cb02ec Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Thu, 22 Feb 2024 11:08:32 +0100 Subject: [PATCH 08/28] Test framework improvements --- setup.cfg | 1 + tests/conftest.py | 160 ++++++++++++++++++++++++++---------- tests/test_knownfailures.py | 17 ++-- tests/test_nodelist.py | 2 - tests/test_nodemanager.py | 5 +- tests/test_objdictgen.py | 5 +- tests/test_odcompare.py | 48 ++++++----- tests/test_odg.py | 17 ++-- 8 files changed, 165 insertions(+), 90 deletions(-) diff --git a/setup.cfg b/setup.cfg index d6b6bc0..38d17fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,6 +52,7 @@ dist = dev = pylint flake8 + isort mypy types-setuptools types-colorama diff --git a/tests/conftest.py b/tests/conftest.py index d25ed75..8aaa380 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,41 +1,36 @@ +""" Pytest configuration file for the objdictgen package """ +from typing import Generator import os -import glob import difflib from dataclasses import dataclass +import subprocess +from pathlib import Path import pytest import objdictgen import objdictgen.node -HERE = os.path.split(__file__)[0] +HERE = Path(__file__).parent # Location of the test OD files -ODDIR = os.path.join(HERE, 'od') +ODDIR = HERE / 'od' -# 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'))) +# Default OD test directories +DEFAULT_ODTESTDIRS = [ + ODDIR / 'legacy-compare', + ODDIR / 'extra-compare', +] -@dataclass -class ODFile: - """ Class representing an OD file """ - filename: str - - def __str__(self): - return self.filename +class ODPath(type(Path())): + """ Overload on Path to add OD specific methods """ def __add__(self, other): - return self.filename + other - - @property - def name(self): - return os.path.split(self.filename)[1] + return ODPath(self.parent / (self.name + other)) - @property - def relpath(self): - return os.path.relpath(self.filename, ODDIR) + def __truediv__(self, other): + return ODPath(Path.__truediv__(self, other)) class Fn: @@ -57,15 +52,90 @@ def diff(a, b, predicate=None, **kw): return not out -# List of all OD files -ODFILES = [ODFile(os.path.abspath(x.replace('.od', ''))) for x in _ODFILES] +@dataclass +class Py2: + """ Class for calling python2 """ + py2: Path | None + objdictgen: Path | None + + def __post_init__(self): + print("\n ****POST****\n") + + def run(self, script, **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) + + indata = script.encode('ascii', 'backslashreplace') + + kw = { + 'input': indata, + 'env': env, + 'text': False, + } + kw.update(**kwargs) + + return subprocess.check_output([self.py2, '-'], executable=self.py2, **kw) + + +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", + ) + def pytest_generate_tests(metafunc): ''' Special fixture generators ''' + + # Collect the list of od test directories + oddirs = metafunc.config.getoption("oddir") + if not oddirs: + oddirs = list(DEFAULT_ODTESTDIRS) + + # Add "odfile" fixture if "odfile" in metafunc.fixturenames: - metafunc.parametrize("odfile", ODFILES, ids=[ - o.relpath for o in ODFILES - ], indirect=True) + + # Make a list of all .od files in tests/od + odfiles = [] + for d in oddirs: + odfiles.extend( + ODPath(f.with_suffix('').absolute()) + for f in d.glob('*.od') + ) + + metafunc.parametrize("odfile", odfiles, + ids=[str(o.relative_to(ODDIR).as_posix()) for o in odfiles], + indirect=False + ) + + # 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") # @@ -73,23 +143,26 @@ def pytest_generate_tests(metafunc): # ======================================== # -@pytest.fixture -def oddir(): - """ Fixture returning the path for the od test directory """ - return os.path.abspath(os.path.join(ODDIR)) - @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)) - yield os.getcwd() - os.chdir(str(cwd)) +def fn(): + """ Fixture providing a helper class for testing functions """ + return Fn() + +@pytest.fixture +def odfile(request) -> Generator[ODPath, None, None]: + """ Fixture for each of the od files in the test directory """ + print(type(request.param)) + yield request.param + +@pytest.fixture +def odpath(): + """ Fixture returning the path for the od test directory """ + return ODPath(ODDIR.absolute()) @pytest.fixture def profile(monkeypatch): @@ -103,11 +176,14 @@ def profile(monkeypatch): yield 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 @pytest.fixture -def fn(): - """ Fixture providing a helper class for testing functions """ - 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)) diff --git a/tests/test_knownfailures.py b/tests/test_knownfailures.py index cb5bab6..7c33fbe 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): +@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): +@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_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..bee7135 100644 --- a/tests/test_nodemanager.py +++ b/tests/test_nodemanager.py @@ -1,4 +1,3 @@ -import os from objdictgen.nodemanager import NodeManager @@ -17,7 +16,7 @@ def test_create_slave(): m1.CloseCurrent() -def test_load(basepath): +def test_load(odpath): m1 = NodeManager() - m1.OpenFileInCurrent(os.path.join(basepath, 'tests', 'od', 'master.od')) + m1.OpenFileInCurrent(odpath / 'master.od') diff --git a/tests/test_objdictgen.py b/tests/test_objdictgen.py index c6a4373..ff2b6df 100644 --- a/tests/test_objdictgen.py +++ b/tests/test_objdictgen.py @@ -1,4 +1,3 @@ -import os import objdictgen.__main__ @@ -16,13 +15,13 @@ def test_objdictgen(wd, mocker, odfile, fn): mocker.patch("sys.argv", [ "objdictgen", - odfile + '.od', + str(odfile + '.od'), od + '.c', ]) objdictgen.__main__.main_objdictgen() - if os.path.exists(odfile + '.c'): + if (odfile + '.c').exists(): 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) diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index 9a71a7d..3849f03 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -67,12 +67,13 @@ def test_load_compare(odfile, suffix): L(od) == L(od) ''' - if not os.path.exists(odfile + '.' + suffix): + fname = odfile + '.' + suffix + if not fname.exists(): pytest.skip("File not found") # Load the OD - m1 = Node.LoadFile(odfile + '.' + suffix) - m2 = Node.LoadFile(odfile + '.' + suffix) + m1 = Node.LoadFile(fname) + m2 = Node.LoadFile(fname) assert m1.__dict__ == m2.__dict__ @@ -166,7 +167,7 @@ def test_cexport(wd, odfile, fn): assert m0.__dict__ == m1.__dict__ # FIXME: If files doesn't exist, this leaves this test half-done. Better way? - if os.path.exists(odfile + '.c'): + if (odfile + '.c').exists(): 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) @@ -197,7 +198,7 @@ def predicate(line): return True # FIXME: If file doesn't exist, this leaves this test half-done. Better way? - if os.path.exists(odfile + '.eds'): + if (odfile + '.eds').exists(): assert fn.diff(odfile + '.eds', od + '.eds', predicate=predicate) @@ -255,8 +256,8 @@ def test_od_json_compare(odfile): L(od) == L(json) ''' - if not os.path.exists(odfile + '.json'): - raise pytest.skip("No .json file for '%s'" %(odfile + '.od')) + if not (odfile + '.json').exists(): + raise pytest.skip(f"No .json file for '{odfile + '.od'}'") m1 = Node.LoadFile(odfile + '.od') m2 = Node.LoadFile(odfile + '.json') @@ -294,41 +295,44 @@ def test_od_json_compare(odfile): @pytest.mark.parametrize("oddut", PROFILE_ODS) @pytest.mark.parametrize("suffix", ['od', 'json']) -def test_save_wo_profile(oddir, oddut, suffix, wd): +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',)) + 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): +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) + 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',)) assert a == b -@pytest.mark.parametrize("equivs", [ +EQUIVS = [ ('minimal.od', 'legacy-minimal.od'), ('minimal.json', 'legacy-minimal.od'), ('master.od', 'legacy-master.od'), @@ -347,16 +351,16 @@ def test_save_with_profile(oddir, oddut, suffix, wd, profile): #('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): +] + +@pytest.mark.parametrize("equivs", EQUIVS, ids=(e[0] for e in EQUIVS)) +def test_legacy_compare(odpath, 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) + m1 = Node.LoadFile(odpath / a) + m2 = Node.LoadFile(odpath / b) 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..21313ba 100644 --- a/tests/test_odg.py +++ b/tests/test_odg.py @@ -1,30 +1,29 @@ -import os import pytest from objdictgen.__main__ import main -@pytest.mark.parametrize("suffix", ['.od', '.json', '.eds']) +@pytest.mark.parametrize("suffix", ['od', 'json', 'eds']) def test_odg_list_woprofile(odfile, suffix): - fname = odfile + suffix - if not os.path.exists(fname): + fname = odfile + '.' + suffix + if not fname.exists(): pytest.skip("File not found") main(( 'list', - fname + str(fname) )) -@pytest.mark.parametrize("suffix", ['.od', '.json', '.eds']) +@pytest.mark.parametrize("suffix", ['od', 'json', 'eds']) def test_odg_list_wprofile(odfile, suffix, profile): - fname = odfile + suffix - if not os.path.exists(fname): + fname = odfile + '.' + suffix + if not fname.exists(): pytest.skip("File not found") main(( 'list', - fname + str(fname) )) From 031edfc40e82e6881f434dabbb5fff49da376f57 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Wed, 28 Feb 2024 01:33:56 +0100 Subject: [PATCH 09/28] Improvements on tests and several fixes * Refactored conftest.py. Add new fixtures * Refactored test_odcompare.py * Change to dynamically compare py2 with py3 runtime during testing. Remove legacy_compare. * Updated lots of ODs * Improved profile loading * Load profile from env var ODG_PROFILE_PATH and support multiple entries * Fix import ordering (with isort) * Added loading name, description and type from EDS * Change profile default in Node to "None" * Migrate to use pathlib.Path (more remains) * Add '*' Mult to evaluate_node, which is used by certain profiles. Updated tests * Cleanup nosis. Remove unused deepcopy * Fix missing number (float) in JSON schema * Minor cleanups --- pyproject.toml | 4 +- src/objdictgen/__init__.py | 24 +- src/objdictgen/__main__.py | 12 +- src/objdictgen/eds_utils.py | 62 +- src/objdictgen/gen_cfile.py | 21 +- src/objdictgen/jsonod.py | 11 +- src/objdictgen/node.py | 20 +- src/objdictgen/nodelist.py | 3 +- src/objdictgen/nodemanager.py | 3 +- src/objdictgen/nosis.py | 144 +- src/objdictgen/schema/od.schema.json | 3 + src/objdictgen/ui/commondialogs.py | 16 +- src/objdictgen/ui/exception.py | 3 +- src/objdictgen/ui/networkedit.py | 2 +- src/objdictgen/ui/nodeeditortemplate.py | 3 +- src/objdictgen/ui/objdictedit.py | 9 +- tests/conftest.py | 205 +- tests/od/README.md | 4 + tests/od/alltypes.json | 238 +- tests/od/alltypes.od | 206 +- tests/od/generate_json.py | 51 - tests/od/legacy-alltypes.od | 208 +- tests/od/legacy-compare/README.md | 4 - tests/od/legacy-compare/jsontest.c | 532 -- tests/od/legacy-compare/jsontest.eds | 908 ---- tests/od/legacy-compare/jsontest.h | 47 - .../legacy-compare/jsontest_objectdefines.h | 161 - tests/od/legacy-compare/master.c | 164 - tests/od/legacy-compare/master.eds | 121 - tests/od/legacy-compare/master.h | 16 - tests/od/legacy-compare/master.json | 89 - tests/od/legacy-compare/master.od | 38 - .../od/legacy-compare/master_objectdefines.h | 29 - tests/od/legacy-compare/minimal.c | 155 - tests/od/legacy-compare/minimal.eds | 112 - tests/od/legacy-compare/minimal.h | 16 - tests/od/legacy-compare/minimal.json | 73 - tests/od/legacy-compare/minimal.od | 34 - .../od/legacy-compare/minimal_objectdefines.h | 26 - tests/od/legacy-compare/slave.c | 569 --- tests/od/legacy-compare/slave.eds | 1207 ----- tests/od/legacy-compare/slave.h | 16 - tests/od/legacy-compare/slave_objectdefines.h | 138 - tests/od/legacy-master.od | 20 +- ...ds401.od => legacy-profile-ds302-ds401.od} | 730 +-- tests/od/legacy-profile-ds302-test.od | 1113 +++++ ...aster-ds302.od => legacy-profile-ds302.od} | 86 +- ...aster-ds401.od => legacy-profile-ds401.od} | 664 +-- tests/od/legacy-profile-test.od | 404 ++ tests/od/legacy-slave.od | 54 +- tests/od/master.json | 8 +- tests/od/master.od | 36 +- tests/od/profile-ds302-ds401.json | 2149 ++++++++ ...-ds302-ds401.od => profile-ds302-ds401.od} | 4332 ++++++++--------- .../jsontest.json => profile-ds302-test.json} | 1356 ++---- .../jsontest.od => profile-ds302-test.od} | 2936 +++-------- tests/od/profile-ds302.json | 278 ++ ...egacy-master-ds302.od => profile-ds302.od} | 412 +- tests/od/profile-ds401.json | 1960 ++++++++ .../od/{master-ds401.od => profile-ds401.od} | 3954 +++++++-------- tests/od/profile-test.json | 567 +++ tests/od/profile-test.od | 1374 ++++++ tests/od/slave-ds302.json | 1124 +++++ tests/od/slave-ds302.od | 747 +++ tests/od/slave-emcy.json | 952 ++++ tests/od/slave-emcy.od | 241 + .../slave.json => slave-heartbeat.json} | 23 +- .../slave.od => slave-heartbeat.od} | 68 +- tests/od/slave-nodeguarding.json | 967 ++++ tests/od/slave-nodeguarding.od | 245 + tests/od/slave-sync.json | 969 ++++ tests/od/slave-sync.od | 245 + tests/od/slave.json | 8 +- tests/od/slave.od | 70 +- tests/od/unicode.json | 77 +- tests/od/unicode.od | 73 +- tests/test_knownfailures.py | 6 +- tests/test_node.py | 6 +- tests/test_nodemanager.py | 12 +- tests/test_objdictgen.py | 54 +- tests/test_odcompare.py | 467 +- tests/test_odg.py | 18 +- 82 files changed, 20641 insertions(+), 13871 deletions(-) create mode 100644 tests/od/README.md delete mode 100644 tests/od/generate_json.py delete mode 100644 tests/od/legacy-compare/README.md delete mode 100644 tests/od/legacy-compare/jsontest.c delete mode 100644 tests/od/legacy-compare/jsontest.eds delete mode 100644 tests/od/legacy-compare/jsontest.h delete mode 100644 tests/od/legacy-compare/jsontest_objectdefines.h delete mode 100644 tests/od/legacy-compare/master.c delete mode 100644 tests/od/legacy-compare/master.eds delete mode 100644 tests/od/legacy-compare/master.h delete mode 100644 tests/od/legacy-compare/master.json delete mode 100644 tests/od/legacy-compare/master.od delete mode 100644 tests/od/legacy-compare/master_objectdefines.h delete mode 100644 tests/od/legacy-compare/minimal.c delete mode 100644 tests/od/legacy-compare/minimal.eds delete mode 100644 tests/od/legacy-compare/minimal.h delete mode 100644 tests/od/legacy-compare/minimal.json delete mode 100644 tests/od/legacy-compare/minimal.od delete mode 100644 tests/od/legacy-compare/minimal_objectdefines.h delete mode 100644 tests/od/legacy-compare/slave.c delete mode 100644 tests/od/legacy-compare/slave.eds delete mode 100644 tests/od/legacy-compare/slave.h delete mode 100644 tests/od/legacy-compare/slave_objectdefines.h rename tests/od/{master-ds302-ds401.od => legacy-profile-ds302-ds401.od} (91%) create mode 100644 tests/od/legacy-profile-ds302-test.od rename tests/od/{master-ds302.od => legacy-profile-ds302.od} (89%) rename tests/od/{legacy-master-ds401.od => legacy-profile-ds401.od} (91%) create mode 100644 tests/od/legacy-profile-test.od create mode 100644 tests/od/profile-ds302-ds401.json rename tests/od/{legacy-master-ds302-ds401.od => profile-ds302-ds401.od} (90%) rename tests/od/{legacy-compare/jsontest.json => profile-ds302-test.json} (50%) rename tests/od/{legacy-compare/jsontest.od => profile-ds302-test.od} (53%) create mode 100644 tests/od/profile-ds302.json rename tests/od/{legacy-master-ds302.od => profile-ds302.od} (88%) create mode 100644 tests/od/profile-ds401.json rename tests/od/{master-ds401.od => profile-ds401.od} (90%) create mode 100644 tests/od/profile-test.json create mode 100644 tests/od/profile-test.od create mode 100644 tests/od/slave-ds302.json create mode 100644 tests/od/slave-ds302.od create mode 100644 tests/od/slave-emcy.json create mode 100644 tests/od/slave-emcy.od rename tests/od/{legacy-compare/slave.json => slave-heartbeat.json} (98%) rename tests/od/{legacy-compare/slave.od => slave-heartbeat.od} (83%) create mode 100644 tests/od/slave-nodeguarding.json create mode 100644 tests/od/slave-nodeguarding.od create mode 100644 tests/od/slave-sync.json create mode 100644 tests/od/slave-sync.od diff --git a/pyproject.toml b/pyproject.toml index de6d3c1..dc294bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -383,9 +383,9 @@ confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFIN # --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 on first line +# 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", +"missing-function-docstring", "duplicate-code", "unspecified-encoding" ] # Enable the message, report, category or checker with the given id(s). You can diff --git a/src/objdictgen/__init__.py b/src/objdictgen/__init__.py index 94ae836..5769ee8 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -18,6 +18,7 @@ # USA import os +from pathlib import Path from objdictgen.node import Node from objdictgen.nodemanager import NodeManager @@ -30,14 +31,25 @@ ODG_PROGRAM = "odg" -SCRIPT_DIRECTORY = os.path.split(__file__)[0] +SCRIPT_DIRECTORY = Path(__file__).parent -PROFILE_DIRECTORIES = [os.path.join(SCRIPT_DIRECTORY, 'config')] -odgdir = os.environ.get('ODG_PROFILE_DIR') -if odgdir: - PROFILE_DIRECTORIES.append(odgdir) +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)) + +# 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__ = [ "LoadFile", diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index b8efbce..0d11ed9 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -311,7 +311,8 @@ def main(debugopts, args=None): 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) @@ -382,7 +383,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) @@ -390,7 +392,8 @@ 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) @@ -438,7 +441,8 @@ 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() diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 6cc814e..db463a8 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -18,13 +18,17 @@ # 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 +import logging from objdictgen import node as nodelib from objdictgen.maps import OD +log = logging.getLogger('objdictgen') + # Regular expression for finding index section names RE_INDEX = re.compile(r'([0-9A-F]{1,4}$)') # Regular expression for finding subindex section names @@ -467,6 +471,9 @@ def verify_value(values, section_name, param): def generate_eds_content(node, filepath): """Generate the EDS file content for the current node in the manager.""" + + filepath = Path(filepath) + # Dictionary of each index contents indexcontents = {} @@ -486,7 +493,7 @@ def generate_eds_content(node, filepath): # Generate FileInfo section fileContent = "[FileInfo]\n" - fileContent += f"FileName={os.path.split(filepath)[-1]}\n" + fileContent += f"FileName={filepath.name}\n" fileContent += "FileVersion=1\n" fileContent += "FileRevision=1\n" fileContent += "EDSVersion=4.0\n" @@ -680,6 +687,17 @@ def generate_node(filepath, nodeid=0): # Parse file and extract dictionary of EDS entry 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 = [f"0x{i:04X}" for i in ( 0x1000, @@ -689,8 +707,9 @@ def generate_node(filepath, nodeid=0): 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: @@ -700,9 +719,8 @@ def generate_node(filepath, nodeid=0): 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(): @@ -763,38 +781,6 @@ def generate_node(filepath, nodeid=0): "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( - # f"Error on entry 0x{entry:04X}: Subindex 0 must be defined for a RECORD 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( - # f"Error on entry 0x{entry:04X}: A RECORD entry must have at least 2 subindexes" - # ) - - # Define entry for the new node - # First case, entry is a DOMAIN or VAR if values["OBJECTTYPE"] in [2, 7]: # Take default value if it is defined diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 7f331ff..3085e81 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -18,8 +18,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -import os import re +from pathlib import Path from objdictgen.maps import OD @@ -117,7 +117,7 @@ def get_type_name(node, typenumber): return typename -def generate_file_content(node, headerfilepath, pointers_dict=None): +def generate_file_content(node, headerfile, pointers_dict=None): """ pointers_dict = {(Idx,Sidx):"VariableName",...} """ @@ -565,7 +565,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): # ------------------------------------------------------------------------------ fileContent = FILE_HEADER + f""" -#include "{headerfilepath}" +#include "{headerfile}" """ fileContent += """ @@ -664,7 +664,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): # Write Header File Content # ------------------------------------------------------------------------------ - texts["file_include_name"] = headerfilepath.replace(".", "_").upper() + texts["file_include_name"] = headerfile.replace(".", "_").upper() headerFileContent = FILE_HEADER + """ #ifndef %(file_include_name)s #define %(file_include_name)s @@ -685,7 +685,7 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): # ------------------------------------------------------------------------------ # Write Header Object Defintions Content # ------------------------------------------------------------------------------ - texts["file_include_objdef_name"] = headerfilepath.replace(".", "_OBJECTDEFINES_").upper() + texts["file_include_objdef_name"] = headerfile.replace(".", "_OBJECTDEFINES_").upper() headerObjectDefinitionContent = FILE_HEADER + """ #ifndef %(file_include_objdef_name)s #define %(file_include_objdef_name)s @@ -711,10 +711,11 @@ def generate_file_content(node, headerfilepath, pointers_dict=None): def generate_file(filepath, node, pointers_dict=None): """Main function to generate the C file from a object dictionary node.""" - filebase = os.path.splitext(filepath)[0] - headerfilepath = filebase + ".h" + filepath = Path(filepath) + headerpath = filepath.with_suffix(".h") + headerdefspath = Path(headerpath.parent / (headerpath.stem + "_objectdefines.h")) content, header, header_defs = generate_file_content( - node, os.path.basename(headerfilepath), pointers_dict, + node, headerpath.name, pointers_dict, ) # Write main .c contents @@ -722,9 +723,9 @@ def generate_file(filepath, node, pointers_dict=None): f.write(content.encode('utf-8')) # Write header file - with open(headerfilepath, "wb") as f: + with open(headerpath, "wb") as f: f.write(header.encode('utf-8')) # Write object definitions header - with open(filebase + "_objectdefines.h", "wb") as f: + with open(headerdefspath, "wb") as f: f.write(header_defs.encode('utf-8')) diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index c94916d..9acee61 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -19,7 +19,6 @@ import json import logging -import os import re from datetime import datetime @@ -27,7 +26,8 @@ import jsonschema import objdictgen -from objdictgen import maps, node as nodelib +from objdictgen import maps +from objdictgen import node as nodelib from objdictgen.maps import OD log = logging.getLogger('objdictgen') @@ -310,10 +310,7 @@ def compare_profile(profilename, params, menu=None): return True, identical except ValueError as exc: - log.debug("Loading profile failed: %s", 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 @@ -912,7 +909,7 @@ def node_fromdict(jd, internal=False): # 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 has been added to store the order of the parameters. node.IndexOrder = [obj["index"] for obj in jd['dictionary']] return node diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 0b79dfa..aa1a106 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -21,9 +21,9 @@ import ast import copy import logging -import os import re import traceback +from pathlib import Path import colorama @@ -53,8 +53,11 @@ class Node: DefaultStringSize = 10 - def __init__(self, name="", type="slave", id=0, description="", profilename="DS-301", - profile=None, specificmenu=None): # pylint: disable=redefined-builtin, invalid-name + def __init__( + self, name="", type="slave", id=0, description="", + profilename="None", profile=None, + specificmenu=None, + ): # pylint: disable=redefined-builtin self.Name = name self.Type = type self.ID = id @@ -1040,14 +1043,15 @@ def ImportProfile(profilename): # 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): + profilepath = Path(profilename) + if not profilepath.exists(): fname = f"{profilename}.prf" + try: profilepath = next( - os.path.join(base, fname) + base / fname for base in objdictgen.PROFILE_DIRECTORIES - if os.path.exists(os.path.join(base, fname)) + if (base / fname).exists() ) except StopIteration: raise ValueError( @@ -1129,6 +1133,8 @@ def evaluate_node(node: ast.AST): return Node.evaluate_node(node.left) + Node.evaluate_node(node.right) if isinstance(node.op, ast.Sub): return Node.evaluate_node(node.left) - Node.evaluate_node(node.right) + if isinstance(node.op, ast.Mult): + return Node.evaluate_node(node.left) * Node.evaluate_node(node.right) raise SyntaxError(f"Unhandled arithmatic operation {type(node.op)}") if isinstance(node, ast.Constant): if isinstance(node.value, int | float | complex): diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 1ecabcd..da6dc53 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -130,7 +130,8 @@ def LoadMasterNode(self, netname=None): index = self.Manager.OpenFileInCurrent(masterpath) else: index = self.Manager.CreateNewNode( - "MasterNode", 0x00, "master", "", "None", "", "Heartbeat", ["DS302"] + name="MasterNode", id=0x00, type="master", description="", + profile="None", filepath="", nmt="Heartbeat", options=["DS302"], ) return index diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index a3e67e3..d2d80c6 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -25,7 +25,8 @@ import colorama -from objdictgen import maps, node as nodelib +from objdictgen import maps +from objdictgen import node as nodelib from objdictgen.maps import OD log = logging.getLogger('objdictgen') diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index 0db16fc..20e7eb2 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -43,20 +43,22 @@ class _EmptyClass: str: 1, } -# 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 = f'({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 = f'({pat_flint})?[-+]{pat_flint}[jJ]' -re_complex = re.compile(pat_complex + r'$') -pat_complex2 = f'({pat_flint}):({pat_flint})' -re_complex2 = re.compile(pat_complex2 + r'$') +# 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): @@ -66,19 +68,19 @@ def aton(s): 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: @@ -87,7 +89,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: @@ -96,10 +98,10 @@ 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)) @@ -207,20 +209,9 @@ def _save_obj_with_id(node, obj): def add_class_to_store(classname='', klass=None): - """Put the class in the store (as 'classname'), return CLASS_STORE""" + """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(f"Cannot create class '{classname}'") def obj_from_node(node): @@ -232,7 +223,9 @@ def obj_from_node(node): # 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) + klass = CLASS_STORE.get(classname) + if klass is None: + raise ValueError(f"Cannot create class '{classname}'") return klass.__new__(klass) @@ -324,12 +317,10 @@ def StreamReader(stream): return stream -def xmldump(iohandle=None, obj=None, binary=0, deepcopy=None, omit=None): +def xmldump(iohandle=None, obj=None, binary=0, omit=None): """Create the XML representation as a string.""" - if deepcopy is None: - deepcopy = 0 return _pickle_toplevel_obj( - StreamWriter(iohandle, binary), obj, deepcopy, omit, + StreamWriter(iohandle, binary), obj, omit, ) @@ -341,15 +332,15 @@ def xmlload(filehandle): # -- support functions -def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): +def _pickle_toplevel_obj(xml_list, py_obj, omit=None): """handle the top object -- add XML header, etc.""" # 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 + id_ = id(py_obj) + VISITED = { + id_: py_obj + } # note -- setting family="obj" lets us know that a mutator was used on # the object. Otherwise, it's tricky to unpickle both @@ -376,14 +367,12 @@ def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): xml_list.append('\n' + '\n') - if deepcopy: - xml_list.append(f'\n') - elif id_ is not None: + if id_ is not None: xml_list.append(f'\n') else: xml_list.append(f'\n') - pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy, omit=omit) + pickle_instance(py_obj, xml_list, level=0, omit=omit) xml_list.append('\n') # returns None if xml_list is a fileobj, but caller should @@ -391,7 +380,7 @@ def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): return xml_list.getvalue() -def pickle_instance(obj, list_, level=0, deepcopy=0, omit=None): +def pickle_instance(obj, list_, level=0, omit=None): """Pickle the given object into a Add XML tags to list. Level is indentation (for aesthetic reasons) @@ -415,7 +404,7 @@ 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)) + list_.append(_attr_tag(key, val, level)) else: raise ValueError(f"'{obj}.__dict__' is not a dict") @@ -431,9 +420,6 @@ def unpickle_instance(node): # slurp raw thing into a an empty object raw = _thing_from_dom(node, _EmptyClass()) - - # code below has same ordering as pickle.py - stuff = raw.__dict__ # finally, decide how to get the stuff into pyobj @@ -450,40 +436,35 @@ def unpickle_instance(node): # --- Functions to create XML output tags --- -def _attr_tag(name, thing, level=0, deepcopy=0): - start_tag = ' ' * level + (f'\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, family_type, thing, extra=''): + """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 = f'{start_tag}{family_type} {extra}>\n' - return (start_tag, 1) - idt = id(thing) if VISITED.get(idt): start_tag = f'{start_tag}{family_type} refid="{idt}" />\n' @@ -567,7 +548,7 @@ def _fix_family(family, typename): raise ValueError(f"family= must be given for unknown type '{typename}'") -def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): +def _tag_completer(start_tag, orig_thing, close_tag, level): tag_body = [] mtag = None @@ -579,6 +560,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): ft = _family_type('none', 'None', None, None) start_tag = f"{start_tag}{ft} />\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): @@ -594,6 +576,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): else: start_tag = f'{start_tag}{ft} value="" />\n' close_tag = '' + elif isinstance(thing, (int, float, complex)): # thing_str = repr(thing) thing_str = ntoa(thing) @@ -609,6 +592,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): else: start_tag = f'{start_tag}{ft} value="{thing_str}" />\n' close_tag = '' + elif isinstance(thing, str): ft = _family_type("atom", "string", mtag, mextra) if in_body: @@ -617,6 +601,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): else: start_tag = f'{start_tag}{ft} value="{safe_string(thing)}" />\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 @@ -628,45 +613,44 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): elif isinstance(thing, tuple): start_tag, do_copy = _tag_compound( start_tag, _family_type('seq', 'tuple', mtag, mextra), - orig_thing, deepcopy) + 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) + 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) + 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(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 diff --git a/src/objdictgen/schema/od.schema.json b/src/objdictgen/schema/od.schema.json index 3abb0dc..3ed703b 100644 --- a/src/objdictgen/schema/od.schema.json +++ b/src/objdictgen/schema/od.schema.json @@ -404,6 +404,9 @@ }, { "type": "boolean" + }, + { + "type": "number" } ] } diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index da39193..762dbde 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -27,7 +27,8 @@ import objdictgen from objdictgen.maps import OD from objdictgen.node import Node -from objdictgen.ui.exception import display_error_dialog, display_exception_dialog +from objdictgen.ui.exception import (display_error_dialog, + display_exception_dialog) log = logging.getLogger('objdictgen') @@ -1102,15 +1103,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] = p + self.Profile.Append(p.stem) self.Profile.Append("Other") self.Profile.SetStringSelection("None") self.NodeName.SetFocus() diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index 29e6a14..b0c5bf4 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -90,7 +90,8 @@ def format_namespace(dic, indent=' '): 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 + 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) diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index b4dc8d1..37c5008 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -225,7 +225,7 @@ def __init__(self, parent, nodelist=None, projectOpen=None): # self.HtmlFrameOpened = [] icon = wx.Icon( - os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), + str(objdictgen.SCRIPT_DIRECTORY / "img" / "networkedit.ico"), wx.BITMAP_TYPE_ICO, ) self.SetIcon(icon) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 206c1df..9359ab0 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -22,7 +22,8 @@ from objdictgen.maps import OD from objdictgen.ui import commondialogs as cdia -from objdictgen.ui.exception import display_error_dialog, display_exception_dialog +from objdictgen.ui.exception import (display_error_dialog, + display_exception_dialog) class NodeEditorTemplate: diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index c5eea3c..74d1fc3 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -30,7 +30,8 @@ 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 add_except_hook, display_error_dialog, display_exception_dialog +from objdictgen.ui.exception import (add_except_hook, display_error_dialog, + display_exception_dialog) log = logging.getLogger('objdictgen') @@ -232,7 +233,7 @@ def __init__(self, parent, manager=None, filesopen=None): self._init_ctrls(parent) icon = wx.Icon( - os.path.join(objdictgen.SCRIPT_DIRECTORY, "img", "networkedit.ico"), + str(objdictgen.SCRIPT_DIRECTORY / "img" / "networkedit.ico"), wx.BITMAP_TYPE_ICO, ) self.SetIcon(icon) @@ -385,7 +386,6 @@ def RefreshBufferState(self): # ------------------------------------------------------------------------------ def OnNewMenu(self, event): # pylint: disable=unused-argument - # FIXME: Unused. Delete this? # self.FilePath = "" with cdia.CreateNodeDialog(self) as dialog: if dialog.ShowModal() != wx.ID_OK: @@ -397,7 +397,8 @@ def OnNewMenu(self, event): # pylint: disable=unused-argument try: index = self.Manager.CreateNewNode( - name, id_, nodetype, description, profile, filepath, nmt, options, + name=name, id=id_, type=nodetype, description=description, + profile=profile, filepath=filepath, nmt=nmt, options=options, ) new_editingpanel = sit.EditingPanel(self.FileOpened, self, self.Manager) new_editingpanel.SetIndex(index) diff --git a/tests/conftest.py b/tests/conftest.py index 8aaa380..6e97640 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ """ Pytest configuration file for the objdictgen package """ +import shutil from typing import Generator import os +import sys import difflib from dataclasses import dataclass import subprocess @@ -11,44 +13,74 @@ import objdictgen.node +# 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 = HERE / 'od' # Default OD test directories DEFAULT_ODTESTDIRS = [ + ODDIR, ODDIR / 'legacy-compare', ODDIR / 'extra-compare', ] +# Files to exclude from py2 legacy testing +PY2_OD_EXCLUDE = [ + ODDIR / "unicode.json", + ODDIR / "unicode.od", +] + class ODPath(type(Path())): """ Overload on Path to add OD specific methods """ + @classmethod + def nfactory(cls, iterable): + for p in iterable: + obj = cls(p.absolute()) + if p not in PY2_OD_EXCLUDE: + yield obj + def __add__(self, other): 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, **kw): + def diff(a, b, predicate=None, postprocess=None, **kw): """ Diff two files """ 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)) + 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(o.rstrip() for o in out)) + print('\n'.join(out)) + pytest.fail(f"Files {a} and {b} differ") return not out @@ -58,10 +90,10 @@ class Py2: py2: Path | None objdictgen: Path | None - def __post_init__(self): - print("\n ****POST****\n") + PIPE = subprocess.PIPE + STDOUT = subprocess.STDOUT - def run(self, script, **kwargs): + def run(self, script=None, *, cmd='-', **kwargs): if not self.py2: pytest.skip("--py2 configuration option not set") @@ -75,8 +107,12 @@ def run(self, script, **kwargs): env = os.environ.copy() env['PYTHONPATH'] = str(self.objdictgen) - indata = script.encode('ascii', 'backslashreplace') + if script is not None: + indata = script.encode('ascii', 'backslashreplace') + else: + indata = None + args = kwargs.pop('args', []) kw = { 'input': indata, 'env': env, @@ -84,7 +120,22 @@ def run(self, script, **kwargs): } kw.update(**kwargs) - return subprocess.check_output([self.py2, '-'], executable=self.py2, **kw) + 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): @@ -101,27 +152,54 @@ def pytest_addoption(parser): 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(DEFAULT_ODTESTDIRS) + # 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: + 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" + ) - # Make a list of all .od files in tests/od - odfiles = [] - for d in oddirs: - odfiles.extend( - ODPath(f.with_suffix('').absolute()) - for f in d.glob('*.od') - ) - - metafunc.parametrize("odfile", odfiles, - ids=[str(o.relative_to(ODDIR).as_posix()) for o in odfiles], - indirect=False + # 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 @@ -138,6 +216,12 @@ def pytest_generate_tests(metafunc): 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 # ======================================== @@ -148,22 +232,25 @@ def basepath(): """ Fixture returning the base of the project """ return (HERE / '..').resolve() + @pytest.fixture def fn(): """ Fixture providing a helper class for testing functions """ return Fn() -@pytest.fixture + +@pytest.fixture(scope="session") def odfile(request) -> Generator[ODPath, None, None]: """ Fixture for each of the od files in the test directory """ - print(type(request.param)) - yield request.param + return request.param + @pytest.fixture def odpath(): """ Fixture returning the path for the od test directory """ return ODPath(ODDIR.absolute()) + @pytest.fixture def profile(monkeypatch): """ Fixture that monkeypatches the profile load directory to include the OD directory @@ -173,12 +260,68 @@ def profile(monkeypatch): newdirs.extend(objdictgen.PROFILE_DIRECTORIES) newdirs.append(ODDIR) monkeypatch.setattr(objdictgen, 'PROFILE_DIRECTORIES', newdirs) - yield None + return None + @pytest.fixture def py2(request) -> Generator[Py2, None, None]: """ Fixture for each of the od files in the test directory """ - yield request.param + return request.param + + +@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()}") + + tmpod = odfile.stem + + shutil.copy(odfile, tmpod + '.od') + + pyapp = f""" +from nodemanager import * +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()}") + + tmpod = odfile.stem + + shutil.copy(odfile, tmpod + '.od') + + pyapp = f""" +from nodemanager import * +manager = NodeManager() +manager.OpenFileInCurrent(r'{tmpod}.od') +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 def wd(tmp_path): @@ -187,3 +330,13 @@ def wd(tmp_path): 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/generate_json.py b/tests/od/generate_json.py deleted file mode 100644 index d09e171..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(f"Reading {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-master.od b/tests/od/legacy-master.od index fcd1dfb..d1c6517 100644 --- a/tests/od/legacy-master.od +++ b/tests/od/legacy-master.od @@ -1,17 +1,17 @@ - - + + -Master generated with legacy objdictedit - +Empty master node + - + @@ -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.od b/tests/od/legacy-slave.od index e5cc258..fdd983a 100644 --- a/tests/od/legacy-slave.od +++ b/tests/od/legacy-slave.od @@ -1,17 +1,17 @@ - - + + -Slave generated with legacy objdictedit - +Slave created with legacy objdictedit + - + @@ -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/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..47264f4 --- /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 profile DS-302 and DS-401", + "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..6acdf32 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..fb29ab8 --- /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": "", + "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..60e3a1f --- /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..eafbda4 --- /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": "", + "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..e4e63ea --- /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 98% rename from tests/od/legacy-compare/slave.json rename to tests/od/slave-heartbeat.json index b3d22be..ce65f59 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": "", "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..7cbfec8 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..a17cf4e --- /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": "", + "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..002f225 --- /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..995d685 --- /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": "", + "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..030147b --- /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..ccc6552 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": "Default slave OD", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave.od b/tests/od/slave.od index bf9126d..c613b56 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/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_knownfailures.py b/tests/test_knownfailures.py index 7c33fbe..ba44f76 100644 --- a/tests/test_knownfailures.py +++ b/tests/test_knownfailures.py @@ -5,10 +5,10 @@ @pytest.mark.parametrize("suffix", ['od', 'json']) def test_edsfail_null(wd, odpath, suffix): - ''' EDS export of null.od fails because it contains no + """ EDS export of null.od fails because it contains no data. This is possibly a bug, or EDS does not support empty EDS. - ''' + """ fa = odpath / 'null' @@ -21,7 +21,7 @@ def test_edsfail_null(wd, odpath, suffix): @pytest.mark.parametrize("suffix", ['od', 'json']) def test_cexportfail_unicode(wd, odpath, suffix): - ''' C-export does not support UNICODE yet. ''' + """ C-export does not support UNICODE yet. """ fa = odpath / 'unicode' diff --git a/tests/test_node.py b/tests/test_node.py index a8ffaa0..947e5b0 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -2,6 +2,7 @@ from objdictgen.node import Node + def test_string_format(): assert Node.string_format('Additional Server SDO %d Parameter[(idx)]', 5, 0) == 'Additional Server SDO 5 Parameter' assert Node.string_format('Restore Manufacturer Defined Default Parameters %d[(sub - 3)]', 1, 5) == 'Restore Manufacturer Defined Default Parameters 2' @@ -10,7 +11,9 @@ def test_string_format(): assert Node.string_format('%s %.3f[(idx,sub)]', 1, 2) == '1 2.000' assert Node.string_format('%s %.3f[( idx , sub )]', 1, 2) == '1 2.000' - with pytest.raises(TypeError): + assert Node.string_format('This is a %s[(sub*8-7)]', 1, 2) == 'This is a 9' + + with pytest.raises(TypeError): Node.string_format('What are these %s[("tests")]', 0, 1) with pytest.raises(TypeError): @@ -27,6 +30,7 @@ def test_evaluate_expression(): assert Node.evaluate_expression('11') == 11 assert Node.evaluate_expression('4+3+2') == 9 assert Node.evaluate_expression('4+3-2') == 5 + assert Node.evaluate_expression('4*3') == 12 with pytest.raises(TypeError): Node.evaluate_expression('3-"tests"') diff --git a/tests/test_nodemanager.py b/tests/test_nodemanager.py index bee7135..d24d120 100644 --- a/tests/test_nodemanager.py +++ b/tests/test_nodemanager.py @@ -5,14 +5,22 @@ def test_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(): 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() diff --git a/tests/test_objdictgen.py b/tests/test_objdictgen.py index ff2b6df..7f5895e 100644 --- a/tests/test_objdictgen.py +++ b/tests/test_objdictgen.py @@ -1,27 +1,53 @@ +from pathlib import Path +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 -def test_objdictgen(wd, mocker, odfile, fn): - """ Test that objdictgen generates equal output as reference """ - od = odfile.name + mocker.patch("sys.argv", [ + "objdictgen", + str(od), + od.stem + '.c', + ]) + + objdictgen.__main__.main_objdictgen() + + +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 mocker.patch("sys.argv", [ "objdictgen", - str(odfile + '.od'), - od + '.c', + str(od), + tmpod + '.c', ]) objdictgen.__main__.main_objdictgen() - if (odfile + '.c').exists(): - 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 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 3849f03..4938616 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,279 +31,295 @@ 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) - - - -# 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 del_IndexOrder(obj): -# if hasattr(obj, 'IndexOrder'): -# delattr(obj, 'IndexOrder') + if preprocess: + preprocess(a, b) + return shave_dict(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) - ''' - fname = odfile + '.' + suffix - if not fname.exists(): - pytest.skip("File not found") +def test_load_compare(odjsoneds): + """ Tests that the file can be loaded twice without failure and no + difference. + """ + od = odjsoneds - # Load the OD - m1 = Node.LoadFile(fname) - m2 = Node.LoadFile(fname) + # Load the OD two times + m1 = Node.LoadFile(od) + m2 = Node.LoadFile(od) - assert m1.__dict__ == m2.__dict__ + a, b = shave_equal(m1, m2) + 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: + with open(od, 'r') as fi: + with open(f'{od.name}.orig', 'w') 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: + shutil.move(tmpod + '.od', tmpod + '.tmp') + with open(tmpod + '.tmp', 'r') as fi: + with open(tmpod + '.od', 'w') as fo: for line in fi: fo.write(RE_ID.sub('', line)) - os.remove(od + '.tmp') + 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__ - - m2 = Node.LoadFile(odfile + '.od') - - # To verify that the export doesn't clobber the object - equal = m1.__dict__ == m2.__dict__ + a, b = shave_equal(m0, m1) + assert a == b - # 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) - assert m1.__dict__ == m2.__dict__ +def test_cexport(odjsoneds, wd, fn): + """ Test that the file can be exported to c + """ + od = odjsoneds + tmpod = od.stem + m0 = Node.LoadFile(od) + m1 = Node.LoadFile(od) -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 + m1.DumpFile(tmpod + '.c', filetype='c') - m0 = Node.LoadFile(odfile + '.od') - m1 = Node.LoadFile(odfile + '.od') + # Assert that the object is unmodified by the export + a, b = shave_equal(m0, m1) + assert a == b - m1.DumpFile(od + '.c', filetype='c') - # Assert that the object is unmodified by the export - assert m0.__dict__ == m1.__dict__ +def test_cexport_py2_compare(py2_cfile, wd, fn): + """ Test that the exported c files match the ones generated by python2 + """ - # FIXME: If files doesn't exist, this leaves this test half-done. Better way? - if (odfile + '.c').exists(): - 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) + # Extract the path to the OD and the path to the python2 c file + od, py2od = py2_cfile + tmpod = od.stem + 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 (odfile + '.eds').exists(): - 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): + def _p(y): + if not isinstance(y, str) or '$NODEID' not in y: + return y + return '"' + y + '"' + return [_p(y) for y in x[1:]] + return x + a['Dictionary'] = { + i: num(m1.GetEntry(i, compute=False)) + for i in m1.GetIndexes() + } + + # EDS files doesn't store any profile info in a way that can be compared, + # so these fields must be ignored + a, b = shave_equal( + m1, m2, preprocess=accept_known_eds_limitation, + ignore=("IndexOrder", "Profile", "ProfileName", "DS302", "UserMapping", + "DefaultStringSize", "ParamsDictionary") + ) + 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 (odfile + '.json').exists(): - raise pytest.skip(f"No .json file for '{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(odpath, oddut, suffix, wd): - ''' Test that saving a od that contains a profile creates identical + """ 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 = odpath / oddut fb = oddut + '.' + suffix @@ -308,17 +329,20 @@ def test_save_wo_profile(odpath, oddut, suffix, wd): 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(odpath, oddut, suffix, wd, profile): - ''' Test that saving a od that contains a profile creates identical + """ Test that saving a od that contains a profile creates identical results as the original. This test have access to the profile dir - ''' + """ + + # FIXME: Does this work? The test succeeds even if the profile is missing fa = odpath / oddut fb = oddut + '.' + suffix @@ -328,39 +352,44 @@ def test_save_with_profile(odpath, oddut, suffix, wd, profile): 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 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'), + ('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"), ] + @pytest.mark.parametrize("equivs", EQUIVS, ids=(e[0] for e in EQUIVS)) -def test_legacy_compare(odpath, equivs): - ''' Test reading the od and compare it with the corresponding json file - ''' +@pytest.mark.parametrize("suffix", ['od', 'json']) +def test_legacy_compare(odpath, equivs, suffix): + """ Test reading the od and compare it with the corresponding json file + """ a, b = equivs - m1 = Node.LoadFile(odpath / a) - m2 = Node.LoadFile(odpath / b) + 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 21313ba..fb7aa0f 100644 --- a/tests/test_odg.py +++ b/tests/test_odg.py @@ -3,27 +3,21 @@ 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 fname.exists(): - pytest.skip("File not found") + od = odjsoneds main(( 'list', - str(fname) + 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 fname.exists(): - pytest.skip("File not found") + od = odjsoneds main(( 'list', - str(fname) + str(od) )) From adf86fd73a2e6440e73430127cface0f10303df2 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Tue, 5 Mar 2024 21:33:28 +0100 Subject: [PATCH 10/28] Stabilizing improvements * Fix encoding issues for OD files with unicode * Fix nosis quoting * Simplify stream handling in nosis. Add support for bytestream * Add --extra to pytest to test additional ODs * Add test fixture `py2_pickle` to read the verbatim OD data from py2 * Minor fixes --- TODO.md | 34 --- pyproject.toml | 2 +- src/objdictgen/eds_utils.py | 36 ++- src/objdictgen/gen_cfile.py | 12 +- src/objdictgen/jsonod.py | 2 +- src/objdictgen/node.py | 22 +- src/objdictgen/nodelist.py | 2 +- src/objdictgen/nosis.py | 158 +++------- src/objdictgen/ui/exception.py | 2 +- tests/conftest.py | 76 ++++- tests/od/legacy-strings.od | 535 +++++++++++++++++++++++++++++++++ tests/od/strings.json | 260 ++++++++++++++++ tests/test_objdictgen.py | 8 +- tests/test_odcompare.py | 37 ++- 14 files changed, 982 insertions(+), 204 deletions(-) delete mode 100644 TODO.md create mode 100644 tests/od/legacy-strings.od create mode 100644 tests/od/strings.json 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/pyproject.toml b/pyproject.toml index dc294bf..68dd439 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -385,7 +385,7 @@ confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFIN # --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", "unspecified-encoding" +"missing-function-docstring", "duplicate-code" ] # Enable the message, report, category or checker with the given id(s). You can diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index db463a8..33c5af0 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -148,7 +148,7 @@ def parse_cpj_file(filepath): networks = [] # Read file text - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: cpj_data = f.read() sections = extract_sections(cpj_data) @@ -283,7 +283,7 @@ def parse_eds_file(filepath): eds_dict = {} # Read file text - with open(filepath, 'r') as f: + with open(filepath, 'r', encoding="utf-8") as f: eds_file = f.read() sections = extract_sections(eds_file) @@ -491,6 +491,11 @@ def generate_eds_content(node, filepath): # 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") + # Generate FileInfo section fileContent = "[FileInfo]\n" fileContent += f"FileName={filepath.name}\n" @@ -610,19 +615,20 @@ def generate_eds_content(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 += 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[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 + 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[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 += f"SubNumber={nb_subentry}\n" # Write subindex definitions diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 3085e81..ad3ae08 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -719,13 +719,13 @@ def generate_file(filepath, node, pointers_dict=None): ) # 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(headerpath, "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(headerdefspath, "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/jsonod.py b/src/objdictgen/jsonod.py index 9acee61..f58f212 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -387,7 +387,7 @@ def generate_node(contents): # validator is better. global SCHEMA # pylint: disable=global-statement if not SCHEMA: - with open(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: diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index aa1a106..ec10a08 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -78,14 +78,14 @@ def __init__( @staticmethod def isXml(filepath): """Check if the file is an XML file""" - with open(filepath, 'r') as f: + with open(filepath, 'r', encoding="utf-8") as f: header = f.read(5) return header == " "Node": """ Open a file and create a new node """ if Node.isXml(filepath): log.debug("Loading XML OD '%s'", filepath) - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: return nosis.xmlload(f) # type: ignore if Node.isEds(filepath): @@ -102,7 +102,7 @@ def LoadFile(filepath: str) -> "Node": return eds_utils.generate_node(filepath) log.debug("Loading JSON OD '%s'", filepath) - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: return Node.LoadJson(f.read()) @staticmethod @@ -114,7 +114,7 @@ def DumpFile(self, filepath, filetype="json", **kwargs): """ Save node into file """ if filetype == 'od': log.debug("Writing XML OD '%s'", filepath) - with open(filepath, "w") as f: + with open(filepath, "w", encoding="utf-8") as f: # Never generate an od with IndexOrder in it nosis.xmldump(f, self, omit=('IndexOrder', )) return @@ -122,14 +122,14 @@ def DumpFile(self, filepath, filetype="json", **kwargs): if filetype == 'eds': log.debug("Writing EDS '%s'", filepath) content = eds_utils.generate_eds_content(self, filepath) - with open(filepath, "w") as f: + with open(filepath, "w", encoding="utf-8") as f: f.write(content) return if filetype == 'json': 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 @@ -849,7 +849,7 @@ def _warn(text): # 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") @@ -894,7 +894,7 @@ def _warn(text): 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(f"Sub index {idx}: Missing name") @@ -1062,7 +1062,7 @@ def ImportProfile(profilename): # The profiles requires some vars to be set # pylint: disable=unused-variable try: - with open(profilepath, "r") as f: + 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 @@ -1074,7 +1074,7 @@ def ImportProfile(profilename): raise ValueError(f"Loading profile '{profilepath}' failed: {exc}") from exc # -------------------------------------------------------------------------- - # Utils + # Evaluation of values # -------------------------------------------------------------------------- @staticmethod diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index da6dc53..55306fb 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -176,7 +176,7 @@ def SaveNodeList(self, netname=None): 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 diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index 20e7eb2..7e2742e 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -23,10 +23,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import ast import logging import re import sys -from io import StringIO +import io from xml.dom import minidom log = logging.getLogger('objdictgen.nosis') @@ -40,7 +41,7 @@ class _EmptyClass: int: 0, float: 0, complex: 0, - str: 1, + str: 0, } # Regexp patterns @@ -140,7 +141,6 @@ def ntoa(num: int|float|complex) -> str: def safe_string(s): """Quote XML entries""" - for repl in XML_QUOTES: s = s.replace(repl[0], repl[1]) # for others, use Python style escapes @@ -148,15 +148,17 @@ def safe_string(s): 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 + """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]) + + s = s.replace("'", "\\x27") # Need this to not interfere with ast + + tree = ast.parse("'" + s + "'", mode='eval') + if isinstance(tree.body, ast.Constant): + return tree.body.s + raise ValueError(f"Invalid string '{s}' passed to unsafe_string()") def safe_content(s): @@ -166,22 +168,13 @@ def safe_content(s): s = s.replace(repl[0], repl[1]) return s - # # wrap "regular" python strings as unicode - # if isinstance(s, str): - # s = u"\xbb\xbb%s\xab\xab" % s - - # return s.encode('utf-8') - 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) - - # # 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') - + # Unqoute XML entries + for repl in XML_QUOTES: + s = s.replace(repl[1], repl[0]) return s @@ -193,13 +186,12 @@ def unsafe_content(s): # entry point expected by XML_Pickle def thing_from_dom(filehandle): global VISITED # pylint: disable=global-statement - VISITED = {} + VISITED = {} # Reset the visited collection return _thing_from_dom(minidom.parse(filehandle), None) def _save_obj_with_id(node, obj): objid = node.getAttribute('id') - if len(objid): # might be None, or empty - shouldn't use as key VISITED[objid] = obj @@ -248,91 +240,32 @@ def get_node_valuetext(node): 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, omit=None): +def xmldump(filehandle=None, obj=None, omit=None): """Create the XML representation as a string.""" - return _pickle_toplevel_obj( - StreamWriter(iohandle, binary), obj, omit, - ) + + stream = filehandle + if filehandle is None: + stream = io.StringIO() + + _pickle_toplevel_obj(stream, obj, omit) + + if filehandle is None: + stream.flush() + return stream.getvalue() def xmlload(filehandle): """Load pickled object from file fh.""" - return thing_from_dom(StreamReader(filehandle)) + if isinstance(filehandle, str): + filehandle = io.StringIO(filehandle) + elif isinstance(filehandle, bytes): + filehandle = io.BytesIO(filehandle) -# -- support functions + return thing_from_dom(filehandle) -def _pickle_toplevel_obj(xml_list, py_obj, omit=None): +def _pickle_toplevel_obj(fh, py_obj, omit=None): """handle the top object -- add XML header, etc.""" # Store the ref id to the pickling object (if not deepcopying) @@ -364,23 +297,19 @@ def _pickle_toplevel_obj(xml_list, py_obj, omit=None): # else: # extra = f'{famtype} class="{klass_tag}"' - xml_list.append('\n' - + '\n') + fh.write('\n' + '\n') if id_ is not None: - xml_list.append(f'\n') + fh.write(f'\n') else: - xml_list.append(f'\n') - - pickle_instance(py_obj, xml_list, level=0, omit=omit) - xml_list.append('\n') + fh.write(f'\n') - # returns None if xml_list is a fileobj, but caller should - # know that (or not care) - return xml_list.getvalue() + pickle_instance(py_obj, fh, level=0, omit=omit) + fh.write('\n') -def pickle_instance(obj, list_, level=0, omit=None): +def pickle_instance(obj, fh, level=0, omit=None): """Pickle the given object into a Add XML tags to list. Level is indentation (for aesthetic reasons) @@ -404,7 +333,7 @@ def pickle_instance(obj, list_, level=0, omit=None): for key, val in stuff.items(): if omit and key in omit: continue - list_.append(_attr_tag(key, val, level)) + fh.write(_attr_tag(key, val, level)) else: raise ValueError(f"'{obj}.__dict__' is not a dict") @@ -731,7 +660,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 diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index b0c5bf4..3f645bb 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -125,7 +125,7 @@ def handle_exception(e_type, e_value, e_traceback, parent=None): 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') as fp: + 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") diff --git a/tests/conftest.py b/tests/conftest.py index 6e97640..ecc606d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ from dataclasses import dataclass import subprocess from pathlib import Path +import pickle import pytest import objdictgen @@ -23,10 +24,8 @@ ODDIR = HERE / 'od' # Default OD test directories -DEFAULT_ODTESTDIRS = [ +ODTESTDIRS = [ ODDIR, - ODDIR / 'legacy-compare', - ODDIR / 'extra-compare', ] # Files to exclude from py2 legacy testing @@ -35,16 +34,18 @@ ODDIR / "unicode.od", ] +# Files to exclude in EDS testing +PY2_EDS_EXCLUDE = [ + ODDIR / "legacy-strings.od", # The legacy tool silently crash on this input +] + class ODPath(type(Path())): """ Overload on Path to add OD specific methods """ @classmethod def nfactory(cls, iterable): - for p in iterable: - obj = cls(p.absolute()) - if p not in PY2_OD_EXCLUDE: - yield obj + return [cls(p.absolute()) for p in iterable] def __add__(self, other): return ODPath(self.parent / (self.name + other)) @@ -71,9 +72,9 @@ def diff(a, b, predicate=None, postprocess=None, **kw): """ Diff two files """ if predicate is None: predicate = lambda x: True - with open(a, 'r') as f: + with open(a, 'r', encoding="utf-8") as f: da = [n.rstrip() for n in f if predicate(n)] - with open(b, 'r') as f: + 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: @@ -149,6 +150,9 @@ def pytest_addoption(parser): 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)}" + ) def pytest_generate_tests(metafunc): @@ -157,7 +161,12 @@ def pytest_generate_tests(metafunc): # Collect the list of od test directories oddirs = metafunc.config.getoption("oddir") if not oddirs: - oddirs = list(DEFAULT_ODTESTDIRS) + 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: @@ -276,12 +285,18 @@ def py2_cfile(odfile, py2, wd_session): if not odfile.exists(): pytest.skip(f"File not found: {odfile.rel_to_wd()}") + 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') @@ -303,14 +318,22 @@ def py2_edsfile(odfile, py2, wd_session): 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) @@ -323,6 +346,39 @@ def py2_edsfile(odfile, py2, wd_session): 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 wd(tmp_path): """ Fixture that changes the working directory to a temp location """ 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/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/test_objdictgen.py b/tests/test_objdictgen.py index 7f5895e..9876855 100644 --- a/tests/test_objdictgen.py +++ b/tests/test_objdictgen.py @@ -1,5 +1,4 @@ -from pathlib import Path import pytest import objdictgen.__main__ @@ -8,6 +7,10 @@ def test_objdictgen_run(odjsoneds, mocker, wd): """Test that we're able to run objdictgen on our od files""" od = odjsoneds + tmpod = od.stem + + if tmpod in ('legacy-strings', 'strings', 'unicode'): + pytest.xfail("UNICODE_STRINGS is not supported in C") mocker.patch("sys.argv", [ "objdictgen", @@ -25,6 +28,9 @@ def test_objdictgen_py2_compare(py2_cfile, mocker, wd, fn): 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), diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index 4938616..ea597cf 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -51,6 +51,20 @@ def test_load_compare(odjsoneds): assert a == b +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(od) + + a, b = shave_dict(py2data, m1.__dict__) + assert a == b + + 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. @@ -71,16 +85,16 @@ def test_odexport(odjsoneds, wd, fn): # 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(od, 'r') as fi: - with open(f'{od.name}.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)) + fo.write(re_id.sub(b'', line)) shutil.move(tmpod + '.od', tmpod + '.tmp') - with open(tmpod + '.tmp', 'r') as fi: - with open(tmpod + '.od', 'w') as fo: + with open(tmpod + '.tmp', 'rb') as fi: + with open(tmpod + '.od', 'wb') as fo: for line in fi: - fo.write(RE_ID.sub('', line)) + fo.write(re_id.sub(b'', line)) os.remove(tmpod + '.tmp') # Load the saved OD @@ -114,6 +128,9 @@ def test_cexport(odjsoneds, wd, fn): od = odjsoneds tmpod = od.stem + if tmpod in ('strings', 'legacy-strings', 'unicode'): + pytest.xfail("UNICODE_STRINGS is not supported in C") + m0 = Node.LoadFile(od) m1 = Node.LoadFile(od) @@ -132,6 +149,9 @@ def test_cexport_py2_compare(py2_cfile, wd, fn): od, py2od = py2_cfile tmpod = od.stem + if tmpod in ('strings', 'legacy-strings'): + pytest.xfail("UNICODE_STRINGS is not supported in C") + m0 = Node.LoadFile(od) m0.DumpFile(tmpod + '.c', filetype='c') @@ -372,12 +392,13 @@ def test_save_with_profile(odpath, oddut, suffix, wd, profile): ( "slave-heartbeat", "legacy-slave-heartbeat"), ( "slave-nodeguarding", "legacy-slave-nodeguarding"), ( "slave-sync", "legacy-slave-sync"), + ( "strings", "legacy-strings"), ] @pytest.mark.parametrize("equivs", EQUIVS, ids=(e[0] for e in EQUIVS)) @pytest.mark.parametrize("suffix", ['od', 'json']) -def test_legacy_compare(odpath, equivs, suffix): +def test_equiv_compare(odpath, equivs, suffix): """ Test reading the od and compare it with the corresponding json file """ a, b = equivs From 75950f3bf9efc8d85468fb7da3ec5341003004c6 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Tue, 5 Mar 2024 23:35:50 +0100 Subject: [PATCH 11/28] Improve expression evaluations * Rename `Node.string_format()` to `Node.eval_name()` * Rename `Node.CompileValue` to `Node.eval_value()` * Add more py syntax support in `Node.evaulate_expression` --- src/objdictgen/node.py | 217 +++++++++++++++++++++++--------------- tests/test_node.py | 127 +++++++++++++++++----- tests/test_nodemanager.py | 6 +- tests/test_nosis.py | 162 +++++++++++++++++----------- tests/test_odcompare.py | 20 ++-- 5 files changed, 347 insertions(+), 185 deletions(-) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index ec10a08..b215501 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -36,9 +36,13 @@ 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'(.*)\[[(](.*)[)]\]') +# 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) # ------------------------------------------------------------------------------ @@ -266,13 +270,15 @@ def GetEntry(self, index, subindex=None, compute=True, aslist=False): """ if index not in self.Dictionary: raise KeyError(f"Index 0x{index:04x} does not exist") + base = self.GetBaseIndexNumber(index) + nodeid = self.ID if subindex is None: if isinstance(self.Dictionary[index], list): return [len(self.Dictionary[index])] + [ - self.CompileValue(value, index, compute) + self.eval_value(value, base, nodeid, compute) for value in self.Dictionary[index] ] - result = self.CompileValue(self.Dictionary[index], index, compute) + result = self.eval_value(self.Dictionary[index], base, nodeid, compute) # This option ensures that the function consistently returns a list if aslist: return [result] @@ -280,9 +286,9 @@ def GetEntry(self, index, subindex=None, compute=True, aslist=False): if subindex == 0: if isinstance(self.Dictionary[index], list): return len(self.Dictionary[index]) - return self.CompileValue(self.Dictionary[index], index, compute) + return self.eval_value(self.Dictionary[index], base, nodeid, compute) if isinstance(self.Dictionary[index], list) and 0 < subindex <= len(self.Dictionary[index]): - return self.CompileValue(self.Dictionary[index][subindex - 1], index, compute) + return self.eval_value(self.Dictionary[index][subindex - 1], base, nodeid, compute) raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") def GetParamsEntry(self, index, subindex=None, aslist=False): @@ -508,27 +514,6 @@ def GetIndexes(self): """ return list(sorted(self.Dictionary)) - 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(f"CompileValue failed for '{value}'") from exc - else: - return value # -------------------------------------------------------------------------- # Node Informations Functions @@ -1078,71 +1063,125 @@ def ImportProfile(profilename): # -------------------------------------------------------------------------- @staticmethod - def string_format(text, idx, sub): # pylint: disable=unused-argument + def eval_value(value, base, nodeid, compute=True): """ - Format the text given with the index and subindex defined + 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]'". """ - result = RE_NAME.match(text) - if result: - fmt = result.groups() - try: - args = fmt[1].split(',') - args = [a.replace('idx', str(idx)) for a in args] - args = [a.replace('sub', str(sub)) for a in args] - - # NOTE: Legacy Python2 type evaluations are baked into the OD - # and cannot be removed currently - if len(args) == 1: - return fmt[0] % (Node.evaluate_expression(args[0].strip())) - if len(args) == 2: - return fmt[0] % ( - Node.evaluate_expression(args[0].strip()), - Node.evaluate_expression(args[1].strip()), - ) - return fmt[0] - except Exception as exc: - log.debug("PARSING FAILED: %s", exc) - raise - else: + # 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 = Node.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 = Node.evaluate_expression(value, + { # These are the vars that can be used within the string + 'nodeid': nodeid, + } + ) + + return value + + @staticmethod + def eval_name(text, idx, sub): + """ + 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.group(1) % Node.evaluate_expression( + result.group(2).strip(), + { # These are the vars that can be used in the string + 'idx': idx, + 'sub': sub, + } + ) + @staticmethod - def evaluate_expression(expression: str): + def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): """Parses a string expression and attempts to calculate the result Supports: - - Addition (i.e. "3+4") - - Subraction (i.e. "7-4") - - Constants (i.e. "5") - This function will handle chained arithmatic i.e. "1+2+3" although - operating order is not neccesarily preserved - + - 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 """ - tree = ast.parse(expression, mode="eval") - return Node.evaluate_node(tree.body) + localvars = localvars or {} + + def _evnode(node: ast.AST): + """ + 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(f"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)}") - @staticmethod - def evaluate_node(node: ast.AST): - """ - Recursively parses ast.Node objects to evaluate arithmatic expressions - """ - if isinstance(node, ast.BinOp): - if isinstance(node.op, ast.Add): - return Node.evaluate_node(node.left) + Node.evaluate_node(node.right) - if isinstance(node.op, ast.Sub): - return Node.evaluate_node(node.left) - Node.evaluate_node(node.right) - if isinstance(node.op, ast.Mult): - return Node.evaluate_node(node.left) * Node.evaluate_node(node.right) - raise SyntaxError(f"Unhandled arithmatic operation {type(node.op)}") - if isinstance(node, ast.Constant): - if isinstance(node.value, int | float | complex): - return node.value - raise TypeError(f"Cannot parse str type constant '{node.value}'") - if isinstance(node, ast.AST): - raise TypeError(f"Unhandled ast node class {type(node)}") - raise TypeError(f"Invalid argument 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 @staticmethod def get_index_range(index): @@ -1244,7 +1283,9 @@ def FindEntryName(index, mappingdictionary, compute=True): if base_index: infos = mappingdictionary[base_index] if infos["struct"] & OD.IdenticalIndexes and compute: - return Node.string_format(infos["name"], (index - base_index) // infos["incr"] + 1, 0) + return Node.eval_name( + infos["name"], idx=(index - base_index) // infos["incr"] + 1, sub=0 + ) return infos["name"] return None @@ -1257,7 +1298,9 @@ def FindEntryInfos(index, mappingdictionary, compute=True): if base_index: obj = mappingdictionary[base_index].copy() if obj["struct"] & OD.IdenticalIndexes and compute: - obj["name"] = Node.string_format(obj["name"], (index - base_index) // obj["incr"] + 1, 0) + obj["name"] = Node.eval_name( + obj["name"], idx=(index - base_index) // obj["incr"] + 1, sub=0 + ) obj.pop("values") return obj return None @@ -1298,7 +1341,9 @@ def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): incr = mappingdictionary[base_index]["incr"] else: incr = 1 - infos["name"] = Node.string_format(infos["name"], (index - base_index) // incr + 1, subindex) + infos["name"] = Node.eval_name( + infos["name"], idx=(index - base_index) // incr + 1, sub=subindex + ) return infos return None @@ -1319,12 +1364,12 @@ def FindMapVariableList(mappingdictionary, node, compute=True): for i in range(len(values) - 1): computed_name = name if compute: - computed_name = Node.string_format(computed_name, 1, i + 1) + computed_name = Node.eval_name(computed_name, idx=1, sub=i + 1) yield (index, i + 1, infos["size"], computed_name) else: computed_name = name if compute: - computed_name = Node.string_format(computed_name, 1, subindex) + computed_name = Node.eval_name(computed_name, idx=1, sub=subindex) yield (index, subindex, infos["size"], computed_name) @staticmethod diff --git a/tests/test_node.py b/tests/test_node.py index 947e5b0..3432599 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -3,49 +3,128 @@ from objdictgen.node import Node -def test_string_format(): - assert Node.string_format('Additional Server SDO %d Parameter[(idx)]', 5, 0) == 'Additional Server SDO 5 Parameter' - assert Node.string_format('Restore Manufacturer Defined Default Parameters %d[(sub - 3)]', 1, 5) == 'Restore Manufacturer Defined Default Parameters 2' - assert Node.string_format('This doesn\'t match the regex', 1, 2) == 'This doesn\'t match the regex' +def test_node_eval_value(): - assert Node.string_format('%s %.3f[(idx,sub)]', 1, 2) == '1 2.000' - assert Node.string_format('%s %.3f[( idx , sub )]', 1, 2) == '1 2.000' + dut = Node.eval_value - assert Node.string_format('This is a %s[(sub*8-7)]', 1, 2) == 'This is a 9' + assert dut("1234", 0, 0, True) == "1234" + assert dut("'$NODEID+1'", 0, 0, False) == "$NODEID+1" + assert dut("'$NODEID+1'", 0, 1000, True) == 1001 - with pytest.raises(TypeError): - Node.string_format('What are these %s[("tests")]', 0, 1) + 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 = Node.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): - Node.string_format('There is nothing to format[(idx, sub)]', 1, 2) + dut("There is nothing to format[(idx, sub)]", 1, 2) with pytest.raises(Exception): - Node.string_format('Unhandled arithmatic[(idx*sub)]', 2, 4) + dut("Unhandled arithmatic[(idx*sub)]", 2, 4) -def test_evaluate_expression(): +def test_node_evaluate_expression(): + + dut = Node.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 Node.evaluate_expression('4+3') == 7 - assert Node.evaluate_expression('4-3') == 1 - assert Node.evaluate_expression('11') == 11 - assert Node.evaluate_expression('4+3+2') == 9 - assert Node.evaluate_expression('4+3-2') == 5 - assert Node.evaluate_expression('4*3') == 12 + 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): - Node.evaluate_expression('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): - Node.evaluate_expression('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): - Node.evaluate_expression('not 5') + dut("{None: 1}") + # List with pytest.raises(TypeError): - Node.evaluate_expression('str') + dut("[]") + # UnaryOp with pytest.raises(TypeError): - Node.evaluate_expression('"str"') + dut("not 5") + # General with pytest.raises(SyntaxError): - Node.evaluate_expression('$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_nodemanager.py b/tests/test_nodemanager.py index d24d120..a5f999c 100644 --- a/tests/test_nodemanager.py +++ b/tests/test_nodemanager.py @@ -2,7 +2,7 @@ from objdictgen.nodemanager import NodeManager -def test_create_master(): +def test_nodemanager_create_master(): m1 = NodeManager() m1.CreateNewNode( @@ -13,7 +13,7 @@ def test_create_master(): m1.CloseCurrent() -def test_create_slave(): +def test_nodemanager_create_slave(): m1 = NodeManager() m1.CreateNewNode( @@ -24,7 +24,7 @@ def test_create_slave(): m1.CloseCurrent() -def test_load(odpath): +def test_nodemanager_load(odpath): m1 = NodeManager() 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_odcompare.py b/tests/test_odcompare.py index ea597cf..daf782f 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -244,23 +244,25 @@ def accept_known_eds_limitation(a, b): """ def num(x): if isinstance(x, list): - def _p(y): - if not isinstance(y, str) or '$NODEID' not in y: - return y - return '"' + y + '"' - return [_p(y) for y in x[1:]] + return x[1:] return x a['Dictionary'] = { i: num(m1.GetEntry(i, compute=False)) for i in m1.GetIndexes() } + b['Dictionary'] = { + i: num(m2.GetEntry(i, compute=False)) + for i in m2.GetIndexes() + } - # EDS files doesn't store any profile info in a way that can be compared, - # so these fields must be ignored + # 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", - "DefaultStringSize", "ParamsDictionary") + ignore=( + "IndexOrder", "Profile", "ProfileName", "DS302", "UserMapping", + "ParamsDictionary", "DefaultStringSize" + ) ) assert a == b From c8bab0f6d76bb01a107ae6e76fd4043a41bfa6e3 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 31 Mar 2024 14:25:03 +0200 Subject: [PATCH 12/28] Packaging improvements * Fix bugs in JSON OD * Remove wheel as requirement for GH actions * Move pytest and mypy config to pyproject.toml * Remove flake8 --- .github/workflows/python-package.yml | 2 +- pyproject.toml | 54 +++++++++++++++++++++++++--- setup.cfg | 11 ------ src/objdictgen/schema/od.schema.json | 15 +++----- 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e632fee..d7a1eb6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -27,7 +27,7 @@ jobs: sudo apt install libgtk-3-dev - name: Install dependencies run: | - python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip setuptools python -m pip install .[test,dist] - name: Test with pytest run: | diff --git a/pyproject.toml b/pyproject.toml index 68dd439..17398b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,53 @@ 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 --cov=objdictgen" +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 @@ -85,6 +129,7 @@ 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 @@ -392,7 +437,8 @@ disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file- # 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 = +# objdictgen: orig: # enable = +enable = ["no-self-use"] [tool.pylint.method_args] # List of qualified names (i.e., library.method) which require a timeout diff --git a/setup.cfg b/setup.cfg index 38d17fa..db63f72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,6 @@ dist = build dev = pylint - flake8 isort mypy types-setuptools @@ -68,13 +67,3 @@ console_scripts = objdictgen = objdictgen.__main__:main_objdictgen objdictedit = objdictgen.__main__:main_objdictedit -[flake8] -max-line-length = 120 -ignore = E128, W503, E303 - -[tool:pytest] -addopts = -l --tb=native --cov=objdictgen -testpaths = - tests -filterwarnings = - ignore::DeprecationWarning diff --git a/src/objdictgen/schema/od.schema.json b/src/objdictgen/schema/od.schema.json index 3ed703b..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", From 7f640b3176b1fc79736c17e9f0470b2d7679b67a Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 31 Mar 2024 16:53:12 +0200 Subject: [PATCH 13/28] Refactor nosis, the XML parser * Add type hints * Remove methods and merge functionality into existing * Remove the unused "extra"and "family" feature --- src/objdictgen/nosis.py | 454 ++++++++++++++++------------------------ 1 file changed, 177 insertions(+), 277 deletions(-) diff --git a/src/objdictgen/nosis.py b/src/objdictgen/nosis.py index 7e2742e..5f597e9 100644 --- a/src/objdictgen/nosis.py +++ b/src/objdictgen/nosis.py @@ -24,12 +24,19 @@ # USA import ast +import io import logging import re import sys -import io +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') @@ -37,11 +44,55 @@ 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: 0, + 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', } # Regexp patterns @@ -62,7 +113,8 @@ class _EmptyClass: RE_COMPLEX2 = re.compile(PAT_COMPLEX2 + r'$') -def aton(s): +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 @@ -139,140 +191,73 @@ def ntoa(num: int|float|complex) -> str: ) -def safe_string(s): +def safe_string(s: str, isattr: bool = True) -> str: """Quote XML entries""" for repl in XML_QUOTES: s = s.replace(repl[0], repl[1]) - # for others, use Python style escapes - return repr(s)[1:-1] # without the extra single-quotes + if isattr: + # for others, use Python style escapes + return repr(s)[1:-1] # without the extra single-quotes -def unsafe_string(s): + return s + + +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]) - s = s.replace("'", "\\x27") # Need this to not interfere with ast + if isattr: + s = s.replace("'", "\\x27") # Need this to not interfere with ast - tree = ast.parse("'" + s + "'", mode='eval') - if isinstance(tree.body, ast.Constant): + 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 - raise ValueError(f"Invalid string '{s}' passed to unsafe_string()") - - -def safe_content(s): - """Markup XML entities and strings so they're XML & unicode-safe""" - # Quote XML entries - for repl in XML_QUOTES: - s = s.replace(repl[0], repl[1]) - return s - -def unsafe_content(s): - """Take the string returned by safe_content() and recreate the - original string.""" - # Unqoute XML entries - for repl in XML_QUOTES: - s = s.replace(repl[1], repl[0]) 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 = {} # Reset the visited collection - 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): +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 -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 = CLASS_STORE.get(classname) - if klass is None: - raise ValueError(f"Cannot create class '{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: # pylint: disable=protected-access - # 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 '' - - -def xmldump(filehandle=None, obj=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.""" - stream = filehandle + fh: io.TextIOWrapper if filehandle is None: - stream = io.StringIO() - - _pickle_toplevel_obj(stream, obj, omit) - - if filehandle is None: - stream.flush() - return stream.getvalue() - - -def xmlload(filehandle): - """Load pickled object from file fh.""" - - if isinstance(filehandle, str): - filehandle = io.StringIO(filehandle) - elif isinstance(filehandle, bytes): - filehandle = io.BytesIO(filehandle) - - return thing_from_dom(filehandle) - + fh = sio = io.StringIO() + else: + fh = filehandle -def _pickle_toplevel_obj(fh, py_obj, 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 - id_ = id(py_obj) + objid = id(py_obj) VISITED = { - id_: py_obj + objid: py_obj } # note -- setting family="obj" lets us know that a mutator was used on @@ -285,45 +270,21 @@ def _pickle_toplevel_obj(fh, py_obj, 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 = f'{famtype}module="{module}" class="{klass_tag}"' - # else: - # extra = f'{famtype} class="{klass_tag}"' - fh.write('\n' - '\n') + id_tag = f' id="{objid}"' if objid is not None else "" - if id_ is not None: - fh.write(f'\n') - else: - fh.write(f'\n') - - pickle_instance(py_obj, fh, level=0, omit=omit) - fh.write('\n') + fh.write(f""" + + +""") - -def pickle_instance(obj, fh, level=0, omit=None): - """Pickle the given object into a - - 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 @@ -333,39 +294,34 @@ def pickle_instance(obj, fh, level=0, omit=None): for key, val in stuff.items(): if omit and key in omit: continue - fh.write(_attr_tag(key, val, level)) + fh.write(_attr_tag(key, val, 0)) else: - raise ValueError(f"'{obj}.__dict__' is not a dict") + raise ValueError(f"'{py_obj}.__dict__' is not a dict") + fh.write('\n') -def unpickle_instance(node): - """Take a or <.. type="PyObject"> DOM node and unpickle - the object.""" + if filehandle is None: + sio.flush() + return sio.getvalue() + return None - # 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) - # slurp raw thing into a an empty object - raw = _thing_from_dom(node, _EmptyClass()) - 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): +def _attr_tag(name: str, thing, level=0): start_tag = ' ' * level + f'\n' return _tag_completer(start_tag, thing, close_tag, level) @@ -389,7 +345,7 @@ def _entry_tag(key, val, level=0): return start_tag + key_block + val_block + close_tag -def _tag_compound(start_tag, family_type, thing, extra=''): +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. @@ -399,136 +355,52 @@ def _tag_compound(start_tag, family_type, thing, extra=''): start_tag = f'{start_tag}{family_type} refid="{idt}" />\n' return (start_tag, 0) - start_tag = f'{start_tag}{family_type} id="{idt}" {extra}>\n' + 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 f'type="{typename}"' - - if mtype and len(mtype): - if mextra: - mextra = f'extra="{mextra}"' - else: - mextra = '' - return f'family="{family}" type="{mtype}" {mextra}' - return f'family="{family}" type="{typename}"' - - -# Encodings for builtin types. -TYPENAMES = { - '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 _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 ... - - typename = TYPENAMES.get(typename) - if typename is not None: - return typename - raise ValueError(f"family= must be given for unknown type '{typename}'") - - -def _tag_completer(start_tag, orig_thing, close_tag, level): +def _tag_completer(start_tag: str, orig_thing, close_tag: str, level: int) -> str: tag_body = [] - mtag = None thing = orig_thing in_body = TYPE_IN_BODY.get(type(orig_thing), 0) - mextra = None if thing is None: - ft = _family_type('none', 'None', None, None) - start_tag = f"{start_tag}{ft} />\n" + 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' - ft = _family_type('uniq', typestr, mtag, mextra) if in_body: - start_tag = f"{start_tag}{ft}>" + start_tag = f'{start_tag}type="{typestr}">' close_tag = close_tag.lstrip() else: - start_tag = f'{start_tag}{ft} value="" />\n' + 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) - ft = _family_type("atom", "numeric", mtag, mextra) if in_body: # we don't call safe_content() here since numerics won't # contain special XML chars. # the unpickler can either call unsafe_content() or not, # it won't matter - start_tag = f'{start_tag}{ft}>{thing_str}' + start_tag = f'{start_tag}type="numeric">{thing_str}' close_tag = close_tag.lstrip() else: - start_tag = f'{start_tag}{ft} value="{thing_str}" />\n' + start_tag = f'{start_tag}type="numeric" value="{thing_str}" />\n' close_tag = '' elif isinstance(thing, str): - ft = _family_type("atom", "string", mtag, mextra) if in_body: - start_tag = f'{start_tag}{ft}>{safe_content(thing)}' + start_tag = f'{start_tag}type="string">{safe_string(thing, isattr=False)}' close_tag = close_tag.lstrip() else: - start_tag = f'{start_tag}{ft} value="{safe_string(thing)}" />\n' + start_tag = f'{start_tag}type="string" value="{safe_string(thing, isattr=True)}" />\n' close_tag = '' # General notes: @@ -540,19 +412,15 @@ def _tag_completer(start_tag, orig_thing, close_tag, level): # (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) + 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)) else: close_tag = '' - elif isinstance(thing, list): - start_tag, do_copy = _tag_compound( - start_tag, _family_type('seq', 'list', mtag, mextra), - orig_thing) + 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: @@ -561,10 +429,8 @@ def _tag_completer(start_tag, orig_thing, close_tag, level): else: close_tag = '' - elif isinstance(thing, dict): - start_tag, do_copy = _tag_compound( - start_tag, _family_type('map', 'dict', mtag, mextra), - orig_thing) + 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: @@ -584,44 +450,78 @@ def _tag_completer(start_tag, orig_thing, close_tag, level): 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': @@ -629,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': From 42a67582f52e103c713b71fb5465cc0b454197b5 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 31 Mar 2024 23:05:28 +0200 Subject: [PATCH 14/28] Comments and whitespace update only --- src/objdictgen/eds_utils.py | 8 +- src/objdictgen/gen_cfile.py | 47 ++-- src/objdictgen/jsonod.py | 22 +- src/objdictgen/maps.py | 260 ++++++++++----------- src/objdictgen/node.py | 2 +- src/objdictgen/nodemanager.py | 1 + src/objdictgen/ui/commondialogs.py | 5 +- src/objdictgen/ui/exception.py | 2 +- src/objdictgen/ui/networkedit.py | 28 +-- src/objdictgen/ui/networkeditortemplate.py | 14 +- src/objdictgen/ui/nodeeditortemplate.py | 24 +- src/objdictgen/ui/objdictedit.py | 18 +- src/objdictgen/ui/subindextable.py | 32 ++- 13 files changed, 244 insertions(+), 219 deletions(-) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 33c5af0..f8e005e 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -208,6 +208,7 @@ def parse_cpj_file(filepath): f"of section '[{section_name}]'" ) topology["Name"] = computed_value + elif keyname.upper() == "NODES": if not is_integer(computed_value): raise ValueError( @@ -215,6 +216,7 @@ def parse_cpj_file(filepath): f"of section '[{section_name}]'" ) topology["Number"] = computed_value + elif keyname.upper() == "EDSBASENAME": if not is_string(computed_value): raise ValueError( @@ -222,6 +224,7 @@ def parse_cpj_file(filepath): f"of section '[{section_name}]'" ) topology["Path"] = computed_value + elif nodepresent_result: if not is_boolean(computed_value): raise ValueError( @@ -232,6 +235,7 @@ def parse_cpj_file(filepath): 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( @@ -242,6 +246,7 @@ def parse_cpj_file(filepath): 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( @@ -252,6 +257,7 @@ def parse_cpj_file(filepath): if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} topology["Nodes"][nodeid]["DCFName"] = computed_value + else: raise ValueError(f"Keyname '{keyname}' not recognised for section '[{section_name}]'") @@ -464,7 +470,7 @@ def verify_value(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(f"Error on section '[{section_name}]': '{param}' incompatible with DataType") from None diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index ad3ae08..52489a1 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -126,6 +126,7 @@ def generate_file_content(node, headerfile, pointers_dict=None): # pylint: disable=invalid-name context = CFileContext() + pointers_dict = pointers_dict or {} texts = {} texts["maxPDOtransmit"] = 0 @@ -147,9 +148,9 @@ def generate_file_content(node, headerfile, 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 = ( @@ -195,9 +196,9 @@ def generate_file_content(node, headerfile, pointers_dict=None): 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 = "" @@ -211,6 +212,7 @@ def generate_file_content(node, headerfile, pointers_dict=None): params_infos = node.GetParamsEntry(index) texts["EntryName"] = entry_infos["name"] values = node.GetEntry(index) + if index in variablelist: strindex += "\n/* index 0x%(index)04X : Mapped variable %(EntryName)s */\n" % texts else: @@ -448,9 +450,9 @@ def generate_file_content(node, headerfile, pointers_dict=None): strindex += " };\n" indexContents[index] = strindex -# ------------------------------------------------------------------------------ -# Declaration of Particular Parameters -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Declaration of Particular Parameters + # -------------------------------------------------------------------------- if 0x1003 not in communicationlist: entry_infos = node.GetEntryInfos(0x1003) @@ -522,18 +524,20 @@ def generate_file_content(node, headerfile, pointers_dict=None): UNS8 %(NodeName)s_obj100D = 0x0; /* 0 */ """ % texts -# ------------------------------------------------------------------------------ -# Declaration of navigation in the Object Dictionary -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Declaration of navigation in the Object Dictionary + # -------------------------------------------------------------------------- strDeclareIndex = "" strDeclareSwitch = "" strQuickIndex = "" + quick_index = {} 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 @@ -550,6 +554,7 @@ def generate_file_content(node, headerfile, pointers_dict=None): quick_index["firstIndex"][cat] = i if cat == "PDO_TRS": maxPDOtransmit += 1 + texts["maxPDOtransmit"] = max(1, maxPDOtransmit) for index_cat in INDEX_CATEGORIES: strQuickIndex += f"\nconst quick_index {texts['NodeName']}_{index_cat} = {{\n" @@ -560,9 +565,9 @@ def generate_file_content(node, headerfile, pointers_dict=None): strQuickIndex += f" {quick_index[index_cat][cat]}{sep} /* {cat} */\n" strQuickIndex += "};\n" -# ------------------------------------------------------------------------------ -# Write File Content -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Write File Content + # -------------------------------------------------------------------------- fileContent = FILE_HEADER + f""" #include "{headerfile}" @@ -660,9 +665,9 @@ def generate_file_content(node, headerfile, pointers_dict=None): """ % texts -# ------------------------------------------------------------------------------ -# Write Header File Content -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Write Header File Content + # -------------------------------------------------------------------------- texts["file_include_name"] = headerfile.replace(".", "_").upper() headerFileContent = FILE_HEADER + """ @@ -682,9 +687,9 @@ def generate_file_content(node, headerfile, pointers_dict=None): headerFileContent += "\n#endif // %(file_include_name)s\n" % texts -# ------------------------------------------------------------------------------ -# Write Header Object Defintions Content -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Write Header Object Defintions Content + # -------------------------------------------------------------------------- texts["file_include_objdef_name"] = headerfile.replace(".", "_OBJECTDEFINES_").upper() headerObjectDefinitionContent = FILE_HEADER + """ #ifndef %(file_include_objdef_name)s diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index f58f212..e0ee7bf 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -55,6 +55,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 +66,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 +75,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 +88,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 +96,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 +120,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 @@ -169,6 +170,7 @@ def __re_sub(match): ) +# FIXME: Move to generic utils/funcs? def exc_amend(exc, text): """ Helper to prefix text to an exception """ args = list(exc.args) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 676c2b7..51a7238 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -33,7 +33,7 @@ # ------------------------------------------------------------------------------ -# Dictionary Mapping and Organisation +# Object Types and Organisation # ------------------------------------------------------------------------------ class ODStructTypes: @@ -146,145 +146,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 b215501..12772a3 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -151,7 +151,7 @@ def DumpJson(self, compact=False, sort=False, internal=False, validate=True): ) # -------------------------------------------------------------------------- - # Node Functions + # Node Informations Functions # -------------------------------------------------------------------------- def GetMappings(self, userdefinedtoo=True): diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index d2d80c6..d1cfaaf 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -236,6 +236,7 @@ def CreateNewNode(self, name, id, type, description, profile, filepath, nmt, opt for idx in range(firstmappingindex, firstmappingindex + 4): addindexlist.append(idx) addsubindexlist.append((idx, 8)) + # Add a new buffer index = self.AddNodeBuffer(node.Copy(), False) self.SetCurrentFilePath(None) diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 762dbde..5920627 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -590,7 +590,7 @@ 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) @@ -1478,6 +1478,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) @@ -1670,6 +1671,7 @@ 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:] @@ -1686,6 +1688,7 @@ def SetValues(self, values): 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 = Node.le_to_be(len(self.Values), 4) diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index 3f645bb..88d3467 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -107,7 +107,7 @@ def handle_exception(e_type, e_value, e_traceback, parent=None): info = { 'app-title': wx.GetApp().GetAppName(), # app_title 'app-version': __version__, - 'wx-version': wx.VERSION_STRING, + '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(), diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 37c5008..ba34493 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -181,8 +181,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: @@ -221,8 +219,6 @@ def __init__(self, parent, nodelist=None, projectOpen=None): else: NetworkEditorTemplate.__init__(self, nodelist, self, False) self._init_ctrls(parent) - # FIXME: Unused. Delete this? - # self.HtmlFrameOpened = [] icon = wx.Icon( str(objdictgen.SCRIPT_DIRECTORY / "img" / "networkedit.ico"), @@ -241,7 +237,7 @@ def __init__(self, parent, nodelist=None, projectOpen=None): 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() @@ -261,9 +257,9 @@ def OnCloseFrame(self, event): 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: @@ -353,15 +349,15 @@ def OnCloseProjectMenu(self, event): # pylint: disable=unused-argument 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: @@ -410,9 +406,9 @@ def RefreshMainMenu(self): self.MenuBar.EnableTop(1, False) self.MenuBar.EnableTop(2, False) -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): NetworkEditorTemplate.RefreshBufferState(self) diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 038028f..824a7d9 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -71,6 +71,7 @@ def RefreshNetworkNodes(self): new_editingpanel.SetIndex(self.Manager.GetCurrentNodeID()) self.NetworkNodes.AddPage(new_editingpanel, "") for idx in self.NodeList.GetSlaveIDs(): + # FIXME: Why is NodeList used where NodeManager is expected? new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) new_editingpanel.SetIndex(idx) self.NetworkNodes.AddPage(new_editingpanel, "") @@ -87,9 +88,9 @@ def OnNodeSelectedChanged(self, event): wx.CallAfter(self.RefreshStatusBar) event.Skip() -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): if self.NodeList is not None: @@ -102,9 +103,9 @@ def RefreshBufferState(self): 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 with cdia.AddSlaveDialog(self.Frame) as dialog: @@ -117,6 +118,7 @@ def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument self.NodeList.AddSlaveNode( values["slaveName"], values["slaveNodeID"], values["edsFile"], ) + # FIXME: Why is NodeList used where NodeManager is expected? new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) new_editingpanel.SetIndex(values["slaveNodeID"]) idx = self.NodeList.GetOrderNumber(values["slaveNodeID"]) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 9359ab0..3eb3266 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -131,9 +131,9 @@ def RefreshProfileMenu(self): edititem.SetItemLabel("Other Profile") edititem.Enable(False) -# ------------------------------------------------------------------------------ -# Buffer Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Buffer Functions + # -------------------------------------------------------------------------- def RefreshBufferState(self): pass @@ -148,9 +148,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() @@ -192,9 +192,9 @@ def profile_cb(event): # pylint: disable=unused-argument self.RefreshCurrentIndexList() return profile_cb -# ------------------------------------------------------------------------------ -# Edit Node informations function -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Edit Node informations function + # -------------------------------------------------------------------------- def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument dialog = cdia.NodeInfosDialog(self.Frame) @@ -209,9 +209,9 @@ def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument self.RefreshCurrentIndexList() self.RefreshProfileMenu() -# ------------------------------------------------------------------------------ -# Add User Types and Variables -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Add User Types and Variables + # -------------------------------------------------------------------------- def AddMapVariable(self): index = self.Manager.GetCurrentNextMapIndex() diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 74d1fc3..a05d9ce 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -314,9 +314,9 @@ def OnCloseFrame(self, event): else: event.Skip() -# ------------------------------------------------------------------------------ -# Refresh Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Refresh Functions + # -------------------------------------------------------------------------- def RefreshTitle(self): if self.FileOpened.GetPageCount() > 0: @@ -370,9 +370,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() @@ -381,9 +381,9 @@ def RefreshBufferState(self): self.RefreshEditMenu() self.RefreshTitle() -# ------------------------------------------------------------------------------ -# Load and Save Funtions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Load and Save Funtions + # -------------------------------------------------------------------------- def OnNewMenu(self, event): # pylint: disable=unused-argument # self.FilePath = "" diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 0246d13..d9f4731 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -105,7 +105,6 @@ class SubindexTable(wx.grid.GridTableBase): - """ A custom wxGrid Table using user supplied data """ @@ -205,6 +204,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 symbols is not defined in wx no more. Investigate. msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) @@ -227,7 +227,7 @@ 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) @@ -534,6 +534,7 @@ def OnSubindexGridCellLeftClick(self, event): subentry_infos = self.Manager.GetSubentryInfos(index, subindex) typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) if typeinfos: + # FIXME: What is bus_id? It is never set anywhere bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) var_name = f"{self.Manager.GetCurrentNodeName()}_{index:04x}_{subindex:02x}" size = typeinfos["size"] @@ -549,6 +550,8 @@ def OnSubindexGridCellLeftClick(self, event): 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] @@ -558,7 +561,9 @@ def OnSubindexGridCellLeftClick(self, event): subentry_infos = self.Manager.GetSubentryInfos(index, subindex) typeinfos = self.Manager.GetEntryInfos(subentry_infos["type"]) if subentry_infos["pdo"] and typeinfos: + # FIXME: What is bus_id? It is never set anywhere bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) + # 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(( @@ -610,9 +615,9 @@ def OnSubindexGridSelectCell(self, event): wx.CallAfter(self.ParentWindow.RefreshStatusBar) event.Skip() -# ------------------------------------------------------------------------------ -# Refresh Functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Refresh Functions + # -------------------------------------------------------------------------- def RefreshIndexList(self): selected = self.IndexList.GetSelection() @@ -684,9 +689,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() @@ -697,6 +702,7 @@ 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: @@ -731,9 +737,9 @@ def OnCallbackCheck(self, event): wx.CallAfter(self.RefreshTable) event.Skip() -# ------------------------------------------------------------------------------ -# Contextual Menu functions -# ------------------------------------------------------------------------------ + # -------------------------------------------------------------------------- + # Contextual Menu functions + # -------------------------------------------------------------------------- def OnIndexListRightUp(self, event): if self.Editable: @@ -789,6 +795,7 @@ def OnSubindexGridRightClick(self, event): self.SubindexGridMenu.FindItemByPosition(3).Enable(False) if showpopup: self.PopupMenu(self.SubindexGridMenu) + # FIXME: Exists in NetworkEditorTemplate, not in NodeEditorTemplate elif (self.Table.GetColLabelValue(event.GetCol(), False) == "value" and self.ParentWindow.GetCurrentNodeId() is not None ): @@ -816,6 +823,7 @@ def OnAddToDCFSubindexMenu(self, event): # pylint: disable=unused-argument subentry_infos = self.Manager.GetSubentryInfos(index, subindex) typeinfos = self.Manager.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": @@ -828,9 +836,11 @@ def OnAddToDCFSubindexMenu(self, event): # pylint: disable=unused-argument value = int(value, 16) else: value = int(value, 16) + # 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): From ae997b599f3a6439c8bd00d5137ec2ea05b89c73 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 1 Apr 2024 20:20:10 +0200 Subject: [PATCH 15/28] Adding type hints pt1 --- src/objdictgen/__main__.py | 52 ++++---- src/objdictgen/maps.py | 38 ++++-- src/objdictgen/node.py | 259 ++++++++++++++++++++++++------------- src/objdictgen/py.typed | 0 src/objdictgen/typing.py | 247 +++++++++++++++++++++++++++++++++++ 5 files changed, 477 insertions(+), 119 deletions(-) create mode 100644 src/objdictgen/py.typed create mode 100644 src/objdictgen/typing.py diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 0d11ed9..281974e 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -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() @@ -44,18 +51,18 @@ class DebugOpts: """ 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(): +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() @@ -70,7 +77,7 @@ def inner(*args, **kw): return decorator -def open_od(fname, validate=True, fix=False): +def open_od(fname: TPath|str, validate=True, fix=False) -> "Node": """ Open and validate the OD file""" try: @@ -85,14 +92,14 @@ def open_od(fname, validate=True, fix=False): 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(f"<<< {path} only in LEFT") @@ -118,7 +125,7 @@ def _printlines(entries): @debug_wrapper() -def main(debugopts, args=None): +def main(debugopts: DebugOpts, args: Sequence[str]|None = None): """ Main command dispatcher """ parser = argparse.ArgumentParser( @@ -142,19 +149,19 @@ def main(debugopts, args=None): opt_od = dict(metavar='od', default=None, help="Object dictionary") parser.add_argument('--version', action='version', version='%(prog)s ' + objdictgen.__version__) - parser.add_argument('-D', '--debug', **opt_debug) + parser.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- 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=""" Generate """, aliases=['gen', 'conv']) - subp.add_argument('od', **opt_od) + 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.") @@ -170,26 +177,26 @@ def main(debugopts, args=None): 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('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- DIFF -- subp = subparser.add_parser('diff', help=""" Compare OD files """, aliases=['compare']) - subp.add_argument('od1', **opt_od) - subp.add_argument('od2', **opt_od) + 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('--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=""" 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=""" @@ -205,21 +212,21 @@ def main(debugopts, args=None): 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=""" 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=""" 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 -- @@ -243,7 +250,8 @@ def main(debugopts, args=None): ): for choice, subparser in subparsers_action.choices.items(): print(f"command '{choice}'") - for info in subparser.format_help().split('\n'): + # 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) @@ -252,7 +260,7 @@ 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: @@ -449,4 +457,4 @@ def main_objdictedit(): # To support -m objdictgen if __name__ == '__main__': # pylint: disable=no-value-for-parameter - main() # type: ignore + main() diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 51a7238..23b4da7 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -17,6 +17,10 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +from collections import UserDict, UserList +from objdictgen.typing import (TODObj, TODSubObj, TODValue, TParamEntry, TPath, + TProfileMenu) + # # Dictionary of translation between access symbol and their signification @@ -24,12 +28,16 @@ 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} # ------------------------------------------------------------------------------ @@ -62,7 +70,7 @@ class ODStructTypes: # # Mapping against name and structure number # - STRINGS = { + STRINGS: dict[int, str|None] = { NOSUB: None, VAR: "var", RECORD: "record", @@ -71,17 +79,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=''): + def to_string(cls: type["ODStructTypes"], val: int, default: str = '') -> str|None: """Return the string representation of the structure value.""" - # type: (type[ODStructTypes], int, str) -> str return cls.STRINGS.get(val, default) @classmethod - def from_string(cls, val, default=None): + def from_string(cls: type["ODStructTypes"], val: str, default: int|None = None) -> int|None: """Return the structure value from the string representation.""" - # type: (type[ODStructTypes], str, int|None) -> int|None try: return next(k for k, v in cls.STRINGS.items() if v == val) except StopIteration: @@ -108,13 +116,25 @@ def from_string(cls, val, default=None): {"min": 0xA000, "max": 0xBFFF, "name": "sip", "description": "Standardized Interface Profile"}, ] +# ------------------------------------------------------------------------------ +# Objects and mapping +# ------------------------------------------------------------------------------ + +class ODMapping(UserDict[int, TODObj]): + """Object Dictionary Mapping.""" + +class ODMappingList(UserList[ODMapping]): + """List of Object Dictionary Mappings.""" + + # # MAPPING_DICTIONARY is the structure used for writing a good organised Object # Dictionary. It follows the specifications of the CANOpen standard. # 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": []}, @@ -287,4 +307,4 @@ def from_string(cls, val, default=None): 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 12772a3..51c9c47 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -24,12 +24,16 @@ import re import traceback from pathlib import Path +from typing import Any, Generator, Iterable, Iterator, Self 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 OD +from objdictgen.maps import OD, ODMapping, ODMappingList +from objdictgen.typing import (NodeProtocol, TIndexEntry, TODObj, TODSubObj, + TODValue, TParamEntry, TPath, TProfileMenu) log = logging.getLogger('objdictgen') @@ -49,52 +53,98 @@ # Definition of Node Object # ------------------------------------------------------------------------------ -class Node: +class Node(NodeProtocol): """ - Class recording the Object Dictionary entries. It checks at each modification - that the structure of the Object Dictionary stay coherent + A Object Dictionary representation of a CAN node. """ - DefaultStringSize = 10 + Name: str + """Name of the node""" + + Type: str + """Type of the node. Should be 'slave' or 'master'""" + + ID: int + """Node ID""" + + Description: str + """Description of the node""" + + 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. + + Profile: ODMapping + """Profile object dictionary mapping""" + + DS302: ODMapping + """DS-302 object dictionary mapping""" + + UserMapping: ODMapping + """Custom user object dictionary mapping""" + + ProfileName: str + """Name of the loaded profile. If no profile is loaded, it should be '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="", type="slave", id=0, description="", - profilename="None", profile=None, - specificmenu=None, - ): # pylint: disable=redefined-builtin - 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 = [] + 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] = [] # -------------------------------------------------------------------------- # Node Input/Output # -------------------------------------------------------------------------- @staticmethod - def isXml(filepath): + 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: str) -> "Node": + def LoadFile(filepath: TPath) -> "Node": """ Open a file and create a new node """ if Node.isXml(filepath): log.debug("Loading XML OD '%s'", filepath) @@ -110,11 +160,11 @@ def LoadFile(filepath: str) -> "Node": return Node.LoadJson(f.read()) @staticmethod - def LoadJson(contents): + def LoadJson(contents: str) -> "Node": """ Import a new Node from a JSON string """ return jsonod.generate_node(contents) - def DumpFile(self, filepath, filetype="json", **kwargs): + def DumpFile(self, filepath: TPath, filetype: str = "json", **kwargs): """ Save node into file """ if filetype == 'od': log.debug("Writing XML OD '%s'", filepath) @@ -144,7 +194,7 @@ def DumpFile(self, filepath, filetype="json", **kwargs): 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.generate_json( self, compact=compact, sort=sort, internal=internal, validate=validate @@ -154,15 +204,13 @@ def DumpJson(self, compact=False, sort=False, internal=False, validate=True): # Node Informations Functions # -------------------------------------------------------------------------- - def GetMappings(self, userdefinedtoo=True): - """ - Function which return the different Mappings available for this node - """ + def GetMappings(self, userdefinedtoo: bool=True) -> ODMappingList: + """Return the different Mappings available for this node""" if userdefinedtoo: return [self.Profile, self.DS302, self.UserMapping] return [self.Profile, self.DS302] - def AddEntry(self, index, subindex=None, value=None): + def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None) -> bool: """ Add a new entry in the Object Dictionary """ @@ -179,10 +227,8 @@ def AddEntry(self, index, subindex=None, value=None): return True return False - def SetEntry(self, index, subindex=None, value=None): - """ - Warning ! Modifies an existing entry in the Object Dictionary. Can't add a new one. - """ + def SetEntry(self, index: int, subindex: int|None = None, value: TODValue|None = None) -> bool: + """Modify an existing entry in the Object Dictionary""" if index not in self.Dictionary: return False if not subindex: @@ -195,7 +241,8 @@ def SetEntry(self, index, subindex=None, value=None): return True return False - def SetParamsEntry(self, index, subindex=None, comment=None, buffer_size=None, save=None, callback=None): + def SetParamsEntry(self, index: int, subindex: int|None = None, comment=None, buffer_size=None, save=None, callback=None) -> bool: + """Set parameter values for an entry in the Object Dictionary.""" 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) @@ -226,7 +273,7 @@ def SetParamsEntry(self, index, subindex=None, comment=None, buffer_size=None, s return True return False - def RemoveEntry(self, index, subindex=None): + def RemoveEntry(self, index: int, subindex: int|None = None) -> bool: """ 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 @@ -253,9 +300,9 @@ def RemoveEntry(self, index, subindex=None): return True return False - def IsEntry(self, index, subindex=None): + def IsEntry(self, index: int, subindex: int=0) -> bool: """ - Check if an entry exists in the Object Dictionary and returns the answer. + Check if an entry exists in the Object Dictionary """ if index in self.Dictionary: if not subindex: @@ -263,10 +310,11 @@ def IsEntry(self, index, subindex=None): return subindex <= len(self.Dictionary[index]) return False - def GetEntry(self, index, subindex=None, compute=True, aslist=False): + def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=False) -> list[TODValue]|TODValue: """ - 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. + 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: raise KeyError(f"Index 0x{index:04x} does not exist") @@ -291,7 +339,8 @@ def GetEntry(self, index, subindex=None, compute=True, aslist=False): return self.eval_value(self.Dictionary[index][subindex - 1], base, nodeid, compute) raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - def GetParamsEntry(self, index, subindex=None, aslist=False): + 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. @@ -328,7 +377,8 @@ def GetParamsEntry(self, index, subindex=None, aslist=False): return result raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - def HasEntryCallbacks(self, index): + 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"] @@ -336,14 +386,14 @@ def HasEntryCallbacks(self, index): return self.ParamsDictionary[index]["callback"] return False - def IsMappingEntry(self, index): + def IsMappingEntry(self, index: int) -> bool: """ Check if an entry exists in the User Mapping Dictionary and returns the answer. """ return index in self.UserMapping - def AddMappingEntry(self, index, subindex=None, name="Undefined", struct=0, size=None, nbmax=None, - default=None, values=None): + def AddMappingEntry(self, index: int, subindex: int|None = None, name="Undefined", struct=0, size=None, nbmax=None, + default=None, values=None) -> bool: """ Add a new entry in the User Mapping Dictionary """ @@ -366,10 +416,9 @@ def AddMappingEntry(self, index, subindex=None, name="Undefined", struct=0, size return True return False - def SetMappingEntry(self, index, subindex=None, name=None, struct=None, size=None, nbmax=None, - default=None, values=None): + def SetMappingEntry(self, index: int, subindex: int|None = None, name=None, struct=None, size=None, nbmax=None, default=None, values=None) -> bool: """ - Warning ! Modifies an existing entry in the User Mapping Dictionary. Can't add a new one. + Modify an existing entry in the User Mapping Dictionary """ if index not in self.UserMapping: return False @@ -433,7 +482,7 @@ def SetMappingEntry(self, index, subindex=None, name=None, struct=None, size=Non return True return False - def RemoveMappingEntry(self, index, subindex=None): + def RemoveMappingEntry(self, index: int, subindex: int|None = None) -> bool: """ 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 @@ -448,7 +497,10 @@ def RemoveMappingEntry(self, index, subindex=None): return True return False - def RemoveMapVariable(self, index, subindex=None): + 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: @@ -460,7 +512,11 @@ def RemoveMapVariable(self, index, subindex=None): if (value & mask) == model: self.Dictionary[i][j] = 0 - def UpdateMapVariable(self, index, subindex, size): + 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: @@ -472,25 +528,28 @@ def UpdateMapVariable(self, index, subindex, size): if (value & mask) == model: self.Dictionary[i][j] = model + size - def RemoveLine(self, index, max_, incr=1): + def RemoveLine(self, index: int, max_: 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 < max_ and self.IsEntry(i + incr): self.Dictionary[i] = self.Dictionary[i + incr] i += incr self.Dictionary.pop(i) - def Copy(self): + def Copy(self) -> Self: """ Return a copy of the node """ return copy.deepcopy(self) - def GetDict(self): + def GetDict(self) -> dict[str, Any]: """ Return the class data as a dict """ return copy.deepcopy(self.__dict__) - def GetIndexDict(self, index): - """ Return a dict representation of the index """ + def GetIndexDict(self, index: int) -> TIndexEntry: + """ Return a full and raw representation of the index """ obj = {} if index in self.Dictionary: obj['dictionary'] = self.Dictionary[index] @@ -519,7 +578,7 @@ def GetIndexes(self): # Node Informations Functions # -------------------------------------------------------------------------- - def GetBaseIndex(self, index): + def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ for mapping in self.GetMappings(): result = self.FindIndex(index, mapping) @@ -527,7 +586,7 @@ def GetBaseIndex(self, index): return result return self.FindIndex(index, maps.MAPPING_DICTIONARY) - def GetBaseIndexNumber(self, index): + def GetBaseIndexNumber(self, index: int) -> int: """ Return the index number from the base object """ for mapping in self.GetMappings(): result = self.FindIndex(index, mapping) @@ -538,12 +597,16 @@ def GetBaseIndexNumber(self, index): return (index - result) // maps.MAPPING_DICTIONARY[result].get("incr", 1) return 0 - def GetCustomisedTypeValues(self, 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) customisabletypes = self.GetCustomisableTypes() return values, customisabletypes[values[1]][1] # type: ignore - def GetEntryName(self, index, compute=True): + def GetEntryName(self, index: int, compute=True) -> str: + """Return the entry name for the given index""" result = None mappings = self.GetMappings() i = 0 @@ -554,7 +617,8 @@ def GetEntryName(self, index, compute=True): result = self.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) return result - def GetEntryInfos(self, index, compute=True): + def GetEntryInfos(self, index: int, compute=True) -> TODObj: + """Return the entry infos for the given index""" result = None mappings = self.GetMappings() i = 0 @@ -568,7 +632,8 @@ def GetEntryInfos(self, index, compute=True): return r301 return result - def GetSubentryInfos(self, index, subindex, compute=True): + def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> TODSubObj: + """Return the subentry infos for the given index and subindex""" result = None mappings = self.GetMappings() i = 0 @@ -586,7 +651,8 @@ def GetSubentryInfos(self, index, subindex, compute=True): return r301 return result - def GetEntryFlags(self, index): + def GetEntryFlags(self, index: int) -> set[str]: + """Return the flags for the given index""" flags = [] info = self.GetEntryInfos(index) if not info: @@ -623,7 +689,8 @@ def GetAllSubentryInfos(self, index, compute=True): info.update(entry) yield info - def GetTypeIndex(self, typename): + def GetTypeIndex(self, typename: str) -> int: + """Return the type index for the given type name.""" result = None mappings = self.GetMappings() i = 0 @@ -634,7 +701,8 @@ def GetTypeIndex(self, typename): result = self.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) return result - def GetTypeName(self, typeindex): + def GetTypeName(self, typeindex: int) -> str: + """Return the type name for the given type index.""" result = None mappings = self.GetMappings() i = 0 @@ -645,7 +713,8 @@ def GetTypeName(self, typeindex): result = self.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) return result - def GetTypeDefaultValue(self, typeindex): + def GetTypeDefaultValue(self, typeindex: int) -> TODValue: + """Return the default value for the given type index.""" result = None mappings = self.GetMappings() i = 0 @@ -656,20 +725,25 @@ def GetTypeDefaultValue(self, typeindex): result = self.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) return result - def GetMapVariableList(self, compute=True): + 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.""" list_ = list(self.FindMapVariableList(maps.MAPPING_DICTIONARY, self, compute)) for mapping in self.GetMappings(): list_.extend(self.FindMapVariableList(mapping, self, compute)) list_.sort() return list_ - def GetMandatoryIndexes(self, node=None): # pylint: disable=unused-argument + def GetMandatoryIndexes(self, node: "Node|None" = None) -> list[int]: # pylint: disable=unused-argument + """Return the mandatory indexes for the node.""" list_ = self.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): list_.extend(self.FindMandatoryIndexes(mapping)) return list_ - 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 @@ -679,7 +753,8 @@ def GetCustomisableTypes(self): # Type helper functions # -------------------------------------------------------------------------- - def IsStringType(self, index): + def IsStringType(self, index: int) -> bool: + """Is the object index a string type?""" if index in (0x9, 0xA, 0xB, 0xF): return True if 0xA0 <= index < 0x100: @@ -688,7 +763,8 @@ def IsStringType(self, index): return True return False - def IsRealType(self, index): + def IsRealType(self, index: int) -> bool: + """Is the object index a real (float) type?""" if index in (0x8, 0x11): return True if 0xA0 <= index < 0x100: @@ -701,16 +777,19 @@ def IsRealType(self, index): # Type and Map Variable Lists # -------------------------------------------------------------------------- - def GetTypeList(self): + def GetTypeList(self) -> list[str]: + """Return a list of all object types available for the current node""" list_ = self.FindTypeList(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): list_.extend(self.FindTypeList(mapping)) return list_ - def GenerateMapName(self, name, index, subindex): # pylint: disable=unused-argument + def GenerateMapName(self, 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 @@ -743,7 +822,8 @@ def GetMapValue(self, mapname): return (index << 16) + (subindex << 8) + size return None - def GetMapIndex(self, value): + def GetMapIndex(self, 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) @@ -751,7 +831,8 @@ 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) @@ -759,9 +840,9 @@ def GetMapName(self, value): 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) @@ -769,9 +850,11 @@ def GetMapList(self): ] return list_ - def GetAllParameters(self, sort=False): - """ Get a list of all the parameters """ - + def GetAllParameters(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] @@ -801,8 +884,8 @@ def GetUnusedParameters(self): if k not in self.Dictionary ] - def RemoveIndex(self, index): - """ Remove the given index """ + def RemoveIndex(self, index: int) -> None: + """ Remove the given index""" self.UserMapping.pop(index, None) self.Dictionary.pop(index, None) self.ParamsDictionary.pop(index, None) @@ -821,7 +904,7 @@ def Validate(self, fix=False): """ 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%04x (%d) '%s': %s", index, index, name, text) @@ -891,7 +974,7 @@ def _warn(text): # 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: diff --git a/src/objdictgen/py.typed b/src/objdictgen/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/objdictgen/typing.py b/src/objdictgen/typing.py new file mode 100644 index 0000000..bbd4eba --- /dev/null +++ b/src/objdictgen/typing.py @@ -0,0 +1,247 @@ +"""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 Node.ImportProfile +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.""" + ... + + +# 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.""" From 21515b3e595adc8de1aeb2ec0a736925c0c3a009 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Mon, 1 Apr 2024 20:50:05 +0200 Subject: [PATCH 16/28] Adding type hints pt2 --- src/objdictgen/eds_utils.py | 54 ++++--- src/objdictgen/gen_cfile.py | 16 ++- src/objdictgen/jsonod.py | 90 ++++++++---- src/objdictgen/nodelist.py | 69 ++++----- src/objdictgen/nodemanager.py | 158 +++++++++++---------- src/objdictgen/ui/commondialogs.py | 36 +++-- src/objdictgen/ui/networkedit.py | 6 +- src/objdictgen/ui/networkeditortemplate.py | 13 +- src/objdictgen/ui/nodeeditortemplate.py | 16 ++- src/objdictgen/ui/objdictedit.py | 13 +- src/objdictgen/ui/subindextable.py | 40 ++++-- 11 files changed, 310 insertions(+), 201 deletions(-) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index f8e005e..a0df823 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -22,10 +22,17 @@ import re from pathlib import Path from time import localtime, strftime -import logging +from typing import TYPE_CHECKING, Any, Callable +# 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') @@ -52,12 +59,12 @@ } # Function for verifying data values -is_integer = lambda x: isinstance(x, int) # pylint: disable=unnecessary-lambda-assignment # noqa: E731 -is_string = lambda x: isinstance(x, str) # pylint: disable=unnecessary-lambda-assignment # noqa: E731 -is_boolean = lambda x: x in (0, 1) # pylint: disable=unnecessary-lambda-assignment # 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), @@ -74,7 +81,7 @@ } # 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"]}, @@ -91,7 +98,7 @@ } -def get_default_value(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) @@ -129,7 +136,7 @@ def get_default_value(node, index, subindex=None): "STANDARDDATATYPES", "SUPPORTEDMODULES"] -def extract_sections(data): +def extract_sections(data: str) -> list[tuple[str, list[str]]]: """Extract sections from a file and returns a dictionary of the informations""" return [ ( @@ -143,7 +150,7 @@ def extract_sections(data): if blocktuple[0].isalnum()] # if EntryName exists -def parse_cpj_file(filepath): +def parse_cpj_file(filepath: TPath): """Parse a CPJ file and return a list of dictionaries of the informations""" networks = [] @@ -159,7 +166,7 @@ def parse_cpj_file(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 @@ -175,6 +182,7 @@ def parse_cpj_file(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"): @@ -284,9 +292,9 @@ def parse_cpj_file(filepath): return networks -def parse_eds_file(filepath): +def parse_eds_file(filepath: TPath) -> dict[str|int, Any]: """Parse an EDS file and returns a dictionary of the informations""" - eds_dict = {} + eds_dict: dict[str|int, Any] = {} # Read file text with open(filepath, 'r', encoding="utf-8") as f: @@ -297,7 +305,7 @@ def parse_eds_file(filepath): # 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()) @@ -363,6 +371,7 @@ def parse_eds_file(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) @@ -458,7 +467,7 @@ def parse_eds_file(filepath): return eds_dict -def verify_value(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: @@ -475,7 +484,7 @@ def verify_value(values, section_name, param): raise ValueError(f"Error on section '[{section_name}]': '{param}' incompatible with DataType") from None -def generate_eds_content(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) @@ -572,9 +581,9 @@ def generate_eds_content(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[:]: @@ -654,7 +663,7 @@ def generate_eds_content(node, filepath): # Save text of the entry in the dictiionary of contents indexcontents[entry] = text - def generate_index_contents(name, entries): + 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" @@ -674,7 +683,7 @@ def generate_index_contents(name, entries): return fileContent -def generate_cpj_content(nodelist): +def generate_cpj_content(nodelist: "NodeList"): """Generate the CPJ file content for the nodelist.""" nodes = nodelist.SlaveNodes @@ -691,7 +700,7 @@ def generate_cpj_content(nodelist): return filecontent -def generate_node(filepath, nodeid=0): +def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": """Generate a Node from an EDS file.""" # Create a new node node = nodelib.Node(id=nodeid) @@ -740,6 +749,9 @@ def generate_node(filepath, nodeid=0): if entry in SECTION_KEYNAMES: continue + # FIXME: entry should be integer, but can that be guaranteed? + assert isinstance(entry, int) + # Extract informations for the entry entry_infos = node.GetEntryInfos(entry) diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 52489a1..8ccfb37 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -19,9 +19,13 @@ # USA import re +from collections import UserDict +from dataclasses import dataclass from pathlib import Path +from typing import Any, Self 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,7 +33,7 @@ RE_STARTS_WITH_DIGIT = re.compile(r'^(\d.*)') RE_NOTW = re.compile(r"[^\w]") -CATEGORIES = [ +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) @@ -48,7 +52,7 @@ def __init__(self): self.default_string_size = 10 -def format_name(name): +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) @@ -93,7 +97,7 @@ def get_valid_type_infos(context, typename, items=None): return typeinfos -def compute_value(type_, value): +def compute_value(type_: str, value: TODValue) -> tuple[str, str]: """Compute value for C file.""" if type_ == "visible_string": return f'"{value}"', "" @@ -108,7 +112,7 @@ def compute_value(type_, value): return f"0x{value:X}", f"\t/* {value} */" -def get_type_name(node, typenumber): +def get_type_name(node: NodeProtocol, typenumber: int) -> str: """Get type name from a type number.""" typename = node.GetTypeName(typenumber) if typename is None: @@ -117,7 +121,7 @@ def get_type_name(node, typenumber): return typename -def generate_file_content(node, headerfile, pointers_dict=None): +def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=None) -> tuple[str, str, str]: """ pointers_dict = {(Idx,Sidx):"VariableName",...} """ @@ -714,7 +718,7 @@ def generate_file_content(node, headerfile, pointers_dict=None): # Main Function # ------------------------------------------------------------------------------ -def generate_file(filepath, node, pointers_dict=None): +def generate_file(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") diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index e0ee7bf..ae399ef 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -17,23 +17,35 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA +import copy import json import logging 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 import node as nodelib -from objdictgen.maps import OD +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): @@ -154,7 +166,7 @@ class ValidationError(Exception): } -def remove_jasonc(text): +def remove_jasonc(text: str) -> str: """ Remove jsonc annotations """ # Copied from https://github.com/NickolaiBeloguzov/jsonc-parser/blob/master/jsonc_parser/parser.py#L11-L39 def __re_sub(match): @@ -171,7 +183,7 @@ def __re_sub(match): # FIXME: Move to generic utils/funcs? -def exc_amend(exc, text): +def exc_amend(exc: Exception, text: str) -> Exception: """ Helper to prefix text to an exception """ args = list(exc.args) if len(args) > 0: @@ -182,7 +194,7 @@ def exc_amend(exc, text): return exc -def str_to_number(string): +def str_to_number(string: str|int|float|None) -> str|int|float|None: """ Convert string to a number, otherwise pass it through """ if string is None or isinstance(string, (int, float)): return string @@ -194,7 +206,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] @@ -206,26 +218,32 @@ 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): +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 @@ -259,7 +277,10 @@ def member_compare(a, must=None, optional=None, not_want=None, msg='', only_if=N raise ValidationError(f"Unexpected parameters '{unexp}'{msg}") -def get_object_types(node=None, dictionary=None): +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] @@ -299,8 +320,11 @@ def get_object_types(node=None, dictionary=None): return i2s, s2i -def compare_profile(profilename, params, menu=None): - """Compare a profile with a set of parameters and menu.""" +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 = nodelib.Node.ImportProfile(profilename) identical = all( @@ -316,7 +340,7 @@ def compare_profile(profilename, params, menu=None): return False, False -def generate_json(node, compact=False, sort=False, internal=False, validate=True): +def generate_json(node: "Node", compact=False, sort=False, internal=False, validate=True) -> str: """ Export a JSON string representation of the node """ # Get the dict representation @@ -341,7 +365,7 @@ def generate_json(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': @@ -367,7 +391,7 @@ def _index_repl(m): return out -def generate_node(contents): +def generate_node(contents: str|TODJson) -> "Node": """ Import from JSON string or objects """ jd = contents @@ -398,7 +422,7 @@ def generate_node(contents): return node_fromdict(jd) -def node_todict(node, sort=False, rich=True, internal=False, validate=True): +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. @@ -414,6 +438,10 @@ 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. + + 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 dict representation of the node object @@ -682,14 +710,12 @@ def node_todict_parameter(obj, node, index): return obj -def validate_nodeindex(node, index, obj): +def validate_nodeindex(node: "Node", index: int, obj): """ 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'] @@ -835,7 +861,7 @@ def validate_nodeindex(node, index, obj): raise ValidationError(f"Unexpexted count of subindexes in mapping object, found {len(nbmax)}") -def node_fromdict(jd, internal=False): +def node_fromdict(jd: TODJson, internal=False) -> "Node": """ Convert a dict jd into a Node """ # Remove all underscore keys from the file @@ -917,8 +943,10 @@ def node_fromdict(jd, internal=False): return node -def node_fromdict_parameter(obj, objtypes_s2i): - """ Convert a dict obj into a Node parameter """ +def node_fromdict_parameter(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TIndexEntry: + """ Convert a json OD object into an object adapted for load into a Node + object. + """ # -- STEP 1a) -- # Move 'definition' into individual mapping type category @@ -1025,7 +1053,7 @@ def node_fromdict_parameter(obj, objtypes_s2i): return obj -def validate_fromdict(jsonobj, objtypes_i2s=None, objtypes_s2i=None): +def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str]|None = None, objtypes_s2i: dict[str, int]|None = None): """ Validate that jsonobj is a properly formatted dictionary that may be imported to the internal OD-format """ @@ -1098,13 +1126,13 @@ def _validate_sub(obj, idx=0, is_var=False, is_repeat=False, is_each=False): # Validate "nbmax" if parsing the "each" sub member_compare(obj.keys(), {'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. @@ -1305,7 +1333,7 @@ def _validate_dictionary(index, obj): raise -def diff_nodes(node1, node2, as_dict=True, validate=True): +def diff_nodes(node1: "Node", node2: "Node", as_dict=True, validate=True) -> TDiffNodes: """Compare two nodes and return the differences.""" diffs = {} diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 55306fb..4008c94 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -20,9 +20,13 @@ import errno import os +from pathlib import Path import shutil 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 @@ -34,39 +38,39 @@ 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, dict] = {} + 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") - def GetMasterNodeID(self): + def GetMasterNodeID(self) -> int: return self.Manager.GetCurrentNodeID() - def GetSlaveName(self, idx): + def GetSlaveName(self, idx: int) -> str: return self.SlaveNodes[idx]["Name"] - def GetSlaveNames(self): + def GetSlaveNames(self) -> list[str]: return [ 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 = {} @@ -89,39 +93,39 @@ def LoadProject(self, root, netname=None): 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): + def GetEDSFilePath(self, edspath: TPath) -> Path: _, file = os.path.split(edspath) eds_folder = self.GetEDSFolder() return os.path.join(eds_folder, file) - def ImportEDSFile(self, edspath): + def ImportEDSFile(self, edspath: TPath): _, file = os.path.split(edspath) shutil.copy(edspath, self.GetEDSFolder()) self.LoadEDS(file) - def LoadEDS(self, eds): + def LoadEDS(self, eds: TPath): edspath = os.path.join(self.GetEDSFolder(), eds) node = eds_utils.generate_node(edspath) self.EDSNodes[eds] = node - def AddSlaveNode(self, nodename, nodeid, eds): + def AddSlaveNode(self, nodename: str, nodeid: int, eds: str): if eds not in self.EDSNodes: raise ValueError(f"'{eds}' EDS file is not available") slave = {"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(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, f"{netname}_master.od") else: @@ -135,7 +139,7 @@ def LoadMasterNode(self, netname=None): ) return index - def SaveMasterNode(self, netname=None): + def SaveMasterNode(self, netname: str = ""): if netname: masterpath = os.path.join(self.Root, f"{netname}_master.od") else: @@ -145,7 +149,7 @@ def SaveMasterNode(self, netname=None): except Exception as exc: # pylint: disable=broad-except raise ValueError(f"Fail to save master node in '{masterpath}'") from exc - def LoadSlaveNodes(self, netname=None): + def LoadSlaveNodes(self, netname: str = ""): cpjpath = os.path.join(self.Root, "nodelist.cpj") if os.path.isfile(cpjpath): try: @@ -167,7 +171,7 @@ def LoadSlaveNodes(self, netname=None): except Exception as exc: # pylint: disable=broad-except raise ValueError(f"Unable to load CPJ file '{cpjpath}'") from exc - def SaveNodeList(self, netname=None): + def SaveNodeList(self, netname: str = ""): cpjpath = '' # For linting try: cpjpath = os.path.join(self.Root, "nodelist.cpj") @@ -182,11 +186,11 @@ def SaveNodeList(self, netname=None): except Exception as exc: # pylint: disable=broad-except 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) @@ -196,7 +200,7 @@ def IsCurrentEntry(self, index): return node.IsEntry(index) return False - 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) @@ -206,7 +210,7 @@ def GetEntryInfos(self, index): return node.GetEntryInfos(index) return None - 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) @@ -216,7 +220,7 @@ def GetSubentryInfos(self, index, subindex): return node.GetSubentryInfos(index, subindex) return None - def GetCurrentValidIndexes(self, min_, max_): + def GetCurrentValidIndexes(self, min_: int, max_: int) -> list[tuple[str, int]]: if self.CurrentSelected is not None: if self.CurrentSelected == 0: return self.Manager.GetCurrentValidIndexes(min_, max_) @@ -231,7 +235,7 @@ def GetCurrentValidIndexes(self, min_, max_): raise ValueError("Can't find node") return [] - def GetCurrentEntryValues(self, index): + def GetCurrentEntryValues(self, index: int): if self.CurrentSelected is not None: node = self.SlaveNodes[self.CurrentSelected]["Node"] if node: @@ -240,18 +244,15 @@ def GetCurrentEntryValues(self, index): raise ValueError("Can't find node") return [], [] - 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): 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() diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index d1cfaaf..bff2e37 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -22,12 +22,18 @@ 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 import node as nodelib -from objdictgen.maps import OD +from objdictgen.maps import OD, ODMapping +from objdictgen.node import Node +from objdictgen.typing import TODSubObj, TPath + +T = TypeVar("T") log = logging.getLogger('objdictgen') @@ -50,19 +56,19 @@ def get_new_id(): 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, currentstate: 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] = [] + self.CurrentIndex: int = -1 + self.MinIndex: int = -1 + self.MaxIndex: int = -1 # if current state is defined if currentstate: self.CurrentIndex = 0 @@ -77,7 +83,7 @@ def __init__(self, currentstate, issaved=False): else: self.LastSave = -1 - def Buffering(self, currentstate): + def Buffering(self, currentstate: T): """ Add a new state in buffer """ @@ -92,13 +98,13 @@ 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): + def Previous(self) -> T: """ Change current state to previous in buffer and return new current state """ @@ -107,7 +113,7 @@ def Previous(self): return self.Buffer[self.CurrentIndex] return None - def Next(self): + def Next(self) -> T: """ Change current state to next in buffer and return new current state """ @@ -116,13 +122,13 @@ def Next(self): return self.Buffer[self.CurrentIndex] return None - 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 """ @@ -134,7 +140,7 @@ def CurrentSaved(self): """ self.LastSave = self.CurrentIndex - def IsCurrentSaved(self): + def IsCurrentSaved(self) -> bool: """ Return True if current state is saved """ @@ -151,17 +157,17 @@ 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.NodeIndex: int|None = None + self.CurrentNode: Node|None = None + self.UndoBuffers: dict[int, UndoBuffer[Node]] = {} # -------------------------------------------------------------------------- # Type and Map Variable Lists # -------------------------------------------------------------------------- - def GetCurrentTypeList(self): + def GetCurrentTypeList(self) -> list[str]|str: """ Return the list of types defined for the current node """ @@ -169,7 +175,7 @@ def GetCurrentTypeList(self): return self.CurrentNode.GetTypeList() return "" - def GetCurrentMapList(self): + def GetCurrentMapList(self) -> list[str]|str: """ Return the list of variables that can be mapped for the current node """ @@ -181,7 +187,10 @@ def GetCurrentMapList(self): # 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 """ @@ -246,7 +255,7 @@ 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 """ @@ -259,7 +268,7 @@ def OpenFileInCurrent(self, filepath, load=True): 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 """ @@ -286,7 +295,7 @@ def SaveCurrentInFile(self, filepath=None, filetype='', **kwargs): self.UndoBuffers[self.NodeIndex].CurrentSaved() return True - def CloseCurrent(self, ignore=False): + def CloseCurrent(self, ignore=False) -> bool: """ Close current state """ @@ -315,7 +324,7 @@ 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 @@ -349,7 +358,7 @@ def AddSubentriesToCurrent(self, index, number, node=None): 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 @@ -407,7 +416,7 @@ 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 """ @@ -421,7 +430,7 @@ def AddSpecificEntryToCurrent(self, menuitem): if None not in indexlist: self.ManageEntriesOfCurrent(indexlist, []) - def GetLineFromIndex(self, base_index): + def GetLineFromIndex(self, base_index: int) -> int|None: """ Search the first index available for a pluri entry from base_index """ @@ -437,7 +446,7 @@ def GetLineFromIndex(self, base_index): return index return None - 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 """ @@ -487,7 +496,7 @@ 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 """ @@ -504,7 +513,7 @@ def SetCurrentEntryToDefault(self, index, subindex, node=None): 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 @@ -558,7 +567,7 @@ def RemoveCurrentVariable(self, index, subindex=None): if index in mappings[-1]: 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: @@ -592,7 +601,7 @@ def AddMapVariableToCurrent(self, index, name, struct, number, node=None): return raise ValueError(f"Index 0x{index:04X} isn't a valid index for Map Variable!") - def AddUserTypeToCurrent(self, type_, min_, max_, length): + def AddUserTypeToCurrent(self, type_: int, min_: int, max_: int, length: int): assert self.CurrentNode # For mypy node = self.CurrentNode index = 0xA0 @@ -644,14 +653,14 @@ def AddUserTypeToCurrent(self, type_, min_, max_, length): # Modify Entry and Mapping Functions # -------------------------------------------------------------------------- - def SetCurrentEntryCallbacks(self, index, value): + def SetCurrentEntryCallbacks(self, index: int, value: bool): if self.CurrentNode and self.CurrentNode.IsEntry(index): entry_infos = self.GetEntryInfos(index) if "callback" not in entry_infos: self.CurrentNode.SetParamsEntry(index, None, 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 @@ -732,11 +741,11 @@ def SetCurrentEntry(self, index, subindex, value, name, editor, node=None): if not disable_buffer: self.BufferCurrentNode() - def SetCurrentEntryName(self, index, name): + def SetCurrentEntryName(self, index: int, name: str): self.CurrentNode.SetMappingEntry(index, name=name) self.BufferCurrentNode() - def SetCurrentUserType(self, index, type_, min_, max_, length): + def SetCurrentUserType(self, index: int, type_: int, min_: int, max_: int, length: int): assert self.CurrentNode # For mypy node = self.CurrentNode customisabletypes = self.GetCustomisableTypes() @@ -783,19 +792,19 @@ def SetCurrentUserType(self, index, type_, min_, max_, length): def BufferCurrentNode(self): self.UndoBuffers[self.NodeIndex].Buffering(self.CurrentNode.Copy()) - def CurrentIsSaved(self): + 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): @@ -804,38 +813,38 @@ def LoadCurrentPrevious(self): def LoadCurrentNext(self): self.CurrentNode = self.UndoBuffers[self.NodeIndex].Next().Copy() - def AddNodeBuffer(self, currentstate=None, issaved=False): + def AddNodeBuffer(self, currentstate: Node|None = None, issaved=False) -> int: self.NodeIndex = get_new_id() self.UndoBuffers[self.NodeIndex] = UndoBuffer(currentstate, issaved) self.FilePaths[self.NodeIndex] = "" self.FileNames[self.NodeIndex] = "" return self.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() - def RemoveNodeBuffer(self, index): + def RemoveNodeBuffer(self, index: int): self.UndoBuffers.pop(index) self.FilePaths.pop(index) self.FileNames.pop(index) - def GetCurrentFilename(self): + 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 f"~{self.FileNames[index]}~" - def SetCurrentFilePath(self, filepath): + def SetCurrentFilePath(self, filepath: TPath|None): self.FilePaths[self.NodeIndex] = filepath if filepath: self.FileNames[self.NodeIndex] = os.path.splitext(os.path.basename(filepath))[0] @@ -843,12 +852,12 @@ def SetCurrentFilePath(self, filepath): self.LastNewIndex += 1 self.FileNames[self.NodeIndex] = f"Unnamed{self.LastNewIndex}" - def GetCurrentFilePath(self): + def GetCurrentFilePath(self) -> Path|None: if len(self.FilePaths) > 0: return self.FilePaths[self.NodeIndex] return None - def GetCurrentBufferState(self): + def GetCurrentBufferState(self) -> tuple[bool, bool]: first = self.UndoBuffers[self.NodeIndex].IsFirst() last = self.UndoBuffers[self.NodeIndex].IsLast() return not first, not last @@ -857,20 +866,20 @@ def GetCurrentBufferState(self): # Profiles Management Functions # -------------------------------------------------------------------------- - def GetCurrentCommunicationLists(self): + def GetCurrentCommunicationLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: list_ = [] for index in maps.MAPPING_DICTIONARY: if 0x1000 <= index < 0x1200: list_.append(index) return self.GetProfileLists(maps.MAPPING_DICTIONARY, list_) - def GetCurrentDS302Lists(self): + def GetCurrentDS302Lists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: return self.GetSpecificProfileLists(self.CurrentNode.DS302) - def GetCurrentProfileLists(self): + def GetCurrentProfileLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: return self.GetSpecificProfileLists(self.CurrentNode.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: @@ -880,7 +889,8 @@ def GetSpecificProfileLists(self, mappingdictionary): validlist.append(index) return self.GetProfileLists(mappingdictionary, validlist) - def GetProfileLists(self, mappingdictionary, list_): + def GetProfileLists(self, mappingdictionary: ODMapping, + list_: list[int]) -> tuple[dict[int, tuple[str, bool]], list[int]]: dictionary = {} current = [] for index in list_: @@ -889,7 +899,7 @@ def GetProfileLists(self, mappingdictionary, list_): current.append(index) return dictionary, current - def GetCurrentNextMapIndex(self): + def GetCurrentNextMapIndex(self) -> int|None: if self.CurrentNode: index = 0x2000 while self.CurrentNode.IsEntry(index) and index < 0x5FFF: @@ -898,18 +908,18 @@ def GetCurrentNextMapIndex(self): return index return None - def CurrentDS302Defined(self): + def CurrentDS302Defined(self) -> bool: if self.CurrentNode: return len(self.CurrentNode.DS302) > 0 return False - def GetUnusedParameters(self): + def GetUnusedParameters(self) -> list[int]: node = self.CurrentNode if not node: raise ValueError("No node loaded") return node.GetUnusedParameters() - def RemoveParams(self, remove): + def RemoveParams(self, remove: list[int]): node = self.CurrentNode if not node: raise ValueError("No node loaded") @@ -922,17 +932,17 @@ def RemoveParams(self, remove): # Node State and Values Functions # -------------------------------------------------------------------------- - def GetCurrentNodeName(self): + def GetCurrentNodeName(self) -> str: if self.CurrentNode: return self.CurrentNode.Name return "" - def GetCurrentNodeID(self, node=None): # pylint: disable=unused-argument + def GetCurrentNodeID(self, node: Node|None = None) -> int|None: # pylint: disable=unused-argument if self.CurrentNode: return self.CurrentNode.ID return None - def GetCurrentNodeInfos(self): + def GetCurrentNodeInfos(self) -> tuple[str, int, str, str]: node = self.CurrentNode name = node.Name id_ = node.ID @@ -940,7 +950,7 @@ def GetCurrentNodeInfos(self): description = node.Description or "" return name, id_, type_, description - def SetCurrentNodeInfos(self, name, id_, type_, description): + def SetCurrentNodeInfos(self, name: str, id_: int, type_: str, description: str): node = self.CurrentNode node.Name = name node.ID = id_ @@ -948,35 +958,35 @@ def SetCurrentNodeInfos(self, name, id_, type_, description): node.Description = description self.BufferCurrentNode() - def GetCurrentNodeDefaultStringSize(self): + def GetCurrentNodeDefaultStringSize(self) -> int: if self.CurrentNode: return self.CurrentNode.DefaultStringSize return nodelib.Node.DefaultStringSize - def SetCurrentNodeDefaultStringSize(self, size): + def SetCurrentNodeDefaultStringSize(self, size: int): if self.CurrentNode: self.CurrentNode.DefaultStringSize = size else: nodelib.Node.DefaultStringSize = size - def GetCurrentProfileName(self): + def GetCurrentProfileName(self) -> str: if self.CurrentNode: return self.CurrentNode.ProfileName return "" - def IsCurrentEntry(self, index): + def IsCurrentEntry(self, index: int) -> bool: if self.CurrentNode: return self.CurrentNode.IsEntry(index) return False - def GetCurrentValidIndexes(self, min_, max_): + def GetCurrentValidIndexes(self, min_: int, max_: int) -> list[tuple[str, int]]: return [ (self.GetEntryName(index), index) for index in self.CurrentNode.GetIndexes() if min_ <= index <= max_ ] - def GetCurrentValidChoices(self, min_, max_): + def GetCurrentValidChoices(self, min_: int, max_: int) -> list[tuple[str, int|None],]: validchoices = [] exclusionlist = [] for menu, indexes in self.CurrentNode.SpecificMenu: @@ -997,17 +1007,17 @@ def GetCurrentValidChoices(self, min_, max_): validchoices.append((self.GetEntryName(index), index)) return validchoices - def HasCurrentEntryCallbacks(self, index): + def HasCurrentEntryCallbacks(self, index: int) -> bool: if self.CurrentNode: return self.CurrentNode.HasEntryCallbacks(index) return False - def GetCurrentEntryValues(self, index): + def GetCurrentEntryValues(self, index: int) -> tuple[list[dict], list[dict]]|None: 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 = [] @@ -1114,7 +1124,7 @@ def GetNodeEntryValues(self, node, index): return data, editors return None - def AddToDCF(self, node_id, index, subindex, size, value): + def AddToDCF(self, node_id: int, index: int, subindex: int, size: int, value: int): if self.CurrentNode.IsEntry(0x1F22, node_id): dcf_value = self.CurrentNode.GetEntry(0x1F22, node_id) if dcf_value: diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 5920627..dee5ff4 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -25,7 +25,9 @@ import wx.grid import objdictgen +from objdictgen import maps from objdictgen.maps import OD +from objdictgen.typing import TGetValues from objdictgen.node import Node from objdictgen.ui.exception import (display_error_dialog, display_exception_dialog) @@ -50,6 +52,10 @@ 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) @@ -168,15 +174,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, list_: list[int]): self.CurrentList = [] self.CurrentList.extend(list_) self.CurrentList.sort() - def GetCurrentList(self): + def GetCurrentList(self) -> list[int]: return self.CurrentList def RefreshLists(self): @@ -455,6 +461,12 @@ 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) @@ -578,7 +590,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 = [] @@ -619,7 +631,7 @@ def OnOK(self, event): # pylint: disable=unused-argument 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: @@ -1147,7 +1159,7 @@ 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(): @@ -1156,8 +1168,8 @@ def GetNMTManagement(self): return "Heartbeat" return None - def GetOptions(self): - options = [] + def GetOptions(self) -> list[str]: + options: list[str] = [] if self.DS302.GetValue(): options.append("DS302") if self.GenSYNC.GetValue(): @@ -1375,8 +1387,8 @@ 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: @@ -1400,7 +1412,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 @@ -1621,7 +1633,7 @@ 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: display_error_dialog(self, f"'{value}' is not a valid value!") wx.CallAfter(self.RefreshValues) diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index ba34493..fa957e3 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -27,6 +27,7 @@ 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 add_except_hook, display_exception_dialog from objdictgen.ui.networkeditortemplate import NetworkEditorTemplate @@ -67,6 +68,9 @@ class NetworkEdit(wx.Frame, 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): @@ -213,7 +217,7 @@ def _init_ctrls(self, prnt): 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) else: diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 824a7d9..e1bc8af 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -18,12 +18,18 @@ # 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, @@ -35,11 +41,12 @@ class NetworkEditorTemplate(net.NodeEditorTemplate): # pylint: disable=attribute-defined-outside-init def _init_ctrls(self, prnt): - self.NetworkNodes = wx.Notebook( + # 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=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 @@ -153,6 +160,6 @@ def OnRemoveSlaveMenu(self, event): # pylint: disable=unused-argument except Exception: # pylint: disable=broad-except display_exception_dialog(self.Frame) - def OpenMasterDCFDialog(self, node_id): + 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 3eb3266..0dd8727 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -21,6 +21,9 @@ import wx from objdictgen.maps import OD +from objdictgen.node import Node +from objdictgen.nodemanager import NodeManager +from objdictgen.ui import commondialogs as common from objdictgen.ui import commondialogs as cdia from objdictgen.ui.exception import (display_error_dialog, display_exception_dialog) @@ -29,10 +32,15 @@ class NodeEditorTemplate: """Template for the NodeEditor class.""" - EDITMENU_ID = None + EDITMENU_ID: int|None = None - def __init__(self, manager, frame, mode_solo): - self.Manager = manager + # Hints for typing + HelpBar: wx.StatusBar + EditMenu: wx.Menu + AddMenu: wx.Menu + + def __init__(self, manager: NodeManager, frame, mode_solo: bool): + self.Manager: NodeManager = manager self.Frame = frame self.ModeSolo = mode_solo @@ -165,7 +173,7 @@ def OnEditProfileMenu(self, event): # pylint: disable=unused-argument dictionary, current = self.Manager.GetCurrentProfileLists() self.EditProfile(title, dictionary, current) - def EditProfile(self, title, dictionary, current): + def EditProfile(self, title: str, dictionary: dict[int, tuple[str, bool]], current: list[int]): with cdia.CommunicationDialog(self.Frame) as dialog: dialog.SetTitle(title) dialog.SetIndexDictionary(dictionary) diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index a05d9ce..5257d58 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -30,8 +30,14 @@ 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 (add_except_hook, display_error_dialog, +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') @@ -207,11 +213,12 @@ def _init_ctrls(self, prnt): accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL, 83, wx.ID_SAVE)]) self.SetAcceleratorTable(accel) - self.FileOpened = wx.Notebook( + # 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, @@ -224,7 +231,7 @@ def _init_ctrls(self, prnt): 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, nodemanager.NodeManager(), self, True) diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index d9f4731..24a4d30 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -25,8 +25,11 @@ from objdictgen import maps from objdictgen.maps import OD +from objdictgen.nodemanager import NodeManager +from objdictgen.ui import commondialogs as common from objdictgen.ui import commondialogs as cdia 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 = [ @@ -108,7 +111,11 @@ 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 @@ -140,7 +147,7 @@ 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|None: # pylint: disable=unused-argument if row < self.GetNumberRows(): colname = self.GetColLabelValue(col, False) value = str(self.data[row].get(colname, "")) @@ -168,7 +175,7 @@ def SetValue(self, row, col, value): value = "None" self.data[row][colname] = value - def ResetView(self, grid): + def ResetView(self, grid: wx.grid.Grid): """ (wx.grid.Grid) -> Reset the grid view. Call this to update the grid if rows and columns have been added or deleted @@ -201,14 +208,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. @@ -233,7 +240,7 @@ def _updateColAttrs(self, grid): 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) @@ -295,7 +302,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): @@ -327,6 +334,9 @@ 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) @@ -479,15 +489,15 @@ def _init_ctrls(self, prnt): 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 = None for values in maps.INDEX_RANGES: text = f" 0x{values['min']:04X}-0x{values['max']:04X} {values['description']}" @@ -843,7 +853,7 @@ def OnAddToDCFSubindexMenu(self, event): # pylint: disable=unused-argument # 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)) @@ -944,3 +954,9 @@ def OnDefaultValueSubindexMenu(self, event): # pylint: disable=unused-argument 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] From 8200d2084467bf831d350e88bff79bcafc2d6d8c Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Tue, 2 Apr 2024 00:14:37 +0200 Subject: [PATCH 17/28] Minor code changes --- src/objdictgen/__main__.py | 12 +- src/objdictgen/eds_utils.py | 26 ++--- src/objdictgen/gen_cfile.py | 25 ++++- src/objdictgen/jsonod.py | 58 +++++----- src/objdictgen/maps.py | 18 +++ src/objdictgen/node.py | 55 +++++----- src/objdictgen/nodelist.py | 6 +- src/objdictgen/nodemanager.py | 122 ++++++++++----------- src/objdictgen/ui/commondialogs.py | 84 +++++++------- src/objdictgen/ui/networkedit.py | 27 ++--- src/objdictgen/ui/networkeditortemplate.py | 27 ++--- src/objdictgen/ui/nodeeditortemplate.py | 31 +++--- src/objdictgen/ui/objdictedit.py | 37 +++---- src/objdictgen/ui/subindextable.py | 16 +-- 14 files changed, 288 insertions(+), 256 deletions(-) diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 281974e..c30f0db 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -282,8 +282,8 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): od.GetPrintLine(k, unused=True) for k in sorted(to_remove) ] - for index in to_remove: - od.RemoveIndex(index) + for idx in to_remove: + od.RemoveIndex(idx) for line, fmt in info: print(line.format(**fmt)) @@ -300,7 +300,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = 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, ) @@ -339,9 +339,9 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # Get the indexes to print and determine the order keys = od.GetAllParameters(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_number(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(f"Unknown index {missing}") diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index a0df823..8eb5e3b 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -210,7 +210,7 @@ def parse_cpj_file(filepath: TPath): nodedcfname_result = RE_NODEDCFNAME.match(keyname.upper()) if keyname.upper() == "NETNAME": - if not is_string(computed_value): + if not isinstance(computed_value, str): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " f"of section '[{section_name}]'" @@ -218,7 +218,7 @@ def parse_cpj_file(filepath: TPath): topology["Name"] = computed_value elif keyname.upper() == "NODES": - if not is_integer(computed_value): + if not isinstance(computed_value, int): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " f"of section '[{section_name}]'" @@ -226,7 +226,7 @@ def parse_cpj_file(filepath: TPath): topology["Number"] = computed_value elif keyname.upper() == "EDSBASENAME": - if not is_string(computed_value): + if not isinstance(computed_value, str): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " f"of section '[{section_name}]'" @@ -234,34 +234,34 @@ def parse_cpj_file(filepath: TPath): topology["Path"] = computed_value elif nodepresent_result: - if not is_boolean(computed_value): + if not isinstance(computed_value, bool): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " "of section '[{section_name}]'" ) - nodeid = int(nodepresent_result.groups()[0]) + 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): + if not isinstance(computed_value, str): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " f"of section '[{section_name}]'" ) - nodeid = int(nodename_result.groups()[0]) + 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): + if not isinstance(computed_value, str): raise ValueError( f"Invalid value '{value}' for keyname '{keyname}' " f"of section '[{section_name}]'" ) - nodeid = int(nodedcfname_result.groups()[0]) + nodeid = int(nodedcfname_result[1]) if nodeid not in topology["Nodes"]: topology["Nodes"][nodeid] = {} topology["Nodes"][nodeid]["DCFName"] = computed_value @@ -325,7 +325,7 @@ def parse_eds_file(filepath: TPath) -> dict[str|int, Any]: # 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 @@ -509,7 +509,7 @@ def generate_eds_content(node: "Node", filepath: TPath): try: value = node.GetEntry(0x1018) except ValueError: - raise ValueError("Missing required Identity (0x1018) object") + raise ValueError("Missing required Identity (0x1018) object") from None # Generate FileInfo section fileContent = "[FileInfo]\n" @@ -610,7 +610,7 @@ def generate_eds_content(node: "Node", filepath: TPath): text += f"DataType=0x{subentry_infos['type']:04X}\n" text += f"AccessType={subentry_infos['access']}\n" if subentry_infos["type"] == 1: - text += f"DefaultValue={BOOL_TRANSLATE[values]}\n" + text += f"DefaultValue={BOOL_TRANSLATE[bool(values)]}\n" else: text += f"DefaultValue={values}\n" text += f"PDOMapping={BOOL_TRANSLATE[subentry_infos['pdo']]}\n" @@ -638,7 +638,7 @@ def generate_eds_content(node: "Node", filepath: TPath): 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[value]}\n" + subtext += f"DefaultValue={BOOL_TRANSLATE[bool(value)]}\n" else: subtext += f"DefaultValue={value}\n" subtext += f"PDOMapping={BOOL_TRANSLATE[subentry_infos['pdo']]}\n" diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 8ccfb37..a7fd597 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -97,16 +97,20 @@ def get_valid_type_infos(context, typename, items=None): return typeinfos -def compute_value(type_: str, value: TODValue) -> tuple[str, str]: +def compute_value(ctype: str, value: TODValue) -> tuple[str, str]: """Compute value for C file.""" - if type_ == "visible_string": + if ctype == "visible_string": return f'"{value}"', "" - if type_ == "domain": + 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 type_.startswith("real"): + if ctype.startswith("real"): return str(value), "" - # value is integer; make sure to handle negative numbers correctly + # 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} */" @@ -173,11 +177,17 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non 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 = get_valid_type_infos(context, typename) context.internal_types[rangename] = (typeinfos[0], typeinfos[1], f"valueRange_{num}") minvalue = node.GetEntry(index, 2) maxvalue = node.GetEntry(index, 3) + # 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[0]}, {minvalue} < value < {maxvalue} */" @@ -358,6 +368,7 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non "_obj%(index)04X_%(name)s%(suffix)s = " "%(value)s;%(comment)s\n" ) % texts + headerObjectDefinitionContent += ( f"\n#define {RE_NOTW.sub('_', texts['NodeName'])}" f"_{RE_NOTW.sub('_', texts['EntryName'])}_Idx {texts['index']:#04x}\n" @@ -373,6 +384,8 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non 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: @@ -414,6 +427,8 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non else: sizeof = f"sizeof ({typeinfos[0]})" 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: diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index ae399ef..57d2a30 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -165,21 +165,19 @@ 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: str) -> str: """ Remove jsonc annotations """ - # Copied from https://github.com/NickolaiBeloguzov/jsonc-parser/blob/master/jsonc_parser/parser.py#L11-L39 - def __re_sub(match): + 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,) # FIXME: Move to generic utils/funcs? @@ -238,7 +236,7 @@ def remove_underscore(d: T) -> T: def member_compare( - a: Iterable[str], + a: Iterable[str], *, must: set[str]|None = None, optional: set[str]|None = None, not_want: set[str]|None = None, @@ -737,7 +735,7 @@ def validate_nodeindex(node: "Node", index: int, obj): # B) Check baseobj object members is present member_compare( baseobj.keys(), - FIELDS_MAPPING_MUST, FIELDS_MAPPING_OPT | FIELDS_PARAMS_PROMOTE, + must=FIELDS_MAPPING_MUST, optional=FIELDS_MAPPING_OPT | FIELDS_PARAMS_PROMOTE, msg=' in mapping object' ) @@ -769,7 +767,7 @@ def validate_nodeindex(node: "Node", index: int, obj): for val in baseobj.get('values', []): member_compare( val.keys(), - FIELDS_MAPVALS_MUST, FIELDS_MAPVALS_OPT, + must=FIELDS_MAPVALS_MUST, optional=FIELDS_MAPVALS_OPT, msg=' in mapping values' ) @@ -805,8 +803,8 @@ def validate_nodeindex(node: "Node", index: int, obj): if isinstance(param, int): # Check that there are no unexpected fields in numbered param 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' ) @@ -819,7 +817,7 @@ def validate_nodeindex(node: "Node", index: int, obj): 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') @@ -1095,7 +1093,7 @@ def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str]|None = None 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): @@ -1124,7 +1122,7 @@ 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 object presense defs = 'must' # Parameter definition (FIELDS_MAPVALS_*) @@ -1170,7 +1168,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']: @@ -1208,12 +1206,15 @@ 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): @@ -1243,7 +1244,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): @@ -1333,18 +1334,23 @@ def _validate_dictionary(index, obj): raise -def diff_nodes(node1: "Node", node2: "Node", as_dict=True, validate=True) -> TDiffNodes: +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') diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 23b4da7..8eaadb5 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -17,10 +17,28 @@ # 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 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 diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 51c9c47..4abe239 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -149,7 +149,7 @@ def LoadFile(filepath: TPath) -> "Node": if Node.isXml(filepath): log.debug("Loading XML OD '%s'", filepath) with open(filepath, "r", encoding="utf-8") as f: - return nosis.xmlload(f) # type: ignore + return nosis.xmlload(f) if Node.isEds(filepath): log.debug("Loading EDS '%s'", filepath) @@ -307,7 +307,8 @@ def IsEntry(self, index: int, subindex: int=0) -> bool: if index in self.Dictionary: if not subindex: return True - return subindex <= len(self.Dictionary[index]) + dictval = self.Dictionary[index] + return isinstance(dictval, list) and subindex <= len(dictval) return False def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=False) -> list[TODValue]|TODValue: @@ -390,6 +391,9 @@ 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 AddMappingEntry(self, index: int, subindex: int|None = None, name="Undefined", struct=0, size=None, nbmax=None, @@ -528,12 +532,12 @@ def UpdateMapVariable(self, index: int, subindex: int, size: int): if (value & mask) == model: self.Dictionary[i][j] = model + size - def RemoveLine(self, index: int, max_: int, incr: int = 1): + 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 < max_ and self.IsEntry(i + incr): + while i < maxval and self.IsEntry(i + incr): self.Dictionary[i] = self.Dictionary[i + incr] i += incr self.Dictionary.pop(i) @@ -653,26 +657,26 @@ def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> T def GetEntryFlags(self, index: int) -> set[str]: """Return the flags for the given index""" - flags = [] + 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): @@ -755,9 +759,9 @@ def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: def IsStringType(self, index: int) -> bool: """Is the object index a string type?""" - if index in (0x9, 0xA, 0xB, 0xF): + if index in (0x9, 0xA, 0xB, 0xF): # VISIBLE_STRING, OCTET_STRING, UNICODE_STRING, DOMAIN return True - if 0xA0 <= index < 0x100: + if 0xA0 <= index < 0x100: # Custom types result = self.GetEntry(index, 1) if result in (0x9, 0xA, 0xB): return True @@ -765,9 +769,9 @@ def IsStringType(self, index: int) -> bool: def IsRealType(self, index: int) -> bool: """Is the object index a real (float) type?""" - if index in (0x8, 0x11): + if index in (0x8, 0x11): # REAL32, REAL64 return True - if 0xA0 <= index < 0x100: + if 0xA0 <= index < 0x100: # Custom types result = self.GetEntry(index, 1) if result in (0x8, 0x11): return True @@ -784,7 +788,8 @@ def GetTypeList(self) -> list[str]: list_.extend(self.FindTypeList(mapping)) return list_ - def GenerateMapName(self, name: str, index: int, subindex: int) -> str: + @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})" @@ -822,7 +827,8 @@ def GetMapValue(self, mapname: str) -> int: return (index << 16) + (subindex << 8) + size return None - def GetMapIndex(self, value: int) -> tuple[int, int, int]: + @staticmethod + def GetMapIndex(value: int) -> tuple[int, int, int]: """Return the index, subindex, size from a map value""" if value: index = value >> 16 @@ -844,11 +850,10 @@ def GetMapList(self) -> list[str]: """ Return the list of variables that can be mapped into pdos for the current node """ - list_ = ["None"] + [ + return ["None"] + [ self.GenerateMapName(name, index, subindex) for index, subindex, size, name in self.GetMapVariableList() ] - return list_ def GetAllParameters(self, sort=False) -> list[int]: """ Get a list of all indices. If node maintains a sort order, @@ -909,9 +914,7 @@ def _warn(text: str): log.warning("WARNING: 0x%04x (%d) '%s': %s", index, index, name, text) # Iterate over all the values and user parameters - params = set(self.Dictionary) - params.update(self.ParamsDictionary) - for index in params: + for index in set(self.Dictionary) | set(self.ParamsDictionary): # # Test if ParamDictionary exists without Dictionary @@ -924,7 +927,6 @@ def _warn(text: str): continue base = self.GetEntryInfos(index) - assert base # For mypy is_var = base["struct"] in (OD.VAR, OD.NVAR) # @@ -986,9 +988,10 @@ def GetPrintLine(self, index: int, 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) diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index 4008c94..b8f05cf 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -265,8 +265,8 @@ def main(projectdir): for line in node.GetPrintParams(raw=True): print(line) print() - for nodeid, node in nodelist.SlaveNodes.items(): - print(f"SlaveNode name={node['Name']} id=0x{nodeid:02X} :") - 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 bff2e37..4589db8 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -61,34 +61,31 @@ class UndoBuffer(Generic[T]): Class implementing a buffer of changes made on the current editing Object Dictionary """ - def __init__(self, currentstate: T|None = None, issaved: bool = False): + def __init__(self, state: T|None = None, issaved: bool = False): """ Constructor initialising buffer """ - self.Buffer: list[T|None] = [] + 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: T): + def Buffering(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: @@ -195,7 +192,7 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, Create a new node and add a new buffer for storing it """ # Create a new node - node = nodelib.Node() + node = Node() # Load profile given if profile != "None": # Import profile @@ -206,7 +203,7 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, else: # Default profile node.ProfileName = "None" - node.Profile = {} + node.Profile = ODMapping() node.SpecificMenu = [] # Initialising node self.CurrentNode = node @@ -238,9 +235,9 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, # 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) @@ -259,7 +256,7 @@ def OpenFileInCurrent(self, filepath: TPath, load=True) -> int: """ Open a file and store it in a new buffer """ - node = nodelib.Node.LoadFile(filepath) + node = Node.LoadFile(filepath) self.CurrentNode = node node.ID = 0 @@ -332,9 +329,10 @@ def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = None disable_buffer = node is not None if node is None: node = self.CurrentNode - assert node # For mypy # 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 @@ -351,7 +349,7 @@ def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = 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.AddEntry(index, length + i, 0) @@ -366,6 +364,8 @@ def RemoveSubentriesFromCurrent(self, index: int, number: int): # Informations about entry infos = self.GetEntryInfos(index) length = self.CurrentNode.GetEntry(index, 0) + # FIXME: This code assumes that subindex 0 is the length of the entry + assert isinstance(length, int) if "nbmin" in infos: nbmin = infos["nbmin"] else: @@ -547,14 +547,14 @@ def RemoveCurrentVariable(self, index: int, subindex: int|None = None): node.RemoveLine(index + 0x200, 0x1BFF) else: found = False - for _, list_ in node.SpecificMenu: - for i in list_: + for _, menulist in node.SpecificMenu: + for i in menulist: iinfos = self.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_: + for j in menulist: jinfos = self.GetEntryInfos(j) node.RemoveLine( j + diff, j + jinfos["incr"] * jinfos["nbmax"], jinfos["incr"] @@ -572,12 +572,11 @@ def AddMapVariableToCurrent(self, index: int, name: str, struct: int, number: in disable_buffer = node is not None if node is None: node = self.CurrentNode - assert node # For mypy if node.IsEntry(index): raise ValueError(f"Index 0x{index:04X} already defined!") node.AddMappingEntry(index, name=name, struct=struct) if struct == OD.VAR: - values = {"name": name, "type": 0x05, "access": "rw", "pdo": True} + values: TODSubObj = {"name": name, "type": 0x05, "access": "rw", "pdo": True} node.AddMappingEntry(index, 0, values=values) node.AddEntry(index, 0, 0) else: @@ -601,8 +600,7 @@ def AddMapVariableToCurrent(self, index: int, name: str, struct: int, number: in return raise ValueError(f"Index 0x{index:04X} isn't a valid index for Map Variable!") - def AddUserTypeToCurrent(self, type_: int, min_: int, max_: int, length: int): - assert self.CurrentNode # For mypy + def AddUserTypeToCurrent(self, objtype: int, minval: int, maxval: int, length: int): node = self.CurrentNode index = 0xA0 while index < 0x100 and node.IsEntry(index): @@ -867,11 +865,11 @@ def GetCurrentBufferState(self) -> tuple[bool, bool]: # -------------------------------------------------------------------------- def GetCurrentCommunicationLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: - list_ = [] + commlist = [] for index in maps.MAPPING_DICTIONARY: if 0x1000 <= index < 0x1200: - list_.append(index) - return self.GetProfileLists(maps.MAPPING_DICTIONARY, list_) + commlist.append(index) + return self.GetProfileLists(maps.MAPPING_DICTIONARY, commlist) def GetCurrentDS302Lists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: return self.GetSpecificProfileLists(self.CurrentNode.DS302) @@ -882,18 +880,18 @@ def GetCurrentProfileLists(self) -> tuple[dict[int, tuple[str, bool]], list[int] 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.CurrentNode.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: ODMapping, - list_: list[int]) -> tuple[dict[int, tuple[str, bool]], list[int]]: - dictionary = {} - current = [] - for index in list_: + profilelist: list[int]) -> tuple[dict[int, tuple[str, bool]], list[int]]: + dictionary: dict[int, tuple[str, bool]] = {} + current: list[int] = [] + for index in profilelist: dictionary[index] = (mappingdictionary[index]["name"], mappingdictionary[index]["need"]) if self.CurrentNode.IsEntry(index): current.append(index) @@ -945,16 +943,16 @@ def GetCurrentNodeID(self, node: Node|None = None) -> int|None: # pylint: disab def GetCurrentNodeInfos(self) -> tuple[str, int, str, str]: node = self.CurrentNode name = node.Name - id_ = node.ID - type_ = node.Type - description = node.Description or "" - return name, id_, type_, description + nodeid = node.ID + nodetype = node.Type + description = node.Description + return name, nodeid, nodetype, description - def SetCurrentNodeInfos(self, name: str, id_: int, type_: str, description: str): - node = self.CurrentNode + def SetCurrentNodeInfos(self, name: str, nodeid: int, nodetype: str, description: str): + node = self.current node.Name = name - node.ID = id_ - node.Type = type_ + node.ID = nodeid + node.Type = nodetype node.Description = description self.BufferCurrentNode() @@ -979,11 +977,11 @@ def IsCurrentEntry(self, index: int) -> bool: return self.CurrentNode.IsEntry(index) return False - def GetCurrentValidIndexes(self, min_: int, max_: int) -> list[tuple[str, int]]: + def GetCurrentValidIndexes(self, minval: int, maxval: int) -> list[tuple[str, int]]: return [ (self.GetEntryName(index), index) for index in self.CurrentNode.GetIndexes() - if min_ <= index <= max_ + if minval <= index <= maxval ] def GetCurrentValidChoices(self, min_: int, max_: int) -> list[tuple[str, int|None],]: @@ -993,7 +991,7 @@ def GetCurrentValidChoices(self, min_: int, max_: int) -> list[tuple[str, int|No exclusionlist.extend(indexes) good = True for index in indexes: - good &= min_ <= index <= max_ + good &= minval <= index <= maxval if good: validchoices.append((menu, None)) list_ = [index for index in maps.MAPPING_DICTIONARY if index >= 0x1000] @@ -1020,17 +1018,18 @@ def GetCurrentEntryValues(self, index: int) -> tuple[list[dict], list[dict]]|Non 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 "" @@ -1056,6 +1055,7 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d 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: @@ -1073,6 +1073,8 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d editor["value"] = "map" dic["value"] = node.GetMapName(dic["value"]) else: + # 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") ): @@ -1091,13 +1093,12 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d 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.format(dic["value"]) @@ -1105,28 +1106,27 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d 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: int, index: int, subindex: int, size: int, value: int): - if self.CurrentNode.IsEntry(0x1F22, node_id): - dcf_value = self.CurrentNode.GetEntry(0x1F22, node_id) + # FIXME: This code assumes that the DCF value is a list + assert isinstance(dcf_value, list) if dcf_value: nbparams = nodelib.Node.be_to_le(dcf_value[:4]) else: diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index dee5ff4..9890efb 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -121,9 +121,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)) @@ -177,9 +177,9 @@ def __init__(self, parent): def SetIndexDictionary(self, dictionary: dict[int, tuple[str, bool]]): self.IndexDictionary = dictionary - def SetCurrentList(self, list_: list[int]): + def SetCurrentList(self, currentlist: list[int]): self.CurrentList = [] - self.CurrentList.extend(list_) + self.CurrentList.extend(currentlist) self.CurrentList.sort() def GetCurrentList(self) -> list[int]: @@ -290,9 +290,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_MAPVARIABLEDIALOG, - name='CommunicationDialog', parent=prnt, pos=wx.Point(376, 223), + name='CommunicationDialog', parent=parent, pos=wx.Point(376, 223), size=wx.Size(444, 186), style=wx.DEFAULT_DIALOG_STYLE, title='Add Map Variable', ) @@ -522,9 +522,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_USERTYPEDIALOG, name='UserTypeDialog', - parent=prnt, pos=wx.Point(376, 223), size=wx.Size(444, 210), + 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)) @@ -639,16 +639,16 @@ def SetValues(self, min=None, max=None, length=None): 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): @@ -683,11 +683,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 # ------------------------------------------------------------------------------ @@ -749,9 +749,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_NODEINFOSDIALOG, - name='NodeInfosDialog', parent=prnt, pos=wx.Point(376, 223), + name='NodeInfosDialog', parent=parent, pos=wx.Point(376, 223), size=wx.Size(300, 280), style=wx.DEFAULT_DIALOG_STYLE, title='Node infos', ) @@ -845,20 +845,20 @@ def OnOK(self, event): # pylint: disable=unused-argument 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(f"0x{id_:02X}") - 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 # ------------------------------------------------------------------------------ @@ -970,9 +970,9 @@ 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), + name='CreateNodeDialog', parent=parent, pos=wx.Point(376, 223), size=wx.Size(450, 350), style=wx.DEFAULT_DIALOG_STYLE, title='Create a new Node', ) @@ -1151,9 +1151,9 @@ 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() @@ -1166,7 +1166,7 @@ def GetNMTManagement(self) -> str: return "NodeGuarding" if self.NMT_Heartbeat.GetValue(): return "Heartbeat" - return None + return "" def GetOptions(self) -> list[str]: options: list[str] = [] @@ -1258,9 +1258,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_ADDSLAVEDIALOG, - name='AddSlaveDialog', parent=prnt, pos=wx.Point(376, 223), + 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', ) @@ -1334,11 +1334,11 @@ def OnOK(self, event): # pylint: disable=unused-argument 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) display_error_dialog(self, "Slave Node ID must be a value in decimal or hexadecimal!") @@ -1390,11 +1390,11 @@ def SetNodeList(self, nodelist): 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 @@ -1563,9 +1563,9 @@ 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), + 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', ) diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index fa957e3..08c9def 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -64,7 +64,7 @@ def usage(): ] = [wx.NewId() for _ in range(6)] -class NetworkEdit(wx.Frame, NetworkEditorTemplate): +class NetworkEdit(NetworkEditorTemplate): """Network Editor UI.""" # pylint: disable=attribute-defined-outside-init @@ -193,10 +193,10 @@ def _init_utils(self): self._init_coll_EditMenu_Items(self.EditMenu) self._init_coll_AddMenu_Items(self.AddMenu) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Frame.__init__( self, id=ID_NETWORKEDIT, name='networkedit', - parent=prnt, pos=wx.Point(149, 178), size=wx.Size(1000, 700), + parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700), style=wx.DEFAULT_FRAME_STYLE, title='Networkedit', ) self._init_utils() @@ -219,9 +219,9 @@ def _init_ctrls(self, prnt): 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) icon = wx.Icon( @@ -254,8 +254,6 @@ def __init__(self, parent, nodelist: nl.NodeList|None = 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 @@ -280,7 +278,7 @@ def OnNewProjectMenu(self, event): # pylint: disable=unused-argument if os.path.isdir(projectpath) and len(os.listdir(projectpath)) == 0: manager = NodeManager() - nodelist = NodeList(manager) + nodelist = nl.NodeList(manager) try: nodelist.LoadProject(projectpath) @@ -310,7 +308,7 @@ def OnOpenProjectMenu(self, event): # pylint: disable=unused-argument if os.path.isdir(projectpath): manager = NodeManager() - nodelist = NodeList(manager) + nodelist = nl.NodeList(manager) try: nodelist.LoadProject(projectpath) @@ -327,13 +325,10 @@ def OnOpenProjectMenu(self, event): # pylint: disable=unused-argument 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: # pylint: disable=broad-except - display_exception_dialog(self) + 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: diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index e1bc8af..790d00b 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -36,15 +36,15 @@ ] = [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): + 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=prnt, pos=wx.Point(0, 0), + name='NetworkNodes', parent=parent, pos=wx.Point(0, 0), size=wx.Size(0, 0), style=wx.NB_LEFT, )) self.NetworkNodes.Bind( @@ -52,9 +52,9 @@ def _init_ctrls(self, prnt): 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__(self, nodelist: NodeList, mode_solo: bool): + self.NodeList = nodelist + NodeEditorTemplate.__init__(self, self.NodeList.Manager, mode_solo) def GetCurrentNodeId(self): selected = self.NetworkNodes.GetSelection() @@ -74,12 +74,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 = EditingPanel(self.NetworkNodes, self, self.Manager) new_editingpanel.SetIndex(self.Manager.GetCurrentNodeID()) self.NetworkNodes.AddPage(new_editingpanel, "") for idx in self.NodeList.GetSlaveIDs(): # FIXME: Why is NodeList used where NodeManager is expected? - new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) + new_editingpanel = EditingPanel(self.NetworkNodes, self, self.NodeList, False) new_editingpanel.SetIndex(idx) self.NetworkNodes.AddPage(new_editingpanel, "") @@ -90,9 +90,10 @@ def OnNodeSelectedChanged(self, event): if selected >= 0: window = self.NetworkNodes.GetPage(selected) self.NodeList.CurrentSelected = window.GetIndex() - raise NotImplementedError("Missing unknown symbol. Please report this issue.") - # 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() # -------------------------------------------------------------------------- @@ -115,7 +116,7 @@ def RefreshBufferState(self): # -------------------------------------------------------------------------- def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument - with cdia.AddSlaveDialog(self.Frame) as dialog: + with AddSlaveDialog(self.Frame) as dialog: dialog.SetNodeList(self.NodeList) if dialog.ShowModal() != wx.ID_OK: return @@ -126,7 +127,7 @@ def OnAddSlaveMenu(self, event): # pylint: disable=unused-argument values["slaveName"], values["slaveNodeID"], values["edsFile"], ) # FIXME: Why is NodeList used where NodeManager is expected? - new_editingpanel = sit.EditingPanel(self.NetworkNodes, self, self.NodeList, False) + 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, "") diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index 0dd8727..b9c5770 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -29,7 +29,7 @@ display_exception_dialog) -class NodeEditorTemplate: +class NodeEditorTemplate(wx.Frame): """Template for the NodeEditor class.""" EDITMENU_ID: int|None = None @@ -39,12 +39,11 @@ class NodeEditorTemplate: EditMenu: wx.Menu AddMenu: wx.Menu - def __init__(self, manager: NodeManager, frame, mode_solo: bool): + def __init__(self, manager: NodeManager, mode_solo: bool): self.Manager: NodeManager = manager - self.Frame = frame + self.Frame = self self.ModeSolo = mode_solo - - self.BusId = None + self.BusId = None # FIXME: Is this used? EditingPanel.OnSubindexGridCellLeftClick can seem to indicate it is iterable self.Closing = False def GetBusId(self): @@ -88,13 +87,13 @@ 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): + 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 = manager.GetEntryInfos(index) + entryinfos = node.GetEntryInfos(index) name = entryinfos["name"] category = "Optional" if entryinfos["need"]: @@ -174,7 +173,7 @@ def OnEditProfileMenu(self, event): # pylint: disable=unused-argument self.EditProfile(title, dictionary, current) def EditProfile(self, title: str, dictionary: dict[int, tuple[str, bool]], current: list[int]): - with cdia.CommunicationDialog(self.Frame) as dialog: + with common.CommunicationDialog(self.Frame) as dialog: dialog.SetTitle(title) dialog.SetIndexDictionary(dictionary) dialog.SetCurrentList(current) @@ -205,13 +204,13 @@ def profile_cb(event): # pylint: disable=unused-argument # -------------------------------------------------------------------------- def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument - dialog = cdia.NodeInfosDialog(self.Frame) - name, id_, type_, description = self.Manager.GetCurrentNodeInfos() + dialog = common.NodeInfosDialog(self.Frame) + name, nodeid, nodetype, description = self.Manager.GetCurrentNodeInfos() defaultstringsize = self.Manager.GetCurrentNodeDefaultStringSize() - dialog.SetValues(name, id_, type_, description, 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) + name, nodeid, nodetype, description, defaultstringsize = dialog.GetValues() + self.Manager.SetCurrentNodeInfos(name, nodeid, nodetype, description) self.Manager.SetCurrentNodeDefaultStringSize(defaultstringsize) self.RefreshBufferState() self.RefreshCurrentIndexList() @@ -224,7 +223,7 @@ def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument def AddMapVariable(self): index = self.Manager.GetCurrentNextMapIndex() if index: - with cdia.MapVariableDialog(self.Frame) as dialog: + with common.MapVariableDialog(self.Frame) as dialog: dialog.SetIndex(index) if dialog.ShowModal() == wx.ID_OK: try: @@ -237,7 +236,7 @@ def AddMapVariable(self): display_error_dialog(self.Frame, "No map variable index left!") def AddUserType(self): - with cdia.UserTypeDialog(self) as dialog: + with common.UserTypeDialog(self) as dialog: dialog.SetTypeList(self.Manager.GetCustomisableTypes()) if dialog.ShowModal() == wx.ID_OK: try: diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 5257d58..7a4baef 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -22,6 +22,7 @@ import logging import os import sys +from typing import cast import wx @@ -72,7 +73,7 @@ def usage(): ] = [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 @@ -198,10 +199,10 @@ def _init_utils(self): self._init_coll_EditMenu_Items(self.EditMenu) self._init_coll_AddMenu_Items(self.AddMenu) - def _init_ctrls(self, prnt): + def _init_ctrls(self, parent): wx.Frame.__init__( self, id=ID_OBJDICTEDIT, name='objdictedit', - parent=prnt, pos=wx.Point(149, 178), size=wx.Size(1000, 700), + parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700), style=wx.DEFAULT_FRAME_STYLE, title='Objdictedit', ) self._init_utils() @@ -234,9 +235,9 @@ def _init_ctrls(self, prnt): def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPath]|None = None): filesopen = filesopen or [] if manager is None: - net.NodeEditorTemplate.__init__(self, nodemanager.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( @@ -249,15 +250,15 @@ def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPa 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, "") @@ -296,8 +297,6 @@ 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(): with wx.MessageDialog( @@ -394,20 +393,20 @@ def RefreshBufferState(self): def OnNewMenu(self, event): # pylint: disable=unused-argument # self.FilePath = "" - with cdia.CreateNodeDialog(self) as dialog: + with CreateNodeDialog(self) as dialog: if dialog.ShowModal() != wx.ID_OK: return - name, id_, nodetype, description = dialog.GetValues() + name, nodeid, nodetype, description = dialog.GetValues() profile, filepath = dialog.GetProfile() nmt = dialog.GetNMTManagement() options = dialog.GetOptions() try: index = self.Manager.CreateNewNode( - name=name, id=id_, type=nodetype, description=description, + name=name, id=nodeid, type=nodetype, description=description, profile=profile, filepath=filepath, nmt=nmt, options=options, ) - 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, "") self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) @@ -439,7 +438,7 @@ def OnOpenMenu(self, event): # pylint: disable=unused-argument if os.path.isfile(filepath): try: index = self.Manager.OpenFileInCurrent(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, "") self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) @@ -455,11 +454,7 @@ def OnOpenMenu(self, event): # pylint: disable=unused-argument 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() @@ -545,7 +540,7 @@ def OnImportEDSMenu(self, event): # pylint: disable=unused-argument if os.path.isfile(filepath): try: index = self.Manager.OpenFileInCurrent(filepath, load=False) - 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, "") self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 24a4d30..cfbc0ba 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -147,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) -> str|None: # 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(): @@ -417,9 +417,9 @@ 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), + name='MainSplitter', parent=parent, pos=wx.Point(0, 0), size=wx.Size(-1, -1), style=wx.SP_3D) self._init_utils() @@ -497,7 +497,7 @@ def __init__(self, parent, window: NodeEditorTemplate, manager: NodeManager, edi self.ListIndex: list[int] = [] self.ChoiceIndex: list[int] = [] self.FirstCall = False - self.Index: int = None + self.Index: int = 0 for values in maps.INDEX_RANGES: text = f" 0x{values['min']:04X}-0x{values['max']:04X} {values['description']}" @@ -718,7 +718,7 @@ def ShowDCFEntryDialog(self, row, col): if selected != wx.NOT_FOUND: index = self.ListIndex[selected] if self.Manager.IsCurrentEntry(index): - dialog = cdia.DCFEntryValuesDialog(self, self.Editable) + 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() @@ -891,8 +891,8 @@ def OnModifyIndexMenu(self, event): # pylint: disable=unused-argument 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() From 1152b0c8a6f6c0805a3ed60a3316add12a028437 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Tue, 2 Apr 2024 00:39:06 +0200 Subject: [PATCH 18/28] Refactor jsonod * Added typing support --- src/objdictgen/__main__.py | 6 +- src/objdictgen/jsonod.py | 621 +++++++++++++++++++++---------------- src/objdictgen/node.py | 51 ++- tests/test_jsonod.py | 63 ++++ 4 files changed, 452 insertions(+), 289 deletions(-) create mode 100644 tests/test_jsonod.py diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index c30f0db..ed9423d 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -264,7 +264,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # 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: @@ -272,7 +272,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # Drop all other indexes than specified if opts.index: - index = [jsonod.str_to_number(i) for i in opts.index] + index = [jsonod.str_to_int(i) for i in opts.index] to_remove |= (set(od.GetAllParameters()) - set(index)) # Have any parameters to delete? @@ -339,7 +339,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # Get the indexes to print and determine the order keys = od.GetAllParameters(sort=not opts.asis) if opts.index: - indexp = [jsonod.str_to_number(i) for i in opts.index] + 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: diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 57d2a30..0484c98 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -192,9 +192,17 @@ def exc_amend(exc: Exception, text: str) -> Exception: return exc -def str_to_number(string: str|int|float|None) -> str|int|float|None: - """ 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'): @@ -281,14 +289,18 @@ def get_object_types( ) -> 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(): + i2s: dict[int, str] = {} + s2i: dict[str, int] = {} + for mapping in mappinglist: + for k, v in mapping.items(): if k >= 0x1000: continue n = v['name'] @@ -298,12 +310,14 @@ def get_object_types( 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: @@ -338,8 +352,8 @@ def compare_profile(profilename: TPath, params: ODMapping, menu: TProfileMenu|No return False, False -def generate_json(node: "Node", compact=False, sort=False, internal=False, validate=True) -> str: - """ 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( @@ -367,7 +381,7 @@ 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: @@ -392,21 +406,24 @@ def _index_repl(m: re.Match[str]) -> str: 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 @@ -414,10 +431,16 @@ def generate_node(contents: str|TODJson) -> "Node": 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) + + return node_fromdict(jd, objtypes_s2i) def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=True) -> tuple[TODJson, dict[str, int]]: @@ -442,49 +465,22 @@ def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=Tr can be used for display purposes. """ - # 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.__version__), - '$date': datetime.isoformat(datetime.now()), - }) - - # Get the order for the indexes - order = node.GetAllParameters(sort=sort) - # 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.GetAllParameters(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"] = f"0x{index:04X}" if rich else index + # Get the internal dict representation of the object, termed "index entry" + ientry = node.GetIndexDict(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 @@ -492,87 +488,51 @@ def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=Tr # 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, 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) @@ -580,11 +540,14 @@ def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=Tr return jd, objtypes_s2i -def node_todict_parameter(obj, node, index): +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: # ============= # - 'callback' might be set in the mapping. If it is, then the @@ -606,72 +569,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"] - # Is the definition for the parameter present? - if not is_repeat: + # New output object + obj: TODObjJson = { + "index": index, + } + odobj: TODObj # The OD object (set below) + + # 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(f"Mapping data not empty. Programming error?. Contains: {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 @@ -679,36 +656,104 @@ 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(f"User parameters not empty. Programming error? Contains: {params}") + raise ValidationError(f"User parameters not empty. Contains: {params}") return obj -def validate_nodeindex(node: "Node", index: int, 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_indexentry(ientry: TIndexEntry): """ Validate index dict contents (see Node.GetIndexDict). The purpose is to validate the assumptions in the data format. @@ -716,55 +761,53 @@ def validate_nodeindex(node: "Node", index: int, obj): 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(), + 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: 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(), must=FIELDS_MAPVALS_MUST, optional=FIELDS_MAPVALS_OPT, @@ -772,22 +815,30 @@ def validate_nodeindex(node: "Node", index: int, obj): ) # 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: @@ -797,11 +848,16 @@ def validate_nodeindex(node: "Node", index: int, obj): # 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(), must=set(), optional=FIELDS_PARAMS, @@ -822,10 +878,10 @@ def validate_nodeindex(node: "Node", index: int, obj): 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 @@ -859,27 +915,13 @@ def validate_nodeindex(node: "Node", index: int, obj): raise ValidationError(f"Unexpexted count of subindexes in mapping object, found {len(nbmax)}") -def node_fromdict(jd: TODJson, internal=False) -> "Node": +def node_fromdict(jd: TODJson, objtypes_s2i: dict[str, int]) -> "Node": """ 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") - # Create the node and fill the most basic data node = nodelib.Node( - name=jd["name"], type=jd["type"], id=jd["id"], - description=jd["description"], profilename=jd["profile"], + name=jd["name"], type=jd["type"], id=jd.get("id", 0), + description=jd["description"], profilename=jd.get("profile", "None"), ) # Restore optional values @@ -887,107 +929,127 @@ def node_fromdict(jd: TODJson, internal=False) -> "Node": 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, 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%04x (%s) Difference between built-in object and imported:", index, index) for line in diff.pretty().splitlines(): log.debug(' %s', line) raise ValidationError( - f"Built-in parameter index 0x{index:04x} ({index}) " + f"Built-in object index 0x{index:04x} ({index}) " "does not match against system parameters" ) - # 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 = [obj["index"] for obj in jd['dictionary']] - return node -def node_fromdict_parameter(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TIndexEntry: +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. """ - # -- STEP 1a) -- + # This function is mutating obj, so we need to copy it + obj = copy.deepcopy(obj) + + # -- 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 ] @@ -996,27 +1058,27 @@ def node_fromdict_parameter(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TI 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} @@ -1024,34 +1086,49 @@ def node_fromdict_parameter(obj: TODObjJson, objtypes_s2i: dict[str, int]) -> TI # 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}") + + return ientry -def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str]|None = None, objtypes_s2i: dict[str, int]|None = None): +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 """ @@ -1325,7 +1402,7 @@ def _validate_dictionary(index, obj): raise ValidationError(f"Item number {num} of 'dictionary' is not a dict") sindex = obj.get('index', f'item {num}') - index = str_to_number(sindex) + index = str_to_int(sindex) try: _validate_dictionary(index, obj) @@ -1355,9 +1432,11 @@ def diff_nodes(node1: "Node", node2: "Node", asdict=True, validate=True) -> TDif "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', ''))) @@ -1367,10 +1446,10 @@ def diff_nodes(node1: "Node", node2: "Node", asdict=True, validate=True) -> TDif 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: @@ -1378,7 +1457,7 @@ def diff_nodes(node1: "Node", node2: "Node", asdict=True, validate=True) -> TDif 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') @@ -1386,7 +1465,7 @@ def diff_nodes(node1: "Node", node2: "Node", asdict=True, validate=True) -> TDif 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/node.py b/src/objdictgen/node.py index 4abe239..19ddd72 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -196,7 +196,7 @@ def DumpFile(self, filepath: TPath, filetype: str = "json", **kwargs): def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> str: """ Dump the node into a JSON string """ - return jsonod.generate_json( + return jsonod.generate_jsonc( self, compact=compact, sort=sort, internal=internal, validate=validate ) @@ -204,11 +204,14 @@ def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> # Node Informations Functions # -------------------------------------------------------------------------- - def GetMappings(self, userdefinedtoo: bool=True) -> ODMappingList: + 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: - return [self.Profile, self.DS302, self.UserMapping] - return [self.Profile, self.DS302] + mapping.append(self.UserMapping) + if withmapping: + mapping.append(maps.MAPPING_DICTIONARY) + return mapping def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None) -> bool: """ @@ -554,21 +557,39 @@ def GetDict(self) -> dict[str, Any]: def GetIndexDict(self, index: int) -> TIndexEntry: """ Return a full and raw representation of the index """ - obj = {} + + 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] + + objmaps = list(_mapping_for_index(index)) + firstobj: TODObj = objmaps[0][1] if objmaps else {} + + obj: TIndexEntry = { + "index": index, + "groups": list(n for n, _ in objmaps), + } + + 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) + + baseindex = self.GetBaseIndex(index) + if index != baseindex: + obj['base'] = baseindex + baseobject = next(_mapping_for_index(baseindex)) + obj['basestruct'] = baseobject[1]["struct"] + + # Ensure that the object is safe to mutate return copy.deepcopy(obj) def GetIndexes(self): 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 From 968242abed3025adca7fcc758fafea94dc17c531 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Tue, 2 Apr 2024 01:32:07 +0200 Subject: [PATCH 19/28] Clean move of functions from node to maps (BROKEN CODE) --- src/objdictgen/maps.py | 404 +++++++++++++++++++++++++++++++++++++++++ src/objdictgen/node.py | 397 ---------------------------------------- 2 files changed, 404 insertions(+), 397 deletions(-) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 8eaadb5..d10204a 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -134,6 +134,231 @@ def from_string(cls: type["ODStructTypes"], val: str, default: int|None = None) {"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) + + +@staticmethod +def eval_value(value, base, nodeid, compute=True): + """ + 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 = Node.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 = Node.evaluate_expression(value, + { # These are the vars that can be used within the string + 'nodeid': nodeid, + } + ) + + return value + +@staticmethod +def eval_name(text, idx, sub): + """ + 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.group(1) % Node.evaluate_expression( + result.group(2).strip(), + { # These are the vars that can be used in the string + 'idx': idx, + 'sub': sub, + } + ) + +@staticmethod +def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): + """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): + """ + 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(f"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 +# ------------------------------------------------------------------------------ + +@staticmethod +def ImportProfile(profilename): + + # 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 # 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(f"Loading profile '{profilepath}' failed: {exc}") from exc + + +@staticmethod +def get_index_range(index): + for irange in maps.INDEX_RANGES: + if irange["min"] <= index <= irange["max"]: + return irange + raise ValueError(f"Cannot find index range for value '0x{index:x}'") + + +@staticmethod +def be_to_le(value): + """ + Convert Big Endian to Little Endian + @param value: value expressed in Big Endian + @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 + 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) + +@staticmethod +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 # ------------------------------------------------------------------------------ @@ -141,6 +366,185 @@ def from_string(cls: type["ODStructTypes"], val: str, default: int|None = None) class ODMapping(UserDict[int, TODObj]): """Object Dictionary Mapping.""" + @staticmethod + def FindTypeIndex(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) + + @staticmethod + def FindTypeName(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 + + @staticmethod + def FindTypeDefaultValue(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 + + @staticmethod + def FindTypeList(mappingdictionary): + """ + Return the list of types defined in mappingdictionary + """ + return [ + mappingdictionary[index]["name"] + for index in mappingdictionary + if index < 0x1000 + ] + + @staticmethod + def FindEntryName(index, mappingdictionary, compute=True): + """ + Return the name of an entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(index, mappingdictionary) + if base_index: + infos = mappingdictionary[base_index] + if infos["struct"] & OD.IdenticalIndexes and compute: + return Node.eval_name( + infos["name"], idx=(index - base_index) // infos["incr"] + 1, sub=0 + ) + return infos["name"] + return None + + @staticmethod + def FindEntryInfos(index, mappingdictionary, compute=True): + """ + Return the informations of one entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(index, mappingdictionary) + if base_index: + obj = mappingdictionary[base_index].copy() + if obj["struct"] & OD.IdenticalIndexes and compute: + obj["name"] = Node.eval_name( + obj["name"], idx=(index - base_index) // obj["incr"] + 1, sub=0 + ) + obj.pop("values") + return obj + return None + + @staticmethod + def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): + """ + Return the informations of one subentry of an entry by searching in mappingdictionary + """ + base_index = Node.FindIndex(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"] = Node.eval_name( + infos["name"], idx=(index - base_index) // incr + 1, sub=subindex + ) + + return infos + return None + + @staticmethod + def FindMapVariableList(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 = Node.eval_name(computed_name, idx=1, sub=i + 1) + yield (index, i + 1, infos["size"], computed_name) + else: + computed_name = name + if compute: + computed_name = Node.eval_name(computed_name, idx=1, sub=subindex) + yield (index, subindex, infos["size"], computed_name) + + @staticmethod + def FindMandatoryIndexes(mappingdictionary): + """ + Return the list of mandatory indexes defined in mappingdictionary + """ + return [ + index + for index in mappingdictionary + if index >= 0x1000 and mappingdictionary[index]["need"] + ] + + @staticmethod + def FindIndex(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 + + # + # 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.""" diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 19ddd72..20f8f1d 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -18,17 +18,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -import ast import copy import logging -import re -import traceback -from pathlib import Path from typing import Any, Generator, Iterable, Iterator, Self 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 OD, ODMapping, ODMappingList @@ -40,14 +35,6 @@ Fore = colorama.Fore Style = colorama.Style -# 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) - # ------------------------------------------------------------------------------ # Definition of Node Object @@ -1125,390 +1112,6 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve if not compact and infos: yield "" - # -------------------------------------------------------------------------- - # Load mapping - # -------------------------------------------------------------------------- - - @staticmethod - def ImportProfile(profilename): - - # 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 # 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(f"Loading profile '{profilepath}' failed: {exc}") from exc - - # -------------------------------------------------------------------------- - # Evaluation of values - # -------------------------------------------------------------------------- - - @staticmethod - def eval_value(value, base, nodeid, compute=True): - """ - 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 = Node.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 = Node.evaluate_expression(value, - { # These are the vars that can be used within the string - 'nodeid': nodeid, - } - ) - - return value - - @staticmethod - def eval_name(text, idx, sub): - """ - 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.group(1) % Node.evaluate_expression( - result.group(2).strip(), - { # These are the vars that can be used in the string - 'idx': idx, - 'sub': sub, - } - ) - - @staticmethod - def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): - """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): - """ - 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(f"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 - - @staticmethod - def get_index_range(index): - for irange in maps.INDEX_RANGES: - if irange["min"] <= index <= irange["max"]: - return irange - raise ValueError(f"Cannot find index range for value '0x{index:x}'") - - @staticmethod - def be_to_le(value): - """ - Convert Big Endian to Little Endian - @param value: value expressed in Big Endian - @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 - 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) - - @staticmethod - 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]) - - # -------------------------------------------------------------------------- - # Search in a Mapping Dictionary - # -------------------------------------------------------------------------- - - @staticmethod - def FindTypeIndex(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) - - @staticmethod - def FindTypeName(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 - - @staticmethod - def FindTypeDefaultValue(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 - - @staticmethod - def FindTypeList(mappingdictionary): - """ - Return the list of types defined in mappingdictionary - """ - return [ - mappingdictionary[index]["name"] - for index in mappingdictionary - if index < 0x1000 - ] - - @staticmethod - def FindEntryName(index, mappingdictionary, compute=True): - """ - Return the name of an entry by searching in mappingdictionary - """ - base_index = Node.FindIndex(index, mappingdictionary) - if base_index: - infos = mappingdictionary[base_index] - if infos["struct"] & OD.IdenticalIndexes and compute: - return Node.eval_name( - infos["name"], idx=(index - base_index) // infos["incr"] + 1, sub=0 - ) - return infos["name"] - return None - - @staticmethod - def FindEntryInfos(index, mappingdictionary, compute=True): - """ - Return the informations of one entry by searching in mappingdictionary - """ - base_index = Node.FindIndex(index, mappingdictionary) - if base_index: - obj = mappingdictionary[base_index].copy() - if obj["struct"] & OD.IdenticalIndexes and compute: - obj["name"] = Node.eval_name( - obj["name"], idx=(index - base_index) // obj["incr"] + 1, sub=0 - ) - obj.pop("values") - return obj - return None - - @staticmethod - def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): - """ - Return the informations of one subentry of an entry by searching in mappingdictionary - """ - base_index = Node.FindIndex(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"] = Node.eval_name( - infos["name"], idx=(index - base_index) // incr + 1, sub=subindex - ) - - return infos - return None - - @staticmethod - def FindMapVariableList(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 = Node.eval_name(computed_name, idx=1, sub=i + 1) - yield (index, i + 1, infos["size"], computed_name) - else: - computed_name = name - if compute: - computed_name = Node.eval_name(computed_name, idx=1, sub=subindex) - yield (index, subindex, infos["size"], computed_name) - - @staticmethod - def FindMandatoryIndexes(mappingdictionary): - """ - Return the list of mandatory indexes defined in mappingdictionary - """ - return [ - index - for index in mappingdictionary - if index >= 0x1000 and mappingdictionary[index]["need"] - ] - - @staticmethod - def FindIndex(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 - # Register node with gnosis nosis.add_class_to_store('Node', Node) From 39a2b5d5c2285297f908c8f7aae8991690da4c14 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Wed, 3 Apr 2024 22:15:27 +0200 Subject: [PATCH 20/28] Modifications to get the moved functions working again --- src/objdictgen/eds_utils.py | 2 +- src/objdictgen/jsonod.py | 2 +- src/objdictgen/maps.py | 35 +++++++++-------------- src/objdictgen/node.py | 54 +++++++++++++++++------------------ src/objdictgen/nodemanager.py | 18 ++++++------ src/objdictgen/typing.py | 2 +- tests/test_node.py | 8 +++--- 7 files changed, 56 insertions(+), 65 deletions(-) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 8eb5e3b..ad8fb52 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -736,7 +736,7 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": try: # Import profile profilename = f"DS-{profilenb}" - mapping, menuentries = nodelib.Node.ImportProfile(profilename) + mapping, menuentries = maps.import_profile(profilename) node.ProfileName = profilename node.Profile = mapping node.SpecificMenu = menuentries diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 0484c98..f664cbe 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -338,7 +338,7 @@ def compare_profile(profilename: TPath, params: ODMapping, menu: TProfileMenu|No identical is True if the profile is identical with the givens params. """ try: - dsmap, menumap = nodelib.Node.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) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index d10204a..09eaae2 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -147,7 +147,6 @@ def from_string(cls: type["ODStructTypes"], val: str, default: int|None = None) RE_NODEID = re.compile(r'\$NODEID\b', re.IGNORECASE) -@staticmethod def eval_value(value, base, nodeid, compute=True): """ Evaluate the value. They can be strings that needs additional @@ -161,7 +160,7 @@ def eval_value(value, base, nodeid, compute=True): # This will remove any surrouning quotes on strings ('"$NODEID+0x20"') # and will resolve "{True:"$NODEID..." expressions. - value = Node.evaluate_expression(value, + value = evaluate_expression(value, { # These are the vars that can be used within the string 'base': base, } @@ -172,7 +171,7 @@ def eval_value(value, base, nodeid, compute=True): value = RE_NODEID.sub("nodeid", value) # This will resolve '$NODEID' expressions - value = Node.evaluate_expression(value, + value = evaluate_expression(value, { # These are the vars that can be used within the string 'nodeid': nodeid, } @@ -180,7 +179,6 @@ def eval_value(value, base, nodeid, compute=True): return value -@staticmethod def eval_name(text, idx, sub): """ Format the text given with the index and subindex defined. @@ -193,7 +191,7 @@ def eval_name(text, idx, sub): # NOTE: Legacy Python2 format evaluations are baked # into the OD and must be supported for legacy - return result.group(1) % Node.evaluate_expression( + return result.group(1) % evaluate_expression( result.group(2).strip(), { # These are the vars that can be used in the string 'idx': idx, @@ -201,7 +199,6 @@ def eval_name(text, idx, sub): } ) -@staticmethod def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): """Parses a string expression and attempts to calculate the result Supports: @@ -273,8 +270,7 @@ def _evnode(node: ast.AST): # Misc functions # ------------------------------------------------------------------------------ -@staticmethod -def ImportProfile(profilename): +def import_profile(profilename): # Test if the profilename is a filepath which can be used directly. If not # treat it as the name @@ -309,16 +305,12 @@ def ImportProfile(profilename): log.debug(traceback.format_exc()) raise ValueError(f"Loading profile '{profilepath}' failed: {exc}") from exc - -@staticmethod def get_index_range(index): - for irange in maps.INDEX_RANGES: + for irange in INDEX_RANGES: if irange["min"] <= index <= irange["max"]: return irange raise ValueError(f"Cannot find index range for value '0x{index:x}'") - -@staticmethod def be_to_le(value): """ Convert Big Endian to Little Endian @@ -336,7 +328,6 @@ def be_to_le(value): # different than the output (int) return int("".join([f"{ord(char):02X}" for char in reversed(value)]), 16) -@staticmethod def le_to_be(value, size): """ Convert Little Endian to Big Endian @@ -411,11 +402,11 @@ def FindEntryName(index, mappingdictionary, compute=True): """ Return the name of an entry by searching in mappingdictionary """ - base_index = Node.FindIndex(index, mappingdictionary) + base_index = ODMapping.FindIndex(index, mappingdictionary) if base_index: infos = mappingdictionary[base_index] if infos["struct"] & OD.IdenticalIndexes and compute: - return Node.eval_name( + return eval_name( infos["name"], idx=(index - base_index) // infos["incr"] + 1, sub=0 ) return infos["name"] @@ -426,11 +417,11 @@ def FindEntryInfos(index, mappingdictionary, compute=True): """ Return the informations of one entry by searching in mappingdictionary """ - base_index = Node.FindIndex(index, mappingdictionary) + base_index = ODMapping.FindIndex(index, mappingdictionary) if base_index: obj = mappingdictionary[base_index].copy() if obj["struct"] & OD.IdenticalIndexes and compute: - obj["name"] = Node.eval_name( + obj["name"] = eval_name( obj["name"], idx=(index - base_index) // obj["incr"] + 1, sub=0 ) obj.pop("values") @@ -442,7 +433,7 @@ def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): """ Return the informations of one subentry of an entry by searching in mappingdictionary """ - base_index = Node.FindIndex(index, mappingdictionary) + base_index = ODMapping.FindIndex(index, mappingdictionary) if base_index: struct = mappingdictionary[base_index]["struct"] if struct & OD.Subindex: @@ -473,7 +464,7 @@ def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): incr = mappingdictionary[base_index]["incr"] else: incr = 1 - infos["name"] = Node.eval_name( + infos["name"] = eval_name( infos["name"], idx=(index - base_index) // incr + 1, sub=subindex ) @@ -496,12 +487,12 @@ def FindMapVariableList(mappingdictionary, node, compute=True): for i in range(len(values) - 1): computed_name = name if compute: - computed_name = Node.eval_name(computed_name, idx=1, sub=i + 1) + computed_name = eval_name(computed_name, idx=1, sub=i + 1) yield (index, i + 1, infos["size"], computed_name) else: computed_name = name if compute: - computed_name = Node.eval_name(computed_name, idx=1, sub=subindex) + computed_name = eval_name(computed_name, idx=1, sub=subindex) yield (index, subindex, infos["size"], computed_name) @staticmethod diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 20f8f1d..9999526 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -314,10 +314,10 @@ def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=F if subindex is None: if isinstance(self.Dictionary[index], list): return [len(self.Dictionary[index])] + [ - self.eval_value(value, base, nodeid, compute) + maps.eval_value(value, base, nodeid, compute) for value in self.Dictionary[index] ] - result = self.eval_value(self.Dictionary[index], base, nodeid, compute) + result = maps.eval_value(self.Dictionary[index], base, nodeid, compute) # This option ensures that the function consistently returns a list if aslist: return [result] @@ -325,9 +325,9 @@ def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=F if subindex == 0: if isinstance(self.Dictionary[index], list): return len(self.Dictionary[index]) - return self.eval_value(self.Dictionary[index], base, nodeid, compute) + return maps.eval_value(self.Dictionary[index], base, nodeid, compute) if isinstance(self.Dictionary[index], list) and 0 < subindex <= len(self.Dictionary[index]): - return self.eval_value(self.Dictionary[index][subindex - 1], base, nodeid, compute) + return maps.eval_value(self.Dictionary[index][subindex - 1], base, nodeid, compute) raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") def GetParamsEntry(self, index: int, subindex: int|None = None, @@ -593,18 +593,18 @@ def GetIndexes(self): def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ for mapping in self.GetMappings(): - result = self.FindIndex(index, mapping) + result = ODMapping.FindIndex(index, mapping) if result: return result - return self.FindIndex(index, maps.MAPPING_DICTIONARY) + return ODMapping.FindIndex(index, maps.MAPPING_DICTIONARY) def GetBaseIndexNumber(self, index: int) -> int: """ Return the index number from the base object """ for mapping in self.GetMappings(): - result = self.FindIndex(index, mapping) + result = ODMapping.FindIndex(index, mapping) if result is not None: return (index - result) // mapping[result].get("incr", 1) - result = self.FindIndex(index, maps.MAPPING_DICTIONARY) + result = ODMapping.FindIndex(index, maps.MAPPING_DICTIONARY) if result is not None: return (index - result) // maps.MAPPING_DICTIONARY[result].get("incr", 1) return 0 @@ -623,10 +623,10 @@ def GetEntryName(self, index: int, compute=True) -> str: mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindEntryName(index, mappings[i], compute) + result = ODMapping.FindEntryName(index, mappings[i], compute) i += 1 if result is None: - result = self.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) + result = ODMapping.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) return result def GetEntryInfos(self, index: int, compute=True) -> TODObj: @@ -635,9 +635,9 @@ def GetEntryInfos(self, index: int, compute=True) -> TODObj: mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindEntryInfos(index, mappings[i], compute) + result = ODMapping.FindEntryInfos(index, mappings[i], compute) i += 1 - r301 = self.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) + r301 = ODMapping.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -650,11 +650,11 @@ def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> T mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindSubentryInfos(index, subindex, mappings[i], compute) + result = ODMapping.FindSubentryInfos(index, subindex, mappings[i], compute) if result: result["user_defined"] = i == len(mappings) - 1 and index >= 0x1000 i += 1 - r301 = self.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) + r301 = ODMapping.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if r301: if result is not None: r301.update(result) @@ -707,10 +707,10 @@ def GetTypeIndex(self, typename: str) -> int: mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindTypeIndex(typename, mappings[i]) + result = ODMapping.FindTypeIndex(typename, mappings[i]) i += 1 if result is None: - result = self.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) + result = ODMapping.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) return result def GetTypeName(self, typeindex: int) -> str: @@ -719,10 +719,10 @@ def GetTypeName(self, typeindex: int) -> str: mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindTypeName(typeindex, mappings[i]) + result = ODMapping.FindTypeName(typeindex, mappings[i]) i += 1 if result is None: - result = self.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) + result = ODMapping.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) return result def GetTypeDefaultValue(self, typeindex: int) -> TODValue: @@ -731,26 +731,26 @@ def GetTypeDefaultValue(self, typeindex: int) -> TODValue: mappings = self.GetMappings() i = 0 while not result and i < len(mappings): - result = self.FindTypeDefaultValue(typeindex, mappings[i]) + result = ODMapping.FindTypeDefaultValue(typeindex, mappings[i]) i += 1 if result is None: - result = self.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) + result = ODMapping.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) return result 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.""" - list_ = list(self.FindMapVariableList(maps.MAPPING_DICTIONARY, self, compute)) + list_ = list(ODMapping.FindMapVariableList(maps.MAPPING_DICTIONARY, self, compute)) for mapping in self.GetMappings(): - list_.extend(self.FindMapVariableList(mapping, self, compute)) + list_.extend(ODMapping.FindMapVariableList(mapping, self, compute)) list_.sort() return list_ def GetMandatoryIndexes(self, node: "Node|None" = None) -> list[int]: # pylint: disable=unused-argument """Return the mandatory indexes for the node.""" - list_ = self.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) + list_ = ODMapping.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): - list_.extend(self.FindMandatoryIndexes(mapping)) + list_.extend(ODMapping.FindMandatoryIndexes(mapping)) return list_ def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: @@ -791,9 +791,9 @@ def IsRealType(self, index: int) -> bool: def GetTypeList(self) -> list[str]: """Return a list of all object types available for the current node""" - list_ = self.FindTypeList(maps.MAPPING_DICTIONARY) + list_ = ODMapping.FindTypeList(maps.MAPPING_DICTIONARY) for mapping in self.GetMappings(): - list_.extend(self.FindTypeList(mapping)) + list_.extend(ODMapping.FindTypeList(mapping)) return list_ @staticmethod @@ -1030,7 +1030,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve continue # Print the parameter range header - ir = self.get_index_range(k) + ir = maps.get_index_range(k) if index_range != ir: index_range = ir if not compact: diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index 4589db8..db411ab 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -196,7 +196,7 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, # Load profile given if profile != "None": # Import profile - mapping, menuentries = nodelib.Node.ImportProfile(filepath) + mapping, menuentries = maps.import_profile(filepath) node.ProfileName = profile node.Profile = mapping node.SpecificMenu = menuentries @@ -220,7 +220,7 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, for option in options: if option == "DS302": # Import profile - mapping, menuentries = nodelib.Node.ImportProfile("DS-302") + mapping, menuentries = maps.import_profile("DS-302") node.DS302 = mapping node.SpecificMenu.extend(menuentries) elif option == "GenSYNC": @@ -1154,17 +1154,17 @@ def GetCustomisedTypeValues(self, index): def GetEntryName(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryName(index, compute) - return nodelib.Node.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) + return ODMapping.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) def GetEntryInfos(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryInfos(index, compute) - return nodelib.Node.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) + return ODMapping.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) def GetSubentryInfos(self, index, subindex, compute=True): if self.CurrentNode: return self.CurrentNode.GetSubentryInfos(index, subindex, compute) - result = nodelib.Node.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) + result = ODMapping.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) if result: result["user_defined"] = False return result @@ -1172,17 +1172,17 @@ def GetSubentryInfos(self, index, subindex, compute=True): def GetTypeIndex(self, typename): if self.CurrentNode: return self.CurrentNode.GetTypeIndex(typename) - return nodelib.Node.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) + return ODMapping.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) def GetTypeName(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeName(typeindex) - return nodelib.Node.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) + return ODMapping.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) def GetTypeDefaultValue(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeDefaultValue(typeindex) - return nodelib.Node.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) + return ODMapping.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) def GetMapVariableList(self, compute=True): if self.CurrentNode: @@ -1192,7 +1192,7 @@ def GetMapVariableList(self, compute=True): def GetMandatoryIndexes(self): if self.CurrentNode: return self.CurrentNode.GetMandatoryIndexes() - return nodelib.Node.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) + return ODMapping.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) def GetCustomisableTypes(self): return { diff --git a/src/objdictgen/typing.py b/src/objdictgen/typing.py index bbd4eba..a466bad 100644 --- a/src/objdictgen/typing.py +++ b/src/objdictgen/typing.py @@ -57,7 +57,7 @@ }, total=False) """Dict-like type for the object dictionary mappings.""" -# See Node.ImportProfile +# See maps.import_profile TProfileMenu = list[tuple[str, list[int]]] """Type for the profile menu entries.""" diff --git a/tests/test_node.py b/tests/test_node.py index 3432599..9ee9d35 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,11 +1,11 @@ import pytest -from objdictgen.node import Node +from objdictgen import maps def test_node_eval_value(): - dut = Node.eval_value + dut = maps.eval_value assert dut("1234", 0, 0, True) == "1234" assert dut("'$NODEID+1'", 0, 0, False) == "$NODEID+1" @@ -18,7 +18,7 @@ def test_node_eval_value(): def test_node_eval_name(): - dut = 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" @@ -45,7 +45,7 @@ def test_node_eval_name(): def test_node_evaluate_expression(): - dut = Node.evaluate_expression + dut = maps.evaluate_expression # BinOp assert dut("4+3") == 7 From ae36205707f8a29c8a5e38d05acda46461074dd8 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sat, 6 Apr 2024 11:09:33 +0200 Subject: [PATCH 21/28] Reorder functions without any changes --- src/objdictgen/maps.py | 62 ++-- src/objdictgen/node.py | 690 ++++++++++++++++++++--------------------- 2 files changed, 377 insertions(+), 375 deletions(-) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 09eaae2..10d8476 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -179,6 +179,7 @@ def eval_value(value, base, nodeid, compute=True): return value + def eval_name(text, idx, sub): """ Format the text given with the index and subindex defined. @@ -199,6 +200,7 @@ def eval_name(text, idx, sub): } ) + def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): """Parses a string expression and attempts to calculate the result Supports: @@ -357,6 +359,25 @@ def le_to_be(value, size): class ODMapping(UserDict[int, TODObj]): """Object Dictionary Mapping.""" + @staticmethod + def FindIndex(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 + @staticmethod def FindTypeIndex(typename, mappingdictionary): """ @@ -397,6 +418,17 @@ def FindTypeList(mappingdictionary): if index < 0x1000 ] + @staticmethod + def FindMandatoryIndexes(mappingdictionary): + """ + Return the list of mandatory indexes defined in mappingdictionary + """ + return [ + index + for index in mappingdictionary + if index >= 0x1000 and mappingdictionary[index]["need"] + ] + @staticmethod def FindEntryName(index, mappingdictionary, compute=True): """ @@ -495,36 +527,6 @@ def FindMapVariableList(mappingdictionary, node, compute=True): computed_name = eval_name(computed_name, idx=1, sub=subindex) yield (index, subindex, infos["size"], computed_name) - @staticmethod - def FindMandatoryIndexes(mappingdictionary): - """ - Return the list of mandatory indexes defined in mappingdictionary - """ - return [ - index - for index in mappingdictionary - if index >= 0x1000 and mappingdictionary[index]["need"] - ] - - @staticmethod - def FindIndex(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 - # # HELPERS # diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 9999526..28c14d0 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -112,6 +112,11 @@ def __init__( self.UserMapping: ODMapping = ODMapping() self.IndexOrder: list[int] = [] + + # -------------------------------------------------------------------------- + # Dunders + # -------------------------------------------------------------------------- + # -------------------------------------------------------------------------- # Node Input/Output # -------------------------------------------------------------------------- @@ -187,6 +192,16 @@ def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> self, compact=compact, sort=sort, internal=internal, validate=validate ) + def GetDict(self) -> dict[str, Any]: + """ Return the class data as a dict """ + return copy.deepcopy(self.__dict__) + + def Copy(self) -> Self: + """ + Return a copy of the node + """ + return copy.deepcopy(self) + # -------------------------------------------------------------------------- # Node Informations Functions # -------------------------------------------------------------------------- @@ -200,107 +215,6 @@ def GetMappings(self, userdefinedtoo: bool=True, withmapping=False) -> ODMapping mapping.append(maps.MAPPING_DICTIONARY) return mapping - def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None) -> bool: - """ - Add a new entry in the Object Dictionary - """ - 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 - - def SetEntry(self, index: int, subindex: int|None = None, value: TODValue|None = None) -> bool: - """Modify an existing entry in the Object Dictionary""" - 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 - - def SetParamsEntry(self, index: int, subindex: int|None = None, comment=None, buffer_size=None, save=None, callback=None) -> bool: - """Set parameter values for an entry in the Object Dictionary.""" - 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 RemoveEntry(self, index: int, subindex: int|None = None) -> bool: - """ - 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: - 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 - - 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 GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=False) -> list[TODValue]|TODValue: """ Returns the value of the entry specified by the index and subindex. If @@ -368,227 +282,48 @@ def GetParamsEntry(self, index: int, subindex: int|None = None, return result raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - 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 and "callback" in self.ParamsDictionary[index]: - return self.ParamsDictionary[index]["callback"] - return False + def GetIndexDict(self, index: int) -> TIndexEntry: + """ Return a full and raw representation of the index """ - 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 _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 AddMappingEntry(self, index: int, subindex: int|None = None, name="Undefined", struct=0, size=None, nbmax=None, - default=None, values=None) -> bool: - """ - 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 + objmaps = list(_mapping_for_index(index)) + firstobj: TODObj = objmaps[0][1] if objmaps else {} - def SetMappingEntry(self, index: int, subindex: int|None = None, name=None, struct=None, size=None, nbmax=None, default=None, values=None) -> bool: + obj: TIndexEntry = { + "index": index, + "groups": list(n for n, _ in objmaps), + } + + 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] + + baseindex = self.GetBaseIndex(index) + if index != baseindex: + obj['base'] = baseindex + baseobject = next(_mapping_for_index(baseindex)) + obj['basestruct'] = baseobject[1]["struct"] + + # Ensure that the object is safe to mutate + return copy.deepcopy(obj) + + def GetIndexes(self): """ - Modify an existing entry in the User Mapping Dictionary + Return a sorted list of indexes in Object Dictionary """ - 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 - - def RemoveMappingEntry(self, index: int, subindex: int|None = None) -> bool: - """ - 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 - - 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 - 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 - - 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 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 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 Copy(self) -> Self: - """ - Return a copy of the node - """ - return copy.deepcopy(self) - - def GetDict(self) -> dict[str, Any]: - """ Return the class data as a dict """ - return copy.deepcopy(self.__dict__) - - def GetIndexDict(self, index: int) -> TIndexEntry: - """ Return a full and raw representation of the index """ - - 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] - - objmaps = list(_mapping_for_index(index)) - firstobj: TODObj = objmaps[0][1] if objmaps else {} - - obj: TIndexEntry = { - "index": index, - "groups": list(n for n, _ in objmaps), - } - - 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] - - baseindex = self.GetBaseIndex(index) - if index != baseindex: - obj['base'] = baseindex - baseobject = next(_mapping_for_index(baseindex)) - obj['basestruct'] = baseobject[1]["struct"] - - # Ensure that the object is safe to mutate - return copy.deepcopy(obj) - - def GetIndexes(self): - """ - Return a sorted list of indexes in Object Dictionary - """ - return list(sorted(self.Dictionary)) - - - # -------------------------------------------------------------------------- - # Node Informations Functions - # -------------------------------------------------------------------------- + return list(sorted(self.Dictionary)) def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ @@ -761,34 +496,6 @@ def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: for index, valuetype in maps.CUSTOMISABLE_TYPES } - # -------------------------------------------------------------------------- - # 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 - - # -------------------------------------------------------------------------- - # Type and Map Variable Lists - # -------------------------------------------------------------------------- - def GetTypeList(self) -> list[str]: """Return a list of all object types available for the current node""" list_ = ODMapping.FindTypeList(maps.MAPPING_DICTIONARY) @@ -897,6 +604,299 @@ def GetUnusedParameters(self): if k not in self.Dictionary ] + # -------------------------------------------------------------------------- + # 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 and "callback" in self.ParamsDictionary[index]: + return self.ParamsDictionary[index]["callback"] + return False + + # -------------------------------------------------------------------------- + # Node mutuation functions + # -------------------------------------------------------------------------- + + def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None) -> bool: + """ + Add a new entry in the Object Dictionary + """ + 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 + + def SetEntry(self, index: int, subindex: int|None = None, value: TODValue|None = None) -> bool: + """Modify an existing entry in the Object Dictionary""" + 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 + + def SetParamsEntry(self, index: int, subindex: int|None = None, comment=None, buffer_size=None, save=None, callback=None) -> bool: + """Set parameter values for an entry in the Object Dictionary.""" + 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 RemoveEntry(self, index: int, subindex: int|None = None) -> bool: + """ + 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: + 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 + + def AddMappingEntry(self, index: int, subindex: int|None = None, name="Undefined", struct=0, size=None, nbmax=None, + default=None, values=None) -> bool: + """ + 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: int, subindex: int|None = None, name=None, struct=None, size=None, nbmax=None, default=None, values=None) -> bool: + """ + Modify an existing entry in the User Mapping Dictionary + """ + 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 + + def RemoveMappingEntry(self, index: int, subindex: int|None = None) -> bool: + """ + 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 + + 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 + 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 + + 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 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 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) -> None: """ Remove the given index""" self.UserMapping.pop(index, None) From 9daa5f3b110d9fa3841fedb7cf7ab49cb9bfb9de Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sat, 6 Apr 2024 13:59:36 +0200 Subject: [PATCH 22/28] Add index_range into an object --- src/objdictgen/maps.py | 88 ++++++++++++++++++------------ src/objdictgen/node.py | 6 +- src/objdictgen/ui/commondialogs.py | 21 ++++--- src/objdictgen/ui/subindextable.py | 15 +++-- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 10d8476..27d6be4 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -117,22 +117,44 @@ def from_string(cls: type["ODStructTypes"], val: str, default: int|None = 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 @@ -307,18 +329,13 @@ def import_profile(profilename): log.debug(traceback.format_exc()) raise ValueError(f"Loading profile '{profilepath}' failed: {exc}") from exc -def get_index_range(index): - for irange in INDEX_RANGES: - if irange["min"] <= index <= irange["max"]: - return irange - raise ValueError(f"Cannot find index range for value '0x{index:x}'") def be_to_le(value): - """ - Convert Big Endian to Little Endian - @param value: value expressed in Big Endian - @param size: number of bytes generated - @return: a string containing the value converted + """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 @@ -326,16 +343,17 @@ def be_to_le(value): # 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) + # # 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 + :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 @@ -344,12 +362,12 @@ def le_to_be(value, size): # 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]) + # # 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]) # ------------------------------------------------------------------------------ diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 28c14d0..4626cdf 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -1030,11 +1030,11 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve continue # Print the parameter range header - ir = maps.get_index_range(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) @@ -1055,7 +1055,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve # 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'): index, subindex, _ = self.GetMapIndex(value) pdo = self.GetSubentryInfos(index, subindex) suffix = '???' if value else '' diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 9890efb..55d2f24 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -28,7 +28,6 @@ from objdictgen import maps from objdictgen.maps import OD from objdictgen.typing import TGetValues -from objdictgen.node import Node from objdictgen.ui.exception import (display_error_dialog, display_exception_dialog) @@ -1688,13 +1687,13 @@ def SetValues(self, values): if values: data = values[4:] current = 0 - for _ in range(Node.be_to_le(values[:4])): + for _ in range(maps.be_to_le(values[:4])): value = {} - value["Index"] = Node.be_to_le(data[current:current + 2]) - value["Subindex"] = Node.be_to_le(data[current + 2:current + 3]) - size = Node.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"] = Node.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() @@ -1703,12 +1702,12 @@ 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 = Node.le_to_be(len(self.Values), 4) + value = maps.le_to_be(len(self.Values), 4) for row in self.Values: - value += Node.le_to_be(row["Index"], 2) - value += Node.le_to_be(row["Subindex"], 1) - value += Node.le_to_be(row["Size"], 4) - value += Node.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): diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index cfbc0ba..2ab003f 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -27,7 +27,6 @@ from objdictgen.maps import OD from objdictgen.nodemanager import NodeManager from objdictgen.ui import commondialogs as common -from objdictgen.ui import commondialogs as cdia from objdictgen.ui.exception import display_error_dialog from objdictgen.ui.nodeeditortemplate import NodeEditorTemplate @@ -500,7 +499,7 @@ def __init__(self, parent, window: NodeEditorTemplate, manager: NodeManager, edi self.Index: int = 0 for values in maps.INDEX_RANGES: - text = f" 0x{values['min']:04X}-0x{values['max']:04X} {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) @@ -641,7 +640,7 @@ 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"]): + 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: @@ -656,12 +655,12 @@ 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(f"0x{index:04X} {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) + self.ChoiceIndex.append(cindex or 0) if (choiceindex != wx.NOT_FOUND and choiceindex < self.IndexChoice.GetCount() and choice == self.IndexChoice.GetString(choiceindex) @@ -884,7 +883,7 @@ def OnModifyIndexMenu(self, event): # pylint: disable=unused-argument index = self.ListIndex[selected] if self.Manager.IsCurrentEntry(index) and index < 0x260: values, valuetype = self.Manager.GetCustomisedTypeValues(index) - dialog = cdia.UserTypeDialog(self) + dialog = common.UserTypeDialog(self) dialog.SetTypeList(self.Manager.GetCustomisableTypes(), values[1]) if valuetype == 0: dialog.SetValues(min=values[2], max=values[3]) From 90a21bf58149defa5fe3ef08824d6a9d79ad95d1 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sat, 6 Apr 2024 18:40:55 +0200 Subject: [PATCH 23/28] Refactor ODMapping and ODMappingList into class methods * Simplify Find* methods --- src/objdictgen/maps.py | 376 ++++++++++++++++++------------- src/objdictgen/node.py | 134 ++++------- src/objdictgen/nodemanager.py | 22 +- src/objdictgen/ui/objdictedit.py | 5 - 4 files changed, 279 insertions(+), 258 deletions(-) diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 27d6be4..6eda035 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -377,173 +377,146 @@ def le_to_be(value, size): class ODMapping(UserDict[int, TODObj]): """Object Dictionary Mapping.""" - @staticmethod - def FindIndex(index, mappingdictionary): - """ - Return the index of the informations in the Object Dictionary in case of identical - indexes - """ - if index in mappingdictionary: + 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 - 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"] + 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 - return None - - @staticmethod - def FindTypeIndex(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) - - @staticmethod - def FindTypeName(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 - - @staticmethod - def FindTypeDefaultValue(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 - - @staticmethod - def FindTypeList(mappingdictionary): - """ - Return the list of types defined in mappingdictionary - """ + 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 [ - mappingdictionary[index]["name"] - for index in mappingdictionary + self[index]["name"] + for index in self if index < 0x1000 ] - @staticmethod - def FindMandatoryIndexes(mappingdictionary): - """ - Return the list of mandatory indexes defined in mappingdictionary - """ + def FindMandatoryIndexes(self) -> list[int]: + """Return a list of all mandatory objects""" return [ index - for index in mappingdictionary - if index >= 0x1000 and mappingdictionary[index]["need"] + for index in self + if index >= 0x1000 and self[index]["need"] ] - @staticmethod - def FindEntryName(index, mappingdictionary, compute=True): - """ - Return the name of an entry by searching in mappingdictionary - """ - base_index = ODMapping.FindIndex(index, mappingdictionary) - if base_index: - infos = mappingdictionary[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"] - return None - - @staticmethod - def FindEntryInfos(index, mappingdictionary, compute=True): - """ - Return the informations of one entry by searching in mappingdictionary - """ - base_index = ODMapping.FindIndex(index, mappingdictionary) - if base_index: - obj = mappingdictionary[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 - return None - - @staticmethod - def FindSubentryInfos(index, subindex, mappingdictionary, compute=True): - """ - Return the informations of one subentry of an entry by searching in mappingdictionary - """ - base_index = ODMapping.FindIndex(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"] + 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: - incr = 1 - infos["name"] = eval_name( - infos["name"], idx=(index - base_index) // incr + 1, sub=subindex - ) - - return infos - return None + 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") - @staticmethod - def FindMapVariableList(mappingdictionary, node, compute=True): + def FindMapVariableList(self, node: "Node", compute=True) -> Generator[tuple[int, int, int, str], None, None]: """ - Return the list of variables that can be mapped defined in mappingdictionary + Generator of all variables that can be mapped to in pdos. + It yields tuple of (index, subindex, size, name) """ - 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 = eval_name(computed_name, idx=1, sub=i + 1) - yield (index, i + 1, infos["size"], computed_name) - else: - computed_name = name - if compute: - computed_name = eval_name(computed_name, idx=1, sub=subindex) - yield (index, subindex, infos["size"], computed_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 @@ -559,6 +532,105 @@ def find(self, predicate: Callable[[int, TODObj], bool|int]) -> Generator[tuple[ 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 diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 4626cdf..7fea8d9 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -117,6 +117,14 @@ def __init__( # Dunders # -------------------------------------------------------------------------- + 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) + + # -------------------------------------------------------------------------- # Node Input/Output # -------------------------------------------------------------------------- @@ -327,22 +335,11 @@ def GetIndexes(self): def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ - for mapping in self.GetMappings(): - result = ODMapping.FindIndex(index, mapping) - if result: - return result - return ODMapping.FindIndex(index, maps.MAPPING_DICTIONARY) + return self.GetMappings(withmapping=True).FindBaseIndex(index) def GetBaseIndexNumber(self, index: int) -> int: """ Return the index number from the base object """ - for mapping in self.GetMappings(): - result = ODMapping.FindIndex(index, mapping) - if result is not None: - return (index - result) // mapping[result].get("incr", 1) - result = ODMapping.FindIndex(index, maps.MAPPING_DICTIONARY) - if result is not None: - return (index - result) // maps.MAPPING_DICTIONARY[result].get("incr", 1) - return 0 + 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 @@ -354,48 +351,34 @@ def GetCustomisedTypeValues(self, index: int) -> tuple[list[TODValue], int]: def GetEntryName(self, index: int, compute=True) -> str: """Return the entry name for the given index""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindEntryName(index, mappings[i], compute) - i += 1 - if result is None: - result = ODMapping.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) - return result + return self.GetMappings(withmapping=True).FindEntryName(index, compute) def GetEntryInfos(self, index: int, compute=True) -> TODObj: """Return the entry infos for the given index""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindEntryInfos(index, mappings[i], compute) - i += 1 - r301 = ODMapping.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) - if r301: - if result is not None: - r301.update(result) + # 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: int, subindex: int, compute: bool = True) -> TODSubObj: """Return the subentry infos for the given index and subindex""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindSubentryInfos(index, subindex, mappings[i], compute) - if result: - result["user_defined"] = i == len(mappings) - 1 and index >= 0x1000 - i += 1 - r301 = ODMapping.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) - if r301: - if result is not None: - r301.update(result) - else: - r301["user_defined"] = False + # 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: int) -> set[str]: @@ -438,55 +421,25 @@ def GetAllSubentryInfos(self, index, compute=True): def GetTypeIndex(self, typename: str) -> int: """Return the type index for the given type name.""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindTypeIndex(typename, mappings[i]) - i += 1 - if result is None: - result = ODMapping.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) - return result + return self.GetMappings(withmapping=True).FindTypeIndex(typename) def GetTypeName(self, typeindex: int) -> str: """Return the type name for the given type index.""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindTypeName(typeindex, mappings[i]) - i += 1 - if result is None: - result = ODMapping.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) - return result + return self.GetMappings(withmapping=True).FindTypeName(typeindex) def GetTypeDefaultValue(self, typeindex: int) -> TODValue: """Return the default value for the given type index.""" - result = None - mappings = self.GetMappings() - i = 0 - while not result and i < len(mappings): - result = ODMapping.FindTypeDefaultValue(typeindex, mappings[i]) - i += 1 - if result is None: - result = ODMapping.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) - return result + return self.GetMappings(withmapping=True).FindTypeDefaultValue(typeindex) 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.""" - list_ = list(ODMapping.FindMapVariableList(maps.MAPPING_DICTIONARY, self, compute)) - for mapping in self.GetMappings(): - list_.extend(ODMapping.FindMapVariableList(mapping, self, compute)) - list_.sort() - return list_ + return list(sorted(self.GetMappings(withmapping=True).FindMapVariableList(self, compute))) def GetMandatoryIndexes(self, node: "Node|None" = None) -> list[int]: # pylint: disable=unused-argument """Return the mandatory indexes for the node.""" - list_ = ODMapping.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) - for mapping in self.GetMappings(): - list_.extend(ODMapping.FindMandatoryIndexes(mapping)) - return list_ + # FIXME: Old code listed MAPPING_DIRECTORY first, this is last. Important? + return self.GetMappings(withmapping=True).FindMandatoryIndexes() def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: """ Return the customisable types. It returns a dict by the index number. @@ -498,10 +451,8 @@ def GetCustomisableTypes(self) -> dict[int, tuple[str, int]]: def GetTypeList(self) -> list[str]: """Return a list of all object types available for the current node""" - list_ = ODMapping.FindTypeList(maps.MAPPING_DICTIONARY) - for mapping in self.GetMappings(): - list_.extend(ODMapping.FindTypeList(mapping)) - return list_ + # FIXME: Old code listed MAPPING_DIRECTORY first, this puts it last. Important? + return self.GetMappings(withmapping=True).FindTypeList() @staticmethod def GenerateMapName(name: str, index: int, subindex: int) -> str: @@ -1057,11 +1008,12 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve value = '"' + value + '"' elif i and index_range and index_range.name in ('rpdom', 'tpdom'): index, subindex, _ = self.GetMapIndex(value) - pdo = self.GetSubentryInfos(index, subindex) - suffix = '???' if value else '' - if pdo: - suffix = str(pdo["name"]) - value = f"0x{value:x} {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 = f"0x{value:x}" else: diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index db411ab..11e0683 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -473,15 +473,17 @@ def ManageEntriesOfCurrent(self, addinglist: list[int], removinglist: list[int], # Second case entry is a record else: i = 1 - subentry_infos = self.GetSubentryInfos(index, i) - while subentry_infos: + while True: + try: + subentry_infos = self.GetSubentryInfos(index, i) + except ValueError: + break if "default" in subentry_infos: default = subentry_infos["default"] else: default = self.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) @@ -1154,17 +1156,17 @@ def GetCustomisedTypeValues(self, index): def GetEntryName(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryName(index, compute) - return ODMapping.FindEntryName(index, maps.MAPPING_DICTIONARY, compute) + return maps.MAPPING_DICTIONARY.FindEntryName(index, compute) def GetEntryInfos(self, index, compute=True): if self.CurrentNode: return self.CurrentNode.GetEntryInfos(index, compute) - return ODMapping.FindEntryInfos(index, maps.MAPPING_DICTIONARY, compute) + return maps.MAPPING_DICTIONARY.FindEntryInfos(index, compute) def GetSubentryInfos(self, index, subindex, compute=True): if self.CurrentNode: return self.CurrentNode.GetSubentryInfos(index, subindex, compute) - result = ODMapping.FindSubentryInfos(index, subindex, maps.MAPPING_DICTIONARY, compute) + result = maps.MAPPING_DICTIONARY.FindSubentryInfos(index, subindex, compute) if result: result["user_defined"] = False return result @@ -1172,17 +1174,17 @@ def GetSubentryInfos(self, index, subindex, compute=True): def GetTypeIndex(self, typename): if self.CurrentNode: return self.CurrentNode.GetTypeIndex(typename) - return ODMapping.FindTypeIndex(typename, maps.MAPPING_DICTIONARY) + return maps.MAPPING_DICTIONARY.FindTypeIndex(typename) def GetTypeName(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeName(typeindex) - return ODMapping.FindTypeName(typeindex, maps.MAPPING_DICTIONARY) + return maps.MAPPING_DICTIONARY.FindTypeName(typeindex) def GetTypeDefaultValue(self, typeindex): if self.CurrentNode: return self.CurrentNode.GetTypeDefaultValue(typeindex) - return ODMapping.FindTypeDefaultValue(typeindex, maps.MAPPING_DICTIONARY) + return maps.MAPPING_DICTIONARY.FindTypeDefaultValue(typeindex) def GetMapVariableList(self, compute=True): if self.CurrentNode: @@ -1192,7 +1194,7 @@ def GetMapVariableList(self, compute=True): def GetMandatoryIndexes(self): if self.CurrentNode: return self.CurrentNode.GetMandatoryIndexes() - return ODMapping.FindMandatoryIndexes(maps.MAPPING_DICTIONARY) + return maps.MAPPING_DICTIONARY.FindMandatoryIndexes() def GetCustomisableTypes(self): return { diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 7a4baef..3711b82 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -27,11 +27,6 @@ import wx import objdictgen -from objdictgen import nodemanager -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 (add_except_hook, display_error_dialog, from objdictgen.nodemanager import NodeManager from objdictgen.typing import TPath from objdictgen.ui.commondialogs import CreateNodeDialog From 7d31a1c4eb7663daf6e06698f64bce136344c276 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 7 Apr 2024 00:49:29 +0200 Subject: [PATCH 24/28] Major refactor and cleanup * Nearly full typing coverage * Refactored methods for cleaner types * Node methods renamed, some deleted * Many Node methods rewritten as a result of typing uncovering bugs * NodeManager use .current as property to conditionally acces the current node * Many superflous methods removed from NodeManager --- setup.cfg | 1 + src/objdictgen/__main__.py | 7 +- src/objdictgen/eds_utils.py | 29 +- src/objdictgen/jsonod.py | 15 +- src/objdictgen/maps.py | 18 +- src/objdictgen/node.py | 593 ++++++++++--------- src/objdictgen/nodelist.py | 110 ++-- src/objdictgen/nodemanager.py | 627 +++++++++------------ src/objdictgen/ui/commondialogs.py | 2 +- src/objdictgen/ui/networkedit.py | 3 +- src/objdictgen/ui/networkeditortemplate.py | 11 +- src/objdictgen/ui/nodeeditortemplate.py | 20 +- src/objdictgen/ui/objdictedit.py | 7 +- src/objdictgen/ui/subindextable.py | 68 +-- tests/test_odcompare.py | 4 +- 15 files changed, 735 insertions(+), 780 deletions(-) diff --git a/setup.cfg b/setup.cfg index db63f72..55da6f4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,7 @@ install_requires = where = src [options.package_data] +objdictgen = py.typed objdictgen.config = *.prf objdictgen.img = * objdictgen.schema = *.json diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index ed9423d..5f3a3d2 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -273,7 +273,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # Drop all other indexes than specified if opts.index: index = [jsonod.str_to_int(i) for i in opts.index] - to_remove |= (set(od.GetAllParameters()) - set(index)) + to_remove |= (set(od.GetAllIndices()) - set(index)) # Have any parameters to delete? if to_remove: @@ -282,8 +282,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): od.GetPrintLine(k, unused=True) for k in sorted(to_remove) ] - for idx in to_remove: - od.RemoveIndex(idx) + od.RemoveIndex(to_remove) for line, fmt in info: print(line.format(**fmt)) @@ -337,7 +336,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = 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: indexp = [jsonod.str_to_int(i) for i in opts.index] keysp = [k for k in keys if k in indexp] diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index ad8fb52..57f60d7 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -501,7 +501,7 @@ def generate_eds_content(node: "Node", filepath: TPath): 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 @@ -693,8 +693,8 @@ def generate_cpj_content(nodelist: "NodeList"): for nodeid in sorted(nodes): 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 += f"Node{nodeid}Name={nodes[nodeid].Name}\n" + filecontent += f"Node{nodeid}DCFName={nodes[nodeid].EDS}\n" filecontent += "EDSBaseName=eds\n" return filecontent @@ -752,11 +752,8 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": # FIXME: entry should be integer, but can that be guaranteed? assert isinstance(entry, int) - # Extract informations for the entry - entry_infos = node.GetEntryInfos(entry) - # If no informations are available, then we write them - if not entry_infos: + if not node.IsMappingEntry(entry): # First case, entry is a DOMAIN or VAR if values["OBJECTTYPE"] in [2, 7]: if values["OBJECTTYPE"] == 2: @@ -764,9 +761,12 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": 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, name=values["PARAMETERNAME"], struct=OD.VAR) + node.AddMappingEntry(entry, entry={ + "name": values["PARAMETERNAME"], + "struct": OD.VAR, + }) # Add mapping for first subindex - node.AddMappingEntry(entry, 0, values={ + node.AddMappingSubEntry(entry, 0, values={ "name": values["PARAMETERNAME"], "type": values["DATATYPE"], "access": ACCESS_TRANSLATE[values["ACCESSTYPE"].upper()], @@ -778,9 +778,12 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": # Extract maximum subindex number defined max_subindex = max(values["subindexes"]) # Add mapping for entry - node.AddMappingEntry(entry, name=values["PARAMETERNAME"], struct=OD.RECORD) + node.AddMappingEntry(entry, entry={ + "name": values["PARAMETERNAME"], + "struct": OD.RECORD + }) # Add mapping for first subindex - node.AddMappingEntry(entry, 0, values={ + node.AddMappingSubEntry(entry, 0, values={ "name": "Number of Entries", "type": 0x05, "access": "ro", @@ -790,7 +793,7 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": for subindex in range(1, int(max_subindex) + 1): # if subindex is defined if subindex in values["subindexes"]: - node.AddMappingEntry(entry, subindex, values={ + node.AddMappingSubEntry(entry, subindex, values={ "name": values["subindexes"][subindex]["PARAMETERNAME"], "type": values["subindexes"][subindex]["DATATYPE"], "access": ACCESS_TRANSLATE[values["subindexes"][subindex]["ACCESSTYPE"].upper()], @@ -798,7 +801,7 @@ def generate_node(filepath: TPath, nodeid: int = 0) -> "Node": }) # if not, we add a mapping for compatibility else: - node.AddMappingEntry(entry, subindex, values={ + node.AddMappingSubEntry(entry, subindex, values={ "name": "Compatibility Entry", "type": 0x05, "access": "rw", diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index f664cbe..4f1d955 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -299,13 +299,10 @@ def get_object_types( # i2s: integer to string, s2i: string to integer i2s: dict[int, str] = {} s2i: dict[str, int] = {} - for mapping in mappinglist: - for k, v in mapping.items(): - if k >= 0x1000: - continue - n = v['name'] - i2s[k] = n - s2i[n] = k + 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") @@ -470,12 +467,12 @@ def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=Tr # Parse through all parameters indexes dictionary: list[TODObjJson] = [] - for index in node.GetAllParameters(sort=sort): + for index in node.GetAllIndices(sort=sort): try: obj: TODObjJson = {} # Get the internal dict representation of the object, termed "index entry" - ientry = node.GetIndexDict(index) + ientry = node.GetIndexEntry(index) # Don't wrangle further if the internal format is wanted, just add it as-is if internal: diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 6eda035..6b6246d 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -169,7 +169,7 @@ def get_index_range(self, index: int) -> IndexRange: RE_NODEID = re.compile(r'\$NODEID\b', re.IGNORECASE) -def eval_value(value, base, nodeid, compute=True): +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 @@ -202,7 +202,7 @@ def eval_value(value, base, nodeid, compute=True): return value -def eval_name(text, idx, sub): +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 @@ -214,8 +214,8 @@ def eval_name(text, idx, sub): # NOTE: Legacy Python2 format evaluations are baked # into the OD and must be supported for legacy - return result.group(1) % evaluate_expression( - result.group(2).strip(), + return result[1] % evaluate_expression( + result[2].strip(), { # These are the vars that can be used in the string 'idx': idx, 'sub': sub, @@ -223,7 +223,7 @@ def eval_name(text, idx, sub): ) -def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): +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 @@ -241,7 +241,7 @@ def evaluate_expression(expression: str, localvars: dict[str, Any]|None = None): """ localvars = localvars or {} - def _evnode(node: ast.AST): + def _evnode(node: ast.AST|None): """ Recursively parses ast.Node objects to evaluate arithmatic expressions """ @@ -257,7 +257,7 @@ def _evnode(node: ast.AST): 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(f"Chained comparisons not supported") + 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])}") @@ -294,7 +294,7 @@ def _evnode(node: ast.AST): # Misc functions # ------------------------------------------------------------------------------ -def import_profile(profilename): +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 @@ -323,7 +323,7 @@ def import_profile(profilename): 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 + 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()) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 7fea8d9..b194e93 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -117,6 +117,10 @@ def __init__( # Dunders # -------------------------------------------------------------------------- + def __iter__(self) -> Iterator[int]: + """Iterate over all indexes in the dictionary""" + return iter(sorted(self.Dictionary)) + def __setattr__(self, name: str, value: Any): """Ensure that that internal attrs are of the right datatype.""" if name in ("Profile", "DS302", "UserMapping"): @@ -200,11 +204,11 @@ def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> self, compact=compact, sort=sort, internal=internal, validate=validate ) - def GetDict(self) -> dict[str, Any]: + def asdict(self) -> dict[str, Any]: """ Return the class data as a dict """ return copy.deepcopy(self.__dict__) - def Copy(self) -> Self: + def copy(self) -> Self: """ Return a copy of the node """ @@ -231,26 +235,43 @@ def GetEntry(self, index: int, subindex: int|None = None, compute=True, aslist=F """ if index not in self.Dictionary: raise KeyError(f"Index 0x{index:04x} does not exist") + dictval = self.Dictionary[index] + + # Variables needed by the eval_value function base = self.GetBaseIndexNumber(index) nodeid = self.ID + if subindex is None: - if isinstance(self.Dictionary[index], list): - return [len(self.Dictionary[index])] + [ + if isinstance(dictval, list): + out: list[TODValue] = [len(dictval)] + out.extend( maps.eval_value(value, base, nodeid, compute) - for value in self.Dictionary[index] - ] - result = maps.eval_value(self.Dictionary[index], 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 maps.eval_value(self.Dictionary[index], base, nodeid, compute) - if isinstance(self.Dictionary[index], list) and 0 < subindex <= len(self.Dictionary[index]): - return maps.eval_value(self.Dictionary[index][subindex - 1], base, nodeid, compute) - raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") + 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]: @@ -260,37 +281,50 @@ def GetParamsEntry(self, index: int, subindex: int|None = None, """ if index not in self.Dictionary: 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 + + if isinstance(dictval, list): + + if 0 <= subindex <= len(dictval): + params = params or {} + return _get_param(params.get(subindex)) # type: ignore[call-overload] + + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") + + # 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] + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") - def GetIndexDict(self, index: int) -> TIndexEntry: + def GetIndexEntry(self, index: int) -> TIndexEntry: """ Return a full and raw representation of the index """ def _mapping_for_index(index: int) -> Generator[tuple[str, TODObj], None, None]: @@ -327,6 +361,14 @@ def _mapping_for_index(index: int) -> Generator[tuple[str, TODObj], None, None]: # Ensure that the object is safe to mutate return copy.deepcopy(obj) + 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) + + # FIXME: Keep this or use the __iterator__ method? def GetIndexes(self): """ Return a sorted list of indexes in Object Dictionary @@ -346,8 +388,11 @@ def GetCustomisedTypeValues(self, index: int) -> tuple[list[TODValue], int]: 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 + # 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""" @@ -405,38 +450,24 @@ def GetEntryFlags(self, index: int) -> set[str]: 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: str) -> int: """Return the type index for the given type name.""" return self.GetMappings(withmapping=True).FindTypeIndex(typename) - def GetTypeName(self, typeindex: int) -> str: + def GetTypeName(self, index: int) -> str: """Return the type name for the given type index.""" - return self.GetMappings(withmapping=True).FindTypeName(typeindex) + return self.GetMappings(withmapping=True).FindTypeName(index) - def GetTypeDefaultValue(self, typeindex: int) -> TODValue: + def GetTypeDefaultValue(self, index: int) -> TODValue: """Return the default value for the given type index.""" - return self.GetMappings(withmapping=True).FindTypeDefaultValue(typeindex) + return self.GetMappings(withmapping=True).FindTypeDefaultValue(index) 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: "Node|None" = None) -> list[int]: # pylint: disable=unused-argument + 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() @@ -464,34 +495,31 @@ def GetMapValue(self, mapname: str) -> int: 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): # 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 + + raise ValueError(f"Mapping '{mapname}' not found") @staticmethod def GetMapIndex(value: int) -> tuple[int, int, int]: @@ -508,8 +536,8 @@ def GetMapName(self, value: int) -> str: 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) -> list[str]: @@ -521,7 +549,7 @@ def GetMapList(self) -> list[str]: for index, subindex, size, name in self.GetMapVariableList() ] - def GetAllParameters(self, sort=False) -> list[int]: + 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. @@ -551,7 +579,7 @@ def GetAllParameters(self, sort=False) -> list[int]: 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 ] @@ -604,208 +632,217 @@ def HasEntryCallbacks(self, index: int) -> bool: 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"] + 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 # -------------------------------------------------------------------------- # Node mutuation functions # -------------------------------------------------------------------------- - def AddEntry(self, index: int, subindex: int|None = None, value: TODValue|list[TODValue]|None = None) -> bool: + 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 True + 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 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 + 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) -> bool: + 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: - return False + raise ValueError(f"Index 0x{index:04x} does not exist") + dictval = self.Dictionary[index] + 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 + # if value is not None: # FIXME: Can this be None? + self.Dictionary[index] = value + return - def SetParamsEntry(self, index: int, subindex: int|None = None, comment=None, buffer_size=None, save=None, callback=None) -> bool: + 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: - 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 + 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 - def RemoveEntry(self, index: int, subindex: int|None = None) -> bool: + 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: - return False - if not subindex: + 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.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) + self.ParamsDictionary[index].pop(subindex, None) # type: ignore[typeddict-item,misc] if len(self.ParamsDictionary[index]) == 0: self.ParamsDictionary.pop(index) - if len(self.Dictionary[index]) == 0: + if len(dictval) == 0: self.Dictionary.pop(index) - if index in self.ParamsDictionary: - self.ParamsDictionary.pop(index) - return True - return False + self.ParamsDictionary.pop(index, None) + return + raise ValueError(f"Failed to remove entry 0x{index:04x} subindex {subindex}") - def AddMappingEntry(self, index: int, subindex: int|None = None, name="Undefined", struct=0, size=None, nbmax=None, - default=None, values=None) -> bool: + 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: - 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 = {} + 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 True - return False + return + raise ValueError(f"Failed to add mapping entry 0x{index:04x} subindex {subindex}") - def SetMappingEntry(self, index: int, subindex: int|None = None, name=None, struct=None, size=None, nbmax=None, default=None, values=None) -> bool: + def SetMappingEntry(self, index: int, entry: TODObj): """ Modify an existing entry in the User Mapping Dictionary """ 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])): + 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.) - 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"]): + 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.) - self.UserMapping[index]["values"][subindex].update(values) - return True - return False - - def RemoveMappingEntry(self, index: int, subindex: int|None = None) -> bool: + 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 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 + 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 + if subindex == len(self.UserMapping[index]["values"]) - 1: + self.UserMapping[index]["values"].pop(subindex) + return + raise ValueError(f"Invalid subindex {subindex} for index 0x{index:04x}") def RemoveMapVariable(self, index: int, subindex: int = 0): """ @@ -816,11 +853,16 @@ def RemoveMapVariable(self, index: int, subindex: int = 0): if subindex: model += subindex << 8 mask += 0xFF << 8 - for i in self.Dictionary: # pylint: disable=consider-using-dict-items + # 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: - for j, value in enumerate(self.Dictionary[i]): + # 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: - self.Dictionary[i][j] = 0 + dictval[j] = 0 def UpdateMapVariable(self, index: int, subindex: int, size: int): """ @@ -832,11 +874,15 @@ def UpdateMapVariable(self, index: int, subindex: int, size: int): if subindex: model += subindex << 8 mask = 0xFF << 8 - for i in self.Dictionary: # pylint: disable=consider-using-dict-items + for i, dictval in self.Dictionary.items(): if 0x1600 <= i <= 0x17FF or 0x1A00 <= i <= 0x1BFF: - for j, value in enumerate(self.Dictionary[i]): + # 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: - self.Dictionary[i][j] = model + size + dictval[j] = model + size def RemoveLine(self, index: int, maxval: int, incr: int = 1): """ Remove the given index and shift all the following indexes """ @@ -848,17 +894,20 @@ def RemoveLine(self, index: int, maxval: int, incr: int = 1): i += incr self.Dictionary.pop(i) - def RemoveIndex(self, index: int) -> None: - """ 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" + 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 @@ -888,14 +937,19 @@ def _warn(text: str): base = self.GetEntryInfos(index) 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: @@ -907,7 +961,7 @@ def _warn(text: str): 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] t_p = ", ".join(str(k) for k in excessive_params) _warn(f"FIX: Deleting ParamDictionary entries {t_p}") @@ -918,8 +972,7 @@ def _warn(text: str): _warn("FIX: Deleting ParamDictionary entry") # Iterate over all user mappings - params = set(self.UserMapping) - for index in params: + for index in set(self.UserMapping): for idx, subvals in enumerate(self.UserMapping[index]['values']): # @@ -954,10 +1007,11 @@ def GetPrintLine(self, index: int, unused=False, compact=False): # Print formattings t_flags = ', '.join(flags) + t_string = maps.ODStructTypes.to_string(obj['struct']) or '???' fmt = { '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 + 'struct': t_string.upper(), 'flags': f" {Fore.CYAN}{t_flags}{Style.RESET_ALL}" if flags else '', 'pre': ' ' if not compact else '', } @@ -971,7 +1025,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: @@ -994,19 +1048,24 @@ 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'): + # FIXME: In PDO mappings, the value is ints + assert isinstance(value, int) index, subindex, _ = self.GetMapIndex(value) try: pdo = self.GetSubentryInfos(index, subindex) @@ -1019,7 +1078,7 @@ def GetPrintParams(self, keys=None, short=False, compact=False, unused=False, ve else: value = str(value) - comment = info['comment'] or '' + comment = entry['comment'] or '' if comment: comment = f"{Fore.LIGHTBLACK_EX}/* {info.get('comment')} */{Style.RESET_ALL}" @@ -1054,12 +1113,12 @@ 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 b8f05cf..a3d3eef 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -22,6 +22,7 @@ import os from pathlib import Path import shutil +from dataclasses import dataclass from objdictgen import eds_utils from objdictgen.node import Node @@ -32,6 +33,13 @@ # Definition of NodeList Object # ------------------------------------------------------------------------------ +@dataclass +class SlaveNode: + """SlaveNodes in NodeList.""" + Name: str + EDS: str + Node: Node + class NodeList: """ @@ -42,7 +50,7 @@ def __init__(self, manager: NodeManager, netname=""): self.Root: Path = Path("") self.Manager: NodeManager = manager self.NetworkName: str = netname - self.SlaveNodes: dict[int, dict] = {} + self.SlaveNodes: dict[int, SlaveNode] = {} self.EDSNodes: dict[str, Node] = {} self.CurrentSelected: int|None = None self.Changed = False @@ -53,17 +61,17 @@ def HasChanged(self) -> bool: 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) -> int: - return self.Manager.GetCurrentNodeID() + return self.Manager.current.ID def GetSlaveName(self, idx: int) -> str: - return self.SlaveNodes[idx]["Name"] + return self.SlaveNodes[idx].Name def GetSlaveNames(self) -> list[str]: return [ - f"0x{idx:02X} {self.SlaveNodes[idx]['Name']}" + f"0x{idx:02X} {self.SlaveNodes[idx].Name}" for idx in sorted(self.SlaveNodes) ] @@ -74,19 +82,17 @@ 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) + 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") - 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": + for file in eds_folder.iterdir(): + if file.is_file() and file.suffix == ".eds": self.LoadEDS(file) self.LoadMasterNode(netname) @@ -98,24 +104,22 @@ def SaveProject(self, netname: str = ""): self.SaveNodeList(netname) def GetEDSFilePath(self, edspath: TPath) -> Path: - _, file = os.path.split(edspath) - eds_folder = self.GetEDSFolder() - return os.path.join(eds_folder, file) + return self.GetEDSFolder() / Path(edspath).name def ImportEDSFile(self, edspath: TPath): - _, file = os.path.split(edspath) - shutil.copy(edspath, self.GetEDSFolder()) - self.LoadEDS(file) + edsfolder = self.GetEDSFolder() + shutil.copy(edspath, edsfolder) + self.LoadEDS(edsfolder / Path(edspath).name) def LoadEDS(self, eds: TPath): - edspath = os.path.join(self.GetEDSFolder(), eds) - node = eds_utils.generate_node(edspath) - self.EDSNodes[eds] = node + eds = Path(eds) + node = eds_utils.generate_node(eds) + self.EDSNodes[eds.name] = node def AddSlaveNode(self, nodename: str, nodeid: int, eds: str): if eds not in self.EDSNodes: raise ValueError(f"'{eds}' EDS file is not available") - slave = {"Name": nodename, "EDS": eds, "Node": self.EDSNodes[eds]} + slave = SlaveNode(Name=nodename, EDS=eds, Node=self.EDSNodes[eds]) self.SlaveNodes[nodeid] = slave self.Changed = True @@ -127,10 +131,10 @@ def RemoveSlaveNode(self, index: int): def LoadMasterNode(self, netname: str = "") -> int: if netname: - masterpath = os.path.join(self.Root, f"{netname}_master.od") + 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( @@ -141,17 +145,17 @@ def LoadMasterNode(self, netname: str = "") -> int: def SaveMasterNode(self, netname: str = ""): if netname: - masterpath = os.path.join(self.Root, f"{netname}_master.od") + 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(f"Fail to save master node in '{masterpath}'") from exc def LoadSlaveNodes(self, netname: str = ""): - cpjpath = os.path.join(self.Root, "nodelist.cpj") - if os.path.isfile(cpjpath): + cpjpath = self.Root / "nodelist.cpj" + if cpjpath.is_file(): try: networks = eds_utils.parse_cpj_file(cpjpath) network = None @@ -172,9 +176,8 @@ def LoadSlaveNodes(self, netname: str = ""): raise ValueError(f"Unable to load CPJ file '{cpjpath}'") from exc def SaveNodeList(self, netname: str = ""): - cpjpath = '' # For linting + cpjpath = self.Root / "nodelist.cpj" try: - cpjpath = os.path.join(self.Root, "nodelist.cpj") content = eds_utils.generate_cpj_content(self) if netname: mode = "a" @@ -193,60 +196,63 @@ def GetOrderNumber(self, nodeid: int) -> int: 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: 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: 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_: int, max_: int) -> list[tuple[str, int]]: + 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: 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: 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) @@ -266,7 +272,7 @@ def main(projectdir): print(line) print() for nodeid, nodeinfo in nodelist.SlaveNodes.items(): - print(f"SlaveNode name={nodeinfo['Name']} id=0x{nodeid:02X} :") - for line in nodeinfo["Node"].GetPrintParams(): + 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 11e0683..74efda3 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -20,7 +20,6 @@ import codecs import logging -import os import re from pathlib import Path from typing import Container, Generic, TypeVar, cast @@ -28,7 +27,6 @@ import colorama from objdictgen import maps -from objdictgen import node as nodelib from objdictgen.maps import OD, ODMapping from objdictgen.node import Node from objdictgen.typing import TODSubObj, TPath @@ -80,7 +78,7 @@ def __init__(self, state: T|None = None, issaved: bool = False): else: self.LastSave = -1 - def Buffering(self, state: T): + def Add(self, state: T): """ Add a new state in buffer """ @@ -99,25 +97,36 @@ def Current(self) -> T: """ Return current state of buffer """ - return self.Buffer[self.CurrentIndex] + 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) -> 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) -> bool: """ @@ -156,29 +165,32 @@ def __init__(self): self.LastNewIndex = 0 self.FilePaths: dict[int, Path|None] = {} self.FileNames: dict[int, str] = {} - self.NodeIndex: int|None = None + 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) -> list[str]|str: - """ - Return the list of types defined for the current node - """ - if self.CurrentNode: - return self.CurrentNode.GetTypeList() - return "" - - def GetCurrentMapList(self) -> list[str]|str: - """ - 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 @@ -211,26 +223,25 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, node.ID = id node.Type = type node.Description = description - addindexlist = self.GetMandatoryIndexes() + 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 = maps.import_profile("DS-302") - node.DS302 = mapping - node.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) @@ -244,7 +255,7 @@ def CreateNewNode(self, name: str, id: int, type: str, description: str, addsubindexlist.append((idx, 8)) # Add a new buffer - index = self.AddNodeBuffer(node.Copy(), False) + index = self.AddNodeBuffer(node.copy(), False) self.SetCurrentFilePath(None) # Add Mandatory indexes self.ManageEntriesOfCurrent(addindexlist, []) @@ -261,7 +272,7 @@ def OpenFileInCurrent(self, filepath: TPath, load=True) -> int: self.CurrentNode = node node.ID = 0 - index = self.AddNodeBuffer(node.Copy(), load) + index = self.AddNodeBuffer(node.copy(), load) self.SetCurrentFilePath(filepath if load else None) return index @@ -280,16 +291,15 @@ def SaveCurrentInFile(self, filepath: TPath|None = 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) -> bool: @@ -297,23 +307,23 @@ 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) + if (self.CurrentNodeIndex in self.UndoBuffers + and (self.UndoBuffers[self.CurrentNodeIndex].IsCurrentSaved() or ignore) ): - self.RemoveNodeBuffer(self.NodeIndex) + 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 @@ -328,7 +338,7 @@ def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = None """ disable_buffer = node is not None if node is None: - node = self.CurrentNode + node = self.current # Informations about entry length = node.GetEntry(index, 0) # FIXME: This code assumes that subindex 0 is the length of the entry @@ -339,7 +349,7 @@ def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = None 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): @@ -351,7 +361,7 @@ def AddSubentriesToCurrent(self, index: int, number: int, node: Node|None = None if infos["struct"] & OD.MultipleSubindexes and 0x2000 <= index <= 0x5FFF: 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() @@ -362,8 +372,9 @@ def RemoveSubentriesFromCurrent(self, index: int, number: int): number of subentry (except 0) isn't less than 1 """ # Informations about entry - infos = self.GetEntryInfos(index) - length = self.CurrentNode.GetEntry(index, 0) + 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) if "nbmin" in infos: @@ -384,7 +395,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, []) @@ -420,8 +431,9 @@ 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) @@ -430,21 +442,22 @@ def AddSpecificEntryToCurrent(self, menuitem: str): if None not in indexlist: self.ManageEntriesOfCurrent(indexlist, []) - def GetLineFromIndex(self, base_index: int) -> int|None: + 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: list[int], removinglist: list[int], node: Node|None = None): """ @@ -452,18 +465,18 @@ def ManageEntriesOfCurrent(self, addinglist: list[int], removinglist: list[int], """ 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"]): @@ -472,25 +485,21 @@ def ManageEntriesOfCurrent(self, addinglist: list[int], removinglist: list[int], node.AddEntry(index, 1, default) # Second case entry is a record else: - i = 1 - while True: - try: - subentry_infos = self.GetSubentryInfos(index, i) - except ValueError: - break + # 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 # 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: @@ -504,13 +513,13 @@ def SetCurrentEntryToDefault(self, index: int, subindex:int, node: Node|None = N """ 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() @@ -520,15 +529,16 @@ 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 - node = self.CurrentNode + node = self.current mappings = node.GetMappings() if index < 0x1000 and subindex is None: - type_ = node.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_ + value["type"] = entrytype node.RemoveMappingEntry(index) node.RemoveEntry(index) elif index == 0x1200 and subindex is None: @@ -551,19 +561,19 @@ def RemoveCurrentVariable(self, index: int, subindex: int|None = None): found = False for _, menulist in node.SpecificMenu: for i in menulist: - iinfos = self.GetEntryInfos(i) + 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 menulist: - jinfos = self.GetEntryInfos(j) + jinfos = node.GetEntryInfos(j) node.RemoveLine( j + diff, j + jinfos["incr"] * jinfos["nbmax"], jinfos["incr"] ) - node.RemoveMapVariable(index, subindex) + node.RemoveMapVariable(index, subindex or 0) if not found: - infos = self.GetEntryInfos(index) + infos = node.GetEntryInfos(index) if not infos["need"]: node.RemoveEntry(index, subindex) if index in mappings[-1]: @@ -573,29 +583,29 @@ def AddMapVariableToCurrent(self, index: int, name: str, struct: int, number: in if 0x2000 <= index <= 0x5FFF: disable_buffer = node is not None if node is None: - node = self.CurrentNode + node = self.current if node.IsEntry(index): raise ValueError(f"Index 0x{index:04X} already defined!") - node.AddMappingEntry(index, name=name, struct=struct) + node.AddMappingEntry(index, entry={"name": name, "struct": struct}) if struct == OD.VAR: values: TODSubObj = {"name": name, "type": 0x05, "access": "rw", "pdo": True} - node.AddMappingEntry(index, 0, values=values) + 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) + 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() @@ -603,49 +613,51 @@ def AddMapVariableToCurrent(self, index: int, name: str, struct: int, number: in raise ValueError(f"Index 0x{index:04X} isn't a valid index for Map Variable!") def AddUserTypeToCurrent(self, objtype: int, minval: int, maxval: int, length: int): - node = self.CurrentNode + node = self.current index = 0xA0 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: - node.AddMappingEntry(index, - name=f"{name}[{min_}-{max_}]", struct=OD.RECORD, size=size, default=default - ) - node.AddMappingEntry(index, 0, values={ + 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.AddMappingEntry(index, 1, values={ + node.AddMappingSubEntry(index, 1, values={ "name": "Type", "type": 0x05, "access": "ro", "pdo": False, }) - node.AddMappingEntry(index, 2, values={ - "name": "Minimum Value", "type": type_, "access": "ro", "pdo": False, + node.AddMappingSubEntry(index, 2, values={ + "name": "Minimum Value", "type": objtype, "access": "ro", "pdo": False, }) - node.AddMappingEntry(index, 3, values={ - "name": "Maximum Value", "type": type_, "access": "ro", "pdo": False, + node.AddMappingSubEntry(index, 3, values={ + "name": "Maximum Value", "type": objtype, "access": "ro", "pdo": False, }) - node.AddEntry(index, 1, type_) - node.AddEntry(index, 2, min_) - node.AddEntry(index, 3, max_) + node.AddEntry(index, 1, objtype) + node.AddEntry(index, 2, minval) + node.AddEntry(index, 3, maxval) elif valuetype == 1: - node.AddMappingEntry(index, - name=f"{name}{length}", struct=OD.RECORD, size=length * size, default=default - ) - node.AddMappingEntry(index, 0, values={ + 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.AddMappingEntry(index, 1, values={ + node.AddMappingSubEntry(index, 1, values={ "name": "Type", "type": 0x05, "access": "ro", "pdo": False, }) - node.AddMappingEntry(index, 2, values={ + node.AddMappingSubEntry(index, 2, values={ "name": "Length", "type": 0x05, "access": "ro", "pdo": False, }) - node.AddEntry(index, 1, type_) + node.AddEntry(index, 1, objtype) node.AddEntry(index, 2, length) self.BufferCurrentNode() @@ -654,25 +666,28 @@ def AddUserTypeToCurrent(self, objtype: int, minval: int, maxval: int, length: i # -------------------------------------------------------------------------- def SetCurrentEntryCallbacks(self, index: int, value: bool): - if self.CurrentNode and self.CurrentNode.IsEntry(index): - entry_infos = self.GetEntryInfos(index) + 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: 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": @@ -685,101 +700,102 @@ def SetCurrentEntry(self, index: int, subindex: int, value: str, name: str, edit # 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) + # FIXME: decode() produce bytes, which is not supported as of now + bvalue = codecs.decode(value, 'hex_codec') + 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 = f'"{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: if editor == "type": - value = self.GetTypeIndex(value) - size = self.GetEntryInfos(value)["size"] + nvalue = node.GetTypeIndex(value) + size = node.GetEntryInfos(nvalue)["size"] node.UpdateMapVariable(index, subindex, size) elif editor in ["access", "raccess"]: - dic = { + value = { 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: value}) # type: ignore[misc] if not disable_buffer: self.BufferCurrentNode() def SetCurrentEntryName(self, index: int, name: str): - self.CurrentNode.SetMappingEntry(index, name=name) + self.current.SetMappingEntry(index, entry={"name": name}) self.BufferCurrentNode() - def SetCurrentUserType(self, index: int, type_: int, min_: int, max_: int, length: int): - assert self.CurrentNode # For mypy - node = self.CurrentNode - 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: - node.SetMappingEntry(index, - name=f"{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: - node.SetMappingEntry(index, 2, values={ - "name": "Minimum Value", "type": type_, "access": "ro", "pdo": False, + node.SetMappingSubEntry(index, 2, values={ + "name": "Minimum Value", "type": objtype, "access": "ro", "pdo": False, }) - node.AddMappingEntry(index, 3, values={ - "name": "Maximum Value", "type": type_, "access": "ro", "pdo": False, + node.AddMappingSubEntry(index, 3, values={ + "name": "Maximum Value", "type": objtype, "access": "ro", "pdo": False, }) - node.SetEntry(index, 1, type_) - node.SetEntry(index, 2, min_) + node.SetEntry(index, 1, objtype) + node.SetEntry(index, 2, minval) if valuetype == 1: - node.AddEntry(index, 3, max_) + node.AddEntry(index, 3, maxval) else: - node.SetEntry(index, 3, max_) + node.SetEntry(index, 3, maxval) elif new_valuetype == 1: - node.SetMappingEntry(index, - name=f"{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: - node.SetMappingEntry(index, 2, values={ + node.SetMappingSubEntry(index, 2, values={ "name": "Length", "type": 0x02, "access": "ro", "pdo": False, }) node.RemoveMappingEntry(index, 3) - node.SetEntry(index, 1, type_) + node.SetEntry(index, 1, objtype) node.SetEntry(index, 2, length) if valuetype == 0: node.RemoveEntry(index, 3) @@ -790,10 +806,10 @@ def SetCurrentUserType(self, index: int, type_: int, min_: int, max_: int, lengt # -------------------------------------------------------------------------- def BufferCurrentNode(self): - self.UndoBuffers[self.NodeIndex].Buffering(self.CurrentNode.Copy()) + self.UndoBuffers[self.nodeindex].Add(self.current.copy()) def CurrentIsSaved(self) -> bool: - return self.UndoBuffers[self.NodeIndex].IsCurrentSaved() + return self.UndoBuffers[self.nodeindex].IsCurrentSaved() def OneFileHasChanged(self) -> bool: return any( @@ -808,22 +824,23 @@ 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: Node|None = None, issaved=False) -> int: - self.NodeIndex = get_new_id() - self.UndoBuffers[self.NodeIndex] = UndoBuffer(currentstate, issaved) - self.FilePaths[self.NodeIndex] = "" - self.FileNames[self.NodeIndex] = "" - return self.NodeIndex + 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: 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: int): self.UndoBuffers.pop(index) @@ -831,7 +848,7 @@ def RemoveNodeBuffer(self, index: int): self.FileNames.pop(index) def GetCurrentFilename(self) -> str: - return self.GetFilename(self.NodeIndex) + return self.GetFilename(self.nodeindex) def GetAllFilenames(self) -> list[str]: return [ @@ -845,21 +862,24 @@ def GetFilename(self, index: int) -> str: return f"~{self.FileNames[index]}~" def SetCurrentFilePath(self, filepath: TPath|None): - self.FilePaths[self.NodeIndex] = filepath + 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] = f"Unnamed{self.LastNewIndex}" + self.FilePaths[nodeindex] = None + self.FileNames[nodeindex] = f"Unnamed{self.LastNewIndex}" 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) -> tuple[bool, bool]: - first = self.UndoBuffers[self.NodeIndex].IsFirst() - last = self.UndoBuffers[self.NodeIndex].IsLast() + first = self.UndoBuffers[self.nodeindex].IsFirst() + last = self.UndoBuffers[self.nodeindex].IsLast() return not first, not last # -------------------------------------------------------------------------- @@ -874,15 +894,15 @@ def GetCurrentCommunicationLists(self) -> tuple[dict[int, tuple[str, bool]], lis return self.GetProfileLists(maps.MAPPING_DICTIONARY, commlist) def GetCurrentDS302Lists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: - return self.GetSpecificProfileLists(self.CurrentNode.DS302) + return self.GetSpecificProfileLists(self.current.DS302) def GetCurrentProfileLists(self) -> tuple[dict[int, tuple[str, bool]], list[int]]: - return self.GetSpecificProfileLists(self.CurrentNode.Profile) + return self.GetSpecificProfileLists(self.current.Profile) def GetSpecificProfileLists(self, mappingdictionary: ODMapping) -> tuple[dict[int, tuple[str, bool]], list[int]]: validlist = [] exclusionlist = [] - for _, menulist in self.CurrentNode.SpecificMenu: + for _, menulist in self.current.SpecificMenu: exclusionlist.extend(menulist) for index in mappingdictionary: if index not in exclusionlist: @@ -893,57 +913,26 @@ 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) -> int|None: - 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) -> bool: - if self.CurrentNode: - return len(self.CurrentNode.DS302) > 0 - return False - - def GetUnusedParameters(self) -> list[int]: - node = self.CurrentNode - if not node: - raise ValueError("No node loaded") - return node.GetUnusedParameters() - - def RemoveParams(self, remove: list[int]): - 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) -> str: - if self.CurrentNode: - return self.CurrentNode.Name - return "" - - def GetCurrentNodeID(self, node: Node|None = None) -> int|None: # pylint: disable=unused-argument - if self.CurrentNode: - return self.CurrentNode.ID - return None - def GetCurrentNodeInfos(self) -> tuple[str, int, str, str]: - node = self.CurrentNode + node = self.current name = node.Name nodeid = node.ID nodetype = node.Type @@ -958,65 +947,37 @@ def SetCurrentNodeInfos(self, name: str, nodeid: int, nodetype: str, description node.Description = description self.BufferCurrentNode() - def GetCurrentNodeDefaultStringSize(self) -> int: - if self.CurrentNode: - return self.CurrentNode.DefaultStringSize - return nodelib.Node.DefaultStringSize - - def SetCurrentNodeDefaultStringSize(self, size: int): - if self.CurrentNode: - self.CurrentNode.DefaultStringSize = size - else: - nodelib.Node.DefaultStringSize = size - - def GetCurrentProfileName(self) -> str: - if self.CurrentNode: - return self.CurrentNode.ProfileName - return "" - - def IsCurrentEntry(self, index: int) -> bool: - if self.CurrentNode: - return self.CurrentNode.IsEntry(index) - return False - 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() + (node.GetEntryName(index), index) + for index in node if minval <= index <= maxval ] - def GetCurrentValidChoices(self, min_: int, max_: int) -> list[tuple[str, int|None],]: - 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 &= minval <= index <= maxval if good: + # FIXME: What does the "None" here mean for the index? validchoices.append((menu, None)) - list_ = [index for index in maps.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) + 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((self.GetEntryName(index), index)) + validchoices.append((node.GetEntryName(index), index)) return validchoices - def HasCurrentEntryCallbacks(self, index: int) -> bool: - if self.CurrentNode: - return self.CurrentNode.HasEntryCallbacks(index) - return False - - def GetCurrentEntryValues(self, index: int) -> tuple[list[dict], list[dict]]|None: - if self.CurrentNode: - return self.GetNodeEntryValues(self.CurrentNode, index) - return None - def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[dict]]|None: if node and node.IsEntry(index): entry_infos = node.GetEntryInfos(index) @@ -1127,82 +1088,20 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d return None 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 = nodelib.Node.be_to_le(dcf_value[:4]) + nbparams = maps.be_to_le(dcf_value[:4]) else: nbparams = 0 - new_value = nodelib.Node.le_to_be(nbparams + 1, 4) + dcf_value[4:] + new_value = maps.le_to_be(nbparams + 1, 4) + dcf_value[4:] new_value += ( - nodelib.Node.le_to_be(index, 2) - + nodelib.Node.le_to_be(subindex, 1) - + nodelib.Node.le_to_be(size, 4) - + nodelib.Node.le_to_be(value, size) + maps.le_to_be(index, 2) + + maps.le_to_be(subindex, 1) + + maps.le_to_be(size, 4) + + maps.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 maps.MAPPING_DICTIONARY.FindEntryName(index, compute) - - def GetEntryInfos(self, index, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetEntryInfos(index, compute) - return maps.MAPPING_DICTIONARY.FindEntryInfos(index, compute) - - def GetSubentryInfos(self, index, subindex, compute=True): - if self.CurrentNode: - return self.CurrentNode.GetSubentryInfos(index, subindex, compute) - result = maps.MAPPING_DICTIONARY.FindSubentryInfos(index, subindex, compute) - if result: - result["user_defined"] = False - return result - - def GetTypeIndex(self, typename): - if self.CurrentNode: - return self.CurrentNode.GetTypeIndex(typename) - return maps.MAPPING_DICTIONARY.FindTypeIndex(typename) - - def GetTypeName(self, typeindex): - if self.CurrentNode: - return self.CurrentNode.GetTypeName(typeindex) - return maps.MAPPING_DICTIONARY.FindTypeName(typeindex) - - def GetTypeDefaultValue(self, typeindex): - if self.CurrentNode: - return self.CurrentNode.GetTypeDefaultValue(typeindex) - return maps.MAPPING_DICTIONARY.FindTypeDefaultValue(typeindex) - - 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 maps.MAPPING_DICTIONARY.FindMandatoryIndexes() - - 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 [] + node.SetEntry(0x1F22, node_id, new_value) diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index 55d2f24..8f8b311 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -1116,7 +1116,7 @@ def __init__(self, parent, buttons=wx.OK | wx.CANCEL): self.Profile.Append("None") self.Directory = str(objdictgen.PROFILE_DIRECTORIES[-1]) for p in objdictgen.PROFILES: - self.ListProfile[p.stem] = p + self.ListProfile[p.stem] = str(p) self.Profile.Append(p.stem) self.Profile.Append("Other") self.Profile.SetStringSelection("None") diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index 08c9def..09cef71 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -26,7 +26,6 @@ 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 add_except_hook, display_exception_dialog @@ -368,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) diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 790d00b..325d739 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -22,9 +22,6 @@ 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 @@ -75,7 +72,7 @@ def RefreshNetworkNodes(self): self.NetworkNodes.DeleteAllPages() if self.NodeList: new_editingpanel = EditingPanel(self.NetworkNodes, self, self.Manager) - new_editingpanel.SetIndex(self.Manager.GetCurrentNodeID()) + new_editingpanel.SetIndex(self.Manager.current.ID) self.NetworkNodes.AddPage(new_editingpanel, "") for idx in self.NodeList.GetSlaveIDs(): # FIXME: Why is NodeList used where NodeManager is expected? @@ -102,11 +99,11 @@ def OnNodeSelectedChanged(self, event): def RefreshBufferState(self): if self.NodeList is not None: - nodeid = self.Manager.GetCurrentNodeID() + nodeid = self.Manager.current.ID if nodeid is not None: - nodename = f"0x{nodeid:02X} {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) diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index b9c5770..c47508d 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -24,7 +24,6 @@ from objdictgen.node import Node from objdictgen.nodemanager import NodeManager from objdictgen.ui import commondialogs as common -from objdictgen.ui import commondialogs as cdia from objdictgen.ui.exception import (display_error_dialog, display_exception_dialog) @@ -46,12 +45,6 @@ def __init__(self, manager: NodeManager, mode_solo: bool): 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() @@ -116,8 +109,9 @@ def SetStatusBarText(self, selection, node: Node): 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() @@ -128,7 +122,7 @@ def RefreshProfileMenu(self): 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, @@ -168,7 +162,7 @@ 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 = f"Edit {self.Manager.GetCurrentProfileName()} Profile" + title = f"Edit {self.Manager.current.ProfileName} Profile" dictionary, current = self.Manager.GetCurrentProfileLists() self.EditProfile(title, dictionary, current) @@ -206,12 +200,12 @@ def profile_cb(event): # pylint: disable=unused-argument def OnNodeInfosMenu(self, event): # pylint: disable=unused-argument dialog = common.NodeInfosDialog(self.Frame) name, nodeid, nodetype, description = self.Manager.GetCurrentNodeInfos() - defaultstringsize = self.Manager.GetCurrentNodeDefaultStringSize() + defaultstringsize = self.Manager.current.DefaultStringSize dialog.SetValues(name, nodeid, nodetype, description, defaultstringsize) if dialog.ShowModal() == wx.ID_OK: name, nodeid, nodetype, description, defaultstringsize = dialog.GetValues() self.Manager.SetCurrentNodeInfos(name, nodeid, nodetype, description) - self.Manager.SetCurrentNodeDefaultStringSize(defaultstringsize) + self.Manager.current.DefaultStringSize = defaultstringsize self.RefreshBufferState() self.RefreshCurrentIndexList() self.RefreshProfileMenu() @@ -237,7 +231,7 @@ def AddMapVariable(self): def AddUserType(self): with common.UserTypeDialog(self) as dialog: - dialog.SetTypeList(self.Manager.GetCustomisableTypes()) + dialog.SetTypeList(self.Manager.current.GetCustomisableTypes()) if dialog.ShowModal() == wx.ID_OK: try: self.Manager.AddUserTypeToCurrent(*dialog.GetValues()) diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index 3711b82..c92a34a 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -263,10 +263,11 @@ def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPa 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() @@ -334,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: @@ -437,7 +438,7 @@ def OnOpenMenu(self, event): # pylint: disable=unused-argument new_editingpanel.SetIndex(index) self.FileOpened.AddPage(new_editingpanel, "") self.FileOpened.SetSelection(self.FileOpened.GetPageCount() - 1) - if self.Manager.CurrentDS302Defined(): + if self.Manager.current.DS302: self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, True) else: self.EditMenu.Enable(ID_OBJDICTEDITEDITMENUDS302PROFILE, False) diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 2ab003f..f7257de 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -272,11 +272,11 @@ def _updateColAttrs(self, grid: wx.grid.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() @@ -538,14 +538,14 @@ 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: # FIXME: What is bus_id? It is never set anywhere - bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) - var_name = f"{self.Manager.GetCurrentNodeName()}_{index:04x}_{subindex:02x}" + 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(( f"{SIZE_CONVERSION[size]}{bus_id}.{index}.{subindex}", @@ -565,13 +565,13 @@ def OnSubindexGridCellLeftClick(self, event): 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: # FIXME: What is bus_id? It is never set anywhere - bus_id = '.'.join(map(str, self.ParentWindow.GetBusId())) + 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"] @@ -598,7 +598,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()] @@ -608,19 +608,19 @@ 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() @@ -688,8 +688,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 @@ -716,7 +716,7 @@ def ShowDCFEntryDialog(self, row, col): selected = self.IndexList.GetSelection() if selected != wx.NOT_FOUND: index = self.ListIndex[selected] - if self.Manager.IsCurrentEntry(index): + 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: @@ -783,9 +783,9 @@ 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) + infos = self.Manager.current.GetEntryInfos(index) # FIXME: And and or combined in the same condition if (0x2000 <= index <= 0x5FFF and infos["struct"] & OD.MultipleSubindexes @@ -811,8 +811,8 @@ def OnSubindexGridRightClick(self, event): 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) @@ -827,10 +827,10 @@ 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() @@ -865,8 +865,8 @@ 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) + 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, @@ -881,10 +881,10 @@ def OnModifyIndexMenu(self, event): # pylint: disable=unused-argument 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) + if self.Manager.current.IsEntry(index) and index < 0x260: + values, valuetype = self.Manager.current.GetCustomisedTypeValues(index) dialog = common.UserTypeDialog(self) - dialog.SetTypeList(self.Manager.GetCustomisableTypes(), values[1]) + dialog.SetTypeList(self.Manager.current.GetCustomisableTypes(), values[1]) if valuetype == 0: dialog.SetValues(min=values[2], max=values[3]) elif valuetype == 1: @@ -900,7 +900,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() @@ -910,7 +910,7 @@ 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): + if self.Manager.current.IsEntry(index): with wx.TextEntryDialog( self, "Number of subindexes to add:", "Add subindexes", "1", wx.OK | wx.CANCEL, @@ -929,7 +929,7 @@ def OnDeleteSubindexMenu(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): with wx.TextEntryDialog( self, "Number of subindexes to delete:", "Delete subindexes", "1", wx.OK | wx.CANCEL, @@ -948,7 +948,7 @@ def OnDefaultValueSubindexMenu(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): row = self.SubindexGrid.GetGridCursorRow() self.Manager.SetCurrentEntryToDefault(index, row) self.ParentWindow.RefreshBufferState() diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index daf782f..f26cd75 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -248,11 +248,11 @@ def num(x): return x a['Dictionary'] = { i: num(m1.GetEntry(i, compute=False)) - for i in m1.GetIndexes() + for i in m1 } b['Dictionary'] = { i: num(m2.GetEntry(i, compute=False)) - for i in m2.GetIndexes() + for i in m2 } # EDS files are does not contain the complete information as OD and JSON From a60bad42f93820eda1e534c547cc0a39f2b905e0 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 7 Apr 2024 12:32:00 +0200 Subject: [PATCH 25/28] Implement improved gen_cfile * Add support for legacy gen_cfiles in Node * Add to NodeProtocol --- src/objdictgen/gen_cfile.py | 686 ++++++++++++++++++++---------------- src/objdictgen/node.py | 37 +- src/objdictgen/typing.py | 24 ++ 3 files changed, 436 insertions(+), 311 deletions(-) diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index a7fd597..1f23e0f 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -45,59 +45,127 @@ """ -class CFileContext: - """Context for generating C file.""" - def __init__(self): +@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") -> Self: + """Add a string to the text without formatting.""" + self.text += str(other) + return 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) -> Self: + """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] -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) + # 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. + """ + # Return cached typeinfos + if typename in self.internal_types: + return self.internal_types[typename] -def get_valid_type_infos(context, typename, items=None): - """Get valid type infos from a typename.""" - 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 = (f"UNS{values[1]}", None, f"uint{values[1]}", True) - elif values[0] == "INTEGER" and int(values[1]) in [i * 8 for i in range(1, 9)]: - typeinfos = (f"INTEGER{values[1]}", None, f"int{values[1]}", False) - elif values[0] == "REAL" and int(values[1]) in (32, 64): - typeinfos = (f"{values[0]}{values[1]}", None, f"real{values[1]}", False) - elif values[0] in ["VISIBLE_STRING", "OCTET_STRING"]: - size = context.default_string_size + 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(f"!!! '{typename}' isn't a valid type for CanFestival.") - if typeinfos[2] not in ["visible_string", "domain"]: - context.internal_types[typename] = typeinfos - else: - # FIXME: The !!! is for special UI handling - raise ValueError(f"!!! '{typename}' isn't a valid type for CanFestival.") - return typeinfos + + # Cache the typeinfos + if typeinfos.ctype not in ["visible_string", "domain"]: + self.internal_types[typename] = typeinfos + return typeinfos -def compute_value(ctype: str, value: TODValue) -> tuple[str, str]: +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 compute_value(value: TODValue, ctype: str) -> tuple[str, str]: """Compute value for C file.""" if ctype == "visible_string": return f'"{value}"', "" @@ -116,15 +184,6 @@ def compute_value(ctype: str, value: TODValue) -> tuple[str, str]: return f"0x{value:X}", f"\t/* {value} */" -def get_type_name(node: NodeProtocol, typenumber: int) -> str: - """Get type name from a type number.""" - typename = node.GetTypeName(typenumber) - if typename is None: - # FIXME: The !!! is for special UI handling - raise ValueError(f"!!! Datatype with value '0x{typenumber:04X}' isn't defined in CanFestival.") - return typename - - def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=None) -> tuple[str, str, str]: """ pointers_dict = {(Idx,Sidx):"VariableName",...} @@ -133,20 +192,18 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non # 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] @@ -160,16 +217,16 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non # Declaration of the value range types # -------------------------------------------------------------------------- - valueRangeContent = "" - strDefine = ( + 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 = """ case valueRange_EMC: + strSwitch = ctx.text(""" case valueRange_EMC: if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED; break; -""" - context.internal_types["valueRange_EMC"] = ("UNS8", "", "valueRange_EMC", True) +""") + ctx.internal_types["valueRange_EMC"] = TypeInfos("UNS8", 0, "valueRange_EMC", True) num = 0 for index in rangelist: rangename = node.GetEntryName(index) @@ -181,8 +238,10 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non # where index 1 is the object type as int assert isinstance(typeindex, int) typename = node.GetTypeName(typeindex) - typeinfos = get_valid_type_infos(context, typename) - context.internal_types[rangename] = (typeinfos[0], typeinfos[1], f"valueRange_{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) # FIXME: It assumed the data is properly formatted @@ -190,22 +249,22 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non assert isinstance(maxvalue, int) strDefine += ( f"\n#define valueRange_{num} 0x{index:02X} " - f"/* Type {typeinfos[0]}, {minvalue} < value < {maxvalue} */" + f"/* Type {typeinfos.type}, {minvalue} < value < {maxvalue} */" ) strSwitch += f" case valueRange_{num}:\n" - if typeinfos[3] and minvalue <= 0: + if typeinfos.is_unsigned and minvalue <= 0: strSwitch += " /* Negative or null low limit ignored because of unsigned type */;\n" else: strSwitch += ( - f" if (*({typeinfos[0]}*)value < ({typeinfos[0]}){minvalue}) return OD_VALUE_TOO_LOW;\n" + f" if (*({typeinfos.type}*)value < ({typeinfos.type}){minvalue}) return OD_VALUE_TOO_LOW;\n" ) strSwitch += ( - f" if (*({typeinfos[0]}*)value > ({typeinfos[0]}){maxvalue}) return OD_VALUE_TOO_HIGH;\n" + 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" @@ -214,102 +273,102 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non # 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 = get_type_name(node, subentry_infos["type"]) - typeinfos = get_valid_type_infos(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]: + if not typeinfos.size: raise ValueError(f"Domain variable not initialized, index: 0x{index:04X}, subindex: 0x00") - texts["subIndexType"] = typeinfos[0] - if typeinfos[1] is not None: + ctx["subIndexType"] = typeinfos.type + if typeinfos.size is not None: if params_infos["buffer_size"]: - texts["suffix"] = f"[{params_infos['buffer_size']}]" + ctx["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffix"] = f"[{typeinfos[1]}]" + ctx["suffix"] = f"[{typeinfos.size}]" else: - texts["suffix"] = "" - texts["value"], texts["comment"] = compute_value(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', format_name(subentry_infos["name"])) - strDeclareHeader += ( - "extern %(subIndexType)s %(name)s%(suffix)s;" - "\t\t/* Mapped at index 0x%(index)04X, subindex 0x00*/\n" - ) % texts - mappedVariableContent += ( - "%(subIndexType)s %(name)s%(suffix)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 += ( + strindex %= ( " " - "%(subIndexType)s %(NodeName)s_obj%(index)04X%(suffix)s = %(value)s;%(comment)s\n" - ) % texts + "{subIndexType} {NodeName}_obj{index:04X}{suffix} = {value};{comment}\n" + ) values = [values] else: subentry_infos = node.GetSubentryInfos(index, 0) - typename = get_type_name(node, subentry_infos["type"]) - typeinfos = get_valid_type_infos(context, typename) - if index == 0x1003: - texts["value"] = 0 - else: - texts["value"] = values[0] - texts["subIndexType"] = typeinfos[0] - strindex += ( + 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)s %(NodeName)s_highestSubIndex_obj%(index)04X = %(value)d; " + "{subIndexType} {NodeName}_highestSubIndex_obj{index:04X} = {value}; " "/* number of subindex - 1*/\n" - ) % texts + ) # Entry type is ARRAY if entry_infos["struct"] & OD.IdenticalSubindexes: subentry_infos = node.GetSubentryInfos(index, 1) typename = node.GetTypeName(subentry_infos["type"]) - typeinfos = get_valid_type_infos(context, typename, values[1:]) - texts["subIndexType"] = typeinfos[0] - if typeinfos[1] is not None: - texts["suffix"] = f"[{typeinfos[1]}]" - texts["type_suffix"] = "*" + 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["suffix"] = "" - texts["type_suffix"] = "" - 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', format_name(entry_infos["name"])) - texts["values_count"] = str(len(values) - 1) - strDeclareHeader += ( - "extern %(subIndexType)s %(name)s[%(values_count)s]%(suffix)s;\t\t" - "/* Mapped at index 0x%(index)04X, subindex 0x01 - 0x%(length)02X */\n" - ) % texts - mappedVariableContent += ( - "%(subIndexType)s %(name)s[]%(suffix)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 = compute_value(typeinfos[2], value) + value, comment = compute_value(value, typeinfos.ctype) if len(value) == 2 and typename == "DOMAIN": raise ValueError( "Domain variable not initialized, " @@ -318,68 +377,71 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non mappedVariableContent += f" {value}{sep}{comment}\n" mappedVariableContent += " };\n" else: - strindex += ( + strindex %= ( " " - "%(subIndexType)s%(type_suffix)s %(NodeName)s_obj%(index)04X[] = \n" - " {\n" - ) % texts + "{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 = compute_value(typeinfos[2], value) + 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', format_name(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 = get_type_name(node, subentry_infos["type"]) - typeinfos = get_valid_type_infos(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["suffix"] = f"[{params_infos['buffer_size']}]" + ctx["suffix"] = f"[{params_infos['buffer_size']}]" else: - texts["suffix"] = f"[{typeinfos[1]}]" + ctx["suffix"] = f"[{typeinfos.size}]" else: - texts["suffix"] = "" - texts["value"], texts["comment"] = compute_value(typeinfos[2], value) - texts["name"] = format_name(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%(suffix)s;\t\t" - "/* Mapped at index 0x%(index)04X, subindex 0x%(subindex)02X */\n" - ) % texts - mappedVariableContent += ( - "%(subIndexType)s %(parent)s_%(name)s%(suffix)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 += ( + strindex %= ( " " - "%(subIndexType)s %(NodeName)s" - "_obj%(index)04X_%(name)s%(suffix)s = " - "%(value)s;%(comment)s\n" - ) % texts - - headerObjectDefinitionContent += ( - f"\n#define {RE_NOTW.sub('_', texts['NodeName'])}" - f"_{RE_NOTW.sub('_', texts['EntryName'])}_Idx {texts['index']:#04x}\n" + "{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 += ( + strindex %= ( " " - "subindex %(NodeName)s_Index%(index)04X[] = \n" - " {\n" - ) % texts + "subindex {NodeName}_Index{index:04X}[] = \n" + " {{\n" + ) generateSubIndexArrayComment = True for subindex, _ in enumerate(values): subentry_infos = node.GetSubentryInfos(index, subindex) @@ -392,40 +454,46 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non sep = "" typename = node.GetTypeName(subentry_infos["type"]) if entry_infos["struct"] & OD.IdenticalSubindexes: - typeinfos = get_valid_type_infos(context, typename, values[1:]) + typeinfos = ctx.get_valid_type_infos(typename, values[1:]) else: - typeinfos = get_valid_type_infos(context, typename, [values[subindex]]) + typeinfos = ctx.get_valid_type_infos(typename, [values[subindex]]) if subindex == 0: if index == 0x1003: - typeinfos = get_valid_type_infos(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 = format_name(subentry_infos["name"]) else: - name = format_name(f"{texts['NodeName']}_obj{texts['index']:04X}") + name = format_name(f"{ctx['NodeName']}_obj{ctx['index']:04X}") elif entry_infos["struct"] & OD.IdenticalSubindexes: if index in variablelist: name = f"{format_name(entry_infos['name'])}[{subindex - 1}]" else: - name = f"{texts['NodeName']}_obj{texts['index']:04X}[{subindex - 1}]" + name = f"{ctx['NodeName']}_obj{ctx['index']:04X}[{subindex - 1}]" else: if index in variablelist: name = format_name(f"{entry_infos['name']}_{subentry_infos['name']}") else: name = ( - f"{texts['NodeName']}_obj{texts['index']:04X}_" + f"{ctx['NodeName']}_obj{ctx['index']:04X}_" f"{format_name(subentry_infos['name'])}" ) - if typeinfos[2] == "visible_string": + 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 = f"sizeof ({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) @@ -437,35 +505,35 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non strindex += ( f" {{ " f"{subentry_infos['access'].upper()}{save}, " - f"{typeinfos[2]}, {sizeof}, (void*)&{start_digit}, NULL " + 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 += f"{typeinfos[0]}* {pointer_name} = &{name};\n" + pointedVariableContent += f"{typeinfos.type}* {pointer_name} = &{name};\n" if not entry_infos["struct"] & OD.IdenticalSubindexes: generateSubIndexArrayComment = True - headerObjectDefinitionContent += ( - f"#define {RE_NOTW.sub('_', texts['NodeName'])}" - f"_{RE_NOTW.sub('_', texts['EntryName'])}" + 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 += ( - f"#define {RE_NOTW.sub('_', texts['NodeName'])}" - f"_{RE_NOTW.sub('_', texts['EntryName'])}" + 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" ) - headerObjectDefinitionContent += "/* subindex define not generated for array objects */\n" + headerObjDefinitionContent += "/* subindex define not generated for array objects */\n" strindex += " };\n" indexContents[index] = strindex @@ -475,83 +543,93 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non if 0x1003 not in communicationlist: entry_infos = node.GetEntryInfos(0x1003) - texts["EntryName"] = entry_infos["name"] - indexContents[0x1003] = """ -/* 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 # -------------------------------------------------------------------------- - 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: @@ -559,12 +637,12 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non 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 + 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: @@ -574,9 +652,9 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non if cat == "PDO_TRS": maxPDOtransmit += 1 - texts["maxPDOtransmit"] = max(1, maxPDOtransmit) + ctx["maxPDOtransmit"] = max(1, maxPDOtransmit) for index_cat in INDEX_CATEGORIES: - strQuickIndex += f"\nconst quick_index {texts['NodeName']}_{index_cat} = {{\n" + 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: @@ -588,7 +666,8 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non # Write File Content # -------------------------------------------------------------------------- - fileContent = FILE_HEADER + f""" + fileContent = ctx.text(FILE_HEADER) + fileContent += f""" #include "{headerfile}" """ @@ -596,35 +675,35 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non /**************************************************************************/ /* 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 += """ /* @@ -642,77 +721,76 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non /**************************************************************************/ /* 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 # -------------------------------------------------------------------------- - texts["file_include_name"] = headerfile.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)s\n" % texts + headerFileContent %= "\n#endif // {file_include_name}\n" # -------------------------------------------------------------------------- # Write Header Object Defintions Content # -------------------------------------------------------------------------- - texts["file_include_objdef_name"] = headerfile.replace(".", "_OBJECTDEFINES_").upper() - headerObjectDefinitionContent = FILE_HEADER + """ -#ifndef %(file_include_objdef_name)s -#define %(file_include_objdef_name)s + 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: @@ -722,18 +800,20 @@ def generate_file_content(node: NodeProtocol, headerfile: str, pointers_dict=Non 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 generate_file(filepath: TPath, node: "NodeProtocol", pointers_dict=None): +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") diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index b194e93..68d6453 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -128,6 +128,33 @@ def __setattr__(self, name: str, value: Any): value = ODMapping(value) super().__setattr__(name, value) + # -------------------------------------------------------------------------- + # Legacy access methods + # -------------------------------------------------------------------------- + + def GetNodeName(self) -> str: + """Get the name of the node""" + return self.Name + + 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 + + def GetNodeDescription(self) -> str: + """Get the description of the node""" + return self.Description + + def GetDefaultStringSize(self) -> int: + """Get the default string size""" + return self.DefaultStringSize + + def GetIndexes(self) -> list[int]: + """ Return a sorted list of indexes in Object Dictionary """ + return list(self) # -------------------------------------------------------------------------- # Node Input/Output @@ -193,7 +220,8 @@ def DumpFile(self, filepath: TPath, filetype: str = "json", **kwargs): if filetype == 'c': log.debug("Writing C files '%s'", filepath) - gen_cfile.generate_file(filepath, self) + # 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") @@ -368,13 +396,6 @@ def GetSubentryLength(self, index: int) -> int: return 0 return len(val) - # FIXME: Keep this or use the __iterator__ method? - def GetIndexes(self): - """ - Return a sorted list of indexes in Object Dictionary - """ - return list(sorted(self.Dictionary)) - def GetBaseIndex(self, index: int) -> int: """ Return the index number of the base object """ return self.GetMappings(withmapping=True).FindBaseIndex(index) diff --git a/src/objdictgen/typing.py b/src/objdictgen/typing.py index a466bad..9ccc9dd 100644 --- a/src/objdictgen/typing.py +++ b/src/objdictgen/typing.py @@ -144,6 +144,30 @@ def GetSubentryInfos(self, index: int, subindex: int, compute: bool = True) -> T """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 # ====== From a2e286e3d6634f2c90368a0eab5bf0eb1a38e687 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 7 Apr 2024 13:52:23 +0200 Subject: [PATCH 26/28] Added nox testing * Reduced min version to py 3.10 --- .gitignore | 1 + noxfile.py | 8 +++++++ setup.cfg | 6 +---- src/objdictgen/gen_cfile.py | 6 ++--- src/objdictgen/node.py | 4 ++-- tests/test_imports.py | 45 +++++++++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 noxfile.py create mode 100644 tests/test_imports.py diff --git a/.gitignore b/.gitignore index a9d6d13..64729a3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ htmlcov/ .coverage tmp*/ dist/ +.nox *.pyc *.bak 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/setup.cfg b/setup.cfg index 55da6f4..2519372 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,10 +17,6 @@ classifiers = 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.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 @@ -31,7 +27,7 @@ package_dir = = src packages = find_namespace: include_package_data = True -python_requires = >=3.6, <4 +python_requires = >=3.10, <4 install_requires = jsonschema colorama diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 1f23e0f..dec13fe 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -22,7 +22,7 @@ from collections import UserDict from dataclasses import dataclass from pathlib import Path -from typing import Any, Self +from typing import Any from objdictgen.maps import OD from objdictgen.typing import NodeProtocol, TODValue, TPath @@ -71,7 +71,7 @@ def __init__(self, context: "CFileContext", text: str): self.text: str = text self.context: "CFileContext" = context - def __iadd__(self, other: "str|Text") -> Self: + def __iadd__(self, other: "str|Text") -> "Text": """Add a string to the text without formatting.""" self.text += str(other) return self @@ -80,7 +80,7 @@ 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) -> Self: + def __imod__(self, other: str) -> "Text": """Add a string to the text with formatting.""" self.text += other.format(**self.context) return self diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 68d6453..a9a70c1 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -20,7 +20,7 @@ import copy import logging -from typing import Any, Generator, Iterable, Iterator, Self +from typing import Any, Generator, Iterable, Iterator import colorama @@ -236,7 +236,7 @@ def asdict(self) -> dict[str, Any]: """ Return the class data as a dict """ return copy.deepcopy(self.__dict__) - def copy(self) -> Self: + def copy(self) -> "Node": """ Return a copy of the node """ 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 From e3cf0ee5f2a628b83a35b56529b32f602158d588 Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Sun, 7 Apr 2024 22:20:54 +0200 Subject: [PATCH 27/28] Updated Github actions configuration --- .github/workflows/python-package.yml | 20 +++++++++++++------- .github/workflows/python-publish.yml | 19 +++++++------------ .gitignore | 1 + pyproject.toml | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d7a1eb6..b7aee22 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,25 +10,31 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: false + fail-fast: true matrix: - python-version: ["3.x"] + python-version: ["3.11"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - cache-dependency-path: '**/setup.py' + cache-dependency-path: '**/setup.cfg' + # This is needed for compiling wxwidgets - name: Install gtk3 run: | sudo apt install libgtk-3-dev - name: Install dependencies run: | python -m pip install --upgrade pip setuptools - python -m pip install .[test,dist] + python -m pip install .[dev] - name: Test with pytest run: | - pytest -v + pytest --cov=objdictgen --cov-report=xml -p no:logging --tb=no + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: Laerdal/python-objdictgen diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 317fb69..6fd0741 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,28 +20,23 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: false + fail-fast: true matrix: python-version: ["3.x"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install gtk3 - run: | - sudo apt install libgtk-3-dev + cache: 'pip' + cache-dependency-path: '**/setup.cfg' - name: Install dependencies run: | - python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip setuptools python -m pip install build - # - name: Build 2.7 package - # if: ${{ matrix.python-version == '2.7' }} - # run: python -m build --wheel - name: Build package - if: ${{ matrix.python-version != '2.7' }} run: python -m build # - name: Publish package to TestPyPI # uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 diff --git a/.gitignore b/.gitignore index 64729a3..d2c422c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ htmlcov/ tmp*/ dist/ .nox +coverage.xml *.pyc *.bak diff --git a/pyproject.toml b/pyproject.toml index 17398b0..8f12171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [tool.pytest.ini_options] minversion = "6.0" -addopts = "-l --tb=native --cov=objdictgen" +addopts = "-l --tb=native" testpaths = [ "tests", ] From 98e7c24d9ef9f256767d07983ca1a31b7f5baa1e Mon Sep 17 00:00:00 2001 From: Svein Seldal Date: Wed, 10 Apr 2024 23:28:27 +0200 Subject: [PATCH 28/28] Fix bugs during GUI testing * Fix failure on reading the "need" field from node * Increase the viewport size of the index parts in GUI * Add error message if entering invalid data in GUI table editor * Fix bug where UI changes in the table didn't update the data * Added in-line documentation in ODStructTypes * Fix incorrect ValueError when having arrays * FIxed Node.DumpFile not guessing the filetype correctly * Fix handling of DOMAIN, which address #10 Testing * Add new "equiv_files" fixture to test equivalent files * Add test for testing `odg compare` * Added OD-file for DOMAIN and updated OD-files for consistency --- src/objdictgen/__main__.py | 3 +- src/objdictgen/eds_utils.py | 2 +- src/objdictgen/maps.py | 20 ++++++-- src/objdictgen/node.py | 17 +++++-- src/objdictgen/nodemanager.py | 20 ++++---- src/objdictgen/ui/nodeeditortemplate.py | 2 +- src/objdictgen/ui/subindextable.py | 12 +++-- tests/conftest.py | 24 +++++++++ tests/od/domain.json | 30 ++++++++++++ tests/od/domain.od | 65 +++++++++++++++++++++++++ tests/od/legacy-domain.od | 65 +++++++++++++++++++++++++ tests/od/legacy-master.od | 2 +- tests/od/legacy-slave-ds302.od | 2 +- tests/od/legacy-slave-emcy.od | 2 +- tests/od/legacy-slave-heartbeat.od | 2 +- tests/od/legacy-slave-nodeguarding.od | 2 +- tests/od/legacy-slave-sync.od | 2 +- tests/od/legacy-slave.od | 2 +- tests/od/profile-ds302-ds401.json | 2 +- tests/od/profile-ds302-ds401.od | 2 +- tests/od/slave-ds302.json | 2 +- tests/od/slave-ds302.od | 2 +- tests/od/slave-emcy.json | 2 +- tests/od/slave-emcy.od | 2 +- tests/od/slave-heartbeat.json | 2 +- tests/od/slave-heartbeat.od | 2 +- tests/od/slave-nodeguarding.json | 2 +- tests/od/slave-nodeguarding.od | 2 +- tests/od/slave-sync.json | 2 +- tests/od/slave-sync.od | 2 +- tests/od/slave.json | 2 +- tests/od/slave.od | 2 +- tests/test_odcompare.py | 21 +------- tests/test_odg.py | 23 ++++++++- 34 files changed, 279 insertions(+), 67 deletions(-) create mode 100644 tests/od/domain.json create mode 100644 tests/od/domain.od create mode 100644 tests/od/legacy-domain.od diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 5f3a3d2..63a2735 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -311,7 +311,8 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): 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 -- diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 57f60d7..b355602 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -655,7 +655,7 @@ def generate_eds_content(node: "Node", filepath: TPath): 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: diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 6b6246d..14c79da 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -67,10 +67,14 @@ class ODStructTypes: # # 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 @@ -78,12 +82,18 @@ 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 @@ -425,7 +435,7 @@ def FindMandatoryIndexes(self) -> list[int]: return [ index for index in self - if index >= 0x1000 and self[index]["need"] + if index >= 0x1000 and self[index].get("need") ] def FindEntryName(self, index: int, compute=True) -> str: diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index a9a70c1..210f78f 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -20,6 +20,7 @@ import copy import logging +from pathlib import Path from typing import Any, Generator, Iterable, Iterator import colorama @@ -195,8 +196,15 @@ def LoadJson(contents: str) -> "Node": """ Import a new Node from a JSON string """ return jsonod.generate_node(contents) - def DumpFile(self, filepath: TPath, filetype: str = "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", encoding="utf-8") as f: @@ -860,8 +868,11 @@ def RemoveMappingEntry(self, index: int, subindex: int|None = None): if subindex is None: self.UserMapping.pop(index) return - if subindex == len(self.UserMapping[index]["values"]) - 1: - self.UserMapping[index]["values"].pop(subindex) + 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}") diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index 74efda3..c3ea9dd 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -377,10 +377,7 @@ def RemoveSubentriesFromCurrent(self, index: int, number: int): length = node.GetEntry(index, 0) # FIXME: This code assumes that subindex 0 is the length of the entry assert isinstance(length, int) - if "nbmin" in infos: - nbmin = infos["nbmin"] - else: - nbmin = 1 + nbmin = infos.get("nbmin", 1) # Entry is an array, or is an array/record of manufacturer specific # FIXME: What is the intended order of the conditions? or-and on same level if (infos["struct"] & OD.IdenticalSubindexes or 0x2000 <= index <= 0x5FFF @@ -574,7 +571,7 @@ def RemoveCurrentVariable(self, index: int, subindex: int|None = None): node.RemoveMapVariable(index, subindex or 0) if not found: infos = node.GetEntryInfos(index) - if not infos["need"]: + if not infos.get("need"): node.RemoveEntry(index, subindex) if index in mappings[-1]: node.RemoveMappingEntry(index, subindex) @@ -700,8 +697,7 @@ def SetCurrentEntry(self, index: int, subindex: int, value: str, name: str, edit # Might fail with binascii.Error if hex is malformed if len(value) % 2 != 0: value = "0" + value - # FIXME: decode() produce bytes, which is not supported as of now - bvalue = codecs.decode(value, 'hex_codec') + bvalue = codecs.decode(value, 'hex_codec').decode() node.SetEntry(index, subindex, bvalue) elif editor == "dcf": node.SetEntry(index, subindex, value) @@ -737,12 +733,14 @@ def SetCurrentEntry(self, index: int, subindex: int, value: str, name: str, edit raise ValueError("Number must be positive") node.SetParamsEntry(index, subindex, params={"buffer_size": nvalue}) else: + nvalue: str|int = value if editor == "type": 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"]: - value = { + nvalue = { # type: ignore[assignment] access: abbrev for abbrev, access in maps.ACCESS_TYPE.items() }[value] @@ -753,7 +751,7 @@ def SetCurrentEntry(self, index: int, subindex: int, value: str, name: str, edit 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: value}) # type: ignore[misc] + node.SetMappingSubEntry(index, subindex, values={name: nvalue}) # type: ignore[misc] if not disable_buffer: self.BufferCurrentNode() @@ -1049,7 +1047,7 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d 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"]] @@ -1065,7 +1063,7 @@ def GetNodeEntryValues(self, node: Node, index: int) -> tuple[list[dict], list[d raise # FIXME: Originial code swallows exception try: dic["value"] = fmt.format(dic["value"]) - except TypeError as exc: + 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" diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index c47508d..d358df0 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -89,7 +89,7 @@ def SetStatusBarText(self, selection, node: Node): entryinfos = node.GetEntryInfos(index) name = entryinfos["name"] category = "Optional" - if entryinfos["need"]: + if entryinfos.get("need"): category = "Mandatory" struct = "VAR" number = "" diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index e3b8764..dfbbfdd 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -432,7 +432,7 @@ def _init_ctrls(self, parent): self.PartList = wx.ListBox(choices=[], id=ID_EDITINGPANELPARTLIST, name='PartList', parent=self, pos=wx.Point(0, 0), - size=wx.Size(-1, -1), style=0) + size=wx.Size(-1, 180), style=0) self.PartList.Bind(wx.EVT_LISTBOX, self.OnPartListBoxClick, id=ID_EDITINGPANELPARTLIST) @@ -729,7 +729,10 @@ def ShowDCFEntryDialog(self, row, col): 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) @@ -741,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() diff --git a/tests/conftest.py b/tests/conftest.py index ecc606d..204e108 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,6 +39,25 @@ 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"), +] + class ODPath(type(Path())): """ Overload on Path to add OD specific methods """ @@ -224,6 +243,11 @@ def odids(odlist): 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.""" 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/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 d1c6517..be304ee 100644 --- a/tests/od/legacy-master.od +++ b/tests/od/legacy-master.od @@ -3,7 +3,7 @@ -Empty master node +Empty master OD 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 fdd983a..d2a33b8 100644 --- a/tests/od/legacy-slave.od +++ b/tests/od/legacy-slave.od @@ -3,7 +3,7 @@ -Slave created with legacy objdictedit +Slave OD diff --git a/tests/od/profile-ds302-ds401.json b/tests/od/profile-ds302-ds401.json index 47264f4..59a9600 100644 --- a/tests/od/profile-ds302-ds401.json +++ b/tests/od/profile-ds302-ds401.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-27T19:26:46.575697", "name": "profile_ds302_ds401", - "description": "Test profile DS-302 and DS-401", + "description": "Test DS-302 and DS-401 profile", "type": "master", "id": 0, "profile": "DS-401", diff --git a/tests/od/profile-ds302-ds401.od b/tests/od/profile-ds302-ds401.od index 6acdf32..cac54cc 100644 --- a/tests/od/profile-ds302-ds401.od +++ b/tests/od/profile-ds302-ds401.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave-ds302.json b/tests/od/slave-ds302.json index fb29ab8..376e583 100644 --- a/tests/od/slave-ds302.json +++ b/tests/od/slave-ds302.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-28T00:45:00.339681", "name": "Slave", - "description": "", + "description": "Slave with DS-302", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave-ds302.od b/tests/od/slave-ds302.od index 60e3a1f..8d2a830 100644 --- a/tests/od/slave-ds302.od +++ b/tests/od/slave-ds302.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave-emcy.json b/tests/od/slave-emcy.json index eafbda4..6470513 100644 --- a/tests/od/slave-emcy.json +++ b/tests/od/slave-emcy.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-28T00:45:42.455357", "name": "Slave", - "description": "", + "description": "Slave with emergency support", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave-emcy.od b/tests/od/slave-emcy.od index e4e63ea..6591915 100644 --- a/tests/od/slave-emcy.od +++ b/tests/od/slave-emcy.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave-heartbeat.json b/tests/od/slave-heartbeat.json index ce65f59..8da43aa 100644 --- a/tests/od/slave-heartbeat.json +++ b/tests/od/slave-heartbeat.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-28T00:46:18.111927", "name": "Slave", - "description": "", + "description": "Slave with heartbeat", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave-heartbeat.od b/tests/od/slave-heartbeat.od index 7cbfec8..0958efa 100644 --- a/tests/od/slave-heartbeat.od +++ b/tests/od/slave-heartbeat.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave-nodeguarding.json b/tests/od/slave-nodeguarding.json index a17cf4e..353b5bf 100644 --- a/tests/od/slave-nodeguarding.json +++ b/tests/od/slave-nodeguarding.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-28T00:46:53.202929", "name": "Slave", - "description": "", + "description": "Slave with Node Guarding", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave-nodeguarding.od b/tests/od/slave-nodeguarding.od index 002f225..92beb67 100644 --- a/tests/od/slave-nodeguarding.od +++ b/tests/od/slave-nodeguarding.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave-sync.json b/tests/od/slave-sync.json index 995d685..904f867 100644 --- a/tests/od/slave-sync.json +++ b/tests/od/slave-sync.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-28T00:47:25.014478", "name": "Slave", - "description": "", + "description": "Slave with SYNC", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave-sync.od b/tests/od/slave-sync.od index 030147b..b8d0c83 100644 --- a/tests/od/slave-sync.od +++ b/tests/od/slave-sync.od @@ -4,7 +4,7 @@ - + diff --git a/tests/od/slave.json b/tests/od/slave.json index ccc6552..b8e8ab7 100644 --- a/tests/od/slave.json +++ b/tests/od/slave.json @@ -5,7 +5,7 @@ "$tool": "odg 3.4", "$date": "2024-02-27T00:53:43.165540", "name": "slave", - "description": "Default slave OD", + "description": "Slave OD", "type": "slave", "id": 0, "profile": "None", diff --git a/tests/od/slave.od b/tests/od/slave.od index c613b56..49e95ca 100644 --- a/tests/od/slave.od +++ b/tests/od/slave.od @@ -4,7 +4,7 @@ - + diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index f26cd75..2823f61 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -380,30 +380,13 @@ def test_save_with_profile(odpath, oddut, suffix, wd, profile): assert a == b -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"), -] -@pytest.mark.parametrize("equivs", EQUIVS, ids=(e[0] for e in EQUIVS)) @pytest.mark.parametrize("suffix", ['od', 'json']) -def test_equiv_compare(odpath, equivs, suffix): +def test_equiv_compare(odpath, equiv_files, suffix): """ Test reading the od and compare it with the corresponding json file """ - a, b = equivs + a, b = equiv_files oda = (odpath / a) + '.' + suffix odb = (odpath / b) + '.od' diff --git a/tests/test_odg.py b/tests/test_odg.py index fb7aa0f..8cec23b 100644 --- a/tests/test_odg.py +++ b/tests/test_odg.py @@ -8,7 +8,7 @@ def test_odg_list_woprofile(odjsoneds): od = odjsoneds main(( - 'list', + 'list', '-D', str(od) )) @@ -18,6 +18,25 @@ def test_odg_list_wprofile(odjsoneds, profile): od = odjsoneds main(( - 'list', + '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), + ))