--- a/LPCManager.py Mon Feb 05 11:21:51 2018 +0100
+++ b/LPCManager.py Mon Feb 05 11:23:20 2018 +0100
@@ -13,7 +13,6 @@
@@ -87,7 +86,7 @@
- CMDpipe = StdoutPseudoFile(self.port)
+ CMDpipe = self.StdoutPseudoFile.StdoutPseudoFile(self.port) if self.projectOpen is not None:
self.projectOpen = self.BeremizIDE.DecodeFileSystemPath(self.projectOpen, False)
@@ -1961,54 +1960,6 @@
- def __init__(self, port):
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.socket.connect(('localhost', port))
- idx = self.Buffer.find("\n")
- text = self.socket.recv(2048)
- idx = self.Buffer.find("\n")
- line = self.Buffer[:idx + 1]
- self.Buffer = self.Buffer[idx + 1:]
- print "command >" + line
- """ Base class for file like objects to facilitate StdOut for the Shell."""
- def write(self, s, style=None):
- self.socket.send(s.encode('utf8'))
- def writeyield(self, s):
- def write_warning(self, s):
- def write_error(self, s):
from threading import Thread, Timer, Semaphore, Lock
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/StdoutPseudoFile.py Mon Feb 05 11:23:20 2018 +0100
@@ -0,0 +1,2044 @@
+# On Windows install schema, we compute path to beremiz +# relative to path to python folder (sys.path[0] in that case) +# note: beware that wx import messes up sys.path[0] +_dist_folder = os.path.split(sys.path[0])[0] +_beremiz_folder = os.path.join(_dist_folder, "beremiz") +# Then we add it to sys.path, to make "import Beremiz" possible +sys.path.append(_beremiz_folder) +from types import StringType, UnicodeType +from util.BitmapLibrary import AddBitmapFolder +# Path of directory containing current python file +_lpcmanager_path = os.path.split(__file__)[0] +PLC_GOT_module = ['GOT', 'GOT_111', 'GOT_131'] +PLC_MC9_module = ['MC9'] +PLC_module = PLC_MC9_module + PLC_GOT_module +# XXX get ride of global arch +class LPCManagerLauncher(BeremizIDELauncher): + BeremizIDELauncher.__init__(self) + self.extensions = [os.path.join(_lpcmanager_path, "extention.py")] + "LPCProjectController", + print("\nUsage of LPCManager.py :") + print("\n %s Projectpath Buildpath port arch\n" % sys.argv[0]) + def ProcessCommandLineArgs(self): + # Command line arguments parsing + opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) + except getopt.GetoptError: + # print help information and exit: + # asking for help causes exit + if o in ("-h", "--help"): + self.projectOpen = args[0] + self.buildpath = args[1] + self.port = int(args[2]) + # overload with exacltly same code, but this is intended. + # we want extensions to use globals of this module, not Beremiz.py + CMDpipe = StdoutPseudoFile(self.port) + if self.projectOpen is not None: + self.projectOpen = self.BeremizIDE.DecodeFileSystemPath(self.projectOpen, False) + CTR = self.LPCProjectController.LPCProjectController( + None, CMDpipe, self.buildpath) + if self.projectOpen is not None and os.path.isdir(self.projectOpen): + result = CTR.LoadProject(self.projectOpen) + CMDpipe.write("Error: Invalid project directory", result) + CMDpipe.write("Error: No such file or directory") + lpcberemiz_cmd = LPCCommand(CTR, CMDpipe) + cmd_thread = Thread(target=lpcberemiz_cmd.cmdloop) + # TODO: join() when exiting + self.frame = self.LPCBeremiz.LPCBeremiz(None, ctr=CTR) + # the "Show" command from composer does it instead + def CreateApplication(self): + # BEREMIZ_DEBUG file detection in beremiz isn't enough (module scope) + # here we set BMZ_DBG interpreter-wise, so that submodules can use it. + __builtin__.__dict__["BMZ_DBG"] = os.path.exists("LPC_DEBUG") + BeremizIDELauncher.CreateApplication(self) + # Add LPCmanager's image folder to searched ones. + AddBitmapFolder(os.path.join(_lpcmanager_path, "images")) +from LPCconnector import LPC_connector_factory +from LPCconnector.PYRO import MW_PYRO_connector_factory +from LPCconnector.WAMP import MWWAMP_connector_factory +from LPCtarget import LPC_target +targets.targets["LPC"] = {"xsd": os.path.join(_lpcmanager_path, "LPCtarget", "XSD"), + "class": lambda: LPC_target, + "code": {os.path.join(_lpcmanager_path, "LPCtarget", "plc_LPC_main.c")}} +targets.toolchains["makefile"] = os.path.join(_lpcmanager_path, "LPCtarget", "XSD_toolchain_makefile") +targets.targets["MC9"] = {"xsd": os.path.join(_lpcmanager_path, "MC9target", "XSD"), + "class": targets.targets["Xenomai"]["class"], + "code": {"plc_MC9_main.c": targets.targets["Xenomai"]["code"]["plc_Xenomai_main.c"], + "plc_MC9_main_retain.c": os.path.join(_lpcmanager_path, + "MC9target", "plc_MC9_main_retain.c")}} +from BeremizIDE import * +from ProjectController import ProjectController +from ConfigTreeNode import ConfigTreeNode +from editors.ProjectNodeEditor import ProjectNodeEditor +from editors.ConfTreeNodeEditor import ConfTreeNodeEditor +from editors.CodeFileEditor import VariablesTable +from editors.CodeFileEditor import VariablesEditor +from editors.CodeFileEditor import CodeEditor +from controls import ProjectPropertiesPanel +from controls.SearchResultPanel import SearchResultPanel +# from controls.LogViewer import LogViewer +from WampOptionsEditor import WampOptionsEditor +from VariableExporter import VariableWriter +from PLCControler import PLCControler, ITEMS_UNEDITABLE, ITEM_POU +from wx.lib.scrolledpanel import ScrolledPanel +from types import MethodType +from IDEFrame import IDEFrame +from dialogs import SearchInProjectDialog +from controls import TextCtrlAutoComplete +from py_ext import PythonFileCTNMixin +from plcopen.structures import TestIdentifier, IDENTIFIER_MODEL +def CTNGenerate_C(self, buildpath, locations): + # location string for that CTN + location_str = "_".join(map(lambda x: str(x), + self.GetCurrentLocation())) + configname = self.GetCTRoot().GetProjectConfigNames()[0] + pyextname = self.CTNName() + varinfos = map(lambda variable: { + "name": variable.getname(), + "desc": repr(variable.getdesc()), + "onchangecode": '"' + variable.getonchange() + \ + "('" + variable.getname() + "')\"" \ + if variable.getonchange() else '""', + "onchange": repr(variable.getonchange()) \ + if variable.getonchange() else None, + "opts": repr(variable.getopts()), + "configname": configname.upper(), + "uppername": variable.getname().upper(), + "IECtype": variable.gettype(), + "initial": repr(variable.getinitial()), + "pyextname": pyextname}, + self.CodeFile.variables.variable) + # python side PLC global variables access stub + globalstubs = "\n".join(["""\ +_%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\ + TypeTranslator["%(IECtype)s"] +_PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s +_PySafeGetPLCGlob_%(name)s.restype = None +_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] +_PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s +_PySafeSetPLCGlob_%(name)s.restype = None +_PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] +_%(pyextname)sGlobalsDesc.append(( + for varinfo in varinfos]) + # Runtime calls (start, stop, init, and cleanup) + for section in self.SECTIONS_NAMES: + if section != "globals": + rtcalls += "def _runtime_%s_%s():\n" % (location_str, section) + sectiontext = self.GetSection(section).strip() + sectiontext.replace('\n', '\n ') + "\n\n" + globalsection = self.GetSection("globals") +## Code generated by Beremiz python mixin confnode +## Code for PLC global variable access +from targets.typemapping import TypeTranslator +_%(pyextname)sGlobalsDesc = [] +__ext_name__ = "%(pyextname)s" +PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc )) +## User code in "global" scope +## Beremiz python runtime calls + # write generated content to python file + runtimefile_path = os.path.join(buildpath, + "runtime_%s.py" % location_str) + runtimefile = open(runtimefile_path, 'w') + runtimefile.write(PyFileContent.encode('utf-8')) + # C code for safe global variables access +extern __IEC_%(IECtype)s_t %(configname)s__%(uppername)s; +IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s; +IEC_%(IECtype)s __%(name)s_wbuffer; +long __%(name)s_rlock = 0; +long __%(name)s_wlock = 0; +int __%(name)s_wbuffer_written = 0; +void __SafeGetPLCGlob_%(name)s(IEC_%(IECtype)s *pvalue){ + while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1)); + *pvalue = __%(name)s_rbuffer; + AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); +void __SafeSetPLCGlob_%(name)s(IEC_%(IECtype)s *value){ + while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1)); + __%(name)s_wbuffer = *value; + __%(name)s_wbuffer_written = 1; + AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0); + vardeconchangefmt = """\ +PYTHON_POLL* __%(name)s_notifier; + if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){ + if(__%(name)s_wbuffer_written == 1){ + %(configname)s__%(uppername)s.value = __%(name)s_wbuffer; + __%(name)s_wbuffer_written = 0; + AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0); + if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){ + __%(name)s_rbuffer = __GET_VAR(%(configname)s__%(uppername)s); + AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); + varpubonchangefmt = """\ + if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){ + IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s); + if(__%(name)s_rbuffer != tmp){ + __%(name)s_rbuffer = %(configname)s__%(uppername)s.value; + PYTHON_POLL_body__(__%(name)s_notifier); + AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); + varinitonchangefmt = """\ + __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE(); + __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE)); + __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s)); + vardec = "\n".join([(vardecfmt + vardeconchangefmt + if varinfo["onchange"] else vardecfmt) % varinfo + for varinfo in varinfos]) + varret = "\n".join([varretfmt % varinfo for varinfo in varinfos]) + varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else + for varinfo in varinfos]) + varinit = "\n".join([varinitonchangefmt % dict( + onchangelen=len(varinfo["onchangecode"]), **varinfo) + for varinfo in varinfos if varinfo["onchange"]]) + # TODO : use config name obtained from model instead of default + # "config.h". User cannot change config name, but project imported + # or created in older beremiz vesion could use different name. + * Code generated by Beremiz py_ext confnode + * for safe global variables access +#include "iec_types_all.h" +/* User variables reference */ +/* Beremiz confnode functions */ +int __init_%(location_str)s(int argc,char **argv){ +void __cleanup_%(location_str)s(void){ +void __retrieve_%(location_str)s(void){ +void __publish_%(location_str)s(void){ + Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str) + pycfile = open(Gen_PyCfile_path, 'w') + pycfile.write(PyCFileContent) + matiec_CFLAGS = '"-I%s"' % os.path.abspath( + self.GetCTRoot().GetIECLibPath()) + return ([(Gen_PyCfile_path, matiec_CFLAGS)], + ("runtime_%s.py" % location_str, file(runtimefile_path, "rb"))) +PythonFileCTNMixin.CTNGenerate_C = CTNGenerate_C +def ResetSearchResults(self): + self.SearchParams = None + self.SearchResults = None + self.VariableSearchResults = [] + self.CurrentFindHighlight = None +CodeEditor.ResetSearchResults = ResetSearchResults +from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD +[STC_CODE_ERROR, STC_CODE_SEARCH_RESULT, + STC_CODE_SECTION] = range(15, 18) + ERROR_HIGHLIGHT: STC_CODE_ERROR, + SEARCH_RESULT_HIGHLIGHT: STC_CODE_SEARCH_RESULT, +def FindVariable(self, direction, search_params, variables): + if self.SearchParams != search_params: + self.SearchParams = search_params + if len(self.VariableSearchResults) > 0: + (r, c) = self.VariableSearchResults[self.index] + variables.VariablesGrid.SetCellBackgroundColour(r, c, SEARCH_RESULT_HIGHLIGHT[1]) + grid = variables.VariablesGrid + cols = grid.GetNumberCols() + rows = grid.GetNumberRows() + self.VariableSearchResults = [] + for row in range(rows): + for col in range(cols): + value = variables.VariablesGrid.GetCellValue(row, col) + search_value = search_params['find_pattern'] + if search_params['case_sensitive'] == False: + search_value = search_value.lower() + if search_value in value: + self.VariableSearchResults.append((row, col)) + if len(self.VariableSearchResults) > 0: + (r, c) = self.VariableSearchResults[self.index] + variables.VariablesGrid.SetCellBackgroundColour(r, c, SEARCH_RESULT_HIGHLIGHT[1]) + self.index += direction + self.index = self.index % len(self.VariableSearchResults) + (r, c) = self.VariableSearchResults[self.index] + variables.VariablesGrid.SetCellBackgroundColour(r, c, SEARCH_RESULT_HIGHLIGHT[0]) + variables.VariablesGrid.MakeCellVisible(r, c) + variables.VariablesGrid.ForceRefresh() +CodeEditor.FindVariable = FindVariable +def SearchInPyfile(self, criteria): + from xml.dom import minidom + dir_list = next(os.walk(self.ProjectPath))[1] + if dir not in ["build", "CanOpen@CanOpen", "project_files"]: + path = os.path.join(self.ProjectPath, dir, 'pyfile.xml') + if os.path.exists(path): + pyfile = minidom.parse(path) + variablelist = pyfile.getElementsByTagName('variable') + if criteria["find_pattern"] in s.attributes['name'].value: +PLCControler.SearchInPyfile = SearchInPyfile +def OnSearchInProjectMenu(self, event): + dialog = SearchInProjectDialog(self) + if dialog.ShowModal() == wx.ID_OK: + criteria = dialog.GetCriteria() + result = self.Controler.SearchInProject(criteria) + pyresult = self.Controler.SearchInPyfile(criteria) + self.ClearSearchResults() + self.SearchResultPanel.SetSearchResults(criteria, result, pyresult) + self.SearchResultPanel.AddPyFileResults(pyresult) + self.SelectTab(self.SearchResultPanel) +IDEFrame.OnSearchInProjectMenu = OnSearchInProjectMenu +def GenerateProjectTreeBranch(self, root, infos, item_alone=False): + item_name = infos["name"] + if infos["type"] in ITEMS_UNEDITABLE: + if len(infos["values"]) == 1: + return self.GenerateProjectTreeBranch(root, infos["values"][0], True) + item_name = _(item_name) + self.ProjectTree.SetItemText(root, item_name) + self.ProjectTree.SetPyData(root, infos) + highlight_colours = self.Highlights.get(infos.get("tagname", None), (wx.WHITE, wx.BLACK)) + self.ProjectTree.SetItemBackgroundColour(root, highlight_colours[0]) + self.ProjectTree.SetItemTextColour(root, highlight_colours[1]) + self.ProjectTree.SetItemExtraImage(root, None) + if infos["type"] == ITEM_POU: + self.ProjectTree.SetItemImage(root, + self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])]) + self.ProjectTree.SetItemExtraImage(root, self.Controler.GetPouType(infos["name"])) + elif infos.has_key("icon") and infos["icon"] is not None: + icon_name = infos["icon"] + if not self.TreeImageDict.has_key(icon_name): + self.TreeImageDict[icon_name] = self.TreeImageList.Add(GetBitmap(icon_name)) + self.ProjectTree.SetItemImage(root, self.TreeImageDict[icon_name]) + elif self.TreeImageDict.has_key(infos["type"]): + self.ProjectTree.SetItemImage(root, self.TreeImageDict[infos["type"]]) + item, root_cookie = self.ProjectTree.GetFirstChild(root) + for values in infos["values"]: + if values["type"] not in ITEMS_UNEDITABLE or len(values["values"]) > 0: + if item is None or not item.IsOk(): + item = self.ProjectTree.AppendItem(root, "") + item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie) + self.GenerateProjectTreeBranch(item, values) + item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie) + while item is not None and item.IsOk(): + item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie) + self.ProjectTree.Delete(item) +IDEFrame.GenerateProjectTreeBranch = GenerateProjectTreeBranch +defaultSearchResultPanelInit = SearchResultPanel.__init__ +def SearchResultPanelInit(self, parent, window): + defaultSearchResultPanelInit(self, parent, window) + self.TreeImageDict["py_file"] = self.TreeImageList.Add(GetBitmap("py_file")) + self.TreeImageDict["wx_glade"] = self.TreeImageList.Add(GetBitmap("wx_glade")) +SearchResultPanel.__init__ = SearchResultPanelInit +def SetSearchResults(self, criteria, search_results, py_results): + self.Criteria = criteria + self.SearchResults = {} + self.ElementsOrder = [] + self.PySearchResults = py_results + for infos, start, end, text in search_results: + if infos[0] not in self.ElementsOrder: + self.ElementsOrder.append(infos[0]) + results = self.SearchResults.setdefault(infos[0], []) + results.append((infos, start, end, text)) +SearchResultPanel.SetSearchResults = SetSearchResults +def AddPyFileResults(self, search_results): + distinct_list = list(set(search_results)) + for result in distinct_list: + name = result.split('@') + matches = search_results.count(result) + if name[1] == "py_ext": + root = self.SearchResultsTree.GetRootItem() + self.SearchResultsTree.AppendItem(root, name[0], image=self.TreeImageDict["py_file"]) + root = self.SearchResultsTree.GetRootItem() + self.SearchResultsTree.AppendItem(root, name[0], image=self.TreeImageDict["wx_glade"]) + text = _("(%d matches)") % search_results.count(result) + start_idx, end_idx = 0, len(text) + style = wx.TextAttr(wx.Colour(0, 127, 174)) + text_ctrl_style = wx.BORDER_NONE | wx.TE_READONLY | wx.TE_RICH2 + if wx.Platform != '__WXMSW__' or len(text.splitlines()) > 1: + text_ctrl_style |= wx.TE_MULTILINE + text_ctrl = wx.TextCtrl(id=-1, parent=self.SearchResultsTree, pos=wx.Point(0, 0), + value=text, style=text_ctrl_style) + width, height = text_ctrl.GetTextExtent(text) + text_ctrl.SetClientSize(wx.Size(width + 1, height)) + text_ctrl.SetBackgroundColour(self.SearchResultsTree.GetBackgroundColour()) + child = self.SearchResultsTree.GetLastChild(root) + text_ctrl.SetInsertionPoint(0) + text_ctrl.SetStyle(start_idx, end_idx, style) + self.SearchResultsTree.SetItemWindow(child, text_ctrl) +SearchResultPanel.AddPyFileResults = AddPyFileResults +def ResetSearchResults(self): + self.ElementsOrder = [] + self.SearchResults = {} + self.PySearchResults = [] +SearchResultPanel.ResetSearchResults = ResetSearchResults +def GetPythonTextCtrlDClickFunction(self, item): + for type in self.ParentWindow.CTR.Children.values(): + if name.BaseParams.attrib['Name'] == item: + selected = self.ParentWindow.TabsOpened.GetSelection() + window = self.ParentWindow.TabsOpened.GetPage(selected) + window.CodeEditor.FindVariable(1, {'find_pattern': self.Criteria['find_pattern'], + 'regular_expression': self.Criteria['regular_expression'], + 'pattern': self.Criteria['pattern'], + 'case_sensitive': self.Criteria['case_sensitive'], + 'filter': self.Criteria['filter']}, window.VariablesPanel) + for (r, c) in window.CodeEditor.VariableSearchResults: + window.VariablesPanel.VariablesGrid.SetCellBackgroundColour(r, c, + SEARCH_RESULT_HIGHLIGHT[0]) +SearchResultPanel.GetPythonTextCtrlDClickFunction = GetPythonTextCtrlDClickFunction +def OnSearchResultsTreeItemActivated(self, event): + self.ShowSearchResults(event.GetItem()) + if event.GetItem()._data is None: + self.GetPythonTextCtrlDClickFunction(event.GetItem()._text) +SearchResultPanel.OnSearchResultsTreeItemActivated = OnSearchResultsTreeItemActivated +defaultProjectPropertiesPanelInit = ProjectPropertiesPanel.__init__ +def OurProjectPropertiesPanelInit(self, parent, controller=None, window=None, enable_required=True): + REQUIRED_PARAMS = ["projectName", "productName", "productVersion", "companyName"] + [TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, + POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES + def create_project_panel(self): + self.ProjectPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.ProjectPanel.SetAutoLayout(1) + self.ProjectPanel.SetupScrolling() + self.AddPage(self.ProjectPanel, _("Project")) + projectpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=5, vgap=15) + projectpanel_sizer.AddGrowableCol(1) + self.ProjectPanel.SetSizer(projectpanel_sizer) + self.AddSizerParams(self.ProjectPanel, projectpanel_sizer, + [("projectName", _('Project Name (required):')), + ("projectVersion", _('Project Version (optional):')), + ("productName", _('Product Name (required):')), + ("productVersion", _('Product Version (required):')), + ("productRelease", _('Product Release (optional):'))]) + self.AddPage(self.ProjectPanel, _("Project")) + def create_author_panel(self): + self.AuthorPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.AuthorPanel.SetAutoLayout(1) + self.AuthorPanel.SetupScrolling() + authorpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=4, vgap=15) + authorpanel_sizer.AddGrowableCol(1) + self.AuthorPanel.SetSizer(authorpanel_sizer) + self.AddSizerParams(self.AuthorPanel, authorpanel_sizer, + [("companyName", _('Company Name (required):')), + ("companyURL", _('Company URL (optional):')), + ("authorName", _('Author Name (optional):')), + ("organization", _('Organization (optional):'))]) + self.AddPage(self.AuthorPanel, _("Author")) + def create_graphic_panel(self): + self.GraphicsPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.GraphicsPanel.SetAutoLayout(1) + self.GraphicsPanel.SetupScrolling() + graphicpanel_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=4, vgap=5) + graphicpanel_sizer.AddGrowableCol(0) + graphicpanel_sizer.AddGrowableRow(3) + self.GraphicsPanel.SetSizer(graphicpanel_sizer) + pageSize_st = wx.StaticText(self.GraphicsPanel, + label=_('Page Size (optional):')) + graphicpanel_sizer.AddWindow(pageSize_st, border=10, + flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT | wx.RIGHT) + pageSize_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5) + pageSize_sizer.AddGrowableCol(1) + graphicpanel_sizer.AddSizer(pageSize_sizer, border=10, + flag=wx.GROW | wx.LEFT | wx.RIGHT) + for name, label in [('PageWidth', _('Width:')), ('PageHeight', _('Height:'))]: + st = wx.StaticText(self.GraphicsPanel, label=label) + pageSize_sizer.AddWindow(st, border=12, + flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT) + sp = wx.SpinCtrl(self.GraphicsPanel, + min=0, max=2 ** 16, style=wx.TE_PROCESS_ENTER) + setattr(self, name, sp) + callback = self.GetPageSizeChangedFunction(sp, name) + self.Bind(wx.EVT_TEXT_ENTER, callback, sp) + sp.Bind(wx.EVT_KILL_FOCUS, callback) + pageSize_sizer.AddWindow(sp, flag=wx.GROW) + scaling_st = wx.StaticText(self.GraphicsPanel, + label=_('Grid Resolution:')) + graphicpanel_sizer.AddWindow(scaling_st, border=10, + flag=wx.GROW | wx.LEFT | wx.RIGHT) + scaling_nb = wx.Notebook(self.GraphicsPanel) + graphicpanel_sizer.AddWindow(scaling_nb, border=10, + flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT) + for language, translation in [("FBD", _("FBD")), ("LD", _("LD")), ("SFC", _("SFC"))]: + scaling_panel = wx.Panel(scaling_nb, style=wx.TAB_TRAVERSAL) + scalingpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5) + scalingpanel_sizer.AddGrowableCol(1) + scaling_panel.SetSizer(scalingpanel_sizer) + for idx, (name, label) in enumerate([('XScale', _('Horizontal:')), + ('YScale', _('Vertical:'))]): + st = wx.StaticText(scaling_panel, label=label) + scalingpanel_sizer.AddWindow(st, border=10, + flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT) + sp = wx.SpinCtrl(scaling_panel, + min=0, max=2 ** 16, style=wx.TE_PROCESS_ENTER) + scaling_controls.append(sp) + callback = self.GetScalingChangedFunction(sp, language, name) + self.Bind(wx.EVT_TEXT_ENTER, callback, sp) + sp.Bind(wx.EVT_KILL_FOCUS, callback) + scalingpanel_sizer.AddWindow(sp, border=10, + flag=wx.GROW | border | wx.RIGHT) + self.Scalings[language] = scaling_controls + scaling_nb.AddPage(scaling_panel, translation) + self.AddPage(self.GraphicsPanel, _("Graphics")) + def create_miscellaneous_panel(self): + self.MiscellaneousPanel = ScrolledPanel(id=-1, parent=self, + name='MiscellaneousPanel', pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + self.MiscellaneousPanel.SetAutoLayout(1) + self.MiscellaneousPanel.SetupScrolling() + miscellaneouspanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=15) + miscellaneouspanel_sizer.AddGrowableCol(1) + miscellaneouspanel_sizer.AddGrowableRow(1) + self.MiscellaneousPanel.SetSizer(miscellaneouspanel_sizer) + language_label = wx.StaticText(self.MiscellaneousPanel, + label=_('Language (optional):')) + miscellaneouspanel_sizer.AddWindow(language_label, border=10, + flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT) + self.Language = wx.ComboBox(self.MiscellaneousPanel, + self.Bind(wx.EVT_COMBOBOX, self.OnLanguageChanged, self.Language) + miscellaneouspanel_sizer.AddWindow(self.Language, border=10, + flag=wx.GROW | wx.TOP | wx.RIGHT) + description_label = wx.StaticText(self.MiscellaneousPanel, + label=_('Content Description (optional):')) + miscellaneouspanel_sizer.AddWindow(description_label, border=10, + flag=wx.BOTTOM | wx.LEFT) + self.ContentDescription = wx.TextCtrl(self.MiscellaneousPanel, + style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.OnContentDescriptionChanged, + self.ContentDescription) + self.ContentDescription.Bind(wx.EVT_KILL_FOCUS, + self.OnContentDescriptionChanged) + miscellaneouspanel_sizer.AddWindow(self.ContentDescription, border=10, + flag=wx.GROW | wx.BOTTOM | wx.RIGHT) + self.AddPage(self.MiscellaneousPanel, _("Miscellaneous")) + wx.Notebook.__init__(self, parent, size=wx.Size(500, 300)) + self.Controller = controller + self.ParentWindow = window + create_project_panel(self) + create_author_panel(self) + create_graphic_panel(self) + create_miscellaneous_panel(self) + for param in REQUIRED_PARAMS: + getattr(self, param).Enable(enable_required) + languages = ["", "en-US", "fr-FR", "zh-CN", "ru-RU"] + for language in languages: + self.Language.Append(language) +ProjectPropertiesPanel.__init__ = OurProjectPropertiesPanelInit +def LeftClick(self, event): + if event.GetCol() == self.grid.GetNumberCols()-1: + options = [self.grid.GetCellValue(event.GetRow(), event.GetCol()), self.grid.GetCellValue(event.GetRow(), event.GetCol()-1)] + desc = self.grid.GetCellValue(event.GetRow(), event.GetCol()-2) + if hasattr(self, "dialog"): + self.dialog.SetOptions(options, desc) + answer = self.dialog.ShowModal() + #self.dialog.SetOptions(options, desc) + self.dialog = WampOptionsEditor(self.Parent.Parent, options, desc) + answer = self.dialog.ShowModal() + opt,OnChange,value,description = self.dialog.GetOptions() + self.grid.SetCellValue(event.GetRow(), event.GetCol(), str(opt)) + self.grid.SetCellValue(event.GetRow(), event.GetCol()-1, value) + self.grid.SetCellValue(event.GetRow(), event.GetCol()-2, description) + self.Parent.RefreshModel() +VariablesTable.LeftClick = LeftClick +def VariablesEditorSetCollSize(self): + ColSizes = [20, 150] + [130] * (len(self.VariablesDefaultValue) - 2) + [300] + for col in range(self.Table.GetNumberCols()): + self.VariablesGrid.SetColSize(col, ColSizes[col]) +VariablesEditor.VariablesEditorSetCollSize = VariablesEditorSetCollSize +def _updateColAttrs(self, grid): + wxGrid -> 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()): + colname = self.GetColLabelValue(col, False) + if colname in ["Name", "Initial", "Description", "OnChange"]: + editor = wx.grid.GridCellTextEditor() + elif colname == "Class": + editor = wx.grid.GridCellChoiceEditor() + editor.SetParameters("input,memory,output") + elif colname == "Type": + grid.SetReadOnly(row, col, True) + grid.SetCellEditor(row, col, editor) + grid.SetCellRenderer(row, col, renderer) + grid.SetCellBackgroundColour(row, col, wx.WHITE) + self.grid.SetRowMinimalHeight + # updated column width, with function VariablesEditorSetCollSize added in VariablesEditor class + self.Parent.VariablesEditorSetCollSize() + # added left click option on grid, with function LeftClick added in VariablesTable class + self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.LeftClick) +VariablesTable._updateColAttrs = _updateColAttrs +"""Zakomentirano ker je ze v beremiz-u""" +# def OnCleanButton(self, event): +# if self.LogSource is not None: +# rez = self.LogSource.ResetLogCount() +# self.GrandParent.CTR.logger.write_warning("Can not reset log messages!\n") +# self.ResetLogMessages() +# LogViewer.OnCleanButton = OnCleanButton +from PLCControler import PLCControler, LOCATION_MODULE, LOCATION_GROUP +defaultGenerateNewName = PLCControler.GenerateNewName +def newGenerateNewName(self, tagname, name, format, start_idx=0, exclude={}, debug=False): + return defaultGenerateNewName(self, tagname, name, format, start_idx=0, exclude={}, debug=False) + while name is None or names.get(name.upper(), False): +PLCControler.GenerateNewName = newGenerateNewName +from canfestival import RootClass as CanOpenRootClass +from canfestival.canfestival import _SlaveCTN, _NodeListCTN, NodeManager +from canfestival.NetworkEditor import NetworkEditor +from canfestival.SlaveEditor import SlaveEditor +# havecanfestival = False +WINDOW_COLOUR = wx.Colour(240, 240, 240) +TITLE_COLOUR = wx.Colour(200, 200, 220) +CHANGED_TITLE_COLOUR = wx.Colour(220, 200, 220) +CHANGED_WINDOW_COLOUR = wx.Colour(255, 240, 240) +if wx.Platform == '__WXMSW__': + faces = {'times': 'Times New Roman', + 'other': 'Comic Sans MS', + faces = {'times': 'Times', + 'other': 'new century schoolbook', +# ConfTreeNodeEditor.SHOW_BASE_PARAMS = False +# ------------------------------------------------------------------------------- +# CANFESTIVAL CONFNODE HACK +# ------------------------------------------------------------------------------- +from canfestival import canfestival +defaultGetCFLAGS = canfestival.local_canfestival_config.getCFLAGS +defaultGetLDFLAGS = canfestival.local_canfestival_config.getLDFLAGS + return str(defaultGetCFLAGS(*args)) + return str(defaultGetLDFLAGS(*args)) +canfestival.local_canfestival_config.getCFLAGS = getCFLAGS +canfestival.local_canfestival_config.getLDFLAGS = getLDFLAGS +import LPCBus as LPCBus_mod + LPCBus_mod.LPCarch = "MC9" + LPCBus_mod.LPCarch = arch +# ------------------------------------------------------------------------------- +# LPC CanFestival ConfNode Class +# ------------------------------------------------------------------------------- + "CAN_Baudrate": "125K", +class LPCSlaveEditor(SlaveEditor): + # SHOW_BASE_PARAMS = False +class LPCCanOpenSlave(_SlaveCTN): + XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:element name="CanFestivalSlaveNode"> + <xsd:attribute name="CAN_Baudrate" type="xsd:string" use="optional" default="%(CAN_Baudrate)s"/> + <xsd:attribute name="NodeId" type="xsd:integer" use="optional" default="%(Slave_NodeId)d"/> + <xsd:attribute name="Sync_Align" type="xsd:integer" use="optional" default="0"/> + <xsd:attribute name="Sync_Align_Ratio" use="optional" default="50"> + <xsd:restriction base="xsd:integer"> + <xsd:minInclusive value="1"/> + <xsd:maxInclusive value="99"/> + EditorType = LPCSlaveEditor + # TODO change netname when name change + NodeManager.__init__(self) + odfilepath = self.GetSlaveODPath() + if (os.path.isfile(odfilepath)): + self.OpenFileInCurrent(odfilepath) + self.CreateNewNode("SlaveNode", # Name - will be changed at build time + 0x00, # NodeID - will be changed at build time + def GetCanDevice(self): + return str(self.BaseParams.getIEC_Channel()) + {"bitmap": "NetworkEdit", + "name": _("Edit slave"), + "tooltip": _("Edit CanOpen slave with ObjdictEdit"), + "method": "_OpenView"}, + ] + _SlaveCTN.ConfNodeMethods +class LPCNetworkEditor(NetworkEditor): + # SHOW_BASE_PARAMS = False +class LPCCanOpenMaster(_NodeListCTN): + XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:element name="CanFestivalNode"> + <xsd:attribute name="CAN_Baudrate" type="xsd:string" use="optional" default="%(CAN_Baudrate)s"/> + <xsd:attribute name="NodeId" type="xsd:integer" use="optional" default="%(Master_NodeId)d"/> + <xsd:attribute name="Sync_TPDOs" type="xsd:boolean" use="optional" default="true"/> + EditorType = LPCNetworkEditor + def GetCanDevice(self): + return str(self.BaseParams.getIEC_Channel()) + {"bitmap": "NetworkEdit", + "name": _("Edit network"), + "tooltip": _("Edit CanOpen Network with NetworkEdit"), + "method": "_OpenView"}, + ] + _NodeListCTN.ConfNodeMethods +class LPCCanOpen(CanOpenRootClass): + CTNChildrenTypes = [("CanOpenNode", LPCCanOpenMaster, "CanOpen Master"), + ("CanOpenSlave", LPCCanOpenSlave, "CanOpen Slave")] + def GetCanDriver(self): + def LoadChildren(self): + ConfigTreeNode.LoadChildren(self) + if self.GetChildByName("Master") is None: + master = self.CTNAddChild("Master", "CanOpenNode", 0) + # master.BaseParams.setEnabled(False) + master.CTNRequestSave() + if self.GetChildByName("Slave") is None: + slave = self.CTNAddChild("Slave", "CanOpenSlave", 1) + # slave.BaseParams.setEnabled(False) +# ------------------------------------------------------------------------------- +# LPCProjectController Class +# ------------------------------------------------------------------------------- +def mycopytree(src, dst): + Copy content of a directory to an other, omit hidden files + @param src: source directory + @param dst: destination directory + for i in os.listdir(src): + if not i.startswith('.'): + srcpath = os.path.join(src, i) + dstpath = os.path.join(dst, i) + if os.path.isdir(srcpath): + if os.path.exists(dstpath): + mycopytree(srcpath, dstpath) + elif os.path.isfile(srcpath): + shutil.copy2(srcpath, dstpath) +[SIMULATION_MODE, TRANSFER_MODE] = range(2) + class LPCProjectNodeEditor(ProjectNodeEditor): + class LPCProjectNodeEditor(ProjectNodeEditor): + ENABLE_REQUIRED = False + "tooltip": _("Simulate PLC"), + "method": "_Simulate"}, + "tooltip": _("Start PLC"), + "tooltip": _("Stop Running PLC"), + "tooltip": _("Build project into build folder"), + "tooltip": _("Clean project build folder"), + "tooltip": _("Transfer PLC"), + "method": "_Transfer"}, + "Started": [("_Simulate", False), + "Stopped": [("_Simulate", False), + "Connected": [("_Simulate", "not simulating"), + ("_Stop", True if arch in PLC_module else "simulating"), + "Disconnected": [("_Simulate", "not simulating"), + ("_Stop", False if arch in PLC_module else "simulating"), + "Empty" :[("_Simulate", "not simulating"), + "tooltip": _("Connect to the target PLC"), + {"bitmap": "Disconnect", + "name": _("Disconnect"), + "tooltip": _("Disconnect from PLC"), + "method": "_Disconnect"}, + "tooltip": _("Update the PLC firmware"), + "method": "_UpdateFw"}, + _MethodFromPLCState["Disconnected"] += [("_Connect", True), + ("_Disconnect", False)] + _MethodFromPLCState["Connected"] += [("_Connect", False), +class LPCProjectController(ProjectController): + StatusMethods = _StatusMethods + EditorType = LPCProjectNodeEditor + def __init__(self, frame, logger, buildpath): + self.OrigBuildPath = buildpath + ProjectController.__init__(self, frame, logger) + self.CTNChildrenTypes += [("LPCBus", LPCBus, "LPC bus"), ("CanOpen", LPCCanOpen, "CanOpen bus")] + self.CTNChildrenTypes += [("LPCBus", LPCBus, "LPC bus")] + self.OnlineMode = "NORMAL" if arch in PLC_module else "OFF" + self.LPCConnector = None + self.ConnectorPath = None + self.CurrentMode = None + self.previous_mode = None + self.SimulationBuildPath = None + self.AbortTransferTimer = None + # Firmware update running status + self.firmawreUpadateIsRunning = False + # Bind mouse double click event on URI_location in Beremiz + def GetProjectName(self): + return self.Project.getname() + def GetDefaultTargetName(self): + if self.CurrentMode == SIMULATION_MODE: + return ProjectController.GetDefaultTargetName(self) + target = ProjectController.GetTarget(self) + if self.CurrentMode != SIMULATION_MODE and arch not in PLC_module: + target.getcontent().setBuildPath(self.BuildPath) + def _getBuildPath(self): + if self.CurrentMode == SIMULATION_MODE: + if self.SimulationBuildPath is None: + self.SimulationBuildPath = os.path.join(tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build") + return self.SimulationBuildPath + return ProjectController._getBuildPath(self) + # MD5 = self.GetLastBuildMD5() + path_export_file = self.BuildPath[:-5] + "\\" + self._builder.exe[:-3] + ".xEye" + zf = zipfile.ZipFile(path_export_file, mode='w', compression=zipfile.ZIP_DEFLATED) + for extrafilespath in [self._getExtraFilesPath(), + self._getProjectFilesPath()]: + dir = extrafilespath.split("\\")[-1] + for name in os.listdir(extrafilespath): + zf.write(extrafilespath + '\\' + str(name), dir + '\\' + str(name), zipfile.ZIP_DEFLATED) + zf.write(self._builder.exe_path, self._builder.exe) + zf.write(self.BuildPath + '\\lastbuildPLC.md5', 'lastbuildPLC.md5') + self.logger.write(_("Export file is successfully created on location: %s\n") % path_export_file) + self.logger.write(_("Export file is not created because eror: %s\n") % e) + save = self.ProjectTestModified() + self.AppFrame._Refresh(TITLE, FILEMENU) + if self.BuildPath is not None: + mycopytree(self.OrigBuildPath, self.BuildPath) + if ProjectController._Build(self): + wx.CallAfter(self.AppFrame.RefreshAll) + def SetProjectName(self, name): + return self.Project.setname(name) + def SetOnlineMode(self, mode, path=None): + # SetOnlineMode is only for MC8 + if self.OnlineMode != mode: + if mode not in ["OFF", ""]: + self.ConnectorPath = path + uri = "LPC://%s/%s" % (self.OnlineMode, path) + self.LPCConnector = connectors.ConnectorFactory(uri, self) + self.logger.write_error(_("Exception while connecting %s!\n") % uri) + self.logger.write_error(traceback.format_exc()) + # Did connection success ? + if self.LPCConnector is None: + self.logger.write_error(_("Connection failed to %s!\n") % uri) + self.OnlineMode = "OFF" + self.LPCConnector = None + self.ConnectorPath = None + def ApplyOnlineMode(self): + if self.CurrentMode != SIMULATION_MODE: + self._SetConnector(self.LPCConnector) + # Init with actual PLC status and print it + self.UpdateMethodsFromPLCStatus() + if self.LPCConnector is not None and self.OnlineMode == "APPLICATION": + self.CompareLocalAndRemotePLC() + if self.previous_plcstate is not None: + status = _(self.previous_plcstate) + self.logger.write(_("PLC is %s\n") % status) + # if self.StatusTimer and not self.StatusTimer.IsRunning(): + # # Start the status Timer + # self.StatusTimer.Start(milliseconds=2000, oneShot=False) + if self.previous_plcstate == "Started": + if self.DebugAvailable() and self.GetIECProgramsAndVariables(): + self.logger.write(_("Debug connect matching running PLC\n")) + self.logger.write_warning(_("Debug do not match PLC - stop/transfert/start to re-enable\n")) + elif self.StatusTimer and self.StatusTimer.IsRunning(): + self.StatusTimer.Stop() + if self.CurrentMode == TRANSFER_MODE: + if self.OnlineMode == "BOOTLOADER": + elif self.OnlineMode == "APPLICATION": + self.CurrentMode = None + self.AbortTransferTimer.Stop() + self.AbortTransferTimer = None + self.logger.write(_("PLC transferred successfully\n")) + # Update a PLCOpenEditor Pou variable location + def UpdateProjectVariableLocation(self, old_leading, new_leading): + self.Project.updateElementAddress(old_leading, new_leading) + # Update a PLCOpenEditor Pou variable name + def UpdateProjectVariableName(self, old_name, new_name): + self.Project.updateElementName(old_name, new_name) + def RemoveProjectVariableByAddress(self, address): + self.Project.removeVariableByAddress(address) + def RemoveProjectVariableByFilter(self, leading): + self.Project.removeVariableByFilter(leading) + def AddProjectDefaultConfiguration(self, config_name="config", res_name="resource1"): + ProjectController.AddProjectDefaultConfiguration(self, config_name, res_name) + self.SetEditedResourceInfos( + self.ComputeConfigurationResourceName(config_name, res_name), + "Triggering": "Cyclic", + def LoadProject(self, ProjectPath, BuildPath=None): + Load a project contained in a folder + @param ProjectPath: path of the project folder + if os.path.basename(ProjectPath) == "": + ProjectPath = os.path.dirname(ProjectPath) + # Verify that project contains a PLCOpen program + plc_file = os.path.join(ProjectPath, "plc.xml") + if os.path.isfile(plc_file): + result = self.OpenXMLFile(plc_file) + self.CreateNewProject({"companyName": "", + if len(self.GetProjectConfigNames()) == 0: + self.AddProjectDefaultConfiguration() + # Change XSD into class members + self._AddParamsMembers() + # Keep track of the root confnode (i.e. project path) + self.ProjectPath = ProjectPath + self.BuildPath = self._getBuildPath() + if self.OrigBuildPath is not None: + mycopytree(self.OrigBuildPath, self.BuildPath) + # If dir have already be made, and file exist + if os.path.isdir(self.CTNPath()) and os.path.isfile(self.ConfNodeXmlFilePath()): + # Load the confnode.xml file into parameters members + result = self.LoadXMLParams() + # Load and init all the children + canopen_child = self.GetChildByName("CanOpen") + if havecanfestival and canopen_child is None: + canopen = self.CTNAddChild("CanOpen", "CanOpen", 0) + canopen.CTNRequestSave() + elif not havecanfestival and canopen_child is not None: + canopen_child.CTNRemove() + if self.CTNTestModified(): + if wx.GetApp() is None: + self.RefreshConfNodesBlockLists() + wx.CallAfter(self.RefreshConfNodesBlockLists) + self.SetParamsAttribute('BeremizRoot.TargetType', 'MC9') + def IsPLCStarted(self): + return self.previous_plcstate == "Started" or self.previous_mode == SIMULATION_MODE + def ShowMethod(self, name, val): + simulating = self.CurrentMode == SIMULATION_MODE + if val.endswith("simulating"): + if val.startswith("not"): + ProjectController.ShowMethod(self, name, val) + def UpdateMethodsFromPLCStatus(self): + simulating = self.CurrentMode == SIMULATION_MODE + if self.OnlineMode == "OFF": + status, log_count = self._connector.GetPLCstatus() + self.UpdatePLCLog(log_count) + status = "Disconnected" + elif self.OnlineMode == "BOOTLOADER": + if self._connector is not None: + status, log_count = self._connector.GetPLCstatus() + if status == "Disconnected": + self._SetConnector(None, False) + self.UpdatePLCLog(log_count) + status = "Disconnected" + if self.previous_plcstate != status or self.previous_mode != self.CurrentMode: + for args in _MethodFromPLCState.get(status, []): + self.previous_plcstate = status + self.previous_mode = self.CurrentMode + if self.AppFrame is not None: + self.AppFrame.RefreshStatusToolBar() + connection_text = _("Connected to: ") + connection_text += _("Simulation") + if status == "Disconnected": + self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 1) + self.AppFrame.ConnectionStatusBar.SetStatusText('', 2) + self.AppFrame.ConnectionStatusBar.SetStatusText(connection_text, 1) + self.AppFrame.ConnectionStatusBar.SetStatusText(status_text, 2) + connection_text += " (%s)" + connection_text += "%s" + self.AppFrame.ConnectionStatusBar.SetStatusText(connection_text % self.ConnectorPath, 1) + self.AppFrame.ConnectionStatusBar.SetStatusText(status_text % _(status), 2) + def Generate_plc_declare_locations(self): + Declare used locations in order to simulatePLC in a black box + return """#include "iec_types_all.h" +#define __LOCATED_VAR(type, name, ...) \ +type *name = &beremiz_##name; +#include "LOCATED_VARIABLES.h" + def Generate_lpc_retain_array_sim(self): + Support for retain array in Simulation + return """/* Support for retain array */ +#define USER_RETAIN_ARRAY_SIZE 2000 +unsigned char readOK = 0; +unsigned int foundIndex = USER_RETAIN_ARRAY_SIZE; +unsigned int retainArray[USER_RETAIN_ARRAY_SIZE][NUM_OF_COLS]; +unsigned int __GetRetainData(unsigned char READ, unsigned int INDEX, unsigned int COLUMN) + if((0<=INDEX) && (INDEX<USER_RETAIN_ARRAY_SIZE) && (0<=COLUMN) && (COLUMN<NUM_OF_COLS)) + return retainArray[INDEX][COLUMN]; +unsigned char __SetRetainData(unsigned char WRITE, unsigned int INDEX, unsigned int WORD1, unsigned int WORD2, unsigned int WORD3) + if((0<=INDEX) && (INDEX<USER_RETAIN_ARRAY_SIZE)) + retainArray[INDEX][0] = WORD1; + retainArray[INDEX][1] = WORD2; + retainArray[INDEX][2] = WORD3; +unsigned char __FindRetainData(unsigned char SEARCH, unsigned int START_IDX, unsigned int END_IDX, unsigned int WORD1, unsigned int WORD2, unsigned int WORD3) + if((SEARCH==1) && (0<=START_IDX) && (START_IDX<USER_RETAIN_ARRAY_SIZE) && (START_IDX<=END_IDX) && (END_IDX<USER_RETAIN_ARRAY_SIZE)) + for(i=START_IDX;i<=END_IDX;i++) + if((retainArray[i][0] == WORD1) && (retainArray[i][1] == WORD2) && (retainArray[i][2] == WORD3)) + foundIndex = USER_RETAIN_ARRAY_SIZE; /* Data not found => return index that is out of array bounds */ +/* Since Beremiz debugger doesn't like pointer-by-reference stuff or global varibles, separate function is a must */ +unsigned char __GetReadStatus(unsigned char dummy) +unsigned int __GetFoundIndex(unsigned char dummy) + Method called by user to Simulate PLC + self._SetConnector(connectors.ConnectorFactory(uri, self)) + self.logger.write_error(_("Exception while connecting %s!\n") % uri) + self.logger.write_error(traceback.format_exc()) + # Did connection success ? + if self._connector is None: + self.logger.write_error(_("Connection failed to %s!\n") % uri) + self.CurrentMode = SIMULATION_MODE + buildpath = self._getBuildPath() + # Eventually create build dir + if not os.path.exists(buildpath): + # Generate SoftPLC IEC code + IECGenRes = self._Generate_SoftPLC() + # If IEC code gen fail, bail out. + self.logger.write_error(_("IEC-61131-3 code generation failed !\n")) + # Reset variable and program list that are parsed from + # CSV file generated by IEC2C compiler. + self.ResetIECProgramsAndVariables() + gen_result = self.CTNGenerate_C(buildpath, self.PLCGeneratedLocatedVars) + CTNCFilesAndCFLAGS, CTNLDFLAGS, DoCalls = gen_result[:3] + # if some files have been generated put them in the list with their location + self.LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), CTNCFilesAndCFLAGS, DoCalls)] + self.LocationCFilesAndCFLAGS = [] + # confnode asks for some LDFLAGS + # LDFLAGS can be either string + if type(CTNLDFLAGS) == type(str()): + self.LDFLAGS = [CTNLDFLAGS] + elif type(CTNLDFLAGS) == type(list()): + self.LDFLAGS = CTNLDFLAGS[:] + # Header file for extensions + open(os.path.join(buildpath, "beremiz.h"), "w").write(targets.GetHeader()) + # Template based part of C code generation + # files are stacked at the beginning, as files of confnode tree root + for generator, filename, name in [ + (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"), + # init/cleanup/retrieve/publish, run and align code + (self.Generate_plc_main, "plc_main.c", "Common runtime"), + # declare located variables for simulate in a black box + (self.Generate_plc_declare_locations, "plc_declare_locations.c", "Declare Locations"), + # declare located variables for simulate in a black box + (self.Generate_lpc_retain_array_sim, "lpc_retain_array_sim.c", "Retain Array for Simulation")]: + code_path = os.path.join(buildpath, filename) + open(code_path, "w").write(code) + # Insert this file as first file to be compiled at root confnode + self.LocationCFilesAndCFLAGS[0][1].insert(0, (code_path, self.plcCFLAGS)) + self.logger.write_error(name + _(" generation failed !\n")) + self.logger.write_error(traceback.format_exc()) + # Get simulation builder + builder = self.GetBuilder() + self.logger.write_error(_("Fatal : cannot get builder.\n")) + if not builder.build(): + self.logger.write_error(_("C Build failed.\n")) + self.logger.write_error(_("C Build crashed !\n")) + self.logger.write_error(traceback.format_exc()) + data = builder.GetBinaryCode() + if self._connector.NewPLC(builder.GetBinaryCodeMD5(), data, []): + self.UnsubscribeAllDebugIECVariable() + self.ProgramTransferred() + if self.AppFrame is not None: + self.AppFrame.CloseObsoleteDebugTabs() + self.AppFrame.RefreshPouInstanceVariablesPanel() + self.logger.write(_("Transfer completed successfully.\n")) + self.logger.write_error(_("Transfer failed\n")) + if not self.StatusTimer.IsRunning(): + # Start the status Timer + self.StatusTimer.Start(milliseconds=500, oneShot=False) + def StopSimulation(self): + self.CurrentMode = None + self._SetConnector(None, False) + ProjectController._Stop(self) + if self.CurrentMode == SIMULATION_MODE: + def CompareLocalAndRemotePLC(self): + # if self.LPCConnector is None: + if self._connector is None: + # We are now connected. Update button status + MD5 = self.GetLastBuildMD5() + # Check remote target PLC correspondance to that md5 + # if MD5 is not None and self.LPCConnector.MatchMD5(MD5): + if MD5 is not None and self._connector.MatchMD5(MD5): + # warns controller that program match + self.ProgramTransferred() + def ResetBuildMD5(self): + builder = self.GetBuilder() + if builder is not None: + builder.ResetBinaryCodeMD5(*([] if arch in PLC_module else [self.OnlineMode])) + def GetLastBuildMD5(self): + builder = self.GetBuilder() + if builder is not None: + return builder.GetBinaryCodeMD5(*([] if arch in PLC_module else [self.OnlineMode])) + def _Clean(self, building = False): + self._CloseView(self._IECCodeView) + runtime_list = fnmatch.filter(os.listdir(self._getBuildPath()), 'runtime_*') + if os.path.isdir(os.path.join(self._getBuildPath())) and os.path.isfile( + os.path.join(self._getBuildPath(), "hmi.py")) or runtime_list != []: + self.logger.write(_("Cleaning the build directory\n")) + # self.logger.write(_(str(os.path.join(self._getBuildPath(), "hmi.py")))) + for file in runtime_list: + os.remove(os.path.join(self._getBuildPath(), file)) + if os.path.isfile(os.path.join(self._getBuildPath(), "hmi.py")): + os.remove(os.path.join(self._getBuildPath(), "hmi.py")) + self.logger.write_error(_("Build directory already clean\n")) + self.ShowMethod("_showIECcode", False) + self.EnableMethod("_Clean", False) + self.CompareLocalAndRemotePLC() + if self.OnlineMode == "NORMAL": + if self.IsPLCStarted(): + dialog = wx.MessageDialog(self.AppFrame, "You must stop the PLC before transfer. Do you want to stop it now and transfer?", style=wx.YES_NO|wx.CENTRE) + if dialog.ShowModal() == wx.ID_YES: + ProjectController._Transfer(self) + if self.CurrentMode is None and self.OnlineMode != "OFF": + self.CurrentMode = TRANSFER_MODE + if ProjectController._Build(self): + ID_ABORTTRANSFERTIMER = wx.NewId() + self.AbortTransferTimer = wx.Timer(self.AppFrame, ID_ABORTTRANSFERTIMER) + self.AppFrame.Bind(wx.EVT_TIMER, self.AbortTransfer, self.AbortTransferTimer) + if self.OnlineMode == "BOOTLOADER": + self.logger.write(_("Resetting PLC\n")) + # self.StatusTimer.Stop() + self.LPCConnector.ResetPLC() + self.AbortTransferTimer.Start(milliseconds=5000, oneShot=True) + self.CurrentMode = None + def BeginTransfer(self): + self.logger.write(_("Start PLC transfer\n")) + self.AbortTransferTimer.Stop() + ProjectController._Transfer(self) + self.AbortTransferTimer.Start(milliseconds=5000, oneShot=True) + def AbortTransfer(self, event): + self.logger.write_warning(_("Timeout waiting PLC to recover\n")) + self.CurrentMode = None + self.AbortTransferTimer.Stop() + self.AbortTransferTimer = None + old_DebugThreadProc = ProjectController.DebugThreadProc + def LPCManager_DebugThreadProc(self, checking=False): + state, ls = self._connector.GetTraceVariables() + while state == "Stopped": + state, ls = self._connector.GetTraceVariables() + self.old_DebugThreadProc() + def _connect_debug(self, checking = False): + self.previous_plcstate = None + self.AppFrame.ResetGraphicViewers() + self.RegisterDebugVarToConnector() + if self.DispatchDebugValuesTimer is not None: + self.DispatchDebugValuesTimer.Start( + int(REFRESH_PERIOD * 1000), oneShot=True) + if self.DebugThread is None: + # self.DebugThread = Thread(target=self.DebugThreadProc) + self.DebugThread = Thread(target=self.LPCManager_DebugThreadProc, args=(checking, )) + self.DebugThread.setDaemon(True) + self.DebugThread.start() + if self.GetIECProgramsAndVariables(): + self._connector.StartPLC() + self.logger.write(_("Starting PLC\n")) + self._connect_debug(True) + self.logger.write_error(_("Couldn't start PLC !\n")) + self.UpdateMethodsFromPLCStatus() + self._SetConnector(None) + Method called by user to flash the firmware of the PLC + from FirmwareUpdateDialog import FirmwareUpdateDialog + from dialogs import DiscoveryDialog + from HostFirmwareUpdater import HostFirmwareUpdater + if self.firmawreUpadateIsRunning == True: + self.logger.write_error(_("Firmware update is already running!\n")) + self.firmawreUpadateIsRunning = True + self.logger.write(_("Firmware update started\n")) + # Launch the firmware selection dialog + dialog = FirmwareUpdateDialog(self.AppFrame) + answer = dialog.ShowModal() + imageFilePath = dialog.GetFirmwareImageFile() + updateType = dialog.GetFirmwareUpdateType() + chunksSize = dialog.GetChunksSize() + reboot = dialog.GetReboot() + if answer == wx.ID_CANCEL: + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + if imageFilePath == None or imageFilePath == "": + self.logger.write_error(_("No firmware image file selected!\n")) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + self.logger.write(_("Firmware image file: %s\n") % imageFilePath) + self.logger.write(_("Firmware update type: Linux kernel image\n")) + self.logger.write(_("Firmware update type: Linux DTB image\n")) + self.logger.write(_("Firmware update type: Root file system image\n")) + self.logger.write_error(_("Unknown firmware update type!\n")) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + if chunksSize < 1024 or chunksSize > 1024 * 1024: + self.logger.write_error(_("Bad chunks size : %d KiB!\n") % chunksSize) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + self.logger.write(_("Chunks size : %d KiB\n") % chunksSize) + self.logger.write(_("Reboot after update : %s\n") % ("Yes" if reboot else "No")) + # Get the target PLC URI + # Check if an uri is already configured in the Beremiz project + uri = self.BeremizRoot.getURI_location() + if uri is not None and uri != "": + self.logger.write(_("PLC URI configured in the Beremiz project will be used: %s\n") % uri) + # PLC URI is not configured in the Beremiz project + # Launch Service Discovery dialog + self.logger.write(_("PLC URI is not configured in the Beremiz project. Launching the Discover dialog\n")) + dialog = DiscoveryDialog(self.AppFrame) + answer = dialog.ShowModal() + # Nothing choosed or cancel button + if uri is None or answer == wx.ID_CANCEL: + self.logger.write_error(_("Connection canceled!\n")) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + # Get connector from uri + self._SetConnector(connectors.ConnectorFactory(uri, self)) + self.logger.write_error(_("Exception while connecting %s!\n") % uri) + self.logger.write_error(traceback.format_exc()) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + # Did connection success ? + if self._connector is None: + self.logger.write_error(_("Connection failed to %s!\n") % uri) + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + self.logger.write(_("Connected.\n")) + # Last confirmation before firmware update + answer = wx.MessageBox(_('Are you sure to launch the firmware update for the selected PLC?'), + _('Firmware Update'), wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT, + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + self.AppFrame.Refresh() + # Lauch the firmaware update on the remote PLC + updater = HostFirmwareUpdater(self._connector, imageFilePath, updateType, chunksSize, reboot, self.logger) + self.logger.write_error(str(e) + "\n") + self.logger.write_error(_("Firmware update canceled!\n")) + self.firmawreUpadateIsRunning = False + self.firmawreUpadateIsRunning = False + def __init__(self, port): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect(('localhost', port)) + idx = self.Buffer.find("\n") + text = self.socket.recv(2048) + idx = self.Buffer.find("\n") + line = self.Buffer[:idx + 1] + self.Buffer = self.Buffer[idx + 1:] + print "command >" + line + """ Base class for file like objects to facilitate StdOut for the Shell.""" + def write(self, s, style=None): + self.socket.send(s.encode('utf8')) + def writeyield(self, s): + def write_warning(self, s): + def write_error(self, s): +from threading import Thread, Timer, Semaphore, Lock +wx_eval_lock = Semaphore(0) +def wx_evaluator(callable, *args, **kwargs): + eval_res = callable(*args, **kwargs) +def evaluator(callable, *args, **kwargs): + wx.CallAfter(wx_evaluator, callable, *args, **kwargs) +# Command log for debug, for viewing from wxInspector + __builtins__.cmdlog = [] +if __name__ == '__main__': + lpcmanager = LPCManagerLauncher()