GithubHelp home page GithubHelp logo

fraunhofer-fit-dien / iec104-python Goto Github PK

View Code? Open in Web Editor NEW
29.0 4.0 5.0 257 KB

A Python module to simulate SCADA and RTU communication over protocol 60870-5-104 to research ICT behavior in power grids.

Home Page: https://iec104-python.readthedocs.io/python/index.html

License: GNU General Public License v3.0

CMake 1.16% Python 9.40% Shell 0.90% Batchfile 0.07% C++ 88.46%
60870-5-104 python3 energy-management iec60870-5 iec60870-5-104 mtu remote-terminal-unit rtu scada

iec104-python's Introduction

iec104-python

Table of contents

  1. Introduction
  2. Licensing
  3. System requirements
  4. Installation
  5. Wiki
  6. Contribution

Introduction

This software provides an object-oriented high-level python module to simulate scada systems and remote terminal units communicating via 60870-5-104 protocol.

The python module c104 combines the use of lib60870-C with state structures and python callback handlers.

Example remote terminal unit

import c104

# server and station preparation
server = c104.Server(ip="0.0.0.0", port=2404)

# add local station and points
station = server.add_station(common_address=47)
measurement_point = station.add_point(io_address=11, type=c104.Type.M_ME_NC_1, report_ms=1000)
command_point = station.add_point(io_address=12, type=c104.Type.C_RC_TA_1)

server.start()

Example scada unit

import c104

client = c104.Client(tick_rate_ms=1000, command_timeout_ms=5000)

# add RTU with station and points
connection = client.add_connection(ip="127.0.0.1", port=2404, init=c104.Init.INTERROGATION)
station = connection.add_station(common_address=47)
measurement_point = station.add_point(io_address=11, type=c104.Type.M_ME_NC_1, report_ms=1000)
command_point = station.add_point(io_address=12, type=c104.Type.C_RC_TA_1)

client.start()

See examples folder for more detailed examples.

Licensing

This software is licensed under the GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html).

See LICENSE file for the complete license text.

Dependencies

lib60870-C

This project is build on top of lib60870-C v2 from MZ Automation GmbH, which is licensed under GPLv3.

The library is used for 60870-5-104 protocol based communication.

» Source code

» Documentation

mbedtls

This project is build on top of mbedtls from the Mbed TLS Contributors, which is licensed under Apache-2.0.

The library is used to add transport layer security to the 60870-5-104 protocol based communication.

» Source code

» Documentation

pybind11

This project is build on top of pybind11 from Wenzel Jakob, which is licensed under a BSD-style license.

The library is used to wrap c++ code into a python module and allow seamless operability between python and c++.

» Source code

» Documentation

catch2

This project is build on top of catch2 from the Catch2 Authors, which is licensed under BSL-1.0.

The library is used as testing framework for test-automation.

» Source code

» Documentation

System requirements

Operating systems

  • Debian/Ubuntu (x64): YES >= 20.04
  • Raspbian (arm32v7): YES
  • Windows (x64): YES
  • Raspbian (aarch64): Not yet tested

Python versions

  • python >= 3.6, < 3.13

Installation

Please adjust the version number to the latest version or use a specific version according to your needs.

Install from pypi.org

python3 -m pip install c104

Install from git with tag

python3 -m pip install c104@git+https://github.com/fraunhofer-fit-dien/iec104-python.git

Documentation

Read more about the Classes and their Properties in our read the docs documentation.

Contribution

How to contribute

  1. Add feature requests and report bugs using GitHub's issues

  2. Create pull requests

How to build for multiple python versions (linux with docker)

  1. Build wheels via docker
    /bin/bash ./bin/linux-build.sh

How to build (linux)

  1. Install dependencies

    sudo apt-get install build-essential python3-pip python3-dev python3-dbg
    python3 -m pip install --upgrade pip
  2. Build wheel

    python3 -m pip wheel .

