lpcmanager

Moved StdouPseudoFile out of LPCManager.py
revamp
2018-02-05, Edouard Tisserant
0c4758cbaa5d
Parents 37a540b68d3b
Children 1bcb42bc5783
Moved StdouPseudoFile out of LPCManager.py
--- 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 @@
import fnmatch
import shutil
-import socket
import threading
import zipfile
import tempfile
@@ -87,7 +86,7 @@
return globals()
def CreateUI(self):
- 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 @@
return True
-
-class StdoutPseudoFile:
- def __init__(self, port):
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.socket.connect(('localhost', port))
- self.Buffer = ""
-
- def __del__(self):
- self.socket.close()
-
- def readline(self):
- idx = self.Buffer.find("\n")
- while idx == -1:
- text = self.socket.recv(2048)
- if text == "":
- return ""
- self.Buffer += text
- idx = self.Buffer.find("\n")
- if idx != -1:
- line = self.Buffer[:idx + 1]
- self.Buffer = self.Buffer[idx + 1:]
- if BMZ_DBG:
- print "command >" + line
- return line
- return ""
-
- """ Base class for file like objects to facilitate StdOut for the Shell."""
-
- def write(self, s, style=None):
- if s != '':
- self.socket.send(s.encode('utf8'))
-
- def writeyield(self, s):
- self.write(s)
-
- def write_warning(self, s):
- self.write(s)
-
- def write_error(self, s):
- self.write(s)
-
- def flush(self):
- pass
-
- def isatty(self):
- return False
-
-
from threading import Thread, Timer, Semaphore, Lock
import cmd
--- /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 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import sys
+import os
+
+# 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)
+
+import fnmatch
+import shutil
+import socket
+import threading
+import zipfile
+import tempfile
+import getopt
+import __builtin__
+from types import StringType, UnicodeType
+import time
+import wx
+from util.BitmapLibrary import AddBitmapFolder
+
+# Path of directory containing current python file
+_lpcmanager_path = os.path.split(__file__)[0]
+
+# XXX Where is MC8 ?
+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
+arch = None
+
+class LPCManagerLauncher(BeremizIDELauncher):
+ def __init__(self):
+ BeremizIDELauncher.__init__(self)
+ self.arch = None
+ self.port = None
+ self.extensions = [os.path.join(_lpcmanager_path, "extention.py")]
+ self.modules.extend([
+ "LPCBeremiz",
+ "StdoutPseudoFile",
+ "LPCProjectController",
+ "LPCCommand"])
+
+ def Usage(self):
+ print("\nUsage of LPCManager.py :")
+ print("\n %s Projectpath Buildpath port arch\n" % sys.argv[0])
+
+ def ProcessCommandLineArgs(self):
+ global arch
+ # Command line arguments parsing
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
+ except getopt.GetoptError:
+ # print help information and exit:
+ self.Usage()
+ sys.exit(2)
+
+ # asking for help causes exit
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ self.Usage()
+ sys.exit()
+
+ if len(args) != 4 :
+ self.Usage()
+ sys.exit()
+ else:
+ self.projectOpen = args[0]
+ self.buildpath = args[1]
+ try:
+ self.port = int(args[2])
+ except:
+ self.Usage()
+ sys.exit()
+ self.arch = args[3]
+ arch = self.arch
+
+ # overload with exacltly same code, but this is intended.
+ # we want extensions to use globals of this module, not Beremiz.py
+ def globals(self):
+ return globals()
+
+ def CreateUI(self):
+ 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)
+ if result:
+ CMDpipe.write("Error: Invalid project directory", result)
+ else:
+ CMDpipe.write("Error: No such file or directory")
+
+ lpcberemiz_cmd = LPCCommand(CTR, CMDpipe)
+
+ cmd_thread = Thread(target=lpcberemiz_cmd.cmdloop)
+ cmd_thread.start()
+ # TODO: join() when exiting
+
+ self.frame = self.LPCBeremiz.LPCBeremiz(None, ctr=CTR)
+
+ def ShowUI(self):
+ # the "Show" command from composer does it instead
+ pass
+
+ 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")
+
+ # Create app usual way
+ BeremizIDELauncher.CreateApplication(self)
+
+ # Add LPCmanager's image folder to searched ones.
+ AddBitmapFolder(os.path.join(_lpcmanager_path, "images"))
+
+
+import connectors
+from LPCconnector import LPC_connector_factory
+from LPCconnector.PYRO import MW_PYRO_connector_factory
+from LPCconnector.WAMP import MWWAMP_connector_factory
+
+import targets
+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((
+ "%(name)s",
+ "%(IECtype)s",
+ %(initial)s,
+ %(desc)s,
+ %(onchange)s,
+ %(opts)s))
+""" % varinfo
+ for varinfo in varinfos])
+
+ # Runtime calls (start, stop, init, and cleanup)
+ rtcalls = ""
+ for section in self.SECTIONS_NAMES:
+ if section != "globals":
+ rtcalls += "def _runtime_%s_%s():\n" % (location_str, section)
+ sectiontext = self.GetSection(section).strip()
+ if sectiontext:
+ rtcalls += ' ' + \
+ sectiontext.replace('\n', '\n ') + "\n\n"
+ else:
+ rtcalls += " pass\n\n"
+
+ globalsection = self.GetSection("globals")
+
+ PyFileContent = """\
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+## Code generated by Beremiz python mixin confnode
+##
+
+## Code for PLC global variable access
+from targets.typemapping import TypeTranslator
+import ctypes
+_%(pyextname)sGlobalsDesc = []
+__ext_name__ = "%(pyextname)s"
+PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
+%(globalstubs)s
+
+## User code in "global" scope
+%(globalsection)s
+
+## Beremiz python runtime calls
+%(rtcalls)s
+
+del __ext_name__
+
+""" % locals()
+
+ # 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'))
+ runtimefile.close()
+
+ # C code for safe global variables access
+
+ vardecfmt = """\
+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;
+"""
+
+ varretfmt = """\
+ 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);
+ }
+"""
+ varpubfmt = """\
+ 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
+ varpubfmt) % varinfo
+ 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.
+ PyCFileContent = """\
+/*
+ * Code generated by Beremiz py_ext confnode
+ * for safe global variables access
+ */
+#include "iec_types_all.h"
+#include "POUS.h"
+#include "config.h"
+#include "beremiz.h"
+
+/* User variables reference */
+%(vardec)s
+
+/* Beremiz confnode functions */
+int __init_%(location_str)s(int argc,char **argv){
+%(varinit)s
+ return 0;
+}
+
+void __cleanup_%(location_str)s(void){
+}
+
+void __retrieve_%(location_str)s(void){
+%(varret)s
+}
+
+void __publish_%(location_str)s(void){
+%(varpub)s
+}
+""" % locals()
+
+ Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str)
+ pycfile = open(Gen_PyCfile_path, 'w')
+ pycfile.write(PyCFileContent)
+ pycfile.close()
+
+ matiec_CFLAGS = '"-I%s"' % os.path.abspath(
+ self.GetCTRoot().GetIECLibPath())
+
+ return ([(Gen_PyCfile_path, matiec_CFLAGS)],
+ "",
+ True,
+ ("runtime_%s.py" % location_str, file(runtimefile_path, "rb")))
+PythonFileCTNMixin.CTNGenerate_C = CTNGenerate_C
+
+def ResetSearchResults(self):
+ self.Highlights = []
+ self.SearchParams = None
+ self.SearchResults = None
+ self.VariableSearchResults = []
+ self.CurrentFindHighlight = None
+ self.index = 0
+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)
+
+HIGHLIGHT_TYPES = {
+ ERROR_HIGHLIGHT: STC_CODE_ERROR,
+ SEARCH_RESULT_HIGHLIGHT: STC_CODE_SEARCH_RESULT,
+}
+def FindVariable(self, direction, search_params, variables):
+ first = False
+ if self.SearchParams != search_params:
+ first = True
+ 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()
+ value = value.lower()
+ if search_value in value:
+ self.VariableSearchResults.append((row, col))
+ self.index = 0
+ if len(self.VariableSearchResults) > 0:
+ (r, c) = self.VariableSearchResults[self.index]
+ variables.VariablesGrid.SetCellBackgroundColour(r, c, SEARCH_RESULT_HIGHLIGHT[1])
+ if not first:
+ 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):
+ result = []
+ from xml.dom import minidom
+ dir_list = next(os.walk(self.ProjectPath))[1]
+ for dir in dir_list:
+ if dir not in ["build", "CanOpen@CanOpen", "project_files"]:
+ path = os.path.join(self.ProjectPath, dir, 'pyfile.xml')
+ variablelist = []
+ if os.path.exists(path):
+ pyfile = minidom.parse(path)
+ variablelist = pyfile.getElementsByTagName('variable')
+ for s in variablelist:
+ if criteria["find_pattern"] in s.attributes['name'].value:
+ result.append(dir)
+ return result
+PLCControler.SearchInPyfile = SearchInPyfile
+
+def OnSearchInProjectMenu(self, event):
+ dialog = SearchInProjectDialog(self)
+ if dialog.ShowModal() == wx.ID_OK:
+ criteria = dialog.GetCriteria()
+ if len(criteria) > 0:
+ 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):
+ to_delete = []
+ 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"])])
+ if item_alone:
+ 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():
+ to_delete.append(item)
+ item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+ for item in to_delete:
+ 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))
+
+ self.RefreshView()
+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"])
+ else:
+ root = self.SearchResultsTree.GetRootItem()
+ self.SearchResultsTree.AppendItem(root, name[0], image=self.TreeImageDict["wx_glade"])
+ if matches > 1:
+ 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.Criteria = None
+ self.ElementsOrder = []
+ self.SearchResults = {}
+ self.PySearchResults = []
+ self.RefreshView()
+SearchResultPanel.ResetSearchResults = ResetSearchResults
+
+
+def GetPythonTextCtrlDClickFunction(self, item):
+ for type in self.ParentWindow.CTR.Children.values():
+ for name in type:
+ if name.BaseParams.attrib['Name'] == item:
+ name._OpenView()
+ selected = self.ParentWindow.TabsOpened.GetSelection()
+ if selected != -1:
+ 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)
+ event.Skip()
+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
+ ] = range(10)
+
+ 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)
+
+ self.Scalings = {}
+ 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)
+
+ scaling_controls = []
+ for idx, (name, label) in enumerate([('XScale', _('Horizontal:')),
+ ('YScale', _('Vertical:'))]):
+ if idx == 0:
+ border = wx.TOP
+ else:
+ border = wx.BOTTOM
+
+ 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,
+ style=wx.CB_READONLY)
+ 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
+ self.Values = None
+
+ 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)
+ else:
+ self.dialog = WampOptionsEditor(self.Parent.Parent, options, desc)
+ answer = self.dialog.ShowModal()
+ opt,OnChange,value,description = self.dialog.GetOptions()
+ if answer == wx.ID_OK:
+ self.grid.SetCellValue(event.GetRow(), event.GetCol(), str(opt))
+ if OnChange:
+ self.grid.SetCellValue(event.GetRow(), event.GetCol()-1, value)
+ self.grid.SetCellValue(event.GetRow(), event.GetCol()-2, description)
+ self.Parent.RefreshModel()
+
+ else:
+ event.Skip()
+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.
+ """
+ typelist = None
+ accesslist = None
+ self.grid = grid
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ editor = None
+ renderer = None
+ colname = self.GetColLabelValue(col, False)
+
+ if colname in ["Name", "Initial", "Description", "OnChange"]:
+ editor = wx.grid.GridCellTextEditor()
+ elif colname == "Class":
+ editor = wx.grid.GridCellChoiceEditor()
+ editor.SetParameters("input,memory,output")
+ elif colname == "Type":
+ pass
+ else:
+ 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()
+# if not rez:
+# self.GrandParent.CTR.logger.write_warning("Can not reset log messages!\n")
+# self.ResetLogMessages()
+# self.RefreshView()
+# event.Skip()
+# 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):
+ if tagname:
+ return defaultGenerateNewName(self, tagname, name, format, start_idx=0, exclude={}, debug=False)
+ else:
+ names = exclude.copy()
+ i = start_idx
+ while name is None or names.get(name.upper(), False):
+ name = (format%i)
+ i += 1
+ return name
+PLCControler.GenerateNewName = newGenerateNewName
+
+
+havecanfestival = False
+# try:
+from canfestival import RootClass as CanOpenRootClass
+from canfestival.canfestival import _SlaveCTN, _NodeListCTN, NodeManager
+from canfestival.NetworkEditor import NetworkEditor
+from canfestival.SlaveEditor import SlaveEditor
+
+havecanfestival = True
+# except:
+# havecanfestival = False
+
+SCROLLBAR_UNIT = 10
+ID_EXPORT = 7500
+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',
+ 'mono': 'Courier New',
+ 'helv': 'Arial',
+ 'other': 'Comic Sans MS',
+ 'size': 16,
+ }
+else:
+ faces = {'times': 'Times',
+ 'mono': 'Courier',
+ 'helv': 'Helvetica',
+ 'other': 'new century schoolbook',
+ 'size': 18,
+ }
+
+# ConfTreeNodeEditor.SHOW_BASE_PARAMS = False
+
+# -------------------------------------------------------------------------------
+# CANFESTIVAL CONFNODE HACK
+# -------------------------------------------------------------------------------
+from canfestival import canfestival
+defaultGetCFLAGS = canfestival.local_canfestival_config.getCFLAGS
+defaultGetLDFLAGS = canfestival.local_canfestival_config.getLDFLAGS
+
+def getCFLAGS(*args):
+ return str(defaultGetCFLAGS(*args))
+
+def getLDFLAGS(*args):
+ return str(defaultGetLDFLAGS(*args))
+
+canfestival.local_canfestival_config.getCFLAGS = getCFLAGS
+canfestival.local_canfestival_config.getLDFLAGS = getLDFLAGS
+
+import LPCBus as LPCBus_mod
+
+if arch in PLC_module:
+ LPCBus_mod.LPCarch = "MC9"
+ LPCBus_mod.arch = arch
+else:
+ LPCBus_mod.LPCarch = arch
+ LPCBus_mod.arch = arch
+
+from LPCBus import *
+
+# -------------------------------------------------------------------------------
+# LPC CanFestival ConfNode Class
+# -------------------------------------------------------------------------------
+
+if havecanfestival:
+ DEFAULT_SETTINGS = {
+ "CAN_Baudrate": "125K",
+ "Slave_NodeId": 2,
+ "Master_NodeId": 1,
+ }
+
+
+class LPCSlaveEditor(SlaveEditor):
+ # SHOW_BASE_PARAMS = False
+ pass
+
+
+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:complexType>
+ <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:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="99"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """ % DEFAULT_SETTINGS
+
+ EditorType = LPCSlaveEditor
+
+ def __init__(self):
+ # TODO change netname when name change
+ NodeManager.__init__(self)
+ odfilepath = self.GetSlaveODPath()
+ if (os.path.isfile(odfilepath)):
+ self.OpenFileInCurrent(odfilepath)
+ else:
+ self.CreateNewNode("SlaveNode", # Name - will be changed at build time
+ 0x00, # NodeID - will be changed at build time
+ "slave", # Type
+ "", # description
+ "None", # profile
+ "", # prfile filepath
+ "heartbeat", # NMT
+ []) # options
+ self.OnCTNSave()
+
+ def GetCanDevice(self):
+ return str(self.BaseParams.getIEC_Channel())
+
+ ConfNodeMethods = [
+ {"bitmap": "NetworkEdit",
+ "name": _("Edit slave"),
+ "tooltip": _("Edit CanOpen slave with ObjdictEdit"),
+ "method": "_OpenView"},
+ ] + _SlaveCTN.ConfNodeMethods
+
+
+class LPCNetworkEditor(NetworkEditor):
+ # SHOW_BASE_PARAMS = False
+ pass
+
+
+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:complexType>
+ <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"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """ % DEFAULT_SETTINGS
+
+ EditorType = LPCNetworkEditor
+
+ def GetCanDevice(self):
+ return str(self.BaseParams.getIEC_Channel())
+
+ ConfNodeMethods = [
+ {"bitmap": "NetworkEdit",
+ "name": _("Edit network"),
+ "tooltip": _("Edit CanOpen Network with NetworkEdit"),
+ "method": "_OpenView"},
+ ] + _NodeListCTN.ConfNodeMethods
+
+
+class LPCCanOpen(CanOpenRootClass):
+ XSD = None
+ CTNChildrenTypes = [("CanOpenNode", LPCCanOpenMaster, "CanOpen Master"),
+ ("CanOpenSlave", LPCCanOpenSlave, "CanOpen Slave")]
+
+ def GetCanDriver(self):
+ return None
+
+ 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)
+ slave.CTNRequestSave()
+
+# -------------------------------------------------------------------------------
+# 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):
+ shutil.rmtree(dstpath)
+ os.makedirs(dstpath)
+ mycopytree(srcpath, dstpath)
+ elif os.path.isfile(srcpath):
+ shutil.copy2(srcpath, dstpath)
+
+
+[SIMULATION_MODE, TRANSFER_MODE] = range(2)
+
+if arch in PLC_module:
+ class LPCProjectNodeEditor(ProjectNodeEditor):
+ pass
+else:
+ class LPCProjectNodeEditor(ProjectNodeEditor):
+ SHOW_PARAMS = False
+ ENABLE_REQUIRED = False
+
+_StatusMethods = [
+ {"bitmap": "Debug",
+ "name": _("Simulate"),
+ "tooltip": _("Simulate PLC"),
+ "method": "_Simulate"},
+ {"bitmap": "Run",
+ "name": _("Run"),
+ "shown": False,
+ "tooltip": _("Start PLC"),
+ "method": "_Run"},
+ {"bitmap": "Stop",
+ "name": _("Stop"),
+ "shown": False,
+ "tooltip": _("Stop Running PLC"),
+ "method": "_Stop"},
+ {"bitmap": "Build",
+ "name": _("Build"),
+ "tooltip": _("Build project into build folder"),
+ "method": "_Build"},
+ {"bitmap": "Clean",
+ "name": _("Clean"),
+ "enabled": False,
+ "tooltip": _("Clean project build folder"),
+ "method": "_Clean"},
+ {"bitmap": "Transfer",
+ "name": _("Transfer"),
+ "shown": False,
+ "tooltip": _("Transfer PLC"),
+ "method": "_Transfer"},
+]
+_MethodFromPLCState = {
+ "Started": [("_Simulate", False),
+ ("_Run", False),
+ ("_Stop", True),
+ ("_Build", True),
+ ("_Transfer", True)],
+ "Stopped": [("_Simulate", False),
+ ("_Run", True),
+ ("_Stop", False),
+ ("_Build", True),
+ ("_Transfer", True)],
+ "Connected": [("_Simulate", "not simulating"),
+ ("_Run", True),
+ ("_Stop", True if arch in PLC_module else "simulating"),
+ ("_Build", True),
+ ("_Transfer", True)],
+ "Disconnected": [("_Simulate", "not simulating"),
+ ("_Run", False),
+ ("_Stop", False if arch in PLC_module else "simulating"),
+ ("_Build", True),
+ ("_Transfer", False)],
+ "Empty" :[("_Simulate", "not simulating"),
+ ("_Run", False),
+ ("_Stop", False),
+ ("_Build", True),
+ ("_Transfer", True)],
+}
+
+if arch in PLC_module:
+ _StatusMethods += [
+ {"bitmap": "Connect",
+ "name": _("Connect"),
+ "shown": True,
+ "tooltip": _("Connect to the target PLC"),
+ "method": "_Connect"},
+ {"bitmap": "Disconnect",
+ "name": _("Disconnect"),
+ "shown": False,
+ "tooltip": _("Disconnect from PLC"),
+ "method": "_Disconnect"},
+ {"bitmap": "UpdateFw",
+ "name": _("UpdateFw"),
+ "shown": True,
+ "tooltip": _("Update the PLC firmware"),
+ "method": "_UpdateFw"},
+ ]
+ _MethodFromPLCState["Disconnected"] += [("_Connect", True),
+ ("_Disconnect", False)]
+
+ _MethodFromPLCState["Connected"] += [("_Connect", False),
+ ("_Disconnect", True)]
+
+class LPCProjectController(ProjectController):
+ StatusMethods = _StatusMethods
+ # ConfNodeMethods = []
+
+ EditorType = LPCProjectNodeEditor
+
+ def __init__(self, frame, logger, buildpath):
+ self.arch = arch
+ self.OrigBuildPath = buildpath
+
+ ProjectController.__init__(self, frame, logger)
+
+ if havecanfestival:
+ self.CTNChildrenTypes += [("LPCBus", LPCBus, "LPC bus"), ("CanOpen", LPCCanOpen, "CanOpen bus")]
+ else:
+ self.CTNChildrenTypes += [("LPCBus", LPCBus, "LPC bus")]
+ self.CTNType = "LPC"
+
+ 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
+ self.building = False
+
+
+ # Bind mouse double click event on URI_location in Beremiz
+ self.UriOptions = True
+
+ def GetProjectName(self):
+ return self.Project.getname()
+
+ def GetDefaultTargetName(self):
+ if self.CurrentMode == SIMULATION_MODE:
+ return ProjectController.GetDefaultTargetName(self)
+ else:
+ return "LPC"
+
+ def GetTarget(self):
+ target = ProjectController.GetTarget(self)
+ if self.CurrentMode != SIMULATION_MODE and arch not in PLC_module:
+ target.getcontent().setBuildPath(self.BuildPath)
+ return target
+
+ 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
+ else:
+ return ProjectController._getBuildPath(self)
+
+ def ToZIPFile(self):
+ # MD5 = self.GetLastBuildMD5()
+ try:
+ 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')
+ zf.close()
+ self.logger.write(_("Export file is successfully created on location: %s\n") % path_export_file)
+ except Exception, e:
+ self.logger.write(_("Export file is not created because eror: %s\n") % e)
+
+ def _Build(self):
+ self._Clean()
+ save = self.ProjectTestModified()
+ if save:
+ self.SaveProject()
+ self.AppFrame._Refresh(TITLE, FILEMENU)
+ if self.BuildPath is not None:
+ mycopytree(self.OrigBuildPath, self.BuildPath)
+ if ProjectController._Build(self):
+ self.ToZIPFile()
+ if save:
+ 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 arch in PLC_module:
+ return None
+
+ mode = mode.upper()
+ if self.OnlineMode != mode:
+ if mode not in ["OFF", ""]:
+ self.OnlineMode = mode
+ self.ConnectorPath = path
+ uri = "LPC://%s/%s" % (self.OnlineMode, path)
+ try:
+ self.LPCConnector = connectors.ConnectorFactory(uri, self)
+ except Exception, msg:
+ 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:
+ # Oups.
+ self.logger.write_error(_("Connection failed to %s!\n") % uri)
+
+ else:
+ self.OnlineMode = "OFF"
+ self.LPCConnector = None
+ self.ConnectorPath = None
+
+ self.ApplyOnlineMode()
+
+
+ def ApplyOnlineMode(self):
+ if self.CurrentMode != SIMULATION_MODE:
+ self.KillDebugThread()
+
+ 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)
+ else:
+ status = ""
+ 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._connect_debug()
+ else:
+ 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":
+ self.BeginTransfer()
+
+ 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)
+ self.BufferProject()
+
+ # Update a PLCOpenEditor Pou variable name
+ def UpdateProjectVariableName(self, old_name, new_name):
+ self.Project.updateElementName(old_name, new_name)
+ self.BufferProject()
+
+ def RemoveProjectVariableByAddress(self, address):
+ self.Project.removeVariableByAddress(address)
+ self.BufferProject()
+
+ def RemoveProjectVariableByFilter(self, leading):
+ self.Project.removeVariableByFilter(leading)
+ self.BufferProject()
+
+ def AddProjectDefaultConfiguration(self, config_name="config", res_name="resource1"):
+ ProjectController.AddProjectDefaultConfiguration(self, config_name, res_name)
+
+ self.SetEditedResourceInfos(
+ self.ComputeConfigurationResourceName(config_name, res_name),
+ [{"Name": "main_task",
+ "Triggering": "Cyclic",
+ "Interval": "T#50ms",
+ "Priority": 0}],
+ [])
+
+ 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):
+ # Load PLCOpen file
+ result = self.OpenXMLFile(plc_file)
+ if result:
+ return result
+ else:
+ self.CreateNewProject({"companyName": "",
+ "productName": "",
+ "productVersion": "",
+ "projectName": "",
+ "pageSize": (0, 0),
+ "scaling": {}})
+ if len(self.GetProjectConfigNames()) == 0:
+ self.AddProjectDefaultConfiguration()
+
+ # Change XSD into class members
+ self._AddParamsMembers()
+ self.Children = {}
+
+ # 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()
+ if result:
+ return result
+ # Load and init all the children
+ self.LoadChildren()
+
+ canopen_child = self.GetChildByName("CanOpen")
+ if havecanfestival and canopen_child is None:
+ canopen = self.CTNAddChild("CanOpen", "CanOpen", 0)
+ canopen.LoadChildren()
+ canopen.CTNRequestSave()
+
+ elif not havecanfestival and canopen_child is not None:
+ canopen_child.CTNRemove()
+
+ if self.CTNTestModified():
+ self.SaveProject()
+
+ if wx.GetApp() is None:
+ self.RefreshConfNodesBlockLists()
+ else:
+ wx.CallAfter(self.RefreshConfNodesBlockLists)
+
+ if arch in PLC_module:
+ self.SetParamsAttribute('BeremizRoot.TargetType', 'MC9')
+
+ return None
+
+ 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 type(val) == str:
+ if val.endswith("simulating"):
+ if val.startswith("not"):
+ val = not simulating
+ else:
+ val = simulating
+
+ ProjectController.ShowMethod(self, name, val)
+
+ def UpdateMethodsFromPLCStatus(self):
+ simulating = self.CurrentMode == SIMULATION_MODE
+ if self.OnlineMode == "OFF":
+ if simulating:
+ status, log_count = self._connector.GetPLCstatus()
+ self.UpdatePLCLog(log_count)
+ status = "Disconnected"
+ elif self.OnlineMode == "BOOTLOADER":
+ status = "Connected"
+ else:
+ if self._connector is not None:
+ status, log_count = self._connector.GetPLCstatus()
+ if status == "Disconnected":
+ self._SetConnector(None, False)
+ else:
+ self.UpdatePLCLog(log_count)
+ else:
+ status = "Disconnected"
+ if self.previous_plcstate != status or self.previous_mode != self.CurrentMode:
+ for args in _MethodFromPLCState.get(status, []):
+ self.ShowMethod(*args)
+ self.previous_plcstate = status
+ self.previous_mode = self.CurrentMode
+ if self.AppFrame is not None:
+ self.AppFrame.RefreshStatusToolBar()
+ connection_text = _("Connected to: ")
+ status_text = ""
+ if simulating:
+ connection_text += _("Simulation")
+ status_text += _("ON")
+ if status == "Disconnected":
+ if not simulating:
+ self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 1)
+ self.AppFrame.ConnectionStatusBar.SetStatusText('', 2)
+ else:
+ self.AppFrame.ConnectionStatusBar.SetStatusText(connection_text, 1)
+ self.AppFrame.ConnectionStatusBar.SetStatusText(status_text, 2)
+ else:
+ if simulating:
+ connection_text += " (%s)"
+ status_text += " (%s)"
+ else:
+ connection_text += "%s"
+ status_text += "%s"
+ self.AppFrame.ConnectionStatusBar.SetStatusText(connection_text % self.ConnectorPath, 1)
+ self.AppFrame.ConnectionStatusBar.SetStatusText(status_text % _(status), 2)
+ return True
+ return False
+
+ 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 beremiz_##name;\
+type *name = &beremiz_##name;
+
+#include "LOCATED_VARIABLES.h"
+
+#undef __LOCATED_VAR
+
+"""
+
+ def Generate_lpc_retain_array_sim(self):
+ """
+ Support for retain array in Simulation
+ """
+ return """/* Support for retain array */
+#define USER_RETAIN_ARRAY_SIZE 2000
+#define NUM_OF_COLS 3
+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(READ == 1)
+ {
+ if((0<=INDEX) && (INDEX<USER_RETAIN_ARRAY_SIZE) && (0<=COLUMN) && (COLUMN<NUM_OF_COLS))
+ {
+ readOK = 1;
+ return retainArray[INDEX][COLUMN];
+ }
+ }
+
+ readOK = 0;
+ return 0;
+}
+
+unsigned char __SetRetainData(unsigned char WRITE, unsigned int INDEX, unsigned int WORD1, unsigned int WORD2, unsigned int WORD3)
+{
+ if(WRITE == 1)
+ {
+ if((0<=INDEX) && (INDEX<USER_RETAIN_ARRAY_SIZE))
+ {
+ retainArray[INDEX][0] = WORD1;
+ retainArray[INDEX][1] = WORD2;
+ retainArray[INDEX][2] = WORD3;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+unsigned char __FindRetainData(unsigned char SEARCH, unsigned int START_IDX, unsigned int END_IDX, unsigned int WORD1, unsigned int WORD2, unsigned int WORD3)
+{
+ unsigned int i;
+
+ 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 = i;
+ return 1;
+ }
+ }
+ }
+
+ foundIndex = USER_RETAIN_ARRAY_SIZE; /* Data not found => return index that is out of array bounds */
+ return 0;
+}
+
+/* Since Beremiz debugger doesn't like pointer-by-reference stuff or global varibles, separate function is a must */
+unsigned char __GetReadStatus(unsigned char dummy)
+{
+ return readOK;
+}
+
+unsigned int __GetFoundIndex(unsigned char dummy)
+{
+ return foundIndex;
+}
+"""
+
+ def _Simulate(self):
+ """
+ Method called by user to Simulate PLC
+ """
+ uri = "LOCAL://"
+ try:
+ self._SetConnector(connectors.ConnectorFactory(uri, self))
+ except Exception, msg:
+ 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:
+ # Oups.
+ self.logger.write_error(_("Connection failed to %s!\n") % uri)
+ self.StopSimulation()
+ return False
+
+ self.CurrentMode = SIMULATION_MODE
+
+ buildpath = self._getBuildPath()
+
+ # Eventually create build dir
+ if not os.path.exists(buildpath):
+ os.makedirs(buildpath)
+
+ # Generate SoftPLC IEC code
+ IECGenRes = self._Generate_SoftPLC()
+
+ # If IEC code gen fail, bail out.
+ if not IECGenRes:
+ self.logger.write_error(_("IEC-61131-3 code generation failed !\n"))
+ self.StopSimulation()
+ return False
+
+ # 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
+ if CTNCFilesAndCFLAGS:
+ self.LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), CTNCFilesAndCFLAGS, DoCalls)]
+ else:
+ self.LocationCFilesAndCFLAGS = []
+
+ # confnode asks for some LDFLAGS
+ if CTNLDFLAGS:
+ # LDFLAGS can be either string
+ if type(CTNLDFLAGS) == type(str()):
+ self.LDFLAGS = [CTNLDFLAGS]
+ # or list of strings
+ elif type(CTNLDFLAGS) == type(list()):
+ self.LDFLAGS = CTNLDFLAGS[:]
+ else:
+ self.LDFLAGS = []
+
+ # 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 [
+ # debugger code
+ (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")]:
+ try:
+ # Do generate
+ code = generator()
+ if code is None:
+ raise
+ 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))
+ except Exception, exc:
+ self.logger.write_error(name + _(" generation failed !\n"))
+ self.logger.write_error(traceback.format_exc())
+ self.StopSimulation()
+ return False
+
+ # Get simulation builder
+ builder = self.GetBuilder()
+ if builder is None:
+ self.logger.write_error(_("Fatal : cannot get builder.\n"))
+ self.StopSimulation()
+ return False
+
+ # Build
+ try:
+ if not builder.build():
+ self.logger.write_error(_("C Build failed.\n"))
+ self.StopSimulation()
+ return False
+ except Exception, exc:
+ self.logger.write_error(_("C Build crashed !\n"))
+ self.logger.write_error(traceback.format_exc())
+ self.StopSimulation()
+ return False
+
+ data = builder.GetBinaryCode()
+ if data is not None:
+ 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"))
+ else:
+ self.logger.write_error(_("Transfer failed\n"))
+ self.StopSimulation()
+ return False
+
+ self._Run()
+
+ 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)
+ self.ApplyOnlineMode()
+
+ def _Stop(self):
+ ProjectController._Stop(self)
+
+ if self.CurrentMode == SIMULATION_MODE:
+ self.StopSimulation()
+
+ def CompareLocalAndRemotePLC(self):
+ # if self.LPCConnector is None:
+ # return
+ if self._connector is None:
+ return
+ # 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]))
+ else:
+ return None
+
+ 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"))
+ else:
+ if not building:
+ self.logger.write_error(_("Build directory already clean\n"))
+ self.ShowMethod("_showIECcode", False)
+ self.EnableMethod("_Clean", False)
+ # kill the builder
+ self._builder = None
+ self.CompareLocalAndRemotePLC()
+
+ def _Transfer(self):
+ 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:
+ self._Stop()
+ else:
+ return
+ ProjectController._Transfer(self)
+ return
+ 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.BeginTransfer()
+
+ else:
+ self.logger.write(_("Resetting PLC\n"))
+ # self.StatusTimer.Stop()
+ self.LPCConnector.ResetPLC()
+ self.AbortTransferTimer.Start(milliseconds=5000, oneShot=True)
+
+ else:
+ 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
+ event.Skip()
+
+ old_DebugThreadProc = ProjectController.DebugThreadProc
+ def LPCManager_DebugThreadProc(self, checking=False):
+ if checking:
+ state, ls = self._connector.GetTraceVariables()
+ while state == "Stopped":
+ state, ls = self._connector.GetTraceVariables()
+ time.sleep(0.1)
+
+ self.old_DebugThreadProc()
+
+ def _connect_debug(self, checking = False):
+ self.previous_plcstate = None
+ if self.AppFrame:
+ 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()
+
+ def _Run(self):
+ """
+ Start PLC
+ """
+ if self.GetIECProgramsAndVariables():
+ self._connector.StartPLC()
+ self.logger.write(_("Starting PLC\n"))
+ self._connect_debug(True)
+ else:
+ self.logger.write_error(_("Couldn't start PLC !\n"))
+ self.UpdateMethodsFromPLCStatus()
+
+ def _Disconnect(self):
+ self._SetConnector(None)
+ self.KillDebugThread()
+
+ def _UpdateFw(self):
+ """
+ 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"))
+ return
+
+ 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()
+ dialog.Destroy()
+
+ if answer == wx.ID_CANCEL:
+ self.logger.write_error(_("Firmware update canceled!\n"))
+ self.firmawreUpadateIsRunning = False
+ return
+
+ 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
+ return
+ else:
+ self.logger.write(_("Firmware image file: %s\n") % imageFilePath)
+
+ if updateType == 1:
+ self.logger.write(_("Firmware update type: Linux kernel image\n"))
+ elif updateType == 2:
+ self.logger.write(_("Firmware update type: Linux DTB image\n"))
+ elif updateType == 3:
+ self.logger.write(_("Firmware update type: Root file system image\n"))
+ else:
+ self.logger.write_error(_("Unknown firmware update type!\n"))
+ self.logger.write_error(_("Firmware update canceled!\n"))
+ self.firmawreUpadateIsRunning = False
+ return
+
+ 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
+ return
+ else:
+ 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)
+ else:
+ # 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()
+ uri = dialog.GetURI()
+ dialog.Destroy()
+
+ # 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
+ return
+
+ # Get connector from uri
+ self._connector = None
+ try:
+ self._SetConnector(connectors.ConnectorFactory(uri, self))
+ except Exception:
+ 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
+ return
+
+ # Did connection success ?
+ if self._connector is None:
+ # Oups.
+ self.logger.write_error(_("Connection failed to %s!\n") % uri)
+ self.logger.write_error(_("Firmware update canceled!\n"))
+ self.firmawreUpadateIsRunning = False
+ return
+ else:
+ 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.AppFrame)
+ if answer != wx.YES:
+ self.logger.write_error(_("Firmware update canceled!\n"))
+ self.firmawreUpadateIsRunning = False
+ return
+ # Some cosmetics
+ self.AppFrame.Refresh()
+ self.AppFrame.Update()
+
+ # Lauch the firmaware update on the remote PLC
+ updater = HostFirmwareUpdater(self._connector, imageFilePath, updateType, chunksSize, reboot, self.logger)
+ try:
+ updater.update()
+ except Exception as e:
+ self.logger.write_error(str(e) + "\n")
+ self.logger.write_error(_("Firmware update canceled!\n"))
+ self.firmawreUpadateIsRunning = False
+ self._Disconnect()
+ return
+ self._Disconnect()
+ self.firmawreUpadateIsRunning = False
+ return True
+
+
+
+class StdoutPseudoFile:
+ def __init__(self, port):
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.connect(('localhost', port))
+ self.Buffer = ""
+
+ def __del__(self):
+ self.socket.close()
+
+ def readline(self):
+ idx = self.Buffer.find("\n")
+ while idx == -1:
+ text = self.socket.recv(2048)
+ if text == "":
+ return ""
+ self.Buffer += text
+ idx = self.Buffer.find("\n")
+ if idx != -1:
+ line = self.Buffer[:idx + 1]
+ self.Buffer = self.Buffer[idx + 1:]
+ if BMZ_DBG:
+ print "command >" + line
+ return line
+ return ""
+
+ """ Base class for file like objects to facilitate StdOut for the Shell."""
+
+ def write(self, s, style=None):
+ if s != '':
+ self.socket.send(s.encode('utf8'))
+
+ def writeyield(self, s):
+ self.write(s)
+
+ def write_warning(self, s):
+ self.write(s)
+
+ def write_error(self, s):
+ self.write(s)
+
+ def flush(self):
+ pass
+
+ def isatty(self):
+ return False
+
+
+from threading import Thread, Timer, Semaphore, Lock
+import cmd
+
+build_lock = Lock()
+wx_eval_lock = Semaphore(0)
+eval_res = None
+
+
+def wx_evaluator(callable, *args, **kwargs):
+ global eval_res
+ eval_res = None
+ try:
+ eval_res = callable(*args, **kwargs)
+ finally:
+ wx_eval_lock.release()
+
+
+def evaluator(callable, *args, **kwargs):
+ global eval_res
+ wx.CallAfter(wx_evaluator, callable, *args, **kwargs)
+ wx_eval_lock.acquire()
+ return eval_res
+
+
+# Command log for debug, for viewing from wxInspector
+if BMZ_DBG:
+ __builtins__.cmdlog = []
+
+
+if __name__ == '__main__':
+
+ lpcmanager = LPCManagerLauncher()
+ lpcmanager.Start()