Fraunhofer-FIT-DIEN / iec104-python

A Python module to simulate SCADA and RTU communication over protocol 60870-5-104 to research ICT behavior in power grids.
https://iec104-python.readthedocs.io/latest/python/index.html
GNU General Public License v3.0
45 stars 7 forks source link

Implementation of Event Driven COT's send from 104 Server #16

Open bambosd opened 5 months ago

bambosd commented 5 months ago

Hello and congratulations on this implementation in python. (i think is the first complete implementation of 60870-5-104)

I don't know if this is an issue, or there is an existing way to do that, i'm posting here since is the suggested way to contribute.

It seems that general interrogation, cyclic and request COT's on the server are implemented nicely, and i was able to handle them with the functions: on_before_auto_transmit , on_before_read and report_ms parameter on the point.

Based on the protocol , there are some event driven COT's, like Spontaneous, Background Scan, Initialized, response to remote command etc. some values are being send only if their value change, like Spontaneous , and some values are send if there is a change over a threshold (dead band) like background scan or initialized, and some are feedback to remote commands, like single point information , Double point information changes. How we can implement this on the server ? (to my understanding we need to execute the check of the values based on continue evaluation and transmit if we detect a change, otherwise not transmit. (on_before_auto_transmit cannot be aborted)

Can this be done with the existing functionality on the server, assuming the threshold check and changed values can be checked internally with existing python modules?

m-unkel commented 5 months ago

Hello, and thank you!

In addition to the functionalities for interrogation, cyclic, and request, the only other implemented Cause of Transmission is RETURN_INFO_REMOTE, which operates as follows:

On the server side, you have the option to assign a related point (and auto return) to a command point. By doing so, when a client sends a command, if successful, the server will automatically transmit the value of the related point using the COT RETURN_INFO_REMOTE, immediately following the acknowledgment of the command.

For example:

sv_measurement_point = sv_station.add_point(io_address=11, type=c104.Type.M_ME_TF_1, report_ms=1000)
sv_command_point =sv_station.add_point(io_address=12, type=c104.Type.C_SE_NC_1, report_ms=0, related_io_address=sv_measurement_point.io_address, related_io_autoreturn=True)

or:

point.related_io_address = 12345
point.related_io_autoreturn = True

The remaining COTs can be utilized manually through the point.transmit() methods argument cause in combination with qualifier.

Regarding event-driven handling, I'm open to suggestions on how to integrate other COTs with this Python module. Your ideas on which COTs to use for event-driven scenarios would be highly appreciated.

Also, I must admit that due to time constraints, I won't be able to release a new version before June.

Thank you for your contribution!

bambosd commented 4 months ago

Hello Martin,

i believe existing COT's: Cyclic, Station interrogation , requested , and spontaneous are covering 95% of use cases on distribution substations. I had trouble to proper implement the Spontaneous COT.

(Because: on_before_read function trigger, even if we set spontaneous COT is overwrite the COT to requested - which is correct) on_before_auto_transmit function trigger is the cyclic update for the values, which transmit with COT Cyclic - which is correct) i'm looking for similar way to trigger continuously a checking function, to implement the Spontaneous COT, explain more below.

in the documentation there is the following example: https://iec104-python.readthedocs.io/python/point.html#c104.Point.transmit sv_measurement_point.transmit(cause=c104.Cot.SPONTANEOUS)

how we can trigger on the server a python function based on a timing cycle (server_tick_rate or other method) , so we can check if a value is changed (altered value or change based on a deadband) and then decide to point.trasmit with COT spontaneous, otherwise not transmit ?? This will be the proper usage of COT Spontaneous.

Can you please provide some guide or example for this kind of implementation for COT Spontaneous? Or alternatively, how we can create a custom trigger for a check function on the server ?

bambosd commented 3 months ago

Hello Martin,

some updates here, trying to make it easier on your next development, please find below implementation of 4 different COT, with examples on how they are used on central SCADA substation control systems.

It seems that the server is just missing a general trigger according to the tick_rate_ms trigger or other periodic trigger just to be able to execute check functions for transmit spontaneous values.

To simulate this, i'm using the auto send trigger of a periodic value , just to trigger the check functions that will check some values to send them as Spontaneous. The conditions for spontaneous usually are two, A) a Binary value goes from True to False or False to True, a double point information goes from ON to OFF and vice-versa, and B) float values if exceed a certain delta / threshold of the value. (This requirement usually is requested by the local DSO / TSO Scada).

Example code on the server to check for spontaneous values every 500ms by using the trigger of a periodic IOA:

--------------------- start of code

spontaneous values creation

mp5 = station.add_point(io_address=301, type=c104.Type.M_ME_NC_1)

def check_all_spontaneous_values(point: c104.Point) -> None: print('checking spontaneous')

# update value from plant field
mp5.value = random.randint(0,10)  # import random

# check if value changed or threshold delta / deadband exceeded:
cond=True

if cond==True:
    mp5.transmit(cause=c104.Cot.SPONTANEOUS)

auxiliary value to trigger check_spontaneous with interval 500ms

spont_trigger = station.add_point(io_address=1, type=c104.Type.M_ME_NC_1, report_ms=500) spont_trigger.on_before_auto_transmit(callable=check_all_spontaneous_values)

--------------------------------------------------------------------------------end

IOA 301 and 302 : only spontaneous config. IOA 1,13,201,202 : always periodic because of report_ms parameter IOA 14,15 : only by request / station interrogation and read.

The test is performed by mz-automation free client tester. image

As of now, the outputs of the 104 Server are tested and functioning correctly with correct COT, we just need a general trigger for the check functions. I believe with this addition in terms of outputs, this 104 server is able to serve 95% of substation real cases.

I'm going to check inputs , setpoints, double commands as well.

m-unkel commented 3 weeks ago

Hello bambosd, thank you for sharing the details of your use case. I implement a general timer callback for points in the v2.0 release.

If you wish to, you can try it out via installing from source branch:

pip install git+https://github.com/Fraunhofer-FIT-DIEN/iec104-python.git@dev-2.0.0

But there are multiple changes under the hood and the breaking changes list in the Changelog is just a short brain dump and not a good instruction yet. I am still working on the compatibility of that branch.

m-unkel commented 1 week ago

Release 2.0.0 is now available, and it includes the feature that addresses this issue. I would greatly appreciate your feedback. If the issue is resolved to your satisfaction, please consider closing this thread.

Thank you!