How to analyze performance (linux)

  1. Install dependencies

    sudo apt-get install google-perftools valgrind
    sudo pip3 install yep
  2. Copy pprof binary

    cd /usr/bin
    sudo wget https://raw.githubusercontent.com/gperftools/gperftools/master/src/pprof
    sudo chmod +x pprof
  3. Execute profiler script

    ./bin/profiler.sh

How to build (windows)

  1. Install dependencies

  2. Build wheel

    python3 -m pip wheel .

Generate documentation

  1. Build c104 module

  2. Install dependencies

    • python3 -m pip install --upgrade sphinx breathe sphinx-autodoc-typehints
    • doxygen
    • graphviz
  3. Build doxygen xml

    doxygen Doxyfile
  4. Build sphinx html

    python3 bin/build-docs.py

Change log

Track all changes in our CHANGELOG documentation.

iec104-python's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

iec104-python's Issues

Setting the quality value at the default Point

Hello. I had the following question: imagine that at some point my daughter's quality parameter changed to point.quality = c 104.Quality.Invalid. But then, after several polls, the quality of this point should become normal again, those QUALITY = Quality set: {}, is_good: True. How could I do that?
I tried to change this somehow in the before_transmit function in point.on_before_read(callable=before_transmit), where point.set(value=32, quality=point.quality, timestamp_ms=int(time.time() * 1000)), but I failed. How can I set the default value to quality again?

TLS Connection Handshake failes

Hello Everyone!

Can anyone get a TLS connection working?

I'm using the minimal example with added

    tls = c104.TransportSecurity(validate=True, only_known=False)
    tls.set_ca_certificate(cert="./root.cer")
    tls.set_certificate(cert="./client1.cer", key="./client1-key.pem")

    tls.add_allowed_remote_certificate(cert="./server.cer")

configuration (for server and client respectively) using the example certificates from the lib60870 tls examples.
No matter what I configure or how I set it up (even with validate=False) I keep getting

TransportSecurity.event] Alarm: Certificate verification failed (t: 2, c: 6, version: TLS 1.2 remote-ip: 127.0.0.1:2404)
on the client and and on the server
TransportSecurity.event] Alarm: handshake failed for unknown reason (t: 2, c: 8, version: TLS 1.2 remote-ip: 127.0.0.1:35534)

I'm not sure if this is a bug in the software or if I'm missing something crucial, but since the documentation of the python part of the library regarding TLS is quite limited I'm running out of ideas on what to try.

Any help/ideas would be appreciated!

Best Regards!
Milan

EDIT:
I'm attaching my minimal test setup: tls_test.zip

server on_receive callback: message.raw cannot be accessed

In my c104.Server, I added a c104.Type.C_SC_NA_1 point, and attached an on_receive callback function to it.

