# HG changeset patch # User laurent # Date 1324233733 -3600 # Node ID c2295d311402762cf42dfa9b9c86b5741543c209 First working implementation of Beremiz plugin for etherlab diff -r 000000000000 -r c2295d311402 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,7 @@ + +syntax: regexp +^\.project$ +syntax: regexp +^\.pydevproject$ +syntax: regexp +\.pyc$ diff -r 000000000000 -r c2295d311402 etherlab/ConfigEditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/ConfigEditor.py Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,568 @@ +import wx + +from controls import CustomGrid, CustomTable, EditorPanel + +[ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3) + +def AppendMenu(parent, help, id, kind, text): + if wx.VERSION >= (2, 6, 0): + parent.Append(help=help, id=id, kind=kind, text=text) + else: + parent.Append(helpString=help, id=id, kind=kind, item=text) + +[ID_SLAVETYPECHOICEDIALOG, ID_SLAVETYPECHOICEDIALOGSTATICTEXT1, + ID_SLAVETYPECHOICEDIALOGSLAVETYPESLIBRARY +] = [wx.NewId() for _init_ctrls in range(3)] + +class SlaveTypeChoiceDialog(wx.Dialog): + + if wx.VERSION < (2, 6, 0): + def Bind(self, event, function, id = None): + if id is not None: + event(self, id, function) + else: + event(self, function) + + def _init_coll_flexGridSizer1_Items(self, parent): + parent.AddWindow(self.staticText1, 0, border=20, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) + parent.AddWindow(self.SlaveTypesLibrary, 0, border=20, flag=wx.GROW|wx.LEFT|wx.RIGHT) + parent.AddSizer(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) + parent.AddGrowableRow(1) + + def _init_sizers(self): + self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10) + + self._init_coll_flexGridSizer1_Items(self.flexGridSizer1) + self._init_coll_flexGridSizer1_Growables(self.flexGridSizer1) + + self.SetSizer(self.flexGridSizer1) + + def _init_ctrls(self, prnt): + wx.Dialog.__init__(self, id=ID_SLAVETYPECHOICEDIALOG, + name='SlaveTypeChoiceDialog', parent=prnt, + size=wx.Size(600, 400), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, + title=_('Browse slave types library')) + self.SetClientSize(wx.Size(600, 400)) + + self.staticText1 = wx.StaticText(id=ID_SLAVETYPECHOICEDIALOGSTATICTEXT1, + label=_('Choose a slave type:'), name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.SlaveTypesLibrary = wx.TreeCtrl(id=ID_SLAVETYPECHOICEDIALOGSLAVETYPESLIBRARY, + name='TypeTree', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT) + + self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) + if wx.VERSION >= (2, 5, 0): + self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId()) + else: + self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId()) + + self._init_sizers() + + def __init__(self, parent, controler, default=None): + self._init_ctrls(parent) + + slaves_types = controler.GetSlaveTypesLibrary() + + root = self.SlaveTypesLibrary.AddRoot("") + self.GenerateSlaveTypesLibraryTreeBranch(root, slaves_types, default) + + def GenerateSlaveTypesLibraryTreeBranch(self, root, children, default): + for infos in children: + item = self.SlaveTypesLibrary.AppendItem(root, infos["name"]) + if infos["type"] == ETHERCAT_DEVICE: + self.SlaveTypesLibrary.SetPyData(item, infos["infos"]) + if infos["infos"] == default: + self.SlaveTypesLibrary.SelectItem(item) + self.SlaveTypesLibrary.EnsureVisible(item) + else: + self.GenerateSlaveTypesLibraryTreeBranch(item, infos["children"], default) + + def GetType(self): + selected = self.SlaveTypesLibrary.GetSelection() + return self.SlaveTypesLibrary.GetPyData(selected) + + def OnOK(self, event): + selected = self.SlaveTypesLibrary.GetSelection() + if not selected.IsOk() or self.SlaveTypesLibrary.GetPyData(selected) is None: + message = wx.MessageDialog(self, _("No valid slave type selected!"), _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + else: + self.EndModal(wx.ID_OK) + + +def GetPDOsTableColnames(): + _ = lambda x : x + return ["#", _("Index"), _("Name"), _("Type")] + +def GetVariablesTableColnames(): + _ = lambda x : x + return ["#", _("Index"), _("SubIndex"), _("Name"), _("Type"), _("PDO")] + +class PDOsTable(CustomTable): + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + if col == 0: + return row + 1 + colname = self.GetColLabelValue(col, False) + value = self.data[row].get(colname, "") + if colname == "Type": + value = _(value) + return value + +class VariablesTable(CustomTable): + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + if col == 0: + return row + 1 + return self.data[row].get(self.GetColLabelValue(col, False), "") + +[ID_SLAVEPANEL, ID_SLAVEPANELTYPELABEL, + ID_SLAVEPANELTYPE, ID_SLAVEPANELTYPEBROWSE, + ID_SLAVEPANELALIASLABEL, ID_SLAVEPANELALIAS, + ID_SLAVEPANELPOSLABEL, ID_SLAVEPANELPOS, + ID_SLAVEPANELSLAVEINFOSSTATICBOX, ID_SLAVEPANELVENDORLABEL, + ID_SLAVEPANELVENDOR, ID_SLAVEPANELPRODUCTCODELABEL, + ID_SLAVEPANELPRODUCTCODE, ID_SLAVEPANELREVISIONNUMBERLABEL, + ID_SLAVEPANELREVISIONNUMBER, ID_SLAVEPANELPHYSICSLABEL, + ID_SLAVEPANELPHYSICS, ID_SLAVEPANELPDOSLABEL, + ID_SLAVEPANELPDOSGRID, ID_SLAVEPANELVARIABLESLABEL, + ID_SLAVEPANELVARIABLESGRID, +] = [wx.NewId() for _init_ctrls in range(21)] + +class SlavePanel(wx.Panel): + + if wx.VERSION < (2, 6, 0): + def Bind(self, event, function, id = None): + if id is not None: + event(self, id, function) + else: + event(self, function) + + def _init_coll_MainSizer_Items(self, parent): + parent.AddSizer(self.PositionSizer, 0, border=5, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) + parent.AddSizer(self.SlaveInfosBoxSizer, 0, border=5, flag=wx.GROW|wx.ALIGN_RIGHT|wx.LEFT|wx.RIGHT) + + def _init_coll_MainSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(1) + + def _init_coll_PositionSizer_Items(self, parent): + parent.AddWindow(self.TypeLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) + parent.AddSizer(self.TypeSizer, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.AliasLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) + parent.AddWindow(self.Alias, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.PosLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) + parent.AddWindow(self.Pos, 0, border=0, flag=wx.GROW) + + def _init_coll_PositionSizer_Growables(self, parent): + parent.AddGrowableCol(1) + parent.AddGrowableCol(3) + parent.AddGrowableCol(5) + parent.AddGrowableRow(0) + + def _init_coll_TypeSizer_Items(self, parent): + parent.AddWindow(self.Type, 1, border=0, flag=0) + parent.AddWindow(self.TypeBrowse, 0, border=0, flag=0) + + def _init_coll_SlaveInfosBoxSizer_Items(self, parent): + parent.AddSizer(self.SlaveInfosSizer, 1, border=5, flag=wx.GROW|wx.ALL) + + def _init_coll_SlaveInfosSizer_Items(self, parent): + parent.AddSizer(self.SlaveInfosDetailsSizer, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.PDOsLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.PDOsGrid, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.VariablesLabel, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.VariablesGrid, 0, border=0, flag=wx.GROW) + + def _init_coll_SlaveInfosSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(2) + parent.AddGrowableRow(4) + + def _init_coll_SlaveInfosDetailsSizer_Items(self, parent): + parent.AddWindow(self.VendorLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW) + parent.AddWindow(self.Vendor, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.ProductCodeLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW) + parent.AddWindow(self.ProductCode, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.RevisionNumberLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW) + parent.AddWindow(self.RevisionNumber, 0, border=0, flag=wx.GROW) + parent.AddWindow(self.PhysicsLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL|wx.GROW) + parent.AddWindow(self.Physics, 0, border=0, flag=wx.GROW) + + def _init_coll_SlaveInfosDetailsSizer_Growables(self, parent): + parent.AddGrowableCol(1) + parent.AddGrowableCol(3) + + def _init_sizers(self): + self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5) + self.PositionSizer = wx.FlexGridSizer(cols=6, hgap=5, rows=1, vgap=0) + self.TypeSizer = wx.BoxSizer(wx.HORIZONTAL) + self.SlaveInfosBoxSizer = wx.StaticBoxSizer(self.SlaveInfosStaticBox, wx.VERTICAL) + self.SlaveInfosSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5) + self.SlaveInfosDetailsSizer = wx.FlexGridSizer(cols=4, hgap=5, rows=2, vgap=5) + + self._init_coll_MainSizer_Growables(self.MainSizer) + self._init_coll_MainSizer_Items(self.MainSizer) + self._init_coll_PositionSizer_Growables(self.PositionSizer) + self._init_coll_PositionSizer_Items(self.PositionSizer) + self._init_coll_TypeSizer_Items(self.TypeSizer) + self._init_coll_SlaveInfosBoxSizer_Items(self.SlaveInfosBoxSizer) + self._init_coll_SlaveInfosSizer_Growables(self.SlaveInfosSizer) + self._init_coll_SlaveInfosSizer_Items(self.SlaveInfosSizer) + self._init_coll_SlaveInfosDetailsSizer_Growables(self.SlaveInfosDetailsSizer) + self._init_coll_SlaveInfosDetailsSizer_Items(self.SlaveInfosDetailsSizer) + + self.SetSizer(self.MainSizer) + + def _init_ctrls(self, prnt): + wx.Panel.__init__(self, id=ID_SLAVEPANEL, name='SlavePanel', parent=prnt, + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + + self.TypeLabel = wx.StaticText(id=ID_SLAVEPANELTYPELABEL, + label=_('Type:'), name='TypeLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.Type = wx.TextCtrl(id=ID_SLAVEPANELTYPE, value='', + name='Type', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_READONLY) + + self.TypeBrowse = wx.Button(id=ID_SLAVEPANELTYPEBROWSE, label='...', + name='TypeBrowse', parent=self, pos=wx.Point(0, 0), + size=wx.Size(30, 24), style=0) + self.Bind(wx.EVT_BUTTON, self.OnTypeBrowseClick, id=ID_SLAVEPANELTYPEBROWSE) + + self.AliasLabel = wx.StaticText(id=ID_SLAVEPANELALIASLABEL, + label=_('Alias:'), name='AliasLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.Alias = wx.SpinCtrl(id=ID_SLAVEPANELALIAS, + name='Alias', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.SP_ARROW_KEYS, min=0, max=0xffff) + self.Bind(wx.EVT_SPINCTRL, self.OnAliasChanged, id=ID_SLAVEPANELALIAS) + + self.PosLabel = wx.StaticText(id=ID_SLAVEPANELPOSLABEL, + label=_('Position:'), name='PositionLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.Pos = wx.SpinCtrl(id=ID_SLAVEPANELPOS, + name='Pos', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.SP_ARROW_KEYS, min=0, max=0xffff) + self.Bind(wx.EVT_SPINCTRL, self.OnPositionChanged, id=ID_SLAVEPANELPOS) + + self.SlaveInfosStaticBox = wx.StaticBox(id=ID_SLAVEPANELSLAVEINFOSSTATICBOX, + label=_('Slave infos:'), name='SlaveInfosStaticBox', parent=self, + pos=wx.Point(0, 0), size=wx.Size(0, 0), style=0) + + self.VendorLabel = wx.StaticText(id=ID_SLAVEPANELVENDORLABEL, + label=_('Vendor:'), name='VendorLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.Vendor = wx.TextCtrl(id=ID_SLAVEPANELVENDOR, value='', + name='Vendor', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_READONLY) + + self.ProductCodeLabel = wx.StaticText(id=ID_SLAVEPANELPRODUCTCODELABEL, + label=_('Product code:'), name='ProductCodeLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.ProductCode = wx.TextCtrl(id=ID_SLAVEPANELPRODUCTCODE, value='', + name='ProductCode', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_READONLY) + + self.RevisionNumberLabel = wx.StaticText(id=ID_SLAVEPANELREVISIONNUMBERLABEL, + label=_('Revision number:'), name='RevisionNumberLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.RevisionNumber = wx.TextCtrl(id=ID_SLAVEPANELREVISIONNUMBER, value='', + name='RevisionNumber', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_READONLY) + + self.PhysicsLabel = wx.StaticText(id=ID_SLAVEPANELPHYSICSLABEL, + label=_('Physics:'), name='PhysicsLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.Physics = wx.TextCtrl(id=ID_SLAVEPANELPHYSICS, value='', + name='Physics', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 24), style=wx.TE_READONLY) + + self.PDOsLabel = wx.StaticText(id=ID_SLAVEPANELPDOSLABEL, + label=_('PDO entries:'), name='PDOsLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.PDOsGrid = CustomGrid(id=ID_SLAVEPANELPDOSGRID, + name='PDOsGrid', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.VSCROLL) + + self.VariablesLabel = wx.StaticText(id=ID_SLAVEPANELVARIABLESLABEL, + label=_('Variable entries:'), name='VariablesLabel', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + + self.VariablesGrid = CustomGrid(id=ID_SLAVEPANELPDOSGRID, + name='PDOsGrid', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.VSCROLL) + if wx.VERSION >= (2, 5, 0): + self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick) + else: + wx.grid.EVT_GRID_CELL_LEFT_CLICK(self.VariablesGrid, self.OnVariablesGridCellLeftClick) + + self._init_sizers() + + def __init__(self, parent, controler, window, slave): + self._init_ctrls(parent) + + self.Controler = controler + self.ParentWindow = window + self.Slave = slave + + self.PDOsTable = PDOsTable(self, [], GetPDOsTableColnames()) + self.PDOsGrid.SetTable(self.PDOsTable) + self.PDOsGridColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT] + self.PDOsGridColSizes = [40, 100, 150, 150] + self.PDOsGrid.SetRowLabelSize(0) + for col in range(self.PDOsTable.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.PDOsGridColAlignements[col], wx.ALIGN_CENTRE) + self.PDOsGrid.SetColAttr(col, attr) + self.PDOsGrid.SetColMinimalWidth(col, self.PDOsGridColSizes[col]) + self.PDOsGrid.AutoSizeColumn(col, False) + + self.VariablesTable = VariablesTable(self, [], GetVariablesTableColnames()) + self.VariablesGrid.SetTable(self.VariablesTable) + self.VariablesGridColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT] + self.VariablesGridColSizes = [40, 100, 100, 150, 150, 100] + self.VariablesGrid.SetRowLabelSize(0) + for col in range(self.VariablesTable.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.VariablesGridColAlignements[col], wx.ALIGN_CENTRE) + self.VariablesGrid.SetColAttr(col, attr) + self.VariablesGrid.SetColMinimalWidth(col, self.VariablesGridColSizes[col]) + self.VariablesGrid.AutoSizeColumn(col, False) + + self.RefreshView() + + def GetSlaveTitle(self): + type_infos = self.Controler.GetSlaveType(self.Slave) + return "%s (%d:%d)" % (type_infos["device_type"], self.Slave[0], self.Slave[1]) + + def GetSlave(self): + return self.Slave + + def SetSlave(self, slave): + if self.Slave != slave: + self.Slave = slave + self.RefreshView() + + def RefreshView(self): + self.Alias.SetValue(self.Slave[0]) + self.Pos.SetValue(self.Slave[1]) + slave_infos = self.Controler.GetSlaveInfos(self.Slave) + if slave_infos is not None: + self.Type.SetValue(slave_infos["device_type"]) + self.Vendor.SetValue(slave_infos["vendor"]) + self.ProductCode.SetValue(slave_infos["product_code"]) + self.RevisionNumber.SetValue(slave_infos["revision_number"]) + self.Physics.SetValue(slave_infos["physics"]) + self.PDOsTable.SetData(slave_infos["pdos"]) + self.PDOsTable.ResetView(self.PDOsGrid) + self.VariablesTable.SetData(slave_infos["variables"]) + self.VariablesTable.ResetView(self.VariablesGrid) + else: + type_infos = self.Controler.GetSlaveType(self.Slave) + self.Type.SetValue(type_infos["device_type"]) + + def OnAliasChanged(self, event): + alias = self.Alias.GetValue() + if alias != self.Slave[0]: + result = self.Controler.SetSlavePos(self.Slave[:2], alias = alias) + if result is not None: + message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + else: + wx.CallAfter(self.ParentWindow.RefreshView, (alias, self.Slave[1])) + wx.CallAfter(self.ParentWindow.RefreshParentWindow) + event.Skip() + + def OnPositionChanged(self, event): + position = self.Pos.GetValue() + if position != self.Slave[1]: + result = self.Controler.SetSlavePos(self.Slave, position = position) + if result is not None: + message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + else: + wx.CallAfter(self.ParentWindow.RefreshView, (self.Slave[0], position)) + wx.CallAfter(self.ParentWindow.RefreshParentWindow) + event.Skip() + + def OnTypeBrowseClick(self, event): + dialog = SlaveTypeChoiceDialog(self, self.Controler, self.Controler.GetSlaveType(self.Slave)) + if dialog.ShowModal() == wx.ID_OK: + result = self.Controler.SetSlaveType(self.Slave, dialog.GetType()) + if result is not None: + message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + else: + wx.CallAfter(self.RefreshView) + wx.CallAfter(self.ParentWindow.RefreshSlaveNodesTitles) + wx.CallAfter(self.ParentWindow.RefreshParentWindow) + dialog.Destroy() + event.Skip() + + def OnVariablesGridCellLeftClick(self, event): + if event.GetCol() == 0: + row = event.GetRow() + data_type = self.VariablesTable.GetValueByName(row, "Type") + var_name = self.VariablesTable.GetValueByName(row, "Name") + entry_index = self.Controler.ExtractHexDecValue(self.VariablesTable.GetValueByName(row, "Index")) + entry_subindex = self.VariablesTable.GetValueByName(row, "SubIndex") + pdo_index = self.VariablesTable.GetValueByName(row, "PDO") + for pdo_row in xrange(self.PDOsTable.GetNumberRows()): + if self.PDOsTable.GetValueByName(row, "Index") == pdo_index: + if self.PDOsTable.GetValueByName(row, "Type") == "Transmit": + dir = "%I" + else: + dir = "%Q" + break + location = "%s%s" % (dir, self.Controler.GetSizeOfType(data_type)) + \ + ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + self.Slave + (entry_index, entry_subindex))) + data = wx.TextDataObject(str((location, "location", data_type, var_name, ""))) + dragSource = wx.DropSource(self.VariablesGrid) + dragSource.SetData(data) + dragSource.DoDragDrop() + event.Skip() + +[ID_CONFIGEDITOR, ID_CONFIGEDITORADDSLAVEBUTTON, + ID_CONFIGEDITORDELETESLAVEBUTTON, ID_CONFIGEDITORSLAVENODES, +] = [wx.NewId() for _init_ctrls in range(4)] + +class ConfigEditor(EditorPanel): + + ID = ID_CONFIGEDITOR + + def _init_coll_MainSizer_Items(self, parent): + parent.AddSizer(self.ButtonSizer, 0, border=5, flag=wx.ALIGN_RIGHT|wx.TOP|wx.LEFT|wx.RIGHT) + parent.AddWindow(self.SlaveNodes, 0, border=5, flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT) + + def _init_coll_MainSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(1) + + def _init_coll_ButtonSizer_Items(self, parent): + parent.AddWindow(self.AddSlaveButton, 0, border=5, flag=wx.RIGHT) + parent.AddWindow(self.DeleteSlaveButton, 0, border=5, flag=0) + + def _init_sizers(self): + self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10) + self.ButtonSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._init_coll_MainSizer_Items(self.MainSizer) + self._init_coll_MainSizer_Growables(self.MainSizer) + self._init_coll_ButtonSizer_Items(self.ButtonSizer) + + self.Editor.SetSizer(self.MainSizer) + + def _init_Editor(self, prnt): + self.Editor = wx.Panel(id=-1, parent=prnt, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + + self.AddSlaveButton = wx.Button(id=ID_CONFIGEDITORADDSLAVEBUTTON, label=_('Add slave'), + name='AddSlaveButton', parent=self.Editor, pos=wx.Point(0, 0), + size=wx.DefaultSize, style=0) + self.Bind(wx.EVT_BUTTON, self.OnAddSlaveButtonClick, id=ID_CONFIGEDITORADDSLAVEBUTTON) + + self.DeleteSlaveButton = wx.Button(id=ID_CONFIGEDITORDELETESLAVEBUTTON, label=_('Delete slave'), + name='DeleteSlaveButton', parent=self.Editor, pos=wx.Point(0, 0), + size=wx.DefaultSize, style=0) + self.Bind(wx.EVT_BUTTON, self.OnDeleteSlaveButtonClick, id=ID_CONFIGEDITORDELETESLAVEBUTTON) + + self.SlaveNodes = wx.Notebook(id=ID_CONFIGEDITORSLAVENODES, + name='SlaveNodes', parent=self.Editor, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.NB_LEFT) + + self._init_sizers() + + def __init__(self, parent, controler, window): + EditorPanel.__init__(self, parent, "", window, controler) + + img = wx.Bitmap(self.Controler.GetIconPath("Cfile.png"), wx.BITMAP_TYPE_PNG).ConvertToImage() + self.SetIcon(wx.BitmapFromImage(img.Rescale(16, 16))) + + def GetTitle(self): + filename = self.Controler.GetFilename() + if not self.Controler.ConfigIsSaved(): + return "~%s~" % filename + return filename + + 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, slave_pos=None): + slaves = self.Controler.GetSlaves() + for i, slave in enumerate(slaves): + if i < self.SlaveNodes.GetPageCount(): + panel = self.SlaveNodes.GetPage(i) + panel.SetSlave(slave) + else: + panel = SlavePanel(self.SlaveNodes, self.Controler, self, slave) + self.SlaveNodes.AddPage(panel, "") + while self.SlaveNodes.GetPageCount() > len(slaves): + self.SlaveNodes.RemovePage(len(slaves)) + self.RefreshSlaveNodesTitles() + self.RefreshButtons() + if slave_pos is not None: + self.SelectSlave(slave_pos) + + def RefreshParentWindow(self): + self.ParentWindow.RefreshTitle() + self.ParentWindow.RefreshFileMenu() + self.ParentWindow.RefreshEditMenu() + self.ParentWindow.RefreshPageTitles() + + def RefreshSlaveNodesTitles(self): + for idx in xrange(self.SlaveNodes.GetPageCount()): + panel = self.SlaveNodes.GetPage(idx) + self.SlaveNodes.SetPageText(idx, panel.GetSlaveTitle()) + + def RefreshButtons(self): + self.DeleteSlaveButton.Enable(self.SlaveNodes.GetPageCount() > 0) + + def SelectSlave(self, slave): + for idx in xrange(self.SlaveNodes.GetPageCount()): + panel = self.SlaveNodes.GetPage(idx) + if panel.GetSlave() == slave: + self.SlaveNodes.SetSelection(idx) + return + + def OnAddSlaveButtonClick(self, event): + slave = self.Controler.AddSlave() + self.RefreshParentWindow() + wx.CallAfter(self.RefreshView, slave) + event.Skip() + + def OnDeleteSlaveButtonClick(self, event): + selected = self.SlaveNodes.GetSelection() + if selected != -1: + panel = self.SlaveNodes.GetPage(selected) + if self.Controler.RemoveSlave(panel.GetSlave()[:2]): + self.RefreshParentWindow() + wx.CallAfter(self.RefreshView) + \ No newline at end of file diff -r 000000000000 -r c2295d311402 etherlab/EtherCATBase.xsd --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/EtherCATBase.xsd Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for future use + + + + + + + + + + obsolete + + + + + obsolete + + + + + + obsolete + + + + + obsolete + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + diff -r 000000000000 -r c2295d311402 etherlab/EtherCATConfig.xsd --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/EtherCATConfig.xsd Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,1122 @@ + + + + + + + + + + + Description of the master + + + + + + General information abaout the master + + + + + + name of the master + + + + + destination MAC address + + + + + source MAC address + + + + + Ether type. + + + + + + + + If this tag exists the master checks the mailbox of one or more slaves for new messages during the cylcic process data communication. + + + + + + Start address of the logical address area, that is configured to the written bit of the input mailibox sync manager. In the cyclic frame the master will then include a LRD command with laddr = StartAddr. + + + + + Number of slave devices of which the state of the mailbox is tested by the master. + + + + + + + + Ethernet over EtherCAT settings + + + + + + + + + + + + Initialization commands that are directed at all slaves. + + + + + + + + + + + + + Description of one or more EtherCAT slave devices. + + + + + + General information about the EtherCAT slave device + + + + + + Name of the slave device. + + + + + Slave device has EtherCAT Slave Controller + + + + Fixed EtherCAT address of the slave device. + + + + + Auto Increment address of the slave device. + + + + + Physics at the individual ports(A,B,C,D) of the slave. + + + + + + EtherCAT VendorId. VendorId, ProductCode and RevisionNo are used to identify a specific slave. + + + + + + + + User friendly name generated from ProductCode and RevisionNo with the help of a vendor specific algorithmn + + + + + + + + Description of the process data of this slave + + + + + + Description of the output process data + + + + + + Start address of the process data of this slave in the output image of the master + + + + + Length of the send process data + + + + + + + + Description of the input process data + + + + + + Start address of the process data of this slave in the inpute image of the master + + + + + Length of the recv process data + + + + + + + + Settings of sync manager 0 + + + + + Settings of sync manager 1 + + + + + Settings of sync manager 2 + + + + + Settings of sync manager 3 + + + + + + Ouput Pdos + + + + + Input Pdos + + + + + + + + + Mailbox settings + + + + + + Output mailbox settings + + + + + Input mailbox settings + + + + + + + + Output mailbox settings for bootstrap state + + + + + Input mailbox settings for bootstrap state + + + + + + + + Supported protocols + + + + + + + + + + + + + + + + + + + + + SDO download cmds + + + + + + init cmd will be send at the defined transitions + + + + + + Timeout in ms + + + + + + SDO index + + + + + SDO subindex + + + + + SDO data + + + + + If true the init command should not be sent + + + + + + + + + + + + + + + + + + + + + + + service channel write req + + + + + + init cmd will be send at the defined transitions + + + + + + + + + + + + + + + Timeout in ms + + + + + Op Code + + + + + Drive number + + + + + IDN to write + + + + + + + data to write + + + + + If true the init command should not be sent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Initialization commands that are necessary for the slave device to run-up. + + + + + + + + + + Possible previous port. The current previous port is set with the Selected attribute + + + + + + deprecated + + + + + Port of the previous slave device this device is connected to + + + + + + + + + + + + Fixed EtherCAT Address of slave device + + + + + + + + + + + + + + + + + Distributed clock settings + + + + + + Determines if this device is the reference clock + + + + + + + + + + + + + + Cycles in which frames are sent + + + + + + + Cycle time of the task sending the frames + + + + + Priority of the task sending the frames + + + + + Id of the task sending the frames + + + + + Frames to be sent in this cycle + + + + + + + EtherCAT sub command + + + + + + Master state the command should be sent in. If the command should not be sent in the current master state one can either remove this command form the frame or set Cmd to Nop. + + + + + + + + + + + + + + + + + Adress position. Either Auto Increment Address or fixed EtherCAT Address + + + + + Offset in DPRAM of the EtherCAT Slave Controller + + + + + + Logical Address + + + + + + + Data that should sent + + + + + Length of the data that should be sent. The data is then filled with 0. + + + + + + Expected working counter. + + + + + Offset in the input image + + + + + Offset in the output image + + + + + Copy information for slave to slave communication. The master has to copy valid input data of this command from the source offest (bit offs in the complete process image) to a destination offset. + + + + + + + + + + + + + + Description of the process image of the master + + + + + + Input process image of the master + + + + + + Size of the input image + + + + + Input variables + + + + + + + + Output process image of the master + + + + + + Size of the output image + + + + + Output variables + + + + + + + + + + + + + + + Schema version (actual 1.3) + + + + + + + + + Transition this command should be sent in + + + + + If BeforeSlave is true this command will be sent before the slave init commands defined for this transition + + + + + + If Requires is set to cycle, this command has to be sent in a seperate cycle. If Requires is set to cycle, this command has to be sent in a seperate frame. + + + + + + + + + + + EtherCAT Command Id + + + + + + + Adress position. Either Auto Increment Address or fixed EtherCAT Address + + + + + Offset in DPRAM of the EtherCAT Slave Controller + + + + + + Logical Address + + + + + + + Data that should sent + + + + + Length of the data that should be sent. The data is then filled with 0. + + + + + + Expected working counter. If the returned working counter is not equal to this value the init command fails. + + + + + Defines how many times the master should retry sending the command before the init command fails. + + + + + If validate is set the returned data has to be validated by the master. + + + + + + Binary data with which the returned data has to be compared with. + + + + + If a data mask is set, the returned data and the data mask is combined with an AND operatore, before comparing the data. + + + + + Timeout in ms. Determis how long the master retries to read out the date, if the validation has failed. + + + + + + + + + + + + Type of Sync Manager (e.g. Outputs) + + + + + + + + + + + + + + + + + + + + Watchdog setting of the sync manager + + + + + Pdo indices + + + + + + + + + + + + + + + + + + + + + + + + + + The index of the PDO will be adapted depend on the slot number and the SlotPdoIncrement value in the Slots-part of the device description + + + + + The index of the PDO will be adapted depend on the slot group number and the SlotGroupPdoIncrement value in the Slots-part of the device description + + + + + + + + + + List of pdo indicies that are excluded if this pdo is in sync manager + + + + + + + The index of the Exclude PDO will be adapted depend on the slot number and the SlotPdoIncrement value in the Slots-part of the device description + + + + + The index of the Exclude-PDO will be adapted depend on the slot group number and the SlotPdoIncrement value in the Slots-part of the device description + + + + + + + + + + + + + + + + + + + pdo is not configurable + + + + + pdo must be configured in a sync manager + + + + + pdo will be configured internally (based on the configured variables) + + + + + default sync manager for this pdo (if set, this PDO will be include in the process image) + + + + + default sync unit for this pdo + + + + + + default oversampling factor + + + + + min. oversampling factor + + + + + max. oversampling factor + + + + + oversampling increment for entry indicies + + + + + If this attribute is True, the PDO shall not be considered if the configurator supports the modular device description because this PDO will be defined via the module definition (only for configurators supporting V1.3). + + + + + + + + + + + + The index will be adapted depend on the slot number and the SlotIndexIncrement value in the Slots-part of the device description + + + + + The index will be adapted depend on the slot group number and the SlotGroupIndexIncrement value in the Slots-part of the device description + + + + + + + + + Default value = 0 + + + + + + Name is mandatory if Index != 0 + + + + + + DataType is mandatory if Index != 0 + + + + + + + + + + + + + + + + + + 1 = swap hi and lo bytes; 2 = swap hi and lo words; 3 = swap both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the variable + + + + + + Datatype of the variable + + + + + Size of the variable in bits + + + + + Bit offset of the variable in the image + + + + + + + + + + + + + + + + + + + + + If this attribute is True, the object shall not be considered if the configurator supports the modular device description because this object will be defined via the module definition (only for configurators supporting V1.3). + + + + + + + + + + + + + + + + + init cmd will be send at the defined transitions + + + + + + Timeout in ms + + + + + data of the mailbox cmd (excl. the mailbox) + + + + + If true the init command should not be sent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r c2295d311402 etherlab/EtherCATInfo.xsd --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/EtherCATInfo.xsd Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,955 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Helps to display multiple groups in the order intended by the vendor + + + + + Type of an optional parent group - only for additional display order possibilities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Schema version (1.4) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + obsolete + + + + + + + for future use + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + for future use + + + + + for future use + + + + + + + + + + + + + + + + for future use + + + + + for future use + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + obsolete + + + + + + obsolete + + + + + + + + + + + + + + + for future use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + obsolete + + + + + obsolete + + + + + obsolete + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for future use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for future use + + + + + + + + + + + + + + + obsolete + + + + + obsolete + + + + + + + obsolete + + + + + + + + + obsolete + + + + + + + obsolete + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + in 100ps + + + + + in 100ps + + + + + + + + + + + + + + + + + + + + + in 100ps + + + + + in 100ps + + + + + + + + + obsolete + + + + + + + + + + + + + for future use + + + + + + for future use + + + + + for future use + + + + + for future use + + + + + for future use + + + + + + for future use; +in bytes + + + + + for future use; +in bytes + + + + + for future use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + obsolete + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r c2295d311402 etherlab/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/README Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,1 @@ +Ethercat \ No newline at end of file diff -r 000000000000 -r c2295d311402 etherlab/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/__init__.py Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,1 @@ +from etherlab import * \ No newline at end of file diff -r 000000000000 -r c2295d311402 etherlab/etherlab.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/etherlab.py Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,672 @@ +import os, shutil +import cPickle +from xml.dom import minidom + +import wx + +from xmlclass import * +from PLCControler import UndoBuffer +from ConfigEditor import ConfigEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE + +TYPECONVERSION = {"BOOL" : "X", "SINT" : "B", "INT" : "W", "DINT" : "D", "LINT" : "L", + "USINT" : "B", "UINT" : "W", "UDINT" : "D", "ULINT" : "L", + "BYTE" : "B", "WORD" : "W", "DWORD" : "D", "LWORD" : "L"} + +DATATYPECONVERSION = {"BOOL" : "BIT", "SINT" : "S8", "INT" : "S16", "DINT" : "S32", "LINT" : "S64", + "USINT" : "U8", "UINT" : "U16", "UDINT" : "U32", "ULINT" : "U64", + "BYTE" : "U8", "WORD" : "U16", "DWORD" : "U32", "LWORD" : "U64"} + +#-------------------------------------------------- +# Ethercat MASTER +#-------------------------------------------------- + +EtherCATConfigClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATConfig.xsd")) + +def ExtractHexDecValue(value): + try: + return int(value) + except: + pass + try: + return int(value.replace("#", "0"), 16) + except: + raise "Invalid value for HexDecValue \"%s\"" % value + +def GenerateHexDecValue(value, base=10): + if base == 10: + return str(value) + elif base == 16: + return "#x%.8x" % value + else: + raise "Not supported base" + +cls = EtherCATConfigClasses.get("Config_Slave", None) +if cls: + + def getType(self): + slave_info = self.getInfo() + return {"device_type": slave_info.getName(), + "vendor": GenerateHexDecValue(slave_info.getVendorId()), + "product_code": GenerateHexDecValue(slave_info.getProductCode(), 16), + "revision_number": GenerateHexDecValue(slave_info.getRevisionNo(), 16)} + setattr(cls, "getType", getType) + + def setType(self, type_infos): + slave_info = self.getInfo() + slave_info.setName(type_infos["device_type"]) + slave_info.setVendorId(ExtractHexDecValue(type_infos["vendor"])) + slave_info.setProductCode(ExtractHexDecValue(type_infos["product_code"])) + slave_info.setRevisionNo(ExtractHexDecValue(type_infos["revision_number"])) + setattr(cls, "setType", setType) + +cls = EtherCATConfigClasses.get("Slave_Info", None) +if cls: + + def getSlavePosition(self): + return self.getPhysAddr(), self.getAutoIncAddr() + setattr(cls, "getSlavePosition", getSlavePosition) + + def setSlavePosition(self, alias, pos): + self.setPhysAddr(alias) + self.setAutoIncAddr(pos) + setattr(cls, "setSlavePosition", setSlavePosition) + +class _EthercatPlug: + XSD = """ + + + + + + + + + """ + + def __init__(self): + filepath = self.ConfigFileName() + + self.Config = EtherCATConfigClasses["EtherCATConfig"]() + if os.path.isfile(filepath): + xmlfile = open(filepath, 'r') + tree = minidom.parse(xmlfile) + xmlfile.close() + + for child in tree.childNodes: + if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "EtherCATConfig": + self.Config.loadXMLTree(child, ["xmlns:xsi", "xsi:noNamespaceSchemaLocation"]) + self.CreateConfigBuffer(True) + else: + self.CreateConfigBuffer(False) + self.OnPlugSave() + + def ExtractHexDecValue(self, value): + return ExtractHexDecValue(value) + + def GetSizeOfType(self, type): + return TYPECONVERSION.get(self.GetPlugRoot().GetBaseType(type), None) + + def ConfigFileName(self): + return os.path.join(self.PlugPath(), "config.xml") + + def GetFilename(self): + return self.MandatoryParams[1].getName() + + def GetSlaves(self): + slaves = [] + for slave in self.Config.getConfig().getSlave(): + slaves.append(slave.getInfo().getSlavePosition()) + slaves.sort() + return slaves + + def GetSlave(self, slave_pos): + for slave in self.Config.getConfig().getSlave(): + slave_info = slave.getInfo() + if slave_info.getSlavePosition() == slave_pos: + return slave + return None + + def AddSlave(self): + slaves = self.GetSlaves() + if len(slaves) > 0: + new_pos = (slaves[-1][0] + 1, 0) + else: + new_pos = (0, 0) + slave = EtherCATConfigClasses["Config_Slave"]() + slave_infos = slave.getInfo() + slave_infos.setName("undefined") + slave_infos.setSlavePosition(new_pos[0], new_pos[1]) + self.Config.getConfig().appendSlave(slave) + self.BufferConfig() + return new_pos + + def RemoveSlave(self, slave_pos): + config = self.Config.getConfig() + for idx, slave in enumerate(config.getSlave()): + slave_infos = slave.getInfo() + if slave_infos.getSlavePosition() == slave_pos: + config.removeSlave(idx) + self.BufferConfig() + return True + return False + + def SetSlavePos(self, slave_pos, alias=None, position=None): + slave = self.GetSlave(slave_pos) + if slave is not None: + slave_info = slave.getInfo() + new_pos = slave_pos + if alias is not None: + new_pos = (alias, new_pos[1]) + if position is not None: + new_pos = (new_pos[0], position) + if self.GetSlave(new_pos) is not None: + return _("Slave with position \"%d:%d\" already exists!" % new_pos) + slave_info.setSlavePosition(*new_pos) + self.BufferConfig() + return None + + def GetSlaveType(self, slave_pos): + slave = self.GetSlave(slave_pos) + if slave is not None: + return slave.getType() + return None + + def SetSlaveType(self, slave_pos, type_infos): + slave = self.GetSlave(slave_pos) + if slave is not None: + slave.setType(type_infos) + self.BufferConfig() + return None + + def GetSlaveInfos(self, slave_pos): + slave = self.GetSlave(slave_pos) + if slave is not None: + type_infos = slave.getType() + device = self.GetModuleInfos(type_infos) + if device is not None: + infos = type_infos.copy() + infos.update({"physics": device.getPhysics(), + "pdos": [], + "variables": []}) + for TxPdo in device.getTxPdo(): + ExtractPdoInfos(TxPdo, "Transmit", infos) + for RxPdo in device.getRxPdo(): + ExtractPdoInfos(RxPdo, "Receive", infos) + return infos + return None + + def GetModuleInfos(self, type_infos): + return self.PlugParent.GetModuleInfos(type_infos) + + def GetSlaveTypesLibrary(self): + return self.PlugParent.GetModulesLibrary() + + def _OpenView(self): + app_frame = self.GetPlugRoot().AppFrame + + configeditor = ConfigEditor(app_frame.TabsOpened, self, app_frame) + + app_frame.EditProjectElement(configeditor, self.GetFilename()) + + PluginMethods = [ + {"bitmap" : os.path.join("images", "EditCfile"), + "name" : _("Edit Config"), + "tooltip" : _("Edit Config"), + "method" : "_OpenView"}, + ] + + def PlugTestModified(self): + return self.ChangesToSave or not self.ConfigIsSaved() + + def OnPlugSave(self): + filepath = self.ConfigFileName() + + text = "\n" + 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() + return True + + def PlugGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + current_location = self.GetCurrentLocation() + # define a unique name for the generated C file + location_str = "_".join(map(lambda x:str(x), current_location)) + + Gen_Ethercatfile_path = os.path.join(buildpath, "ethercat_%s.c"%location_str) + + file_generator = _EthercatCFileGenerator(self, Gen_Ethercatfile_path) + + for location in locations: + loc = location["LOC"][len(current_location):] + file_generator.DeclareVariable(loc[:2], loc[2], loc[3], location["IEC_TYPE"], location["DIR"], location["NAME"]) + + file_generator.GenerateCFile() + + return [(Gen_Ethercatfile_path, '"-I%s"'%os.path.abspath(self.GetPlugRoot().GetIECLibPath()))], "-lethercat -lrtdm", True + +#------------------------------------------------------------------------------- +# Current Buffering Management Functions +#------------------------------------------------------------------------------- + + """ + Return a copy of the config + """ + 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() + else: + return True + + def LoadPrevious(self): + self.Config = cPickle.loads(self.ConfigBuffer.Previous()) + + def LoadNext(self): + self.Config = cPickle.loads(self.ConfigBuffer.Next()) + + def GetBufferState(self): + first = self.ConfigBuffer.IsFirst() + last = self.ConfigBuffer.IsLast() + return not first, not last + + +SLAVE_PDOS_CONFIGURATION_DECLARATION = """ +/* Slave %(slave)d, "%(device_type)s" + * Vendor ID: 0x%(vendor).8x + * Product code: 0x%(product_code).8x + * Revision number: 0x%(revision_number).8x + */ + +ec_pdo_entry_info_t slave_%(slave)d_pdo_entries[] = { +%(pdos_entries_infos)s +}; + +ec_pdo_info_t slave_%(slave)d_pdos[] = { +%(pdos_infos)s +}; + +ec_sync_info_t slave_%(slave)d_syncs[] = { +%(pdos_sync_infos)s + {0xff} +}; +""" + +SLAVE_CONFIGURATION_TEMPLATE = """ + if (!(slave%(slave)d = ecrt_master_slave_config(master, %(alias)d, %(position)d, 0x%(vendor).8x, 0x%(product_code).8x))) { + fprintf(stderr, "Failed to get slave %(device_type)s configuration at alias %(alias)d and position %(position)d.\\n"); + return -1; + } + + if (ecrt_slave_config_pdos(slave%(slave)d, EC_END, slave_%(slave)d_syncs)) { + fprintf(stderr, "Failed to configure PDOs for slave %(device_type)s at alias %(alias)d and position %(position)d.\\n"); + return -1; + } +""" + +class _EthercatCFileGenerator: + + def __init__(self, controler, filepath): + self.Controler = controler + self.FilePath = filepath + + self.UsedVariables = {} + + def __del__(self): + self.Controler = None + + def DeclareVariable(self, slave_identifier, index, subindex, iec_type, dir, name): + slave_variables = self.UsedVariables.setdefault(slave_identifier, {}) + + entry_infos = slave_variables.get((index, subindex), None) + if entry_infos is None: + slave_variables[(index, subindex)] = (iec_type, dir, name) + elif entry_infos != (iec_type, dir, name): + raise ValueError, _("Definition conflict for location \"%s\"") % name + + def GenerateCFile(self): + + current_location = self.Controler.GetCurrentLocation() + # define a unique name for the generated C file + location_str = "_".join(map(lambda x:str(x), current_location)) + + plc_etherlab_filepath = os.path.join(os.path.split(__file__)[0], "plc_etherlab.c") + plc_etherlab_file = open(plc_etherlab_filepath, 'r') + plc_etherlab_code = plc_etherlab_file.read() + plc_etherlab_file.close() + + str_completion = { + "location": location_str, + "configure_pdos": int(self.Controler.EtherlabNode.getConfigurePDOs()), + "master_number": self.Controler.EtherlabNode.getMasterNumber(), + "located_variables_declaration": [], + "used_pdo_entry_offset_variables_declaration": [], + "used_pdo_entry_configuration": [], + "pdos_configuration_declaration": "", + "slaves_declaration": "", + "slaves_configuration": "", + "retrieve_variables": [], + "publish_variables": [], + } + + for slave_idx, slave_pos in enumerate(self.Controler.GetSlaves()): + + slave = self.Controler.GetSlave(slave_pos) + if slave is not None: + type_infos = slave.getType() + + device = self.Controler.GetModuleInfos(type_infos) + if device is not None: + + pdos = device.getTxPdo() + device.getRxPdo() + if len(pdos) > 0: + slave_variables = self.UsedVariables.get(slave_pos, {}) + + for element in ["vendor", "product_code", "revision_number"]: + type_infos[element] = ExtractHexDecValue(type_infos[element]) + type_infos.update(dict(zip(["slave", "alias", "position"], (slave_idx,) + slave_pos))) + + str_completion["slaves_declaration"] += "static ec_slave_config_t *slave%(slave)d = NULL;\n" % type_infos + str_completion["slaves_configuration"] += SLAVE_CONFIGURATION_TEMPLATE % type_infos + + pdos_infos = { + "pdos_entries_infos": [], + "pdos_infos": [], + "pdos_sync_infos": [], + } + pdos_infos.update(type_infos) + + sync_managers = [] + for sync_manager_idx, sync_manager in enumerate(device.getSm()): + sync_manager_infos = { + "index": sync_manager_idx, + "slave": slave_idx, + "pdos_number": 0, + } + + sync_manager_control_byte = ExtractHexDecValue(sync_manager.getControlByte()) + sync_manager_direction = sync_manager_control_byte & 0x0c + sync_manager_watchdog = sync_manager_control_byte & 0x40 + if sync_manager_direction: + sync_manager_infos["sync_manager_type"] = "EC_DIR_OUTPUT" + else: + sync_manager_infos["sync_manager_type"] = "EC_DIR_INPUT" + if sync_manager_watchdog: + sync_manager_infos["watchdog"] = "EC_WD_ENABLE" + else: + sync_manager_infos["watchdog"] = "EC_WD_DISABLE" + + sync_managers.append(sync_manager_infos) + + entry_offset = 0 + for pdo in pdos: + sync_managers[pdo.getSm()]["pdos_number"] += 1 + entries = pdo.getEntry() + + pdo_infos = { + "slave": slave_idx, + "index": ExtractHexDecValue(pdo.getIndex().getcontent()), + "name": ExtractName(pdo.getName()), + "offset": entry_offset, + "entries_number": len(entries) + } + pdos_infos["pdos_infos"].append(" {0x%(index).4x, %(entries_number)d, slave_%(slave)d_pdo_entries + %(offset)d}, /* %(name)s */" % pdo_infos) + entry_offset += len(entries) + + for entry in pdo.getEntry(): + index = ExtractHexDecValue(entry.getIndex().getcontent()) + subindex = ExtractHexDecValue(entry.getSubIndex()) + entry_infos = { + "index": index, + "subindex": subindex, + "name": ExtractName(entry.getName()), + "bitlen": entry.getBitLen(), + } + entry_infos.update(type_infos) + pdos_infos["pdos_entries_infos"].append(" {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos) + + entry_declaration = slave_variables.get((index, subindex), None) + if entry_declaration is not None: + entry_infos.update(dict(zip(["var_type", "dir", "var_name"], entry_declaration))) + + if entry_infos["var_type"] != entry.getDataType().getcontent(): + raise ValueError, _("Wrong type for location \"%s\"!") % entry_infos["var_name"] + + data_type = DATATYPECONVERSION.get(entry_infos["var_type"], None) + if data_type is None: + raise ValueError, _("Type of location \"%s\" not yet supported!") % entry_infos["var_name"] + + if (entry_infos["dir"] == "I" and sync_managers[pdo.getSm()]["sync_manager_type"] != "EC_DIR_INPUT" or + entry_infos["dir"] == "Q" and sync_managers[pdo.getSm()]["sync_manager_type"] != "EC_DIR_OUTPUT"): + raise ValueError, _("Wrong direction for location \"%s\"!") % entry_infos["var_name"] + + str_completion["located_variables_declaration"].extend(["IEC_%(var_type)s beremiz%(var_name)s;" % entry_infos, + "IEC_%(var_type)s *%(var_name)s = &beremiz%(var_name)s;" % entry_infos]) + if data_type == "BIT": + str_completion["used_pdo_entry_offset_variables_declaration"].extend(["static unsigned int slave%(slave)d_%(index).4x_%(subindex).2x;" % entry_infos, + "static unsigned int slave%(slave)d_%(index).4x_%(subindex).2x_bit;" % entry_infos]) + + str_completion["used_pdo_entry_configuration"].append(" {%(alias)d, %(position)d, 0x%(vendor).8x, 0x%(product_code).8x, 0x%(index).4x, %(subindex)d, &slave%(slave)d_%(index).4x_%(subindex).2x, &slave%(slave)d_%(index).4x_%(subindex).2x_bit}," % entry_infos) + + if entry_infos["dir"] == "I": + str_completion["retrieve_variables"].append(" beremiz%(name)s = EC_READ_BIT(domain1_pd + slave%(slave)d_%(index).4x_%(subindex).2x, slave%(slave)d_%(index).4x_%(subindex).2x_bit);" % entry_infos) + elif entry_infos["dir"] == "Q": + str_completion["publish_variables"].append(" EC_WRITE_BIT(domain1_pd + slave%(slave)d_%(index).4x_%(subindex).2x, slave%(slave)d_%(index).4x_%(subindex).2x_bit, beremiz%(var_name)s);" % entry_infos) + + else: + entry_infos["data_type"] = data_type + + str_completion["used_pdo_entry_offset_variables_declaration"].append("static unsigned int slave%(slave)d_%(index).4x_%(subindex).2x;" % entry_infos) + + str_completion["used_pdo_entry_configuration"].append(" {%(alias)d, %(position)d, 0x%(vendor).8x, 0x%(product_code).8x, 0x%(index).4x, %(subindex)d, &slave%(slave)d_%(index).4x_%(subindex).2x}," % entry_infos) + + if entry_infos["dir"] == "I": + str_completion["retrieve_variables"].append(" beremiz%(name)s = EC_READ_BIT(domain1_pd + slave%(slave)d_%(index).4x_%(subindex).2x);" % entry_infos) + elif entry_infos["dir"] == "Q": + str_completion["publish_variables"].append(" EC_WRITE_BIT(domain1_pd + slave%(slave)d_%(index).4x_%(subindex).2x, beremiz%(var_name)s);" % entry_infos) + + pdo_offset = 0 + for sync_manager_infos in sync_managers: + sync_manager_infos["offset"] = pdo_offset + pdos_infos["pdos_sync_infos"].append(" {%(index)d, %(sync_manager_type)s, %(pdos_number)d, slave_%(slave)d_pdos + %(offset)d, %(watchdog)s}," % sync_manager_infos) + pdo_offset += sync_manager_infos["pdos_number"] + + for element in ["pdos_entries_infos", "pdos_infos", "pdos_sync_infos"]: + pdos_infos[element] = "\n".join(pdos_infos[element]) + + str_completion["pdos_configuration_declaration"] += SLAVE_PDOS_CONFIGURATION_DECLARATION % pdos_infos + + for element in ["used_pdo_entry_offset_variables_declaration", "used_pdo_entry_configuration", "located_variables_declaration", "retrieve_variables", "publish_variables"]: + str_completion[element] = "\n".join(str_completion[element]) + + etherlabfile = open(self.FilePath,'w') + etherlabfile.write(plc_etherlab_code % str_completion) + etherlabfile.close() + +#-------------------------------------------------- +# Ethercat Plugin +#-------------------------------------------------- + +EtherCATInfoClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATInfo.xsd")) + +def GroupItemCompare(x, y): + if x["type"] == y["type"]: + if x["type"] == ETHERCAT_GROUP: + return x["order"].__cmp__(y["order"]) + else: + return x["name"].__cmp__(y["name"]) + elif x["type"] == ETHERCAT_GROUP: + return -1 + return 1 + +def SortGroupItems(group): + for item in group["children"]: + if item["type"] == ETHERCAT_GROUP: + SortGroupItems(item) + group["children"].sort(GroupItemCompare) + +def ExtractName(names, default=None): + if len(names) == 1: + return names[0].getcontent() + else: + for name in names: + if name.getLcId() == 1033: + return name.getcontent() + return default + +def ExtractPdoInfos(pdo, pdo_type, infos): + pdo_index = pdo.getIndex().getcontent() + infos["pdos"].append({"Index": pdo_index, + "Name": ExtractName(pdo.getName()), + "Type": pdo_type}) + for entry in pdo.getEntry(): + infos["variables"].append({"Index": entry.getIndex().getcontent(), + "SubIndex": entry.getSubIndex(), + "Name": ExtractName(entry.getName()), + "Type": entry.getDataType().getcontent(), + "PDO": pdo_index}) + +class RootClass: + + PlugChildsTypes = [("EthercatNode",_EthercatPlug,"Ethercat Master")] + + def __init__(self): + self.LoadModulesLibrary() + + def GetModulesLibraryPath(self): + library_path = os.path.join(self.PlugPath(), "modules") + if not os.path.exists(library_path): + os.mkdir(library_path) + return library_path + + def _ImportModuleLibrary(self): + dialog = wx.FileDialog(self.GetPlugRoot().AppFrame, _("Choose an XML file"), os.getcwd(), "", _("XML files (*.xml)|*.xml|All files|*.*"), wx.OPEN) + if dialog.ShowModal() == wx.ID_OK: + filepath = dialog.GetPath() + if os.path.isfile(filepath): + shutil.copy(filepath, self.GetModulesLibraryPath()) + self.LoadModulesLibrary() + else: + self.GetPlugRoot().logger.write_error(_("No such XML file: %s\n") % filepath) + dialog.Destroy() + + PluginMethods = [ + {"bitmap" : os.path.join("images", "ImportDEF"), + "name" : _("Import module library"), + "tooltip" : _("Import module library"), + "method" : "_ImportModuleLibrary"}, + ] + + def LoadModulesLibrary(self): + self.ModulesLibrary = {} + + library_path = self.GetModulesLibraryPath() + + files = os.listdir(library_path) + for file in files: + filepath = os.path.join(library_path, file) + if os.path.isfile(filepath) and os.path.splitext(filepath)[-1] == ".xml": + xmlfile = open(filepath, 'r') + xml_tree = minidom.parse(xmlfile) + xmlfile.close() + + modules_infos = None + for child in xml_tree.childNodes: + if child.nodeType == xml_tree.ELEMENT_NODE and child.nodeName == "EtherCATInfo": + modules_infos = EtherCATInfoClasses["EtherCATInfo.xsd"]["EtherCATInfo"]() + modules_infos.loadXMLTree(child, ["xmlns:xsi", "xsi:noNamespaceSchemaLocation"]) + + if modules_infos is not None: + vendor = modules_infos.getVendor() + + vendor_category = self.ModulesLibrary.setdefault(vendor.getId(), {"name": ExtractName(vendor.getName(), _("Miscellaneous")), + "groups": {}}) + + for group in modules_infos.getDescriptions().getGroups().getGroup(): + group_type = group.getType() + + vendor_category["groups"].setdefault(group_type, {"name": ExtractName(group.getName(), group_type), + "parent": group.getParentGroup(), + "order": group.getSortOrder(), + "devices": []}) + + for device in modules_infos.getDescriptions().getDevices().getDevice(): + device_group = device.getGroupType() + if not vendor_category["groups"].has_key(device_group): + raise ValueError, "Not such group \"%\"" % device_group + vendor_category["groups"][device_group]["devices"].append((device.getType().getcontent(), device)) + + def GetModulesLibrary(self): + library = [] + children_dict = {} + for vendor_id, vendor in self.ModulesLibrary.iteritems(): + groups = [] + library.append({"name": vendor["name"], + "type": ETHERCAT_VENDOR, + "children": groups}) + for group_type, group in vendor["groups"].iteritems(): + group_infos = {"name": group["name"], + "order": group["order"], + "type": ETHERCAT_GROUP, + "children": children_dict.setdefault(group_type, [])} + if group["parent"] is not None: + parent_children = children_dict.setdefault(group["parent"], []) + parent_children.append(group_infos) + else: + groups.append(group_infos) + device_dict = {} + for device_type, device in group["devices"]: + device_infos = {"name": ExtractName(device.getName()), + "type": ETHERCAT_DEVICE, + "infos": {"device_type": device_type, + "vendor": vendor_id, + "product_code": device.getType().getProductCode(), + "revision_number": device.getType().getRevisionNo()}} + group_infos["children"].append(device_infos) + device_type_occurrences = device_dict.setdefault(device_type, []) + device_type_occurrences.append(device_infos) + for device_type_occurrences in device_dict.itervalues(): + if len(device_type_occurrences) > 1: + for occurrence in device_type_occurrences: + occurrence["name"] += _(" (rev. %s)") % occurrence["infos"]["revision_number"] + library.sort(lambda x, y: x["name"].__cmp__(y["name"])) + return library + + def GetModuleInfos(self, type_infos): + vendor = self.ModulesLibrary.get(type_infos["vendor"], None) + if vendor is not None: + for group_name, group in vendor["groups"].iteritems(): + for device_type, device in group["devices"]: + product_code = device.getType().getProductCode() + revision_number = device.getType().getRevisionNo() + if (device_type == type_infos["device_type"] and + product_code == type_infos["product_code"] and + revision_number == type_infos["revision_number"]): + return device + return None + diff -r 000000000000 -r c2295d311402 etherlab/plc_etherlab.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etherlab/plc_etherlab.c Sun Dec 18 19:42:13 2011 +0100 @@ -0,0 +1,123 @@ +/* + * Etherlab Asynchronous execution code + * + * */ + +#include +#include +#include + +#include "ecrt.h" +#include "ec_rtdm.h" + +#ifdef _WINDOWS_H + #include "iec_types.h" +#else + #include "iec_std_lib.h" +#endif + +// declaration of interface variables +%(located_variables_declaration)s + +// Optional features +#define CONFIGURE_PDOS %(configure_pdos)d + +// process data +static uint8_t *domain1_pd = NULL; +%(used_pdo_entry_offset_variables_declaration)s + +const static ec_pdo_entry_reg_t domain1_regs[] = { +%(used_pdo_entry_configuration)s + {} +}; +/*****************************************************************************/ + +#if CONFIGURE_PDOS +%(pdos_configuration_declaration)s +#endif + +int rt_fd = -1; +CstructMstrAttach MstrAttach; +char rt_dev_file[64]; +long long wait_period_ns = 100000LL; + +// EtherCAT +static ec_master_t *master = NULL; +static ec_domain_t *domain1 = NULL; +%(slaves_declaration)s + +/* Beremiz plugin functions */ +int __init_%(location)s(int argc,char **argv) +{ + int rtstatus; + + MstrAttach.masterindex = %(master_number)d; + + master = ecrt_request_master(MstrAttach.masterindex); + if (!master) return -1; + + domain1 = ecrt_master_create_domain(master); + if (!domain1) return -1; + +#if CONFIGURE_PDOS + %(slaves_configuration)s +#endif + + if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) { + fprintf(stderr, "PDO entry registration failed!\n"); + return -1; + } + + sprintf(&rt_dev_file[0],"%%s%%u",EC_RTDM_DEV_FILE_NAME,0); + rt_fd = rt_dev_open( &rt_dev_file[0], 0); + if (rt_fd < 0) { + printf("Can't open %%s\n", &rt_dev_file[0]); + return -1; + } + + // attach the master over rtdm driver + MstrAttach.domainindex = ecrt_domain_index(domain1); + rtstatus = ecrt_rtdm_master_attach(rt_fd, &MstrAttach); + if (rtstatus < 0) { + printf("Cannot attach to master over rtdm\n"); + return -1; + } + + if (ecrt_master_activate(master)) + return -1; + + if (!(domain1_pd = ecrt_domain_data(domain1))) { + fprintf(stderr, "domain1_pd: 0x%%.6lx\n", (unsigned long)domain1_pd); + return -1; + } + + fprintf(stdout, "Master %(master_number)d activated...\n"); + return 0; +} + +void __cleanup_%(location)s(void) +{ + if (rt_fd >= 0) { + rt_dev_close(rt_fd); + } +} + +void __retrieve_%(location)s(void) +{ + // receive ethercat + ecrt_rtdm_master_recieve(rt_fd); + ecrt_rtdm_domain_process(rt_fd); + + rt_task_sleep(rt_timer_ns2tsc(wait_period_ns)); + + // send process data + ecrt_rtdm_domain_queque(rt_fd); + ecrt_rtdm_master_send(rt_fd); + +%(retrieve_variables)s +} + +void __publish_%(location)s(void) +{ +%(publish_variables)s +}