
"""Discom Data Export Library for Python

The classes, methods and functions defined in this library are required to access the Discom Data Export API (Historical Data Service / Real-time Data Service)
Please see the scripts 'DemoTestClient.Py', 'DemoTestControlClient.Py' and 'DiscomDataExportConfig.py' for information on how to use this library

$Date: 2021-06-21 17:12:50 +0200 (Mo, 21 Jun 2021) $
$Revision: 27411 $

"""

# standard library
import socket
import json
import sys
import io
import time
import copy
import types
from datetime import date, datetime
from enum import Enum
from enum import IntEnum
from json import JSONEncoder
from json import JSONDecoder

# 3rd party packages
import numpy as np

# local source
import DiscomDataExportConfig as Conf

__author__ = "Pyare Pueschel and Philip-Michael Materna"
__copyright__ = "Copyright 2019, Discom GmbH"

__license__ = "Discom"
__version__ = "1.0.0"
__maintainer__ = "Pyare Pueschel and Philip-Michael Materna"
__email__ = "pyare.pueschel@bksv.com"

class CustomEncoder(JSONEncoder):
    """Custom Json encoder.

    JSONEncoder: The default JSONEncoder. 
    """
    def default(self, o):
        if isinstance(o, datetime):
            return o.isoformat()
        if isinstance(o, unit_t):
            return o.json()
        if isinstance(o, Unit):
            return o.json()
        if isinstance(o, SingleValue):
            return o.json()
        if isinstance(o, Vector):
            return o.json()
        if isinstance(o, Polygon2D):
            return o.json()
        if isinstance(o, VectorList):
            return o.json()
        if isinstance(o, Matrix):
            return o.json()
        if isinstance(o, MasterValueClass):
            return o.json()
        else:
            return o.__dict__

class DiscomJSONEncoder(JSONEncoder):
    """Custom Json encoder.

    JSONEncoder: The default JSONEncoder. 
    """
    def array_to_list(self, obj):
            ret = list()
            for Xentry in obj:
                try:
                    for Yentry in Xentry:
                        ret.append(Yentry)
                except:
                    ret.append(Xentry)
            return ret

    def default(self, obj):
        if isinstance(obj, GuiSettingsList):
            return obj.json()
        if isinstance(obj, GuiSettings):
            return obj.json()
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, Quinter):
            return obj.__dict__
        if isinstance(obj, unit_t):
            return obj.json()
        if isinstance(obj, Unit):
            return obj.json()
        if isinstance(obj, SingleValue):
            return obj.json()
        if isinstance(obj, Vector):
            return obj.json()
        if isinstance(obj, Polygon2D):
            return obj.json()
        if isinstance(obj, VectorList):
            return obj.json()
        if isinstance(obj, Matrix):
            return obj.json()
        if isinstance(obj, MasterValueClass):
            return obj.json()
        if isinstance(obj, MeasurementInfo):
            return obj.json()
        if isinstance(obj, MeasurementDataList):
            return obj.json()
        if isinstance(obj, MeasurementDataIndex):
            return obj.json()
        if isinstance(obj, ErrorList):
            return obj.json()
        if isinstance(obj, ErrorListElement):
            return obj.json()
        if isinstance(obj, np.float32):
            return float(str(obj))
        if isinstance(obj, np.ndarray):
            return self.array_to_list(obj)
        if isinstance(obj, SqlQueryObject):
            return obj.json()
        else:
            return JSONEncoder.default(self,obj)