In the callback function, I cannot read the message.raw property (e.g. simply: raw = message.raw or print(f"value: {message.raw})). I get the following error:

Point.on_receive] Error:
TypeError: Unregistered type : _object

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/app/server.py", line 100, in _c104_point_shutdown_callback
    raw = message.raw
TypeError: Unable to convert function return value to a Python type! The signature was
  (arg0: c104.IncomingMessage) -> _object

The message.raw_explain and message.value properties can be accessed as expected. The client that transmits the message is also a iec104-python c104.Client, running in a separate docker container.

c104 version: 1.18.0
python version: 3.12.2

Feature Request: Support for Transmitting Single Event of Protection Messages

Overview:
Currently, the server lacks the capability to transmit Single Event of Protection (SEP) messages, which limits its functionality in researching critical scenarios. This feature request proposes enhancing the server functionality to support the transmission of SEP messages. Additionally, the client should be empowered with a callback handler mechanism to efficiently handle and respond to these messages.

Timeline:
A tentative release date for this feature is expected to be after June 14.

c104 Type does not match with client request

I am trying to launch a server and a client at the same time.
The exchange of data is a M_ME_NC_1 (Measured value, short floating point number).
I used Wireshark to see the exchanged packages and I see that there is a C_RD_NA_1 (Read Command) that makes the point read to fail.
The client code I used:

import c104
client = c104.Client(tick_rate_ms=1000, command_timeout_ms=1000)
connection = client.add_connection(ip="127.0.0.1", port=2404, init=c104.Init.INTERROGATION)
station = connection.add_station(common_address=4)
point01 = station.add_point(io_address=20053, type=c104.Type.M_ME_NC_1)
client.start()
if point01.read():
   print(f"-> SUCCESS {point01.value}")
else:
   print("-> FAILURE")

The Wireshark shows that during the request there is the package with the command mentioned:
simple_wireshark_remote

And for that reason it seems to fail the read of the point value:

[c104.Client] Created
[c104.Client] add_connection] IP 127.0.0.1 | PORT 2404
[c104.Connection] Created
[c104.Connection] add_station] CA 4
[c104.Connection] state] CLOSED -> CLOSED_AWAIT_OPEN
[c104.Connection] connect] Asynchronous connect to 127.0.0.1:2404
[c104.Client] start] Started
[c104.Connection] set_open] 127.0.0.1:2404
[c104.Connection] set_open] Opening connection to 127.0.0.1:2404
[c104.Connection] state] CLOSED_AWAIT_OPEN -> OPEN_MUTED
[c104.Connection] set_open] Unmuting connection to 127.0.0.1:2404
[c104.Connection] unmute] Unmute connection to 127.0.0.1:2404
[c104.Connection] raw_message_handler] Stats | TOTAL 0 �s
[c104.Connection] set_open] DONE 127.0.0.1:2404
[c104.Connection] connection_handler] Connection OPENED to 127.0.0.1:2404 | TOTAL 92 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 8 �s
[c104.Connection] set_muted] Muted: 0 for connection to 127.0.0.1:2404
[c104.Connection] state] OPEN_MUTED -> OPEN_AWAIT_INTERROGATION
[c104.Connection] connection_handler] Connection ACTIVATED to 127.0.0.1:2404 | TOTAL 17 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 1 �s
[c104.Client] thread_run] Connected servers: 1
[c104.Connection] raw_message_handler] Stats | TOTAL 0 �s
[c104.Connection] await_command_success] Await 4-C_RD_NA_1-20053
[c104.Connection] raw_message_handler] Stats | TOTAL 9 �s
[c104.Connection] state] OPEN_AWAIT_INTERROGATION -> OPEN
[c104.Connection] set_command_success] Result 65535-C_IC_NA_1-0: 1
[c104.Connection] asdu_handler] C_IC_NA_1 Response Stats | CA 65535 | IOA 0 | TOTAL 27 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 12 �s
[c104.Connection] set_command_success] Result 65535-C_IC_NA_1-0: 1
[c104.Connection] asdu_handler] C_IC_NA_1 Response Stats | CA 65535 | IOA 0 | TOTAL 23 �s
[c104.Client] thread_run] Active servers: 1
[c104.Connection] await_command_success] Timeout 4-C_RD_NA_1-20053
[c104.Connection] await_command_success] Stats 4-C_RD_NA_1-20053 | TOTAL 18446744073709440 �s
-> FAILURE
[c104.Connection] raw_message_handler] Stats | TOTAL 7 �s
[c104.Connection] set_closed] Connection closed to 127.0.0.1:2404
[c104.Connection] state] OPEN -> CLOSED_AWAIT_RECONNECT
[c104.Connection] connection_handler] Connection CLOSED to 127.0.0.1:2404 | TOTAL 40 �s
[c104.Connection] state] CLOSED_AWAIT_RECONNECT -> CLOSED
[c104.Client] stop] Stopped
[c104.Client] Removed
[c104.Connection] Removed

If I try it making my local server, I see the command C_RD_NA_1, but does not fail. Can I configure any other parameter o could be a problem from the server from the third party? It seems that the third party server has no problem with other connections from other users.

However, as mentioned, when I create my local server with the code from the example (changing the common_address=47 and io_address=11 similar to the example of documentation), I correctly access the data:

[c104.Client] Created
[c104.Client] add_connection] IP 127.0.0.1 | PORT 2404
[c104.Connection] Created
[c104.Connection] add_station] CA 47
[c104.Connection] state] CLOSED -> CLOSED_AWAIT_OPEN
[c104.Connection] connect] Asynchronous connect to 127.0.0.1:2404
[c104.Client] start] Started
Waiting for connection to 127.0.0.1:2404
[c104.Connection] set_open] 127.0.0.1:2404
[c104.Connection] set_open] Opening connection to 127.0.0.1:2404
[c104.Connection] state] CLOSED_AWAIT_OPEN -> OPEN_MUTED
[c104.Connection] set_open] Unmuting connection to 127.0.0.1:2404
[c104.Connection] unmute] Unmute connection to 127.0.0.1:2404
[c104.Connection] raw_message_handler] Stats | TOTAL 0 �s
[c104.Connection] set_open] DONE 127.0.0.1:2404
[c104.Connection] connection_handler] Connection OPENED to 127.0.0.1:2404 | TOTAL 77 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 3 �s
[c104.Connection] set_muted] Muted: 0 for connection to 127.0.0.1:2404
[c104.Connection] state] OPEN_MUTED -> OPEN_AWAIT_INTERROGATION
[c104.Connection] connection_handler] Connection ACTIVATED to 127.0.0.1:2404 | TOTAL 13 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 16 �s
[c104.Connection] asdu_handler] M_ME_NC_1 Report Stats | CA 47 | IOA 11 | TOTAL 35 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 1 �s
[c104.Client] thread_run] Connected servers: 1
[c104.Connection] raw_message_handler] Stats | TOTAL 5 �s
[c104.Connection] state] OPEN_AWAIT_INTERROGATION -> OPEN
[c104.Connection] set_command_success] Result 47-C_IC_NA_1-0: 1
[c104.Connection] asdu_handler] C_IC_NA_1 Response Stats | CA 47 | IOA 0 | TOTAL 25 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 5 �s
[c104.Connection] asdu_handler] M_ME_NC_1 Report Stats | CA 47 | IOA 11 | TOTAL 13 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 1 �s
[c104.Connection] set_command_success] Result 47-C_IC_NA_1-0: 1
[c104.Connection] asdu_handler] C_IC_NA_1 Response Stats | CA 47 | IOA 0 | TOTAL 13 �s
[c104.Connection] raw_message_handler] Stats | TOTAL 0 �s
[c104.Connection] await_command_success] Await 47-C_RD_NA_1-11
[c104.Connection] raw_message_handler] Stats | TOTAL 4 �s
[c104.Connection] set_command_success] Result 47-C_RD_NA_1-11: 1
[c104.Connection] asdu_handler] M_ME_NC_1 Report Stats | CA 47 | IOA 11 | TOTAL 32 �s
[c104.Connection] await_command_success] Result 47-C_RD_NA_1-11: 1
[c104.Connection] await_command_success] Stats 47-C_RD_NA_1-11 | TOTAL 999497 �s
-> SUCCESS 44.627750396728516
[c104.Connection] raw_message_handler] Stats | TOTAL 7 �s
[c104.Connection] set_closed] Connection closed to 127.0.0.1:2404
[c104.Connection] state] OPEN -> CLOSED_AWAIT_RECONNECT
[c104.Connection] connection_handler] Connection CLOSED to 127.0.0.1:2404 | TOTAL 43 �s
[c104.Connection] state] CLOSED_AWAIT_RECONNECT -> CLOSED
[c104.Client] stop] Stopped
[c104.Client] Removed
[c104.Connection] Removed

Python Script stops when the client is stopped without a connection

Hello. I noticed that when the commands client.stop(), client.reconnect_all(), and connection.close() are called without a valid connection between client and server, the execution of the Python script stops immediately, not running until the end.

Below is a code example that reproduces this error on my machine:

import c104

IP_ADDRESS = "10.11.1.21" # Any invalid IP
STATION_COMMON_ADDRESS = 1

if __name__ == "__main__":
    client = c104.Client(tick_rate_ms=0, command_timeout_ms=5000)
    connection = client.add_connection(
        ip=IP_ADDRESS, port=2404, init=c104.Init.INTERROGATION
    )
    station = connection.add_station(common_address=STATION_COMMON_ADDRESS)

    while True:
        print("Loop Start")
        client.start()
        client.stop() # or client.reconnect_all()
        print("Loop End") # This never occurs, because the script will stop at client.stop()

Tested on Python 3.10 and 3.12 with C104 1.18.0 and 1.17.1.

Thanks in advance.

Add option to use Select or Execute in Qualifier of Command

When trying to send a Single Command (C_SC_NA_1) from an IEC104 client to a slave device, an error occurs because the command has the QOC's S/E field set to "execute" (value 0).

Therefore, it is necessary to send the command with the QOC's S/E field set to "select" (value 1), and then send the same command with "execute".

Below are images collected through Wireshark.

The first images displays the packets for the execution of a Single Command (C_SC_NA_1) using the TestHarness software from Triangle Microworks. In it, you can observe the sending of an ASDU of type C_SC_NA_1 with COT = ACT and S/E = Select. Subsequently, the slave responds with ACTCON.
wireshark - iec104 - testharness - 1

After the Select, the same command is sent again with COT = ACT and S/E = Execute. The slave responds with ACTCON as expected.
wireshark - iec104 - testharness - 2

Following that, you can verify the correct writing of the value to the slave device.

The next images show the same command being sent through the IEC104 Python client built with this library. It can be seen that when sending a command, only the command with the S/E = Execute field is sent, missing the Select part before. Therefore, the slave responds with ACTCON_NEGA, and the sent value is not updated on the slave device.
wireshark - iec104 - python client

Because of this, the value sent by the client is never updated on the slave device.

Wheel problem

I wanted python based c104 emulator. This package looked exactly what I needed. But I run into installation problem. The exact error message is as follows:

note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for c104
Failed to build c104
ERROR: Could not build wheels for c104, which is required to install pyproject.toml-based projects

My python and pip should be latest updated versions of their kind.
Attached snip of the error. How to solve 'wheel' problem? I know they say do not reinvent wheel, use it. But I cannot even have this wheel.

githubUpload

Unable to install c104 package

Hello!

I'm attempting to freshly install the package of the current version 1.17.1. However, during the installation process an error appears:

Error message
Collecting c104
  Using cached c104-1.17.1.tar.gz (21 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: c104
  Building wheel for c104 (pyproject.toml) ... error
  error: subprocess-exited-with-error

  × Building wheel for c104 (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [70 lines of output]
      running bdist_wheel
      running build
      running build_ext
      CMake Warning:
        Ignoring extra path from command line:

         "<omitted>\AppData\Local\Temp\pip-install-fsnogc9u\c104_ac3d6c9f0b6448bcb87ad28f9437a588"


      CMake Error: The source directory "<omitted>/AppData/Local/Temp/pip-install-fsnogc9u/c104_ac3d6c9f0b6448bcb87ad28f9437a588" does not appear to contain CMakeLists.txt.
      Specify --help for usage, or press the help button on the CMake GUI.
      Traceback (most recent call last):
        File "<omitted>\AppData\Local\Programs\Python\Python312\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 353, in <module>
          main()
        File "<omitted>\AppData\Local\Programs\Python\Python312\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "<omitted>\AppData\Local\Programs\Python\Python312\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 251, in build_wheel
          return _build_backend().build_wheel(wheel_directory, config_settings,
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\build_meta.py", line 404, in build_wheel
          return self._build_with_temp_dir(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\build_meta.py", line 389, in _build_with_temp_dir
          self.run_setup()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\build_meta.py", line 311, in run_setup
          exec(code, locals())
        File "<string>", line 131, in <module>
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\__init__.py", line 103, in setup
          return distutils.core.setup(**attrs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\core.py", line 185, in setup
          return run_commands(dist)
                 ^^^^^^^^^^^^^^^^^^
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\core.py", line 201, in run_commands
          dist.run_commands()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 969, in run_commands
          self.run_command(cmd)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\dist.py", line 963, in run_command
          super().run_command(command)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command
          cmd_obj.run()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\wheel\bdist_wheel.py", line 368, in run
          self.run_command("build")
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\cmd.py", line 318, in run_command
          self.distribution.run_command(command)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\dist.py", line 963, in run_command
          super().run_command(command)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command
          cmd_obj.run()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\command\build.py", line 131, in run
          self.run_command(cmd_name)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\cmd.py", line 318, in run_command
          self.distribution.run_command(command)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\dist.py", line 963, in run_command
          super().run_command(command)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command
          cmd_obj.run()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\command\build_ext.py", line 88, in run
          _build_ext.run(self)
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\command\build_ext.py", line 345, in run
          self.build_extensions()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\command\build_ext.py", line 467, in build_extensions
          self._build_extensions_serial()
        File "<omitted>\AppData\Local\Temp\pip-build-env-r9yoj7_l\overlay\Lib\site-packages\setuptools\_distutils\command\build_ext.py", line 493, in _build_extensions_serial
          self.build_extension(ext)
        File "<string>", line 123, in build_extension
        File "<omitted>\AppData\Local\Programs\Python\Python312\Lib\subprocess.py", line 571, in run
          raise CalledProcessError(retcode, process.args,
      subprocess.CalledProcessError: Command '['cmake', '<omitted>\\AppData\\Local\\Temp\\pip-install-fsnogc9u\\c104_ac3d6c9f0b6448bcb87ad28f9437a588', '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=<omitted>\\AppData\\Local\\Temp\\pip-install-fsnogc9u\\c104_ac3d6c9f0b6448bcb87ad28f9437a588\\build\\lib.win-amd64-cpython-312\\', '-DPYTHON_EXECUTABLE=<omitted>\\AppData\\Local\\Programs\\Python\\Python312\\python.exe', '-DCMAKE_BUILD_TYPE=Release', '-DC104_VERSION_INFO=1.17.1', '-A', 'x64', '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE=<omitted>\\AppData\\Local\\Temp\\pip-install-fsnogc9u\\c104_ac3d6c9f0b6448bcb87ad28f9437a588\\build\\lib.win-amd64-cpython-312']' returned non-zero exit status 1.
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for c104
Failed to build c104
ERROR: Could not build wheels for c104, which is required to install pyproject.toml-based projects

I tried to install the package on Windows, Ubuntu, both Python312 and Python 311. All attempts produce same error.

Note:
Few months ago I tested the package version 1.16.1 and it was installed and worked without problems, therefore I'm trying to figure out if the issue's on my side or in the package distribution.

Keep Client Open

Hi! I'm creating a master client with you lib, but I would like to now how to keep the client open, for now if I don't set the sleep, it's going to finish

import c104
import random
import time


def handle_packets(connection: c104.Connection, data: bytes) -> None:
    print(
        "-->| {1} [{0}] | CON {2}:{3} HAHAHAHAHAHAHAHHAHAHA".format(
            data.hex(),
            c104.explain_bytes(apdu=data),
            connection.ip,
            connection.port,
        )
    )
    return


class IECMasterClient:
    def __init__(self, ip: str, port: int, common_address: int = 27808):
        self.ip = ip
        self.port = port
        self.common_address = common_address
        self.client = c104.Client(tick_rate_ms=1000, command_timeout_ms=1000)
        self.connection = self.client.add_connection(
            ip=ip, port=port, init=c104.Init.INTERROGATION
        )
        self.connection.on_receive_raw(callable=handle_packets)
        self.station = self.connection.add_station(common_address=self.common_address)

    def connect(self):
        self.connection.connect()

    def send_n_level(self, ioa: int, value: float):
        print("self.connection.is_connected --- ", self.connection.is_connected)
        point = self.station.get_point(io_address=ioa)
        if not point:
            point = self.station.add_point(io_address=ioa, type=c104.Type.C_SE_NC_1)
        point.value = value
        if point.transmit(cause=c104.Cot.ACTIVATION):
            print(f"n-level ioa {ioa} -> value: {value} SUCCESS")
        else:
            print(f"n-level ioa {ioa} -> value: {value} FAILURE")

    def transmit(self, point: c104.Point, cause: c104.Cot = c104.Cot.ACTIVATION):
        if point.transmit(cause=cause):
            return True
        return False

    def stop(self):
        self.client.stop()

    def start(self):
        self.client.start()


if __name__ == "__main__":
    c104.set_debug_mode(c104.Debug.Client | c104.Debug.Connection)
    client = IECMasterClient(ip="127.0.0.1", port=2404)
    client.start()
    time.sleep(1)
    client.send_n_level(11, 666)

    time.sleep()

timeout on transmit

Hello,
Thank you for this repo, very useful!

I have implemented a Client that needs to transmit periodically some points to a remote server.
Is there a way to ignore the server response on tranmsits ? I keep on getting failed responses that seem to block the transmits for a few seconds, and yet the server receives the points correctly.

Thanks in advance !

import functools
import threading
import time

import c104

from iec.handler import cl_on_new_point, cl_on_new_station
from instance.config import get_settings


def cl_pt_on_receive_point(
    point: c104.Point, previous_state: dict, message: c104.IncomingMessage
) -> c104.ResponseState:
    return c104.ResponseState.SUCCESS


def cl_on_new_station(
    client: c104.Client, connection: c104.Connection, common_address: int
) -> None:
    connection.add_station(common_address=common_address)


def cl_on_new_point(
    client: c104.Client, station: c104.Station, io_address: int, point_type: c104.Type
) -> None:
    point = station.add_point(io_address=io_address, type=point_type)
    point.on_receive(callable=cl_pt_on_receive_point)


class IECClient:
    def __init__(
        self,
        ip,
        port,
        originator_address=132,
        tick_rate_ms=1000,
        command_timeout_ms=5000,
        common_address=27808,
    ):
        self.ip = ip
        self.port = port
        self.common_address = common_address
        self.client = c104.Client(tick_rate_ms=tick_rate_ms, command_timeout_ms=command_timeout_ms)
        self.client.originator_address = originator_address
        self.connection = self.client.add_connection(
            ip=self.ip, port=self.port, init=c104.Init.INTERROGATION
        )

        self.client.on_new_station(callable=functools.partial(cl_on_new_station))
        self.client.on_new_point(callable=cl_on_new_point)
        self.station = self.connection.add_station(common_address=self.common_address)

    def stop(self):
        self.client.stop()

    def start(self):
        self.client.start()
        while not self.connection.is_connected:
            print(f"Waiting for connection to {self.connection.ip}:{self.connection.port}")
            time.sleep(3)

    def send_point(self, ioa: int, value: float):
        point = self.station.get_point(io_address=ioa)
        if not point:
            point = self.station.add_point(io_address=ioa, type=c104.Type.C_SE_NC_1)
        point.value = value

        transmit_thread = threading.Thread(target=self._transmit_point, args=(point, ioa, value))
        transmit_thread.start()

    def _transmit_point(self, point, ioa, value):
        if point.transmit(cause=c104.Cot.ACTIVATION):
            print("SUCCESS")
        else:
            print("FAILURE")

    def send_interrogation(self):
        interrogation = self.connection.interrogation(common_address=self.common_address)
        print(f"Interrogation: {interrogation}")


iec_master_client = IECClient(ip=settings.IEC_SERVER_HOST, port=settings.IEC_SERVER_PORT)

Sending timestamps not in UTC

Hello!

I am trying to adjust the time of a timestamp when sending point data, and I would like to know how to do so. When sending data about a point with the type "type=c 104.Type.M_ME_TF_1", I want the timestamp to be in the local time zone of the server, rather than the UTC time. For example, the server's local time zone is Europe/Istanbul. Could you please help me with this?

Thank you for your attention.

Implementation of Event Driven COT's send from 104 Server

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?

Error on raspbian ARMv7

using import c104 produces this error:

.venv/lib/python3.7/site-packages/c104.cpython-37m-arm-linux-gnueabihf.so: undefined symbol: __atomic_load_8

Any idea how to fix this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.