# HG changeset patch # User Laurent Bessard # Date 1362441574 -3600 # Node ID 392791b5cc0449a8ab4be098eeb6ddd5741bba3d # Parent 58d07e039896ec9d85ab4531c2b4c2b5fe29b7b3 Improved Ethercat Network Configurator panels diff -r 58d07e039896 -r 392791b5cc04 etherlab/ConfigEditor.py --- a/etherlab/ConfigEditor.py Wed Feb 27 22:40:45 2013 +0100 +++ b/etherlab/ConfigEditor.py Tue Mar 05 00:59:34 2013 +0100 @@ -1,10 +1,13 @@ import os +import re +from types import TupleType import wx import wx.grid import wx.gizmos import wx.lib.buttons +from plcopen.structures import IEC_KEYWORDS, TestIdentifier from controls import CustomGrid, CustomTable, FolderTree from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT from util.BitmapLibrary import GetBitmap @@ -17,9 +20,12 @@ else: parent.Append(helpString=help, id=id, kind=kind, item=text) -def GetVariablesTableColnames(): +def GetVariablesTableColnames(position=False): _ = lambda x : x - return ["#", _("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")] + colname = ["#"] + if position: + colname.append(_("Position")) + return colname + [_("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")] ACCESS_TYPES = { 'ro': 'R', @@ -27,72 +33,78 @@ 'rw': 'R/W'} def GetAccessValue(access, pdo_mapping): - value = ACCESS_TYPES.get(access) + value = ACCESS_TYPES.get(access, "") if pdo_mapping != "": value += "/P" return value -class NodeEditor(ConfTreeNodeEditor): - - CONFNODEEDITOR_TABS = [ - (_("Ethercat node"), "_create_EthercatNodeEditor")] - - def _create_EthercatNodeEditor(self, prnt): - self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL) - - main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5) - main_sizer.AddGrowableCol(0) - main_sizer.AddGrowableRow(1) - - variables_label = wx.StaticText(self.EthercatNodeEditor, - label=_('Variable entries:')) - main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT) - - self.VariablesGrid = wx.gizmos.TreeListCtrl(self.EthercatNodeEditor, - style=wx.TR_DEFAULT_STYLE | - wx.TR_ROW_LINES | - wx.TR_COLUMN_LINES | - wx.TR_HIDE_ROOT | - wx.TR_FULL_ROW_HIGHLIGHT) - self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN, +VARIABLES_FILTERS = [ + (_("All"), (0x0000, 0xffff)), + (_("Communication Parameters"), (0x1000, 0x1fff)), + (_("Manufacturer Specific"), (0x2000, 0x5fff)), + (_("Standardized Device Profile"), (0x6000, 0x9fff))] + +ETHERCAT_INDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,4})$") +ETHERCAT_SUBINDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,2})$") +LOCATION_MODEL = re.compile("(?:%[IQM](?:[XBWLD]?([0-9]+(?:\.[0-9]+)*)))$") + +class NodeVariablesSizer(wx.FlexGridSizer): + + def __init__(self, parent, controler, position_column=False): + wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=2, vgap=5) + self.AddGrowableCol(0) + self.AddGrowableRow(1) + + self.Controler = controler + self.PositionColumn = position_column + + self.VariablesFilter = wx.ComboBox(parent) + self.VariablesFilter.Bind(wx.EVT_COMBOBOX, self.OnVariablesFilterChanged) + self.AddWindow(self.VariablesFilter, flag=wx.GROW) + + self.VariablesGrid = wx.gizmos.TreeListCtrl(parent, + style=wx.TR_DEFAULT_STYLE | + wx.TR_ROW_LINES | + wx.TR_COLUMN_LINES | + wx.TR_HIDE_ROOT | + wx.TR_FULL_ROW_HIGHLIGHT) + self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN, self.OnVariablesGridLeftClick) - main_sizer.AddWindow(self.VariablesGrid, border=10, - flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT) - - self.EthercatNodeEditor.SetSizer(main_sizer) - - return self.EthercatNodeEditor - - def __init__(self, parent, controler, window): - ConfTreeNodeEditor.__init__(self, parent, controler, window) - - for colname, colsize, colalign in zip(GetVariablesTableColnames(), - [40, 150, 100, 100, 150, 100], - [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, - wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]): - self.VariablesGrid.AddColumn(_(colname), colsize, colalign) - self.VariablesGrid.SetMainColumn(1) - - def GetBufferState(self): - return False, False - + self.AddWindow(self.VariablesGrid, flag=wx.GROW) + + self.Filters = [] + for desc, value in VARIABLES_FILTERS: + self.VariablesFilter.Append(desc) + self.Filters.append(value) + + self.VariablesFilter.SetSelection(0) + self.CurrentFilter = self.Filters[0] + + if position_column: + for colname, colsize, colalign in zip(GetVariablesTableColnames(position_column), + [40, 80, 350, 80, 100, 80, 80], + [wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, + wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, + wx.ALIGN_LEFT]): + self.VariablesGrid.AddColumn(_(colname), colsize, colalign) + self.VariablesGrid.SetMainColumn(2) + else: + for colname, colsize, colalign in zip(GetVariablesTableColnames(), + [40, 350, 80, 100, 80, 80], + [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, + wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]): + self.VariablesGrid.AddColumn(_(colname), colsize, colalign) + self.VariablesGrid.SetMainColumn(1) + def RefreshView(self): - ConfTreeNodeEditor.RefreshView(self) - - self.RefreshSlaveInfos() - - def RefreshSlaveInfos(self): - slave_infos = self.Controler.GetSlaveInfos() - if slave_infos is not None: - self.RefreshVariablesGrid(slave_infos["entries"]) - else: - self.RefreshVariablesGrid([]) + entries = self.Controler.GetSlaveVariables(self.CurrentFilter) + self.RefreshVariablesGrid(entries) def RefreshVariablesGrid(self, entries): root = self.VariablesGrid.GetRootItem() if not root.IsOk(): - root = self.VariablesGrid.AddRoot("Slave entries") - self.GenerateVariablesGridBranch(root, entries, GetVariablesTableColnames()) + root = self.VariablesGrid.AddRoot(_("Slave entries")) + self.GenerateVariablesGridBranch(root, entries, GetVariablesTableColnames(self.PositionColumn)) self.VariablesGrid.Expand(root) def GenerateVariablesGridBranch(self, root, entries, colnames, idx=0): @@ -113,6 +125,8 @@ self.VariablesGrid.SetItemText(item, value, col) if entry["PDOMapping"] == "": self.VariablesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY) + else: + self.VariablesGrid.SetItemBackgroundColour(item, wx.WHITE) self.VariablesGrid.SetItemPyData(item, entry) idx = self.GenerateVariablesGridBranch(item, entry["children"], colnames, idx) if not no_more_items: @@ -128,70 +142,703 @@ self.VariablesGrid.Delete(item) return idx - + + def OnVariablesFilterChanged(self, event): + filter = self.VariablesFilter.GetSelection() + if filter != -1: + self.CurrentFilter = self.Filters[filter] + self.RefreshView() + else: + try: + value = self.VariablesFilter.GetValue() + result = ETHERCAT_INDEX_MODEL.match(value) + if result is not None: + value = result.group(1) + index = int(value) + self.CurrentFilter = (index, index) + self.RefreshView() + except: + pass + event.Skip() + def OnVariablesGridLeftClick(self, event): item, flags, col = self.VariablesGrid.HitTest(event.GetPosition()) if item.IsOk(): entry = self.VariablesGrid.GetItemPyData(item) data_type = entry.get("Type", "") - pdo_mapping = entry.get("PDOMapping", "") + data_size = self.Controler.GetSizeOfType(data_type) - if (col == -1 and pdo_mapping != "" and - self.Controler.GetSizeOfType(data_type) is not None): - + if col == -1 and data_size is not None: + pdo_mapping = entry.get("PDOMapping", "") + access = entry.get("Access", "") entry_index = self.Controler.ExtractHexDecValue(entry.get("Index", "0")) entry_subindex = self.Controler.ExtractHexDecValue(entry.get("SubIndex", "0")) - var_name = "%s_%4.4x_%2.2x" % (self.Controler.CTNName(), entry_index, entry_subindex) - if pdo_mapping == "R": - dir = "%I" + if self.PositionColumn: + slave_pos = self.Controler.ExtractHexDecValue(entry.get("Position", "0")) else: - dir = "%Q" - location = "%s%s" % (dir, self.Controler.GetSizeOfType(data_type)) + \ - ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + (self.Controler.GetSlavePos(), entry_index, entry_subindex))) + slave_pos = self.Controler.GetSlavePos() - data = wx.TextDataObject(str((location, "location", data_type, var_name, ""))) - dragSource = wx.DropSource(self.VariablesGrid) - dragSource.SetData(data) - dragSource.DoDragDrop() - return + if pdo_mapping != "": + var_name = "%s_%4.4x_%2.2x" % (self.Controler.CTNName(), entry_index, entry_subindex) + if pdo_mapping == "R": + dir = "%I" + else: + dir = "%Q" + location = "%s%s" % (dir, data_size) + \ + ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + + (slave_pos, entry_index, entry_subindex))) + + data = wx.TextDataObject(str((location, "location", data_type, var_name, "", access))) + dragSource = wx.DropSource(self.VariablesGrid) + dragSource.SetData(data) + dragSource.DoDragDrop() + return + + elif self.ColumnPosition: + location = self.Controler.GetCurrentLocation() +\ + (slave_pos, entry_index, entry_subindex) + data = wx.TextDataObject(str((location, "variable", access))) + dragSource = wx.DropSource(self.VariablesGrid) + dragSource.SetData(data) + dragSource.DoDragDrop() + return + + event.Skip() + +class NodeEditor(ConfTreeNodeEditor): + + CONFNODEEDITOR_TABS = [ + (_("Ethercat node"), "_create_EthercatNodeEditor")] + + def _create_EthercatNodeEditor(self, prnt): + self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL) + + main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5) + main_sizer.AddGrowableCol(0) + main_sizer.AddGrowableRow(1) + + variables_label = wx.StaticText(self.EthercatNodeEditor, + label=_('Variable entries:')) + main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT) + + self.NodeVariables = NodeVariablesSizer(self.EthercatNodeEditor, self.Controler) + main_sizer.AddSizer(self.NodeVariables, border=10, + flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT) + + self.EthercatNodeEditor.SetSizer(main_sizer) + + return self.EthercatNodeEditor + + def __init__(self, parent, controler, window): + ConfTreeNodeEditor.__init__(self, parent, controler, window) + + def GetBufferState(self): + return False, False + + def RefreshView(self): + ConfTreeNodeEditor.RefreshView(self) + + self.NodeVariables.RefreshView() + +CIA402NodeEditor = NodeEditor + +def GetProcessVariablesTableColnames(): + _ = lambda x : x + return ["#", _("Name"), + _("Read from (nodeid, index, subindex)"), + _("Write to (nodeid, index, subindex)"), + _("Description")] + +class ProcessVariablesTable(CustomTable): + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + if col == 0: + return row + 1 + colname = self.GetColLabelValue(col, False) + if colname.startswith("Read from"): + value = self.data[row].get("ReadFrom", "") + if value == "": + return value + return "%d, #x%0.4x, #x%0.2x" % value + elif colname.startswith("Write to"): + value = self.data[row].get("WriteTo", "") + if value == "": + return value + return "%d, #x%0.4x, #x%0.2x" % value + return self.data[row].get(colname, "") + + def SetValue(self, row, col, value): + if col < len(self.colnames): + colname = self.GetColLabelValue(col, False) + if colname.startswith("Read from"): + self.data[row]["ReadFrom"] = value + elif colname.startswith("Write to"): + self.data[row]["WriteTo"] = value + else: + self.data[row][colname] = value + + def _updateColAttrs(self, grid): + """ + wx.grid.Grid -> update the column attributes to add the + appropriate renderer given the column name. + + Otherwise default to the default renderer. + """ + for row in range(self.GetNumberRows()): + for col in range(self.GetNumberCols()): + editor = None + renderer = None + colname = self.GetColLabelValue(col, False) + if colname in ["Name", "Description"]: + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + grid.SetReadOnly(row, col, False) + else: + grid.SetReadOnly(row, col, True) + + grid.SetCellEditor(row, col, editor) + grid.SetCellRenderer(row, col, renderer) + + self.ResizeRow(grid, row) + +class ProcessVariableDropTarget(wx.TextDropTarget): + + def __init__(self, parent): + wx.TextDropTarget.__init__(self) + self.ParentWindow = parent + + def OnDropText(self, x, y, data): + self.ParentWindow.Select() + x, y = self.ParentWindow.ProcessVariablesGrid.CalcUnscrolledPosition(x, y) + col = self.ParentWindow.ProcessVariablesGrid.XToCol(x) + row = self.ParentWindow.ProcessVariablesGrid.YToRow(y - self.ParentWindow.ProcessVariablesGrid.GetColLabelSize()) + message = None + try: + values = eval(data) + except: + message = _("Invalid value \"%s\" for process variable")%data + values = None + if not isinstance(values, TupleType): + message = _("Invalid value \"%s\" for process variable")%data + values = None + if values is not None and 2 <= col <= 3: + location = None + if values[1] == "location": + result = LOCATION_MODEL.match(values[0]) + if result is not None: + location = map(int, result.group(1).split('.')) + master_location = self.ParentWindow.GetMasterLocation() + if (master_location == tuple(location[:len(master_location)]) and + len(location) - len(master_location) == 3): + if col == 2: + self.ParentWindow.ProcessVariablesTable.SetValueByName( + row, "ReadFrom", tuple(location[len(master_location):])) + else: + self.ParentWindow.ProcessVariablesTable.SetValueByName( + row, "WriteTo", tuple(location[len(master_location):])) + self.ParentWindow.SaveProcessVariables() + self.ParentWindow.RefreshProcessVariables() + else: + message = _("Invalid value \"%s\" for process variable")%data + + if message is not None: + wx.CallAfter(self.ShowMessage, message) + + def ShowMessage(self, message): + message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + +def GetStartupCommandsTableColnames(): + _ = lambda x : x + return [_("Position"), _("Index"), _("Subindex"), _("Value"), _("Description")] + +class StartupCommandDropTarget(wx.TextDropTarget): + + def __init__(self, parent): + wx.TextDropTarget.__init__(self) + self.ParentWindow = parent + + def OnDropText(self, x, y, data): + self.ParentWindow.Select() + message = None + try: + values = eval(data) + except: + message = _("Invalid value \"%s\" for startup command")%data + values = None + if not isinstance(values, TupleType): + message = _("Invalid value \"%s\" for startup command")%data + values = None + if values is not None: + location = None + if values[1] == "location": + result = LOCATION_MODEL.match(values[0]) + if result is not None: + location = map(int, result.group(1).split('.')) + access = values[5] + elif values[1] == "variable": + location = values[0] + access = values[2] + if location is not None: + master_location = self.ParentWindow.GetMasterLocation() + if (master_location == tuple(location[:len(master_location)]) and + len(location) - len(master_location) == 3): + if access in ["wo", "rw"]: + self.ParentWindow.AddStartupCommand(*location[len(master_location):]) + else: + message = _("Entry can't be write through SDO") + else: + message = _("Invalid value \"%s\" for startup command")%data + + if message is not None: + wx.CallAfter(self.ShowMessage, message) + + def ShowMessage(self, message): + message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + +class StartupCommandsTable(CustomTable): + + """ + A custom wx.grid.Grid Table using user supplied data + """ + def __init__(self, parent, data, colnames): + # The base class must be initialized *first* + CustomTable.__init__(self, parent, data, colnames) + self.old_value = None + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + colname = self.GetColLabelValue(col, False) + value = self.data[row].get(colname, "") + if colname == "Index": + return "#x%0.4x" % value + elif colname == "Subindex": + return "#x%0.2x" % value + return value + + def SetValue(self, row, col, value): + if col < len(self.colnames): + colname = self.GetColLabelValue(col, False) + if colname in ["Index", "Subindex"]: + if colname == "Index": + result = ETHERCAT_INDEX_MODEL.match(value) + else: + result = ETHERCAT_SUBINDEX_MODEL.match(value) + if result is None: + return + value = int(result.group(1), 16) + elif colname == "Value": + value = int(value) + elif colname == "Position": + self.old_value = self.data[row][colname] + value = int(value) + self.data[row][colname] = value + + def GetOldValue(self): + return self.old_value + + def _updateColAttrs(self, grid): + """ + wx.grid.Grid -> update the column attributes to add the + appropriate renderer given the column name. + + Otherwise default to the default renderer. + """ + for row in range(self.GetNumberRows()): + for col in range(self.GetNumberCols()): + editor = None + renderer = None + colname = self.GetColLabelValue(col, False) + if colname in ["Position", "Value"]: + editor = wx.grid.GridCellNumberEditor() + renderer = wx.grid.GridCellNumberRenderer() + else: + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + + grid.SetCellEditor(row, col, editor) + grid.SetCellRenderer(row, col, renderer) + grid.SetReadOnly(row, col, False) + + self.ResizeRow(grid, row) + + def GetCommandIndex(self, position, command_idx): + for row, command in enumerate(self.data): + if command["Position"] == position and command["command_idx"] == command_idx: + return row + return None + +class MasterNodesVariablesSizer(NodeVariablesSizer): + + def __init__(self, parent, controler): + NodeVariablesSizer.__init__(self, parent, controler, True) + + self.CurrentNodesFilter = {} + + def SetCurrentNodesFilter(self, nodes_filter): + self.CurrentNodesFilter = nodes_filter + + def RefreshView(self): + if self.CurrentNodesFilter is not None: + args = self.CurrentNodesFilter.copy() + args["limits"] = self.CurrentFilter + entries = self.Controler.GetNodesVariables(**args) + self.RefreshVariablesGrid(entries) + +class MasterEditor(ConfTreeNodeEditor): + + CONFNODEEDITOR_TABS = [ + (_("Network"), "_create_EthercatMasterEditor")] + + def _create_EthercatMasterEditor(self, prnt): + self.EthercatMasterEditor = wx.ScrolledWindow(prnt, + style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL) + self.EthercatMasterEditor.Bind(wx.EVT_SIZE, self.OnResize) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + self.NodesFilter = wx.ComboBox(self.EthercatMasterEditor, + style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_COMBOBOX, self.OnNodesFilterChanged, self.NodesFilter) + self.Bind(wx.EVT_TEXT_ENTER, self.OnNodesFilterChanged, self.NodesFilter) + + process_variables_header = wx.BoxSizer(wx.HORIZONTAL) + + process_variables_label = wx.StaticText(self.EthercatMasterEditor, + label=_("Process variables mapped between nodes:")) + process_variables_header.AddWindow(process_variables_label, 1, + flag=wx.ALIGN_CENTER_VERTICAL) + + for name, bitmap, help in [ + ("AddVariableButton", "add_element", _("Add process variable")), + ("DeleteVariableButton", "remove_element", _("Remove process variable")), + ("UpVariableButton", "up", _("Move process variable up")), + ("DownVariableButton", "down", _("Move process variable down"))]: + button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), + size=wx.Size(28, 28), style=wx.NO_BORDER) + button.SetToolTipString(help) + setattr(self, name, button) + process_variables_header.AddWindow(button, border=5, flag=wx.LEFT) + + self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL) + self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150)) + self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self)) + self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, + self.OnProcessVariablesGridCellChange) + self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, + self.OnProcessVariablesGridCellLeftClick) + + startup_commands_header = wx.BoxSizer(wx.HORIZONTAL) + + startup_commands_label = wx.StaticText(self.EthercatMasterEditor, + label=_("Startup service variables assignments:")) + startup_commands_header.AddWindow(startup_commands_label, 1, + flag=wx.ALIGN_CENTER_VERTICAL) + + for name, bitmap, help in [ + ("AddCommandButton", "add_element", _("Add startup service variable")), + ("DeleteCommandButton", "remove_element", _("Remove startup service variable"))]: + button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), + size=wx.Size(28, 28), style=wx.NO_BORDER) + button.SetToolTipString(help) + setattr(self, name, button) + startup_commands_header.AddWindow(button, border=5, flag=wx.LEFT) + + self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL) + self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self)) + self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150)) + self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, + self.OnStartupCommandsGridCellChange) + + second_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Nodes variables filter:")) + second_staticbox_sizer = wx.StaticBoxSizer(second_staticbox, wx.VERTICAL) + + self.NodesVariables = MasterNodesVariablesSizer(self.EthercatMasterEditor, self.Controler) + second_staticbox_sizer.AddSizer(self.NodesVariables, 1, border=5, flag=wx.GROW|wx.ALL) + + main_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Node filter:")) + staticbox_sizer = wx.StaticBoxSizer(main_staticbox, wx.VERTICAL) + main_sizer.AddSizer(staticbox_sizer, border=10, flag=wx.GROW|wx.ALL) + main_staticbox_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=0) + main_staticbox_sizer.AddGrowableCol(0) + main_staticbox_sizer.AddGrowableRow(1) + main_staticbox_sizer.AddGrowableRow(3) + staticbox_sizer.AddSizer(main_staticbox_sizer, 1, flag=wx.GROW) + main_staticbox_sizer.AddWindow(self.NodesFilter, border=5, flag=wx.GROW|wx.ALL) + main_staticbox_sizer.AddSizer(process_variables_header, border=5, + flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + main_staticbox_sizer.AddWindow(self.ProcessVariablesGrid, 1, + border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + main_staticbox_sizer.AddSizer(startup_commands_header, + border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + main_staticbox_sizer.AddWindow(self.StartupCommandsGrid, 1, + border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + main_staticbox_sizer.AddSizer(second_staticbox_sizer, 1, + border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + + self.EthercatMasterEditor.SetSizer(main_sizer) + + return self.EthercatMasterEditor + + def __init__(self, parent, controler, window): + ConfTreeNodeEditor.__init__(self, parent, controler, window) + + self.ProcessVariablesDefaultValue = {"Name": "", "ReadFrom": "", "WriteTo": "", "Description": ""} + self.ProcessVariablesTable = ProcessVariablesTable(self, [], GetProcessVariablesTableColnames()) + self.ProcessVariablesColSizes = [40, 100, 150, 150, 200] + self.ProcessVariablesColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT] + + self.ProcessVariablesGrid.SetTable(self.ProcessVariablesTable) + self.ProcessVariablesGrid.SetButtons({"Add": self.AddVariableButton, + "Delete": self.DeleteVariableButton, + "Up": self.UpVariableButton, + "Down": self.DownVariableButton}) + + def _AddVariablesElement(new_row): + self.ProcessVariablesTable.InsertRow(new_row, self.ProcessVariablesDefaultValue.copy()) + self.SaveProcessVariables() + self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid) + return new_row + setattr(self.ProcessVariablesGrid, "_AddRow", _AddVariablesElement) + + def _DeleteVariablesElement(row): + self.ProcessVariablesTable.RemoveRow(row) + self.SaveProcessVariables() + self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid) + setattr(self.ProcessVariablesGrid, "_DeleteRow", _DeleteVariablesElement) + def _MoveVariablesElement(row, move): + new_row = self.ProcessVariablesTable.MoveRow(row, move) + if new_row != row: + self.SaveProcessVariables() + self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid) + return new_row + setattr(self.ProcessVariablesGrid, "_MoveRow", _MoveVariablesElement) + + self.ProcessVariablesGrid.SetRowLabelSize(0) + for col in range(self.ProcessVariablesTable.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.ProcessVariablesColAlignements[col], wx.ALIGN_CENTRE) + self.ProcessVariablesGrid.SetColAttr(col, attr) + self.ProcessVariablesGrid.SetColMinimalWidth(col, self.ProcessVariablesColSizes[col]) + self.ProcessVariablesGrid.AutoSizeColumn(col, False) + self.ProcessVariablesGrid.RefreshButtons() + + self.StartupCommandsDefaultValue = {"Position": 0, "Index": 0, "Subindex": 0, "Value": 0, "Description": ""} + self.StartupCommandsTable = StartupCommandsTable(self, [], GetStartupCommandsTableColnames()) + self.StartupCommandsColSizes = [100, 100, 50, 100, 200] + self.StartupCommandsColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT] + + self.StartupCommandsGrid.SetTable(self.StartupCommandsTable) + self.StartupCommandsGrid.SetButtons({"Add": self.AddCommandButton, + "Delete": self.DeleteCommandButton}) + + def _AddCommandsElement(new_row): + command = self.StartupCommandsDefaultValue.copy() + command_idx = self.Controler.AppendStartupCommand(command) + self.RefreshStartupCommands() + self.RefreshBuffer() + return self.StartupCommandsTable.GetCommandIndex(command["Position"], command_idx) + setattr(self.StartupCommandsGrid, "_AddRow", _AddCommandsElement) + + def _DeleteCommandsElement(row): + command = self.StartupCommandsTable.GetRow(row) + self.Controler.RemoveStartupCommand(command["Position"], command["command_idx"]) + self.RefreshStartupCommands() + self.RefreshBuffer() + setattr(self.StartupCommandsGrid, "_DeleteRow", _DeleteCommandsElement) + + self.StartupCommandsGrid.SetRowLabelSize(0) + for col in range(self.StartupCommandsTable.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.StartupCommandsColAlignements[col], wx.ALIGN_CENTRE) + self.StartupCommandsGrid.SetColAttr(col, attr) + self.StartupCommandsGrid.SetColMinimalWidth(col, self.StartupCommandsColSizes[col]) + self.StartupCommandsGrid.AutoSizeColumn(col, False) + self.StartupCommandsGrid.RefreshButtons() + + def RefreshBuffer(self): + self.ParentWindow.RefreshTitle() + self.ParentWindow.RefreshFileMenu() + self.ParentWindow.RefreshEditMenu() + self.ParentWindow.RefreshPageTitles() + + def RefreshView(self): + ConfTreeNodeEditor.RefreshView(self) + + self.RefreshNodesFilter() + self.RefreshProcessVariables() + self.RefreshStartupCommands() + self.NodesVariables.RefreshView() + + def RefreshNodesFilter(self): + value = self.NodesFilter.GetValue() + self.NodesFilter.Clear() + self.NodesFilter.Append(_("All")) + self.NodesFilterValues = [{}] + for vendor_id, vendor_name in self.Controler.GetLibraryVendors(): + self.NodesFilter.Append(_("%s's nodes") % vendor_name) + self.NodesFilterValues.append({"vendor": vendor_id}) + self.NodesFilter.Append(_("CIA402 nodes")) + self.NodesFilterValues.append({"slave_profile": 402}) + if value in self.NodesFilter.GetStrings(): + self.NodesFilter.SetStringSelection(value) + else: + try: + int(value) + self.NodesFilter.SetValue(value) + except: + self.NodesFilter.SetSelection(0) + self.RefreshCurrentNodesFilter() + + def RefreshCurrentNodesFilter(self): + filter = self.NodesFilter.GetSelection() + if filter != -1: + self.CurrentNodesFilter = self.NodesFilterValues[filter] + else: + try: + self.CurrentNodesFilter = {"slave_pos": int(self.NodesFilter.GetValue())} + except: + self.CurrentNodesFilter = None + self.NodesVariables.SetCurrentNodesFilter(self.CurrentNodesFilter) + + def RefreshProcessVariables(self): + self.ProcessVariablesTable.SetData( + self.Controler.GetProcessVariables()) + self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid) + + def SaveProcessVariables(self): + self.Controler.SetProcessVariables( + self.ProcessVariablesTable.GetData()) + self.RefreshBuffer() + + def RefreshStartupCommands(self): + if self.CurrentNodesFilter is not None: + self.StartupCommandsTable.SetData( + self.Controler.GetStartupCommands(**self.CurrentNodesFilter)) + self.StartupCommandsTable.ResetView(self.StartupCommandsGrid) + + def SelectStartupCommand(self, position, command_idx): + self.StartupCommandsGrid.SetSelectedRow( + self.StartupCommandsTable.GetCommandIndex(position, command_idx)) + + def GetMasterLocation(self): + return self.Controler.GetCurrentLocation() + + def AddStartupCommand(self, position, index, subindex): + command = self.StartupCommandsDefaultValue.copy() + command["Position"] = position + command["Index"] = index + command["Subindex"] = subindex + command_idx = self.Controler.AppendStartupCommand(command) + self.RefreshStartupCommands() + self.RefreshBuffer() + self.StartupCommandsGrid.SetSelectedRow( + self.StartupCommandsTable.GetCommandIndex(position, command_idx)) + + def OnNodesFilterChanged(self, event): + self.RefreshCurrentNodesFilter() + if self.CurrentNodesFilter is not None: + self.RefreshStartupCommands() + self.NodesVariables.RefreshView() event.Skip() - -CIA402NodeEditor = NodeEditor - - + + def OnProcessVariablesGridCellChange(self, event): + row, col = event.GetRow(), event.GetCol() + colname = self.ProcessVariablesTable.GetColLabelValue(col, False) + value = self.ProcessVariablesTable.GetValue(row, col) + message = None + if colname == "Name": + if not TestIdentifier(value): + message = _("\"%s\" is not a valid identifier!") % value + elif value.upper() in IEC_KEYWORDS: + message = _("\"%s\" is a keyword. It can't be used!") % value + elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.ProcessVariablesTable.GetData()) if idx != row]: + message = _("An variable named \"%s\" already exists!") % value + if message is None: + self.SaveProcessVariables() + wx.CallAfter(self.ProcessVariablesTable.ResetView, self.ProcessVariablesGrid) + event.Skip() + else: + dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) + dialog.ShowModal() + dialog.Destroy() + event.Veto() + + def OnProcessVariablesGridCellLeftClick(self, event): + event.Skip() + + def OnStartupCommandsGridCellChange(self, event): + row, col = event.GetRow(), event.GetCol() + colname = self.StartupCommandsTable.GetColLabelValue(col, False) + value = self.StartupCommandsTable.GetValue(row, col) + message = None + if colname == "Position": + if value not in self.Controler.GetSlaves(): + message = _("No slave defined at position %d!") % value + if message is None: + self.Controler.RemoveStartupCommand( + self.StartupCommandsTable.GetOldValue(), + self.StartupCommandsTable.GetValueByName(row, "command_idx")) + command = self.StartupCommandsTable.GetRow(row) + command_idx = self.Controler.AppendStartupCommand(command) + wx.CallAfter(self.RefreshStartupCommands) + wx.CallAfter(self.SelectStartupCommand, command["Position"], command_idx) + else: + self.Controler.SetStartupCommandInfos(self.StartupCommandsTable.GetRow(row)) + if message is None: + self.RefreshBuffer() + event.Skip() + else: + dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) + dialog.ShowModal() + dialog.Destroy() + event.Veto() + + def OnResize(self, event): + self.EthercatMasterEditor.GetBestSize() + xstart, ystart = self.EthercatMasterEditor.GetViewStart() + window_size = self.EthercatMasterEditor.GetClientSize() + maxx, maxy = self.EthercatMasterEditor.GetMinSize() + posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT)) + posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT)) + self.EthercatMasterEditor.Scroll(posx, posy) + self.EthercatMasterEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, + maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy) + event.Skip() + def GetModulesTableColnames(): _ = lambda x : x return [_("Name"), _("PDO alignment (bits)")] -class LibraryEditorPanel(wx.ScrolledWindow): +class LibraryEditorSizer(wx.FlexGridSizer): def __init__(self, parent, module_library, buttons): - wx.ScrolledWindow.__init__(self, parent, - style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL) - self.Bind(wx.EVT_SIZE, self.OnResize) + wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=4, vgap=5) self.ModuleLibrary = module_library - main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=5) - main_sizer.AddGrowableCol(0) - main_sizer.AddGrowableRow(1) - main_sizer.AddGrowableRow(3) - - ESI_files_label = wx.StaticText(self, + self.AddGrowableCol(0) + self.AddGrowableRow(1) + self.AddGrowableRow(3) + + ESI_files_label = wx.StaticText(parent, label=_("ESI Files:")) - main_sizer.AddWindow(ESI_files_label, border=10, + self.AddWindow(ESI_files_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT) folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0) folder_tree_sizer.AddGrowableCol(0) folder_tree_sizer.AddGrowableRow(0) - main_sizer.AddSizer(folder_tree_sizer, border=10, + self.AddSizer(folder_tree_sizer, border=10, flag=wx.GROW|wx.LEFT|wx.RIGHT) - self.ESIFiles = FolderTree(self, self.GetPath(), editable=False) + self.ESIFiles = FolderTree(parent, self.GetPath(), editable=False) self.ESIFiles.SetFilter(".xml") - self.ESIFiles.SetMinSize(wx.Size(600, 300)) folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW) buttons_sizer = wx.BoxSizer(wx.VERTICAL) @@ -199,7 +846,7 @@ flag=wx.ALIGN_CENTER_VERTICAL) for idx, (name, bitmap, help, callback) in enumerate(buttons): - button = wx.lib.buttons.GenBitmapButton(self, + button = wx.lib.buttons.GenBitmapButton(parent, bitmap=GetBitmap(bitmap), size=wx.Size(28, 28), style=wx.NO_BORDER) button.SetToolTipString(help) @@ -211,28 +858,25 @@ if callback is None: callback = getattr(self, "On" + name, None) if callback is not None: - self.Bind(wx.EVT_BUTTON, callback, button) + parent.Bind(wx.EVT_BUTTON, callback, button) buttons_sizer.AddWindow(button, border=10, flag=flag) - modules_label = wx.StaticText(self, + modules_label = wx.StaticText(parent, label=_("Modules library:")) - main_sizer.AddSizer(modules_label, border=10, + self.AddSizer(modules_label, border=10, flag=wx.LEFT|wx.RIGHT) - self.ModulesGrid = wx.gizmos.TreeListCtrl(self, + self.ModulesGrid = wx.gizmos.TreeListCtrl(parent, style=wx.TR_DEFAULT_STYLE | wx.TR_ROW_LINES | wx.TR_COLUMN_LINES | wx.TR_HIDE_ROOT | wx.TR_FULL_ROW_HIGHLIGHT) - self.ModulesGrid.SetMinSize(wx.Size(600, 300)) self.ModulesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DCLICK, self.OnModulesGridLeftDClick) - main_sizer.AddWindow(self.ModulesGrid, border=10, + self.AddWindow(self.ModulesGrid, border=10, flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT) - self.SetSizer(main_sizer) - for colname, colsize, colalign in zip(GetModulesTableColnames(), [400, 150], [wx.ALIGN_LEFT, wx.ALIGN_RIGHT]): @@ -242,6 +886,10 @@ def GetPath(self): return self.ModuleLibrary.GetPath() + def SetControlMinSize(self, size): + self.ESIFiles.SetMinSize(size) + self.ModulesGrid.SetMinSize(size) + def GetSelectedFilePath(self): return self.ESIFiles.GetPath() @@ -349,18 +997,6 @@ dialog.Destroy() event.Skip() - - def OnResize(self, event): - self.GetBestSize() - xstart, ystart = self.GetViewStart() - window_size = self.GetClientSize() - maxx, maxy = self.GetMinSize() - posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT)) - posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT)) - self.Scroll(posx, posy) - self.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, - maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy) - event.Skip() class DatabaseManagementDialog(wx.Dialog): @@ -373,10 +1009,11 @@ main_sizer.AddGrowableCol(0) main_sizer.AddGrowableRow(0) - self.DatabaseEditor = LibraryEditorPanel(self, database, + self.DatabaseSizer = LibraryEditorSizer(self, database, [("ImportButton", "ImportESI", _("Import file to ESI files database"), None), ("DeleteButton", "remove_element", _("Remove file from database"), None)]) - main_sizer.AddWindow(self.DatabaseEditor, border=10, + self.DatabaseSizer.SetControlMinSize(wx.Size(0, 0)) + main_sizer.AddSizer(self.DatabaseSizer, border=10, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) @@ -387,10 +1024,10 @@ self.SetSizer(main_sizer) - self.DatabaseEditor.RefreshView() + self.DatabaseSizer.RefreshView() def GetValue(self): - return self.DatabaseEditor.GetSelectedFilePath() + return self.DatabaseSizer.GetSelectedFilePath() class LibraryEditor(ConfTreeNodeEditor): @@ -398,11 +1035,17 @@ (_("Modules Library"), "_create_ModuleLibraryEditor")] def _create_ModuleLibraryEditor(self, prnt): - self.ModuleLibraryEditor = LibraryEditorPanel(prnt, + self.ModuleLibraryEditor = wx.ScrolledWindow(prnt, + style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL) + self.ModuleLibraryEditor.Bind(wx.EVT_SIZE, self.OnResize) + + self.ModuleLibrarySizer = LibraryEditorSizer(self.ModuleLibraryEditor, self.Controler.GetModulesLibraryInstance(), [("ImportButton", "ImportESI", _("Import ESI file"), None), ("AddButton", "ImportDatabase", _("Add file from ESI files database"), self.OnAddButton), ("DeleteButton", "remove_element", _("Remove file from library"), None)]) + self.ModuleLibrarySizer.SetControlMinSize(wx.Size(0, 200)) + self.ModuleLibraryEditor.SetSizer(self.ModuleLibrarySizer) return self.ModuleLibraryEditor @@ -413,7 +1056,7 @@ def RefreshView(self): ConfTreeNodeEditor.RefreshView(self) - self.ModuleLibraryEditor.RefreshView() + self.ModuleLibrarySizer.RefreshView() def OnAddButton(self, event): dialog = DatabaseManagementDialog(self, @@ -425,7 +1068,20 @@ dialog.Destroy() - wx.CallAfter(self.ModuleLibraryEditor.RefreshView) + wx.CallAfter(self.ModuleLibrarySizer.RefreshView) event.Skip() + def OnResize(self, event): + self.ModuleLibraryEditor.GetBestSize() + xstart, ystart = self.ModuleLibraryEditor.GetViewStart() + window_size = self.ModuleLibraryEditor.GetClientSize() + maxx, maxy = self.ModuleLibraryEditor.GetMinSize() + posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT)) + posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT)) + self.ModuleLibraryEditor.Scroll(posx, posy) + self.ModuleLibraryEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, + maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy) + event.Skip() + + diff -r 58d07e039896 -r 392791b5cc04 etherlab/etherlab.py --- a/etherlab/etherlab.py Wed Feb 27 22:40:45 2013 +0100 +++ b/etherlab/etherlab.py Tue Mar 05 00:59:34 2013 +0100 @@ -9,7 +9,7 @@ from POULibrary import POULibrary from ConfigTreeNode import ConfigTreeNode from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY -from ConfigEditor import NodeEditor, CIA402NodeEditor, LibraryEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE +from ConfigEditor import NodeEditor, CIA402NodeEditor, MasterEditor, LibraryEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE try: from MotionLibrary import Headers, AxisXSD @@ -161,6 +161,9 @@ def GetSlaveInfos(self): return self.CTNParent.GetSlaveInfos(self.GetSlavePos()) + def GetSlaveVariables(self, limits): + return self.CTNParent.GetSlaveVariables(self.GetSlavePos(), limits) + def GetVariableLocationTree(self): return {"name": self.BaseParams.getName(), "type": LOCATION_CONFNODE, @@ -470,6 +473,11 @@ else: raise ValueError, "Not supported base" +def sort_commands(x, y): + if x["Index"] == y["Index"]: + return cmp(x["Subindex"], y["Subindex"]) + return cmp(x["Index"], y["Index"]) + cls = EtherCATConfigClasses.get("Config_Slave", None) if cls: @@ -488,37 +496,146 @@ slave_info.setProductCode(ExtractHexDecValue(type_infos["product_code"])) slave_info.setRevisionNo(ExtractHexDecValue(type_infos["revision_number"])) setattr(cls, "setType", setType) - -class _EthercatCTN: - XSD = """ + + def getInitCmds(self, create_default=False): + Mailbox = self.getMailbox() + if Mailbox is None: + if create_default: + self.addMailbox() + Mailbox = self.getMailbox() + else: + return None + CoE = Mailbox.getCoE() + if CoE is None: + if create_default: + Mailbox.addCoE() + CoE = Mailbox.getCoE() + else: + return None + InitCmds = CoE.getInitCmds() + if InitCmds is None and create_default: + CoE.addInitCmds() + InitCmds = CoE.getInitCmds() + return InitCmds + setattr(cls, "getInitCmds", getInitCmds) + + def getStartupCommands(self): + pos = self.getInfo().getPhysAddr() + InitCmds = self.getInitCmds() + if InitCmds is None: + return [] + commands = [] + for idx, InitCmd in enumerate(InitCmds.getInitCmd()): + comment = InitCmd.getComment() + if comment is None: + comment = "" + commands.append({ + "command_idx": idx, + "Position": pos, + "Index": InitCmd.getIndex(), + "Subindex": InitCmd.getSubIndex(), + "Value": InitCmd.getData(), + "Description": comment}) + commands.sort(sort_commands) + return commands + setattr(cls, "getStartupCommands", getStartupCommands) + + def appendStartupCommand(self, command_infos): + InitCmds = self.getInitCmds(True) + command = EtherCATConfigClasses["InitCmds_InitCmd"]() + command.setIndex(command_infos["Index"]) + command.setSubIndex(command_infos["Subindex"]) + command.setData(command_infos["Value"]) + command.setComment(command_infos["Description"]) + InitCmds.appendInitCmd(command) + return len(InitCmds.getInitCmd()) - 1 + setattr(cls, "appendStartupCommand", appendStartupCommand) + + def setStartupCommand(self, command_infos): + InitCmds = self.getInitCmds() + if InitCmds is not None: + commands = InitCmds.getInitCmd() + if command_infos["command_idx"] < len(commands): + command = commands[command_infos["command_idx"]] + command.setIndex(command_infos["Index"]) + command.setSubIndex(command_infos["Subindex"]) + command.setData(command_infos["Value"]) + command.setComment(command_infos["Description"]) + setattr(cls, "setStartupCommand", setStartupCommand) + + def removeStartupCommand(self, command_idx): + InitCmds = self.getInitCmds() + if InitCmds is not None: + if command_idx < len(InitCmds.getInitCmd()): + InitCmds.removeInitCmd(command_idx) + setattr(cls, "removeStartupCommand", removeStartupCommand) + +ProcessVariablesXSD = """ - + - + + + + + + + + + + + + + + + + + - """ +""" + +ProcessVariablesClasses = GenerateClassesFromXSDstring(ProcessVariablesXSD) + +class _EthercatCTN: CTNChildrenTypes = [("EthercatSlave", _EthercatSlaveCTN, "Ethercat Slave")] if HAS_MCL: CTNChildrenTypes.append(("EthercatCIA402Slave", _EthercatCIA402SlaveCTN, "Ethercat CIA402 Slave")) + EditorType = MasterEditor def __init__(self): - filepath = self.ConfigFileName() - + config_filepath = self.ConfigFileName() + config_is_saved = False self.Config = EtherCATConfigClasses["EtherCATConfig"]() - if os.path.isfile(filepath): - xmlfile = open(filepath, 'r') - tree = minidom.parse(xmlfile) - xmlfile.close() + if os.path.isfile(config_filepath): + config_xmlfile = open(config_filepath, 'r') + config_tree = minidom.parse(config_xmlfile) + config_xmlfile.close() - for child in tree.childNodes: - if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "EtherCATConfig": + for child in config_tree.childNodes: + if child.nodeType == config_tree.ELEMENT_NODE and child.nodeName == "EtherCATConfig": self.Config.loadXMLTree(child) - self.CreateConfigBuffer(True) + config_is_saved = True + + process_filepath = self.ProcessVariablesFileName() + process_is_saved = False + self.ProcessVariables = ProcessVariablesClasses["ProcessVariables"]() + if os.path.isfile(process_filepath): + process_xmlfile = open(process_filepath, 'r') + process_tree = minidom.parse(process_xmlfile) + process_xmlfile.close() + + for child in process_tree.childNodes: + if child.nodeType == process_tree.ELEMENT_NODE and child.nodeName == "ProcessVariables": + self.ProcessVariables.loadXMLTree(child) + process_is_saved = True + + if config_is_saved and process_is_saved: + self.CreateBuffer(True) else: - self.CreateConfigBuffer(False) + self.CreateBuffer(False) self.OnCTNSave() def ExtractHexDecValue(self, value): @@ -529,7 +646,10 @@ def ConfigFileName(self): return os.path.join(self.CTNPath(), "config.xml") - + + def ProcessVariablesFileName(self): + return os.path.join(self.CTNPath(), "process_variables.xml") + def GetSlaves(self): slaves = [] for slave in self.Config.getConfig().getSlave(): @@ -544,6 +664,97 @@ return slave return None + def FilterSlave(self, slave, vendor=None, slave_pos=None, slave_profile=None): + if slave_pos is not None and slave.getInfo().getPhysAddr() != slave_pos: + return False + type_infos = slave.getType() + if vendor is not None and ExtractHexDecValue(type_infos["vendor"]) != vendor: + return False + device, alignment = self.GetModuleInfos(type_infos) + if slave_profile is not None and slave_profile not in device.GetProfileNumbers(): + return False + return True + + def GetStartupCommands(self, vendor=None, slave_pos=None, slave_profile=None): + commands = [] + for slave in self.Config.getConfig().getSlave(): + if self.FilterSlave(slave, vendor, slave_pos, slave_profile): + commands.append((slave.getInfo().getPhysAddr(), slave.getStartupCommands())) + commands.sort() + return reduce(lambda x, y: x + y[1], commands, []) + + def AppendStartupCommand(self, command_infos): + slave = self.GetSlave(command_infos["Position"]) + if slave is not None: + command_idx = slave.appendStartupCommand(command_infos) + self.BufferModel() + return command_idx + return None + + def SetStartupCommandInfos(self, command_infos): + slave = self.GetSlave(command_infos["Position"]) + if slave is not None: + slave.setStartupCommand(command_infos) + self.BufferModel() + + def RemoveStartupCommand(self, slave_pos, command_idx): + slave = self.GetSlave(slave_pos) + if slave is not None: + slave.removeStartupCommand(command_idx) + self.BufferModel() + + def SetProcessVariables(self, variables): + vars = [] + for var in variables: + variable = ProcessVariablesClasses["ProcessVariables_variable"]() + variable.setName(var["Name"]) + variable.setComment(var["Description"]) + if var["ReadFrom"] != "": + position, index, subindex = var["ReadFrom"] + if variable.getReadFrom() is None: + variable.addReadFrom() + read_from = variable.getReadFrom() + read_from.setPosition(position) + read_from.setIndex(index) + read_from.setSubIndex(subindex) + elif variable.getReadFrom() is not None: + variable.deleteReadFrom() + if var["WriteTo"] != "": + position, index, subindex = var["WriteTo"] + if variable.getWriteTo() is None: + variable.addWriteTo() + write_to = variable.getWriteTo() + write_to.setPosition(position) + write_to.setIndex(index) + write_to.setSubIndex(subindex) + elif variable.getWriteTo() is not None: + variable.deleteWriteTo() + vars.append(variable) + self.ProcessVariables.setvariable(vars) + self.BufferModel() + + def GetProcessVariables(self): + variables = [] + for variable in self.ProcessVariables.getvariable(): + var = {"Name": variable.getName(), + "Description": variable.getComment()} + read_from = variable.getReadFrom() + if read_from is not None: + var["ReadFrom"] = (read_from.getPosition(), + read_from.getIndex(), + read_from.getSubIndex()) + else: + var["ReadFrom"] = "" + write_to = variable.getWriteTo() + if write_to is not None: + var["WriteTo"] = (write_to.getPosition(), + write_to.getIndex(), + write_to.getSubIndex()) + else: + var["WriteTo"] = "" + variables.append(var) + return variables + def _ScanNetwork(self): app_frame = self.GetCTRoot().AppFrame @@ -599,7 +810,7 @@ slave_infos.setPhysAddr(newConfNodeOpj.BaseParams.getIEC_Channel()) slave_infos.setAutoIncAddr(0) self.Config.getConfig().appendSlave(slave) - self.BufferConfig() + self.BufferModel() self.OnCTNSave() return newConfNodeOpj @@ -611,7 +822,7 @@ slave_infos = slave.getInfo() if slave_infos.getPhysAddr() == slave_pos: config.removeSlave(idx) - self.BufferConfig() + self.BufferModel() self.OnCTNSave() ConfigTreeNode._doRemoveChild(self, CTNInstance) @@ -620,7 +831,7 @@ if slave is not None: slave_info = slave.getInfo() slave_info.setPhysAddr(new_pos) - self.BufferConfig() + self.BufferModel() def GetSlaveAlias(self, slave_pos): slave = self.GetSlave(slave_pos) @@ -634,7 +845,7 @@ if slave is not None: slave_info = slave.getInfo() slave_info.setAutoIncAddr(alias) - self.BufferConfig() + self.BufferModel() def GetSlaveType(self, slave_pos): slave = self.GetSlave(slave_pos) @@ -646,50 +857,81 @@ slave = self.GetSlave(slave_pos) if slave is not None: slave.setType(type_infos) - self.BufferConfig() + self.BufferModel() def GetSlaveInfos(self, slave_pos): slave = self.GetSlave(slave_pos) if slave is not None: type_infos = slave.getType() - device, alignement = self.GetModuleInfos(type_infos) + device, alignment = self.GetModuleInfos(type_infos) if device is not None: infos = type_infos.copy() - entries = device.GetEntriesList() - entries_list = entries.items() - entries_list.sort() - entries = [] - current_index = None - current_entry = None - for (index, subindex), entry in entries_list: - entry["children"] = [] - if index != current_index: - current_index = index - current_entry = entry - entries.append(entry) - elif current_entry is not None: - current_entry["children"].append(entry) - else: - entries.append(entry) infos.update({"physics": device.getPhysics(), "sync_managers": device.GetSyncManagers(), - "entries": entries}) + "entries": self.GetSlaveVariables(device)}) return infos return None + def GetSlaveVariables(self, slave_pos=None, limits=None, device=None): + if device is None and slave_pos is not None: + slave = self.GetSlave(slave_pos) + if slave is not None: + type_infos = slave.getType() + device, alignment = self.GetModuleInfos(type_infos) + if device is not None: + entries = device.GetEntriesList(limits) + entries_list = entries.items() + entries_list.sort() + entries = [] + current_index = None + current_entry = None + for (index, subindex), entry in entries_list: + entry["children"] = [] + if slave_pos is not None: + entry["Position"] = str(slave_pos) + entry + if index != current_index: + current_index = index + current_entry = entry + entries.append(entry) + elif current_entry is not None: + current_entry["children"].append(entry) + else: + entries.append(entry) + return entries + return [] + + def GetNodesVariables(self, vendor=None, slave_pos=None, slave_profile=None, limits=None): + entries = [] + for slave_position in self.GetSlaves(): + if slave_pos is not None and slave_position != slave_pos: + continue + slave = self.GetSlave(slave_position) + type_infos = slave.getType() + if vendor is not None and ExtractHexDecValue(type_infos["vendor"]) != vendor: + continue + device, alignment = self.GetModuleInfos(type_infos) + if slave_profile is not None and slave_profile not in device.GetProfileNumbers(): + continue + entries.extend(self.GetSlaveVariables(slave_position, limits, device)) + return entries + def GetModuleInfos(self, type_infos): return self.CTNParent.GetModuleInfos(type_infos) def GetSlaveTypesLibrary(self, profile_filter=None): return self.CTNParent.GetModulesLibrary(profile_filter) + def GetLibraryVendors(self): + return self.CTNParent.GetVendors() + def GetDeviceLocationTree(self, slave_pos, current_location, device_name): slave = self.GetSlave(slave_pos) vars = [] if slave is not None: type_infos = slave.getType() - device, alignement = self.GetModuleInfos(type_infos) + device, alignment = self.GetModuleInfos(type_infos) if device is not None: sync_managers = [] for sync_manager in device.getSm(): @@ -725,21 +967,31 @@ return vars def CTNTestModified(self): - return self.ChangesToSave or not self.ConfigIsSaved() + return self.ChangesToSave or not self.ModelIsSaved() def OnCTNSave(self): - filepath = self.ConfigFileName() - - text = "\n" - extras = {"xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance", + config_filepath = self.ConfigFileName() + + config_text = "\n" + config_extras = {"xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance", "xsi:noNamespaceSchemaLocation" : "EtherCATInfo.xsd"} - text += self.Config.generateXMLText("EtherCATConfig", 0, extras) - - xmlfile = open(filepath,"w") - xmlfile.write(text.encode("utf-8")) - xmlfile.close() - - self.ConfigBuffer.CurrentSaved() + config_text += self.Config.generateXMLText("EtherCATConfig", 0, config_extras) + + config_xmlfile = open(config_filepath,"w") + config_xmlfile.write(config_text.encode("utf-8")) + config_xmlfile.close() + + process_filepath = self.ProcessVariablesFileName() + + process_text = "\n" + process_extras = {"xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance"} + process_text += self.ProcessVariables.generateXMLText("ProcessVariables", 0, process_extras) + + process_xmlfile = open(process_filepath,"w") + process_xmlfile.write(process_text.encode("utf-8")) + process_xmlfile.close() + + self.Buffer.CurrentSaved() return True def _Generate_C(self, buildpath, locations): @@ -810,27 +1062,27 @@ def Copy(self, model): return cPickle.loads(cPickle.dumps(model)) - def CreateConfigBuffer(self, saved): - self.ConfigBuffer = UndoBuffer(cPickle.dumps(self.Config), saved) - - def BufferConfig(self): - self.ConfigBuffer.Buffering(cPickle.dumps(self.Config)) - - def ConfigIsSaved(self): - if self.ConfigBuffer is not None: - return self.ConfigBuffer.IsCurrentSaved() + def CreateBuffer(self, saved): + self.Buffer = UndoBuffer(cPickle.dumps((self.Config, self.ProcessVariables)), saved) + + def BufferModel(self): + self.Buffer.Buffering(cPickle.dumps((self.Config, self.ProcessVariables))) + + def ModelIsSaved(self): + if self.Buffer is not None: + return self.Buffer.IsCurrentSaved() else: return True def LoadPrevious(self): - self.Config = cPickle.loads(self.ConfigBuffer.Previous()) + self.Config, self.ProcessVariables = cPickle.loads(self.Buffer.Previous()) def LoadNext(self): - self.Config = cPickle.loads(self.ConfigBuffer.Next()) + self.Config, self.ProcessVariables = cPickle.loads(self.Buffer.Next()) def GetBufferState(self): - first = self.ConfigBuffer.IsFirst() - last = self.ConfigBuffer.IsLast() + first = self.Buffer.IsFirst() + last = self.Buffer.IsLast() return not first, not last @@ -983,7 +1235,7 @@ # Initialize strings for formatting master code template str_completion = { "location": location_str, - "master_number": etherlab_node_infos.getMasterNumber(), + "master_number": self.BaseParams.getIEC_Channel(), "located_variables_declaration": [], "used_pdo_entry_offset_variables_declaration": [], "used_pdo_entry_configuration": [], @@ -1017,7 +1269,7 @@ slave_pos = (slave_alias, alias[slave_alias]) # Extract slave device informations - device, alignement = self.Controler.GetModuleInfos(type_infos) + device, alignment = self.Controler.GetModuleInfos(type_infos) if device is not None: # Extract slaves variables to be mapped @@ -1413,6 +1665,7 @@ self.DataTypes = {} for dictionary in self.GetProfileDictionaries(): + dictionary.load() datatypes = dictionary.getDataTypes() if datatypes is not None: @@ -1431,7 +1684,7 @@ return None setattr(cls, "getCoE", getCoE) - def GetEntriesList(self): + def GetEntriesList(self, limits=None): if self.DataTypes is None: self.ExtractDataTypes() @@ -1443,62 +1696,63 @@ for object in dictionary.getObjects().getObject(): entry_index = object.getIndex().getcontent() index = ExtractHexDecValue(entry_index) - entry_type = object.getType() - entry_name = ExtractName(object.getName()) - - entry_type_infos = self.DataTypes.get(entry_type, None) - if entry_type_infos is not None: - content = entry_type_infos.getcontent() - for subitem in content["value"]: - entry_subidx = subitem.getSubIdx() - if entry_subidx is None: - entry_subidx = "0" - subidx = ExtractHexDecValue(entry_subidx) - subitem_access = "" - subitem_pdomapping = "" - subitem_flags = subitem.getFlags() - if subitem_flags is not None: - access = subitem_flags.getAccess() + if limits is None or limits[0] <= index <= limits[1]: + entry_type = object.getType() + entry_name = ExtractName(object.getName()) + + entry_type_infos = self.DataTypes.get(entry_type, None) + if entry_type_infos is not None: + content = entry_type_infos.getcontent() + for subitem in content["value"]: + entry_subidx = subitem.getSubIdx() + if entry_subidx is None: + entry_subidx = "0" + subidx = ExtractHexDecValue(entry_subidx) + subitem_access = "" + subitem_pdomapping = "" + subitem_flags = subitem.getFlags() + if subitem_flags is not None: + access = subitem_flags.getAccess() + if access is not None: + subitem_access = access.getcontent() + pdomapping = subitem_flags.getPdoMapping() + if pdomapping is not None: + subitem_pdomapping = pdomapping.upper() + entries[(index, subidx)] = { + "Index": entry_index, + "SubIndex": entry_subidx, + "Name": "%s - %s" % + (entry_name.decode("utf-8"), + ExtractName(subitem.getDisplayName(), + subitem.getName()).decode("utf-8")), + "Type": subitem.getType(), + "BitSize": subitem.getBitSize(), + "Access": subitem_access, + "PDOMapping": subitem_pdomapping} + else: + entry_access = "" + entry_pdomapping = "" + entry_flags = object.getFlags() + if entry_flags is not None: + access = entry_flags.getAccess() if access is not None: - subitem_access = access.getcontent() - pdomapping = subitem_flags.getPdoMapping() + entry_access = access.getcontent() + pdomapping = entry_flags.getPdoMapping() if pdomapping is not None: - subitem_pdomapping = pdomapping.upper() - entries[(index, subidx)] = { - "Index": entry_index, - "SubIndex": entry_subidx, - "Name": "%s - %s" % - (entry_name.decode("utf-8"), - ExtractName(subitem.getDisplayName(), - subitem.getName()).decode("utf-8")), - "Type": subitem.getType(), - "BitSize": subitem.getBitSize(), - "Access": subitem_access, - "PDOMapping": subitem_pdomapping} - else: - entry_access = "" - entry_pdomapping = "" - entry_flags = object.getFlags() - if entry_flags is not None: - access = entry_flags.getAccess() - if access is not None: - entry_access = access.getcontent() - pdomapping = entry_flags.getPdoMapping() - if pdomapping is not None: - entry_pdomapping = pdomapping.upper() - entries[(index, 0)] = { - "Index": entry_index, - "SubIndex": "0", - "Name": entry_name, - "Type": entry_type, - "BitSize": object.getBitSize(), - "Access": entry_access, - "PDOMapping": entry_pdomapping} + entry_pdomapping = pdomapping.upper() + entries[(index, 0)] = { + "Index": entry_index, + "SubIndex": "0", + "Name": entry_name, + "Type": entry_type, + "BitSize": object.getBitSize(), + "Access": entry_access, + "PDOMapping": entry_pdomapping} for TxPdo in self.getTxPdo(): - ExtractPdoInfos(TxPdo, "Transmit", entries) + ExtractPdoInfos(TxPdo, "Transmit", entries, limits) for RxPdo in self.getRxPdo(): - ExtractPdoInfos(RxPdo, "Receive", entries) + ExtractPdoInfos(RxPdo, "Receive", entries, limits) return entries setattr(cls, "GetEntriesList", GetEntriesList) @@ -1544,7 +1798,7 @@ return name.getcontent() return default -def ExtractPdoInfos(pdo, pdo_type, entries): +def ExtractPdoInfos(pdo, pdo_type, entries, limits=None): pdo_index = pdo.getIndex().getcontent() pdo_name = ExtractName(pdo.getName()) for pdo_entry in pdo.getEntry(): @@ -1553,27 +1807,28 @@ index = ExtractHexDecValue(entry_index) subindex = ExtractHexDecValue(entry_subindex) - entry = entries.get((index, subindex), None) - if entry is not None: - entry["PDO index"] = pdo_index - entry["PDO name"] = pdo_name - entry["PDO type"] = pdo_type - else: - entry_type = pdo_entry.getDataType() - if entry_type is not None: - if pdo_type == "Transmit": - access = "ro" - pdomapping = "T" - else: - access = "wo" - pdomapping = "R" - entries[(index, subindex)] = { - "Index": entry_index, - "SubIndex": entry_subindex, - "Name": ExtractName(pdo_entry.getName()), - "Type": entry_type.getcontent(), - "Access": access, - "PDOMapping": pdomapping} + if limits is None or limits[0] <= index <= limits[1]: + entry = entries.get((index, subindex), None) + if entry is not None: + entry["PDO index"] = pdo_index + entry["PDO name"] = pdo_name + entry["PDO type"] = pdo_type + else: + entry_type = pdo_entry.getDataType() + if entry_type is not None: + if pdo_type == "Transmit": + access = "ro" + pdomapping = "T" + else: + access = "wo" + pdomapping = "R" + entries[(index, subindex)] = { + "Index": entry_index, + "SubIndex": entry_subindex, + "Name": ExtractName(pdo_entry.getName()), + "Type": entry_type.getcontent(), + "Access": access, + "PDOMapping": pdomapping} DEFAULT_ALIGNMENT = 8 @@ -1585,7 +1840,10 @@ os.makedirs(self.Path) self.ParentLibrary = parent_library - self.LoadModules() + if parent_library is not None: + self.LoadModules() + else: + self.Library = None self.LoadAlignments() def GetPath(self): @@ -1633,6 +1891,8 @@ vendor_category["groups"][device_group]["devices"].append((device.getType().getcontent(), device)) def GetModulesLibrary(self, profile_filter=None): + if self.Library is None: + self.LoadModules() library = [] for vendor_id, vendor in self.Library.iteritems(): groups = [] @@ -1678,6 +1938,9 @@ library.sort(lambda x, y: cmp(x["name"], y["name"])) return library + def GetVendors(self): + return [(vendor_id, vendor["name"]) for vendor_id, vendor in self.Library.items()] + def GetModuleInfos(self, module_infos): vendor = ExtractHexDecValue(module_infos["vendor"]) vendor_infos = self.Library.get(vendor) @@ -1785,6 +2048,9 @@ def GetModulesLibrary(self, profile_filter=None): return self.ModulesLibrary.GetModulesLibrary(profile_filter) + def GetVendors(self): + return self.ModulesLibrary.GetVendors() + def GetModuleInfos(self, module_infos): return self.ModulesLibrary.GetModuleInfos(module_infos)