graphics/DebugDataConsumer.py
author Andrey Skvortsov <andrej.skvortzov@gmail.com>
Thu, 17 Nov 2016 13:48:04 +0300
changeset 1565 894f31f8ca64
parent 1365 debc97102b23
child 1571 486f94a8032c
permissions -rw-r--r--
make about dialog boxes use standard wx about dialogs

Any information in dialog can be easily changed without much effort
and the dialog can be easy translated.

Unfortunately on Windows there is no good standard about dialog,
therefore own implementation is used there.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard. 
#
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#General Public License for more details.
#
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import datetime

#-------------------------------------------------------------------------------
#                        Date and Time conversion function
#-------------------------------------------------------------------------------

SECOND = 1000000      # Number of microseconds in one second
MINUTE = 60 * SECOND  # Number of microseconds in one minute
HOUR = 60 * MINUTE    # Number of microseconds in one hour
DAY = 24 * HOUR       # Number of microseconds in one day

# Date corresponding to Epoch (1970 January the first)
DATE_ORIGIN = datetime.datetime(1970, 1, 1)

def get_microseconds(value):
    """
    Function converting time duration expressed in day, second and microseconds
    into one expressed in microseconds
    @param value: Time duration to convert
    @return: Time duration expressed in microsecond
    """
    return float(value.days * DAY + \
                 value.seconds * SECOND + \
                 value.microseconds)
    return 

def generate_time(value):
    """
    Function converting time duration expressed in day, second and microseconds
    into a IEC 61131 TIME literal
    @param value: Time duration to convert
    @return: IEC 61131 TIME literal
    """
    microseconds = get_microseconds(value)
    
    # Get absolute microseconds value and save if it was negative
    negative = microseconds < 0
    microseconds = abs(microseconds)
    
    # TIME literal prefix
    data = "T#"
    if negative:
        data += "-"
    
    # In TIME literal format, it isn't mandatory to indicate null values
    # if no greater non-null values are available. This variable is used to
    # inhibit formatting until a non-null value is found
    not_null = False
    
    for val, format in [
            (int(microseconds) / DAY, "%dd"),                # Days
            ((int(microseconds) % DAY) / HOUR, "%dh"),       # Hours
            ((int(microseconds) % HOUR) / MINUTE, "%dm"),    # Minutes
            ((int(microseconds) % MINUTE) / SECOND, "%ds")]: # Seconds
        
        # Add value to TIME literal if value is non-null or another non-null 
        # value have already be found
        if val > 0 or not_null:
            data += format % val
            
            # Update non-null variable
            not_null = True
    
    # In any case microseconds have to be added to TIME literal 
    data += "%gms" % (microseconds % SECOND / 1000.)
    
    return data

def generate_date(value):
    """
    Function converting time duration expressed in day, second and microseconds
    into a IEC 61131 DATE literal
    @param value: Time duration to convert
    @return: IEC 61131 DATE literal
    """
    return (DATE_ORIGIN + value).strftime("DATE#%Y-%m-%d")

def generate_datetime(value):
    """
    Function converting time duration expressed in day, second and microseconds
    into a IEC 61131 DATE_AND_TIME literal
    @param value: Time duration to convert
    @return: IEC 61131 DATE_AND_TIME literal
    """
    return (DATE_ORIGIN + value).strftime("DT#%Y-%m-%d-%H:%M:%S.%f")

def generate_timeofday(value):
    """
    Function converting time duration expressed in day, second and microseconds
    into a IEC 61131 TIME_OF_DAY literal
    @param value: Time duration to convert
    @return: IEC 61131 TIME_OF_DAY literal
    """
    microseconds = get_microseconds(value)
    
    # TIME_OF_DAY literal prefix
    data = "TOD#"
    
    for val, format in [
            (int(microseconds) / HOUR, "%2.2d:"),               # Hours
            ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),    # Minutes
            ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),  # Seconds
            (microseconds % SECOND, "%6.6d")]:                  # Microseconds
        
        # Add value to TIME_OF_DAY literal
        data += format % val
    
    return data

# Dictionary of translation functions from value send by debugger to IEC 
# literal stored by type
TYPE_TRANSLATOR = {
    "TIME": generate_time,
    "DATE": generate_date,
    "DT": generate_datetime,
    "TOD": generate_timeofday,
    "STRING": lambda v: "'%s'" % v,
    "WSTRING": lambda v: '"%s"' % v,
    "REAL": lambda v: "%.6g" % v,
    "LREAL": lambda v: "%.6g" % v}

#-------------------------------------------------------------------------------
#                            Debug Data Consumer Class
#-------------------------------------------------------------------------------

"""
Class that implements an element that consumes debug values
Value update can be inhibited during the time the associated Debug Viewer is
refreshing
"""

class DebugDataConsumer:
    
    def __init__(self):
        """
        Constructor
        """
        # Debug value and forced flag
        self.Value = None
        self.Forced = False
        
        # Store debug value and forced flag when value update is inhibited
        self.LastValue = None
        self.LastForced = False
        
        # Value IEC data type
        self.DataType = None
        
        # Flag that value update is inhibited
        self.Inhibited = False
    
    def Inhibit(self, inhibit):
        """
        Set flag to inhibit or activate value update
        @param inhibit: Inhibit flag
        """
        # Save inhibit flag
        self.Inhibited = inhibit
        
        # When reactivated update value and forced flag with stored values
        if not inhibit and self.LastValue is not None:
            self.SetForced(self.LastForced)
            self.SetValue(self.LastValue)
            
            # Reset stored values
            self.LastValue = None
            self.LastForced = False
    
    def SetDataType(self, data_type):
        """
        Set value IEC data type
        @param data_type: Value IEC data type
        """
        self.DataType = data_type
    
    def NewValues(self, tick, values, raw="BOOL"):
        """
        Function called by debug thread when a new debug value is available
        @param tick: PLC tick when value was captured
        @param value: Value captured
        @param forced: Forced flag, True if value is forced (default: False)
        @param raw: Data type of values not translated (default: 'BOOL')
        """
        value, forced = values
        
        # Translate value to IEC literal
        if self.DataType != raw:
            value = TYPE_TRANSLATOR.get(self.DataType, str)(value)
        
        # Store value and forced flag when value update is inhibited
        if self.Inhibited:
            self.LastValue = value
            self.LastForced = forced
        
        # Update value and forced flag in any other case
        else:
            self.SetForced(forced)
            self.SetValue(value)
    
    def SetValue(self, value):
        """
        Update value.
        May be overridden by inherited classes
        @param value: New value
        """
        self.Value = value
    
    def GetValue(self):
        """
        Return current value
        @return: Current value
        """
        return self.Value
    
    def SetForced(self, forced):
        """
        Update Forced flag.
        May be overridden by inherited classes
        @param forced: New forced flag
        """
        self.Forced = forced
    
    def IsForced(self):
        """
        Indicate if current value is forced
        @return: Current forced flag
        """
        return self.Forced