Improved Ethercat Network Configurator panels
authorLaurent Bessard
Tue, 05 Mar 2013 23:04:59 +0100
changeset 2099 ea5384ab152c
parent 2098 392791b5cc04
child 2100 bb43a81356eb
Improved Ethercat Network Configurator panels
etherlab/ConfigEditor.py
etherlab/etherlab.py
--- a/etherlab/ConfigEditor.py	Tue Mar 05 00:59:34 2013 +0100
+++ b/etherlab/ConfigEditor.py	Tue Mar 05 23:04:59 2013 +0100
@@ -331,14 +331,25 @@
                 master_location = self.ParentWindow.GetMasterLocation()
                 if (master_location == tuple(location[:len(master_location)]) and 
                     len(location) - len(master_location) == 3):
+                    values = tuple(location[len(master_location):])
+                    var_type = self.ParentWindow.Controler.GetSlaveVariableDataType(*values)
                     if col == 2:
-                        self.ParentWindow.ProcessVariablesTable.SetValueByName(
-                            row, "ReadFrom", tuple(location[len(master_location):]))
+                        other_values = self.ParentWindow.ProcessVariablesTable.GetValueByName(row, "WriteTo")
                     else:
-                        self.ParentWindow.ProcessVariablesTable.SetValueByName(
-                            row, "WriteTo", tuple(location[len(master_location):]))
-                    self.ParentWindow.SaveProcessVariables()
-                    self.ParentWindow.RefreshProcessVariables()
+                        other_values = self.ParentWindow.ProcessVariablesTable.GetValueByName(row, "ReadFrom")
+                    if other_values != "":
+                        other_type = self.ParentWindow.Controler.GetSlaveVariableDataType(*other_values)
+                    else:
+                        other_type = None
+                    if other_type is None or var_type == other_type:
+                        if col == 2:
+                            self.ParentWindow.ProcessVariablesTable.SetValueByName(row, "ReadFrom", values)
+                        else:
+                            self.ParentWindow.ProcessVariablesTable.SetValueByName(row, "WriteTo", values)
+                        self.ParentWindow.SaveProcessVariables()
+                        self.ParentWindow.RefreshProcessVariables()
+                    else:
+                        message = _("'Read from' and 'Write to' variables types are not compatible")
                 else:
                     message = _("Invalid value \"%s\" for process variable")%data
                     
@@ -375,7 +386,7 @@
             location = None
             if values[1] == "location":
                 result = LOCATION_MODEL.match(values[0])
-                if result is not None:
+                if result is not None and len(values) > 5:
                     location = map(int, result.group(1).split('.'))
                     access = values[5]
             elif values[1] == "variable":
@@ -531,6 +542,7 @@
               self.OnProcessVariablesGridCellChange)
         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
               self.OnProcessVariablesGridCellLeftClick)
+        self.ProcessVariablesGrid.Bind(wx.EVT_KEY_DOWN, self.OnProcessVariablesGridKeyDown)
         
         startup_commands_header = wx.BoxSizer(wx.HORIZONTAL)
         
@@ -586,7 +598,10 @@
 
     def __init__(self, parent, controler, window):
         ConfTreeNodeEditor.__init__(self, parent, controler, window)
-    
+        
+        self.ProcessVariables = []
+        self.LastCommandInfos = None
+        
         self.ProcessVariablesDefaultValue = {"Name": "", "ReadFrom": "", "WriteTo": "", "Description": ""}
         self.ProcessVariablesTable = ProcessVariablesTable(self, [], GetProcessVariablesTableColnames())
         self.ProcessVariablesColSizes = [40, 100, 150, 150, 200]
@@ -619,6 +634,17 @@
             return new_row
         setattr(self.ProcessVariablesGrid, "_MoveRow", _MoveVariablesElement)
         
+        _refresh_buttons = getattr(self.ProcessVariablesGrid, "RefreshButtons")
+        def _RefreshButtons():
+            if self.NodesFilter.GetSelection() == 0:
+                _refresh_buttons()
+            else:
+                self.AddVariableButton.Enable(False)
+                self.DeleteVariableButton.Enable(False)
+                self.UpVariableButton.Enable(False)
+                self.DownVariableButton.Enable(False)
+        setattr(self.ProcessVariablesGrid, "RefreshButtons", _RefreshButtons)
+        
         self.ProcessVariablesGrid.SetRowLabelSize(0)
         for col in range(self.ProcessVariablesTable.GetNumberCols()):
             attr = wx.grid.GridCellAttr()
