import ctypes import time import struct import sys import keyboard from pyiec101.iec101api import * # enbale to view traffic VIEW_TRAFFIC = 1 # print the struct sIEC101DataAttributeID and sIEC101DataAttributeData def vPrintDataInformation(psIEC101DataAttributeID , psIEC101DataAttributeData ): print(f" Link Address {psIEC101DataAttributeID.contents.u16DataLinkAddress}") print(f" Common Address {psIEC101DataAttributeID.contents.u16CommonAddress}") print(f" Typeid ID is {psIEC101DataAttributeID.contents.eTypeID} IOA {psIEC101DataAttributeID.contents.u32IOA}") print(f" Datatype->{psIEC101DataAttributeData.contents.eDataType} Datasize->{ psIEC101DataAttributeData.contents.eDataSize}" ) if(psIEC101DataAttributeData.contents.tQuality) != eIEC870QualityFlags.GD : if(psIEC101DataAttributeData.contents.tQuality & eIEC870QualityFlags.IV) == eIEC870QualityFlags.IV: print(" IEC_INVALID_FLAG") if(psIEC101DataAttributeData.contents.tQuality & eIEC870QualityFlags.NT) == eIEC870QualityFlags.NT: print(" IEC_NONTOPICAL_FLAG") if(psIEC101DataAttributeData.contents.tQuality & eIEC870QualityFlags.SB) == eIEC870QualityFlags.SB: print(" IEC_SUBSTITUTED_FLAG") if(psIEC101DataAttributeData.contents.tQuality & eIEC870QualityFlags.BL) == eIEC870QualityFlags.BL: print(" IEC_BLOCKED_FLAG") data_type = psIEC101DataAttributeData.contents.eDataType if data_type in (eDataTypes.SINGLE_POINT_DATA, eDataTypes.DOUBLE_POINT_DATA, eDataTypes.UNSIGNED_BYTE_DATA): data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 1)) u8data = struct.unpack('B', data)[0] print(f" Data : {u8data}") elif data_type == eDataTypes.SIGNED_BYTE_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 1)) i8data = struct.unpack('b', data)[0] print(f" Data : {i8data}") elif data_type == eDataTypes.UNSIGNED_WORD_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 2)) u16data = struct.unpack('H', data)[0] print(f" Data : {u16data}") elif data_type == eDataTypes.SIGNED_WORD_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 2)) i16data = struct.unpack('h', data)[0] print(f" Data : {i16data}") elif data_type == eDataTypes.UNSIGNED_DWORD_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 4)) u32data = struct.unpack('I', data)[0] print(f" Data : {u32data}") elif data_type == eDataTypes.SIGNED_DWORD_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 4)) i32data = struct.unpack('i', data)[0] print(f" Data : {i32data}") elif data_type == eDataTypes.FLOAT32_DATA: data = bytearray(ctypes.string_at(psIEC101DataAttributeData.contents.pvData, 4)) f32data = struct.unpack('f', data)[0] print(f" Data : {f32data:.3f}") if psIEC101DataAttributeData.contents.sTimeStamp.u16Year != 0: print(f" Date : {psIEC101DataAttributeData.contents.sTimeStamp.u8Day:02}-{psIEC101DataAttributeData.contents.sTimeStamp.u8Month:02}-{psIEC101DataAttributeData.contents.sTimeStamp.u16Year:04} DOW -{psIEC101DataAttributeData.contents.sTimeStamp.u8DayoftheWeek}") print(f" Time : {psIEC101DataAttributeData.contents.sTimeStamp.u8Hour:02}:{psIEC101DataAttributeData.contents.sTimeStamp.u8Minute:02}:{psIEC101DataAttributeData.contents.sTimeStamp.u8Seconds:02}:{psIEC101DataAttributeData.contents.sTimeStamp.u16MilliSeconds:03}") # update callback def cbUpdate(u16ObjectId, psIEC101DataAttributeID, psIEC101DataAttributeData, psIEC101UpdateParameters, ptErrorValue): i16rErrorCode = ctypes.c_short() i16rErrorCode = 0 print(" cbUpdate() called ") print(" Client ID : %u" % u16ObjectId) vPrintDataInformation(psIEC101DataAttributeID, psIEC101DataAttributeData) message = f" COT {psIEC101UpdateParameters.contents.eCause}" print(message) return i16rErrorCode # Client Status Callback def cbClientStatus(u16ObjectId, psIEC101ClientConnectionID , peSat, ptErrorValue): i16rErrorCode = ctypes.c_short() i16rErrorCode = 0 print(" cbClientStatus called - from IEC101 client") print(" Client ID : %u" % u16ObjectId) print(" DataLink Address %u Serial port number %u " % (psIEC101ClientConnectionID.contents.u16DataLinkAddress, psIEC101ClientConnectionID.contents.u16SerialPortNumber)) if peSat.contents.value == eStatus.CONNECTED: print(" Status - Connected") else: print(" Status - Disconnected") return i16rErrorCode # Debug callback def cbDebug(u16ObjectId, psIEC101DebugData , ptErrorValue): i16rErrorCode = ctypes.c_short() i16rErrorCode = 0 # u16nav = ctypes.c_ushort() u16nav = 0 #print(" cbDebug() called") print(f" {psIEC101DebugData.contents.sTimeStamp.u8Hour}:{psIEC101DebugData.contents.sTimeStamp.u8Minute}:{psIEC101DebugData.contents.sTimeStamp.u8Seconds} Server ID: {u16ObjectId}", end='') if (psIEC101DebugData.contents.u32DebugOptions & eDebugOptionsFlag.DEBUG_OPTION_TX) == eDebugOptionsFlag.DEBUG_OPTION_TX: print(f" client id {u16ObjectId} transmit com Port {psIEC101DebugData.contents.u16ComportNumber}", end='') print(" ->", end='') u16nav = 0 for u16nav in range(int(psIEC101DebugData.contents.u16TxCount)): #print(f" {psIEC101DebugData.contents.au8TxData[u16nav]:02x}", end='') try: print(f" {psIEC101DebugData.contents.au8TxData[u16nav]:02x}", end='') except TypeError: print("TypeError: Check list of indices") if (psIEC101DebugData.contents.u32DebugOptions & eDebugOptionsFlag.DEBUG_OPTION_RX) == eDebugOptionsFlag.DEBUG_OPTION_RX: print(f" client id {u16ObjectId} receive com Port {psIEC101DebugData.contents.u16ComportNumber}", end='') print(" <-", end='') #print(f"u16RxCount - {psIEC101DebugData.contents.u16RxCount} len aaray - {len(psIEC101DebugData.contents.au8RxData)}") for u16nav in range(int(psIEC101DebugData.contents.u16RxCount)): print(f" {psIEC101DebugData.contents.au8RxData[u16nav]:02x}", end='') if (psIEC101DebugData.contents.u32DebugOptions & eDebugOptionsFlag.DEBUG_OPTION_ERROR) == eDebugOptionsFlag.DEBUG_OPTION_ERROR: print(f" Error message {psIEC101DebugData.contents.au8ErrorMessage}") print(f" ErrorCode {psIEC101DebugData.contents.iErrorCode}") print(f" ErrorValue {psIEC101DebugData.contents.tErrorvalue}") print("", flush=True) return i16rErrorCode # print error code and description def errorcodestring(errorcode): sIEC101ErrorCodeDes = sIEC101ErrorCode() sIEC101ErrorCodeDes.iErrorCode = errorcode iec101_lib.IEC101ErrorCodeString(sIEC101ErrorCodeDes) return sIEC101ErrorCodeDes.LongDes.decode("utf-8") # print error value and description def errorvaluestring(errorvalue): sIEC101ErrorValueDes = sIEC101ErrorValue() sIEC101ErrorValueDes.iErrorValue = errorvalue iec101_lib.IEC101ErrorValueString(sIEC101ErrorValueDes) return sIEC101ErrorValueDes.LongDes.decode("utf-8") # send command for particular typeid and IOA FROM USER INPUT def sendCommand(myClient): i16ErrorCode = ctypes.c_short() tErrorValue = ctypes.c_short() print("sendCommand CALLED") while True: try: u32ioa = ctypes.c_uint32(int(input("C_SE_TC_1 command Enter Information object address(IOA) "))) except ValueError: print("Please enter a number -ioa") else: break while True: try: f32value = ctypes.c_float(float(input("Enter command float value: "))) except ValueError: print("Please enter a float number ") else: break psDAID = sIEC101DataAttributeID() psNewValue = sIEC101DataAttributeData() psIEC104CommandParameters = sIEC101CommandParameters() psDAID.u16SerialPortNumber = 2 psDAID.u16DataLinkAddress= 1 psDAID.u16CommonAddress= 1 psIEC104CommandParameters.u8OriginatorAddress = 1 psIEC104CommandParameters.eQOCQU = eCommandQOCQU.NOADDDEF psDAID.eTypeID = eIEC870TypeID.C_SE_TC_1 psDAID.u32IOA = u32ioa psDAID.pvUserData = None psNewValue.tQuality = eIEC870QualityFlags.GD psNewValue.pvData = ctypes.cast(ctypes.pointer(f32value),ctypes.c_void_p) psNewValue.eDataType = eDataTypes.FLOAT32_DATA psNewValue.eDataSize = eDataSizes.FLOAT32_SIZE now = time.time() timeinfo = time.localtime(now) #current date psNewValue.sTimeStamp.u8Day = timeinfo.tm_mday psNewValue.sTimeStamp.u8Month = timeinfo.tm_mon psNewValue.sTimeStamp.u16Year = timeinfo.tm_year psNewValue.sTimeStamp.u8Hour = timeinfo.tm_hour psNewValue.sTimeStamp.u8Minute = timeinfo.tm_min psNewValue.sTimeStamp.u8Seconds = timeinfo.tm_sec psNewValue.sTimeStamp.u16MilliSeconds = 0 psNewValue.sTimeStamp.u16MicroSeconds = 0 psNewValue.sTimeStamp.i8DSTTime = 0 psNewValue.sTimeStamp.u8DayoftheWeek = 4 psNewValue.bTimeInvalid = False #printf(" update float value %f",f32data); # Update server i16ErrorCode = iec101_lib.IEC101Operate(myClient, ctypes.byref(psDAID), ctypes.byref(psNewValue),ctypes.byref(psIEC104CommandParameters),ctypes.byref((tErrorValue))) if i16ErrorCode != 0: message = f"IEC 60870-5-104 Library API Function - IEC104Operate() failed: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) else: message = f"IEC 60870-5-104 Library API Function - IEC104Operatee() success: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) # main program def main(): print(" \t\t**** IEC 60870-5-101 Protocol Client Library Test ****") # Check library version against the library header file if iec101_lib.IEC101GetLibraryVersion().decode("utf-8") != IEC101_VERSION: print(" Error: Version Number Mismatch") print(" Library Version is : {}".format(iec101_lib.IEC101GetLibraryVersion().decode("utf-8"))) print(" The Header Version used is : {}".format(IEC101_VERSION)) print("") input(" Press Enter to free IEC 101 client object") exit(0) print(" Library Version is : {}".format(iec101_lib.IEC101GetLibraryVersion().decode("utf-8"))) print(" Library Build on : {}".format(iec101_lib.IEC101GetLibraryBuildTime().decode("utf-8"))) print(" Library License Information : {}".format(iec101_lib.IEC101GetLibraryLicenseInfo().decode("utf-8"))) i16ErrorCode = ctypes.c_short() tErrorValue = ctypes.c_short() sParameters = sIEC101Parameters() # Initialize IEC 60870-5-101 Client object parameters sParameters.eAppFlag = eApplicationFlag.APP_CLIENT # This is a IEC101 Client sParameters.ptReadCallback = IEC101ReadCallback(0) # Read Callback sParameters.ptWriteCallback = IEC101WriteCallback(0) # Write Callback sParameters.ptUpdateCallback = IEC101UpdateCallback(cbUpdate) # Update Callback sParameters.ptSelectCallback = IEC101ControlSelectCallback(0) # Select Callback sParameters.ptOperateCallback = IEC101ControlOperateCallback(0) # Operate Callback sParameters.ptCancelCallback = IEC101ControlCancelCallback(0) # Cancel Callback sParameters.ptFreezeCallback = IEC101ControlFreezeCallback(0) # Freeze Callback sParameters.ptDebugCallback = IEC101DebugMessageCallback(cbDebug) # Debug Callback sParameters.ptPulseEndActTermCallback = IEC101ControlPulseEndActTermCallback(0) # pulse end callback sParameters.ptParameterActCallback = IEC101ParameterActCallback(0) # Parameter activation callback sParameters.ptDirectoryCallback = IEC101DirectoryCallback(0) # Directory Callback sParameters.ptClientStatusCallback = IEC101ClientStatusCallback(cbClientStatus) # client connection status Callback sParameters.u32Options = 0 sParameters.u16ObjectId = 1 #Client ID which used in callbacks to identify the iec 101 Client object # Create a CLIENT object myClient = iec101_lib.IEC101Create(ctypes.byref(sParameters), ctypes.byref((i16ErrorCode)), ctypes.byref((tErrorValue))) if i16ErrorCode.value != 0: message = f"IEC 60870-5-101 Library API Function - IEC101Create() failed: {i16ErrorCode.value} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) exit(0) else: message = f"IEC 60870-5-101 Library API Function - IEC101Create() success: {i16ErrorCode.value} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) while(True): sIEC101Config = sIEC101ConfigurationParameters() sIEC101Config.sClientSet.eLink = eDataLinkTransmission.UNBALANCED_MODE sIEC101Config.sClientSet.benabaleUTCtime = False # Debug option settings if 'VIEW_TRAFFIC' in globals(): sIEC101Config.sClientSet.sDebug.u32DebugOptions = (eDebugOptionsFlag.DEBUG_OPTION_RX | eDebugOptionsFlag.DEBUG_OPTION_TX) else: sIEC101Config.sClientSet.sDebug.u32DebugOptions = 0 sIEC101Config.sClientSet.bAutoGenIEC101DataObjects = True # if it true ,the IEC101 Objects created automaticallay, use u16NoofObject = 0, psIEC104Objects = NULL*/ sIEC101Config.sClientSet.u8NoofClient = 1 arraypointer = (sIEC101ClientObject * sIEC101Config.sClientSet.u8NoofClient )() sIEC101Config.sClientSet.psClientObjects = ctypes.cast(arraypointer, ctypes.POINTER(sIEC101ClientObject)) arraypointer[0].sSerialSet.eSerialType = eSerialTypes.SERIAL_RS232 # check computer configuration serial com port number, if server and client application running in same system, we can use com0com arraypointer[0].sSerialSet.u16SerialPortNumber = 2 arraypointer[0].sSerialSet.eSerialBitRate = eSerialBitRate.BITRATE_9600 arraypointer[0].sSerialSet.eWordLength = eSerialWordLength.WORDLEN_8BITS arraypointer[0].sSerialSet.eSerialParity = eSerialParity.EVEN arraypointer[0].sSerialSet.eStopBits = eSerialStopBits.STOPBIT_1BIT #serial port flow control arraypointer[0].sSerialSet.sFlowControl.bWinCTSoutputflow = False arraypointer[0].sSerialSet.sFlowControl.bWinDSRoutputflow = False arraypointer[0].sSerialSet.sFlowControl.eWinDTR = eWinDTRcontrol.WIN_DTR_CONTROL_DISABLE arraypointer[0].sSerialSet.sFlowControl.eWinRTS = eWinRTScontrol.WIN_RTS_CONTROL_DISABLE arraypointer[0].sSerialSet.sFlowControl.eLinuxFlowControl = eLinuxSerialFlowControl.FLOW_NONE arraypointer[0].sSerialSet.sRxTimeParam.u16CharacterTimeout = 1 arraypointer[0].sSerialSet.sRxTimeParam.u16MessageTimeout = 0 arraypointer[0].sSerialSet.sRxTimeParam.u16InterCharacterDelay = 5 arraypointer[0].sSerialSet.sRxTimeParam.u16PostDelay = 1 arraypointer[0].sSerialSet.sRxTimeParam.u16PreDelay = 1 arraypointer[0].sSerialSet.sRxTimeParam.u8CharacterRetries = 20 arraypointer[0].sSerialSet.sRxTimeParam.u8MessageRetries = 0 arraypointer[0].sClientProtSet.eCASize = eCommonAddressSize.CA_TWO_BYTE arraypointer[0].sClientProtSet.eCOTsize = eCauseofTransmissionSize.COT_TWO_BYTE arraypointer[0].sClientProtSet.u8OriginatorAddress = 1 arraypointer[0].sClientProtSet.eIOAsize = eInformationObjectAddressSize.IOA_THREE_BYTE arraypointer[0].sClientProtSet.elinkAddrSize = eDataLinkAddressSize.DL_TWO_BYTE arraypointer[0].sClientProtSet.u16DataLinkAddress = 1 arraypointer[0].sClientProtSet.u8TotalNumberofStations = 1 arraypointer[0].sClientProtSet.au16CommonAddress[0] = 1 arraypointer[0].sClientProtSet.au16CommonAddress[1] = 0 arraypointer[0].sClientProtSet.au16CommonAddress[2] = 0 arraypointer[0].sClientProtSet.au16CommonAddress[3] = 0 arraypointer[0].sClientProtSet.au16CommonAddress[4] = 0 arraypointer[0].sClientProtSet.u32LinkLayerTimeout = 5000 arraypointer[0].sClientProtSet.u32PollInterval = 100 arraypointer[0].sClientProtSet.u32GeneralInterrogationInterval = 5 # in sec if 0 , gi will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group1InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group2InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group3InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group4InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group5InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group6InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group7InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group8InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group9InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group10InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group11InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group12InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group13InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group14InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group15InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group16InterrogationInterval = 0 # in sec if 0 , group 1 interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32CounterInterrogationInterval = 5 # in sec if 0 , ci will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group1CounterInterrogationInterval = 0 # in sec if 0 , group 1 counter interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group2CounterInterrogationInterval = 0 # in sec if 0 , group 1 counter interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group3CounterInterrogationInterval = 0 # in sec if 0 , group 1 counter interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32Group4CounterInterrogationInterval = 0 # in sec if 0 , group 1 counter interrogation will not send in particular interval*/ arraypointer[0].sClientProtSet.u32ClockSyncInterval = 5 # in sec if 0 , clock sync, will not send in particular interval */ arraypointer[0].sClientProtSet.u32CommandTimeout = 10000 arraypointer[0].sClientProtSet.bCommandResponseActtermUsed = True # File transfer protocol configuration parameters arraypointer[0].sClientProtSet.bEnableFileTransfer = False arraypointer[0].sClientProtSet.u32FileTransferTimeout = 1000000 arraypointer[0].sClientProtSet.ai8FileTransferDirPath = "//FileTest//".encode('utf-8') arraypointer[0].u16NoofObject = 0; # Define number of objects arraypointer[0].psIEC101Objects = None # client 1 configuration ends i16ErrorCode = iec101_lib.IEC101LoadConfiguration(myClient, ctypes.byref(sIEC101Config), ctypes.byref((tErrorValue))) if i16ErrorCode != 0: message = f"IEC 60870-5-101 Library API Function - IEC101LoadConfiguration() failed: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) break else: message = f"IEC 60870-5-101 Library API Function - IEC101LoadConfiguration() success: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) i16ErrorCode = iec101_lib.IEC101Start(myClient, ctypes.byref((tErrorValue))) if i16ErrorCode != 0: message = f"IEC 60870-5-101 Library API Function - IEC101Start() failed: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) break else: message = f"IEC 60870-5-101 Library API Function - IEC101Start() success: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) print("press x to exit") while(True): if keyboard.is_pressed('x'): print("x pressed, exiting loop") keyboard.release('x') time.sleep(0.1) break elif keyboard.is_pressed('s'): print("u pressed, send command called") keyboard.release('s') time.sleep(0.1) sendCommand(myClient) #Xprint("sleep called") time.sleep(0.1) break i16ErrorCode = iec101_lib.IEC101Stop(myClient, ctypes.byref((tErrorValue))) if i16ErrorCode != 0: message = f"IEC 60870-5-101 Library API Function - IEC101Stop() failed: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) else: message = f"IEC 60870-5-101 Library API Function - IEC101Stop() success: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) i16ErrorCode = iec101_lib.IEC101Free(myClient, ctypes.byref((tErrorValue))) if i16ErrorCode != 0: message = f"IEC 60870-5-101 Library API Function - IEC101Free() failed: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) else: message = f"IEC 60870-5-101 Library API Function - IEC101Free() success: {i16ErrorCode} - {errorcodestring(i16ErrorCode)}, {tErrorValue.value} - {errorvaluestring(tErrorValue)}" print(message) print("Exiting the program...") if __name__ == "__main__": main()