mz-automation / libiec61850

Official repository for libIEC61850, the open-source library for the IEC 61850 protocols
http://libiec61850.com/libiec61850
GNU General Public License v3.0
863 stars 461 forks source link

Difficulty in making a simple GOOSE observer in Python #473

Open TiagoHekker opened 1 year ago

TiagoHekker commented 1 year ago

Dear All,

I tried and not succeed in making a simple GOOSE observer in Python. I made a Python Wrapper using SWIG and made a simple GOOSE publisher in Python without much problems. I based my code in the example provided in the folder: libiec61850-1.5.1\examples\goose_publisher I put below my simple GOOSE Publisher in Python:

import iec61850
import time
import sys

def main():

    confrev = 1
    ied_macaddress = "01:0C:CD:01:0B:03"
    ied_name="GOOSE_PUBL"
    appid="0B03"
    gocbref='GOOSE_PUBLTO_RCU22001/LLN0$GO$RCU22001_GrAct' 
    datasetref='GOOSE_PUBLTO_RCU22001/LLN0$Dataset1' 
    goid="RCU22001_GrAct" 
    interface="1" 

    dataSetValues = iec61850.LinkedList_create()

    iec61850.LinkedList_add(dataSetValues, iec61850.MmsValue_newBoolean(False))
    iec61850.LinkedList_add(dataSetValues, iec61850.MmsValue_newIntegerFromInt32(5))

    gooseCommParameters = iec61850.CommParameters()

    gooseCommParameters.appId = int(appid, 16)
    gooseCommParameters.vlanId = 0
    gooseCommParameters.vlanPriority = 4

    hex_parts = ied_macaddress.split(":")
    dst_mac_address = [int(part, 16) for part in hex_parts]

    iec61850.CommParameters_setDstAddress(gooseCommParameters, *dst_mac_address)

    publisher = iec61850.GoosePublisher_create(gooseCommParameters, interface)

    if (publisher):
        iec61850.GoosePublisher_setGoCbRef(publisher, gocbref)
        iec61850.GoosePublisher_setConfRev(publisher, confrev)
        iec61850.GoosePublisher_setDataSetRef(publisher, datasetref)
        iec61850.GoosePublisher_setTimeAllowedToLive(publisher, 250)
        iec61850.GoosePublisher_setGoID(publisher, goid)

        for i in range(10):
            if (iec61850.GoosePublisher_publish(publisher, dataSetValues) == -1):
                print("Error sending message!\n")
            time.sleep(1)

        iec61850.GoosePublisher_destroy(publisher)

    else:
        print("Failed to create GOOSE publisher. Reason can be that the Ethernet interface doesn't exist or root permission are required.\n")

    iec61850.LinkedList_destroyDeep_MmsValueDelete(dataSetValues)

    exit()

if __name__ == "__main__":
    main()

Now, making a GOOSE observer is another story for me, I based my code in the C example that comes the source in the folder: \libiec61850-1.5.1\examples\goose_observer

A GOOSE observer in Python I was never able to make it work.

The first Python code accuses the reception of one GOOSE Messages even if there are no GOOSE messages in the network, it classifies it as invalid. The program continues to run but when I send GOOSE messages it does not acuse them:

import signal
import sys
import iec61850
import time

running = True

def sigint_handler(signalId, frame):
    global running
    running = False

def sleep_milliseconds(milliseconds):
    seconds = milliseconds / 1000.0
    time.sleep(seconds)