@@ -651,7 +677,7 @@
             self.RefreshStartupCommands()
             self.RefreshBuffer()
         setattr(self.StartupCommandsGrid, "_DeleteRow", _DeleteCommandsElement)
-            
+        
         self.StartupCommandsGrid.SetRowLabelSize(0)
         for col in range(self.StartupCommandsTable.GetNumberCols()):
             attr = wx.grid.GridCellAttr()
@@ -667,6 +693,17 @@
         self.ParentWindow.RefreshEditMenu()
         self.ParentWindow.RefreshPageTitles()
     
+    def GetBufferState(self):
+        return self.Controler.GetBufferState()
+    
+    def Undo(self):
+        self.Controler.LoadPrevious()
+        self.RefreshView()
+            
+    def Redo(self):
+        self.Controler.LoadNext()
+        self.RefreshView()
+    
     def RefreshView(self):
         ConfTreeNodeEditor.RefreshView(self)
         
@@ -707,20 +744,31 @@
         self.NodesVariables.SetCurrentNodesFilter(self.CurrentNodesFilter)
     
     def RefreshProcessVariables(self):
-        self.ProcessVariablesTable.SetData(
-            self.Controler.GetProcessVariables())
-        self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
+        if self.CurrentNodesFilter is not None:
+            self.ProcessVariables = self.Controler.GetProcessVariables()
+            slaves = self.Controler.GetSlaves(**self.CurrentNodesFilter)
+            data = []
+            for variable in self.ProcessVariables:
+                if (variable["ReadFrom"] == "" or variable["ReadFrom"][0] in slaves or
+                    variable["WriteTo"] == "" or variable["WriteTo"][0] in slaves):
+                    data.append(variable)
+            self.ProcessVariablesTable.SetData(data)
+            self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
+            self.ProcessVariablesGrid.RefreshButtons()
     
     def SaveProcessVariables(self):
         self.Controler.SetProcessVariables(
             self.ProcessVariablesTable.GetData())
         self.RefreshBuffer()
     
-    def RefreshStartupCommands(self):
+    def RefreshStartupCommands(self, position=None, command_idx=None):
         if self.CurrentNodesFilter is not None:
+            self.StartupCommandsGrid.CloseEditControl()
             self.StartupCommandsTable.SetData(
                 self.Controler.GetStartupCommands(**self.CurrentNodesFilter))
             self.StartupCommandsTable.ResetView(self.StartupCommandsGrid)
+            if position is not None and command_idx is not None:
+                self.SelectStartupCommand(position, command_idx)
     
     def SelectStartupCommand(self, position, command_idx):
         self.StartupCommandsGrid.SetSelectedRow(
@@ -768,15 +816,43 @@
             dialog.ShowModal()
             dialog.Destroy()
             event.Veto()
-        
+    
     def OnProcessVariablesGridCellLeftClick(self, event):
+        row = event.GetRow()
+        if event.GetCol() == 0:
+            var_name = self.ProcessVariablesTable.GetValueByName(row, "Name")
+            var_type = self.Controler.GetSlaveVariableDataType(
+                *self.ProcessVariablesTable.GetValueByName(row, "ReadFrom"))
+            data_size = self.Controler.GetSizeOfType(var_type)
+            number = self.ProcessVariablesTable.GetValueByName(row, "Number")
+            location = "%%M%s" % data_size + \
+                       ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + (number,)))
+            
+            data = wx.TextDataObject(str((location, "location", var_type, var_name, "")))
+            dragSource = wx.DropSource(self.ProcessVariablesGrid)
+            dragSource.SetData(data)
+            dragSource.DoDragDrop()
         event.Skip()
     
+    def OnProcessVariablesGridKeyDown(self, event):
+        keycode = event.GetKeyCode()
+        col = self.ProcessVariablesGrid.GetGridCursorCol()
+        row = self.ProcessVariablesGrid.GetGridCursorRow()
+        colname = self.ProcessVariablesTable.GetColLabelValue(col, False)
+        if (keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and 
+            (colname.startswith("Read from") or colname.startswith("Write to"))):
+            self.ProcessVariablesTable.SetValue(row, col, "")
+            self.SaveProcessVariables()
+            wx.CallAfter(self.ProcessVariablesTable.ResetView, self.ProcessVariablesGrid)
+        else:
+            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
+        veto = False
         if colname == "Position":
             if value not in self.Controler.GetSlaves():
                 message = _("No slave defined at position %d!") % value
