Laurent@814: # -*- coding: utf-8 -*- Laurent@814: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. Laurent@814: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD Laurent@814: # andrej@1571: # See COPYING file for copyrights details. Laurent@814: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. Laurent@814: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. Laurent@814: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Laurent@814: andrej@1881: andrej@1881: from __future__ import absolute_import Laurent@814: import re Laurent@814: import datetime andrej@2434: from builtins import str as text Laurent@814: Laurent@814: import wx Laurent@814: andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: # Helpers andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: andrej@1739: LOCATIONDATATYPES = {"X": ["BOOL"], andrej@1739: "B": ["SINT", "USINT", "BYTE", "STRING"], andrej@1739: "W": ["INT", "UINT", "WORD", "WSTRING"], andrej@1739: "D": ["DINT", "UDINT", "REAL", "DWORD"], andrej@1739: "L": ["LINT", "ULINT", "LREAL", "LWORD"]} Laurent@814: andrej@1736: Laurent@814: def gen_get_function(f): Laurent@814: def get_function(v): Laurent@814: try: Laurent@814: return f(v) andrej@1780: except Exception: Laurent@814: return None Laurent@814: return get_function Laurent@814: andrej@1736: Laurent@878: def gen_get_string(delimiter): Laurent@878: STRING_MODEL = re.compile("%(delimiter)s([^%(delimiter)s]*)%(delimiter)s$" % {"delimiter": delimiter}) andrej@1750: Laurent@878: def get_string(v): Laurent@878: result = STRING_MODEL.match(v) Laurent@878: if result is not None: Laurent@878: return result.group(1) Laurent@878: return None Laurent@878: return get_string Laurent@878: andrej@1749: Laurent@814: getinteger = gen_get_function(int) Laurent@814: getfloat = gen_get_function(float) Laurent@878: getstring = gen_get_string("'") Laurent@878: getwstring = gen_get_string('"') Laurent@814: Laurent@814: SECOND = 1000000 Laurent@814: MINUTE = 60 * SECOND Laurent@814: HOUR = 60 * MINUTE Laurent@814: DAY = 24 * HOUR Laurent@814: andrej@2439: IEC_TIME_MODEL = re.compile(r"(?:(?:T|TIME)#)?(-)?(?:(%(float)s)D_?)?(?:(%(float)s)H_?)?(?:(%(float)s)M(?!S)_?)?(?:(%(float)s)S_?)?(?:(%(float)s)MS)?$" % {"float": r"[0-9]+(?:\.[0-9]+)?"}) andrej@2439: IEC_DATE_MODEL = re.compile(r"(?:(?:D|DATE)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})$") andrej@2439: IEC_DATETIME_MODEL = re.compile(r"(?:(?:DT|DATE_AND_TIME)#)?([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)$") andrej@2439: IEC_TIMEOFDAY_MODEL = re.compile(r"(?:(?:TOD|TIME_OF_DAY)#)?([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]+)?)$") Laurent@814: andrej@1736: andrej@1730: def gettime(v): Laurent@814: result = IEC_TIME_MODEL.match(v.upper()) Laurent@814: if result is not None: Laurent@814: negative, days, hours, minutes, seconds, milliseconds = result.groups() Laurent@814: microseconds = 0 Laurent@814: not_null = False Laurent@814: for value, factor in [(days, DAY), Laurent@814: (hours, HOUR), Laurent@814: (minutes, MINUTE), Laurent@814: (seconds, SECOND), Laurent@814: (milliseconds, 1000)]: Laurent@814: if value is not None: Laurent@814: microseconds += float(value) * factor Laurent@814: not_null = True Laurent@814: if not not_null: Laurent@814: return None Laurent@814: if negative is not None: Laurent@814: microseconds = -microseconds Laurent@814: return datetime.timedelta(microseconds=microseconds) andrej@1730: andrej@1730: else: Laurent@814: return None Laurent@814: andrej@1736: Laurent@814: def getdate(v): Laurent@814: result = IEC_DATE_MODEL.match(v.upper()) Laurent@814: if result is not None: Laurent@814: year, month, day = result.groups() Laurent@814: try: Laurent@814: date = datetime.datetime(int(year), int(month), int(day)) andrej@1846: except ValueError: Laurent@814: return None Laurent@814: base_date = datetime.datetime(1970, 1, 1) Laurent@814: return date - base_date andrej@1730: else: Laurent@814: return None Laurent@814: andrej@1736: Laurent@814: def getdatetime(v): Laurent@814: result = IEC_DATETIME_MODEL.match(v.upper()) Laurent@814: if result is not None: Laurent@814: year, month, day, hours, minutes, seconds = result.groups() Laurent@814: try: Laurent@941: date = datetime.datetime(int(year), int(month), int(day), int(hours), int(minutes), int(float(seconds)), int((float(seconds) * SECOND) % SECOND)) andrej@1846: except ValueError: Laurent@814: return None Laurent@814: base_date = datetime.datetime(1970, 1, 1) Laurent@814: return date - base_date andrej@1730: else: Laurent@814: return None Laurent@814: andrej@1736: Laurent@814: def gettimeofday(v): Laurent@814: result = IEC_TIMEOFDAY_MODEL.match(v.upper()) Laurent@814: if result is not None: Laurent@814: hours, minutes, seconds = result.groups() Laurent@814: microseconds = 0 Laurent@814: for value, factor in [(hours, HOUR), Laurent@814: (minutes, MINUTE), Laurent@814: (seconds, SECOND)]: Laurent@814: microseconds += float(value) * factor Laurent@814: return datetime.timedelta(microseconds=microseconds) Laurent@814: else: Laurent@814: return None Laurent@814: andrej@1749: Laurent@814: GetTypeValue = {"BOOL": lambda x: {"TRUE": True, "FALSE": False, "0": False, "1": True}.get(x.upper(), None), Laurent@814: "SINT": getinteger, Laurent@814: "INT": getinteger, Laurent@814: "DINT": getinteger, Laurent@814: "LINT": getinteger, Laurent@814: "USINT": getinteger, Laurent@814: "UINT": getinteger, Laurent@814: "UDINT": getinteger, Laurent@814: "ULINT": getinteger, Laurent@814: "BYTE": getinteger, Laurent@814: "WORD": getinteger, Laurent@814: "DWORD": getinteger, Laurent@814: "LWORD": getinteger, Laurent@814: "REAL": getfloat, Laurent@814: "LREAL": getfloat, Laurent@814: "STRING": getstring, Laurent@878: "WSTRING": getwstring, Laurent@814: "TIME": gettime, Laurent@814: "DATE": getdate, Laurent@814: "DT": getdatetime, Laurent@814: "TOD": gettimeofday} Laurent@814: andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: # Force Variable Dialog andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: andrej@1736: andrej@2508: class ForceVariableDialog(wx.Dialog): andrej@2508: """Dialog to enforce new value for variables in debug panel""" Laurent@814: Laurent@814: def __init__(self, parent, iec_type, defaultValue=""): andrej@2513: """ andrej@2513: Constructor andrej@2513: @param parent: Parent wx.Window of dialog for modal andrej@2513: @param iec_type: IEC type of variable (string). For example 'BOOL', 'LREAL'. andrej@2513: @param defaultValue: current variable value as string. Default is empty string. andrej@2513: """ andrej@2508: wx.Dialog.__init__( andrej@1768: self, parent, andrej@2508: name='ForceVariableDialog', andrej@2508: title=_("Please enter value for a \"%s\" variable:") % iec_type, andrej@2508: style=wx.DEFAULT_DIALOG_STYLE, pos=wx.DefaultPosition) andrej@1730: andrej@1730: self.IEC_Type = iec_type andrej@2508: info_sizer = wx.BoxSizer(wx.VERTICAL) andrej@2508: andrej@2508: message_label = wx.StaticText(self, label=_("Forcing Variable Value")) andrej@2508: info_sizer.AddWindow(message_label, border=10, andrej@2508: flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) andrej@2508: andrej@2513: if GetTypeValue[self.IEC_Type] in [getinteger, getfloat]: andrej@2513: self.InitCtrlNumber(info_sizer, defaultValue) andrej@2513: elif self.IEC_Type == "BOOL": andrej@2513: self.InitCtrlBool(info_sizer, defaultValue) andrej@2513: else: andrej@2513: self.InitCtrlDefault(info_sizer, defaultValue) andrej@2513: self.GetEnteredValue = self.GetValueDefault andrej@1701: andrej@2508: button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE) andrej@2508: self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton()) andrej@2508: info_sizer.AddSizer(button_sizer, border=10, flag=wx.ALIGN_RIGHT | wx.ALL) andrej@2508: andrej@2508: self.SetSizer(info_sizer) andrej@1701: self.Fit() andrej@2513: self.ValueCtrl.SetFocus() andrej@2513: andrej@2513: # --------------------------------- andrej@2513: # default type methods andrej@2513: # --------------------------------- andrej@2513: andrej@2513: def InitCtrlDefault(self, info_sizer, defaultValue): andrej@2513: """Add simple text control to change variable of any type""" andrej@2513: self.ValueCtrl = wx.TextCtrl(self) andrej@2513: self.ValueCtrl.SetValue(defaultValue) andrej@2513: info_sizer.AddWindow(self.ValueCtrl, border=10, proportion=1, andrej@2513: flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) andrej@2513: andrej@2513: def GetValueDefault(self): andrej@2513: """ andrej@2513: Returns text representation for a variable value andrej@2513: @return: variable value as a string andrej@2513: """ andrej@2513: return text(self.ValueCtrl.GetValue()) andrej@2513: andrej@2513: # ----------------------------------------------- andrej@2513: # integer and floating point number type methods andrej@2513: # ---------------------------------------------- andrej@2513: andrej@2513: def InitCtrlNumber(self, info_sizer, defaultValue): andrej@2513: """Add controls to change float and integer variables""" andrej@2513: sizer = wx.BoxSizer(wx.HORIZONTAL) andrej@2513: self.InitCtrlDefault(sizer, defaultValue) andrej@2513: self.SpinButtonCtrl = wx.SpinButton(self, style=wx.HORIZONTAL | wx.SP_WRAP) andrej@2513: sizer.AddWindow(self.SpinButtonCtrl, border=10, andrej@2513: flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND) andrej@2513: self.Bind(wx.EVT_SPIN_UP, self.SpinButtonChanged) andrej@2513: self.Bind(wx.EVT_SPIN_DOWN, self.SpinButtonChanged) andrej@2513: info_sizer.AddWindow(sizer, proportion=1, flag=wx.EXPAND) andrej@2513: andrej@2513: def SpinButtonChanged(self, evt): andrej@2513: """Increment/decrement variable value""" andrej@2513: value = self.GetValue() andrej@2513: if value is not None: andrej@2513: up = evt.GetEventType() == wx.EVT_SPIN_UP._getEvtType() andrej@2513: value = value + 1 if up else value - 1 andrej@2513: self.ValueCtrl.SetValue(text(value)) andrej@2513: evt.Skip() andrej@2513: andrej@2513: # ----------------------------------------------- andrej@2513: # bool type related methods andrej@2513: # ---------------------------------------------- andrej@2513: andrej@2513: def InitCtrlBool(self, info_sizer, defaultValue): andrej@2513: """Add button to change value of boolean variable""" andrej@2513: self.ValueCtrl = wx.ToggleButton(self, label=_("Toggle value")) andrej@2513: value = GetTypeValue[self.IEC_Type](defaultValue) andrej@2513: if value is not None: andrej@2513: self.ValueCtrl.SetValue(value) andrej@2513: andrej@2513: info_sizer.AddWindow(self.ValueCtrl, border=10, andrej@2513: flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW) andrej@1701: Laurent@814: def OnOK(self, event): andrej@2511: """ andrej@2511: Checks new entered value andrej@2511: before closing dialog window andrej@2511: """ Laurent@814: message = None andrej@2511: ret = True andrej@2513: value = self.GetEnteredValue() Laurent@814: if value == "": Laurent@814: message = _("You must type a value!") Laurent@814: elif GetTypeValue[self.IEC_Type](value) is None: andrej@1744: message = _("Invalid value \"{a1}\" for \"{a2}\" variable!").format(a1=value, a2=self.IEC_Type) Laurent@814: if message is not None: andrej@1745: dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR) Laurent@814: dialog.ShowModal() Laurent@814: dialog.Destroy() andrej@2511: ret = False Laurent@814: else: Laurent@814: self.EndModal(wx.ID_OK) andrej@2511: event.Skip(ret) Laurent@814: Laurent@814: def GetValue(self): andrej@2510: """ andrej@2510: Return new enforce value of particular type andrej@2510: """ andrej@2513: return GetTypeValue[self.IEC_Type](self.GetEnteredValue())