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:
andrej@1881:
andrej@1881: from __future__ import absolute_import
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
laurent@1282: from types import ListType
Edouard@725: from threading import Timer, Lock, Thread
etisserant@20: from datetime import datetime
Edouard@725: from weakref import WeakKeyDictionary
Edouard@1433: from itertools import izip
Edouard@725:
andrej@1832: import wx
andrej@1832:
andrej@1832: import features
Edouard@725: import connectors
andrej@1680: import util.paths as paths
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
Laurent@814: from dialogs import DiscoveryDialog
laurent@738: from PLCControler import PLCControler
Edouard@725: from plcopen.structures import IEC_KEYWORDS
andrej@1834: import targets
andrej@1850: from targets.typemapping import DebugTypesSize, UnpackDebugBuffer
Laurent@1330: from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
Edouard@725:
andrej@1680: base_folder = paths.AbsParentDir(__file__)
etisserant@20:
laurent@813: MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
lbessard@356:
laurent@738: ITEM_CONFNODE = 25
laurent@738:
andrej@1736:
laurent@1282: def ExtractChildrenTypesFromCatalog(catalog):
laurent@1282: children_types = []
andrej@1847: for n, d, _h, c in catalog:
laurent@1282: if isinstance(c, ListType):
laurent@1282: children_types.extend(ExtractChildrenTypesFromCatalog(c))
laurent@1282: else:
laurent@1282: children_types.append((n, GetClassImporter(c), d))
laurent@1282: return children_types
laurent@1282:
andrej@1736:
laurent@1282: def ExtractMenuItemsFromCatalog(catalog):
laurent@1282: menu_items = []
andrej@1740: for n, d, h, c in catalog:
laurent@1282: if isinstance(c, ListType):
laurent@1282: children = ExtractMenuItemsFromCatalog(c)
laurent@1282: else:
laurent@1282: children = []
laurent@1282: menu_items.append((n, d, h, 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):
andrej@1630: def __init__(self):
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()
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):
andrej@1742: cmd = "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else "")
andrej@1742: paths = [
andrej@1685: os.path.join(base_folder, "matiec")
andrej@1685: ]
andrej@1740: path = self.findObject(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 = [
andrej@1685: os.path.join(base_folder, "matiec", "lib"),
andrej@1685: "/usr/lib/matiec"
andrej@1685: ]
andrej@1740: path = self.findObject(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:
andrej@1630: # Invoke compiler. Output files are listed to stdout, errors to stderr
andrej@1847: _status, result, _err_result = ProcessLogger(None, buildcmd,
andrej@1847: no_stdout=True,
andrej@1847: no_stderr=True).spin()
andrej@1846: except Exception:
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@733: """+targets.GetTargetChoices()+"""
etisserant@106:
etisserant@106:
Edouard@1413: """+(("""
Edouard@1413:
laurent@730:
andrej@1742: """+"\n".join([''
andrej@1847: for libname, _lib 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):
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: """
andrej@1824: # For root object, available Children Types are modules of the confnode 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:
andrej@1789: ProjectController.iec2c_cfg = Iec2CSettings()
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@227: self.IECdebug_lock = Lock()
etisserant@222:
andrej@1742: self.DebugTimer = None
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.DebugThread = None
etisserant@286: self.debug_break = False
greg@350: self.previous_plcstate = None
Edouard@717: # copy ConfNodeMethods so that it can be later customized
laurent@754: self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
Edouard@728:
laurent@1282: def __del__(self):
laurent@1282: if self.DebugTimer:
laurent@1282: self.DebugTimer.cancel()
laurent@1282: self.KillDebugThread()
Edouard@1407:
Edouard@728: def LoadLibraries(self):
Edouard@728: self.Libraries = []
andrej@1742: TypeStack = []
andrej@1740: for libname, clsname in features.libraries:
Edouard@731: if self.BeremizRoot.Libraries is None or getattr(self.BeremizRoot.Libraries, "Enable_"+libname+"_Library"):
Edouard@732: Lib = GetClassImporter(clsname)()(self, libname, TypeStack)
Edouard@728: TypeStack.append(Lib.GetTypes())
Edouard@728: self.Libraries.append(Lib)
Edouard@1407:
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):
laurent@411: if wx.Platform == '__WXMSW__':
laurent@510: return "Win32"
laurent@411: else:
laurent@510: return "Linux"
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()
Laurent@1315: target.setcontent(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@1407: child.update(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
greg@427: def CheckProjectPathPerm(self, dosave=True):
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")
laurent@757: projectfiles_path = os.path.join(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
surkovsv93@1708: config = self.Project.getconfiguration(self.GetProjectMainConfigurationName())
surkovsv93@1708: resource = config.getresource()[0].getname()
surkovsv93@1708: config = config.getname()
surkovsv93@1708: resource_tagname = self.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()
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)
andrej@1744: self.logger.write_warning(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"))
surkovsv93@1606: dialog = wx.MessageDialog(self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR)
surkovsv93@1606: dialog.ShowModal()
surkovsv93@1606: return False
surkovsv93@1606: else:
surkovsv93@1606: plc_file = os.path.join(new_project_path, "plc.xml")
surkovsv93@1606: if os.path.isfile(plc_file):
surkovsv93@1606: message = (_("Selected directory already contains another project. Overwrite? \n"))
surkovsv93@1606: dialog = wx.MessageDialog(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):
greg@427: if self.CheckProjectPathPerm(False):
Laurent@1061: if from_project_path is not None:
Laurent@1061: old_projectfiles_path = self._getProjectFilesPath(from_project_path)
Laurent@1061: if os.path.isdir(old_projectfiles_path):
Edouard@1407: shutil.copytree(old_projectfiles_path,
Laurent@1061: 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")
andrej@1739: dirdialog = wx.DirDialog(self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
greg@427: answer = dirdialog.ShowModal()
greg@427: dirdialog.Destroy()
greg@427: if answer == wx.ID_OK:
greg@427: newprojectpath = dirdialog.GetPath()
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()
andrej@1746: return [lib.GetTypes() for lib in self.Libraries]
Edouard@728:
Edouard@728: def GetLibrariesSTCode(self):
andrej@1746: return "\n".join([lib.GetSTCode() for lib in self.Libraries])
Edouard@728:
Edouard@728: def GetLibrariesCCode(self, buildpath):
andrej@1742: if len(self.Libraries) == 0:
andrej@1740: return [], [], ()
Edouard@728: self.GetIECProgramsAndVariables()
andrej@1734: LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath(self.GetIECLibPath())
andrej@1742: LocatedCCodeAndFlags = []
andrej@1742: Extras = []
Edouard@728: for lib in self.Libraries:
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:])
andrej@1740: return map(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@717: def ConfNodePath(self):
andrej@1680: return paths.AbsDir(__file__)
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:
greg@427: self.DefaultBuildPath = os.path.join(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):
lbessard@97: # IEC2C compiler generate a list of located variables : LOCATED_VARIABLES.h
andrej@1740: location_file = open(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
lbessard@348: LOCATED_MODEL = re.compile("__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()
lbessard@97: # rewrite string for variadic location as a tuple of 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):
Laurent@883: return self._GlobalInstances()
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:
laurent@361: self.logger.write(_("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
etisserant@20: # ask PLCOpenEditor controller to write ST/IL/SFC code file
andrej@1847: _program, errors, warnings = self.GenerateProgram(self._getIECgeneratedcodepath())
lbessard@309: if len(warnings) > 0:
laurent@361: self.logger.write_warning(_("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 !
andrej@1734: self.logger.write_error(_("Error in ST/IL/SFC code generator :\n%s\n") % errors[0])
etisserant@20: return False
lbessard@65: plc_file = open(self._getIECcodepath(), "w")
Edouard@717: # Add ST Library from confnodes
Edouard@728: plc_file.write(self.GetLibrariesSTCode())
lbessard@65: if os.path.isfile(self._getIECrawcodepath()):
lbessard@65: plc_file.write(open(self._getIECrawcodepath(), "r").read())
lbessard@65: plc_file.write("\n")
lbessard@356: plc_file.close()
lbessard@356: plc_file = open(self._getIECcodepath(), "r")
lbessard@356: self.ProgramOffset = 0
andrej@1847: for dummy in plc_file.xreadlines():
lbessard@356: self.ProgramOffset += 1
lbessard@356: plc_file.close()
lbessard@356: plc_file = open(self._getIECcodepath(), "a")
lbessard@65: plc_file.write(open(self._getIECgeneratedcodepath(), "r").read())
lbessard@65: plc_file.close()
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:
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@1451: # Invoke compiler. Output files are listed to stdout, errors to stderr
Edouard@1451: status, result, err_result = ProcessLogger(self.logger, buildcmd,
andrej@1768: no_stdout=True, no_stderr=True).spin()
andrej@1740: except Exception, 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:
lbessard@356: self.logger.write_warning("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:
andrej@1734: self.logger.write_error(_("Error : IEC to C compiler returned %d\n") % status)
etisserant@20: return False
Edouard@1407:
etisserant@20: # Now extract C files of stdout
andrej@1746: C_files = [fname for fname in result.splitlines() if fname[-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:
laurent@415: self.logger.write_error(_("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
andrej@1740: C_files = map(lambda filename: os.path.join(buildpath, filename), C_files)
Edouard@1050:
Edouard@1050: # prepend beremiz include to configuration header
andrej@1746: H_files = [fname for fname in result.splitlines() if fname[-2:] == ".h" or fname[-2:] == ".H"]
Edouard@1050: H_files.remove("LOCATED_VARIABLES.h")
andrej@1740: H_files = map(lambda filename: os.path.join(buildpath, filename), H_files)
Edouard@1050: for H_file in H_files:
andrej@1756: with file(H_file, 'r') as original:
andrej@1756: data = original.read()
andrej@1756: with file(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"))
lbessard@97: # Keep track of generated located variables for later use by 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()
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:
laurent@677: def ResetBuildMD5(self):
andrej@1742: builder = self.GetBuilder()
laurent@677: if builder is not None:
laurent@677: builder.ResetBinaryCodeMD5()
laurent@677: self.EnableMethod("_Transfer", False)
laurent@677:
etisserant@203: def GetLastBuildMD5(self):
andrej@1742: builder = self.GetBuilder()
etisserant@203: if builder is not None:
etisserant@203: return builder.GetBinaryCodeMD5()
etisserant@203: else:
etisserant@203: return None
etisserant@203:
etisserant@203: #######################################################################
etisserant@203: #
etisserant@203: # C CODE GENERATION METHODS
etisserant@203: #
etisserant@203: #######################################################################
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"]
etisserant@203: VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
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@1740: for line in open(csvfile, 'r').xreadlines():
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
andrej@1740: attrs = dict(zip(ProgramsListAttributeName, line.strip().split(';')))
andrej@1722: # Truncate "C_path" to remove conf an resources names
andrej@1740: attrs["C_path"] = '__'.join(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
andrej@1740: attrs = dict(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:
laurent@361: self.logger.write_error(_("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@1432: bofs = 0
andrej@1739: for v in self._DbgVariablesList:
Edouard@1432: sz = DebugTypesSize.get(v["type"], 0)
Edouard@1432: variable_decl_array += [
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 +
andrej@1740: "}"]
Edouard@1432: bofs += sz
Edouard@1430: debug_code = targets.GetCode("plc_debug.c") % {
andrej@1777: "buffer_size": bofs,
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]),
andrej@1777: "variable_decl_array": ",\n".join(variable_decl_array)
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
andrej@1740: locstrs = map(lambda x: "_".join(map(str, x)),
andrej@1847: [loc for loc, _Cfiles, DoCalls in
andrej@1768: 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
andrej@1878: "__publish_%s();" % locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
andrej@1740: "init_calls": "\n ".join([
andrej@1878: "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 +
andrej@1878: "__cleanup_%s();" % locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
greg@338: }
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: }
Laurent@1315: plc_main_code += targets.GetTargetCode(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"))
laurent@677: self.ResetBuildMD5()
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: self.ResetBuildMD5()
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@1407: self.logger.write_error(_("C Build crashed !\n"))
Edouard@1407: self.logger.write_error(traceback.format_exc())
Edouard@1407: self.ResetBuildMD5()
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@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)
andrej@1846: except Exception:
Edouard@1407: self.logger.write_error(_("Runtime IO extensions C code generation failed !\n"))
etisserant@203: self.logger.write_error(traceback.format_exc())
laurent@677: self.ResetBuildMD5()
etisserant@24: return False
etisserant@18:
Edouard@728: # Generate C code and compilation params from liraries
Edouard@728: try:
Edouard@728: LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(buildpath)
andrej@1846: except Exception:
Edouard@1407: self.logger.write_error(_("Runtime library extensions C code generation failed !\n"))
Edouard@728: self.logger.write_error(traceback.format_exc())
Edouard@728: self.ResetBuildMD5()
Edouard@728: return False
Edouard@728:
andrej@1758: self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + 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
andrej@1740: open(os.path.join(buildpath, "beremiz.h"), "w").write(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@717: # Insert this file as first file to be compiled at root confnode
andrej@1740: self.LocationCFilesAndCFLAGS[0][1].insert(0, (code_path, self.plcCFLAGS))
andrej@1846: except Exception:
laurent@361: self.logger.write_error(name+_(" generation failed !\n"))
etisserant@203: self.logger.write_error(traceback.format_exc())
laurent@677: self.ResetBuildMD5()
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:
andrej@1730: row = 1 if from_location[0] < start_row else (from_location[0] - start_row)
andrej@1579: col = 1 if (start_row != from_location[0]) else (from_location[1] - start_col)
andrej@1579: start = (row, col)
andrej@1579:
andrej@1730: row = 1 if to_location[0] < start_row else (to_location[0] - start_row)
andrej@1579: col = 1 if (start_row != to_location[0]) else (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:
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":
laurent@782: if self._IECCodeView is None:
laurent@774: plc_file = self._getIECcodepath()
Edouard@1407:
Laurent@1262: self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
laurent@774: self._IECCodeView.SetTextSyntax("ALL")
laurent@774: self._IECCodeView.SetKeywords(IEC_KEYWORDS)
laurent@774: try:
laurent@774: text = file(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":
laurent@782: if self._IECRawCodeView is None:
laurent@806: controler = MiniTextControler(self._getIECrawcodepath(), self)
Edouard@1407:
Laurent@1262: self._IECRawCodeView = IECCodeViewer(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":
laurent@782: if self._ProjectFilesView is None:
laurent@782: self._ProjectFilesView = FileManagementPanel(self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
Edouard@1407:
laurent@784: extensions = []
laurent@784: 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:
laurent@789: editor_name = editors.keys()[0]
laurent@789: elif len(editors) > 0:
laurent@789: names = 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]()
laurent@789: self._FileEditors[filepath] = editor(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
laurent@784: if view in 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:
etisserant@203: def UpdateMethodsFromPLCStatus(self):
andrej@1555: updated = False
laurent@486: status = None
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)
laurent@486: if status is None:
Laurent@1116: self._SetConnector(None, False)
etisserant@203: status = "Disconnected"
andrej@1828: if self.previous_plcstate != status:
ed@446: for args in {
andrej@1740: "Started": [("_Run", False),
andrej@1739: ("_Stop", True)],
andrej@1740: "Stopped": [("_Run", True),
andrej@1739: ("_Stop", False)],
andrej@1740: "Empty": [("_Run", False),
andrej@1739: ("_Stop", False)],
andrej@1740: "Broken": [],
andrej@1740: "Disconnected": [("_Run", False),
andrej@1739: ("_Stop", False),
andrej@1739: ("_Transfer", False),
andrej@1739: ("_Connect", True),
andrej@1739: ("_Disconnect", False)],
andrej@1740: }.get(status, []):
ed@446: self.ShowMethod(*args)
Edouard@922: self.previous_plcstate = status
Laurent@918: if self.AppFrame is not None:
andrej@1555: updated = True
Laurent@918: self.AppFrame.RefreshStatusToolBar()
Laurent@1000: if status == "Disconnected":
andrej@1587: self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 1)
Laurent@1000: self.AppFrame.ConnectionStatusBar.SetStatusText('', 2)
Laurent@1000: else:
Laurent@1000: self.AppFrame.ConnectionStatusBar.SetStatusText(
Laurent@1000: _("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), 1)
andrej@1587: self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 2)
andrej@1555: return updated
Edouard@1407:
andrej@1587: def GetTextStatus(self, status):
andrej@1587: msgs = {
andrej@1587: "Started": _("Started"),
andrej@1587: "Stopped": _("Stopped"),
andrej@1587: "Empty": _("Empty"),
andrej@1587: "Broken": _("Broken"),
andrej@1587: "Disconnected": _("Disconnected")
andrej@1587: }
andrej@1587: return msgs.get(status, status)
andrej@1730:
andrej@1744: def ShowPLCProgress(self, status="", progress=0):
andrej@1574: self.AppFrame.ProgressStatusBar.Show()
andrej@1587: self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(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 = ""
andrej@1574: 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@1407: buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
andrej@1847: [list() for dummy in xrange(len(self.TracedIECPath))])
Laurent@1363: ticks, self.DebugTicks = self.DebugTicks, []
Laurent@1363: return ticks, buffers
Edouard@1407:
etisserant@239: def RegisterDebugVarToConnector(self):
andrej@1742: self.DebugTimer = None
etisserant@239: Idxs = []
etisserant@239: self.TracedIECPath = []
Edouard@1433: self.TracedIECTypes = []
etisserant@239: if self._connector is not None:
etisserant@239: self.IECdebug_lock.acquire()
etisserant@239: IECPathsToPop = []
andrej@1740: for IECPath, data_tuple in self.IECdebug_datas.iteritems():
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
andrej@1740: Idx, IEC_Type = self._IECPathToIdx.get(IECPath, (None, None))
etisserant@239: if Idx is not None:
Edouard@1407: if IEC_Type in DebugTypesSize:
Edouard@592: Idxs.append((Idx, IEC_Type, fvalue, IECPath))
Edouard@592: else:
andrej@1734: self.logger.write_warning(_("Debug: Unsupported type to debug '%s'\n") % IEC_Type)
etisserant@239: else:
andrej@1734: self.logger.write_warning(_("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@1433: IdxsT = zip(*Idxs)
Edouard@1433: self.TracedIECPath = IdxsT[3]
Edouard@1433: self.TracedIECTypes = IdxsT[1]
Edouard@1433: self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
edouard@465: else:
edouard@465: self.TracedIECPath = []
edouard@465: self._connector.SetTraceVariablesList([])
Laurent@1363: self.SnapshotAndResetDebugValuesBuffers()
etisserant@239: self.IECdebug_lock.release()
Edouard@1407:
Laurent@1146: def IsPLCStarted(self):
Laurent@1146: return self.previous_plcstate == "Started"
Edouard@1407:
lbessard@243: def ReArmDebugRegisterTimer(self):
lbessard@243: if self.DebugTimer is not None:
lbessard@243: self.DebugTimer.cancel()
Edouard@1407:
Laurent@1089: # Prevent to call RegisterDebugVarToConnector when PLC is not started
Laurent@1089: # If an output location var is forced it's leads to segmentation fault in runtime
Laurent@1089: # Links between PLC located variables and real variables are not ready
Laurent@1146: if self.IsPLCStarted():
Laurent@1089: # Timer to prevent rapid-fire when registering many variables
Laurent@1089: # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
andrej@1744: self.DebugTimer = Timer(0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector])
Laurent@1089: # Rearm anti-rapid-fire timer
Laurent@1089: self.DebugTimer.start()
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: self.IECdebug_lock.acquire()
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
andrej@1878: None,
andrej@1878: buffer_list] # Forced value
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:
etisserant@239: self.IECdebug_lock.release()
Edouard@1407:
lbessard@243: self.ReArmDebugRegisterTimer()
Edouard@1407:
etisserant@239: return IECdebug_data[1]
etisserant@239:
etisserant@239: def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
lbessard@243: self.IECdebug_lock.acquire()
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,
Edouard@1430: IECdebug_data[0].itervalues(),
Laurent@1365: False)
lbessard@243: self.IECdebug_lock.release()
lbessard@243:
lbessard@243: self.ReArmDebugRegisterTimer()
etisserant@239:
lbessard@334: def UnsubscribeAllDebugIECVariable(self):
lbessard@334: self.IECdebug_lock.acquire()
Laurent@1089: self.IECdebug_datas = {}
lbessard@334: self.IECdebug_lock.release()
lbessard@334:
greg@355: self.ReArmDebugRegisterTimer()
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: self.IECdebug_lock.acquire()
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:
laurent@474: self.IECdebug_lock.release()
Edouard@1407:
laurent@474: self.ReArmDebugRegisterTimer()
Edouard@1407:
laurent@463: def ReleaseDebugIECVariable(self, IECPath):
andrej@1775: if IECPath not in self.IECdebug_datas:
laurent@474: return
Edouard@1407:
laurent@474: self.IECdebug_lock.acquire()
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:
laurent@474: self.IECdebug_lock.release()
Edouard@1407:
laurent@474: self.ReArmDebugRegisterTimer()
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))
andrej@1740: for weakcallable, buffer_list in WeakCallableDict.iteritems():
greg@355: function = getattr(weakcallable, function_name, None)
greg@355: if function is not None:
Laurent@1365: if buffer_list:
Edouard@1430: function(*cargs)
laurent@474: else:
Edouard@1430: function(*tuple([lst[-1] for lst in cargs]))
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:
etisserant@235: def DebugThreadProc(self):
etisserant@239: """
etisserant@239: This thread waid PLC debug data, and dispatch them to subscribers
etisserant@239: """
etisserant@286: self.debug_break = False
edouard@461: debug_getvar_retry = 0
etisserant@286: while (not self.debug_break) and (self._connector is not None):
Edouard@1434: plc_status, Traces = self._connector.GetTraceVariables()
edouard@578: debug_getvar_retry += 1
andrej@1782: # print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
andrej@1739: if plc_status == "Started":
Edouard@1434: if len(Traces) > 0:
Edouard@1434: self.IECdebug_lock.acquire()
andrej@1739: for debug_tick, debug_buff in Traces:
Edouard@1434: debug_vars = UnpackDebugBuffer(debug_buff, self.TracedIECTypes)
andrej@1766: if debug_vars is not None and len(debug_vars) == len(self.TracedIECPath):
Edouard@1434: for IECPath, values_buffer, value in izip(
Edouard@1434: self.TracedIECPath,
Edouard@1434: self.DebugValuesBuffers,
Edouard@1434: debug_vars):
andrej@1760: IECdebug_data = self.IECdebug_datas.get(IECPath, None) # FIXME get
Edouard@1434: if IECdebug_data is not None and value is not None:
Edouard@1434: forced = IECdebug_data[2:4] == ["Forced", value]
Edouard@1434: if not IECdebug_data[4] and len(values_buffer) > 0:
Edouard@1434: values_buffer[-1] = (value, forced)
Edouard@1434: else:
Edouard@1434: values_buffer.append((value, forced))
Edouard@1434: self.DebugTicks.append(debug_tick)
Edouard@1434: debug_getvar_retry = 0
Edouard@1434: self.IECdebug_lock.release()
Edouard@1434:
edouard@578: if debug_getvar_retry != 0:
Edouard@1434: # Be patient, tollerate PLC to come with fresh samples
edouard@460: time.sleep(0.1)
edouard@578: else:
edouard@578: self.debug_break = True
Edouard@689: self.logger.write(_("Debugger disabled\n"))
Edouard@689: self.DebugThread = None
Laurent@1363: if self.DispatchDebugValuesTimer is not None:
Laurent@1363: self.DispatchDebugValuesTimer.Stop()
Laurent@1363:
Laurent@1363: def DispatchDebugValuesProc(self, event):
Laurent@1363: self.IECdebug_lock.acquire()
Laurent@1363: debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers()
Laurent@1363: self.IECdebug_lock.release()
Laurent@1366: start_time = time.time()
Laurent@1363: if len(self.TracedIECPath) == len(buffers):
Edouard@1433: for IECPath, values in izip(self.TracedIECPath, buffers):
Laurent@1363: if len(values) > 0:
Laurent@1363: self.CallWeakcallables(IECPath, "NewValues", debug_ticks, values)
Laurent@1363: if len(debug_ticks) > 0:
Laurent@1363: self.CallWeakcallables("__tick__", "NewDataAvailable", debug_ticks)
Edouard@1407:
Laurent@1366: delay = time.time() - start_time
Laurent@1366: next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay)
Laurent@1366: if self.DispatchDebugValuesTimer is not None and self.DebugThread is not None:
Laurent@1366: self.DispatchDebugValuesTimer.Start(
Laurent@1366: int(next_refresh * 1000), oneShot=True)
Laurent@1363: event.Skip()
etisserant@235:
etisserant@286: def KillDebugThread(self):
laurent@703: tmp_debugthread = self.DebugThread
etisserant@286: self.debug_break = True
laurent@703: if tmp_debugthread is not None:
Edouard@624: self.logger.writeyield(_("Stopping debugger...\n"))
laurent@703: tmp_debugthread.join(timeout=5)
laurent@703: if tmp_debugthread.isAlive() and self.logger:
Edouard@624: self.logger.write_warning(_("Couldn't stop debugger.\n"))
edouard@578: else:
Edouard@624: self.logger.write(_("Debugger stopped.\n"))
etisserant@286: self.DebugThread = None
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@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@578: if self.DebugThread is None:
edouard@578: self.DebugThread = Thread(target=self.DebugThreadProc)
edouard@578: self.DebugThread.start()
Edouard@1407:
edouard@462: def _Run(self):
etisserant@203: """
edouard@464: Start PLC
etisserant@203: """
greg@350: if self.GetIECProgramsAndVariables():
edouard@462: self._connector.StartPLC()
edouard@464: self.logger.write(_("Starting PLC\n"))
edouard@465: self._connect_debug()
etisserant@203: else:
edouard@464: self.logger.write_error(_("Couldn't start PLC !\n"))
laurent@675: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
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:
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: wx.Yield()
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@922: # don't accept re-connetion if already connected
etisserant@203: if self._connector is not None:
laurent@361: self.logger.write_error(_("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@740: dialog = DiscoveryDialog(self.AppFrame)
Edouard@740: answer = dialog.ShowModal()
Edouard@740: uri = 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))
andrej@1846: except Exception:
andrej@1734: self.logger.write_error(_("Exception while connecting %s!\n") % uri)
etisserant@203: self.logger.write_error(traceback.format_exc())
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:
etisserant@203: self.ShowMethod("_Connect", False)
etisserant@203: self.ShowMethod("_Disconnect", True)
etisserant@203: self.ShowMethod("_Transfer", True)
etisserant@203:
etisserant@203: self.CompareLocalAndRemotePLC()
Edouard@1407:
greg@350: # Init with actual PLC status and print it
ed@446: self.UpdateMethodsFromPLCStatus()
andrej@1740: if self.previous_plcstate in ["Started", "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@922: self.logger.write_warning(_("Debug does not match PLC - stop/transfert/start to re-enable\n"))
etisserant@203:
etisserant@203: def CompareLocalAndRemotePLC(self):
etisserant@203: if self._connector is None:
etisserant@203: return
etisserant@203: # We are now connected. Update button status
etisserant@203: MD5 = self.GetLastBuildMD5()
etisserant@203: # Check remote target PLC correspondance to that md5
etisserant@203: if MD5 is not None:
etisserant@203: if not self._connector.MatchMD5(MD5):
andrej@1779: # self.logger.write_warning(
andrej@1779: # _("Latest build does not match with target, please transfer.\n"))
etisserant@203: self.EnableMethod("_Transfer", True)
etisserant@203: else:
andrej@1779: # self.logger.write(
andrej@1779: # _("Latest build matches target, no transfer needed.\n"))
etisserant@203: self.EnableMethod("_Transfer", True)
edouard@465: # warns controller that program match
edouard@465: self.ProgramTransferred()
andrej@1782: # self.EnableMethod("_Transfer", False)
etisserant@203: else:
andrej@1779: # self.logger.write_warning(
andrej@1779: # _("Cannot compare latest build to target. Please build.\n"))
etisserant@203: self.EnableMethod("_Transfer", False)
etisserant@203:
etisserant@203: def _Disconnect(self):
Laurent@978: self._SetConnector(None)
Edouard@1407:
etisserant@203: def _Transfer(self):
Edouard@1407: # Get the last build PLC's
etisserant@203: MD5 = self.GetLastBuildMD5()
Edouard@1407:
Edouard@1407: # Check if md5 file is empty : ask user to build PLC
andrej@1739: if MD5 is None:
laurent@361: self.logger.write_error(_("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:
etisserant@203: # Get temprary directory path
laurent@757: extrafiles = []
laurent@757: for extrafilespath in [self._getExtraFilesPath(),
laurent@757: self._getProjectFilesPath()]:
Edouard@1407:
laurent@757: extrafiles.extend(
andrej@1878: [(name, open(os.path.join(extrafilespath, name),
andrej@1878: 'rb').read())
andrej@1878: for name in os.listdir(extrafilespath)])
Edouard@1407:
etisserant@203: # Send PLC on target
etisserant@203: builder = self.GetBuilder()
etisserant@203: if builder is not None:
etisserant@203: data = builder.GetBinaryCode()
andrej@1739: if data is not None:
laurent@707: if self._connector.NewPLC(MD5, data, extrafiles) and self.GetIECProgramsAndVariables():
lbessard@334: self.UnsubscribeAllDebugIECVariable()
lbessard@246: self.ProgramTransferred()
laurent@692: if self.AppFrame is not None:
laurent@692: self.AppFrame.CloseObsoleteDebugTabs()
laurent@746: self.AppFrame.RefreshPouInstanceVariablesPanel()
laurent@361: self.logger.write(_("Transfer completed successfully.\n"))
andrej@1752: self.AppFrame.LogViewer.ResetLogCounters()
etisserant@203: else:
laurent@361: self.logger.write_error(_("Transfer failed\n"))
andrej@1730: self.HidePLCProgress()
etisserant@203: else:
laurent@415: self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
laurent@415:
laurent@675: wx.CallAfter(self.UpdateMethodsFromPLCStatus)
etisserant@105:
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: {
andrej@1739: "bitmap": "Disconnect",
andrej@1739: "name": _("Disconnect"),
andrej@1739: "tooltip": _("Disconnect from PLC"),
andrej@1739: "method": "_Disconnect",
andrej@1739: "shown": False,
andrej@1739: },
andrej@1739: {
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)()