def gooseListener(subscriber):
    print("\n\nGOOSE MESSAGE FOUND\n\n")
    print("  vlanTag: {}\n".format("found" if iec61850.GooseSubscriber_isVlanSet(subscriber) else "NOT found"))
    if (iec61850.GooseSubscriber_isVlanSet(subscriber)):
        print("    vlanId: {}".format(iec61850.GooseSubscriber_getVlanId(subscriber)))
        print("    vlanPrio: {}".format(iec61850.GooseSubscriber_getVlanPrio(subscriber)))
    print("  appId: {}".format(iec61850.GooseSubscriber_getAppId(subscriber)))
    macBuf = ""
    #iec61850.GooseSubscriber_getSrcMac(subscriber,macBuf)
    #print(f"  srcMac: {macBuf}\n")
    #iec61850.GooseSubscriber_getDstMac(subscriber,macBuf)
    #print(f"  dstMac: {macBuf}\n")
    print("  goId: {}".format(iec61850.GooseSubscriber_getGoId(subscriber)))
    print("  goCbRef: {}".format(iec61850.GooseSubscriber_getGoCbRef(subscriber)))
    print("  dataSet: " , iec61850.GooseSubscriber_getDataSet(subscriber) , "\n")
    print("  confRev: " , iec61850.GooseSubscriber_getConfRev(subscriber) , "\n")
    print("  ndsCom: {}\n".format("True" if iec61850.GooseSubscriber_needsCommission(subscriber) else "False"))
    print("  simul: {}\n".format("True" if iec61850.GooseSubscriber_isTest(subscriber) else "False"))
    print("  stNum: " , iec61850.GooseSubscriber_getStNum(subscriber) , " sqNum: ", iec61850.GooseSubscriber_getSqNum(subscriber) , "\n")
    print("  timeToLive: ", iec61850.GooseSubscriber_getTimeAllowedToLive(subscriber) , "\n")
    timestamp = iec61850.GooseSubscriber_getTimestamp(subscriber)
    print(f"#lu#  timestamp: {timestamp}\n")
    print("  timestamp: ", (int) (timestamp / 1000), "." , (int) (timestamp % 1000) , "\n")
    print("  message is {}\n".format("valid" if iec61850.GooseSubscriber_isValid(subscriber) else "INVALID"))

#def gooseListener():
#    print("\n\nGOOSE MESSAGE FOUND\n\n")

# Main function
def main():

    receiver = iec61850.GooseReceiver_create()

    if len(sys.argv) > 1:
        print("Set interface id: {}\n".format(sys.argv[1]))
        iec61850.GooseReceiver_setInterfaceId(receiver, sys.argv[1])
    else :
        print("Using interface id: {}\n".format("1"))
        iec61850.GooseReceiver_setInterfaceId(receiver, "1")

    print("#1")
    subscriber = iec61850.GooseSubscriber_create("", None)
    print("#2")
    iec61850.GooseSubscriber_setObserver(subscriber)
    print("#3")
    iec61850.GooseSubscriber_setListener(subscriber, gooseListener(subscriber), None)    
#    iec61850.GooseSubscriber_setListener(subscriber, gooseListener(), None)
    print("#4")
    iec61850.GooseReceiver_addSubscriber(receiver, subscriber)
    print("#5")
    iec61850.GooseReceiver_start(receiver)
    print("#6")

    print("#running = {}".format(running))
    if iec61850.GooseReceiver_isRunning(receiver) :
        signal.signal(signal.SIGINT, sigint_handler)

        while running : 
            sleep_milliseconds(100)
#            print("#running = {}".format(running))
    else :
        print("Failed to start GOOSE subscriber. Reason can be that the Ethernet interface doesn't exist or root permission are required.\n")
    print("#7")
    print("#running = {}".format(running))
    print("\n Stage END \n")
    print("#running = {}".format(running))
    iec61850.GooseReceiver_stop(receiver)
    iec61850.GooseReceiver_destroy(receiver)

if __name__ == "__main__":
    main()

Python_1

On the second Python code I use some functions I saw in the iec61850.py library file but it runs without passing the iec61850.GooseSubscriber_setObserver function and then the program just stops (it should constantly continue to work till I stop it with a Control C):

import signal
import sys
import iec61850
import time

running = True

def sigint_handler(signalId, frame):
    global running
    running = False

def sleep_milliseconds(milliseconds):
    seconds = milliseconds / 1000.0
    time.sleep(seconds)