def custom_json_decoder(dct):
    """Custom Json decoder for the DiscomDataExportLibrary classes.

    dct: DiscomDataExportLibrary class encoded as a JSON dictionary. 

    Note: The decoding of the custom classes requires the dictionary key 'objectName' containing the name of the encoded class
    """
    if 'objectName' in dct:
        if dct['objectName'] == 'ConnectionRequest':
            return ConnectionRequest(dct['ClientName'], dct['ClientType'], dct['Encoding'])
        
        if dct['objectName'] == 'NewTest':
            MeasurementInfoAttribute = MeasurementInfo(dct['Info']['PkIdent'], dct['Info']['TimeOfNextMeasurement'], dct['Info']['TimeStamp'], dct['Info']['FileOffset'], dct['Info']['FileName'], dct['Info']['FilePath'], dct['Info']['StoragePath'], dct['Info']['Serial']
                                                       , dct['Info']['Type'], dct['Info']['TestBench'], dct['Info']['BaseType'], dct['Info']['Result'], dct['Info']['ResultId'], dct['Info']['TestProperty'], dct['Info']['TestKind'], dct['Info']['Ordinal']
                                                       , dct['Info']['ThisLast'])
            return NewTest(MeasurementInfoAttribute, eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'NewMode':
            return NewMode(dct['Name'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'EndOfCommunication':
            return EndOfCommunication(eObjectEncoding(dct['Encoding']))

        if dct['objectName'] == 'EndOfData':
            return EndOfData(dct['Info'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'EndOfTest':
            return EndOfTest(dct['Info'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'EndOfTestEval':
            if dct['SetMeasurementValues'] is None: # Backwards Compatibility
                dct['SetMeasurementValues'] = False
            return EndOfTestEval(bool(dct['GetErrorList']),bool(dct['SetErrorList']),bool(dct['SetMeasurementValues']) ,eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'EndOfModeEval':
            if dct['SetMeasurementValues'] is None: # Backwards Compatibility
                dct['SetMeasurementValues'] = False
            return EndOfModeEval(dct['Info'],bool(dct['GetErrorList']),bool(dct['SetErrorList']),bool(dct['SetMeasurementValues']) ,eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'HistoricalDataQueryInfo':
            return HistoricalDataQueryInfo(dct['NrOfMeasurements'],dct['Index'],eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'SqlQueryObject':
            return SqlQueryObject(dct['ArchiveSearchPath'], dct['Database'], dct['DbConnectionString'], dct['DbCommandTimeout'], dct['ClientId'], dct['DateFrom'], dct['DateUntil'], dct['TopN'], dct['BaseTypeIds'], dct['TypeIds'], dct['TestBenchIds'], dct['ResultIds'], dct['TestKindIds'], dct['PropertyIds'], dct['OnlyLastMeasurement'], dct['ExcludeLastMeasurement'], dct['OrdinalIds'], dct['SerialNumbers'], dct['QuinterRequests'], dct['PkIdents'], dct['ErrorCodes'], dct['ErrorSequence'], dct['ExportErrors'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'ArchiveQueryObject':
            return ArchiveQueryObject(dct['Archives'], dct['ClientId'], dct['DateFrom'], dct['DateUntil'], dct['TopN'], dct['BaseTypeIds'], dct['TypeIds'], dct['TestBenchIds'], dct['ResultIds'], dct['TestKindIds'], dct['PropertyIds'], dct['OnlyLastMeasurement'], dct['ExcludeLastMeasurement'], dct['OrdinalIds'], dct['SerialNumbers'], eObjectEncoding(dct['Encoding']))

        if dct['objectName'] == 'HistoryDataManager':
            return HistoryDataManager(dct['Id'], dct['Name'], dct['QueryObject'], dct['IsProcessingArchives'], dct['ProcessArchivesIndex'], dct['CurrentArchiveName'], dct['ArchivesToBeProcessed'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'HistoryControlManager':
            return HistoryControlManager(dct['Id'], dct['Name'], dct['QueryObject'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'MasterValueClass':
            QuinterAttribute = Quinter(dct['Clavis']['Mode'], dct['Clavis']['Instrument'], dct['Clavis']['InstrumentParam'], dct['Clavis']['Channel'], dct['Clavis']['Signal'], dct['Clavis']['Location'])
            DataTypeAttribute = DataType.Invalid
            SingleValueAttribute = SingleValue(Unit(), 0, [], [])
            if dct['SingleValue'] is not None:
                DataTypeAttribute = DataType.SingleValue
                SingleValueAttribute.Value = dct['SingleValue']['Value']
                SingleValueAttribute.Unit = Unit(dct['SingleValue']['Unit']['Symbol'],dct['SingleValue']['Unit']['SI_Unit'],dct['SingleValue']['Unit']['Exponent'],dct['SingleValue']['Unit']['Power'],dct['SingleValue']['Unit']['Logref'],dct['SingleValue']['Unit']['Flags'])
                i = 0
                if dct['SingleValue']['LimitNames'] is not None:
                    while i < len(dct['SingleValue']['LimitNames']):
                        SingleValueAttribute.LimitNames.append(dct['SingleValue']['LimitNames'][i])
                        i += 1
                SingleValueAttribute.Limits = dct['SingleValue']['Limits']
            
            VectorAttribute = Vector(0, 0, 0, [], Unit(), Unit(), [], [])
            if dct['Vector'] is not None:
                DataTypeAttribute = DataType.Vector
                VectorAttribute.Len = dct['Vector']['Len']
                VectorAttribute.Delta = dct['Vector']['Delta']
                VectorAttribute.Zero = dct['Vector']['Zero']
                VectorAttribute.Delta_Unit = Unit(dct['Vector']['Delta_Unit']['Symbol'],dct['Vector']['Delta_Unit']['SI_Unit'],dct['Vector']['Delta_Unit']['Exponent'],dct['Vector']['Delta_Unit']['Power'],dct['Vector']['Delta_Unit']['Logref'],dct['Vector']['Delta_Unit']['Flags'])
                VectorAttribute.Unit = Unit(dct['Vector']['Unit']['Symbol'],dct['Vector']['Unit']['SI_Unit'],dct['Vector']['Unit']['Exponent'],dct['Vector']['Unit']['Power'],dct['Vector']['Unit']['Logref'],dct['Vector']['Unit']['Flags'])
                i = 0
                if dct['Vector']['LimitNames'] is not None:
                    while i < len(dct['Vector']['LimitNames']):
                        VectorAttribute.LimitNames.append(dct['Vector']['LimitNames'][i])
                        i += 1
                if 'Values' in dct['Vector']:
                    j = 0
                    VectorAttribute.Values = dct['Vector']['Values']
                    if dct['Vector']['Limits'] is not None:
                        while j < len(dct['Vector']['Limits']):
                            VectorAttribute.Limits.append(dct['Vector']['Limits'][j])
                            j += 1

            Polygon2DAttribute = Polygon2D(0, [], Unit(), Unit(), [], [])
            if dct['Polygon2D'] is not None:
                DataTypeAttribute = DataType.Polygon2D
                Polygon2DAttribute.Len = dct['Polygon2D']['Len']
                Polygon2DAttribute.Pos_Unit =  Unit(dct['Polygon2D']['Pos_Unit']['Symbol'],dct['Polygon2D']['Pos_Unit']['SI_Unit'],dct['Polygon2D']['Pos_Unit']['Exponent'],dct['Polygon2D']['Pos_Unit']['Power'],dct['Polygon2D']['Pos_Unit']['Logref'],dct['Polygon2D']['Pos_Unit']['Flags'])
                Polygon2DAttribute.Unit = Unit(dct['Polygon2D']['Unit']['Symbol'],dct['Polygon2D']['Unit']['SI_Unit'],dct['Polygon2D']['Unit']['Exponent'],dct['Polygon2D']['Unit']['Power'],dct['Polygon2D']['Unit']['Logref'],dct['Polygon2D']['Unit']['Flags'])
                i = 0
                if dct['Polygon2D']['LimitNames'] is not None:
                    while i < len(dct['Polygon2D']['LimitNames']):
                        Polygon2DAttribute.LimitNames.append(dct['Polygon2D']['LimitNames'][i])
                        i += 1
                if 'Values' in dct['Polygon2D']:
                    i = 0
                    if dct['Polygon2D']['Values'] is not None:
                        while i < len(dct['Polygon2D']['Values']):
                            Polygon2DAttribute.Values.append((dct['Polygon2D']['Values'][i]['Item1'],dct['Polygon2D']['Values'][i]['Item2']))
                            i += 1
                    j = 0
                    if dct['Polygon2D']['Limits'] is not None:
                        while j < len(dct['Polygon2D']['Limits']):
                            limit = list()
                            while i < len(dct['Polygon2D']['Limits'][j]):
                                limit.append((dct['Polygon2D']['Limits'][j][i]['Item1'],dct['Polygon2D']['Limits'][j][i]['Item2']))
                                i += 1
                            Polygon2DAttribute.Limits.append(limit)
                            j += 1

            VectorListAttribute = VectorList(0, 0, 0, 0, [], Unit(), Unit(), Unit(), [], [])
            if dct['VectorList'] is not None:
                DataTypeAttribute = DataType.VectorList
                VectorListAttribute.Element_Len = dct['VectorList']['Element_Len']
                VectorListAttribute.Len = dct['VectorList']['Len']
                VectorListAttribute.Element_Delta = dct['VectorList']['Element_Delta']
                VectorListAttribute.Pos_Unit = Unit(dct['VectorList']['Pos_Unit']['Symbol'],dct['VectorList']['Pos_Unit']['SI_Unit'],dct['VectorList']['Pos_Unit']['Exponent'],dct['VectorList']['Pos_Unit']['Power'],dct['VectorList']['Pos_Unit']['Logref'],dct['VectorList']['Pos_Unit']['Flags'])
                VectorListAttribute.Unit = Unit(dct['VectorList']['Unit']['Symbol'],dct['VectorList']['Unit']['SI_Unit'],dct['VectorList']['Unit']['Exponent'],dct['VectorList']['Unit']['Power'],dct['VectorList']['Unit']['Logref'],dct['VectorList']['Unit']['Flags'])
                VectorListAttribute.Element_Delta_Unit = Unit(dct['VectorList']['Element_Delta_Unit']['Symbol'],dct['VectorList']['Element_Delta_Unit']['SI_Unit'],dct['VectorList']['Element_Delta_Unit']['Exponent'],dct['VectorList']['Element_Delta_Unit']['Power'],dct['VectorList']['Element_Delta_Unit']['Logref'],dct['VectorList']['Element_Delta_Unit']['Flags'])
                i = 0
                if dct['VectorList']['LimitNames'] is not None:
                    while i < len(dct['VectorList']['LimitNames']):
                        VectorListAttribute.LimitNames.append(dct['VectorList']['LimitNames'][i])
                        i += 1
                if 'Values' in dct['VectorList']:
                    h = 0
                    if dct['VectorList']['Values'] is not None:
                        while h < len(dct['VectorList']['Values']):
                            VectorListAttribute.Values.append(dct['VectorList']['Values'][h])
                            h += 1
                    j = 0
                    if dct['VectorList']['Limits'] is not None:
                        while j < len(dct['VectorList']['Limits']):
                            VectorListAttribute.Limits.append(dct['VectorList']['Limits'][j])
                            j += 1
            MatrixAttribute = Matrix(0, 0, 0, 0, 0, 0, [], Unit(), Unit(), Unit())
            if dct['Matrix'] is not None:
                DataTypeAttribute = DataType.Matrix
                MatrixAttribute.Element_Len = dct['Matrix']['Element_Len']
                MatrixAttribute.Len = dct['Matrix']['Len']
                MatrixAttribute.Delta = dct['Matrix']['Delta']
                MatrixAttribute.Zero = dct['Matrix']['Zero']
                MatrixAttribute.Element_Delta = dct['Matrix']['Element_Delta']
                MatrixAttribute.Element_Zero = dct['Matrix']['Element_Zero']
                MatrixAttribute.Element_Delta_Unit = Unit(dct['Matrix']['Element_Delta_Unit']['Symbol'],dct['Matrix']['Element_Delta_Unit']['SI_Unit'],dct['Matrix']['Element_Delta_Unit']['Exponent'],dct['Matrix']['Element_Delta_Unit']['Power'],dct['Matrix']['Element_Delta_Unit']['Logref'],dct['Matrix']['Element_Delta_Unit']['Flags'])
                MatrixAttribute.Unit = Unit(dct['Matrix']['Unit']['Symbol'],dct['Matrix']['Unit']['SI_Unit'],dct['Matrix']['Unit']['Exponent'],dct['Matrix']['Unit']['Power'],dct['Matrix']['Unit']['Logref'],dct['Matrix']['Unit']['Flags'])
                MatrixAttribute.Delta_Unit = Unit(dct['Matrix']['Delta_Unit']['Symbol'],dct['Matrix']['Delta_Unit']['SI_Unit'],dct['Matrix']['Delta_Unit']['Exponent'],dct['Matrix']['Delta_Unit']['Power'],dct['Matrix']['Delta_Unit']['Logref'],dct['Matrix']['Delta_Unit']['Flags'])
                if 'Values' in dct['Matrix']:
                    j = 0
                    if dct['Matrix']['Values'] is not None:
                        while j < len(dct['Matrix']['Values']):
                            MatrixAttribute.Values.append(dct['Matrix']['Values'][j])
                            j += 1
            return MasterValueClass(QuinterAttribute, SingleValueAttribute, VectorAttribute, Polygon2DAttribute, VectorListAttribute, MatrixAttribute, ByteBufferManager(), eObjectEncoding(dct['Encoding']),DataTypeAttribute)
        
        if dct['objectName'] == 'ControlSocketMessage':
            return ControlSocketMessage(dct['Text'], dct['MessageType'], eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'ControlSocketCommand':
            return ControlSocketCommand(dct['ClientId'], eSocketCommandType(dct['CommandType']), eObjectEncoding(dct['Encoding']))
        
        if dct['objectName'] == 'QuinterRequest':
            return QuinterRequest(Quinter(dct['Quinter']['Mode'], dct['Quinter']['Instrument'], dct['Quinter']['InstrumentParam'], dct['Quinter']['Channel'], dct['Quinter']['Signal'], dct['Quinter']['Location']), dct['ExportLimits'], DataTransformation(dct['DataTransformation']['Original_Data'], dct['DataTransformation']['Len'], dct['DataTransformation']['Delta'], dct['DataTransformation']['Zero']), eObjectEncoding(dct['Encoding']), eObjectEncoding(dct['ValuesEncoding']))
    
        if dct['objectName'] == 'GuiSettingsList':
            qrl = GuiSettingsList([])
            qrl.Name =  dct['Name']
            i = 0
            while i < len(dct['GuiSettings']):
                guiSettings = GuiSettings(dct['GuiSettings'][i]['ExportType'])
                qrl.GuiSettings.append(guiSettings)
                i += 1
                 
            return qrl
        
        if dct['objectName'] == 'QuinterRequestList':
            qrl = QuinterRequestList([])
            qrl.ClientId =  dct['ClientId']
            qrl.Encoding =  eObjectEncoding(dct['Encoding'])
            i = 0
            while i < len(dct['QuinterRequests']):
                quinter = Quinter(dct['QuinterRequests'][i]['Quinter']['Mode'], dct['QuinterRequests'][i]['Quinter']['Instrument'], dct['QuinterRequests'][i]['Quinter']['InstrumentParam'], dct['QuinterRequests'][i]['Quinter']['Channel'], dct['QuinterRequests'][i]['Quinter']['Signal'], dct['QuinterRequests'][i]['Quinter']['Location'])
                dataTransformation = DataTransformation(dct['QuinterRequests'][i]['DataTransformation']['Original_Data'], dct['QuinterRequests'][i]['DataTransformation']['Len'], dct['QuinterRequests'][i]['DataTransformation']['Delta'], dct['QuinterRequests'][i]['DataTransformation']['Zero'])
                encoding = eObjectEncoding(dct['QuinterRequests'][i]['Encoding'])
                valuesEncoding = eObjectEncoding(dct['QuinterRequests'][i]['ValuesEncoding'])
                exportLimits = dct['QuinterRequests'][i]['ExportLimits']
                quinterRequest = QuinterRequest(quinter, dataTransformation, exportLimits, encoding, valuesEncoding)
                qrl.QuinterRequests.append(quinterRequest)
                i += 1
                 
            return qrl
        
        if dct['objectName'] == 'ErrorList':
            erl = ErrorList([])
            erl.PurgeOriginalErrors = bool(dct['PurgeOriginalErrors'])
            erl.Encoding = eObjectEncoding(dct['Encoding'])
            i = 0
            while i < len(dct['Errors']):
                quinter = Quinter(dct['Errors'][i]['Quinter']['Mode'], dct['Errors'][i]['Quinter']['Instrument'], dct['Errors'][i]['Quinter']['InstrumentParam'], dct['Errors'][i]['Quinter']['Channel'], dct['Errors'][i]['Quinter']['Signal'], dct['Errors'][i]['Quinter']['Location'])
                encoding = eObjectEncoding(dct['Errors'][i]['Encoding'])
                errorlistelement = ErrorListElement(dct['Errors'][i]['SortingIndex'], dct['Errors'][i]['InternalErrorCode'], dct['Errors'][i]['Value'], dct['Errors'][i]['Limit'], dct['Errors'][i]['Average'], dct['Errors'][i]['Position'], quinter, dct['Errors'][i]['ExternalErrorCode'], dct['Errors'][i]['ErrorText'], dct['Errors'][i]['Priority'], dct['Errors'][i]['Group'], dct['Errors'][i]['ErrorResult'], encoding)
                erl.Errors.append(errorlistelement)
                i += 1

            return erl

    return dct

class eObjectEncoding(IntEnum):
    """Enum which classifies the encoding types.

    JSON = 0        Standard json
    JSONARY = 1     Custom encoding combining standard json for the 'non-data' class members and a binary encoding for the 'large-data' class members of a specific DiscomDataExport class (MasterValueClass)

    Note: Google's ProtocolBuffer encoding can be implemented upon request
    """
    JSON = 0 
    JSONARY = 1

    def __str__(self):
        return str(self.value)
    
    def ReturnInt(self):
        return self.value

class eClientType(IntEnum):
    """Enum which classifies the client types.

    Controller = 0          Control client: Used for controling/monitoring the HistoricalData client
    HistoricalData = 1      Historical data client: Used for sending quinter requests and collecting the corresponding data from the HistoricalDataService
    LiveData = 2            Live (real-time) data client: Used for sending quinter requests and collecting the corresponding data from the TasAlyser (respectively querying and returning error lists)

    Note: The client type is selected when sending a connection request to the server  
    """
    Controller = 0 
    HistoricalData = 1 
    LiveData = 2
    
    def __str__(self):
        return str(self.value)
  
class eResultType(IntEnum):
    """Enum which classifies the result types.

    NOK = 0             Bad measurement
    OK = 1              Good measurement
    NotEvaluated = 2    Not evaluated 
    SystemError = 3     System error

    Note: In addition to returning a result type, the external evaluation may edit or add to the list of errors (detected by the TasAlyser software) 
    """
    NOK = 0 
    OK = 1
    NotEvaluated = 2
    SystemError = 3
    
    def __str__(self):
        return str(self.value)

class eSocketCommandType(IntEnum):
    """Enum which classifies the (control) socket command.

    Info = 0                Info command (the exact info returned upon receiving this command is not yet determined)
    UpdateSingle = 1        Update the info about a single HistoryDataManager object (i.e. the wrapper object of a historical data client on the server side) 
    UpdateAll = 2           Update the info about all HistoryDataManager objects 
    StopSingle = 3          Stops the processing of a SQL query and the associated processing of archives (extracting the data of quinter requests and sending them to the data client) for a specific client
    StopAll = 4             Stops the processing of a SQL query and the associated processing of archives (extracting the data of quinter requests and sending them to the data client) for all connected clients with active processing
    Shutdown = 5            Shuts a specific client down

    Note: A control client can send a ControlSocketCommand (one of its properties is this enum) to the server which in return sends back information or executes different actions. A ControlSocketCommand targets a single data client or all data clients. 
    """
    Info = 0
    UpdateSingle = 1
    UpdateAll = 2
    StopSingle = 3
    StopAll = 4
    Shutdown = 5
    
    def __str__(self):
        return str(self.value)

class unit_t(Enum):
    """Enum which classifies the unit_t types of the data.

    Note: Documentation will follow soon
    """
    u_invalid_e = 0
    u_count_e = 1
    u_x_e = 2
    u_y_e = 3
    u_z_e = 4
    u_N_e = 5
    u_Percent_e = 6
    u_s_e = 7
    u_Hz_e = 8
    u_rpm_e = 9
    u_Rev_e = 10
    u_Ord_e = 11
    u_V_e = 12
    u_Pa_e = 13
    u_Nm_e = 14
    u_C_e = 15
    u_mps2_e = 16
    u_g_e = 17
    u_m_e = 18
    u_degree_e = 19
    u_rad_e = 20
    u_kmh_e = 21
    u_mph_e = 22
    u_mps_e = 23
    u_radps_e = 24
    u_radps2_e = 25
    u_A_e = 26
    u_pu_e = 27
    u_bar_e = 28
    u_DtTm_e = 29
    u_end_e = 30

    def __str__(self):
        return str(self.value)
    def json(self):
        return {'Unit': str(self.value)}

#def ConvertProtMasterValueClass(prot):
#    clavis = Quinter(prot.Clavis.Mode, prot.Clavis.Instrument, prot.Clavis.InstrumentParam, prot.Clavis.Channel, prot.Clavis.Signal, prot.Clavis.Location)
#    mvc = MasterValueClass(clavis)
#    mvc.Encoding = prot.Encoding
#    mvc.SingleValue = SingleValue(prot.SingleValue.Unit, prot.SingleValue.Value)
#    mvc.Vector = Vector(prot.Vector.Len, prot.Vector.Delta, prot.Vector.Zero, [], prot.Vector.Delta_Unit, prot.Vector.Unit)
#    mvc.Polygon2D = Polygon2D(prot.Polygon2D.Len, [], prot.Polygon2D.Pos_Unit, prot.Polygon2D.Unit)
#    mvc.VectorList = VectorList(prot.VectorList.Element_Len, prot.VectorList.Len, prot.VectorList.Element_Delta, prot.VectorList.Element_Zero, [], prot.VectorList.Pos_Unit, prot.VectorList.Unit, prot.VectorList.Element_Delta_Unit)
#    mvc.Matrix = Matrix(prot.Matrix.Element_Len, prot.Matrix.Len, prot.Matrix.Element_Delta, prot.Matrix.Element_Zero, prot.Matrix.Delta, prot.Matrix.Zero, [], prot.Matrix.Element_Delta_Unit, prot.Matrix.Delta_Unit, prot.Matrix.Unit)
#    return mvc

def serialize_protocol_buffer(objectToSerialize):
    """Returns a ProtocolBuffer-serialized object.

    objectToSerialize: The object to serialize. 

    Note: The object which is to be serialized needs to be converted to the corresponding class defined in the ProtocolBufferClass.py
    """
    obj = objectToSerialize.toProtocolBufferObject()
    serializedObject = obj.SerializeToString()
    return serializedObject

def serialize_jsonary(objectToSerialize):
    """Returns a JSONARY-serialized object.

    objectToSerialize: The object to serialize. 

    Note: Since the JSONARY encoding is only for sending large data objects (MasterValueClass) to the client, this method (i.e. the reverse way from the client to the server) is not yet fully implemented
    """
    serializedObject = objectToSerialize
    return serializedObject

def deserialize_jsonary(objectToDeserialize, byteValues, name):
    """Returns a deserialized object encoded with Jsonary.

    objectToDeserialize:    The JSON object to deserialize. 
    byteValues:             The binary encoded data object (usually a float array of measurement values and/or limits to evaluate the values)
    
    Note: The JSONARY encoding only applies to the data members ('Values' and 'Limits') of the sub-classes 'Vector', 'Polygon2D', 'VectorList', and 'Matrix' wrapped in the MasterValueClass object 
    """
    deserializedObject = None
    
    # bytes to string
    rs = objectToDeserialize.decode("utf-8")
    
    # deserialize json-encoded object
    deserializedObject= JSONDecoder(object_hook = custom_json_decoder).decode(rs)
    
    # get float array from byte buffer
    if name == 'MasterValueClass' and byteValues is not None:
        deserializedObject.FillFloatArrays(byteValues)
        
    return deserializedObject

def socket_read_all(socketRead, n):
    """Loops over a socket read until all n bytes are read  

    socketRead: Method, the socket.read method of the socket class
    n:          Int32, the number of bytes to read
    
    Note:  
    """
    data = b''
    while len(data) < n:
        packet = socketRead(n - len(data))
        if not packet:
            return None
        data += packet
    return data

class UtilFunction():
    """Helper class containing static utility methods   

    Note:  
    """
    @staticmethod
    def WriteToFile(buffer, buffer2, filepath):
        """Writes the incoming byte values to file. 

        buffer:     Byte[], the json encoded part of the objects
        buffer2:    Byte[], the binary encoded part of the objects
        filepath:   String, the file path  
    
        Note:  If the file already exists the data is appended to it. The buffer2 object is only used in case of using the JSONARY encoding (mixed encoding) 
        """
        with open(filepath, 'a+b') as fid:
            fid.write(buffer)
            if buffer2 is not None:
                fid.write(buffer2)

class VersionNoMatch(Exception):
    pass

class MessageHeader():
    """Header class which needs to be send ahead of any type of object send over the socket.   

    Note: The header contains all relevant information to decode the object which is sent after the MessageHeader object. The MessageHeader object itself is sent as a hex string.
    """    
    Version = 2

    def __init__(self, nrBytesOfMessage = 0, totalNrBytesOfMessage = 0, messageObjectName = '', objectEncoding = eObjectEncoding.JSON, addInfo = ''):
        self.NrBytesOfFollowingMessage = nrBytesOfMessage               # Int32, the number of bytes of the following object
        self.TotalNrBytesOfFollowingMessage = totalNrBytesOfMessage     # Int32, the total number of bytes of the following object in case the following object is split into two different data packets (which is the case for the JSONARY encoding) 
        self.MessageObjectName = messageObjectName                      # String, the name of the following object
        self.ObjectEncoding = objectEncoding                            # Enum, the type of encoding of the following object
        self.AdditionalInfo = addInfo                                   # String, additional info

    def Send(self, socketSendMethod):
        """Constructs the hex string to be sent over the socket. 

        self:               MessageHeader, object
        socketSendMethod:   Method, the socket send method
    
        Note:   
        """
        s1 = '{0:0{1}x}'.format(self.Version, 4)
        s2 = '{0:0{1}x}'.format(len(self.MessageObjectName), 4)
        s3 = '{0:0{1}x}'.format(self.ObjectEncoding.ReturnInt(), 4)
        s4 = '{0:0{1}x}'.format(self.NrBytesOfFollowingMessage, 8)
        s5 = '{0:0{1}x}'.format(self.TotalNrBytesOfFollowingMessage, 8)
        s6 = '{0:0{1}x}'.format(len(self.AdditionalInfo), 4)
        
        msg = s1 + s2 + self.MessageObjectName + s3 + s4 + s5 + s6 + self.AdditionalInfo
        ms = msg.encode("utf-8")
        socketSendMethod(ms)

    @staticmethod
    def Read(socketRead):
        """Read method to decode an MessageHeader object sent over the socket. 

        socketRead: Method, the socket read method
    
        Note:   
        """
        mh = MessageHeader()

        # read first 4 bytes
        vb = socket_read_all(socketRead,4)
        # if first 4 bytes are null return
        if not vb: return 

        try:
            vs = vb.decode("utf-8")
            # if hex string contains the 0x prefix else use 16 as the base
            mh.Version = int(vs, 16)

            if mh.Version is not MessageHeader().Version:
                raise VersionNoMatch()

            # read next 4 bytes
            lb =  socket_read_all(socketRead,4)
            ls = lb.decode("utf-8") 
            # if hex string contains the 0x prefix else use 16 as the base
            lenStr = int(ls, 16)

            # read next lenStr bytes
            tb = socket_read_all(socketRead,lenStr)
            mh.MessageObjectName = tb.decode("utf-8") 

            # read next 4 bytes
            ob = socket_read_all(socketRead,4)    
            os = ob.decode("utf-8") 
            # if hex string contains the 0x prefix else use 16 as the base
            obje = int(os, 16)
            mh.ObjectEncoding = obje

            # read next 8 bytes
            mb = socket_read_all(socketRead,8)
            ms = mb.decode("utf-8") 
            # if hex string contains the 0x prefix else use 16 as the base
            mh.NrBytesOfFollowingMessage = int(ms, 16)

            # read next 8 bytes
            mb2 = socket_read_all(socketRead,8)
            ms2 = mb2.decode("utf-8") 
            # if hex string contains the 0x prefix else use 16 as the base
            mh.TotalNrBytesOfFollowingMessage = int(ms2, 16)

             # read next 4 bytes
            la =  socket_read_all(socketRead,4)
            laO = la.decode("utf-8") 
            # if hex string contains the 0x prefix else use 16 as the base
            lenStrO = int(laO, 16)

            # read next lenStr bytes
            ta = socket_read_all(socketRead,lenStrO) 
            mh.AdditionalInfo = ta.decode("utf-8") 
        except:
            print("Message Header Read Failed: " + str(vs))
            print(sys.exc_info()[1])
        return mh
    
    @staticmethod
    def WriteFile(filepath, self):
        """Writes the MessageHeader object to file. 

        filepath:   String, file path
        self:       MessageHeader object
    
        Note:   
        """ 
        s1 = '{0:0{1}x}'.format(self.Version, 4)
        s2 = '{0:0{1}x}'.format(len(self.MessageObjectName), 4)
        s3 = '{0:0{1}x}'.format(self.ObjectEncoding.ReturnInt(), 4)
        s4 = '{0:0{1}x}'.format(self.NrBytesOfFollowingMessage, 8)
        s5 = '{0:0{1}x}'.format(self.TotalNrBytesOfFollowingMessage, 8)
        s6 = '{0:0{1}x}'.format(len(self.AdditionalInfo), 4)
        
        msg = s1 + s2 + self.MessageObjectName + s3 + s4 + s5 + s6 + self.AdditionalInfo
        ms = msg.encode("utf-8")
        ms2 = None
        UtilFunction.WriteToFile(ms, ms2, filepath)

class Message():
    """Helper class which wraps the socket send and read depending on the encoding.   

    Note: The class's static methods are utilized in the send and read methods which are implemented in each class that is to be send over the socket.
    """  
    @staticmethod
    def Send(object, name, socketSendMethod, encoding = eObjectEncoding.JSON, additionalInfo = ''):
        """Encodes the object according to the specified encoding and sends the byte stream over the socket. 

        object:             Object, the object which is to be encoded and sent 
        name:               String, the name of the object class
        socketSendMethod:   Method, the socket send method
        encoding:           Enum, the type of encoding
        additionalInfo:     String, any additional info to be passed to the MessageHeader

        Note:      
        """         
        crb = None

        # JSONARY
        if encoding == eObjectEncoding.JSONARY:
            crb = serialize_jsonary(object)
            try:
                len(crb)
            except:
                encoding = eObjectEncoding.JSON
        # JSON
        if encoding == eObjectEncoding.JSON:
            serializedString = json.dumps(object, cls=CustomEncoder)
            # string to bytes
            crb = serializedString.encode("utf-8")

        
        if crb is None: 
            return

        # create and send message header
        mh = MessageHeader(len(crb), len(crb), name, encoding, additionalInfo)
        mh.Send(socketSendMethod)

        # send objetc
        socketSendMethod(crb)

    @staticmethod
    def Read(socketRead, mh, writeToFile = False, filepath = ''):
        """Reads the byte stream from a socket and deserializes to the respective object. 

        socketRead:     Method, the socket read method
        mh:             MessageHeader, object which precedes the object encoded in the byte stream
        writeToFile:    Bool, write to file instead of deserializing to the respective object (according to the MessageHeader)
        filepath:       String, the file path to write to 
        
        Note:      
        """       
        deserializedObject = None
        rb2 = None

        # socket read all 
        rb = socket_read_all(socketRead, mh.NrBytesOfFollowingMessage)
        # in case the data packet is split in 2 different objects: 1. object encoded as json 2. data (float array) encoded as byte array
        if mh.TotalNrBytesOfFollowingMessage != mh.NrBytesOfFollowingMessage:
            rb2 = socket_read_all(socketRead, mh.TotalNrBytesOfFollowingMessage - mh.NrBytesOfFollowingMessage)

        # write to file
        if writeToFile:
            if (mh != None):
                try:
                    mh.WriteFile(filepath, mh)
                    UtilFunction.WriteToFile(rb, rb2, filepath)
                except:
                    print("Error while File Writing: ", sys.exc_info()[1])
        
        # deserialize to the respective object         
        # JSON
        if mh.ObjectEncoding == eObjectEncoding.JSON:
            # bytes to string
            rs = rb.decode("utf-8")
            deserializedObject= JSONDecoder(object_hook = custom_json_decoder).decode(rs)
        # JSONARY
        if mh.ObjectEncoding == eObjectEncoding.JSONARY:
            deserializedObject = deserialize_jsonary(rb, rb2, mh.MessageObjectName)
                
        return deserializedObject

class GuiSettingsList(object):
    """Object used for storing the settings to a JSON file

    Note: The connection request is required to connect to the Discom Export API. Client name and client type (control or data client) are mandatory parameters.  
    """      
    def __init__(self, settings = []):
        self.Settings = settings    # Enum, client type 
    def json(self):
        if self.Settings is not None:
            return {'objectName': self.__class__.__name__, 'Settings': self.Settings}
    
class GuiSettings(object):
    """Object used for storing the settings to a JSON file

    Note: The connection request is required to connect to the Discom Export API. Client name and client type (control or data client) are mandatory parameters.  
    """      
    def __init__(self, name = '', queryType = False, exportType = False, serviceType = True, line = '', servicePath = '', configPath = '', archivePath = '', exportPath = '', archiveQueryPath = '', dateFrom = datetime.now(), dateUntil = datetime.now(), lastN = 0, allOrLast = False, basetype = '', type = '', testbench = '', result = '', location = '', instrument = '', parameter = '', mode = '', channel = ''):
        self.Name = name                            # String, name of the settings
        self.QueryType = queryType                  # Bool, query type
        self.ExportType = exportType                # Bool, export type
        self.ServiceType = serviceType              # Bool, service type
        self.Line = line                            # String, selected line
        self.ConfigPath = configPath                # String, selected configPath
        self.ArchivePath = archivePath              # String, selected archivePath
        self.ExportPath = exportPath                # String, selected exportPath
        self.ArchiveQueryPath = archiveQueryPath    # String, selected archiveQueryPath
        self.ServicePath = servicePath              # String, selected servicePath
        self.DateFrom = dateFrom                    # Datetime, date from
        self.DateUntil = dateUntil                  # Datetime, date until
        self.LastN = lastN                          # Int32, last n measurements
        self.AllOrLast = allOrLast                  # Bool, all or last measurements
        self.Basetype = basetype                    # String, selected base type
        self.Type = type                            # String, selected type
        self.Testbench = testbench                  # String, selected test bench
        self.Result = result                        # String, selected result type
        self.Location = location                    # String, selected location
        self.Instrument = instrument                # String, selected instrument
        self.Parameter = parameter                  # String, selected parameter
        self.Mode = mode                            # String, selected mode
        self.Channel = channel                      # String, selected channel      
    def json(self):
        return {'objectName': self.__class__.__name__, 'Name': self.Name, 'QueryType': self.QueryType, 'ExportType': self.ExportType, 'ServiceType': self.ServiceType,'Line': self.Line, 'ServicePath': self.ServicePath, 'ConfigPath': self.ConfigPath, 'ArchivePath': self.ArchivePath, 'ExportPath': self.ExportPath, 'ArchiveQueryPath': self.ArchiveQueryPath, 
                'DateFrom': self.DateFrom, 'DateUntil': self.DateUntil, 'LastN': self.LastN, 'AllOrLast': self.AllOrLast, 'Basetype': self.Basetype,'Type': self.Type,'Testbench': self.Testbench,'Result': self.Result,
                'Location': self.Location, 'Instrument': self.Instrument, 'Parameter': self.Parameter, 'Mode': self.Mode, 'Channel': self.Channel}

class HistoricalDataQueryInfo(object):
    """Object used to return information about the SQL database query

    """       
    def __init__(self, nrOfMeasurements = 0, index = 0, encoding = eObjectEncoding.JSON):
        self.NrOfMeasurements = nrOfMeasurements                # Int32, total number of measurements returned by the SQL query
        self.Index = index                                      # Int32, index indicating the current step in the archives' processing
        self.Encoding = encoding                                # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;


class SqlQueryObject(object):
    """Object used to query the DISCOM result database (SQL Server database).

    Note: The object's properties define the query filters which are used to subset the archives from which data is requested (in the form of sending QuinterRequest objects)        
    """       
    def __init__(self, archiveSearchPath = '', database = '', dbConnectionString = '', dbCommandTimeout = 60, clientId = '', dateFrom = datetime(2000,1,1) , dateUntil = datetime.now(), topN = 0,  baseTypeIds = [], typeIds = [], testBenchIds = [], resultIds = [], testKindIds = [], propertyIds = [], onlyLastMeasurement = False, excludeLastMeasurement = False, ordinalIds = [], serialNumbers = [], quinterRequests = [], pkIdents = [], errorCodes = [], errorSequence = [], exportErrors = False, encoding = eObjectEncoding.JSON):
        self.ArchiveSearchPath = archiveSearchPath              # String, the path to the location where the measurement files (archive files) are stored
        self.Database = database                                # String, name of the Discom result database
        self.DbConnectionString = dbConnectionString            # String, SQL Server connection string
        self.DbCommandTimeout = dbCommandTimeout                # Int, Database SQL command timeout in seconds
        self.ClientId = clientId                                # String, Client target for the SQL query (data client that queries for specific set of Quinters, receives and processes the data ): If the configuration file is used, the ClientId must be the DataClientName
        self.DateFrom = dateFrom                                # Datetime, time frame: From
        self.DateUntil = dateUntil                              # Datetime, time frame: Until
        self.TopN = topN                                        # Int32, if TopN is greater than zero, the time parameters defined above are ignored (Top N = the last N measurements)
        self.BaseTypeIds = baseTypeIds                          # List<string>, name list of base models/types which should be included in the query (if list is empty all base models/types will be included in the query)
        self.TypeIds = typeIds                                  # List<string>, name list of models/types which should be included in the query
        self.TestBenchIds = testBenchIds                        # List<string>, name list of test benches which should be included in the query
        self.ResultIds = resultIds                              # List<string>, name list of result codes from the database (1 = OK, 2 = NOK, 4 = System Error)
        self.TestKindIds = testKindIds                          # List<string>, name list of test kind codes from the database (1 = Production/normal measurement, 2 = Reference measurement, 3 = Special test, 4 = Test, 5 = Average, 6 = Recode, 7 = WavePlayback, 8 = Evaluation)
        self.PropertyIds = propertyIds                          # List<string>, name list of property codes from the database (1 = Normal measurement, 2 = Repaired)
        self.OnlyLastMeasurement = onlyLastMeasurement          # Bool, only show the last measurement of repetitive measurements
        self.ExcludeLastMeasurement = excludeLastMeasurement    # Bool, exclude the last measurement of repetitive measurements
        self.OrdinalIds = ordinalIds                            # List<int>, Id list of ordinal codes from the database  (1 = First measurement, 2 = second measurement, etc.)
        self.SerialNumbers = serialNumbers                      # List<string>, name list of serials
        self.QuinterRequests = quinterRequests                  # List of quinters that will be queried
        self.PkIdents = pkIdents                                # List<int>, PkIdents that will be queried
        self.ErrorCodes = errorCodes                            # List<string> name list of error codes that will be queried
        self.ErrorSequence = errorSequence                      # List<int>, error sequence that will be queried (0 = first error, 1 = second error, etc.)
        self.ExportErrors = exportErrors                        # Bool, export errors
        self.Encoding = encoding                                # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class ArchiveQueryObject(object):
    """Object used to Open Archives without the SQL Database

    Note: The object's properties define the query filters which are used to subset the archives from which data is requested (in the form of sending QuinterRequest objects)        
    """       
    def __init__(self, archives = [], database = '', clientId = '', dateFrom = datetime(2000,1,1), dateUntil = datetime.now(), topN = 0,  baseTypeIds = [], typeIds = [], testBenchIds = [], resultIds = [], testKindIds = [], propertyIds = [], onlyLastMeasurement = False, excludeLastMeasurement = False, ordinalIds = [], serialNumbers = [], encoding = eObjectEncoding.JSON):
        self.Archives = archives                                # String, the path and name to the location where the measurement files (archive files) are stored
        self.ClientId = clientId                                # String, Client target for the SQL query (data client that queries for specific set of Quinters, receives and processes the data ): If the configuration file is used, the ClientId must be the DataClientName
        self.DateFrom = dateFrom                                # Datetime, time frame: From
        self.DateUntil = dateUntil                              # Datetime, time frame: Until
        self.TopN = topN                                        # Int32, if TopN is greater than zero, the time parameters defined above are ignored (Top N = the last N measurements)
        self.BaseTypeIds = baseTypeIds                          # List<string>, name list of base models/types which should be included in the query (if list is empty all base models/types will be included in the query)
        self.TypeIds = typeIds                                  # List<string>, name list of models/types which should be included in the query
        self.TestBenchIds = testBenchIds                        # List<string>, name list of test benches which should be included in the query
        self.ResultIds = resultIds                              # List<string>, name list of result codes from the database (1 = OK, 2 = NOK, 4 = System Error)
        self.TestKindIds = testKindIds                          # List<string>, name list of test kind codes from the database (1 = Production/normal measurement, 2 = Reference measurement, 3 = Special test, 4 = Test, 5 = Average, 6 = Recode, 7 = WavePlayback, 8 = Evaluation)
        self.PropertyIds = propertyIds                          # List<string>, name list of property codes from the database (1 = Normal measurement, 2 = Repaired)
        self.OnlyLastMeasurement = onlyLastMeasurement          # Bool, only show the last measurement of repetitive measurements
        self.ExcludeLastMeasurement = excludeLastMeasurement    # Bool, exclude the last measurement of repetitive measurements
        self.OrdinalIds = ordinalIds                            # List<int>, Id list of ordinal codes from the database  (1 = First measurement, 2 = second measurement, etc.)
        self.SerialNumbers = serialNumbers                      # List<string>, name list of serials
        self.Encoding = encoding                                # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class HistoryDataManager(object):
   """Object which wraps a historical data client socket connection.

   Note: Upon each successful connection request the socket connection is wrapped in this object class (on the server side). The C# server library exposes additional methods and functions for processing the data client requests. On the client side, this object is only used to pass information to and from the server.        
   """  
   def __init__(self, id, name, queryObject = SqlQueryObject(), isProcessingArchives = False, processArchivesIndex = 0, currentArchiveName = '', archivesToBeProcessed = 0, encoding = eObjectEncoding.JSON):
        self.Id = id                                            # Int32, unique id
        self.Name = name                                        # String, unique name
        self.QueryObject = queryObject                          # SqlQueryObject, query object which is associated with this wrapper class 
        self.IsProcessingArchives = isProcessingArchives        # Bool, indicates whether the server is currently processing a SQL query request (+ QuinterRequests)
        self.ProcessArchivesIndex = processArchivesIndex        # Int32, the index of the currently processed archive in the subset of archives returned by the SQL query
        self.CurrentArchiveName = currentArchiveName            # String, name of the archive which is currently processed
        self.ArchivesToBeProcessed = archivesToBeProcessed      # Int32, total number of archives returned by the SQL query over which the server loops 
        self.Encoding = encoding                                # Enum, object encoding

   def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

   @staticmethod
   def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class HistoryControlManager(object):
   """Object which wraps a historical control client socket connection.

   Note: Each connection request for a control data client is wrapped in this object class (on the server side). The control client is used to control/monitor the data clients. It is not meant to send Quinter requests and receive the corresponding data.       
   """  
   def __init__(self, id, name, queryObject = SqlQueryObject(), encoding = eObjectEncoding.JSON):
        self.Id = id                        # Int32, unique id
        self.Name = name                    # String, unique name
        self.QueryObject = queryObject      # SqlQueryObject, query object which is associated with this wrapper class 
        self.Encoding = encoding            # Enum, object encoding

   def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

   @staticmethod
   def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class Unit(object):
    """Object which wraps a Unit
    """
    def __init__(self, symbol = "", si_unit = unit_t.u_invalid_e, exponent = 1, power = 1, logref = 0, flags = 1):
        self.Symbol = symbol        # String Representation
        self.SI_Unit = si_unit      # die Basis-Einheit aus UnitStrings
        self.Exponent = exponent    # 10er-Exponent
        self.Power = power          # Potenz, z.B. das Quadrat in m/s^2
        self.Logref = logref        # Logarithmische Referenz dieser Einheit (nicht der des Signals!)
        self.Flags = flags          # Bitmaske für Zusatzinformationen. Insbes. zeigt Bit 0 an: ist logarithmisch.

    def json(self):
        if self.Flags == 0:
            return {'Symbol': self.Symbol, 'Logref': self.Logref}
        else:
            return {'Symbol': self.Symbol}

def GetUnit(Unit):
    """Returns a String Text Representation of a Unit

    Unit: Enum Int of Unit

    Note: The object which is to be serialized needs to be converted to the corresponding class defined in the ProtocolBufferClass.py
    """
    return Unit.Symbol

def DefineDefaultSingleValue():
    return SingleValue(Unit(),0,[],[])

class SingleValue(object):
    """Object which wraps a Single Value.
    
    Note: This data class is only sent over the socket as part of the MasterValueClass      
    """     
    def __init__(self, unit = Unit(), value = 0, limits = [], limitNames = []):
        self.Value = value              # Single, measured value (x)
        self.Unit = unit                # uint32 enum (unit_t), unit of measured value
        self.Limits = limits            # List<single>, limits against which the measured value is evaluated 
        self.LimitNames = limitNames    # string [], array of limit names
    def json(self):
        return {'Value': self.Value, 'Unit': self.Unit, 'Limits': self.Limits, 'LimitNames': self.LimitNames}

def DefineDefaultVector():
    return Vector(0,0,0,[],Unit(),Unit(),[],[])

class Vector(object):
    """Object which wraps a Vector.
    
    Note: This data class is only sent over the socket as part of the MasterValueClass      
    """    
    def __init__(self, len = 0, delta = 0, zero = 0, values = [], delta_unit = Unit(), unit = Unit(), limits = [], limitNames = []):
         self.Len = len                  # Uint32, number of data points
         self.Delta = delta              # Single, increment between data points
         self.Zero = zero                # Single, starting point of the vector
         self.Values = values            # Single array, array of measured values (x)
         self.Delta_Unit = delta_unit    # uint32 enum (unit_t) 
         self.Unit = unit                # uint32 enum (unit_t)
         self.Limits = limits            # List<single []>, limits against which the measured values are evaluated
         self.LimitNames = limitNames    # string [], array of limit names
    def json(self):
        return {'Len': self.Len, 'Delta': self.Delta, 'Zero': self.Zero, 'Unit': self.Unit,'Delta_Unit': self.Delta_Unit,
                'Values': self.Values, 'Limits': self.Limits, 'LimitNames': self.LimitNames}
   
def DefineDefaultPolygon2D():
    return Polygon2D(0,[],Unit(),Unit(),[],[])

class Polygon2D(object):
    """Object which wraps a Polygon2D.
    
    Note: This data class is only sent over the socket as part of the MasterValueClass      
    """     
    def __init__(self, len = 0, values = [], pos_unit = Unit(), unit = Unit(), limits = [], limitNames = []):
         self.Len = len                  # Uint32, number of data points
         self.Values = values            # List<Tuple<single,single>>, tuple consists of measured value vs. control value (x, y)
         self.Pos_Unit = pos_unit        # uint32 enum (unit_t)
         self.Unit = unit                # uint32 enum (unit_t)
         self.Limits = limits            # List<List<Tuple<single,single>>>, limits against which the measured values are evaluated
         self.LimitNames = limitNames    # string [], array of limit names
    def json(self):
        return {'Len': self.Len, 'Unit': self.Unit,'Pos_Unit': self.Pos_Unit,'Values': self.Values, 'Limits': self.Limits, 'LimitNames': self.LimitNames}

def DefineDefaultVectorList():
    return VectorList(0,0,0,0,[],Unit(),Unit(),Unit(),[],[])

class VectorList(object):
    """Object which wraps a VectorList.
    
    Note: This data class is only sent over the socket as part of the MasterValueClass      
    """     
    def __init__(self, element_len = 0, len = 0, element_delta = 0, element_zero = 0, values = [], pos_unit = Unit(), unit = Unit(), element_delta_unit = Unit(), limits = [], limitNames = []):
         self.Element_Len = element_len                  # Uint32, number of vectors
         self.Len = len                                  # Uint32, number of data points
         self.Element_Delta = element_delta              # Single, increment between vectors
         self.Element_Zero = element_zero                # Single, starting point of the first vector
         self.Values = values                            # List<single []>, list of arrays of measured values (x)
         self.Pos_Unit = pos_unit                        # uint32 enum (unit_t)
         self.Unit = unit                                # uint32 enum (unit_t) 
         self.Element_Delta_Unit = element_delta_unit    # uint32 enum (unit_t)
         self.Limits = limits                            # List<List< single []>>, limits against which the measured values are evaluated
         self.LimitNames = limitNames                    # string [], array of limit names
    def json(self):
        return {'Len': self.Len, 'Element_Len': self.Element_Len,'Element_Delta': self.Element_Delta, 
                'Unit': self.Unit,'Element_Delta_Unit': self.Element_Delta_Unit,'Pos_Unit': self.Pos_Unit , 
                'Values': self.Values, 'Limits': self.Limits, 'LimitNames': self.LimitNames}

def DefineDefaultMatrix():
    return Matrix(0,0,0,0,0,0,[],Unit(),Unit(),Unit())

class Matrix(object):
    """Object which wraps a Matrix.
    
    Note: This data class is only sent over the socket as part of the MasterValueClass      
    """     
    def __init__(self, element_len = 0, len = 0, elemet_delta = 0, element_zero = 0, delta = 0, zero = 0, values = [], element_delta_unit = Unit(), delta_unit = Unit(), unit = Unit()):
         self.Element_Len = element_len                  # Uint32, number of data points (z)
         self.Len = len                                  # Uint32, number of data points (y)
         self.Element_Delta = elemet_delta               # Single, increment z dimension
         self.Element_Zero = element_zero                # Single, starting point z dimension
         self.Delta = delta                              # Single, increment y dimension
         self.Zero = zero                                # Single, starting point y dimension
         self.Values = values                            # List<single []>, list of arrays of measured values (x)
         self.Element_Delta_Unit = element_delta_unit    # uint32 enum (unit_t)
         self.Delta_Unit = delta_unit                    # uint32 enum (unit_t)
         self.Unit = unit                                # uint32 enum (unit_t)
    def json(self):
        return {'Len': self.Len, 'Element_Len': self.Element_Len,'Delta': self.Delta, 'Element_Delta': self.Element_Delta, 'Element_Zero': self.Element_Zero, 
                'Unit': self.Unit, 'Delta_Unit': self.Delta_Unit, 'Element_Delta_Unit': self.Element_Delta_Unit,
                'Values': self.Values}


class ByteBufferManager(object):
    """Object which functions as a byte buffer storage.

    Note: This class is used to receive the binary encoded data 
    """ 
    def __init__(self, buffer = b''):
        self.Buffer = buffer
        self.BufferCount = len(buffer)

class DataType(Enum):
    """Enum For Data Type of a MasterValueClass
    """
    Invalid = -1
    SingleValue = 0
    Vector = 1
    Polygon2D = 2
    VectorList = 3
    Matrix = 4
    def __str__(self):
        return str(self.value)

class MasterValueClass(object):
    """Main object for sending the actual measurement data.

    Note: This class contains all data (sub)classes as properties. Only of these classes is active, the others are null
    """     
    def __init__(self, quinter, singleValue = SingleValue(), vector = Vector(), polygon2D = Polygon2D(), vectorList = VectorList(), matrix = Matrix(), byteValues = ByteBufferManager(), encoding = eObjectEncoding.JSON, dataType = DataType.Invalid):
        self.Clavis = quinter               # Unique Quinter
        self.Encoding = encoding            # Object encoding
        self.SingleValue = singleValue      # Single value object
        self.Vector = vector                # Vector object
        self.Polygon2D = polygon2D          # Polygon2D object
        self.VectorList = vectorList        # VectorList object
        self.Matrix = matrix                # Matrix object
        self.ByteValues = byteValues        # ByteValues object
        self.DataType = dataType            # DataType Present

    def json(self):
        if(self.DataType == DataType.SingleValue ):
            return { 'Clavis': self.Clavis, 'SingleValue': self.SingleValue }
        if(self.DataType == DataType.Vector ):
            return { 'Clavis': self.Clavis, 'Vector': self.Vector }
        if(self.DataType == DataType.Polygon2D ):
            return { 'Clavis': self.Clavis, 'Polygon2D': self.Polygon2D }
        if(self.DataType == DataType.VectorList ):
            return { 'Clavis': self.Clavis, 'VectorList': self.VectorList }
        if(self.DataType == DataType.Matrix ):
            return { 'Clavis': self.Clavis, 'Matrix': self.Matrix }
        return { 'Clavis': self.Clavis, 'SingleValue': self.SingleValue, 'Vector': self.Vector, 'Polygon2D': self.Polygon2D, 'VectorList': self.VectorList, 'Matrix': self.Matrix  }

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader, WriteToFile = False, filepath = ''):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader, WriteToFile, filepath);
        return obj;

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    def FillFloatArrays(self, byteValues): 
        """Method which converts the byteValues into the Values/Limits properties of the subclasses (SingleValue, Vector, etc.).
        
        byteValues:   Byte array 
            
        Note: This method is only applied when the JSONARY encoding is used and the measurement data is binary encoded.        
        """ 
        dt = np.dtype(np.float32)
        
        if self.Vector is not None and self.Vector.Len > 0:
            count = self.Vector.Len
            self.Vector.Values = np.frombuffer(byteValues, dtype=dt, count=count, offset=0)
            if self.Vector.LimitNames is not None and len(self.Vector.LimitNames) > 0:
                n = len(self.Vector.LimitNames)
                #count = int(len(byteValues) / ( 4 * n))
                for i in range(n):
                    self.Vector.Limits.append(np.frombuffer(byteValues, dtype=dt, count=count, offset=(count * i * 4)))

        if self.Polygon2D is not None and self.Polygon2D.Len > 0:
            n = self.Polygon2D.Len * 2
            count = self.Polygon2D.Len * 2
            offset = 0
            values = np.frombuffer(byteValues, dtype=dt, count=count, offset=0)
            j = 0
            for i in range(self.Polygon2D.Len):
                insert = (values[j],values[j+1])
                self.Polygon2D.Values.append(insert)
                j = j + 2
            if self.Polygon2D.LimitNames is not None and len(self.Polygon2D.LimitNames) > 0:
                k = len(self.Polygon2D.LimitNames)
                for l in range(k):
                    offset = count * (l+1) * 4
                    values = np.frombuffer(byteValues, dtype=dt, count=count, offset=offset)
                    j = 0
                    for i in range(self.Polygon2D.Len):
                        insert = (values[j],values[j+1])
                        self.Polygon2D.Limits.append(insert)
                        j = j + 2

        if self.VectorList is not None and self.VectorList.Len > 0:
            n = self.VectorList.Len
            count = self.VectorList.Element_Len
            offset = 0
            for i in range(n):
                offset = count * i * 4
                self.VectorList.Values.append(np.frombuffer(byteValues, dtype=dt, count=count, offset=offset))
            if self.VectorList.LimitNames is not None and len(self.VectorList.LimitNames) > 0:
                k = len(self.VectorList.LimitNames)
                for l in range(k):
                    offset2 = offset + count * l * 4
                    self.VectorList.Limits[k].append(np.frombuffer(byteValues, dtype=dt, count=count, offset=offset2))
                  
        if self.Matrix is not None and self.Matrix.Len > 0:
            n = self.Matrix.Len
            count = self.Matrix.Element_Len
            for i in range(n):
                offset = count * i * 4
                self.Matrix.Values.append(np.frombuffer(byteValues, dtype=dt, count=count, offset=offset))
    
class MessageType(IntEnum):
    """Enum For Message Type
    """
    Hidden = 0
    Info = 1
    Message = 2
    Warning = 3
    Error = 4
    Fatal = 5
    def __str__(self):
        return str(self.value)

class ControlSocketMessage(object):
    """Object for sending messages from the service to the clients and vice-versa.

    Note: 
    """      
    def __init__(self, text, messageType, encoding = eObjectEncoding.JSON):
        self.Text = text                    # String, info text
        self.MessageType = messageType      # Enum, message type
        self.Encoding = encoding            # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class ControlSocketCommand(object):    
    """Object for sending commands from the service to the clients and vice-versa.

    Note: The command can target a specific client or all clients. The type of command is specified by the command type enum.
    """ 
    def __init__(self, clientId, commandType, encoding = eObjectEncoding.JSON):
        self.ClientId = clientId            # String, name id of the client which is the command target (if left out, then the command will not be directed to any specific client and the command type determines its actions)
        self.CommandType = commandType      # Enum, command type specifying the action which will be triggered
        self.Encoding = encoding            # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class QuinterRequestList(object):
    """Object containing a list of Quinter requests targeted at a specific client.

    Note: If the Quinter requests of a specific data client are to be changed, the control client can send a QuinterRequestList object.
    """     
    def __init__(self, clientId = '', quinterRequests = [], encoding = eObjectEncoding.JSON):
        self.ClientId = clientId                    # String, name id of the client the Quinter requests of which are to be updated 
        self.QuinterRequests = quinterRequests      # List<QuinterRequest> objects
        self.Encoding = encoding                    # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;
    
class DataTransformation(object):
    """Object defining the data representation (format) of the extracted data.

    Note: The TasAlyser-internal data representation (format) can be converted to another representation (e.g. irregular polygon can be projected onto a fixed grid).  
    """ 
    def __init__(self, original_data = True, len = 0, delta = 1, zero = 0):
        self.Original_Data = original_data      # Bool, flag indicating whether to keep the original data representation
        self.Len = len                          # Uint32, number of data points
        self.Delta = delta                      # Single, fixed increment
        self.Zero = zero                        # Single, starting point of the fixed grid representation

class QuinterRequest(object):
    """Object defining a Quinter request.

    Note: The Quinter request needs to be defined by the data client and send to the server in order to kick off the data extraction and reception.
    """     
    def __init__(self, quinter, dataTransformation = DataTransformation(), exportLimits = False, encoding = eObjectEncoding.JSON, valuesEncoding = eObjectEncoding.JSONARY):
        self.Quinter = quinter                          # Quinter, object (unique set of keys which define a measurement parameter)
        self.DataTransformation = dataTransformation    # DataTransformation, object which determines the data format of the requested data
        self.ExportLimits = exportLimits                # Bool, flag indicating whether to export the limits
        self.Encoding = encoding                        # Enum, object encoding
        self.ValuesEncoding = valuesEncoding            # Enum, encoding of the MasterValueClass which is send as a response to the Quinter request
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class ErrorList(object):
    """Object defining an error list object.

    Note: When the TasAlyser measurement analysis encounters an error in the noise analysis (because the measured values exceeds the limits) a specific error which is linked to the respective measurement parameter is added to a error list. This error list can be requested and changed by the data client.  
    """  
    def __init__(self, errors = [], purgeOriginalErrors = False, encoding = eObjectEncoding.JSON):
        self.Errors = errors                            # ErrorListElement, List of
        self.PurgeOriginalErrors = purgeOriginalErrors  # Bool, flag indicating whether the original error list is to be cleared and overwritten by a new error list from the external evaluation.
        self.Encoding = encoding                        # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader, WriteToFile = False, filepath = ''):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader, WriteToFile, filepath);
        return obj;

    def json(self):
        return { 'ErrorList': self.Errors }

class ErrorListElement(object):
    """Object defining an error list element.

    Note: All the error information required for further analysis.  
    """ 
    def __init__(self, sortingIndex, internalErrorCode, value, limit, average, position, quinter, externalErrorCode = 0, errorText = '', priority = 0, group = 0, errorResult = eResultType.NOK, encoding = eObjectEncoding.JSON):
        self.SortingIndex = sortingIndex                # Int32, Sorting index according to a user's priority
        self.ExternalErrorCode = externalErrorCode      # Int32, error code of the error information defined in the Discom parameter database and which can not be changed (error code which the test bench can send to the TasAlyser)
        self.InternalErrorCode = internalErrorCode      # Int32, error code of the error information defined in the Discom parameter database and which can not be changed
        self.ErrorText = errorText                      # String, error text (needs to be defined in the Discom parameter database and can not be changed)
        self.ErrorResult = errorResult                  # Int32, error results (predefined and can not be changed)
        self.Priority = priority                        # Int32, priority of the error (needs to be defined in the Discom parameter database and can not be changed)
        self.Group = group                              # Int32, group that the error belongs to (needs to be defined in the Discom parameter database and can not be changed)
        self.Value = value                              # Single, measured value which exceeded the limit
        self.Limit = limit                              # Single, limit defined which was exceeded by the measured value
        self.Average = average                          # Single, average (average from the learned limits's algorithm which is used to calculate the limit)                      
        self.Position = position                        # Single, the position (e.g. order position) at with the limit was max exceeded by the measured value  
        self.Quinter = quinter                          # Quinter, object
        self.Encoding = encoding                        # Enum, object encoding

    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader, WriteToFile = False, filepath = ''):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader, WriteToFile, filepath);
        return obj;

    def json(self):
        return { 'SortingIndex': self.SortingIndex,
                'ExternalErrorCode': self.ExternalErrorCode,
                'InternalErrorCode': self.InternalErrorCode,
                'ErrorText': self.ErrorText,
                'ErrorResult': self.ErrorResult,
                'Priority': self.Priority,
                'Group': self.Group,
                'Value': self.Value,
                'Limit': self.Limit,
                'Average': self.Average,
                'Position': self.Position,
                'Clavis': self.Quinter
                }

def DefineDefaultError():
    return ErrorListElement(0,0,0,0,0,0,Quinter("","","","","",""))

class Quinter(object):
    """Object defining a Discom quinter (unique set of keys).

    Note: The Quinter defines a unique set of keys which pinpoints the measurement variable.  
    """     
    def __init__(self, mode, instrument, instrumentParam, channel, signal, location):
        self.Mode = mode                            # String, test step e.g. 1-D
        self.Instrument = instrument                # String, instrument e.g. Crest
        self.InstrumentParam = instrumentParam      # String, instrument parameter e.g. Max
        self.Channel = channel                      # String, e.g. Synch
        self.Signal = signal                        # String, e.g. VS
        self.Location = location                    # String, e.g. Pinion

    def __eq__(self, obj):
        return self.CompareClavis(obj)

    def __lt__(self, obj):
        return False

    def __le__(self, obj):
        return self.__eq__(obj)

    def __gt__(self, obj):
        return False
    
    def __ge__(self, obj):
        return self.__eq__(obj)
    
    def __ne__(self, obj):
        return not self.__eq__(obj)

    def QuinterText(self):
        """Returns the set of keys formatted in one string.
        """  
        return self.Mode + "/" + self.Instrument + "/" + self.InstrumentParam + "/" + self.Channel + "/" + self.Signal + "/" + self.Location;

    def ClavisText(self):
        """Returns the set of keys formatted in one string.
        """  
        return self.Mode + "_" + self.Instrument + "_" + self.InstrumentParam + "_" + self.Channel + "_" + self.Signal + "_" + self.Location;

    def CompareClavis(self, clavis):
        """Returns true if the quinter is the same as self.

        quinter: Another Quinter object which is to be compared with this Quinter (self).
        
        Note:
        """ 
        if (self.Channel == clavis.Channel and
            self.Instrument == clavis.Instrument and
            self.InstrumentParam == clavis.InstrumentParam and
            self.Location == clavis.Location and
            self.Mode == clavis.Mode and
            self.Signal == clavis.Signal): 
            return True
        else: return False

def DefineDefaultMasterValueClass(dataType = DataType.Invalid, quinter = Quinter("","","","","","")):
    return MasterValueClass(quinter,DefineDefaultSingleValue(),DefineDefaultVector(),DefineDefaultPolygon2D(),DefineDefaultVectorList,DefineDefaultMatrix(),ByteBufferManager(),eObjectEncoding.JSON,dataType)

class ConnectionRequest(object):
    """Object used for connecting to the API.

    Note: The connection request is required to connect to the Discom Export API. Client name and client type (control or data client) are mandatory parameters.  
    """      
    def __init__(self, clientName, clientType, encoding = eObjectEncoding.JSON):
        self.ClientName = clientName    # String, name of the client (must be unique)
        self.ClientType = clientType    # Enum, client type
        self.Encoding = encoding        # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class MeasurementInfo(object):
    """Info object containing measurement metadata.

    Note: This object is sent to the client to provide measurement metadata.  
    """     
    def __init__(self, pkIdent, timeOfNextMeasurement, timeStamp, fileOffset, fileName, filePath, storagePath, serial, typeT, testBench, baseType, result, resultId, testProperty, testKind, ordinal, thisLast):
       self.PkIdent = pkIdent                               # Int32, id of measurement
       self.TimeOfNextMeasurement = timeOfNextMeasurement   # Datetime, time stamp of the next measurement
       self.TimeStamp = timeStamp                           # Datetime, time stamp of the measurement
       self.FileOffset = fileOffset                         # Int32, file offset which points to a specific Quinter location within the archive
       self.FileName = fileName                             # String, file name
       self.FilePath = filePath                             # String, file path
       self.StoragePath = storagePath                       # String, storage path --> deprecated
       self.Serial = serial                                 # String, serial number
       self.Type = typeT                                    # String, type/model name
       self.TestBench = testBench                           # String, test bench name
       self.BaseType = baseType                             # String, base type/model name
       self.Result = result                                 # String, result code name
       self.ResultId = resultId                             # Int32, result code id
       self.TestProperty = testProperty                     # String, test property 
       self.TestKind = testKind                             # String, test kind
       self.Ordinal = ordinal                               # Int32, repetition number
       self.ThisLast = thisLast                             # Bool, last repetition flag

    def json(self):
        if self.FileName is None:
            return { 'PkIdent': self.PkIdent,'TimeOfNextMeasurement': self.TimeOfNextMeasurement,'TimeStamp': self.TimeStamp, 'Serial': self.Serial, 'Type': self.Type, 'TestBench': self.TestBench, 'TestKind': self.TestKind, 'TestProperty': self.TestProperty, 'BaseType': self.BaseType}
        return { 'TimeStamp': self.TimeStamp, 'FileOffset': self.FileOffset, 'FileName': self.FileName, 'FilePath': self.FilePath, 'StoragePath': self.StoragePath, 'Serial': self.Serial,
                'Type': self.Type, 'TestBench': self.TestBench, 'BaseType': self.BaseType, 'Result': self.Result, 'ResultId': self.ResultId, 'TestProperty': self.TestProperty, 'TestKind': self.TestKind, 'Ordinal': self.Ordinal, 'ThisLast': self.ThisLast}
    def get_print_str(self):
        return str(self.json()).replace(',','\r\n')

class NewTest(object):
    """New test info object.

    Note: This object is sent to the clients to notify that a new test is active.  
    """     
    def __init__(self, mInfo, encoding = eObjectEncoding.JSON):
        self.MeasurementInfo = mInfo    # MeasurementInfo object, general meta data 
        self.Encoding = encoding        # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader, WriteToFile = False, filepath = ''):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader, WriteToFile, filepath);
        return obj;

class NewMode(object):
    """New mode info object.

    Note: This object is sent to the clients to notify that the measurement for new mode has finished.  
    """     
    def __init__(self, name, encoding = eObjectEncoding.JSON):
        self.Name = name            # String, mode name
        self.Encoding = encoding    # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class EndOfCommunication(object):
    """EndOfCommunication
    """     
    def __init__(self, encoding = eObjectEncoding.JSON):
        self.Encoding = encoding    # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class EndOfData(object):
    """End of data info object.

    Note: This object is sent to the client to notify that all data for a specific Quinter request has been extracted and sent to the client.  
    """     
    def __init__(self, info, encoding = eObjectEncoding.JSON):
        self.Info = info            # String, additional info 
        self.Encoding = encoding    # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class EndOfTest(object):
    """End of test info object.

    Note: This object is sent to the client to notify that all data of a unit test or an archive is available for querying.  
    """      
    def __init__(self, info, encoding = eObjectEncoding.JSON):
        self.Info = info            # String, additional info
        self.Encoding = encoding    # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader, WriteToFile = False, filepath = ''):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader, WriteToFile, filepath);
        return obj;

class EndOfTestEval(object):
    """End of test evaluation info object.

    Note: This object is sent to the server to notify that the external evaluation has finished.  
    """     
    def __init__(self, getErrorList = False, setErrorList = False, setMeasurementValues = False, encoding = eObjectEncoding.JSON):
        self.GetErrorList = getErrorList    # Bool, indicating whether to extract the error list from the TasAlyser
        self.SetErrorList = setErrorList    # Bool, indicating whether to set or add to the error list
        self.SetMeasurementValues = setMeasurementValues # Bool, indicating to set MeasurementValues
        self.Encoding = encoding            # Enum, object encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class EndOfModeEval(object):
    """End of mode evaluation info object.

    Note: This object is sent to the server to notify that the external evaluation for a specific mode has finished and the server may send the next 'NewMode' info.  
    """       
    def __init__(self, info="End of Mode Eval", getErrorList = False, setErrorList = False, setMeasurementValues = False, encoding = eObjectEncoding.JSON):
        self.Info = info
        self.GetErrorList = getErrorList
        self.SetErrorList = setErrorList
        self.SetMeasurementValues = setMeasurementValues
        self.Encoding = encoding
        
    def Send(self, socketSendMethod):
        """Send method.
        socketSendMethod:   Socket send method
        Note: This method wraps the Message.Send method       
        """ 
        Message.Send(self, self.__class__.__name__, socketSendMethod, self.Encoding);

    @staticmethod
    def Read(socketRead, messageHeader):
        """Read method.
        socketRead:     Socket read method
        messageHeader:  MessageHeader object
        Note: This method wraps the Message.Read method       
        """ 
        obj = Message.Read(socketRead, messageHeader);
        return obj;

class MeasurementDataIndex(object):
    """MeasurementDataIndex
    """
    def __init__(self, mInfo = None, errorList = None, clavisList = list()):
        self.MeasurementInfo = mInfo        # MeasurementInfo object, general meta data 
        self.ErrorList = errorList          # List of Errors
        self.ClavisList = clavisList        # List of Clavis

    def json(self):
        return {'MeasurementInfo': self.MeasurementInfo, 'ClavisList': self.ClavisList}

class MeasurementDataList(object):
    """MeasurementDataList object.
    """     
    def __init__(self, mInfo = None, mError = list(), valueList = list()):
        self.MeasurementInfo = mInfo        # MeasurementInfo object, general meta data 
        self.ErrorList = mError             # ErrorList object
        self.MeasurementList = valueList    # Enum, object encoding
    def json(self):
        if self.ErrorList is not None and self.MeasurementInfo is not None:
            return {'MeasurementInfo': self.MeasurementInfo, 'ErrorList': self.ErrorList, 'MeasurementList': self.MeasurementList}
        if self.ErrorList is not None:
            return {'MeasurementInfo': self.MeasurementInfo, 'ErrorList': self.ErrorList}
        if self.MeasurementInfo is not None:
            return {'MeasurementInfo': self.MeasurementInfo, 'MeasurementList': self.MeasurementList}
        return { 'MeasurementList': self.MeasurementList }

def OnNewMode(socket, mode, messageHeader, quinterRequests):
    """Event handler function when a 'NewMode' is recevied from the server.

    socket:             Socket, socket object
    mode:               String, mode name
    messageHeader:      MessageHeader object
    quinterRequests:    List<QuinterRequest>

    Returns:
        List of received MasterValueClasses

    Note: This function needs to be called when a 'NewMode' object has been received from the server.
    """
    # List of data to be returned
    mvc_list = list()
    # Iterate the Quinter requests
    for qr in quinterRequests:
        
        # Send the current Quinter request
        qr.Send(socket.send)

        # Info message
        print("The following Quinter request was sent to the server: " + qr.Quinter.QuinterText())

        # Wait for the response from the server
        mh = MessageHeader.Read(socket.recv)
        
        # Loop until an 'EndOfData' is received
        while mh.MessageObjectName != "EndOfData":
            # If a 'MasterValueClass' object is received
            if mh.MessageObjectName == "MasterValueClass":
                # Read the object
                mvc = MasterValueClass.Read(socket.recv, mh)

                # If not Written to File, add to return value
                if mvc == None:
                    q = qr.Quinter
                else:
                    q = mvc.Clavis
                    mvc_list.append(mvc)

                # Print info message
                print("Data was received for the following Quinter request: " + qr.Quinter.QuinterText())

            # If a 'ControlSocketMessage' object is received
            if mh.MessageObjectName == "ControlSocketMessage":
                # Read the object
                csm = ControlSocketMessage.Read(socket.recv, mh)
                # Print info message
                print("No Data Received " + csm.Text)
            
            # Keep on reading
            mh = messageHeader.Read(socket.recv)
        
        # Reading the 'EndOfData' object
        eod = EndOfData.Read(socket.recv, mh)
        # Print info message
        print("End of data was received.")
    
    # Define and send 'EndOfMode' evaluation - the external evaluation can request the current TasAlyser error list and specify whether error information from the external evaluation is to be added to the list or to be completely overwritten
    # This only applies to the real-time (live) data processing, please see the DemoTestLive.py for its implementation. 
    eome = EndOfModeEval("End of mode eval")
    eome.Send(socket.send)
    print("End of mode command was sent to the server")
    if len(mvc_list) == 0:
        mvc_list = None
    return mvc_list

def OnEndOfTest(socket, quinterRequests, setErrorList = False, getErrorList = False):
    """Event handler function when a 'EndOfTest' is recevied from the server.

    socket:             Socket, socket object
    quinterRequests:    List<QuinterRequest>
    setErrorList:       Bool, indicating whether to set or add to the error list
    getErrorList:       Bool, indicating whether to get the error list

    Returns:
        List of received MasterValueClasses

    Note: This function needs to be called when a 'EndOfTest' object has been received from the server.
    """
    # List of data to be returned
    mvc_list = list()
    # Iterate the Quinter requests
    for qr in quinterRequests:
        
        # Send the current Quinter request
        qr.Send(socket.send);
        
        # Info message
        print("The following Quinter request was sent to the server: " + qr.Quinter.QuinterText())
        
        # Wait for the response from the server
        mh = MessageHeader.Read(socket.recv)
        
        # Loop until an 'EndOfData' is received
        while mh.MessageObjectName != "EndOfData":
            # If a 'MasterValueClass' object is received
            if mh.MessageObjectName == "MasterValueClass":
                # Read the object
                mvc = MasterValueClass.Read(socket.recv, mh, Conf.File_C.WriteToFile, Conf.File_C.FilePath)      
                # If not Written to File, add to return value
                if mvc == None:
                    q = qr.Quinter
                else:
                    q = mvc.Clavis
                    mvc_list.append(mvc)
                # Print info message
                print("Data was received for the following Quinter request: " + q.QuinterText())
            
            # If a 'ControlSocketMessage' object is received
            elif mh.MessageObjectName == "ControlSocketMessage":
                # Read the object
                csm = ControlSocketMessage.Read(socket.recv, mh)
                # Print info message
                print("No Data Received " + csm.Text)

            # Keep on reading
            mh = MessageHeader.Read(socket.recv)
        
        # Reading the 'EndOfData' object
        eod = EndOfData.Read(socket.recv, mh)
        print("End of data was received.")

    # Define and send 'EndOfTest' evaluation
    # As part of the 'EndOfTestEval' the external evaluation can request the current TasAlyser error list and specify whether error information from the external evaluation is to be added to the list or to be completely overwritten
    # This only applies to the real-time  (live) data processing, please see the DemoTestLive.py for its implementation.
    eote = EndOfTestEval(getErrorList, setErrorList)
    eote.Send(socket.send);
    print("End of test eval command was sent to the server")
    if len(mvc_list) == 0:
        mvc_list = None
    return mvc_list

def OnNewTest(newTest = None):
    """Event handler function when a 'NewTest' is recevied from the server.

    newTest: NewTest, NewTest Object
    
    returns MeasurementInfo Object of the new Test/Archive
    """
    if newTest is not None:
        MI = newTest.MeasurementInfo
    else:
        MI = None
    return MI

def OnAbort(sock):
    """Event handler function when a 'Abort' is recevied from the server.

    sock: Socket, socket object
    
    Note: This function is not yet properly implemented!
    """     
    mh = MessageHeader.Read(sock.recv)
                
    while mh.MessageObjectName != None:
            
        if mh.MessageObjectName == "EndOfData":
            eod = EndOfData.Read(sock.read, mh)
        if mh.MessageObjectName == "MasterValueClass":
            mvc = MasterValueClass.Read(sock.read, mh)
        if mh.MessageObjectName == "NewMode":
            nm = NewMode.Read(sock.read, mh)
        if mh.MessageObjectName == "NewTest":
            nt = NewTest.Read(sock.read, mh)
        
        mh = messageHeader.Read(sock.recv);

def CloseSocket(sock):
    """Function which closes a socket connection.

    sock: Socket, socket object
    
    Note: Shuts the socke down and dispose the resources.
    """ 
     # Shutdown socket
    if sock is not None: 
       sock.shutdown(socket.SHUT_RDWR)

    # Dispose resources
    if sock is not None:
       sock.close()

def ClavisFromAdditionalInfo(AdditionalInfoText):
    ql = AdditionalInfoText.split(";",5)
    ret = Quinter(
    ql[0].split(":",2)[1],
    ql[1].split(":",2)[1],
    ql[2].split(":",2)[1],
    ql[3].split(":",2)[1],
    ql[4].split(":",2)[1],
    ql[5].split(":",2)[1]
    )
    return ret

def read_messageheaders_from_file(filepath = Conf.File_C.FilePath):
    """Function which reads a Discom export file and returns a list of MessageHeader objects.

    filepath: String, file path
    
    Note: Reads a data file which was exported using the Discom Data Export API.
    """ 
    with open( filepath, 'r+b') as DataFile:
        ret= list()
        Mhl= None
        # read first 4 bytes
        vb = DataFile.read(4)
        while vb:
            mh = MessageHeader()
            vs = vb.decode("utf-8")
            mh.Version = int(vs,16)
            # read next 4 bytes
            lb =  DataFile.read(4)
            ls =  lb.decode("utf-8") 
            lenStr = int(ls, 16)
            # read next lenStr bytes
            tb = DataFile.read(lenStr);     
            mh.MessageObjectName = tb.decode("utf-8")
            # read next 4 bytes
            ob = DataFile.read(4);    
            os = ob.decode("utf-8") 
            obje = int(os, 16)
            mh.ObjectEncoding = obje
            # read next 8 bytes
            mb = DataFile.read(8);  
            ms = mb.decode("utf-8") 
            mh.NrBytesOfFollowingMessage = int(ms, 16)
            # read next 8 bytes
            mb2 = DataFile.read(8);  
            ms2 = mb2.decode("utf-8") 
            mh.TotalNrBytesOfFollowingMessage = int(ms2, 16)
            # read next 4 bytes
            la =  DataFile.read(4)
            laO = la.decode("utf-8") 
            lenStrO = int(laO, 16)
            # read next lenStr bytes
            ta = DataFile.read(lenStrO)
            mh.AdditionalInfo = ta.decode("utf-8")
            
            if (mh.MessageObjectName == 'ErrorList'):
                if Mhl is None:
                    Mhl= MeasurementDataIndex()
                # bytes to string
                rb = DataFile.read(mh.NrBytesOfFollowingMessage)
                rb2 = None
                if mh.TotalNrBytesOfFollowingMessage != mh.NrBytesOfFollowingMessage:
                    rb2 = DataFile.read(mh.TotalNrBytesOfFollowingMessage - mh.NrBytesOfFollowingMessage)
            
                # deserialize json to object and return
                deserializedObject = None
                if mh.ObjectEncoding == eObjectEncoding.JSON:
                    rs = rb.decode("utf-8")
                    deserializedObject= JSONDecoder(object_hook = custom_json_decoder).decode(rs)
                if deserializedObject:
                    if type(deserializedObject).__name__ == 'ErrorList':
                        Mhl.ErrorList=copy.copy(deserializedObject.Errors)
            if (mh.MessageObjectName == 'NewTest'):
                if Mhl is not None:
                    ret.append(copy.copy(Mhl))
                Mhl= MeasurementDataIndex()


                # bytes to string
                rb = DataFile.read(mh.NrBytesOfFollowingMessage)
                rb2 = None
                if mh.TotalNrBytesOfFollowingMessage != mh.NrBytesOfFollowingMessage:
                    rb2 = DataFile.read(mh.TotalNrBytesOfFollowingMessage - mh.NrBytesOfFollowingMessage)
            
                # deserialize json to object and return
                deserializedObject = None
                if mh.ObjectEncoding == eObjectEncoding.JSON:
                    rs = rb.decode("utf-8")
                    deserializedObject= JSONDecoder(object_hook = custom_json_decoder).decode(rs)
                if deserializedObject:
                    if type(deserializedObject).__name__ == 'NewTest':
                        Mhl.MeasurementInfo=copy.copy(deserializedObject.MeasurementInfo)
            if (mh.MessageObjectName == 'MasterValueClass'):
                if Mhl is None:
                    Mhl= MeasurementDataIndex()
                Mhl.ClavisList.append(ClavisFromAdditionalInfo(mh.AdditionalInfo))
                # skip data
                DataFile.seek(mh.TotalNrBytesOfFollowingMessage,1)
            # try next
            vb = DataFile.read(4)
            # if end of file append the last of values
            if not vb:
                ret.append(copy.copy(Mhl))
    return ret

def read_data_from_file(filepath = Conf.File_C.FilePath, encoding = eObjectEncoding.JSON):
    """Function which reads a Discom export file and returns a list of MasterValueClass objects.

    filepath:   String, file path
    encodiung:  Enum, object encoding 
    
    Note: Reads a data file which was exported using the Discom Data Export API and returns the MasterValueClass objects.
    """ 
    with open( filepath, 'r+b') as DataFile:
        ret= list()
        Mhl= None
        # read first 4 bytes
        vb = DataFile.read(4)
        while vb:
            mh = MessageHeader()
            vs = vb.decode("utf-8")
            mh.Version = int(vs,16)
            # read next 4 bytes
            lb =  DataFile.read(4)
            ls =  lb.decode("utf-8") 
            lenStr = int(ls, 16)
            # read next lenStr bytes
            tb = DataFile.read(lenStr);     
            mh.MessageObjectName = tb.decode("utf-8")
            # read next 4 bytes
            ob = DataFile.read(4);    
            os = ob.decode("utf-8") 
            obje = int(os, 16)
            mh.ObjectEncoding = obje
            # read next 8 bytes
            mb = DataFile.read(8);  
            ms = mb.decode("utf-8") 
            mh.NrBytesOfFollowingMessage = int(ms, 16)
            # read next 8 bytes
            mb2 = DataFile.read(8);  
            ms2 = mb2.decode("utf-8") 
            mh.TotalNrBytesOfFollowingMessage = int(ms2, 16)
            # read next 4 bytes
            la =  DataFile.read(4)
            laO = la.decode("utf-8") 
            lenStrO = int(laO, 16)
            # read next lenStr bytes
            ta = DataFile.read(lenStrO)
            mh.AdditionalInfo = ta.decode("utf-8")
            
            # bytes to string
            rb = DataFile.read(mh.NrBytesOfFollowingMessage)
            rb2 = None
            if mh.TotalNrBytesOfFollowingMessage != mh.NrBytesOfFollowingMessage:
                rb2 = DataFile.read(mh.TotalNrBytesOfFollowingMessage - mh.NrBytesOfFollowingMessage)
            
            # deserialize json to object and return
            deserializedObject = None
            if mh.ObjectEncoding == eObjectEncoding.JSON:
                rs = rb.decode("utf-8")
                deserializedObject= JSONDecoder(object_hook = custom_json_decoder).decode(rs)
            if mh.ObjectEncoding == eObjectEncoding.JSONARY:
                deserializedObject = deserialize_jsonary(rb, rb2, mh.MessageObjectName)
            
            if deserializedObject:
                if type(deserializedObject).__name__ == 'MasterValueClass':
                    if Mhl is None:
                        Mhl= MeasurementDataList()
                    Mhl.MeasurementList.append(copy.copy(deserializedObject))
                if type(deserializedObject).__name__ == 'NewTest':
                    if Mhl is not None:
                        ret.append(copy.copy(Mhl))
                    Mhl= MeasurementDataList(deserializedObject.MeasurementInfo, list(), list())
                    Mhl.MeasurementInfo = copy.copy(deserializedObject.MeasurementInfo)
                if type(deserializedObject).__name__ == 'ErrorList':
                    if Mhl is None:
                        Mhl= MeasurementDataList()
                    Mhl.ErrorList = copy.copy(deserializedObject.Errors)
                    
            # try next
            vb = DataFile.read(4)
            # if end of file append the last of values
            if not vb:
                ret.append(copy.copy(Mhl))
        return ret
       
def LoadQuinterConfig():
    """Function which loads the DiscomDataExportConfig file and returns a list of Quinter requests.

    Note: 
    """ 
    ret = list()
    i = 0
    while i < len(Conf.Quinter_C.Quinters):
        quinter = Quinter(
                            Conf.Quinter_C.Quinters[i][0],
                            Conf.Quinter_C.Quinters[i][1],
                            Conf.Quinter_C.Quinters[i][2],
                            Conf.Quinter_C.Quinters[i][3],
                            Conf.Quinter_C.Quinters[i][4],
                            Conf.Quinter_C.Quinters[i][5]
                        )
        dataTransformation = DataTransformation(
                                                    Conf.Quinter_C.DataTransformation[i][0],
                                                    Conf.Quinter_C.DataTransformation[i][1],
                                                    Conf.Quinter_C.DataTransformation[i][2],
                                                    Conf.Quinter_C.DataTransformation[i][3]
                                                )

        ret.append(QuinterRequest(quinter, dataTransformation, Conf.Quinter_C.ExportLimits[i], eObjectEncoding.JSON, eObjectEncoding(Conf.Quinter_C.ValuesEncoding[i])))
        i += 1
    return ret
       
def LoadQuinterListConfig():
    """Function which loads the DiscomDataExportConfig file and returns a list of Quinter requests.

    Note: 
    """ 
    ret = list()
    i = 0
    while i < len(Conf.Quinter_C.Quinters):
        quinter = Quinter(
                            Conf.Quinter_C.Quinters[i][0],
                            Conf.Quinter_C.Quinters[i][1],
                            Conf.Quinter_C.Quinters[i][2],
                            Conf.Quinter_C.Quinters[i][3],
                            Conf.Quinter_C.Quinters[i][4],
                            Conf.Quinter_C.Quinters[i][5]
                        )
        ret.append(quinter)
        i += 1
    return ret
def DefHistDataConRequest():
    """Function which loads the DiscomDataExportConfig file and returns a ConnectionRequest object for a data client.

    Note: 
    """ 
    return ConnectionRequest(Conf.TCP_C.DataClientName, eClientType.HistoricalData)

def DefCtrlClientConRequest():
    """Function which loads the DiscomDataExportConfig file and returns a ConnectionRequest object for a control client.

    Note: 
    """ 
    return ConnectionRequest(Conf.TCP_C.ControlClientName, eClientType.Controller)

def DefLiveConRequest():
    """Function which loads the DiscomDataExportConfig file and returns a ConnectionRequest object for a live client.

    Note: 
    """ 
    return ConnectionRequest(Conf.TCP_C.LiveClientName, eClientType.LiveData)

def DefSqlQueryOfConf():
    """Function which loads the DiscomDataExportConfig file and returns a SqlQuery object.

    Note: 
    """ 
    RetsqlQueryObject = SqlQueryObject()
    RetsqlQueryObject.ArchiveSearchPath = Conf.SQL_C.ArchiveSearchPath
    RetsqlQueryObject.Database = Conf.SQL_C.Database
    RetsqlQueryObject.DbConnectionString = Conf.SQL_C.DbConnectionString
    RetsqlQueryObject.DbCommandTimeout = Conf.SQL_C.DbCommandTimeout
    RetsqlQueryObject.ClientId = Conf.SQL_C.ClientId
    RetsqlQueryObject.DateFrom = Conf.SQL_C.DateFrom
    RetsqlQueryObject.DateUntil = Conf.SQL_C.DateUntil
    RetsqlQueryObject.TopN = Conf.SQL_C.TopN
    RetsqlQueryObject.BaseTypeIds = Conf.SQL_C.BaseTypeIds
    RetsqlQueryObject.TypeIds = Conf.SQL_C.TypeIds
    RetsqlQueryObject.TestBenchIds = Conf.SQL_C.TestBenchIds
    RetsqlQueryObject.ResultIds = Conf.SQL_C.ResultIds
    RetsqlQueryObject.TestKindIds = Conf.SQL_C.TestKindIds
    RetsqlQueryObject.PropertyIds = Conf.SQL_C.PropertyIds
    RetsqlQueryObject.OnlyLastMeasurement = Conf.SQL_C.OnlyLastMeasurement
    RetsqlQueryObject.ExcludeLastMeasurement = Conf.SQL_C.ExcludeLastMeasurement
    RetsqlQueryObject.OrdinalIds = Conf.SQL_C.OrdinalIds
    RetsqlQueryObject.SerialNumbers = Conf.SQL_C.SerialNumbers
    RetsqlQueryObject.PkIdents = Conf.SQL_C.PkIdents
    RetsqlQueryObject.ErrorCodes = Conf.SQL_C.ErrorCodes
    RetsqlQueryObject.ErrorSequence = Conf.SQL_C.ErrorSequence
    RetsqlQueryObject.ExportErrors = Conf.SQL_C.ExportErrors
    RetsqlQueryObject.QuinterRequests = LoadQuinterListConfig()
    
    return RetsqlQueryObject

def DefArchiveQueryOfConf():
    """Function which loads the DiscomDataExportConfig file and returns a ArchiveQuery object.

    Note: 
    """ 
    RetArchiveQueryObject = ArchiveQueryObject()
    RetArchiveQueryObject.Archives = Conf.Archive_C.Archives
    RetArchiveQueryObject.ClientId = Conf.Archive_C.ClientId
    RetArchiveQueryObject.DateFrom = Conf.Archive_C.DateFrom
    RetArchiveQueryObject.DateUntil = Conf.Archive_C.DateUntil
    RetArchiveQueryObject.TopN = Conf.Archive_C.TopN
    RetArchiveQueryObject.BaseTypeIds = Conf.Archive_C.BaseTypeIds
    RetArchiveQueryObject.TypeIds = Conf.Archive_C.TypeIds
    RetArchiveQueryObject.TestBenchIds = Conf.Archive_C.TestBenchIds
    RetArchiveQueryObject.ResultIds = Conf.Archive_C.ResultIds
    RetArchiveQueryObject.TestKindIds = Conf.Archive_C.TestKindIds
    RetArchiveQueryObject.PropertyIds = Conf.Archive_C.PropertyIds
    RetArchiveQueryObject.SerialNumbers = Conf.Archive_C.SerialNumbers
    
    return RetArchiveQueryObject

def DoDataReqMakro():
    """Function which triggers the data extraction via a control client.

    Note: Based on the configuration file a connection request is made and then a SqlQuery object is sent to the server.
    """ 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((Conf.TCP_C.HOST, Conf.TCP_C.PORT))
    cr =  DefCtrlClientConRequest()
    cr.Send(s.send)
    sqlQueryObject = DefSqlQueryOfConf()
    sqlQueryObject.Send(s.send)

def DoDataReqMakro_Archive():
    """Function which triggers the data extraction via a control client.

    Note: Based on the configuration file a connection request is made and then a SqlQuery object is sent to the server.
    """ 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((Conf.TCP_C.HOST, Conf.TCP_C.PORT))
    cr =  DefCtrlClientConRequest()
    cr.Send(s.send)
    archiveQueryObject = DefArchiveQueryOfConf()
    archiveQueryObject.Send(s.send)

def ShutdownClient(sock, clientId = None):
    """Function which sends a shutdown command for a specific client.

    sock:       Socket, socket
    clientId:   String, id of the client targeted

    Note: Function which sends a shutdown command for a specific client. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if clientId is None:
        csc = ControlSocketCommand(Conf.TCP_C.DataClientName, eSocketCommandType.Shutdown)
    else:
        csc = ControlSocketCommand(clientId, eSocketCommandType.Shutdown)
    csc.Send(sock.send)

def StopArchivesProcessing(sock, clientId = None):
    """Function which sends a control socket command to a specific client to stop the processing of archives.

    sock:       Socket, socket
    clientId:   String, id of the client targeted

    Note: Function which sends a control socket command to a specific client. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if clientId is None:
        csc = ControlSocketCommand(Conf.TCP_C.DataClientName, eSocketCommandType.StopSingle)
    else:
        csc = ControlSocketCommand(clientId, eSocketCommandType.StopSingle)
    csc.Send(sock.send)

def ConnectClientAndReturnSocket(clientName = None, clientType = None, serverIP = None, port = None):
    """Function which connects to socket and returns the socket.

    clientName: String, name of the client targeted
    clientType: String, id of the client targeted
    serverIP:   String, server ip
    port:       Int32, port number

    Note: Function which connects to a socket and returns the socket. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if serverIP is None:
        s.connect((Conf.TCP_C.HOST, Conf.TCP_C.PORT))
    else: 
        s.connect((serverIP, port))
    
    if clientName is None:
        cr = ConnectionRequest(Conf.TCP_C.DataClientName, eClientType.HistoricalData)
    else:
        cr = ConnectionRequest(clientName, clientType)
    cr.Send(s.send)

    return s

def ConnectClient(sock, clientName = None, clientType = None, serverIP = None, port = None):
    """Function which sends a connection request to a socket.

    sock:       Socket, socket
    clientName: String, name of the client targeted
    clientType: String, id of the client targeted
    serverIP:   String, server ip
    port:       Int32, port number

    Note: Function which sends a connection request to a socket. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if serverIP is None:
        sock.connect((Conf.TCP_C.HOST, Conf.TCP_C.PORT))
    else: 
        sock.connect((serverIP, port))

    if clientName is None:
        cr = ConnectionRequest(Conf.TCP_C.DataClientName, eClientType.HistoricalData)
    else:
        cr = ConnectionRequest(clientName, clientType)
    cr.Send(sock.send)

def RequestConnection(sock, clientName = None, clientType = None):
    """Function which requests a socket connection.

    sock:       Socket, socket
    clientName: String, name of the client targeted
    clientType: String, id of the client targeted
    
    Note: Function which sends connection request. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if clientName is None:
        cr = ConnectionRequest(Conf.TCP_C.DataClientName, eClientType.HistoricalData)
    else:
        cr = ConnectionRequest(clientName, clientType)
    cr.Send(sock.send)    

def SendSqlQuery(sock, sqlQueryObject = None):
    """Function which sends a SQL query to the server.

    sock:           Socket, socket
    sqlQueryObject: SqlQueryObject, 
    
    Note: Function which sends a SQL query. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if sqlQueryObject is None:
        sqlQueryObject = DefSqlQueryOfConf()
    sqlQueryObject.Send(sock.send)

def SendArchiveQuery(sock, archiveQueryObject = None):
    """Function which sends a Archive query to the server.

    sock:               Socket, socket
    archiveQueryObject: ArchiveQueryObject, 
    
    Note: Function which sends a SQL query. If input arguments are not defined then use the ones defined in the DiscomDataExportConfig.
    """ 
    if archiveQueryObject is None:
        archiveQueryObject = DefArchiveQueryOfConf()
    archiveQueryObject.Send(sock.send)

def QueryConnectedDataClients(sock):
    """Function which queries for all connected data clients.
    
    sock:           Socket, socket
    
    Note: Function which queries for all connected data clients and returns them in a list.
    """    
    HistoryDataManagers = list()
    
    csc = ControlSocketCommand(None, eSocketCommandType.UpdateAll)
    csc.Send(sock.send)

    # Loop and read messages from the server/service
    while True:
    
        try:
            # Read MessageHeader
            mh =  MessageHeader.Read(sock.recv)
            
            print('MessageHeader ', mh.MessageObjectName)

            # If not a MessageHeader break out of the loop
            if not mh:
                break

            # If object is of type HistoryDataManager
            if mh.MessageObjectName == 'HistoryDataManager':
                print('Received ', mh.MessageObjectName)

                hdm = HistoryDataManager.Read(sock.recv, mh);
                
                print('Received ', hdm.Name)
                
                HistoryDataManagers.append(hdm)
                
            # If object is of type ControlSocketMessage
            if mh.MessageObjectName == 'ControlSocketMessage':            
                csm = ControlSocketMessage.Read(sock.recv, mh);
               
                print('Received ControlSocketMessage: ', csm.Text)

            # If object is of type EndOfData
            if mh.MessageObjectName == 'EndOfData':            
                eod = EndOfData.Read(sock.recv, mh);

                return HistoryDataManagers
                
        except:
            print("Error:", sys.exc_info()[0])

            # Exit the loop
            break
        
####################################################################################################
##################################### API Interface ################################################
####################################################################################################

import threading

class Client:
    """
    Discom Data Client: Receives Data From the Discom Data Service.
    """
    # Communication Socket
    _Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Messagethread
    __Thread = threading.Thread()
    # Auto Reconnect after Milliseconds
    __AutoReconnect = (False,1000)
    # Client Active
    __Active = False
    # Write Console Output of received Objects
    Verbose = False
    # Server Communication End Event
    _ServerCommunicationEnd = threading.Event()
    # Constructor
    def __init__(self,clientName,iPAdress,iPPort,clientType):
        # Identifier of the Data Client on the Discom Data Service.
        self.ClientName = clientName
        # IP Adress of the Service
        self.IPAdress   = iPAdress
        # IP Port of the Service
        self.IPPort     = iPPort
        # Client Type: Live,Historical or Control
        self.ClientType = clientType
    # Connect the Socket
    def _ConnectSocket(self):
        self.__Active = True
        # Connect, Need to instance new Socket to not share Resources
        self._Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._Socket.connect((self.IPAdress, self.IPPort))
        # Request a data connection
        print("Connecting to the server with IP:Port " + self.IPAdress + ":"  + str(self.IPPort) )
        cr =  ConnectionRequest(self.ClientName,self.ClientType)
        cr.Send(self._Socket.send)
    # Set the Auto Reconnect Property
    def SetAutoReconnect(self,auto=False,time=1000):
        self.__AutoReconnect = (auto,time)
    # Thread Body for Reconnection to Service
    def __ReconOfServerCommunication(self):
        while ( self.__Active  or self.__AutoReconnect[0] ):
            try:
                # Connect to the Server
                self._ConnectSocket()
                self._ServerCommunication(self._ServerCommunicationEnd)
            except VersionNoMatch:
                self._ErrorLog("Client and Server Version do not match.")
            except:
                if not self.__AutoReconnect[0]:
                    self._ErrorLog(sys.exc_info()[1])
                else:
                    self._InfoLog(sys.exc_info()[1])
            if self.__AutoReconnect[0]:
                time.sleep(self.__AutoReconnect[1]/1000)
    # Connect to the Server and start the communication Thread
    def Connect(self):
        self.Thread = threading.Thread(target = self.__ReconOfServerCommunication)
        self.Thread.daemon = False
        self.__Active = True
        self.Thread.start()
        self._WaitForConnected(3000)
    # Disconnect and end communication
    def Disconnect(self):
        if not self.__Active:
            return
        # Client not active
        self.__Active = False
        self._ServerCommunicationEnd.wait()
        try:
            if self._Socket is not None:
                self._Socket.shutdown(socket.SHUT_RDWR)
            if self._Socket is not None:
                self._Socket.close()
        except:
            self._ErrorLog(sys.exc_info()[1])
    # Returns True if Connected
    def _Connected(self):
        if not self.__Active:
            return False
        try:
            self._Socket.recv(0)
            return True
        except:
            return False
    # Returns Active State
    def _Active(self):
        return self.__Active
    # Wait For Connected, time in milliseconds
    def _WaitForConnected(self,timeout=1000,period=0.1):
        mustend = time.time() + timeout/1000
        while time.time() < mustend:
            time.sleep(period)
            if self._Connected(): return True
        return False
    # Server Communication
    def _ServerCommunication(self,ServerCommunicationEnd):
        raise NotImplementedError()
    # Simplify Error Messages
    def _ErrorLog(self,message):
        raise NotImplementedError()
    # Simplify Info Messages
    def _InfoLog(self,message):
        raise NotImplementedError()
    # Ensure to release the Socket properly, so the Service notices the Disconnection
    def __del__(self):
        self.Disconnect()

class ControlClient(Client):
    """
    Discom Control Client: Trigger Historical Archive Processing
    """
    def Disconnect(self):
        if not self._Connected():
            return
        eoc = EndOfCommunication()
        eoc.Send(self._Socket.send)
        Client.Disconnect(self)
    #Server Communication
    def _ServerCommunication(self,ServerCommunicationEnd):
        # Loop While Connected
        while self._Connected() is True:
            try:
                # First read the Messageheader
                mh = MessageHeader.Read(self._Socket.recv)
                ServerCommunicationEnd.clear()
                if mh is None:
                    continue
                if mh == "":
                    break
                # Now distingish based on the different possible Object Types
                if mh.MessageObjectName == 'HistoryDataManager':
                    hdm = HistoryDataManager.Read(self._Socket.recv,mh)
                    self.OnHistoryDataManager(hdm)
                elif mh.MessageObjectName == 'ControlSocketCommand':
                    csc = ControlSocketCommand.Read(self._Socket.recv,mh)
                    self.OnControlSocketCommand(csc)
                elif mh.MessageObjectName == 'ControlSocketMessage':
                    csm = ControlSocketMessage.Read(self._Socket.recv,mh)
                    self.OnControlSocketMessage(csm)
                else:
                    self._Socket.recv(mh.NrBytesOfFollowingMessage)
                ServerCommunicationEnd.set()
            except:
                ServerCommunicationEnd.set()
                if self._Active():
                    self._ErrorLog(sys.exc_info()[1])
    # Simplify Error Messages
    def _ErrorLog(self,message):
        self.Log(self.ClientName, message, MessageType.Error)
    # Simplify Info Messages
    def _InfoLog(self,message):
        self.Log(self.ClientName, message, MessageType.Info)
    # Handling for Messages
    def Log(self,clientName,message,messageType):
        dummy = True
    # Handle for History Data Manager
    def OnHistoryDataManager(self,historyDataManager):
        dummy = True
    # Handle for ControlSocketCommand
    def OnControlSocketCommand(self,controlSocketCommand):
        dummy = True
    # Handle for Control Socket Message
    def OnControlSocketMessage(self,controlSocketMessage):
        self._InfoLog(controlSocketMessage.Text)
    # Trigger Historical Archive Processing
    def TriggerHistoricalArchiveProcessing(self,QueryObject):
        try:
            if self._WaitForConnected(5000):
                QueryObject.Send(self._Socket.send)
        except:
            self._ErrorLog(sys.exc_info()[1])
    # Trigger Command
    def TriggerCommand(self,controlSocketCommand):
        try:
            if(self._WaitForConnected()):
                controlSocketCommand.Send(self._Socket.send)
            else:
                self._InfoLog("Not Connected")
        except:
            self._ErrorLog(sys.exc_info()[1])

class DataClient(Client):
    """
    Discom Data Client: Receive Measurement Data
    """
    # Define Measurement Value Requests for EndOfTest
    ClavisList = list()
    # Define Measurement Value Requests for Modes
    ClavisListOnMode = {}
    # Set Error List
    SetErrorList = False
    # Export Error List
    GetErrorList = False
    # Set Measurement Values
    SetMeasurementValues = False
    # Write To File
    WriteToFile = False
    # File Path
    WriteToFilePath = ""
    # keep information for the current Test
    __CurrentTest = None
    # Subpart of Communication: Get Measurementvalues
    def __GetDataCommunication(self, mh = MessageHeader(), mode = ""):
        ret = list()
        clavisList = list()
        if mode != "":
            clavisList = self.ClavisListOnMode.get(mode)
            if clavisList is None:
                return ret
        else:
            clavisList = self.ClavisList
        for qr in clavisList:
            # Send quinter request and wait for data
            qr.Send(self._Socket.send)
            # Read the reply
            mh = MessageHeader.Read(self._Socket.recv)
            while mh.MessageObjectName != 'EndOfData':
                # If Clavis not found, we could get a Message
                if mh.MessageObjectName == 'ControlSocketMessage':
                    csm = ControlSocketMessage.Read(self._Socket.recv,mh)
                    self.OnControlSocketMessage(csm)
                elif mh.MessageObjectName == 'MasterValueClass':
                    mvc = MasterValueClass.Read(self._Socket.recv,mh,self.WriteToFile,self.WriteToFilePath)
                    if mvc is not None:
                        ret.append(mvc)
                else:
                    raise Exception("MasterValue Class Expected, Received: " + mh.MessageObjectName)
                mh = MessageHeader.Read(self._Socket.recv)
            eod = EndOfData.Read(self._Socket.recv,mh)
        return ret
    # Server Communication
    def _ServerCommunication(self,ServerCommunicationEnd):
        # Loop the Communication Event Handles
        while self._Connected() is True:
            try:
                # First read the Messageheader
                mh = MessageHeader.Read(self._Socket.recv)
                ServerCommunicationEnd.clear()
                if mh is None:
                    continue
                # Now distingish based on the different possible Object Types

                if mh.MessageObjectName == 'NewTest':
                    # Get the measurement info from NewTest object
                    self.__CurrentTest = NewTest.Read(self._Socket.recv,mh,self.WriteToFile,self.WriteToFilePath)
                    # OnNewTest handler
                    self.OnNewTest(self.__CurrentTest)

                elif mh.MessageObjectName == 'NewMode':
                    # Get the new mode object
                    newMode = NewMode.Read(self._Socket.recv,mh)
                    # Get Data
                    modeData = self.__GetDataCommunication(mh,newMode.Name)
                    # Send EndOfTestEval
                    eome = EndOfModeEval()
                    eome.GetErrorList = self.GetErrorList
                    if self.ClientType is eClientType.LiveData:
                        eome.SetErrorList = self.SetErrorList
                        eome.SetMeasurementValues = self.SetMeasurementValues
                    eome.Send(self._Socket.send)
                    # Get Error List
                    elm = ErrorList()
                    if self.GetErrorList is True:
                        mh = MessageHeader.Read(self._Socket.recv)
                        elm = ErrorList.Read(self._Socket.recv,mh,self.WriteToFile,self.WriteToFilePath)
                    onm_ret = self.OnNewMode(modeData,newMode.Name,elm)
                    errorListToTas = ErrorList()
                    measurementValuesToTas = None
                    if onm_ret is not None:
                        if isinstance(onm_ret,tuple):
                            errorListToTas,measurementValuesToTas = onm_ret
                            if isinstance(measurementValuesToTas,MasterValueClass):
                                temp = copy.copy(measurementValuesToTas)
                                measurementValuesToTas = list()
                                measurementValuesToTas.append(temp)
                        elif isinstance(onm_ret,ErrorList):
                            errorListToTas = onm_ret
                        elif isinstance(onm_ret,list):
                            measurementValuesToTas = onm_ret
                        elif isinstance(onm_ret,MasterValueClass):
                            temp = copy.copy(onm_ret)
                            measurementValuesToTas = list()
                            measurementValuesToTas.append(temp)
                    if self.SetErrorList is True:
                        errorListToTas.Send(self._Socket.send)
                    if self.SetMeasurementValues is True:
                        if measurementValuesToTas is not None:
                            for mv in measurementValuesToTas:
                                mv.Send(self._Socket.send)
                        eod = EndOfData("")
                        eod.Send(self._Socket.send)

                elif mh.MessageObjectName == 'EndOfTest':
                    # Get the new mode object
                    eod = EndOfTest.Read(self._Socket.recv,mh,self.WriteToFile,self.WriteToFilePath)
                    # Get Data
                    data = self.__GetDataCommunication(mh)
                    # Send EndOfTestEval
                    eote = EndOfTestEval()
                    eote.GetErrorList = self.GetErrorList
                    if self.ClientType is eClientType.LiveData:
                        eote.SetErrorList = self.SetErrorList
                        eote.SetMeasurementValues = self.SetMeasurementValues
                    eote.Send(self._Socket.send)
                    # Get Error List
                    elm = ErrorList()
                    if self.GetErrorList is True:
                        mh = MessageHeader.Read(self._Socket.recv)
                        elm = ErrorList.Read(self._Socket.recv,mh,self.WriteToFile,self.WriteToFilePath)
                    onm_ret = self.OnEndOfTest(data,elm)
                    errorListToTas = ErrorList()
                    measurementValuesToTas = None
                    if onm_ret is not None:
                        if isinstance(onm_ret,tuple):
                            errorListToTas,measurementValuesToTas = onm_ret
                            if isinstance(measurementValuesToTas,MasterValueClass):
                                temp = copy.copy(measurementValuesToTas)
                                measurementValuesToTas = list()
                                measurementValuesToTas.append(temp)
                        elif isinstance(onm_ret,ErrorList):
                            errorListToTas = onm_ret
                        elif isinstance(onm_ret,list):
                            measurementValuesToTas = onm_ret
                        elif isinstance(onm_ret,MasterValueClass):
                            temp = copy.copy(onm_ret)
                            measurementValuesToTas = list()
                            measurementValuesToTas.append(temp)
                    if self.SetErrorList is True:
                        errorListToTas.Send(self._Socket.send)
                    if self.SetMeasurementValues is True:
                        if measurementValuesToTas is not None:
                            for mv in measurementValuesToTas:
                                mv.Send(self._Socket.send)
                        eod = EndOfData("")
                        eod.Send(self._Socket.send)

                elif mh.MessageObjectName == 'QuinterRequestList':
                    # Get the new mode object
                    qrl = QuinterRequestList.Read(self._Socket.recv,mh)
                    self.OnQuinterRequestList(qrl)

                elif mh.MessageObjectName == 'HistoricalDataQueryInfo':
                    # Get the new mode object
                    hdq = HistoricalDataQueryInfo.Read(self._Socket.recv,mh)
                    self.OnHistoricalDataQueryInfo(hdq)
                    
                elif mh.MessageObjectName == 'ControlSocketCommand':
                    # Get the new mode object
                    csc = ControlSocketCommand.Read(self._Socket.recv,mh)
                    if csc.CommandType is eSocketCommandType.Shutdown:
                        if csc.ClientId == self.ClientName:
                            ServerCommunicationEnd.set()
                            self.Disconnect()
                    self.OnControlSocketCommand(csc)
                    
                elif mh.MessageObjectName == 'ControlSocketMessage':
                    # Get the new mode object
                    csm = ControlSocketMessage.Read(self._Socket.recv,mh)
                    self.OnControlSocketMessage(csm)

                else:
                    try:
                        self._Socket.recv(mh.NrBytesOfFollowingMessage)
                    except:
                        self._ErrorLog(sys.exc_info()[1])
                        self._ErrorLog("Continuing...")

                ServerCommunicationEnd.set()
            except:
                ServerCommunicationEnd.set()
                if self._Active():
                    self._ErrorLog(sys.exc_info()[1])
                    self.Disconnect()
    # Simplify Error Messages
    def _ErrorLog(self,message):
        self.Log(self.ClientName, message, MessageType.Error)
    # Simplify Info Messages
    def _InfoLog(self,message):
        self.Log(self.ClientName, message, MessageType.Info)
    # Handling for Messages
    def Log(self,clientName,message,messageType):
        dummy = True
    # Handling for the New Test Event
    def OnNewTest(self,newTest):
        dummy = True
    # Handling for the New Test Event
    def OnNewMode(self,measurementValues,mode,errorList):
        dummy = True
    # Handling for the OnEndOfTest
    def OnEndOfTest(self,measurementValues,errorList):
        return errorList
    # Handling for the OnHistoricalDataQueryInfo
    def OnHistoricalDataQueryInfo(self,historicalDataQueryInfo):
        dummy = True
    # Received new QuinterRequestList, Default: set as new Request List
    def OnQuinterRequestList(self,quinterRequestList):
        self.ClavisList = quinterRequestList.QuinterRequests
    # Handling for the OnControlSocketCommand
    def OnControlSocketCommand(self,controlSocketCommand):
        dummy = True
    # Handling for the ControlSocketMessage
    def OnControlSocketMessage(self,controlSocketMessage):
        self._InfoLog(controlSocketMessage.Text)

        
####################################################################################################
##################################### High Level API Interface #####################################
####################################################################################################

class HistoricalDataGetter:
    """
    Discom HistoricalDataGetter: Get Data From the Discom Data Service.
    """
    # Server Communication End Event
    __finishing_event = threading.Event()

    # Control Client
    class DG_ControlClient(ControlClient):
        def Log(self,clientName,message,messageType):
            print(message)

    # Data Client
    class DG_DataClient(DataClient):
        def __init__(self, clientName, iPAdress, iPPort, clientType, finish_event = threading.Event()):
            self.Finish_Event = finish_event
            self.ArchiveMeasurementDataList = MeasurementDataList()
            self.Data_Received = list()
            return super().__init__(clientName, iPAdress, iPPort, clientType)
        def Log(self,clientName,message,messageType):
            print(message)
        def OnNewTest(self, newTest):
            self.ArchiveMeasurementDataList.MeasurementInfo = copy.copy(newTest.MeasurementInfo)
        def OnEndOfTest(self, measurementValues, errorList):
            self.ArchiveMeasurementDataList.MeasurementList = copy.copy(measurementValues)
            if self.GetErrorList is True:
                self.ArchiveMeasurementDataList.ErrorList = copy.copy(errorList)
            self.Data_Received.append(copy.copy(self.ArchiveMeasurementDataList))
            self.ArchiveMeasurementDataList = MeasurementDataList()
        def OnHistoricalDataQueryInfo(self, historicalDataQueryInfo):
            if historicalDataQueryInfo.Index == historicalDataQueryInfo.NrOfMeasurements:
                self.Finish_Event.set()

    # Constructor
    def __init__(self,iPAdress,iPPort):
        # ipAdress
        self.IPAdress = iPAdress
        self.IPPort = iPPort

    def GetData(self,query,clavislist):
        try:
            # Define an Event to get called when the Worker Thread finishes Historical Archive Query
            finish_event = threading.Event()
            # Define Service Connection
            Service_IP = self.IPAdress
            Service_Port = self.IPPort
            # Initialize Clients
            ccc = self.DG_ControlClient("ccc",Service_IP,Service_Port,eClientType.Controller)
            cdc = self.DG_DataClient("cdc",Service_IP,Service_Port,eClientType.HistoricalData,finish_event)
            cdc.GetErrorList = True
            # Define Measurement Value Request
            cdc.ClavisList = clavislist
            # Connect the Clients to the Service
            cdc.Connect()
            ccc.Connect()
            ######################## Get the Data ########################
            # Send the Query to the Service to trigger processing
            query.ClientId = cdc.ClientName
            ccc.TriggerHistoricalArchiveProcessing(query)
            # wait until processing finishes
            finish_event.wait()
            ######################## End all Processing ########################
            # Disconecct the Clients (Will also be called on Destruct)
            ccc.Disconnect()
            cdc.Disconnect()
            return cdc.Data_Received
        except:
            print(sys.exc_info()[1])
            return None