@@ -786,13 +862,22 @@
                     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)
+                wx.CallAfter(self.RefreshStartupCommands, command["Position"], command_idx)
+                veto = True
         else:
-            self.Controler.SetStartupCommandInfos(self.StartupCommandsTable.GetRow(row))
+            command = self.StartupCommandsTable.GetRow(row)
+            if self.LastCommandInfos != command:
+                self.LastCommandInfos = command.copy()
+                self.Controler.SetStartupCommandInfos(command)
+                if colname in ["Index", "SubIndex"]: 
+                    wx.CallAfter(self.RefreshStartupCommands, command["Position"], command["command_idx"])
+                    veto = True
         if message is None:
             self.RefreshBuffer()
-            event.Skip()
+            if veto:
+                event.Veto()
+            else:
+                event.Skip()
         else:
             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
             dialog.ShowModal()
--- a/etherlab/etherlab.py	Tue Mar 05 00:59:34 2013 +0100
+++ b/etherlab/etherlab.py	Tue Mar 05 23:04:59 2013 +0100
@@ -650,20 +650,6 @@
     def ProcessVariablesFileName(self):
         return os.path.join(self.CTNPath(), "process_variables.xml")
     
-    def GetSlaves(self):
-        slaves = []
-        for slave in self.Config.getConfig().getSlave():
-            slaves.append(slave.getInfo().getPhysAddr())
-        slaves.sort()
-        return slaves
-
-    def GetSlave(self, slave_pos):
-        for slave in self.Config.getConfig().getSlave():
-            slave_info = slave.getInfo()
-            if slave_info.getPhysAddr() == slave_pos:
-                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
@@ -675,6 +661,21 @@
             return False
         return True
 
+    def GetSlaves(self, vendor=None, slave_pos=None, slave_profile=None):
+        slaves = []
+        for slave in self.Config.getConfig().getSlave():
+            if self.FilterSlave(slave, vendor, slave_pos, slave_profile):
+                slaves.append(slave.getInfo().getPhysAddr())
+        slaves.sort()
+        return slaves
+
+    def GetSlave(self, slave_pos):
+        for slave in self.Config.getConfig().getSlave():
+            slave_info = slave.getInfo()
+            if slave_info.getPhysAddr() == slave_pos:
+                return slave
+        return None
+
     def GetStartupCommands(self, vendor=None, slave_pos=None, slave_profile=None):
         commands = []
         for slave in self.Config.getConfig().getSlave():
@@ -735,8 +736,10 @@
         
     def GetProcessVariables(self):
         variables = []
+        idx = 0
         for variable in self.ProcessVariables.getvariable():
             var = {"Name": variable.getName(),
+                   "Number": idx,
                    "Description": variable.getComment()}
             read_from = variable.getReadFrom()
             if read_from is not None:
@@ -753,6 +756,7 @@
             else:
                 var["WriteTo"] = ""
             variables.append(var)
+            idx += 1
         return variables
     
     def _ScanNetwork(self):
@@ -831,7 +835,18 @@
         if slave is not None:
             slave_info = slave.getInfo()
             slave_info.setPhysAddr(new_pos)
-            self.BufferModel()
+            for variable in self.ProcessVariables.getvariable():
+                read_from = variable.getReadFrom()
+                if read_from is not None and read_from.getPosition() == slave_pos:
+                    read_from.setPosition(new_pos)
+                write_to = variable.getWriteTo()
+                if write_to is not None and write_to.getPosition() == slave_pos:
+                    write_to.setPosition(new_pos)
+            self.CreateBuffer(True)
+            self.OnCTNSave()
+            if self._View is not None:
+                self._View.RefreshView()
+                self._View.RefreshBuffer()
     
     def GetSlaveAlias(self, slave_pos):
         slave = self.GetSlave(slave_pos)
@@ -901,6 +916,17 @@
             return entries
         return []
     
+    def GetSlaveVariableDataType(self, slave_pos, index, subindex):
+        slave = self.GetSlave(slave_pos)
+        if slave is not None:
+            device, alignment = self.GetModuleInfos(slave.getType())
+            if device is not None:
+                entries = device.GetEntriesList()
+                entry_infos = entries.get((index, subindex))
+                if entry_infos is not None:
+                    return entry_infos["Type"]
+        return None
+    
     def GetNodesVariables(self, vendor=None, slave_pos=None, slave_profile=None, limits=None):
         entries = []
         for slave_position in self.GetSlaves():