def gooseListener(subscriber):
    print("\n\nGOOSE MESSAGE FOUND\n\n")
    print("  vlanTag: {}\n".format("found" if iec61850.GooseSubscriber_isVlanSet(subscriber) else "NOT found"))
    if (iec61850.GooseSubscriber_isVlanSet(subscriber)):
        print("    vlanId: {}".format(iec61850.GooseSubscriber_getVlanId(subscriber)))
        print("    vlanPrio: {}".format(iec61850.GooseSubscriber_getVlanPrio(subscriber)))
    print("  appId: {}".format(iec61850.GooseSubscriber_getAppId(subscriber)))
    macBuf = ""
    #iec61850.GooseSubscriber_getSrcMac(subscriber,macBuf)
    #print(f"  srcMac: {macBuf}\n")
    #iec61850.GooseSubscriber_getDstMac(subscriber,macBuf)
    #print(f"  dstMac: {macBuf}\n")
    print("  goId: {}".format(iec61850.GooseSubscriber_getGoId(subscriber)))
    print("  goCbRef: {}".format(iec61850.GooseSubscriber_getGoCbRef(subscriber)))
    print("  dataSet: " , iec61850.GooseSubscriber_getDataSet(subscriber) , "\n")
    print("  confRev: " , iec61850.GooseSubscriber_getConfRev(subscriber) , "\n")
    print("  ndsCom: {}\n".format("True" if iec61850.GooseSubscriber_needsCommission(subscriber) else "False"))
    print("  simul: {}\n".format("True" if iec61850.GooseSubscriber_isTest(subscriber) else "False"))
    print("  stNum: " , iec61850.GooseSubscriber_getStNum(subscriber) , " sqNum: ", iec61850.GooseSubscriber_getSqNum(subscriber) , "\n")
    print("  timeToLive: ", iec61850.GooseSubscriber_getTimeAllowedToLive(subscriber) , "\n")
    timestamp = iec61850.GooseSubscriber_getTimestamp(subscriber)
    print(f"#lu#  timestamp: {timestamp}\n")
    print("  timestamp: ", (int) (timestamp / 1000), "." , (int) (timestamp % 1000) , "\n")
    print("  message is {}\n".format("valid" if iec61850.GooseSubscriber_isValid(subscriber) else "INVALID"))

#def gooseListener():
#    print("\n\nGOOSE MESSAGE FOUND\n\n")

# Main function
def main():

    receiver = iec61850.GooseReceiver_create()

    if len(sys.argv) > 1:
        print("Set interface id: {}\n".format(sys.argv[1]))
        iec61850.GooseReceiver_setInterfaceId(receiver, sys.argv[1])
    else :
        print("Using interface id: {}\n".format("1"))
        iec61850.GooseReceiver_setInterfaceId(receiver, "1")

    subscriber = iec61850.GooseSubscriberForPython()
    iec61850.GooseSubscriber_setObserver(subscriber.setLibiec61850GooseSubscriber(None))
    iec61850.GooseSubscriber_setListener(subscriber.setLibiec61850GooseSubscriber(None), gooseListener(subscriber.setLibiec61850GooseSubscriber(None)), None)
    iec61850.GooseReceiver_addSubscriber(receiver, subscriber.setLibiec61850GooseSubscriber(None))
    iec61850.GooseReceiver_start(receiver)

    print("#running = {}".format(running))
    if iec61850.GooseReceiver_isRunning(receiver) :
        signal.signal(signal.SIGINT, sigint_handler)

        while running : 
            sleep_milliseconds(100)
#            print("#running = {}".format(running))
    else :
        print("Failed to start GOOSE subscriber. Reason can be that the Ethernet interface doesn't exist or root permission are required.\n")
    print("#7")
    print("#running = {}".format(running))
    print("\n Stage END \n")
    print("#running = {}".format(running))
    iec61850.GooseReceiver_stop(receiver)
    iec61850.GooseReceiver_destroy(receiver)

if __name__ == "__main__":
    main()

Can anyone help me in checking what am I doing wrong? Am I using the incorrect function to make the GOOSE observer in Python?

Regards

nikunj1222 commented 3 months ago

Hi @TiagoHekker,

Please check the goose subcription implementation done in the following example πŸ‘ https://github.com/nikunj1222/Python_IEC61850/blob/main/iecGOOSEsub.py

Adarsh-365 commented 3 months ago

did you solve this issue ??

nikunj1222 commented 3 months ago

If your aim is to receive the goose in python on windows, then yes it worked for me. I am able to received goose as dictionary.

Adarsh-365 commented 3 months ago

Can you provide its code it will help me thanks !!!

nikunj1222 commented 3 months ago

Hi @Adarsh-365 the full code is avaiable on the this github repo πŸ‘ along with some other examples

https://github.com/nikunj1222/Python_IEC61850/blob/main/iecGOOSEsub.py

Adarsh-365 commented 3 months ago

Thanks @nikunj1222 i try it

from datetime import datetime
from operator import sub
import os,sys,time
import signal
from winreg import SetValue
from xmlrpc.client import boolean
import iec61850 as iec61850

class gooseSub:

    GooseSubRunning = 1
    _subscribehstatus = False
    subcribeGooseDatasetvalue = {}
    stNum = 0
    sqNum = 0
    timeToLive = 0
    ConfRev = 0
    TestMode = False
    NdsCommissioning = False
    LastGooseupdatetimestamp = 0
    subGoosevalid = False

    '''
    def signal_handler(signum, frame):  
        print("Signal ID:", signum, " Frame: ", frame) 

    def sigint_handler(signalId):
        GooseSubRunning = 0
    '''    
    def __init__(self,interfaceid='1',GOOSEappid=0x0001, GOOSEID='IEC61850System/LLN0$GO$gcb01', 
                 GOOSEctrlblkname='IEC61850System/LLN0$GO$gcb01',
                 MacAddress = [0x01,0x0c,0xcd,0x01,0x00,0x00], debugmode = False):
        self.interfaceid = interfaceid
        self.GOOSEappid  = GOOSEappid
        self.GOOSEID = GOOSEID
        self.GOOSEctrlblkname = GOOSEctrlblkname
        self.MacAddress = MacAddress
        self.debugmode = debugmode

    def gooseListner(self, subscriber) :
        print("GOOSE event:\n")
        print('stNum: ', iec61850.GooseSubscriber_getStNum(subscriber))
        print('sqNum: ', iec61850.GooseSubscriber_getSqNum(subscriber))
        print("timeToLive: ", iec61850.GooseSubscriber_getTimeAllowedToLive(subscriber))    
        print("ConfRev: ", iec61850.GooseSubscriber_getConfRev(subscriber))      
        print("Test Mode: ", iec61850.GooseSubscriber_isTest(subscriber))         
        print("Needs Commissioning: ", iec61850.GooseSubscriber_needsCommission(subscriber))     
        print("Publish Goose Dataset Ref: ", iec61850.GooseSubscriber_getDataSet(subscriber))         
        print("Publish Goose GOiD: ", iec61850.GooseSubscriber_getGoId(subscriber))

        RcvgooseTimestamp = iec61850.GooseSubscriber_getTimestamp(subscriber)

        print("Last goose Change timestamp: ", datetime.fromtimestamp(RcvgooseTimestamp/1e3))
        print("Goose message is valid: ", iec61850.GooseSubscriber_isValid(subscriber))

        values = iec61850.GooseSubscriber_getDataSetValues(subscriber)

        buffer=iec61850.MmsValue_printToBuffer(values,1024)

        #print("AllData:\n", buffer[0])

    def GOOSESubscriber(self):
        print("Goose Subscribed Using interface : ", self.interfaceid)

        receiver = iec61850.GooseReceiver_create()

        iec61850.GooseReceiver_setInterfaceId(receiver, self.interfaceid)

        subscriber = iec61850.GooseSubscriber_create(self.GOOSEctrlblkname, None)

        iec61850.GooseReceiver_addSubscriber(receiver, subscriber)
        iec61850.GooseReceiver_start(receiver)

        iec61850.GooseSubscriber_setDstMac(subscriber, self.MacAddress[0],self.MacAddress[1],self.MacAddress[2],self.MacAddress[3],self.MacAddress[4],self.MacAddress[5])
        iec61850.GooseSubscriber_setAppId(subscriber, self.GOOSEappid)

        iec61850.GooseSubscriber_setListener(subscriber, self.gooseListner(subscriber),None)

        if iec61850.GooseReceiver_isRunning(receiver):
            self._subscribehstatus = True
            #signal.signal(signal.SIGINT, signal_handler)
            while self.GooseSubRunning :
                if self.debugmode == True:
                    iec61850.GooseSubscriber_setListener(subscriber, self.gooseListner(subscriber),None)
                self.stNum = iec61850.GooseSubscriber_getStNum(subscriber)
                self.sqNum = iec61850.GooseSubscriber_getSqNum(subscriber)
                self.timeToLive = iec61850.GooseSubscriber_getTimeAllowedToLive(subscriber)
                self.ConfRev = iec61850.GooseSubscriber_getConfRev(subscriber)
                self.TestMode = iec61850.GooseSubscriber_isTest(subscriber)
                self.NdsCommissioning = iec61850.GooseSubscriber_needsCommission(subscriber)
                self.LastGooseupdatetimestamp = iec61850.GooseSubscriber_getTimestamp(subscriber)
                self.subGoosevalid = iec61850.GooseSubscriber_isValid(subscriber)        
                MMSsubGoosedataset = iec61850.GooseSubscriber_getDataSetValues(subscriber)
                buffer=iec61850.MmsValue_printToBuffer(MMSsubGoosedataset,1024)
                self.subcribeGooseDatasetvalue = buffer[0]
                time.sleep(1)
        else :
            print("Failed to start GOOSE subscriber. Reason can be that the Ethernet interface doesn't exist or root permission are required.\n")
            self._subscribehstatus = False
        iec61850.GooseReceiver_stop(receiver)
        iec61850.GooseReceiver_destroy(receiver)

x=gooseSub()  
sub=x.GOOSESubscriber()
x.gooseListner(sub) 

my python crashes on iec61850.GooseReceiver_start(receiver)``

from above repo you mentioned I try
https://github.com/nikunj1222/Python_IEC61850/blob/main/61850_server_Discovery%20-%20V1.py

work for me for MMS but for goose above code not working please tell me what I am doing wrong here thanks for help.

nikunj1222 commented 3 months ago

you have to make sure if your goid, goose appid, conf revision and mac address are matching.

Adarsh-365 commented 3 months ago

@nikunj1222 ok Than what should be interfaceid ??

nikunj1222 commented 3 months ago

Yes i forgot to mention about that. Since on windows interfaceID is an integer number. You need to start script starting from 0 till 6, one of these should work. Dont forget to mention this interfaceid as string in the script.

Adarsh-365 commented 3 months ago

@nikunj1222 I did for 0 it shows,

GOOSE event:

stNum:  0
sqNum:  0
timeToLive:  0
ConfRev:  0
Test Mode:  False
Needs Commissioning:  False
Publish Goose Dataset Ref:  
Publish Goose GOiD:  
Last goose Change timestamp:  1970-01-01 05:30:00
Goose message is valid:  False
(null)
(null)
(null)
(null)

and for other(1-6) it shows Windows fatal exception: access violation

Did you Create Publisher and subscriber in code or just subscriber and use any Hardware for Goose Publisher ??

@nikunj1222 What I am doing here is I have Omicron IED scout software which created simulated Relay which Broadcast Goose Signal I can see it on Wireshark. if I provide Omicron Relay .cid file of it. from it could you help me what I am doing wrong here Thanks in Advance!!!πŸ™πŸ™

nikunj1222 commented 3 months ago

@Adarsh-365 how many ethernet card you have in your testing computer ? send me your CID file i am sure your goose parameters are not matching.

Adarsh-365 commented 3 months ago

@nikunj1222 only one ethernet card Relay2_r3.zip Thanks For your Help😊

nikunj1222 commented 3 months ago

Your CID file has GCB01 enable with following parameters : Appid : 'IEC61850System/LLN0$GO$gcb01' confRev="1" MAC-2 : 01-0C-CD-01-00-00 APPID : 0001

Change your script to following shown in bold :

def init(self,interfaceid='0',GOOSEappid=0x0001, GOOSEID='IEC61850System/LLN0$GO$gcb01', GOOSEctrlblkname='IEC61850System/LLN0$GO$gcb01', MacAddress = [0x01,0x0c,0xcd,0x01,0x00,0x00], debugmode = True): self.interfaceid = interfaceid self.GOOSEappid = GOOSEappid self.GOOSEID = GOOSEID self.GOOSEctrlblkname = GOOSEctrlblkname self.MacAddress = MacAddress self.debugmode = debugmode

nikunj1222 commented 3 months ago

@Adarsh-365 also if possible send wireshark capture to understand the what is happening on the network.

Adarsh-365 commented 3 months ago

@nikunj1222 Thanks it working

sqNum:  1047
timeToLive:  1000
ConfRev:  1
Test Mode:  True
Needs Commissioning:  False
Publish Goose Dataset Ref:  IEC61850System/LLN0$Goose
Publish Goose GOiD:  IEC61850System/LLN0$GO$gcb01
Last goose Change timestamp:  2024-06-14 14:42:34.799000
Goose message is valid:  True
{false,true}

IEC.zip

Adarsh-365 commented 3 months ago

@nikunj1222 Can you tell me how much the Transmission Time of Goose from publisher to subscriber currently we are getting around 100 to 120ms ? , But as per standards it should 0 to 4ms. one more thing for windows interface id is number 0-6 in string and for linux what it will be ??

nikunj1222 commented 3 months ago

@Adarsh-365 For Linux it will the name of the interface itself e.g "Eth-1" or "Eth-0"

If your aim is to measure the accuracy python is not the correct tool. I use python only to do my routine functional test. For accuracy test you need to build your library in C on test on Linux.