andrej@1511: #!/usr/bin/env python
andrej@1511: # -*- coding: utf-8 -*-
andrej@1511:
andrej@1511: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1511: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1511: #
andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
andrej@1680: # Copyright (C) 2017: Andrey Skvortsov
andrej@1511: #
andrej@1511: # See COPYING file for copyrights details.
andrej@1511: #
andrej@1511: # This program is free software; you can redistribute it and/or
andrej@1511: # modify it under the terms of the GNU General Public License
andrej@1511: # as published by the Free Software Foundation; either version 2
andrej@1511: # of the License, or (at your option) any later version.
andrej@1511: #
andrej@1511: # This program is distributed in the hope that it will be useful,
andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1511: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrej@1511: # GNU General Public License for more details.
andrej@1511: #
andrej@1511: # You should have received a copy of the GNU General Public License
andrej@1511: # along with this program; if not, write to the Free Software
andrej@1511: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
andrej@1511:
etisserant@14: """
Edouard@1407: Beremiz Project Controller
etisserant@14: """
andrej@1881:
edouard@3810: import sys
andrej@1732: import os
andrej@1732: import traceback
laurent@411: import time
andrej@1834: from time import localtime
etisserant@14: import shutil
andrej@1732: import re
andrej@1732: import tempfile
Edouard@3376: import hashlib
edouard@3833: import shutil
etisserant@20: from datetime import datetime
Edouard@725: from weakref import WeakKeyDictionary
andrej@2456: from functools import reduce
edouard@3879: from collections import OrderedDict
kinsamanka@3750:
andrej@1832: import wx
andrej@1832:
andrej@1832: import features
Edouard@725: import connectors
edouard@4042: import util.paths as pathutils
laurent@815: from util.misc import CheckPathPerm, GetClassImporter
Edouard@742: from util.MiniTextControler import MiniTextControler
Edouard@726: from util.ProcessLogger import ProcessLogger
Laurent@814: from util.BitmapLibrary import GetBitmap
Laurent@814: from editors.FileManagementPanel import FileManagementPanel
Laurent@814: from editors.ProjectNodeEditor import ProjectNodeEditor
laurent@815: from editors.IECCodeViewer import IECCodeViewer
Laurent@1363: from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
Edouard@2337: from dialogs import UriEditor, IDManager
laurent@738: from PLCControler import PLCControler
Edouard@725: from plcopen.structures import IEC_KEYWORDS
Edouard@1948: from plcopen.types_enums import ComputeConfigurationResourceName, ITEM_CONFNODE
andrej@1834: import targets
edouard@3887: from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer, ValueToIECBytes
andrej@2416: from runtime import PlcStatus
Edouard@2645: from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
Edouard@2645: from POULibrary import UserAddressedException
Edouard@725:
etisserant@20:
Edouard@2248: MATIEC_ERROR_MODEL = re.compile(
andrej@2439: r".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
lbessard@356:
laurent@738:
laurent@1282: def ExtractChildrenTypesFromCatalog(catalog):
laurent@1282: children_types = []
edouard@3874: for name, displayname, _helpstr, moduleclassname in catalog:
edouard@3874: if isinstance(moduleclassname, list):
edouard@3874: children_types.extend(ExtractChildrenTypesFromCatalog(moduleclassname))
laurent@1282: else:
edouard@3874: children_types.append((name, GetClassImporter(moduleclassname), displayname))
laurent@1282: return children_types
laurent@1282:
andrej@1736:
laurent@1282: def ExtractMenuItemsFromCatalog(catalog):
laurent@1282: menu_items = []
edouard@3874: for name, displayname, helpstr, moduleclassname in catalog:
edouard@3874: if isinstance(moduleclassname, list):
edouard@3874: children = ExtractMenuItemsFromCatalog(moduleclassname)
laurent@1282: else:
laurent@1282: children = []
edouard@3874: menu_items.append((name, displayname, helpstr, children))
laurent@1282: return menu_items
laurent@1282:
andrej@1736:
laurent@1282: def GetAddMenuItems():
laurent@1282: return ExtractMenuItemsFromCatalog(features.catalog)
laurent@1282:
andrej@1736:
andrej@1831: class Iec2CSettings(object):
Edouard@2248:
edouard@2729: def __init__(self, controler):
andrej@1685: self.iec2c = None
andrej@1630: self.iec2c_buildopts = None
andrej@1754: self.ieclib_path = self.findLibPath()
andrej@1685: self.ieclib_c_path = self.findLibCPath()
edouard@2729: self.controler = controler
andrej@1685:
andrej@1685: def findObject(self, paths, test):
andrej@1742: path = None
andrej@1685: for p in paths:
andrej@1685: if test(p):
andrej@1685: path = p
andrej@1730: break
andrej@1685: return path
andrej@1730:
andrej@1685: def findCmd(self):
kinsamanka@3780: cmd = "iec2c" + (".exe" if os.name == 'nt' else "")
andrej@1742: paths = [
edouard@4042: pathutils.ThirdPartyPath("matiec")
andrej@1685: ]
Edouard@2248: path = self.findObject(
Edouard@2248: paths, lambda p: os.path.isfile(os.path.join(p, cmd)))
andrej@1685:
andrej@1685: # otherwise use iec2c from PATH
andrej@1685: if path is not None:
andrej@1742: cmd = os.path.join(path, cmd)
andrej@1685:
andrej@1685: return cmd
andrej@1730:
andrej@1685: def findLibPath(self):
andrej@1742: paths = [
edouard@4042: pathutils.ThirdPartyPath("matiec", "lib"),
andrej@1685: "/usr/lib/matiec"
andrej@1685: ]
Edouard@2248: path = self.findObject(
Edouard@2248: paths, lambda p: os.path.isfile(os.path.join(p, "ieclib.txt")))
andrej@1685: return path
andrej@1730:
andrej@1630: def findLibCPath(self):
andrej@1742: path = None
andrej@1789: if self.ieclib_path is not None:
andrej@1789: paths = [
andrej@1789: os.path.join(self.ieclib_path, "C"),
andrej@1789: self.ieclib_path]
andrej@1789: path = self.findObject(
andrej@1789: paths,
andrej@1789: lambda p: os.path.isfile(os.path.join(p, "iec_types.h")))
andrej@1630: return path
andrej@1630:
andrej@1630: def findSupportedOptions(self):
andrej@1734: buildcmd = "\"%s\" -h" % (self.getCmd())
andrej@1742: options = ["-f", "-l", "-p"]
andrej@1630:
andrej@1630: buildopt = ""
andrej@1630: try:
Edouard@2248: # Invoke compiler.
Edouard@2248: # Output files are listed to stdout, errors to stderr
edouard@2729: _status, result, _err_result = ProcessLogger(self.controler.logger, buildcmd,
andrej@1847: no_stdout=True,
andrej@1847: no_stderr=True).spin()
andrej@1846: except Exception:
edouard@2729: self.controler.logger.write_error(_("Couldn't launch IEC compiler to determine compatible options.\n"))
andrej@1630: return buildopt
andrej@1630:
andrej@1630: for opt in options:
andrej@1630: if opt in result:
andrej@1630: buildopt = buildopt + " " + opt
andrej@1630: return buildopt
andrej@1630:
andrej@1630: def getCmd(self):
andrej@1685: if self.iec2c is None:
andrej@1685: self.iec2c = self.findCmd()
andrej@1630: return self.iec2c
andrej@1630:
andrej@1630: def getOptions(self):
andrej@1630: if self.iec2c_buildopts is None:
andrej@1630: self.iec2c_buildopts = self.findSupportedOptions()
andrej@1630: return self.iec2c_buildopts
andrej@1630:
andrej@1630: def getLibPath(self):
andrej@1630: return self.ieclib_path
andrej@1630:
andrej@1630: def getLibCPath(self):
andrej@1630: if self.ieclib_c_path is None:
andrej@1630: self.ieclib_c_path = self.findLibCPath()
andrej@1630: return self.ieclib_c_path
andrej@1630:
andrej@1749:
andrej@1824: def GetProjectControllerXSD():
etisserant@13: XSD = """
etisserant@13:
etisserant@13:
etisserant@13:
lbessard@86:
lbessard@86:
lbessard@86:
laurent@411:
Edouard@2248: """ + targets.GetTargetChoices() + """
etisserant@106:
etisserant@106:
Edouard@2248: """ + (("""
Edouard@1413:
laurent@730:
Edouard@2248: """ + "\n".join([''
Edouard@2248: for libname, _lib, default in features.libraries]) + """
Edouard@1413:
andrej@1742: """) if len(features.libraries) > 0 else '') + """
lbessard@86:
greg@204:
Edouard@728:
etisserant@13:
etisserant@13:
etisserant@13:
etisserant@13: """
andrej@1824: return XSD
andrej@1824:
andrej@1824:
andrej@1824: class ProjectController(ConfigTreeNode, PLCControler):
Edouard@2248:
andrej@1824: """
andrej@1824: This class define Root object of the confnode tree.
andrej@1824: It is responsible of :
andrej@1824: - Managing project directory
andrej@1824: - Building project
andrej@1824: - Handling PLCOpenEditor controler and view
andrej@1824: - Loading user confnodes and instanciante them as children
andrej@1824: - ...
andrej@1824:
andrej@1824: """
Edouard@2248: # For root object, available Children Types are modules of the confnode
Edouard@2248: # packages.
andrej@1824: CTNChildrenTypes = ExtractChildrenTypesFromCatalog(features.catalog)
andrej@1824: XSD = GetProjectControllerXSD()
laurent@738: EditorType = ProjectNodeEditor
andrej@1789: iec2c_cfg = None
Edouard@1407:
etisserant@290: def __init__(self, frame, logger):
lbessard@41: PLCControler.__init__(self)
andrej@1836: ConfigTreeNode.__init__(self)
etisserant@227:
andrej@1789: if ProjectController.iec2c_cfg is None:
edouard@2729: ProjectController.iec2c_cfg = Iec2CSettings(self)
andrej@1789:
etisserant@20: self.MandatoryParams = None
etisserant@203: self._builder = None
etisserant@203: self._connector = None
Laurent@1363: self.DispatchDebugValuesTimer = None
Laurent@1363: self.DebugValuesBuffers = []
Laurent@1363: self.DebugTicks = []
Laurent@978: self.SetAppFrame(frame, logger)
Edouard@1407:
etisserant@203: # Setup debug information
etisserant@227: self.IECdebug_datas = {}
etisserant@222:
Edouard@2708: self.DebugUpdatePending = False
etisserant@203: self.ResetIECProgramsAndVariables()
etisserant@203:
etisserant@118: # In both new or load scenario, no need to save
greg@350: self.ChangesToSave = False
etisserant@23: # root have no parent
Edouard@718: self.CTNParent = None
Edouard@717: # Keep track of the confnode type name
Edouard@718: self.CTNType = "Beremiz"
Edouard@718: self.Children = {}
laurent@738: self._View = None
Edouard@717: # After __init__ root confnode is not valid
etisserant@20: self.ProjectPath = None
greg@427: self._setBuildPath(None)
etisserant@286: self.debug_break = False
greg@350: self.previous_plcstate = None
Edouard@2263: # copy StatusMethods so that it can be later customized
laurent@754: self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
Edouard@2485: self.DebugToken = None
Edouard@3395: self.LastComplainDebugToken = None
Edouard@2485: self.debug_status = PlcStatus.Stopped
Edouard@728:
Edouard@3376: self.IECcodeDigest = None
Edouard@3376: self.LastBuiltIECcodeDigest = None
Edouard@3376:
Edouard@728: def LoadLibraries(self):
edouard@3879: self.Libraries = OrderedDict()
andrej@1742: TypeStack = []
edouard@3874: for libname, clsname, default in features.libraries:
edouard@3874: lib_enabled = False if type(default)==str else default
Edouard@2462: if self.BeremizRoot.Libraries is not None:
Edouard@2462: enable_attr = getattr(self.BeremizRoot.Libraries,
Edouard@2462: "Enable_" + libname + "_Library")
Edouard@2462: if enable_attr is not None:
Edouard@2462: lib_enabled = enable_attr
Edouard@2462:
Edouard@2462: if lib_enabled:
Edouard@732: Lib = GetClassImporter(clsname)()(self, libname, TypeStack)
Edouard@728: TypeStack.append(Lib.GetTypes())
edouard@3879: self.Libraries[libname] = Lib
Edouard@1407:
edouard@3874: def CTNAddChild(self, CTNName, CTNType, IEC_Channel=0):
edouard@3874: """
edouard@3874: Project controller applies libraries requirements when adding new CTN
edouard@3874: """
edouard@3874: res = ConfigTreeNode.CTNAddChild(self, CTNName, CTNType, IEC_Channel)
edouard@3874:
edouard@3874: # find library associated with new CTN, if any
edouard@3874: associated_lib = {default:libname
edouard@3874: for libname, _clsname, default
edouard@3874: in features.libraries}.get(CTNType, None)
edouard@3874:
edouard@3874: # if any, then enable it if it wasn't and inform user
edouard@3874: if associated_lib is not None:
edouard@3874: # FIXME: This should be done with GetParamsAttribute
edouard@3874: # but it fails with missing optional attributes
edouard@3874: attrname = "Enable_" + associated_lib + "_Library"
edouard@3874: libobj = self.BeremizRoot.Libraries
edouard@3874: lib_enabled = False if libobj is None else getattr(libobj, attrname)
edouard@3874: if not lib_enabled:
edouard@3874: # use SetParamsAttribute to trigger reload of libs
edouard@3874: self.SetParamsAttribute("BeremizRoot.Libraries.Enable_" + associated_lib + "_Library", True)
edouard@3874: msg = _("Enabled {a1} library, required by {a2} extension\n").format(
edouard@3874: a1=associated_lib, a2=CTNType)
edouard@3874: self.GetCTRoot().logger.write(msg)
edouard@3874:
edouard@3874: return res
edouard@3874:
laurent@417: def SetAppFrame(self, frame, logger):
laurent@417: self.AppFrame = frame
laurent@417: self.logger = logger
laurent@417: self.StatusTimer = None
Laurent@1363: if self.DispatchDebugValuesTimer is not None:
Laurent@1363: self.DispatchDebugValuesTimer.Stop()
Laurent@1363: self.DispatchDebugValuesTimer = None
Edouard@1407:
laurent@417: if frame is not None:
Edouard@1407:
laurent@417: # Timer to pull PLC status
Laurent@1363: self.StatusTimer = wx.Timer(self.AppFrame, -1)
Edouard@1407: self.AppFrame.Bind(wx.EVT_TIMER,
andrej@1768: self.PullPLCStatusProc,
andrej@1768: self.StatusTimer)
Edouard@1395:
Edouard@1395: if self._connector is not None:
Edouard@1395: frame.LogViewer.SetLogSource(self._connector)
Edouard@1395: self.StatusTimer.Start(milliseconds=500, oneShot=False)
Edouard@1407:
Laurent@1363: # Timer to dispatch debug values to consumers
Laurent@1363: self.DispatchDebugValuesTimer = wx.Timer(self.AppFrame, -1)
Edouard@1407: self.AppFrame.Bind(wx.EVT_TIMER,
andrej@1768: self.DispatchDebugValuesProc,
andrej@1768: self.DispatchDebugValuesTimer)
Edouard@1407:
Edouard@717: self.RefreshConfNodesBlockLists()
laurent@417:
laurent@417: def ResetAppFrame(self, logger):
laurent@417: if self.AppFrame is not None:
laurent@417: self.AppFrame.Unbind(wx.EVT_TIMER, self.StatusTimer)
laurent@417: self.StatusTimer = None
laurent@417: self.AppFrame = None
surkovsv93@1793: self.KillDebugThread()
laurent@417: self.logger = logger
laurent@417:
laurent@738: def CTNName(self):
laurent@738: return "Project"
laurent@738:
Edouard@718: def CTNTestModified(self):
andrej@1757: return self.ChangesToSave or not self.ProjectIsSaved()
etisserant@118:
Edouard@718: def CTNFullName(self):
laurent@656: return ""
laurent@656:
Edouard@718: def GetCTRoot(self):
etisserant@23: return self
etisserant@23:
greg@418: def GetIECLibPath(self):
andrej@1789: return self.iec2c_cfg.getLibCPath()
Edouard@1407:
greg@418: def GetIEC2cPath(self):
andrej@1789: return self.iec2c_cfg.getCmd()
Edouard@1407:
etisserant@23: def GetCurrentLocation(self):
etisserant@23: return ()
etisserant@47:
etisserant@47: def GetCurrentName(self):
etisserant@47: return ""
Edouard@1407:
etisserant@47: def _GetCurrentName(self):
etisserant@47: return ""
etisserant@47:
lbessard@17: def GetProjectPath(self):
lbessard@17: return self.ProjectPath
etisserant@51:
etisserant@51: def GetProjectName(self):
etisserant@51: return os.path.split(self.ProjectPath)[1]
Edouard@1407:
laurent@781: def GetIconName(self):
laurent@781: return "PROJECT"
Edouard@1407:
laurent@510: def GetDefaultTargetName(self):
edouard@3810: if sys.platform.startswith('linux'):
edouard@3810: return "Linux"
edouard@3810: elif sys.platform.startswith('darwin'):
edouard@3810: return "OSX"
edouard@3810: elif sys.platform.startswith('win32'):
laurent@510: return "Win32"
edouard@3810:
edouard@3810: # Fall back to Linux as default target
edouard@3810: return "Linux"
edouard@3810:
laurent@510:
laurent@510: def GetTarget(self):
laurent@510: target = self.BeremizRoot.getTargetType()
laurent@510: if target.getcontent() is None:
Laurent@1315: temp_root = self.Parser.CreateRoot()
Laurent@1315: target = self.Parser.CreateElement("TargetType", "BeremizRoot")
Laurent@1315: temp_root.setTargetType(target)
laurent@510: target_name = self.GetDefaultTargetName()
Edouard@2248: target.setcontent(
Edouard@2248: self.Parser.CreateElement(target_name, "TargetType"))
laurent@411: return target
Edouard@1407:
andrej@1744: def GetParamsAttributes(self, path=None):
Edouard@717: params = ConfigTreeNode.GetParamsAttributes(self, path)
laurent@411: if params[0]["name"] == "BeremizRoot":
laurent@411: for child in params[0]["children"]:
laurent@411: if child["name"] == "TargetType" and child["value"] == '':
Edouard@2248: child.update(
Edouard@2248: self.GetTarget().getElementInfos("TargetType"))
laurent@411: return params
Edouard@1407:
laurent@411: def SetParamsAttribute(self, path, value):
laurent@411: if path.startswith("BeremizRoot.TargetType.") and self.BeremizRoot.getTargetType().getcontent() is None:
laurent@607: self.BeremizRoot.setTargetType(self.GetTarget())
Laurent@1080: res = ConfigTreeNode.SetParamsAttribute(self, path, value)
Laurent@1080: if path.startswith("BeremizRoot.Libraries."):
Laurent@1080: wx.CallAfter(self.RefreshConfNodesBlockLists)
Laurent@1080: return res
Edouard@1407:
greg@427: # helper func to check project path write permission
edouard@3318: def CheckProjectPathPerm(self):
greg@427: if CheckPathPerm(self.ProjectPath):
greg@427: return True
Laurent@1080: if self.AppFrame is not None:
andrej@1768: dialog = wx.MessageDialog(
andrej@1768: self.AppFrame,
andrej@1768: _('You must have permission to work on the project\nWork on a project copy ?'),
andrej@1768: _('Error'),
andrej@1768: wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
Laurent@1080: answer = dialog.ShowModal()
Laurent@1080: dialog.Destroy()
Laurent@1080: if answer == wx.ID_YES:
Laurent@1080: if self.SaveProjectAs():
Laurent@1080: self.AppFrame.RefreshTitle()
Laurent@1080: self.AppFrame.RefreshFileMenu()
Laurent@1080: self.AppFrame.RefreshPageTitles()
Laurent@1080: return True
greg@427: return False
Edouard@1407:
Laurent@1061: def _getProjectFilesPath(self, project_path=None):
Laurent@1061: if project_path is not None:
Laurent@1061: return os.path.join(project_path, "project_files")
Edouard@2248: projectfiles_path = os.path.join(
Edouard@2248: self.GetProjectPath(), "project_files")
laurent@757: if not os.path.exists(projectfiles_path):
laurent@757: os.mkdir(projectfiles_path)
laurent@757: return projectfiles_path
Edouard@1407:
Laurent@839: def AddProjectDefaultConfiguration(self, config_name="config", res_name="resource1"):
Laurent@839: self.ProjectAddConfiguration(config_name)
Laurent@839: self.ProjectAddConfigurationResource(config_name, res_name)
Laurent@839:
surkovsv93@1708: def SetProjectDefaultConfiguration(self):
surkovsv93@1708: # Sets default task and instance for new project
Edouard@2248: config = self.Project.getconfiguration(
Edouard@2248: self.GetProjectMainConfigurationName())
surkovsv93@1708: resource = config.getresource()[0].getname()
surkovsv93@1708: config = config.getname()
Edouard@1948: resource_tagname = ComputeConfigurationResourceName(config, resource)
surkovsv93@1708: def_task = [
surkovsv93@1708: {'Priority': '0', 'Single': '', 'Interval': 'T#20ms', 'Name': 'task0', 'Triggering': 'Cyclic'}]
surkovsv93@1708: def_instance = [
surkovsv93@1708: {'Task': def_task[0].get('Name'), 'Type': self.GetProjectPouNames()[0], 'Name': 'instance0'}]
surkovsv93@1708: self.SetEditedResourceInfos(resource_tagname, def_task, def_instance)
surkovsv93@1708:
greg@256: def NewProject(self, ProjectPath, BuildPath=None):
lbessard@17: """
lbessard@17: Create a new project in an empty folder
lbessard@17: @param ProjectPath: path of the folder where project have to be created
lbessard@17: @param PLCParams: properties of the PLCOpen program created
lbessard@17: """
laurent@415: # Verify that chosen folder is empty
lbessard@17: if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0:
laurent@415: return _("Chosen folder isn't empty. You can't use it for a new project!")
Edouard@1407:
lbessard@41: # Create PLCOpen program
laurent@738: self.CreateNewProject(
laurent@738: {"projectName": _("Unnamed"),
laurent@738: "productName": _("Unnamed"),
laurent@801: "productVersion": "1",
laurent@738: "companyName": _("Unknown"),
laurent@738: "creationDateTime": datetime(*localtime()[:6])})
Laurent@839: self.AddProjectDefaultConfiguration()
Edouard@1407:
etisserant@13: # Change XSD into class members
etisserant@13: self._AddParamsMembers()
Edouard@718: self.Children = {}
Edouard@717: # Keep track of the root confnode (i.e. project path)
lbessard@17: self.ProjectPath = ProjectPath
greg@427: self._setBuildPath(BuildPath)
Edouard@717: # get confnodes bloclist (is that usefull at project creation?)
Edouard@717: self.RefreshConfNodesBlockLists()
edouard@3969: # default scaling
edouard@3969: for iec_lang in ["FBD", "LD", "SFC"]:
edouard@3969: PLCControler.SetProjectProperties(self, properties={"scaling": {iec_lang: (8, 8)}})
etisserant@114: # this will create files base XML files
etisserant@114: self.SaveProject()
lbessard@17: return None
Edouard@1407:
greg@256: def LoadProject(self, ProjectPath, BuildPath=None):
lbessard@17: """
lbessard@17: Load a project contained in a folder
lbessard@17: @param ProjectPath: path of the project folder
lbessard@17: """
lbessard@190: if os.path.basename(ProjectPath) == "":
lbessard@190: ProjectPath = os.path.dirname(ProjectPath)
Laurent@978: # Verify that project contains a PLCOpen program
lbessard@17: plc_file = os.path.join(ProjectPath, "plc.xml")
lbessard@17: if not os.path.isfile(plc_file):
surkovsv93@1602: return _("Chosen folder doesn't contain a program. It's not a valid project!"), True
lbessard@17: # Load PLCOpen file
Laurent@1330: error = self.OpenXMLFile(plc_file)
Laurent@1330: if error is not None:
Laurent@1330: if self.Project is not None:
andrej@1581: (fname_err, lnum, src) = (("PLC",) + error)
Edouard@2248: self.logger.write_warning(
Edouard@2248: XSDSchemaErrorMessage.format(a1=fname_err, a2=lnum, a3=src))
Laurent@1330: else:
surkovsv93@1602: return error, False
Laurent@839: if len(self.GetProjectConfigNames()) == 0:
Laurent@839: self.AddProjectDefaultConfiguration()
lbessard@17: # Change XSD into class members
lbessard@17: self._AddParamsMembers()
Edouard@718: self.Children = {}
Edouard@717: # Keep track of the root confnode (i.e. project path)
lbessard@17: self.ProjectPath = ProjectPath
greg@427: self._setBuildPath(BuildPath)
etisserant@13: # If dir have already be made, and file exist
Edouard@718: if os.path.isdir(self.CTNPath()) and os.path.isfile(self.ConfNodeXmlFilePath()):
andrej@1733: # Load the confnode.xml file into parameters members
etisserant@203: result = self.LoadXMLParams()
lbessard@17: if result:
surkovsv93@1602: return result, False
andrej@1733: # Load and init all the children
Edouard@718: self.LoadChildren()
Edouard@717: self.RefreshConfNodesBlockLists()
andrej@1555: self.UpdateButtons()
surkovsv93@1602: return None, False
Edouard@1407:
laurent@738: def RecursiveConfNodeInfos(self, confnode):
laurent@738: values = []
laurent@738: for CTNChild in confnode.IECSortedChildren():
laurent@738: values.append(
laurent@738: {"name": "%s: %s" % (CTNChild.GetFullIEC_Channel(),
Edouard@1407: CTNChild.CTNName()),
Laurent@1105: "tagname": CTNChild.CTNFullName(),
Edouard@1407: "type": ITEM_CONFNODE,
laurent@738: "confnode": CTNChild,
laurent@781: "icon": CTNChild.GetIconName(),
laurent@738: "values": self.RecursiveConfNodeInfos(CTNChild)})
laurent@738: return values
Edouard@1407:
laurent@738: def GetProjectInfos(self):
laurent@738: infos = PLCControler.GetProjectInfos(self)
laurent@738: configurations = infos["values"].pop(-1)
laurent@738: resources = None
laurent@738: for config_infos in configurations["values"]:
laurent@738: if resources is None:
laurent@738: resources = config_infos["values"][0]
laurent@738: else:
laurent@738: resources["values"].extend(config_infos["values"][0]["values"])
laurent@738: if resources is not None:
laurent@738: infos["values"].append(resources)
laurent@738: infos["values"].extend(self.RecursiveConfNodeInfos(self))
laurent@738: return infos
Edouard@1407:
laurent@403: def CloseProject(self):
Edouard@718: self.ClearChildren()
laurent@417: self.ResetAppFrame(None)
Edouard@1407:
surkovsv93@1606: def CheckNewProjectPath(self, old_project_path, new_project_path):
surkovsv93@1606: if old_project_path == new_project_path:
surkovsv93@1606: message = (_("Save path is the same as path of a project! \n"))
Edouard@2248: dialog = wx.MessageDialog(
Edouard@2248: self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR)
surkovsv93@1606: dialog.ShowModal()
surkovsv93@1606: return False
surkovsv93@1606: else:
andrej@2505: if not CheckPathPerm(new_project_path):
andrej@2505: dialog = wx.MessageDialog(
andrej@2505: self.AppFrame,
andrej@2505: _('No write permissions in selected directory! \n'),
andrej@2505: _("Error"), wx.OK | wx.ICON_ERROR)
andrej@2505: dialog.ShowModal()
andrej@2505: return False
andrej@2505: if not os.path.isdir(new_project_path) or len(os.listdir(new_project_path)) > 0:
andrej@2505: plc_file = os.path.join(new_project_path, "plc.xml")
andrej@2505: if os.path.isfile(plc_file):
andrej@2505: message = _("Selected directory already contains another project. Overwrite? \n")
andrej@2505: else:
andrej@2505: message = _("Selected directory isn't empty. Continue? \n")
Edouard@2248: dialog = wx.MessageDialog(
Edouard@2248: self.AppFrame, message, _("Error"), wx.YES_NO | wx.ICON_ERROR)
surkovsv93@1606: answer = dialog.ShowModal()
surkovsv93@1606: return answer == wx.ID_YES
surkovsv93@1606: return True
surkovsv93@1606:
Laurent@1061: def SaveProject(self, from_project_path=None):
edouard@3318: if self.CheckProjectPathPerm():
Laurent@1061: if from_project_path is not None:
Edouard@2248: old_projectfiles_path = self._getProjectFilesPath(
Edouard@2248: from_project_path)
Laurent@1061: if os.path.isdir(old_projectfiles_path):
edouard@3833: shutil.copytree(old_projectfiles_path,
andrej@2506: self._getProjectFilesPath(self.ProjectPath))
lbessard@41: self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
Laurent@1061: result = self.CTNRequestSave(from_project_path)
greg@427: if result:
greg@427: self.logger.write_error(result)
Edouard@1407:
Laurent@1061: def SaveProjectAs(self):
greg@427: # Ask user to choose a path with write permissions
laurent@529: if wx.Platform == '__WXMSW__':
laurent@529: path = os.getenv("USERPROFILE")
laurent@529: else:
laurent@529: path = os.getenv("HOME")
Edouard@2248: dirdialog = wx.DirDialog(
edouard@3480: self.AppFrame, _("Create or choose an empty directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
greg@427: answer = dirdialog.ShowModal()
edouard@3480: newprojectpath = dirdialog.GetPath()
greg@427: dirdialog.Destroy()
greg@427: if answer == wx.ID_OK:
greg@427: if os.path.isdir(newprojectpath):
surkovsv93@1606: if self.CheckNewProjectPath(self.ProjectPath, newprojectpath):
surkovsv93@1606: self.ProjectPath, old_project_path = newprojectpath, self.ProjectPath
surkovsv93@1606: self.SaveProject(old_project_path)
surkovsv93@1606: self._setBuildPath(self.BuildPath)
greg@427: return True
greg@427: return False
Edouard@728:
Edouard@728: def GetLibrariesTypes(self):
Edouard@728: self.LoadLibraries()
edouard@3879: return [lib.GetTypes() for lib in self.Libraries.values()]
Edouard@728:
Edouard@728: def GetLibrariesSTCode(self):
edouard@3879: return "\n".join([lib.GetSTCode() for lib in self.Libraries.values()])
Edouard@728:
Edouard@728: def GetLibrariesCCode(self, buildpath):
andrej@1742: if len(self.Libraries) == 0:
andrej@1740: return [], [], ()
Edouard@728: self.GetIECProgramsAndVariables()
Edouard@2248: LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath(
Edouard@2248: self.GetIECLibPath())
andrej@1742: LocatedCCodeAndFlags = []
andrej@1742: Extras = []
edouard@3879: for lib in self.Libraries.values():
andrej@1742: res = lib.Generate_C(buildpath, self._VariablesList, LibIECCflags)
Edouard@728: LocatedCCodeAndFlags.append(res[:2])
andrej@1742: if len(res) > 2:
Laurent@869: Extras.extend(res[2:])
kinsamanka@3750: return list(map(list, list(zip(*LocatedCCodeAndFlags)))) + [tuple(Extras)]
Edouard@1407:
Edouard@717: # Update PLCOpenEditor ConfNode Block types from loaded confnodes
Edouard@717: def RefreshConfNodesBlockLists(self):
Edouard@718: if getattr(self, "Children", None) is not None:
Edouard@717: self.ClearConfNodeTypes()
Edouard@728: self.AddConfNodeTypesList(self.GetLibrariesTypes())
laurent@395: if self.AppFrame is not None:
laurent@716: self.AppFrame.RefreshLibraryPanel()
laurent@395: self.AppFrame.RefreshEditor()
Edouard@1407:
laurent@443: # Update a PLCOpenEditor Pou variable location
laurent@443: def UpdateProjectVariableLocation(self, old_leading, new_leading):
laurent@443: self.Project.updateElementAddress(old_leading, new_leading)
laurent@443: self.BufferProject()
laurent@443: if self.AppFrame is not None:
laurent@468: self.AppFrame.RefreshTitle()
laurent@730: self.AppFrame.RefreshPouInstanceVariablesPanel()
laurent@468: self.AppFrame.RefreshFileMenu()
laurent@468: self.AppFrame.RefreshEditMenu()
Laurent@842: wx.CallAfter(self.AppFrame.RefreshEditor)
Edouard@1407:
laurent@401: def GetVariableLocationTree(self):
laurent@411: '''
Edouard@717: This function is meant to be overridden by confnodes.
laurent@411:
laurent@411: It should returns an list of dictionaries
Edouard@1407:
laurent@411: - IEC_type is an IEC type like BOOL/BYTE/SINT/...
laurent@411: - location is a string of this variable's location, like "%IX0.0.0"
laurent@411: '''
laurent@411: children = []
Edouard@718: for child in self.IECSortedChildren():
laurent@411: children.append(child.GetVariableLocationTree())
laurent@411: return children
Edouard@1407:
Edouard@718: def CTNPath(self, CTNName=None):
etisserant@13: return self.ProjectPath
Edouard@1407:
Edouard@718: def ConfNodeXmlFilePath(self, CTNName=None):
Edouard@718: return os.path.join(self.CTNPath(CTNName), "beremiz.xml")
etisserant@18:
laurent@669: def ParentsTypesFactory(self):
Edouard@717: return self.ConfNodeTypesFactory()
laurent@363:
greg@427: def _setBuildPath(self, buildpath):
greg@427: self.BuildPath = buildpath
greg@427: self.DefaultBuildPath = None
greg@427: if self._builder is not None:
greg@427: self._builder.SetBuildPath(self._getBuildPath())
greg@427:
etisserant@20: def _getBuildPath(self):
greg@427: # BuildPath is defined by user
greg@427: if self.BuildPath is not None:
greg@427: return self.BuildPath
greg@427: # BuildPath isn't defined by user but already created by default
greg@427: if self.DefaultBuildPath is not None:
greg@427: return self.DefaultBuildPath
greg@427: # Create a build path in project folder if user has permissions
greg@427: if CheckPathPerm(self.ProjectPath):
greg@427: self.DefaultBuildPath = os.path.join(self.ProjectPath, "build")
greg@427: # Create a build path in temp folder
greg@427: else:
Edouard@2248: self.DefaultBuildPath = os.path.join(
Edouard@2248: tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build")
Edouard@1407:
greg@427: if not os.path.exists(self.DefaultBuildPath):
greg@427: os.makedirs(self.DefaultBuildPath)
greg@427: return self.DefaultBuildPath
Edouard@1407:
etisserant@203: def _getExtraFilesPath(self):
etisserant@203: return os.path.join(self._getBuildPath(), "extra_files")
etisserant@203:
etisserant@20: def _getIECcodepath(self):
etisserant@20: # define name for IEC code file
etisserant@20: return os.path.join(self._getBuildPath(), "plc.st")
Edouard@1407:
lbessard@65: def _getIECgeneratedcodepath(self):
lbessard@65: # define name for IEC generated code file
lbessard@65: return os.path.join(self._getBuildPath(), "generated_plc.st")
Edouard@1407:
lbessard@65: def _getIECrawcodepath(self):
lbessard@65: # define name for IEC raw code file
Edouard@718: return os.path.join(self.CTNPath(), "raw_plc.st")
Edouard@1407:
lbessard@97: def GetLocations(self):
lbessard@97: locations = []
andrej@1740: filepath = os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h")
lbessard@97: if os.path.isfile(filepath):
Edouard@2248: # IEC2C compiler generate a list of located variables :
Edouard@2248: # LOCATED_VARIABLES.h
Edouard@2248: location_file = open(
Edouard@2248: os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h"))
lbessard@97: # each line of LOCATED_VARIABLES.h declares a located variable
lbessard@97: lines = [line.strip() for line in location_file.readlines()]
lbessard@97: # This regular expression parses the lines genereated by IEC2C
Edouard@2248: LOCATED_MODEL = re.compile(
andrej@2439: r"__LOCATED_VAR\((?P[A-Z]*),(?P[_A-Za-z0-9]*),(?P[QMI])(?:,(?P[XBWDL]))?,(?P[,0-9]*)\)")
lbessard@97: for line in lines:
Edouard@1407: # If line match RE,
lbessard@97: result = LOCATED_MODEL.match(line)
lbessard@97: if result:
lbessard@97: # Get the resulting dict
lbessard@97: resdict = result.groupdict()
Edouard@2248: # rewrite string for variadic location as a tuple of
Edouard@2248: # integers
andrej@1740: resdict['LOC'] = tuple(map(int, resdict['LOC'].split(',')))
Edouard@1407: # set located size to 'X' if not given
lbessard@97: if not resdict['SIZE']:
lbessard@97: resdict['SIZE'] = 'X'
lbessard@97: # finally store into located variable list
lbessard@97: locations.append(resdict)
lbessard@97: return locations
Edouard@1407:
Laurent@883: def GetConfNodeGlobalInstances(self):
edouard@3266: LibGlobals = []
edouard@3879: for lib in self.Libraries.values():
edouard@3266: LibGlobals += lib.GlobalInstances()
edouard@3266: CTNGlobals = self._GlobalInstances()
edouard@3266: return LibGlobals + CTNGlobals
Edouard@1407:
etisserant@203: def _Generate_SoftPLC(self):
Edouard@1407: if self._Generate_PLC_ST():
Edouard@1407: return self._Compile_ST_to_SoftPLC()
Edouard@1407: return False
Edouard@1407:
Edouard@1407: def _Generate_PLC_ST(self):
etisserant@20: """
lbessard@64: Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C
etisserant@20: @param buildpath: path where files should be created
etisserant@20: """
etisserant@20:
Edouard@717: # Update PLCOpenEditor ConfNode Block types before generate ST code
Edouard@717: self.RefreshConfNodesBlockLists()
Edouard@1407:
Edouard@2248: self.logger.write(
Edouard@2248: _("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
etisserant@20: # ask PLCOpenEditor controller to write ST/IL/SFC code file
Edouard@2248: _program, errors, warnings = self.GenerateProgram(
Edouard@2248: self._getIECgeneratedcodepath())
lbessard@309: if len(warnings) > 0:
Edouard@2248: self.logger.write_warning(
Edouard@2248: _("Warnings in ST/IL/SFC code generator :\n"))
lbessard@309: for warning in warnings:
andrej@1734: self.logger.write_warning("%s\n" % warning)
lbessard@309: if len(errors) > 0:
etisserant@20: # Failed !
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Error in ST/IL/SFC code generator :\n%s\n") % errors[0])
etisserant@20: return False
Edouard@3376:
Edouard@717: # Add ST Library from confnodes
Edouard@3376: IECCodeContent = self.GetLibrariesSTCode()
Edouard@3376:
Edouard@3376: IECrawcodepath = self._getIECrawcodepath()
Edouard@3376: if os.path.isfile(IECrawcodepath):
Edouard@3376: IECCodeContent += open(IECrawcodepath, "r").read() + "\n"
Edouard@3376:
Edouard@3376:
Edouard@3376: # Compute offset before ST resulting of transformation from user POUs
Edouard@3376: self.ProgramOffset = IECCodeContent.count("\n")
Edouard@3376:
Edouard@3421: POUsIECCodeContent = open(self._getIECgeneratedcodepath(), "r").read()
Edouard@3376:
edouard@3380: IECcodepath = self._getIECcodepath()
edouard@3380:
edouard@3380: if not os.path.exists(IECcodepath):
edouard@3380: self.LastBuiltIECcodeDigest = None
edouard@3380:
edouard@3380: with open(IECcodepath, "w") as plc_file:
Edouard@3376: plc_file.write(IECCodeContent)
Edouard@3421: plc_file.write(POUsIECCodeContent)
Edouard@3376:
Edouard@3376: hasher = hashlib.md5()
kinsamanka@3755: hasher.update(IECCodeContent.encode())
kinsamanka@3755: hasher.update(POUsIECCodeContent.encode())
Edouard@3376: self.IECcodeDigest = hasher.hexdigest()
Edouard@3376:
Edouard@1407: return True
Edouard@1407:
Edouard@1407: def _Compile_ST_to_SoftPLC(self):
andrej@1789: iec2c_libpath = self.iec2c_cfg.getLibPath()
andrej@1789: if iec2c_libpath is None:
andrej@1789: self.logger.write_error(_("matiec installation is not found\n"))
andrej@1789: return False
andrej@1789:
Edouard@3376: if self.LastBuiltIECcodeDigest == self.IECcodeDigest:
Edouard@3376: self.logger.write(_("IEC program did no change, not re-compiling into C code.\n"))
Edouard@3376: return True
Edouard@3376:
laurent@415: self.logger.write(_("Compiling IEC Program into C code...\n"))
Edouard@1407: buildpath = self._getBuildPath()
andrej@1734: buildcmd = "\"%s\" %s -I \"%s\" -T \"%s\" \"%s\"" % (
andrej@1878: self.iec2c_cfg.getCmd(),
andrej@1878: self.iec2c_cfg.getOptions(),
andrej@1878: iec2c_libpath,
andrej@1878: buildpath,
andrej@1878: self._getIECcodepath())
Edouard@1451:
Edouard@1451: try:
Edouard@2248: # Invoke compiler.
Edouard@2248: # Output files are listed to stdout, errors to stderr
Edouard@1451: status, result, err_result = ProcessLogger(self.logger, buildcmd,
Edouard@2248: no_stdout=True,
Edouard@2248: no_stderr=True).spin()
andrej@2418: except Exception as e:
Edouard@1451: self.logger.write_error(buildcmd + "\n")
Edouard@1451: self.logger.write_error(repr(e) + "\n")
Edouard@1451: return False
Edouard@1451:
etisserant@20: if status:
etisserant@20: # Failed !
Edouard@1407:
lbessard@356: # parse iec2c's error message. if it contains a line number,
lbessard@356: # then print those lines from the generated IEC file.
lbessard@356: for err_line in err_result.split('\n'):
lbessard@356: self.logger.write_warning(err_line + "\n")
lbessard@356:
lbessard@356: m_result = MATIEC_ERROR_MODEL.match(err_line)
lbessard@356: if m_result is not None:
andrej@1847: first_line, _first_column, last_line, _last_column, _error = m_result.groups()
lbessard@356: first_line, last_line = int(first_line), int(last_line)
Edouard@1407:
lbessard@356: last_section = None
lbessard@356: f = open(self._getIECcodepath())
lbessard@356:
lbessard@356: for i, line in enumerate(f.readlines()):
laurent@661: i = i + 1
lbessard@356: if line[0] not in '\t \r\n':
lbessard@356: last_section = line
lbessard@356:
lbessard@356: if first_line <= i <= last_line:
lbessard@356: if last_section is not None:
Edouard@2248: self.logger.write_warning(
Edouard@2248: "In section: " + last_section)
andrej@1737: last_section = None # only write section once
lbessard@356: self.logger.write_warning("%04d: %s" % (i, line))
lbessard@356:
lbessard@356: f.close()
Edouard@1407:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Error : IEC to C compiler returned %d\n") % status)
etisserant@20: return False
Edouard@1407:
etisserant@20: # Now extract C files of stdout
Edouard@2248: C_files = [fname for fname in result.splitlines() if fname[
Edouard@2248: -2:] == ".c" or fname[-2:] == ".C"]
etisserant@20: # remove those that are not to be compiled because included by others
etisserant@20: C_files.remove("POUS.c")
etisserant@115: if not C_files:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Error : At least one configuration and one resource must be declared in PLC !\n"))
etisserant@115: return False
etisserant@20: # transform those base names to full names with path
kinsamanka@3750: C_files = [os.path.join(buildpath, filename) for filename in C_files]
Edouard@1050:
Edouard@1050: # prepend beremiz include to configuration header
Edouard@2248: H_files = [fname for fname in result.splitlines() if fname[
Edouard@2248: -2:] == ".h" or fname[-2:] == ".H"]
Edouard@1050: H_files.remove("LOCATED_VARIABLES.h")
kinsamanka@3750: H_files = [os.path.join(buildpath, filename) for filename in H_files]
Edouard@1050: for H_file in H_files:
andrej@2442: with open(H_file, 'r') as original:
andrej@1756: data = original.read()
andrej@2442: with open(H_file, 'w') as modified:
andrej@1756: modified.write('#include "beremiz.h"\n' + data)
Edouard@1050:
laurent@361: self.logger.write(_("Extracting Located Variables...\n"))
Edouard@2248: # Keep track of generated located variables for later use by
Edouard@2248: # self._Generate_C
lbessard@97: self.PLCGeneratedLocatedVars = self.GetLocations()
Edouard@718: # Keep track of generated C files for later use by self.CTNGenerate_C
etisserant@18: self.PLCGeneratedCFiles = C_files
etisserant@49: # compute CFLAGS for plc
andrej@1789: self.plcCFLAGS = '"-I%s" -Wno-unused-function' % self.iec2c_cfg.getLibCPath()
Edouard@3376:
Edouard@3376: self.LastBuiltIECcodeDigest = self.IECcodeDigest
Edouard@3376:
etisserant@18: return True
etisserant@18:
etisserant@203: def GetBuilder(self):
etisserant@203: """
etisserant@203: Return a Builder (compile C code into machine code)
etisserant@203: """
etisserant@203: # Get target, module and class name
Laurent@1315: targetname = self.GetTarget().getcontent().getLocalTag()
Edouard@733: targetclass = targets.GetBuilder(targetname)
etisserant@203:
Edouard@1407: # if target already
andrej@1740: if self._builder is None or not isinstance(self._builder, targetclass):
etisserant@203: # Get classname instance
etisserant@203: self._builder = targetclass(self)
etisserant@203: return self._builder
etisserant@203:
Edouard@2248: #
etisserant@203: #
etisserant@203: # C CODE GENERATION METHODS
etisserant@203: #
Edouard@2248: #
Edouard@1407:
Edouard@718: def CTNGenerate_C(self, buildpath, locations):
etisserant@203: """
Edouard@1407: Return C code generated by iec2c compiler
etisserant@203: when _generate_softPLC have been called
etisserant@203: @param locations: ignored
etisserant@203: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
etisserant@203: """
etisserant@283:
Edouard@1407: return ([(C_file_name, self.plcCFLAGS)
andrej@1878: for C_file_name in self.PLCGeneratedCFiles],
andrej@1768: "", # no ldflags
andrej@1768: False) # do not expose retreive/publish calls
Edouard@1407:
etisserant@203: def ResetIECProgramsAndVariables(self):
etisserant@203: """
etisserant@203: Reset variable and program list that are parsed from
etisserant@203: CSV file generated by IEC2C compiler.
etisserant@203: """
etisserant@203: self._ProgramList = None
etisserant@203: self._VariablesList = None
Edouard@1432: self._DbgVariablesList = None
edouard@532: self._IECPathToIdx = {}
laurent@670: self._Ticktime = 0
etisserant@235: self.TracedIECPath = []
Edouard@1433: self.TracedIECTypes = []
etisserant@235:
etisserant@203: def GetIECProgramsAndVariables(self):
etisserant@203: """
etisserant@203: Parse CSV-like file VARIABLES.csv resulting from IEC2C compiler.
etisserant@203: Each section is marked with a line staring with '//'
etisserant@203: list of all variables used in various POUs
etisserant@203: """
etisserant@203: if self._ProgramList is None or self._VariablesList is None:
etisserant@203: try:
andrej@1740: csvfile = os.path.join(self._getBuildPath(), "VARIABLES.csv")
etisserant@203: # describes CSV columns
etisserant@203: ProgramsListAttributeName = ["num", "C_path", "type"]
Edouard@2248: VariablesListAttributeName = [
Edouard@3396: "num", "vartype", "IEC_path", "C_path", "type", "derived", "retain"]
etisserant@203: self._ProgramList = []
etisserant@203: self._VariablesList = []
Edouard@1432: self._DbgVariablesList = []
etisserant@203: self._IECPathToIdx = {}
Edouard@1407:
etisserant@203: # Separate sections
etisserant@203: ListGroup = []
andrej@2441: for line in open(csvfile, 'r').readlines():
etisserant@203: strippedline = line.strip()
etisserant@203: if strippedline.startswith("//"):
etisserant@203: # Start new section
etisserant@203: ListGroup.append([])
etisserant@203: elif len(strippedline) > 0 and len(ListGroup) > 0:
etisserant@203: # append to this section
etisserant@203: ListGroup[-1].append(strippedline)
Edouard@1407:
etisserant@203: # first section contains programs
etisserant@203: for line in ListGroup[0]:
etisserant@203: # Split and Maps each field to dictionnary entries
Edouard@2248: attrs = dict(
kinsamanka@3750: list(zip(ProgramsListAttributeName, line.strip().split(';'))))
andrej@1722: # Truncate "C_path" to remove conf an resources names
Edouard@2248: attrs["C_path"] = '__'.join(
Edouard@2248: attrs["C_path"].split(".", 2)[1:])
etisserant@203: # Push this dictionnary into result.
etisserant@203: self._ProgramList.append(attrs)
Edouard@1407:
etisserant@203: # second section contains all variables
Laurent@883: config_FBs = {}
Edouard@1432: Idx = 0
etisserant@203: for line in ListGroup[1]:
etisserant@203: # Split and Maps each field to dictionnary entries
Edouard@2248: attrs = dict(
kinsamanka@3750: list(zip(VariablesListAttributeName, line.strip().split(';'))))
andrej@1722: # Truncate "C_path" to remove conf an resources names
andrej@1740: parts = attrs["C_path"].split(".", 2)
laurent@639: if len(parts) > 2:
Laurent@883: config_FB = config_FBs.get(tuple(parts[:2]))
Laurent@883: if config_FB:
Laurent@883: parts = [config_FB] + parts[2:]
Laurent@883: attrs["C_path"] = '.'.join(parts)
Edouard@1407: else:
Laurent@883: attrs["C_path"] = '__'.join(parts[1:])
laurent@639: else:
laurent@639: attrs["C_path"] = '__'.join(parts)
Laurent@883: if attrs["vartype"] == "FB":
Laurent@883: config_FBs[tuple(parts)] = attrs["C_path"]
Paul@1517: if attrs["vartype"] != "FB" and attrs["type"] in DebugTypesSize:
Edouard@1432: # Push this dictionnary into result.
Edouard@1432: self._DbgVariablesList.append(attrs)
Edouard@1432: # Fill in IEC<->C translation dicts
andrej@1742: IEC_path = attrs["IEC_path"]
andrej@1742: self._IECPathToIdx[IEC_path] = (Idx, attrs["type"])
Edouard@1432: # Ignores numbers given in CSV file
Edouard@1432: # Idx=int(attrs["num"])
Edouard@1432: # Count variables only, ignore FBs
andrej@1742: Idx += 1
etisserant@203: self._VariablesList.append(attrs)
Edouard@1407:
laurent@670: # third section contains ticktime
laurent@670: if len(ListGroup) > 2:
Edouard@1407: self._Ticktime = int(ListGroup[2][0])
Edouard@1407:
andrej@1846: except Exception:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Cannot open/parse VARIABLES.csv!\n"))
etisserant@203: self.logger.write_error(traceback.format_exc())
etisserant@203: self.ResetIECProgramsAndVariables()
etisserant@203: return False
etisserant@203:
etisserant@203: return True
etisserant@203:
etisserant@203: def Generate_plc_debugger(self):
etisserant@203: """
etisserant@203: Generate trace/debug code out of PLC variable list
etisserant@203: """
etisserant@203: self.GetIECProgramsAndVariables()
etisserant@203:
etisserant@203: # prepare debug code
Edouard@1432: variable_decl_array = []
Edouard@3396: retain_indexes = []
Edouard@3396: for i, v in enumerate(self._DbgVariablesList):
Edouard@3396: variable_decl_array.append(
andrej@1742: "{&(%(C_path)s), " % v +
andrej@1740: {
andrej@1740: "EXT": "%(type)s_P_ENUM",
andrej@1740: "IN": "%(type)s_P_ENUM",
andrej@1740: "MEM": "%(type)s_O_ENUM",
andrej@1740: "OUT": "%(type)s_O_ENUM",
andrej@1740: "VAR": "%(type)s_ENUM"
andrej@1740: }[v["vartype"]] % v +
Edouard@3396: "}")
Edouard@3396:
Edouard@3396: if v["retain"] == "1":
Edouard@3396: retain_indexes.append("/* "+v["C_path"]+" */ "+str(i))
Edouard@3396:
Edouard@1430: debug_code = targets.GetCode("plc_debug.c") % {
andrej@1777: "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
andrej@1777: p for p in self._ProgramList]),
andrej@1777: "extern_variables_declarations": "\n".join([
andrej@1777: {
andrej@1777: "EXT": "extern __IEC_%(type)s_p %(C_path)s;",
andrej@1777: "IN": "extern __IEC_%(type)s_p %(C_path)s;",
andrej@1777: "MEM": "extern __IEC_%(type)s_p %(C_path)s;",
andrej@1777: "OUT": "extern __IEC_%(type)s_p %(C_path)s;",
andrej@1777: "VAR": "extern __IEC_%(type)s_t %(C_path)s;",
andrej@1777: "FB": "extern %(type)s %(C_path)s;"
andrej@1777: }[v["vartype"]] % v
andrej@1777: for v in self._VariablesList if v["C_path"].find('.') < 0]),
Edouard@2632: "variable_decl_array": ",\n".join(variable_decl_array),
Edouard@3396: "retain_vardsc_index_array": ",\n".join(retain_indexes),
Edouard@2632: "var_access_code": targets.GetCode("var_access.c")
andrej@1777: }
Edouard@1407:
etisserant@203: return debug_code
Edouard@1407:
Edouard@985: def Generate_plc_main(self):
etisserant@203: """
Edouard@717: Use confnodes layout given in LocationCFilesAndCFLAGS to
Edouard@717: generate glue code that dispatch calls to all confnodes
etisserant@203: """
etisserant@203: # filter location that are related to code that will be called
etisserant@203: # in retreive, publish, init, cleanup
kinsamanka@3750: locstrs = ["_".join(map(str, x)) for x in [loc for loc, _Cfiles, DoCalls in
kinsamanka@3750: self.LocationCFilesAndCFLAGS if loc and DoCalls]]
Edouard@1407:
etisserant@203: # Generate main, based on template
Edouard@728: if not self.BeremizRoot.getDisable_Extensions():
Edouard@1430: plc_main_code = targets.GetCode("plc_main_head.c") % {
andrej@1740: "calls_prototypes": "\n".join([(
andrej@1878: "int __init_%(s)s(int argc,char **argv);\n" +
andrej@1878: "void __cleanup_%(s)s(void);\n" +
andrej@1878: "void __retrieve_%(s)s(void);\n" +
andrej@1878: "void __publish_%(s)s(void);") % {'s': locstr} for locstr in locstrs]),
andrej@1740: "retrieve_calls": "\n ".join([
andrej@1878: "__retrieve_%s();" % locstr for locstr in locstrs]),
andrej@1740: "publish_calls": "\n ".join([ # Call publish in reverse order
kinsamanka@3750: "__publish_%s();" % locstrs[i - 1] for i in range(len(locstrs), 0, -1)]),
andrej@1740: "init_calls": "\n ".join([
Edouard@2248: "init_level=%d; " % (i + 1) +
andrej@1878: "if((res = __init_%s(argc,argv))){" % locstr +
andrej@1878: # "printf(\"%s\"); "%locstr + #for debug
andrej@1878: "return res;}" for i, locstr in enumerate(locstrs)]),
andrej@1740: "cleanup_calls": "\n ".join([
andrej@1878: "if(init_level >= %d) " % i +
kinsamanka@3750: "__cleanup_%s();" % locstrs[i - 1] for i in range(len(locstrs), 0, -1)])
Edouard@2248: }
greg@338: else:
Edouard@1430: plc_main_code = targets.GetCode("plc_main_head.c") % {
andrej@1740: "calls_prototypes": "\n",
andrej@1740: "retrieve_calls": "\n",
andrej@1740: "publish_calls": "\n",
andrej@1740: "init_calls": "\n",
andrej@1740: "cleanup_calls": "\n"
andrej@1740: }
Edouard@2248: plc_main_code += targets.GetTargetCode(
Edouard@2248: self.GetTarget().getcontent().getLocalTag())
Edouard@1430: plc_main_code += targets.GetCode("plc_main_tail.c")
etisserant@203: return plc_main_code
etisserant@203:
Edouard@623: def _Build(self):
etisserant@20: """
Edouard@717: Method called by user to (re)build SoftPLC and confnode tree
etisserant@20: """
laurent@395: if self.AppFrame is not None:
laurent@395: self.AppFrame.ClearErrors()
laurent@774: self._CloseView(self._IECCodeView)
Edouard@1407:
etisserant@20: buildpath = self._getBuildPath()
etisserant@20:
etisserant@20: # Eventually create build dir
etisserant@18: if not os.path.exists(buildpath):
etisserant@18: os.mkdir(buildpath)
etisserant@203:
etisserant@203: self.logger.flush()
laurent@361: self.logger.write(_("Start build in %s\n") % buildpath)
etisserant@203:
etisserant@203: # Generate SoftPLC IEC code
etisserant@203: IECGenRes = self._Generate_SoftPLC()
andrej@1555: self.UpdateButtons()
etisserant@203:
etisserant@203: # If IEC code gen fail, bail out.
etisserant@203: if not IECGenRes:
Edouard@1407: self.logger.write_error(_("PLC code generation failed !\n"))
etisserant@20: return False
etisserant@20:
etisserant@203: # Reset variable and program list that are parsed from
etisserant@203: # CSV file generated by IEC2C compiler.
etisserant@203: self.ResetIECProgramsAndVariables()
Edouard@1407:
Edouard@1407: # Collect platform specific C code
Edouard@1407: # Code and other files from extension
Edouard@1407: if not self._Generate_runtime():
Edouard@1407: return False
Edouard@1407:
Edouard@1407: # Get current or fresh builder
Edouard@1407: builder = self.GetBuilder()
Edouard@1407: if builder is None:
Edouard@1407: self.logger.write_error(_("Fatal : cannot get builder.\n"))
Edouard@1407: return False
Edouard@1407:
Edouard@1407: # Build
Edouard@1407: try:
andrej@1739: if not builder.build():
Edouard@1407: self.logger.write_error(_("C Build failed.\n"))
Edouard@1407: return False
andrej@1846: except Exception:
Edouard@2463: builder.ResetBinaryMD5()
Edouard@1407: self.logger.write_error(_("C Build crashed !\n"))
Edouard@1407: self.logger.write_error(traceback.format_exc())
Edouard@1407: return False
Edouard@1407:
Edouard@1407: self.logger.write(_("Successfully built.\n"))
Edouard@1407: # Update GUI status about need for transfer
Edouard@1407: self.CompareLocalAndRemotePLC()
Edouard@1407: return True
Edouard@1407:
Edouard@1407: def _Generate_runtime(self):
Edouard@1407: buildpath = self._getBuildPath()
Edouard@1407:
Edouard@2631: # CTN code gen is expected AFTER Libraries code gen,
Edouard@2631: # at least SVGHMI relies on it.
Edouard@2631:
Edouard@2631: # Generate C code and compilation params from liraries
Edouard@2631: try:
Edouard@2631: LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(
Edouard@2631: buildpath)
Edouard@2640: except UserAddressedException as e:
edouard@3816: self.logger.write_error(str(e))
Edouard@2640: return False
Edouard@2640: except Exception as e:
Edouard@2631: self.logger.write_error(
Edouard@2631: _("Runtime library extensions C code generation failed !\n"))
Edouard@2631: self.logger.write_error(traceback.format_exc())
Edouard@2631: return False
Edouard@2631:
Edouard@717: # Generate C code and compilation params from confnode hierarchy
etisserant@24: try:
Edouard@728: CTNLocationCFilesAndCFLAGS, CTNLDFLAGS, CTNExtraFiles = self._Generate_C(
Edouard@1407: buildpath,
etisserant@203: self.PLCGeneratedLocatedVars)
Edouard@2640: except UserAddressedException as e:
edouard@3816: self.logger.write_error(str(e))
Edouard@2640: return False
andrej@1846: except Exception:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Runtime IO extensions C code generation failed !\n"))
etisserant@203: self.logger.write_error(traceback.format_exc())
etisserant@24: return False
etisserant@18:
edouard@3612: # Extensions also need plcCFLAGS in case they include beremiz.h
edouard@3612: CTNLocationCFilesAndCFLAGS = [
edouard@3612: (loc, [
edouard@3612: (code, self.plcCFLAGS+" "+cflags)
edouard@3612: for code,cflags in code_and_cflags], do_calls)
edouard@3612: for loc, code_and_cflags, do_calls in CTNLocationCFilesAndCFLAGS]
edouard@3612:
Edouard@2248: self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \
Edouard@2248: CTNLocationCFilesAndCFLAGS
Edouard@728: self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
Edouard@728: ExtraFiles = CTNExtraFiles + LibExtraFiles
Edouard@1407:
laurent@361: # Get temporary directory path
etisserant@203: extrafilespath = self._getExtraFilesPath()
etisserant@203: # Remove old directory
etisserant@203: if os.path.exists(extrafilespath):
etisserant@203: shutil.rmtree(extrafilespath)
etisserant@203: # Recreate directory
etisserant@203: os.mkdir(extrafilespath)
etisserant@203: # Then write the files
andrej@1740: for fname, fobject in ExtraFiles:
andrej@1740: fpath = os.path.join(extrafilespath, fname)
etisserant@203: open(fpath, "wb").write(fobject.read())
etisserant@203: # Now we can forget ExtraFiles (will close files object)
etisserant@203: del ExtraFiles
Edouard@1407:
Edouard@1001: # Header file for extensions
Edouard@2248: open(os.path.join(buildpath, "beremiz.h"), "w").write(
Edouard@2248: targets.GetHeader())
Edouard@1001:
etisserant@203: # Template based part of C code generation
Edouard@717: # files are stacked at the beginning, as files of confnode tree root
andrej@1878: c_source = [
andrej@1878: # debugger code
andrej@1878: (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
andrej@1878: # init/cleanup/retrieve/publish, run and align code
andrej@1878: (self.Generate_plc_main, "plc_main.c", "Common runtime")
andrej@1878: ]
andrej@1878:
andrej@1878: for generator, filename, name in c_source:
etisserant@203: try:
etisserant@203: # Do generate
etisserant@203: code = generator()
greg@335: if code is None:
andrej@1861: raise Exception
andrej@1740: code_path = os.path.join(buildpath, filename)
etisserant@203: open(code_path, "w").write(code)
Edouard@2248: # Insert this file as first file to be compiled at root
Edouard@2248: # confnode
Edouard@2248: self.LocationCFilesAndCFLAGS[0][1].insert(
Edouard@2248: 0, (code_path, self.plcCFLAGS))
andrej@1846: except Exception:
Edouard@2248: self.logger.write_error(name + _(" generation failed !\n"))
etisserant@203: self.logger.write_error(traceback.format_exc())
etisserant@203: return False
laurent@361: self.logger.write(_("C code generated successfully.\n"))
etisserant@49: return True
Edouard@1407:
lbessard@202: def ShowError(self, logger, from_location, to_location):
lbessard@202: chunk_infos = self.GetChunkInfos(from_location, to_location)
lbessard@202: for infos, (start_row, start_col) in chunk_infos:
Edouard@2248: row = 1 if from_location[0] < start_row else (
Edouard@2248: from_location[0] - start_row)
Edouard@2248: col = 1 if (start_row != from_location[0]) else (
Edouard@2248: from_location[1] - start_col)
andrej@1579: start = (row, col)
andrej@1579:
Edouard@2248: row = 1 if to_location[0] < start_row else (
Edouard@2248: to_location[0] - start_row)
Edouard@2248: col = 1 if (start_row != to_location[0]) else (
Edouard@2248: to_location[1] - start_col)
andrej@1579: end = (row, col)
andrej@1730:
laurent@396: if self.AppFrame is not None:
laurent@396: self.AppFrame.ShowError(infos, start, end)
Edouard@1407:
laurent@774: _IECCodeView = None
andrej@1751:
Edouard@2337: def _showIDManager(self):
Edouard@2337: dlg = IDManager(self.AppFrame, self)
Edouard@2337: dlg.ShowModal()
Edouard@2337: dlg.Destroy()
Edouard@2337:
etisserant@203: def _showIECcode(self):
laurent@716: self._OpenView("IEC code")
laurent@716:
laurent@774: _IECRawCodeView = None
andrej@1751:
laurent@716: def _editIECrawcode(self):
laurent@716: self._OpenView("IEC raw code")
Edouard@1407:
laurent@782: _ProjectFilesView = None
andrej@1751:
laurent@782: def _OpenProjectFiles(self):
laurent@815: self._OpenView("Project Files")
Edouard@1407:
laurent@784: _FileEditors = {}
andrej@1751:
laurent@784: def _OpenFileEditor(self, filepath):
laurent@784: self._OpenView(filepath)
Edouard@1407:
laurent@782: def _OpenView(self, name=None, onlyopened=False):
laurent@716: if name == "IEC code":
edouard@3747: if not self._IECCodeView:
laurent@774: plc_file = self._getIECcodepath()
Edouard@1407:
Edouard@2248: self._IECCodeView = IECCodeViewer(
Edouard@2248: self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
laurent@774: self._IECCodeView.SetTextSyntax("ALL")
laurent@774: self._IECCodeView.SetKeywords(IEC_KEYWORDS)
laurent@774: try:
andrej@2442: text = open(plc_file).read()
andrej@1780: except Exception:
laurent@774: text = '(* No IEC code have been generated at that time ! *)'
andrej@1744: self._IECCodeView.SetText(text=text)
andrej@1730: self._IECCodeView.Editor.SetReadOnly(True)
laurent@782: self._IECCodeView.SetIcon(GetBitmap("ST"))
laurent@806: setattr(self._IECCodeView, "_OnClose", self.OnCloseEditor)
Edouard@1407:
laurent@784: if self._IECCodeView is not None:
laurent@774: self.AppFrame.EditProjectElement(self._IECCodeView, name)
Edouard@1407:
laurent@774: return self._IECCodeView
Edouard@1407:
laurent@716: elif name == "IEC raw code":
edouard@3747: if not self._IECRawCodeView:
laurent@806: controler = MiniTextControler(self._getIECrawcodepath(), self)
Edouard@1407:
Edouard@2248: self._IECRawCodeView = IECCodeViewer(
Edouard@2248: self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name)
laurent@782: self._IECRawCodeView.SetTextSyntax("ALL")
laurent@782: self._IECRawCodeView.SetKeywords(IEC_KEYWORDS)
laurent@782: self._IECRawCodeView.RefreshView()
laurent@782: self._IECRawCodeView.SetIcon(GetBitmap("ST"))
laurent@806: setattr(self._IECRawCodeView, "_OnClose", self.OnCloseEditor)
Edouard@1407:
laurent@784: if self._IECRawCodeView is not None:
laurent@782: self.AppFrame.EditProjectElement(self._IECRawCodeView, name)
Edouard@1407:
laurent@782: return self._IECRawCodeView
Edouard@1407:
laurent@815: elif name == "Project Files":
edouard@3747: if not self._ProjectFilesView:
Edouard@2248: self._ProjectFilesView = FileManagementPanel(
Edouard@2248: self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
Edouard@1407:
laurent@784: extensions = []
andrej@2415: for extension, _name, _editor in features.file_editors:
laurent@784: if extension not in extensions:
laurent@784: extensions.append(extension)
Edouard@1407: self._ProjectFilesView.SetEditableFileExtensions(extensions)
Edouard@1407:
laurent@784: if self._ProjectFilesView is not None:
laurent@782: self.AppFrame.EditProjectElement(self._ProjectFilesView, name)
Edouard@1407:
laurent@784: return self._ProjectFilesView
Edouard@1407:
laurent@789: elif name is not None and name.find("::") != -1:
laurent@789: filepath, editor_name = name.split("::")
andrej@1775: if filepath not in self._FileEditors:
laurent@789: if os.path.isfile(filepath):
laurent@789: file_extension = os.path.splitext(filepath)[1]
Edouard@1407:
laurent@796: editors = dict([(edit_name, edit_class)
laurent@796: for extension, edit_name, edit_class in features.file_editors
laurent@789: if extension == file_extension])
Edouard@1407:
laurent@789: if editor_name == "":
laurent@789: if len(editors) == 1:
kinsamanka@3750: editor_name = list(editors.keys())[0]
laurent@789: elif len(editors) > 0:
kinsamanka@3750: names = list(editors.keys())
andrej@1768: dialog = wx.SingleChoiceDialog(
andrej@1768: self.AppFrame,
andrej@1768: _("Select an editor:"),
andrej@1768: _("Editor selection"),
andrej@1768: names,
andrej@1768: wx.DEFAULT_DIALOG_STYLE | wx.OK | wx.CANCEL)
laurent@789: if dialog.ShowModal() == wx.ID_OK:
laurent@789: editor_name = names[dialog.GetSelection()]
laurent@789: dialog.Destroy()
Edouard@1407:
laurent@789: if editor_name != "":
laurent@789: name = "::".join([filepath, editor_name])
Edouard@1407:
laurent@789: editor = editors[editor_name]()
Edouard@2248: self._FileEditors[filepath] = editor(
Edouard@2248: self.AppFrame.TabsOpened, self, name, self.AppFrame)
laurent@789: self._FileEditors[filepath].SetIcon(GetBitmap("FILE"))
Laurent@897: if isinstance(self._FileEditors[filepath], DebugViewer):
Laurent@897: self._FileEditors[filepath].SetDataProducer(self)
Edouard@1407:
andrej@1763: if filepath in self._FileEditors:
laurent@789: editor = self._FileEditors[filepath]
laurent@789: self.AppFrame.EditProjectElement(editor, editor.GetTagName())
Edouard@1407:
laurent@789: return self._FileEditors.get(filepath)
laurent@738: else:
laurent@786: return ConfigTreeNode._OpenView(self, self.CTNName(), onlyopened)
etisserant@20:
laurent@774: def OnCloseEditor(self, view):
laurent@774: ConfigTreeNode.OnCloseEditor(self, view)
laurent@774: if self._IECCodeView == view:
laurent@774: self._IECCodeView = None
laurent@774: if self._IECRawCodeView == view:
laurent@774: self._IECRawCodeView = None
laurent@782: if self._ProjectFilesView == view:
laurent@782: self._ProjectFilesView = None
kinsamanka@3750: if view in list(self._FileEditors.values()):
laurent@792: self._FileEditors.pop(view.GetFilePath())
laurent@774:
etisserant@203: def _Clean(self):
laurent@774: self._CloseView(self._IECCodeView)
greg@108: if os.path.isdir(os.path.join(self._getBuildPath())):
laurent@361: self.logger.write(_("Cleaning the build directory\n"))
greg@108: shutil.rmtree(os.path.join(self._getBuildPath()))
greg@108: else:
laurent@361: self.logger.write_error(_("Build directory already clean\n"))
etisserant@286: # kill the builder
etisserant@286: self._builder = None
etisserant@203: self.CompareLocalAndRemotePLC()
andrej@1555: self.UpdateButtons()
andrej@1555:
andrej@1555: def _UpdateButtons(self):
andrej@1555: self.EnableMethod("_Clean", os.path.exists(self._getBuildPath()))
andrej@1555: self.ShowMethod("_showIECcode", os.path.isfile(self._getIECcodepath()))
edouard@1634: if self.AppFrame is not None and not self.UpdateMethodsFromPLCStatus():
andrej@1555: self.AppFrame.RefreshStatusToolBar()
andrej@1730:
andrej@1555: def UpdateButtons(self):
andrej@1555: wx.CallAfter(self._UpdateButtons)
andrej@1555:
Edouard@911: def UpdatePLCLog(self, log_count):
Laurent@978: if log_count:
Laurent@978: if self.AppFrame is not None:
Laurent@978: self.AppFrame.LogViewer.SetLogCounters(log_count)
Edouard@1407:
Edouard@2224: DefaultMethods = {
Edouard@2224: "_Run": False,
Edouard@2224: "_Stop": False,
Edouard@2224: "_Transfer": False,
Edouard@2224: "_Connect": True,
Edouard@2594: "_Repair": False,
Edouard@2224: "_Disconnect": False
Edouard@2224: }
Edouard@2224:
Edouard@2224: MethodsFromStatus = {
andrej@2416: PlcStatus.Started: {"_Stop": True,
andrej@2416: "_Transfer": True,
andrej@2416: "_Connect": False,
andrej@2416: "_Disconnect": True},
andrej@2416: PlcStatus.Stopped: {"_Run": True,
andrej@2416: "_Transfer": True,
andrej@2416: "_Connect": False,
Edouard@2652: "_Disconnect": True,
Edouard@2652: "_Repair": True},
andrej@2416: PlcStatus.Empty: {"_Transfer": True,
andrej@2416: "_Connect": False,
andrej@2416: "_Disconnect": True},
andrej@2416: PlcStatus.Broken: {"_Connect": False,
Edouard@2594: "_Repair": True,
andrej@2416: "_Disconnect": True},
andrej@2416: PlcStatus.Disconnected: {},
Edouard@2224: }
Edouard@2224:
etisserant@203: def UpdateMethodsFromPLCStatus(self):
andrej@1555: updated = False
Edouard@2602: status = PlcStatus.Disconnected
etisserant@203: if self._connector is not None:
Edouard@923: PLCstatus = self._connector.GetPLCstatus()
Edouard@923: if PLCstatus is not None:
Edouard@923: status, log_count = PLCstatus
Edouard@923: self.UpdatePLCLog(log_count)
Edouard@2602: if status == PlcStatus.Disconnected:
Laurent@1116: self._SetConnector(None, False)
andrej@2416: status = PlcStatus.Disconnected
andrej@1828: if self.previous_plcstate != status:
Edouard@2224: allmethods = self.DefaultMethods.copy()
Edouard@2224: allmethods.update(
Edouard@2224: self.MethodsFromStatus.get(status, {}))
kinsamanka@3750: for method, active in list(allmethods.items()):
Edouard@2248: self.ShowMethod(method, active)
Edouard@922: self.previous_plcstate = status
Laurent@918: if self.AppFrame is not None:
andrej@1555: updated = True
Laurent@918: self.AppFrame.RefreshStatusToolBar()
edouard@3865: texts = [_(PlcStatus.Disconnected), ''] \
edouard@3865: if status == PlcStatus.Disconnected or self._connector is None else \
edouard@3865: [_("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), _(status)]
edouard@3865: for i,txt in enumerate(texts):
edouard@3865: self.AppFrame.ConnectionStatusBar.SetStatusText(txt, i+1)
andrej@1555: return updated
Edouard@1407:
andrej@1744: def ShowPLCProgress(self, status="", progress=0):
andrej@1574: self.AppFrame.ProgressStatusBar.Show()
Edouard@2248: self.AppFrame.ConnectionStatusBar.SetStatusText(
andrej@2416: _(status), 1)
andrej@1574: self.AppFrame.ProgressStatusBar.SetValue(progress)
andrej@1574:
andrej@1574: def HidePLCProgress(self):
andrej@1574: # clear previous_plcstate to restore status
andrej@1574: # in UpdateMethodsFromPLCStatus()
andrej@1574: self.previous_plcstate = ""
edouard@3501: if self.AppFrame is not None:
edouard@3501: self.AppFrame.ProgressStatusBar.Hide()
andrej@1574: self.UpdateMethodsFromPLCStatus()
andrej@1730:
ed@446: def PullPLCStatusProc(self, event):
Laurent@918: self.UpdateMethodsFromPLCStatus()
Edouard@1407:
Laurent@1363: def SnapshotAndResetDebugValuesBuffers(self):
Edouard@2485: debug_status = PlcStatus.Disconnected
Edouard@2485: if self._connector is not None and self.DebugToken is not None:
Edouard@2485: debug_status, Traces = self._connector.GetTraceVariables(self.DebugToken)
Edouard@2248: # print [dict.keys() for IECPath, (dict, log, status, fvalue) in
Edouard@2248: # self.IECdebug_datas.items()]
Edouard@2485: if debug_status == PlcStatus.Started:
Edouard@1996: if len(Traces) > 0:
Edouard@1996: for debug_tick, debug_buff in Traces:
Edouard@2248: debug_vars = UnpackDebugBuffer(
Edouard@2248: debug_buff, self.TracedIECTypes)
Edouard@3257: if debug_vars is not None:
kinsamanka@3750: for IECPath, values_buffer, value in zip(
Edouard@1996: self.TracedIECPath,
Edouard@1996: self.DebugValuesBuffers,
Edouard@1996: debug_vars):
Edouard@2248: IECdebug_data = self.IECdebug_datas.get(
Edouard@2248: IECPath, None)
Edouard@1996: if IECdebug_data is not None and value is not None:
andrej@2512: forced = (IECdebug_data[2] == "Forced") \
andrej@2512: and (value is not None) and \
andrej@2512: (IECdebug_data[3] is not None)
andrej@2512:
Edouard@1996: if not IECdebug_data[4] and len(values_buffer) > 0:
Edouard@1996: values_buffer[-1] = (value, forced)
Edouard@1996: else:
Edouard@1996: values_buffer.append((value, forced))
Edouard@1996: self.DebugTicks.append(debug_tick)
Edouard@3395: else:
Edouard@3395: # complain if trace is incomplete, but only once per debug session
Edouard@3395: if self.LastComplainDebugToken != self.DebugToken :
Edouard@3395: self.logger.write_warning(
Edouard@3395: _("Debug: target couldn't trace all requested variables.\n"))
Edouard@3395: self.LastComplainDebugToken = self.DebugToken
Edouard@3395:
Edouard@3395:
Edouard@1995:
Edouard@1407: buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
kinsamanka@3750: [list() for dummy in range(len(self.TracedIECPath))])
Edouard@1995:
Laurent@1363: ticks, self.DebugTicks = self.DebugTicks, []
Edouard@1995:
Edouard@2485: return debug_status, ticks, buffers
Edouard@1407:
Edouard@3395: RegisterDebugVariableErrorCodes = {
Edouard@3577: # Connector only can return None
Edouard@3577: None : _("Debug: connection problem.\n"),
Edouard@3395: # TRACE_LIST_OVERFLOW
Edouard@3395: 1 : _("Debug: Too many variables traced. Max 1024.\n"),
Edouard@3395: # FORCE_LIST_OVERFLOW
Edouard@3395: 2 : _("Debug: Too many variables forced. Max 256.\n"),
Edouard@3395: # FORCE_BUFFER_OVERFLOW
Edouard@3577: 3 : _("Debug: Cumulated forced variables size too large. Max 1KB.\n"),
edouard@3887: # FORCE_INVALID
edouard@3887: 3 : _("Debug: Invalid forced value.\n"),
Edouard@3577: # DEBUG_SUSPENDED
edouard@3887: 5 : _("Debug: suspended.\n")
Edouard@3395: }
Edouard@3395:
etisserant@239: def RegisterDebugVarToConnector(self):
etisserant@239: Idxs = []
etisserant@239: self.TracedIECPath = []
Edouard@1433: self.TracedIECTypes = []
Edouard@2485: if self._connector is not None and self.debug_status != PlcStatus.Broken:
etisserant@239: IECPathsToPop = []
kinsamanka@3750: for IECPath, data_tuple in self.IECdebug_datas.items():
andrej@1847: WeakCallableDict, _data_log, _status, fvalue, _buffer_list = data_tuple
etisserant@239: if len(WeakCallableDict) == 0:
etisserant@239: # Callable Dict is empty.
etisserant@239: # This variable is not needed anymore!
etisserant@239: IECPathsToPop.append(IECPath)
greg@355: elif IECPath != "__tick__":
Edouard@1407: # Convert
Edouard@2248: Idx, IEC_Type = self._IECPathToIdx.get(
Edouard@2248: IECPath, (None, None))
etisserant@239: if Idx is not None:
Edouard@1407: if IEC_Type in DebugTypesSize:
edouard@3887: Idxs.append(
edouard@3887: (Idx, IEC_Type, IECPath,
edouard@3887: ValueToIECBytes(IEC_Type, fvalue)))
Edouard@592: else:
Edouard@2248: self.logger.write_warning(
Edouard@2248: _("Debug: Unsupported type to debug '%s'\n") % IEC_Type)
etisserant@239: else:
Edouard@2248: self.logger.write_warning(
Edouard@2248: _("Debug: Unknown variable '%s'\n") % IECPath)
etisserant@239: for IECPathToPop in IECPathsToPop:
etisserant@239: self.IECdebug_datas.pop(IECPathToPop)
etisserant@239:
ed@457: if Idxs:
ed@457: Idxs.sort()
edouard@3887: Idxs, self.TracedIECTypes, self.TracedIECPath, Fvalues, = list(zip(*Idxs))
edouard@3887: res = self._connector.SetTraceVariablesList(list(zip(Idxs, Fvalues)))
Edouard@3395: if res is not None and res > 0:
Edouard@3395: self.DebugToken = res
Edouard@3395: else:
Edouard@3395: self.DebugToken = None
Edouard@3395: self.logger.write_warning(
Edouard@3395: self.RegisterDebugVariableErrorCodes.get(
Edouard@3577: -res if res is not None else None,
Edouard@3577: _("Debug: Unknown error")))
edouard@465: else:
edouard@465: self.TracedIECPath = []
edouard@465: self._connector.SetTraceVariablesList([])
Edouard@2485: self.DebugToken = None
Edouard@2485: self.debug_status, _debug_ticks, _buffers = self.SnapshotAndResetDebugValuesBuffers()
Edouard@2708: self.DebugUpdatePending = False
Edouard@1407:
Laurent@1146: def IsPLCStarted(self):
andrej@2416: return self.previous_plcstate == PlcStatus.Started
Edouard@1407:
Edouard@2708: def AppendDebugUpdate(self):
Edouard@2708: if not self.DebugUpdatePending :
Edouard@2708: wx.CallAfter(self.RegisterDebugVarToConnector)
Edouard@2708: self.DebugUpdatePending = True
lbessard@243:
laurent@463: def GetDebugIECVariableType(self, IECPath):
andrej@1847: _Idx, IEC_Type = self._IECPathToIdx.get(IECPath, (None, None))
laurent@463: return IEC_Type
Edouard@1407:
Edouard@1430: def SubscribeDebugIECVariable(self, IECPath, callableobj, buffer_list=False):
etisserant@239: """
etisserant@239: Dispatching use a dictionnary linking IEC variable paths
Edouard@1407: to a WeakKeyDictionary linking
Edouard@1430: weakly referenced callables
etisserant@239: """
andrej@1775: if IECPath != "__tick__" and IECPath not in self._IECPathToIdx:
lbessard@246: return None
Edouard@1407:
etisserant@239: # If no entry exist, create a new one with a fresh WeakKeyDictionary
etisserant@239: IECdebug_data = self.IECdebug_datas.get(IECPath, None)
etisserant@239: if IECdebug_data is None:
andrej@1754: IECdebug_data = [
andrej@1878: WeakKeyDictionary(), # Callables
andrej@1878: [], # Data storage [(tick, data),...]
andrej@1878: "Registered", # Variable status
Edouard@3257: None, # Forced value
Edouard@3257: buffer_list]
etisserant@239: self.IECdebug_datas[IECPath] = IECdebug_data
Laurent@1365: else:
Laurent@1365: IECdebug_data[4] |= buffer_list
Edouard@1407:
andrej@1742: IECdebug_data[0][callableobj] = buffer_list
etisserant@239:
Edouard@2708: self.AppendDebugUpdate()
Edouard@1407:
etisserant@239: return IECdebug_data[1]
etisserant@239:
etisserant@239: def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
etisserant@239: IECdebug_data = self.IECdebug_datas.get(IECPath, None)
lbessard@243: if IECdebug_data is not None:
andrej@1740: IECdebug_data[0].pop(callableobj, None)
Laurent@1255: if len(IECdebug_data[0]) == 0:
Laurent@1255: self.IECdebug_datas.pop(IECPath)
Laurent@1365: else:
Laurent@1365: IECdebug_data[4] = reduce(
andrej@1745: lambda x, y: x | y,
kinsamanka@3750: iter(IECdebug_data[0].values()),
Laurent@1365: False)
lbessard@243:
Edouard@2708: self.AppendDebugUpdate()
etisserant@239:
lbessard@334: def UnsubscribeAllDebugIECVariable(self):
Laurent@1089: self.IECdebug_datas = {}
lbessard@334:
Edouard@2708: self.AppendDebugUpdate()
greg@355:
laurent@474: def ForceDebugIECVariable(self, IECPath, fvalue):
andrej@1775: if IECPath not in self.IECdebug_datas:
laurent@474: return
Edouard@1407:
laurent@474: # If no entry exist, create a new one with a fresh WeakKeyDictionary
laurent@474: IECdebug_data = self.IECdebug_datas.get(IECPath, None)
laurent@474: IECdebug_data[2] = "Forced"
laurent@474: IECdebug_data[3] = fvalue
Edouard@1407:
Edouard@2708: self.AppendDebugUpdate()
Edouard@1407:
laurent@463: def ReleaseDebugIECVariable(self, IECPath):
andrej@1775: if IECPath not in self.IECdebug_datas:
laurent@474: return
Edouard@1407:
laurent@474: # If no entry exist, create a new one with a fresh WeakKeyDictionary
laurent@474: IECdebug_data = self.IECdebug_datas.get(IECPath, None)
laurent@474: IECdebug_data[2] = "Registered"
laurent@474: IECdebug_data[3] = None
Edouard@1407:
Edouard@2708: self.AppendDebugUpdate()
Edouard@1407:
greg@355: def CallWeakcallables(self, IECPath, function_name, *cargs):
greg@355: data_tuple = self.IECdebug_datas.get(IECPath, None)
greg@355: if data_tuple is not None:
andrej@1847: WeakCallableDict, _data_log, _status, _fvalue, buffer_list = data_tuple
andrej@1782: # data_log.append((debug_tick, value))
kinsamanka@3750: for weakcallable, buffer_list in WeakCallableDict.items():
greg@355: function = getattr(weakcallable, function_name, None)
greg@355: if function is not None:
edouard@3476: # FIXME: apparently, despite of weak ref objects,
edouard@3476: # some dead C/C++ wx object are still reachable from here
edouard@3476: # leading to RuntimeError exception
edouard@3476: try:
edouard@3476: if buffer_list:
edouard@3476: function(*cargs)
edouard@3476: else:
edouard@3476: function(*tuple([lst[-1] for lst in cargs]))
edouard@3476: except RuntimeError:
edouard@3476: pass
lbessard@334:
laurent@670: def GetTicktime(self):
laurent@670: return self._Ticktime
laurent@670:
laurent@699: def RemoteExec(self, script, **kwargs):
laurent@699: if self._connector is None:
laurent@703: return -1, "No runtime connected!"
laurent@699: return self._connector.RemoteExec(script, **kwargs)
laurent@699:
Laurent@1363: def DispatchDebugValuesProc(self, event):
Edouard@3257: event.Skip()
Edouard@3257: start_time = time.time()
Edouard@2485: self.debug_status, debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers()
Edouard@1407:
Edouard@2485: if self.debug_status == PlcStatus.Broken:
Edouard@2485: self.logger.write_warning(
Edouard@2485: _("Debug: token rejected - other debug took over - reconnect to recover\n"))
Edouard@3257: return
Edouard@3257:
Edouard@3257: for IECPath, values in zip(self.TracedIECPath, buffers):
Edouard@3257: if len(values) > 0:
Edouard@3257: self.CallWeakcallables(
Edouard@3257: IECPath, "NewValues", debug_ticks, values)
Edouard@3257: if len(debug_ticks) > 0:
Edouard@3257: self.CallWeakcallables(
Edouard@3257: "__tick__", "NewDataAvailable", debug_ticks)
Edouard@3257:
Edouard@3257: delay = time.time() - start_time
Edouard@3257: next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay)
Edouard@3257: if self.DispatchDebugValuesTimer is not None:
Edouard@3257: res = self.DispatchDebugValuesTimer.Start(
Edouard@3257: int(next_refresh * 1000), oneShot=True)
etisserant@235:
etisserant@286: def KillDebugThread(self):
Laurent@1363: if self.DispatchDebugValuesTimer is not None:
Laurent@1363: self.DispatchDebugValuesTimer.Stop()
etisserant@286:
Edouard@1407: def _connect_debug(self):
Edouard@914: self.previous_plcstate = None
edouard@465: if self.AppFrame:
edouard@465: self.AppFrame.ResetGraphicViewers()
Edouard@2485:
Edouard@2485: self.debug_status = PlcStatus.Started
Edouard@2485:
edouard@465: self.RegisterDebugVarToConnector()
Laurent@1363: if self.DispatchDebugValuesTimer is not None:
Laurent@1366: self.DispatchDebugValuesTimer.Start(
Laurent@1366: int(REFRESH_PERIOD * 1000), oneShot=True)
Edouard@1407:
edouard@462: def _Run(self):
etisserant@203: """
edouard@464: Start PLC
etisserant@203: """
edouard@3501: success = False
greg@350: if self.GetIECProgramsAndVariables():
edouard@462: self._connector.StartPLC()
edouard@464: self.logger.write(_("Starting PLC\n"))
edouard@465: self._connect_debug()
edouard@3501: success = True
etisserant@203: else:
edouard@464: self.logger.write_error(_("Couldn't start PLC !\n"))
laurent@675: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
edouard@3501: return success
Edouard@1407:
etisserant@203: def _Stop(self):
etisserant@203: """
etisserant@203: Stop PLC
etisserant@203: """
edouard@483: if self._connector is not None and not self._connector.StopPLC():
edouard@483: self.logger.write_error(_("Couldn't stop PLC !\n"))
edouard@483:
Edouard@689: # debugthread should die on his own
andrej@1782: # self.KillDebugThread()
Edouard@1407:
laurent@675: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
etisserant@203:
edouard@3501: def StartLocalRuntime(self):
edouard@3501: if self.AppFrame:
edouard@3501: return self.AppFrame.StartLocalRuntime()
edouard@3501:
Laurent@1116: def _SetConnector(self, connector, update_status=True):
Laurent@978: self._connector = connector
Laurent@978: if self.AppFrame is not None:
Laurent@978: self.AppFrame.LogViewer.SetLogSource(connector)
Laurent@992: if connector is not None:
Edouard@1396: if self.StatusTimer is not None:
Edouard@1396: # Start the status Timer
andrej@1524: self.StatusTimer.Start(milliseconds=500, oneShot=False)
Laurent@992: else:
Edouard@1395: if self.StatusTimer is not None:
Edouard@1396: # Stop the status Timer
Edouard@1395: self.StatusTimer.Stop()
Laurent@1116: if update_status:
Laurent@1116: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
Laurent@992:
etisserant@203: def _Connect(self):
edouard@3501: success = False
Edouard@922: # don't accept re-connetion if already connected
etisserant@203: if self._connector is not None:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Already connected. Please disconnect\n"))
etisserant@203: return
Edouard@1407:
etisserant@203: # Get connector uri
andrej@1767: uri = self.BeremizRoot.getURI_location().strip()
etisserant@203:
etisserant@203: # if uri is empty launch discovery dialog
etisserant@203: if uri == "":
Edouard@740: try:
Edouard@740: # Launch Service Discovery dialog
Edouard@2334: dialog = UriEditor(self.AppFrame, self)
Edouard@740: answer = dialog.ShowModal()
Edouard@2481: uri = str(dialog.GetURI())
Edouard@740: dialog.Destroy()
andrej@1780: except Exception:
laurent@801: self.logger.write_error(_("Local service discovery failed!\n"))
Edouard@763: self.logger.write_error(traceback.format_exc())
Edouard@740: uri = None
Edouard@1407:
etisserant@203: # Nothing choosed or cancel button
laurent@392: if uri is None or answer == wx.ID_CANCEL:
laurent@392: self.logger.write_error(_("Connection canceled!\n"))
etisserant@203: return
etisserant@203: else:
andrej@1776: self.BeremizRoot.setURI_location(uri)
laurent@764: self.ChangesToSave = True
laurent@746: if self._View is not None:
laurent@746: self._View.RefreshView()
laurent@789: if self.AppFrame is not None:
laurent@764: self.AppFrame.RefreshTitle()
laurent@764: self.AppFrame.RefreshFileMenu()
laurent@764: self.AppFrame.RefreshEditMenu()
laurent@764: self.AppFrame.RefreshPageTitles()
Edouard@1407:
etisserant@203: # Get connector from uri
etisserant@203: try:
Laurent@978: self._SetConnector(connectors.ConnectorFactory(uri, self))
Edouard@2469: except Exception as e:
Edouard@2248: self.logger.write_error(
edouard@2492: _("Exception while connecting to '{uri}': {ex}\n").format(
edouard@2492: uri=uri, ex=e))
etisserant@203:
etisserant@203: # Did connection success ?
etisserant@203: if self._connector is None:
etisserant@203: # Oups.
andrej@1734: self.logger.write_error(_("Connection failed to %s!\n") % uri)
etisserant@203: else:
edouard@4032: VersionsInfoBytes = self._connector.ExtendedCall("GetVersions", bytes())
edouard@4036: if VersionsInfoBytes is not None:
edouard@4036: VersionsInfo = VersionsInfoBytes.decode()
edouard@4036: self.logger.write(f"Version string: {VersionsInfo}\n")
etisserant@203: self.CompareLocalAndRemotePLC()
Edouard@1407:
greg@350: # Init with actual PLC status and print it
ed@446: self.UpdateMethodsFromPLCStatus()
andrej@2416: if self.previous_plcstate in [PlcStatus.Started, PlcStatus.Stopped]:
edouard@465: if self.DebugAvailable() and self.GetIECProgramsAndVariables():
Edouard@922: self.logger.write(_("Debugger ready\n"))
edouard@465: self._connect_debug()
edouard@465: else:
Edouard@2248: self.logger.write_warning(
Edouard@2248: _("Debug does not match PLC - stop/transfert/start to re-enable\n"))
edouard@3501: success = True
edouard@3501: return success
etisserant@203:
etisserant@203: def CompareLocalAndRemotePLC(self):
etisserant@203: if self._connector is None:
etisserant@203: return
Edouard@2463: builder = self.GetBuilder()
edouard@2492: if builder is None:
Edouard@2463: return
Edouard@2463: MD5 = builder.GetBinaryMD5()
Edouard@2463: if MD5 is None:
Edouard@2463: return
etisserant@203: # Check remote target PLC correspondance to that md5
Edouard@2463: if self._connector.MatchMD5(MD5):
Edouard@2464: self.logger.write(
Edouard@2464: _("Latest build matches with connected target.\n"))
Edouard@2463: self.ProgramTransferred()
etisserant@203: else:
Edouard@2463: self.logger.write(
Edouard@2463: _("Latest build does not match with connected target.\n"))
etisserant@203:
etisserant@203: def _Disconnect(self):
Laurent@978: self._SetConnector(None)
Edouard@1407:
etisserant@203: def _Transfer(self):
edouard@3501: success = False
Edouard@1928: if self.IsPLCStarted():
Edouard@1928: dialog = wx.MessageDialog(
Edouard@1953: self.AppFrame,
Edouard@1953: _("Cannot transfer while PLC is running. Stop it now?"),
Edouard@1953: style=wx.YES_NO | wx.CENTRE)
Edouard@1928: if dialog.ShowModal() == wx.ID_YES:
Edouard@1928: self._Stop()
Edouard@1928: else:
Edouard@1928: return
Edouard@1928:
Edouard@2463: builder = self.GetBuilder()
Edouard@2463: if builder is None:
Edouard@2463: self.logger.write_error(_("Fatal : cannot get builder.\n"))
Edouard@2463: return False
Edouard@2463:
edouard@2492: # recover md5 from last build
Edouard@2463: MD5 = builder.GetBinaryMD5()
Edouard@1407:
Edouard@1407: # Check if md5 file is empty : ask user to build PLC
andrej@1739: if MD5 is None:
Edouard@2248: self.logger.write_error(
Edouard@2248: _("Failed : Must build before transfer.\n"))
etisserant@203: return False
etisserant@203:
etisserant@203: # Compare PLC project with PLC on target
etisserant@203: if self._connector.MatchMD5(MD5):
etisserant@203: self.logger.write(
laurent@415: _("Latest build already matches current target. Transfering anyway...\n"))
etisserant@203:
Edouard@2463: # purge any non-finished transfer
Edouard@2463: # note: this would abord any runing transfer with error
Edouard@2463: self._connector.PurgeBlobs()
Edouard@2463:
Edouard@2621: try:
Edouard@2621: # transfer extra files
Edouard@2621: extrafiles = []
Edouard@2621: for extrafilespath in [self._getExtraFilesPath(),
Edouard@2621: self._getProjectFilesPath()]:
Edouard@2621:
Edouard@2621: for name in os.listdir(extrafilespath):
Edouard@2621: extrafiles.append((
Edouard@2621: name,
Edouard@2621: self._connector.BlobFromFile(
Edouard@2621: # use file name as a seed to avoid collisions
Edouard@2621: # with files having same content
Edouard@2621: os.path.join(extrafilespath, name), name)))
Edouard@2621:
Edouard@2621: # Send PLC on target
Edouard@2621: object_path = builder.GetBinaryPath()
Edouard@2621: # arbitrarily use MD5 as a seed, could be any string
Edouard@2621: object_blob = self._connector.BlobFromFile(object_path, MD5)
Edouard@2621: except IOError as e:
Edouard@2621: self.HidePLCProgress()
Edouard@2621: self.logger.write_error(repr(e))
Edouard@2621: else:
Edouard@2621: self.HidePLCProgress()
Edouard@2621: self.logger.write(_("PLC data transfered successfully.\n"))
Edouard@2621:
Edouard@2621: if self._connector.NewPLC(MD5, object_blob, extrafiles):
Edouard@2621: if self.GetIECProgramsAndVariables():
Edouard@2621: self.UnsubscribeAllDebugIECVariable()
Edouard@2621: self.ProgramTransferred()
edouard@3501: if self.AppFrame is not None:
edouard@3501: self.AppFrame.CloseObsoleteDebugTabs()
edouard@3501: self.AppFrame.RefreshPouInstanceVariablesPanel()
edouard@3501: self.AppFrame.LogViewer.ResetLogCounters()
Edouard@2621: self.logger.write(_("PLC installed successfully.\n"))
edouard@3501: success = True
Edouard@2621: else:
Edouard@2621: self.logger.write_error(_("Missing debug data\n"))
etisserant@203: else:
Edouard@2621: self.logger.write_error(_("PLC couldn't be installed\n"))
laurent@415:
laurent@675: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
edouard@3501: return success
etisserant@105:
Edouard@2594: def _Repair(self):
Edouard@2594: dialog = wx.MessageDialog(
Edouard@2594: self.AppFrame,
Edouard@2594: _('Delete target PLC application?'),
Edouard@2594: _('Repair'),
Edouard@2594: wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
Edouard@2594: answer = dialog.ShowModal()
Edouard@2594: dialog.Destroy()
Edouard@2594: if answer == wx.ID_YES:
Edouard@2596: self._connector.RepairPLC()
Edouard@2594:
laurent@738: StatusMethods = [
andrej@1739: {
andrej@1739: "bitmap": "Build",
andrej@1739: "name": _("Build"),
andrej@1739: "tooltip": _("Build project into build folder"),
andrej@1739: "method": "_Build"
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "Clean",
andrej@1739: "name": _("Clean"),
andrej@1739: "tooltip": _("Clean project build folder"),
andrej@1739: "method": "_Clean",
andrej@1739: "enabled": False,
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "Run",
andrej@1739: "name": _("Run"),
andrej@1739: "tooltip": _("Start PLC"),
andrej@1739: "method": "_Run",
andrej@1739: "shown": False,
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "Stop",
andrej@1739: "name": _("Stop"),
andrej@1739: "tooltip": _("Stop Running PLC"),
andrej@1739: "method": "_Stop",
andrej@1739: "shown": False,
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "Connect",
andrej@1739: "name": _("Connect"),
andrej@1739: "tooltip": _("Connect to the target PLC"),
andrej@1739: "method": "_Connect"
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "Transfer",
andrej@1739: "name": _("Transfer"),
andrej@1739: "tooltip": _("Transfer PLC"),
andrej@1739: "method": "_Transfer",
andrej@1739: "shown": False,
andrej@1739: },
andrej@1739: {
Edouard@2652: "bitmap": "Disconnect",
Edouard@2652: "name": _("Disconnect"),
Edouard@2652: "tooltip": _("Disconnect from PLC"),
Edouard@2652: "method": "_Disconnect",
Edouard@2652: "shown": False,
Edouard@2652: },
Edouard@2652: {
Edouard@2594: "bitmap": "Repair",
Edouard@2594: "name": _("Repair"),
Edouard@2594: "tooltip": _("Repair broken PLC"),
Edouard@2594: "method": "_Repair",
Edouard@2594: "shown": False,
Edouard@2594: },
Edouard@2594: {
Edouard@2337: "bitmap": "IDManager",
Edouard@2337: "name": _("ID Manager"),
Edouard@2337: "tooltip": _("Manage secure connection identities"),
Edouard@2337: "method": "_showIDManager",
Edouard@2337: },
Edouard@2337: {
andrej@1739: "bitmap": "ShowIECcode",
andrej@1739: "name": _("Show code"),
andrej@1739: "tooltip": _("Show IEC code generated by PLCGenerator"),
andrej@1739: "method": "_showIECcode",
andrej@1739: "shown": False,
andrej@1739: },
laurent@738: ]
laurent@738:
laurent@738: ConfNodeMethods = [
andrej@1739: {
andrej@1739: "bitmap": "editIECrawcode",
andrej@1739: "name": _("Raw IEC code"),
andrej@1739: "tooltip": _("Edit raw IEC code added to code generated by PLCGenerator"),
andrej@1739: "method": "_editIECrawcode"
andrej@1739: },
andrej@1739: {
andrej@1739: "bitmap": "ManageFolder",
andrej@1739: "name": _("Project Files"),
andrej@1739: "tooltip": _("Open a file explorer to manage project files"),
andrej@1739: "method": "_OpenProjectFiles"
andrej@1739: },
lbessard@65: ]
laurent@738:
laurent@738: def EnableMethod(self, method, value):
laurent@738: for d in self.StatusMethods:
andrej@1742: if d["method"] == method:
andrej@1742: d["enabled"] = value
laurent@738: return True
laurent@738: return False
laurent@738:
laurent@738: def ShowMethod(self, method, value):
laurent@738: for d in self.StatusMethods:
andrej@1742: if d["method"] == method:
andrej@1742: d["shown"] = value
laurent@738: return True
laurent@738: return False
laurent@738:
laurent@738: def CallMethod(self, method):
laurent@738: for d in self.StatusMethods:
andrej@1742: if d["method"] == method and d.get("enabled", True) and d.get("shown", True):
laurent@738: getattr(self, method)()