@@ -1005,7 +1031,7 @@
         
         LocationCFilesAndCFLAGS, LDFLAGS, extra_files = ConfigTreeNode._Generate_C(self, buildpath, locations)
         
-        self.FileGenerator.GenerateCFile(Gen_Ethercatfile_path, location_str, self.EtherlabNode)
+        self.FileGenerator.GenerateCFile(Gen_Ethercatfile_path, location_str, self.BaseParams.getIEC_Channel())
         
         LocationCFilesAndCFLAGS.append(
             (current_location, 
@@ -1041,7 +1067,7 @@
         for slave_pos in slaves:
             slave = self.GetSlave(slave_pos)
             if slave is not None:
-                self.FileGenerator.DeclareSlave(slave_pos, slave.getInfo().getAutoIncAddr(), slave.getType())
+                self.FileGenerator.DeclareSlave(slave_pos, slave)
         
         for location in locations:
             loc = location["LOC"][len(current_location):]
@@ -1210,8 +1236,8 @@
     def __del__(self):
         self.Controler = None            
     
-    def DeclareSlave(self, slave_index, slave_alias, slave):
-        self.Slaves.append((slave_index, slave_alias, slave))
+    def DeclareSlave(self, slave_index, slave):
+        self.Slaves.append((slave_index, slave.getInfo().getAutoIncAddr(), slave))
 
     def DeclareVariable(self, slave_index, index, subindex, iec_type, dir, name):
         slave_variables = self.UsedVariables.setdefault(slave_index, {})
@@ -1224,7 +1250,7 @@
         elif entry_infos["infos"] != (iec_type, dir, name):
             raise ValueError, _("Definition conflict for location \"%s\"") % name 
         
-    def GenerateCFile(self, filepath, location_str, etherlab_node_infos):
+    def GenerateCFile(self, filepath, location_str, master_number):
         
         # Extract etherlab master code template
         plc_etherlab_filepath = os.path.join(os.path.split(__file__)[0], "plc_etherlab.c")
@@ -1235,7 +1261,7 @@
         # Initialize strings for formatting master code template
         str_completion = {
             "location": location_str,
-            "master_number": self.BaseParams.getIEC_Channel(),
+            "master_number": master_number,
             "located_variables_declaration": [],
             "used_pdo_entry_offset_variables_declaration": [],
             "used_pdo_entry_configuration": [],
@@ -1259,7 +1285,8 @@
         alias = {}
         
         # Generating code for each slave
-        for (slave_idx, slave_alias, type_infos) in self.Slaves:
+        for (slave_idx, slave_alias, slave) in self.Slaves:
+            type_infos = slave.getType()
             
             # Defining slave alias and auto-increment position
             if alias.get(slave_alias) is not None:
@@ -1289,13 +1316,20 @@
                     
                     # If device support CanOpen over Ethernet, adding code for calling 
                     # init commands when initializing slave in master code template strings
+                    initCmds = []
                     for initCmd in device_coe.getInitCmd():
-                        index = ExtractHexDecValue(initCmd.getIndex())
-                        subindex = ExtractHexDecValue(initCmd.getSubIndex())
+                        initCmds.append({
+                            "Index": ExtractHexDecValue(initCmd.getIndex()),
+                            "Subindex": ExtractHexDecValue(initCmd.getSubIndex()),
+                            "Value": initCmd.getData().getcontent()})
+                    initCmds.extend(slave.getStartupCommands())
+                    for initCmd in initCmds:
+                        index = initCmd["Index"]
+                        subindex = initCmd["Subindex"]
                         entry = device_entries.get((index, subindex), None)
                         if entry is not None:
                             data_size = entry["BitSize"] / 8
-                            data_str = ("0x%%.%dx" % (data_size * 2)) % initCmd.getData().getcontent()
+                            data_str = ("0x%%.%dx" % (data_size * 2)) % initCmd["Value"]
                             init_cmd_infos = {
                                 "index": index,
                                 "subindex": subindex,
@@ -1554,6 +1588,8 @@
                                     dynamic_pdos[pdo_type]["pdos"].append(pdo)
                                 
                                 pdo["entries"].append("    {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos)
+                                if entry_infos["bitlen"] < alignment:
+                                    pdo["entries"].append("    {0x0000, 0x00, %d}, /* None */" % (alignment - entry_infos["bitlen"]))
                                 pdo["entries_number"] += 1
                                 
                                 if pdo["entries_number"] == 255: