Figuring out the libiec61850 python bindings

If you, like us, have had to deal with iec61850 communication for anything, you might have stumbled upon the libiec61850 library:

https://github.com/mz-automation/libiec61850

The library is basically the only open-source library out there, which provides most bindings for communicating with the iec61850 protocol.

The library is meant to be used as a shared object/DLL and is highly optimized for C, C++ and C# development.

I was about to sit down and create python bindings for the whole library myself, using ctypes, when I stumbled upon them having experimental python bindings.

This was great as it could definitely solve my headache of having to create ctype structures for the majority of the objects in the library.

There's just one small problem with the python bindings for this library. There is not a lot of documentation for it. There is:

  • One short tutorial file, which is how to compile the python bindings.
  • A test_pyiec61850.py with a total of 2 comments.
  • And 2 example python files.

Other than that, you don't really have much to work with except the source files.

So this is the journey of a sleep depraved researcher who had to figure out these bindings to create a small client to communicate with an iec61850 server.

Hopefully this blog post will be found by someone else, who had the same struggles as I did.

Installing the thing.

The tutorial file has a very small and neat description for installing the python bindings.

Before building you should install swig and python. To build python bindings you have to turn on the BUILD_PYTHON_BINDINGS flag in CMake from cmake-gui or in command line:

$ cmake -DBUILD_PYTHON_BINDINGS=ON .

Then compile the library and install it. CMake and swig will automatically detect your python version and install the python library in python library directories.

pyiec61850 library is to be imported calling

import iec61850

So first we install swig with pip3 install swig

If you don't have the build tools required, you'll need cmake, make, and the equivalent of the build-essential toolkit on your computer

For the build toolkit on ubuntu or other apt based distros you can run sudo apt-get install build-essential cmake make -y and you should have the prerequisites.

Once done, we can clone the repository with git clone https://github.com/mz-automation/libiec61850.git

Head into the directory and run cmake -DBUILD_PYTHON_BINDINGS=ON . We get some warnings, but no biggie.

Next we can do make and the project should start building. Again we'll get some warnings, but nothing out of the ordinary.

Now we can do sudo make install to install the bindings.

So now let's just go into the python3 interpreter and import the library and.. Oh.

>>> import iec61850
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/iec61850.py", line 12 in <module>
    import _iec61850
  ImportError: libiec61850.so.1.5.2: cannot open shared object file: No such file or directory
>>>

If you've never created python bindings for a shared object before, this might be very confusing.

Basically the library installs the shared object file, for the C bindings. Swig then creates python bindings to the shared object file, so you can use them in Python.

This also means that Python needs to know where the shared object file is, to be able to work with it. But somehow it got installed somewhere it can't find it.

It turns out, that for some reason the libiec61850 shared object gets installed inside of /usr/local/lib

Since this path is not normally on your LD_LIBRARY path by default. Python can not find it and therefore can't import it. There are 2 solutions to this problem:

  1. Updating your LD_LIBRARY path so Python can find the shared object file in that folder.
  2. Or just move the libiec61850.so.1.5.2 somewhere that Python already checks.

Since the 1st option requires me to do something more complicated than a copy, I decided to opt for the second option. Just moving the library into /lib.

NOTE: If you want to use the C bindings in the future and want to statically compile, you also need to move the .a files into the right folder (usually /usr/lib/x86_64-linux-gnu/)

Now if we import the library, we'll succeed!

>>> import iec61850
>>> 

Understanding the iec61850 protocol as a n00b

Now I ended up with my next problem. I did not know how the iec61850 protocol even worked and now I needed to test out this pretty much undocumented framework.

When looking up the iec61850 protocol, you get a lot of articles with a lot of charts and lot of different things, which to me, were extremely confusing. I kinda struggled to just find anything that shortly and easily explained the iec61850 protocol.

So after struggling a lot trying to figure out what is what, I'll try to give a short and simple explanation of what you need to know to interact with the iec61850 protocol.

Structure

The iec61850 protocol stores data in a tree-like structure called a model. The model may have multiple logical devices, each logical device may have multiple different logical nodes which all may have multiple different data objects, and each data object may have multiple different data attributes.

An example of a tree can be seen below:

  • LD = Logical Device
  • LN = Logical Node
  • DO = Data Object
  • DA = Data Attribute

iec61850 diagram example

Usually when you have things seperated in a tree-like structure, in IT you would think that changing LD 1 should never affect LD 2, only LD 1. However this is not the case for most implementations, as they are usually controlling multiple physical machines or one machine with multiple physical parts.

This means that modifying any attribute, data object, logical node or logical device, likely will affect other nodes, objects, devices or attributes, just like in real life.

As an example, let's say you have 2 logical devices constituting a driver of a car.

  • Current speed
  • Pressure on throttle

Modifying current speed would not do anything, as the current speed is decided by how much pressure is put on the throttle. However modifying the pressure on the throttle will change the current speed.

Of course this is a very simplified example, but the general idea applies. Before changing values, keep in mind of how the physical architecture is set up and what problems may arise from doing a change.

FC types

Each data object will have an FC (functional constraint) type, which tells the client what type of functionality the object supports.

To be able to write to an object or read from an object, you need to know what the object's FC type is.

The FC types implemented in libiec61850 is as follows:

IEC61850_FC_ST    -   Status information
IEC61850_FC_MX    -   Measurands
IEC61850_FC_SP    -   Setpoints
IEC61850_FC_SV    -   Substitutions
IEC61850_FC_CF    -   Configuration
IEC61850_FC_DC    -   Description
IEC61850_FC_SG    -   Setting group
IEC61850_FC_SE    -   Setting group editable
IEC61850_FC_SR    -   Service response / Service tracking
IEC61850_FC_OR    -   Operate received
IEC61850_FC_BL    -   Blocking
IEC61850_FC_EX    -   Extended definition
IEC61850_FC_CO    -   Control
IEC61850_FC_US    -   Unicast SV
IEC61850_FC_MS    -   Multicast SV
IEC61850_FC_RP    -   Unbuffered report
IEC61850_FC_BR    -   Buffered report
IEC61850_FC_LG    -   Log control blocks
IEC61850_FC_GO    -   Goose control blocks

IEC61850_FC_ALL   -   Wildcard value
IEC61850_FC_NONE  -   Wildcard value

The most common FC's you'll probably see are:

  • ST
  • MX
  • SP
  • SV
  • CF
  • CO

ST usually has the status information of a device, so reading it is usually interesting, but writing to it is not.

MX usually contains measurements of a logical device, so it's interesting to read but not to write.

SP and SV values are usually interesting values to set and substitute, dependent on what they are.

CF values are usually very interesting to read, as they can give you information on eg. maximum and minimum values of a data attribute.

CO is always interesting since it's a control object, which usually can let the client control some part of the logical device. The implementation of the control object highly depends on the vendor.

File directories

An iec61850 server may implement a file directory which allows the server to serve files to a client.

This can be anything from configuration files to firmware and documentation.

File directories are completely seperate from the device tree and will usually not show up whilst listing the device tree.

Clients may be allowed to read, delete and rename files in a file directory.

Data types

Lastly, each data attribute has a data type. The following data types are available in libiec61850:

Boolean
Float
Int32
Object
Octet String
uInt32
Visible String

To read a data attribute, you have to figure out what the data type is first, then read it.

With all the basics out of the way, let's get to figuring out this library!

Listing all objects and figuring out FC type.

Luckily there is an example python file of listing all objects inside of the libiec61850 repo, which looks like the following:

#!/usr/bin/python
import os,sys
import iec61850
if __name__=="__main__":
    hostname = "localhost";
    tcpPort = 102
    if len(sys.argv)>1:
        hostname = sys.argv[1]
    if len(sys.argv)>2:
        port = sys.argv[2]
    con = iec61850.IedConnection_create()
    error = iec61850.IedConnection_connect(con, hostname, tcpPort)
    if (error == iec61850.IED_ERROR_OK):
        [deviceList, error] = iec61850.IedConnection_getLogicalDeviceList(con)
        device = iec61850.LinkedList_getNext(deviceList)
        while device:
            LD_name=iec61850.toCharP(device.data)
            print("LD: %s" % LD_name)
            [logicalNodes, error] = iec61850.IedConnection_getLogicalDeviceDirectory(con, LD_name)
            logicalNode = iec61850.LinkedList_getNext(logicalNodes)
            while logicalNode:
                LN_name=iec61850.toCharP(logicalNode.data)
                print(" LN: %s" % LN_name)
                [LNobjects, error] = iec61850.IedConnection_getLogicalNodeVariables(con, LD_name+"/"+LN_name)
                LNobject = iec61850.LinkedList_getNext(LNobjects)
                while LNobject:
                    print("  DO: %s" % iec61850.toCharP(LNobject.data))
                    LNobject = iec61850.LinkedList_getNext(LNobject)
                iec61850.LinkedList_destroy(LNobjects)
                logicalNode = iec61850.LinkedList_getNext(logicalNode)
            iec61850.LinkedList_destroy(logicalNodes)
            device = iec61850.LinkedList_getNext(device)
        iec61850.LinkedList_destroy(deviceList)
        iec61850.IedConnection_close(con)
    else:
        print("Failed to connect to %s:%i\n"%(hostname, tcpPort))
    iec61850.IedConnection_destroy(con)

This seems extremely complicated, but in essence what it does is:

  1. Creates a connection and connects
  2. Runs getLogicalDeviceList() to grab a list of all the logical devices
  3. For each logical device, it runs getDeviceDirectory() to grab all logical nodes
  4. For each logical node, it runs getLogicalNodeVariables() to grab all the data objects
  5. Loops through all the data objects found using LinkedList_getNext()

It then prints the logical devices, nodes and data objects as it finds it in a tree like structure.

It should be noted that this example treats data object and data attribute names as the same thing.

Although this is true from an iec61850 perspective, from a coding perspective, a data attribute is NOT the same as a data object.

The main difference is that:

  • A data object may hold multiple data attributes
  • A data attribute may not hold more data objects

This means that a data attribute is usually always a leaf node.

Eg. if your output from the script looks like this:

LD: PNO02
 LN: HMI01
  DO: SP
  DO: SP$IPAddress
  DO: SP$IPAddress$setVal
  DO: SP$MACAddress
  DO: SP$MACAddress$setVal
  DO: SP$Port
  DO: SP$Port$setVal

Then in essence, what is data objects, logical devices, logical nodes and data attributes would look like this.

LD: PNO02                   <---- Logical Device
 LN: HMI01                  <---- Logical Node
  DO: SP                    <---- FC Type
  DO: SP$IPAddress          <---- Data Object
  DO: SP$IPAddress$setVal   <---- Data Attribute
  DO: SP$MACAddress         <---- Data Object
  DO: SP$MACAddress$setVal  <---- Data Attribute
  DO: SP$Port               <---- Data Object
  DO: SP$Port$setVal        <---- Data Attribute

The FC type is the same as what we covered in the section "FC types". In this case only data attributes of type SP exist.

Listing all objects this way is also a way to figure out what FC type an object is. As it will always be the name of the root data object node.

So if we want to read/write eg. IPAdress, we would look at the listing like this:

LD: PNO02
 LN: HMI01
  DO: SP <-------------------- Root object node of what we want to read/write
  DO: SP$IPAddress
  DO: SP$IPAddress$setVal <--- The node we want to read/write
  DO: SP$MACAddress
  DO: SP$MACAddress$setVal
  DO: SP$Port
  DO: SP$Port$setVal

And see that SP is the root object node, which makes the IEC61850_FC_SP the correct FC type for reading/writing to/from IPAddress.

Reading objects

Once the FC type has been figured out, it's time to figure out the data type.

We can figure out the data type of a leaf node by reading it as an object and parsing its type.

When reading a node, the format is like the following LD/LN.DO.DA. Note that SP is omitted as we give the readObject function the FC type as an argument.

An example of figuring out the IPAddress nodes' type from the previous example would be:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",8102)

da_to_read = "PNO02/HMI01.IPAddress.setVal"
da_fc_type = iec61850.IEC61850_FC_SP

res = iec61850.IedConnection_readObject(con, da_to_read, da_fc_type)

if(type(res) == list):
    type = iec61850.MmsValue_getTypeString(res[0])
    print(type)
else:
    print("Failed reading the data attribute")

iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

Note that we are reading from setVal and not from IPAddress. This is because usually items are both set and read from the leaf node even though it's called set. Confusing, I know.

However this does not mean that the IPAddress node itself may not store data too, it is up to the client to figure this out.

But for this case, it prints out integer, but it may also print types like visibleString, octetString, float, etc.

Once you know the data type, you can read the data attribute from the remote server using the corresponding function:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",8102)

da_to_read = "PNO02/HMI01.IPAddress.setVal"
da_fc_type = iec61850.IEC61850_FC_SP

res = iec61850.IedConnection_readInt32Value(con, da_to_read, da_fc_type)

print(res)

iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

When reading a data attribute you wil always receive a list as a response. The first element will be the value returned by the server. The 2nd value will be the error code.

If the error code is 0 (same as iec61850.IED_ERROR_OK), then the call was successful.

The following is a list of all read functions provided by libiec61850:

IedConnection_readBooleanValue
IedConnection_readDataSetValues
IedConnection_readDataSetValuesAsync
IedConnection_readFloatValue
IedConnection_readInt32Value
IedConnection_readInt64Value
IedConnection_readObject
IedConnection_readObjectAsync
IedConnection_readQualityValue
IedConnection_readStringValue
IedConnection_readTimestampValue
IedConnection_readUnsigned32Value

Writing objects

To write to a data object, you need to figure out the data and FC type which we covered in the last section.

Once the type has been found, you use the corresponding write function to write to it.

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

da_to_write = "PNO02/HMI01.IPAddress.setVal"
da_fc_type = iec61850.IEC61850_FC_SP
data_to_write = 0x12131415

error = iec61850.IedConnection_writeInt32Value(con, da_to_read, da_fc_type, data_to_write)

print(error)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

The following is a list of all write functions found in libiec61850.

IedConnection_writeBooleanValue
IedConnection_writeDataSetValues
IedConnection_writeDataSetValuesAsync
IedConnection_writeFloatValue
IedConnection_writeInt32Value
IedConnection_writeObject
IedConnection_writeObjectAsync
IedConnection_writeOctetString
IedConnection_writeUnsigned32Value
IedConnection_writeVisibleStringValue

Write functions always return just an integer, which is the error code. If the error code is 0, the write was successful.

Do note however, just because a write is successful it does not mean it impacts the application, it just means that you could write the value, without any trouble.

For example if you're writing to a value which is constantly updated, by the server, the server might just reset the value, when it receives information again from external factors.

Error codes

When writing or reading data objects, you may receive an error code as a response.

Dependent on your permissions to the object, the FC type of the object and access control on the iec61850 server the error code may constitute a multitude of things.

The following is a list of all possible error codes and their meaning, taken directly from libiec61850's source code:

# The service request can not be executed because the client is not yet connected
IED_ERROR_NOT_CONNECTED = 1

# Connect service not execute because the client is already connected
IED_ERROR_ALREADY_CONNECTED = 2

# The service request can not be executed caused by a loss of connection
IED_ERROR_CONNECTION_LOST = 3

# The service or some given parameters are not supported by the client stack or by the server
IED_ERROR_SERVICE_NOT_SUPPORTED = 4

# Connection rejected by server
IED_ERROR_CONNECTION_REJECTED = 5

# Cannot send request because outstanding call limit is reached
IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED = 6

# API function has been called with an invalid argument
IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT = 10
IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH = 11

# The object provided object reference is invalid (there is a syntactical error).
IED_ERROR_OBJECT_REFERENCE_INVALID = 12

# Received object is of unexpected type
IED_ERROR_UNEXPECTED_VALUE_RECEIVED = 13

# The communication to the server failed with a timeout
IED_ERROR_TIMEOUT = 20

# The server rejected the access to the requested object/service due to access control
IED_ERROR_ACCESS_DENIED = 21

# The server reported that the requested object does not exist (returned by server)
IED_ERROR_OBJECT_DOES_NOT_EXIST = 22

# The server reported that the requested object already exists
IED_ERROR_OBJECT_EXISTS = 23

# The server does not support the requested access method (returned by server)
IED_ERROR_OBJECT_ACCESS_UNSUPPORTED = 24

# The server expected an object of another type (returned by server)
IED_ERROR_TYPE_INCONSISTENT = 25

# The object or service is temporarily unavailable (returned by server)
IED_ERROR_TEMPORARILY_UNAVAILABLE = 26

# The specified object is not defined in the server (returned by server)
IED_ERROR_OBJECT_UNDEFINED = 27

# The specified address is invalid (returned by server)
IED_ERROR_INVALID_ADDRESS = 28

# Service failed due to a hardware fault (returned by server)
IED_ERROR_HARDWARE_FAULT = 29

# The requested data type is not supported by the server (returned by server)
IED_ERROR_TYPE_UNSUPPORTED = 30

# The provided attributes are inconsistent (returned by server)
IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 31

# The provided object value is invalid (returned by server)
IED_ERROR_OBJECT_VALUE_INVALID = 32

# The object is invalidated (returned by server)
IED_ERROR_OBJECT_INVALIDATED = 33

# Received an invalid response message from the server
IED_ERROR_MALFORMED_MESSAGE = 34

# Service not implemented 
IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98

# unknown error
IED_ERROR_UNKNOWN = 99

All of these constants are also built-in to the python bindings of libiec61850 and can be accessed through the iec61850 object.

File Directory reading

Lastly we'll cover the hardest thing to figure out how to do with the pyiec61850 bindings. How to list the files on the iec61850 server.

One would think that the following code should list all files in the file directory:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

rootdir = iec61850.IedConnection_getFileDirectory(con,'')
iec61850.LinkedList_printStringList(rootdir[0])
direcEntry = iec61850.LinkedList_create()
direcEntry = iec61850.LinkedList_getNext(rootdir[0])
while direcEntry:
    print(iec61850.FileDirectoryEntry_getFileName(direcEntry.data))
    direcEntry = iec61850.LinkedList_getNext(direcEntry)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

Here we're grabbing the root directory and ask it to print it, whereafter we're iterating through the list to get file names.

However since no proper bindings have been made for FileDirectoryEntry we get the following traceback:

Traceback (most recent call last):
  File "/home/zopazz/Documents/iec61850/read.py", line 12, in <module>
    print(iec61850.FileDirectoryEntry_getFileName(direcEntry.data))
  File "/usr/lib/python3/dist-packages/iec61850.py", line 790, in FileDirectoryEntry_getFileName
    return _iec61850.FileDirectoryEntry_getFileName(_self)
TypeError: in method 'FileDirectoryEntry_getFileName', argument 1 of type 'FileDirectoryEntry'

The problem lies in that swig sees the object of direcEntry.data to be a void pointer, whilst FileDirectoryEntry_getFileName takes a FileDirectoryEntry object.

Since swig sees that this is obviously a void pointer and not a FileDirectoryEntry object, it tells us we're 100% wrong and won't allow us to do it.

So what's the solution?

The solution is to create a missing binding, which allows us to cast a void pointer to a FileDirectoryEntry, as this is a problem with swig missing a binding.

We can do this by first going into pyiec61850/iec61850.i and adding the following to line 14:

FileDirectoryEntry toFileDirectoryEntry(void* data)
{
    return (FileDirectoryEntry) data;
}

And then adding following to line 63

FileDirectoryEntry toFileDirectoryEntry(void*);

We basically create an extremely simple binding for swig, which casts a void pointer to a FileDirectoryEntry.

Once that is done, we need to recompile and reinstall the whole thing, just as we did in step one.

Once that is done, we can add it to our simple example above and we get a full directory listing:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

rootdir = iec61850.IedConnection_getFileDirectory(con,'')
direcEntry = iec61850.LinkedList_create()
direcEntry = iec61850.LinkedList_getNext(rootdir[0])
while direcEntry:
    print(iec61850.FileDirectoryEntry_getFileName(iec61850.toFileDirectoryEntry(direcEntry.data)))
    direcEntry = iec61850.LinkedList_getNext(direcEntry)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

This also allows us to use the rest of the FileDirectoryEntry functionality in libiec61850.

Downloading a file

Once you know what files the iec61850 server is serving, you probably want to be able to download it as a start.

I got terrible news for you then, because the IedConnection_getFile function needs a handler, which we can't give it, as that is not available with the python bindings we were given.

So let's create some bindings for this. I stuck to editing the iec61850.i file as modifying the other source code seemed a bit daunting.

First, on line 18 let's add the following lines of code:

FILE* openFile(char* name)
{
    return fopen(name,"w+");
}

static bool IedConnection_downloadHandler(void* parameter, uint8_t* buffer, uint32_t bytesRead)
{
    FILE* fp = (FILE*) parameter;
    if(fp == NULL){
       return false;
    }
    if (bytesRead > 0) {
        if (fwrite(buffer, bytesRead, 1, fp) != 1) {
            printf("Failed to write local file!\n");
            fclose(fp);
            return false;
        }
    }
    fclose(fp);
    return true;
}

IedClientGetFileHandler getIedconnectionDownloadHandler(){
    return (IedClientGetFileHandler) &IedConnection_downloadHandler;
}

Let's quickly break down what we've done here.

First, we need a way to open files, so the user can choose the filename it will be saved as. So we create an openFile function. We need to create this function this way, so we can pass the file pointer to the download handler afterwards.

Next, we need the download handler itself. I decided for the download handler that we can just pass it a file pointer and it will create the file for us.

Lastly, we need a function which grabs our download handler and typecasts it into an IedClientGetFileHandler, as this is the type the IedConnection_getFile function accepts, which is why we have getIedConnectionDownloadHandler

On line 86, we then add the following lines of code:

FILE* openFile(char*);
static bool IedConnection_downloadHandler(void*, uint8_t*, uint32_t);
IedClientGetFileHandler getIedconnectionDownloadHandler();

This is to make sure that the functions appear inside of the iec61850 library.

Now we recompile, again and reinstall, again. Once done we can use the following code to download the file from the iec61850 server:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

download_handler = iec61850.getIedconnectionDownloadHandler()
fp = iec61850.openFile("./testfile")

[readBytes, error] = iec61850.IedConnection_getFile(con,"test",download_handler,fp)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

And we now have a way to get files from the server.

Uploading and deleting files

Lastly, we can upload or delete files, which luckily just works out of the box.

To upload a file we can use the IedConnection_setFile function. Remember to call IedConnection_setFilestoreBasepath, first to specify from which directory you're working from.

Then we can just specify the local name and the destination filename:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

iec61850.IedConnection_setFilestoreBasepath(con,"./")

local_filename = "out.txt"
destination_filename = "test.txt"

setpoint = iec61850.IedConnection_setFile(con, local_filename, destination_filename)

print(setpoint)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

Deleting files is done by just specifying the filename:

import iec61850

con = iec61850.IedConnection_create()

error = iec61850.IedConnection_connect(con,"127.0.0.1",102)

iec61850.IedConnection_setFilestoreBasepath(con,"./")

file_to_delete = "test"

setpoint = iec61850.IedConnection_deleteFile(con, file_to_delete)

print(setpoint)


iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)

Conclusion

We've now learned how to do the most basic operations with an iec61850 server using the libiec61850 python bindings.

Although the bindings are not perfect, with a few modifications we can get most of the functionality working.

I highly recommend that you manually implement your own patches covered in this blog post on the latest version of libiec61850.

If however, you're too lazy, you can find the patched version I've made during this blog post here:

https://github.com/Zopazz/libiec61850

This repository will not be kept up to date, hence why I highly recommend you apply the patches yourself.

I hope you enjoyed the journey of figuring out the libiec61850 bindings :-)

Jens Nielsen - Senior Security Researcher @ ICSRange.

Previous
Previous

Bsides CPH

Next
Next

Cisco Advisory CVE-2023-20235