lpcmanager

Added LPCLibrary.py
revamp
2018-02-05, Edouard Tisserant
37a540b68d3b
Added LPCLibrary.py
#!/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()