### Example SCADA Unit (Client) Setup Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Illustrates how to configure a client to act as a SCADA unit. This includes adding connections to RTUs, defining stations, and specifying points for communication. ```python import c104 client = c104.Client() # 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) command_point = station.add_point(io_address=12, type=c104.Type.C_RC_TA_1) client.start() ``` -------------------------------- ### Example Remote Terminal Unit (RTU) Setup Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Demonstrates how to set up a server to act as a remote terminal unit. This involves initializing the server, adding stations, and defining measurement and command points. ```python 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=5000) command_point = station.add_point(io_address=12, type=c104.Type.C_RC_TA_1) server.start() ``` -------------------------------- ### SCADA Unit (Client) Setup Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/index.md Configures a client to act as a SCADA unit. It initializes the client with specified tick and command timeouts, adds a connection to a remote terminal unit, and defines measurement and command points for that station. The client is then started. ```python 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() ``` -------------------------------- ### Install Performance Analysis Tools (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Installs google-perftools, valgrind, and the 'yep' profiler for performance analysis on Linux systems. ```bash sudo apt-get install google-perftools valgrind sudo pip3 install yep ``` -------------------------------- ### Install iec104-python from PyPI Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Command to install the iec104-python package using pip from the Python Package Index. ```bash python3 -m pip install c104 ``` -------------------------------- ### Install iec104-python from Git Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Command to install the iec104-python package directly from its GitHub repository using pip. This method requires build tools to be present. ```bash python3 -m pip install c104@git+https://github.com/fraunhofer-fit-dien/iec104-python.git ``` -------------------------------- ### Build Sphinx HTML Documentation Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Builds the project's HTML documentation using Sphinx. Requires the documentation dependencies to be installed. ```bash python3 bin/build-docs.py ``` -------------------------------- ### Remote Terminal Unit (RTU) Server Setup Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/index.md Sets up a server to act as a remote terminal unit. It initializes the server, adds a station with a common address, and defines measurement and command points. The server is then started to listen for connections. ```python 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() ``` -------------------------------- ### Install Build Dependencies (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Installs essential build tools and Python development packages required for compiling the iec104-python library on a Linux system. ```bash sudo apt-get install build-essential python3-pip python3-dev python3-dbg python3 -m pip install --upgrade pip ``` -------------------------------- ### Build Doxygen XML Documentation Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Generates Doxygen XML files from the source code. Requires Doxygen and Graphviz to be installed. ```bash doxygen Doxyfile ``` -------------------------------- ### Create and Configure IEC 104 Server (RTU) Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Set up a local IEC 104 server that listens for client connections. Configure stations, measurement points with reporting intervals, and command points with specific handlers. The server must be started and kept running. ```python import c104 import time # Create a server listening on all interfaces server = c104.Server(ip="0.0.0.0", port=2404, tick_rate_ms=100) # Add a station with common address 47 station = server.add_station(common_address=47) # Add a measurement point with periodic reporting every 5 seconds measurement_point = station.add_point( io_address=11, type=c104.Type.M_ME_NC_1, # Short floating point measurement report_ms=5000 ) measurement_point.value = 23.5 # Add a command point linked to the measurement command_point = station.add_point( io_address=12, type=c104.Type.C_SE_NC_1, # Short floating point setpoint command related_io_address=11, related_io_autoreturn=True, command_mode=c104.CommandMode.DIRECT ) # Define command handler def on_setpoint_command(point: c104.Point, previous_info: c104.Information, message: c104.IncomingMessage) -> c104.ResponseState: print(f"Received setpoint command: {point.value} (was: {previous_info.value})") return c104.ResponseState.SUCCESS command_point.on_receive(callable=on_setpoint_command) # Start server server.start() print(f"Server running on {server.ip}:{server.port}") # Keep running try: while server.is_running: time.sleep(1) except KeyboardInterrupt: server.stop() ``` -------------------------------- ### Build Wheel Package (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Builds a wheel package for the iec104-python project using pip, typically after cloning the repository and installing dependencies on Linux. ```bash python3 -m pip wheel . ``` -------------------------------- ### Fix Client Get Connection Method Arguments Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md The `Client.get_connection` method now correctly accepts either `ip` and `port` or a `common_address` argument. ```python Fix Client.get_connection method to accept ip and port or common_address argument ``` -------------------------------- ### Send Interrogation and Counter Commands Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Initiate interrogation commands to retrieve current values from remote stations. Supports general interrogation, counter interrogation with freeze, and clock synchronization. Requires a c104.Client and connection setup. ```python import c104 import time client = c104.Client() connection = client.add_connection(ip="127.0.0.1", port=2404, init=c104.Init.NONE) station = connection.add_station(common_address=47) # Add points that will be populated by interrogation response station.add_point(io_address=11, type=c104.Type.M_ME_NC_1) station.add_point(io_address=12, type=c104.Type.M_ME_NC_1) client.start() # Wait for connection while not connection.is_connected: time.sleep(0.5) # Send general interrogation to all groups if connection.interrogation( common_address=47, cause=c104.Cot.ACTIVATION, qualifier=c104.Qoi.STATION, # General interrogation wait_for_response=True ): print("Interrogation completed") for point in station.points: print(f" IOA {point.io_address}: {point.value}") # Send counter interrogation with freeze if connection.counter_interrogation( common_address=47, cause=c104.Cot.ACTIVATION, qualifier=c104.Rqt.GENERAL, freeze=c104.Frz.FREEZE_WITH_RESET, wait_for_response=True ): print("Counter interrogation completed") # Clock synchronization if connection.clock_sync(common_address=47, wait_for_response=True): print("Clock synchronized") ``` -------------------------------- ### Server and Client Command Handling with Select-Before-Execute Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Demonstrates server-side handling of single and double commands using the SELECT_AND_EXECUTE mode, and client-side transmission of a single command. ```python import c104 # Server-side command handling with select-and-execute server = c104.Server(ip="0.0.0.0", port=2404, select_timeout_ms=10000) station = server.add_station(common_address=47) # Single command point with select-and-execute single_cmd = station.add_point( io_address=20, type=c104.Type.C_SC_TA_1, # Single command with timestamp command_mode=c104.CommandMode.SELECT_AND_EXECUTE ) def on_single_command(point: c104.Point, previous_info: c104.Information, message: c104.IncomingMessage) -> c104.ResponseState: if message.is_select_command: print(f"SELECT received for IOA {point.io_address}") return c104.ResponseState.SUCCESS # Accept selection # Execute command print(f"EXECUTE single command: {point.value}, selected_by: {point.selected_by}") return c104.ResponseState.SUCCESS single_cmd.on_receive(callable=on_single_command) # Double command with qualifier double_cmd = station.add_point(io_address=21, type=c104.Type.C_DC_TA_1) def on_double_command(point: c104.Point, previous_info: c104.Information, message: c104.IncomingMessage) -> c104.ResponseState: info = message.info print(f"Double command: state={info.state}, qualifier={info.qualifier}") return c104.ResponseState.SUCCESS double_cmd.on_receive(callable=on_double_command) # Step/regulating command step_cmd = station.add_point(io_address=22, type=c104.Type.C_RC_TA_1) server.start() # Client-side command transmission client = c104.Client() connection = client.add_connection(ip="127.0.0.1", port=2404, init=c104.Init.NONE) cl_station = connection.add_station(common_address=47) cl_single_cmd = cl_station.add_point(io_address=20, type=c104.Type.C_SC_TA_1) cl_single_cmd.command_mode = c104.CommandMode.SELECT_AND_EXECUTE client.start() # Set command value and transmit cl_single_cmd.info = c104.SingleCmd(on=True, qualifier=c104.Qoc.SHORT_PULSE) cl_single_cmd.transmit(cause=c104.Cot.ACTIVATION) ``` -------------------------------- ### Client Station Initialization Complete Callback Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Callback triggered when a station's initialization process is complete. It receives the cause of initialization, such as power-on or reset. ```python # Handle station initialization complete def on_station_initialized(client: c104.Client, station: c104.Station, cause: c104.Coi) -> None: causes = { c104.Coi.LOCAL_POWER_ON: "Power on", c104.Coi.LOCAL_MANUAL_RESET: "Manual reset", c104.Coi.REMOTE_RESET: "Remote reset" } print(f"Station {station.common_address} initialized: {causes.get(cause, cause)}") client.on_station_initialized(callable=on_station_initialized) client.start() ``` -------------------------------- ### Copy and Set Permissions for pprof Binary (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Downloads the pprof binary to /usr/bin and makes it executable for performance profiling on Linux. ```bash cd /usr/bin sudo wget https://raw.githubusercontent.com/gperftools/gperftools/master/src/pprof sudo chmod +x pprof ``` -------------------------------- ### Waiting for Connection Establishment and Initialization Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Instead of simply checking if a connection is established, wait for the connection state to become OPEN. This ensures that not only is the connection established, but also that any necessary initialization commands have been completed. ```python while connection.state != c104.ConnectionState.OPEN: time.sleep(1) ``` -------------------------------- ### Server Callbacks for Connection and Synchronization Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Sets up a basic IEC 104 server and station, ready to implement custom callbacks for handling server events like connection requests and clock synchronization. ```python import c104 import datetime server = c104.Server(ip="0.0.0.0", port=2404) station = server.add_station(common_address=47) ``` -------------------------------- ### Handle Station Initialization Callback Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md The `Client.on_station_initialized` callback handles end-of-initialization messages. ```python Client.on_station_initialized ``` -------------------------------- ### Clone Repository and Initialize Submodules (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Clones the iec104-python repository and updates its submodules, which is a necessary step before building the project from source on Linux. ```bash git clone --depth=1 --branch=main https://github.com/Fraunhofer-FIT-DIEN/iec104-python.git cd iec104-python git submodule update --init ``` -------------------------------- ### Configure Periodic Transmission and Timers Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Sets up a server to simulate an RTU, configuring points for automatic periodic reporting and custom timer-based events. Callbacks are used to update point values before transmission or on timer intervals. ```python import c104 import random server = c104.Server(ip="0.0.0.0", port=2404, tick_rate_ms=100) station = server.add_station(common_address=47) # Measurement point with automatic periodic reporting periodic_point = station.add_point( io_address=10, type=c104.Type.M_ME_NC_1, report_ms=5000 # Report every 5 seconds ) # Callback before periodic transmission - update value def on_before_auto_transmit(point: c104.Point) -> None: # Simulate sensor reading point.value = random.uniform(0.0, 100.0) print(f"Auto-transmit IOA {point.io_address}: {point.value:.2f}") periodic_point.on_before_auto_transmit(callable=on_before_auto_transmit) # Callback before interrogation read def on_before_read(point: c104.Point) -> None: # Update value when client requests it point.value = random.uniform(0.0, 100.0) print(f"Read request IOA {point.io_address}: {point.value:.2f}") periodic_point.on_before_read(callable=on_before_read) # Custom timer for non-reporting points timer_point = station.add_point(io_address=11, type=c104.Type.M_ME_NC_1) def on_timer(point: c104.Point) -> None: # Custom processing every second point.value = random.uniform(0.0, 50.0) # Manually decide when to transmit if point.value > 40.0: point.transmit(cause=c104.Cot.SPONTANEOUS) timer_point.on_timer(callable=on_timer, interval_ms=1000) server.start() ``` -------------------------------- ### Updating Command Point with Qualifier and Timestamp Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Shows how to update a command point's information, including its state, qualifier, and timestamp. This replaces the older 'set' method and requires direct assignment to the 'info' object. ```python cl_double_command.info = c104.DoubleCmd(state=c104.Double.ON, qualifier=c104.Qoc.LONG_PULSE, recorded_at=datetime.datetime.fromtimestamp(1711111111.111)) ``` -------------------------------- ### Server Constructor and Properties Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Updates to the `c104.Server` constructor, including a new timeout argument and reduced tick rate. ```APIDOC ## Server Constructor and Properties ### Description This section covers modifications to the `c104.Server` constructor. ### Constructor Changes: - **select_timeout_ms** (int) - New argument with a default value of `100ms`. - **tick_rate_ms** (int) - Default value reduced from `1000ms` to `100ms`. The minimum tick rate is `50ms`. ### Property Added: - **client.tick_rate_ms** (int) - Read-only property indicating the server's tick rate. (Note: This seems to be a typo in the source, likely intended to be `server.tick_rate_ms`) ``` -------------------------------- ### Client Protocol Parameters Configuration Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Configure IEC 60870-5-104 protocol timing parameters for a client connection, including timeouts, intervals, and window sizes. ```python # Client connection parameters client = c104.Client() connection = client.add_connection(ip="127.0.0.1", port=2404) conn_params = connection.protocol_parameters conn_params.connection_timeout = 30 conn_params.message_timeout = 15 conn_params.confirm_interval = 10 conn_params.keep_alive_interval = 20 conn_params.send_window_size = 12 conn_params.receive_window_size = 8 ``` -------------------------------- ### Create and Configure IEC 104 Client (SCADA) Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Initialize an IEC 104 client to connect to a remote RTU. Configure connections with automatic interrogation and define stations and points to match the server. A callback is set up to handle incoming measurements, and a setpoint command can be transmitted. ```python import c104 import time # Create client with configuration client = c104.Client(tick_rate_ms=100, command_timeout_ms=10000) # Add connection to RTU with automatic interrogation on connect connection = client.add_connection( ip="127.0.0.1", port=2404, init=c104.Init.INTERROGATION # Send interrogation command on connect ) # Add station and points matching the server configuration station = connection.add_station(common_address=47) measurement_point = station.add_point(io_address=11, type=c104.Type.M_ME_NC_1) command_point = station.add_point(io_address=12, type=c104.Type.C_SE_NC_1) # Define callback for incoming measurements def on_measurement_receive(point: c104.Point, previous_info: c104.Information, message: c104.IncomingMessage) -> c104.ResponseState: print(f"Measurement updated: IOA {point.io_address} = {point.value}, Quality: {point.quality}") return c104.ResponseState.SUCCESS measurement_point.on_receive(callable=on_measurement_receive) # Start client and wait for connection client.start() while connection.state != c104.ConnectionState.OPEN: print(f"Connection state: {connection.state}") time.sleep(1) print(f"Connected to {connection.ip}:{connection.port}") print(f"Current measurement value: {measurement_point.value}") # Send a setpoint command command_point.value = 42.5 if command_point.transmit(cause=c104.Cot.ACTIVATION): print("Setpoint command sent successfully") ``` -------------------------------- ### Client Constructor and Properties Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Changes to the `c104.Client` constructor arguments and addition of the `tick_rate_ms` property. ```APIDOC ## Client Constructor and Properties ### Description This section covers modifications to the `c104.Client` constructor and the introduction of new properties. ### Constructor Changes: - **command_timeout_ms** (int) - Default value reduced from `1000ms` to `100ms`. - **tick_rate_ms** (int) - Default value reduced from `1000ms` to `100ms`. The minimum tick rate is `50ms`. ### Property Added: - **client.tick_rate_ms** (int) - Read-only property indicating the client's tick rate. ``` -------------------------------- ### Build Wheels via Docker (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Executes a script to build wheel packages for the iec104-python project using Docker, facilitating cross-version compatibility on Linux systems. ```bash /bin/bash ./bin/linux-build.sh ``` -------------------------------- ### Execute Profiler Script (Linux) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Runs the profiler script located in the ./bin/ directory to analyze performance on Linux. ```bash ./bin/profiler.sh ``` -------------------------------- ### Client Auto-Discovery: New Station Callback Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Callback to automatically create a new station when the server reports an unknown common address. This enables dynamic station registration. ```python import c104 client = c104.Client() connection = client.add_connection(ip="127.0.0.1", port=2404, init=c104.Init.INTERROGATION) # Auto-create stations when server reports unknown common address def on_new_station(client: c104.Client, connection: c104.Connection, common_address: int) -> None: print(f"Discovered new station: CA={common_address}") station = connection.add_station(common_address=common_address) client.on_new_station(callable=on_new_station) ``` -------------------------------- ### Batch Message Transmission for Monitoring Points Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Shows how to create and transmit batch messages containing multiple monitoring points efficiently. Supports adding points individually or as a list. ```python import c104 server = c104.Server(ip="0.0.0.0", port=2404) station = server.add_station(common_address=47) # Create multiple measurement points of the same type point1 = station.add_point(io_address=100, type=c104.Type.M_ME_NC_1) point2 = station.add_point(io_address=101, type=c104.Type.M_ME_NC_1) point3 = station.add_point(io_address=102, type=c104.Type.M_ME_NC_1) point1.value = 10.5 point2.value = 20.7 point3.value = 30.9 server.start() # Create batch with same station and type points batch = c104.Batch( cause=c104.Cot.SPONTANEOUS, points=[point1, point2, point3] ) # Or add points individually batch2 = c104.Batch(cause=c104.Cot.PERIODIC) batch2.add_point(point1) batch2.add_point(point2) # Transmit batch to all connected clients if server.transmit_batch(batch): print(f"Batch sent: {batch.number_of_objects} points, type={batch.type}") ``` -------------------------------- ### Server Protocol Parameters Configuration Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Configure IEC 60870-5-104 protocol timing parameters for a server connection, including timeouts, intervals, and window sizes. ```python import c104 # Server protocol parameters server = c104.Server(ip="0.0.0.0", port=2404) server_params = server.protocol_parameters # T0: Connection timeout (seconds) server_params.connection_timeout = 30 # T1: Message timeout - time to wait for acknowledgment (seconds) server_params.message_timeout = 15 # T2: Confirm interval - max time before sending acknowledgment (seconds) server_params.confirm_interval = 10 # T3: Keep-alive interval - max time without traffic before test frame (seconds) server_params.keep_alive_interval = 20 # K: Send window size - max unconfirmed outgoing messages server_params.send_window_size = 12 # W: Receive window size - threshold to send acknowledgment server_params.receive_window_size = 8 ``` -------------------------------- ### Signal Station Initialization Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md The `Station.signal_initialized()` method supports sending end-of-initialization messages per station to all connected clients. ```python Station.signal_initialized(cause=...) ``` -------------------------------- ### Point Callbacks and Properties Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Details on registering timer callbacks and accessing point interval properties. ```APIDOC ## Point Callbacks and Properties ### Description This section describes the `on_timer` callback functionality and the `interval_ms` property for IEC 104 points. ### Callback: `point.on_timer(callable, interval_ms)` - **callable** (callable) - The function to be called when the timer expires. Signature: `(point: c104.Point) -> None`. - **interval_ms** (int) - The interval in milliseconds between timer executions. Must be a positive integer and a multiple of the server or client's `tick_rate_ms`. ### Property: `point.interval_ms` - **Type**: `int` - **Description**: Read-only property that defines the interval between `on_timer` callback executions. This can only be modified through the `point.on_timer(...)` method. ``` -------------------------------- ### Configuring Server Tick Rate and Select Timeout Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Configure the tick rate and select timeout for the server. The minimum tick rate is 50ms. ```python server = c104.Server(tick_rate_ms=100, select_timeout_ms=100) ``` -------------------------------- ### Server Connection Authorization Callback Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Implement a callback to authorize incoming client connections based on IP address. The callback must return True to allow connection or False to reject. ```python def on_connect(server: c104.Server, ip: str) -> bool: allowed_ips = ["127.0.0.1", "192.168.1.100"] if ip in allowed_ips: print(f"Accepted connection from {ip}") return True print(f"Rejected connection from {ip}") return False server.on_connect(callable=on_connect) ``` -------------------------------- ### Build Project via Powershell (Windows) Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/README.md Configures and builds the project using CMake and MSBuild via Powershell on Windows. Requires Visual Studio Buildtools and a correctly set Python path. ```powershell cmake -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -B cmake-build-release -A x64 -DPython_EXECUTABLE=C:\PATH_TO_PYTHON\python.exe &"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" /m /p:Platform=x64 /p:Configuration=Release c104.sln /t:Rebuild ``` -------------------------------- ### Configuring TLS Transport Security for Server and Client Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Illustrates how to configure TLS encryption for secure IEC 104 communication. Includes server-side certificate validation and client-side hostname verification. ```python import c104 import datetime # Server TLS configuration server_tls = c104.TransportSecurity(validate=True, only_known=True) server_tls.set_certificate(cert="certs/server.crt", key="certs/server.key", passphrase="") server_tls.set_ca_certificate(cert="certs/ca.crt") server_tls.add_allowed_remote_certificate(cert="certs/client.crt") server_tls.set_version(min=c104.TlsVersion.TLS_1_2, max=c104.TlsVersion.TLS_1_3) server_tls.set_ciphers([ c104.TlsCipher.ECDHE_RSA_WITH_AES_256_GCM_SHA384, c104.TlsCipher.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, c104.TlsCipher.TLS1_3_AES_256_GCM_SHA384 ]) server_tls.set_renegotiation_time(interval=datetime.timedelta(minutes=30)) server = c104.Server( ip="0.0.0.0", port=2405, transport_security=server_tls ) # Client TLS configuration client_tls = c104.TransportSecurity(validate=True, only_known=False) client_tls.set_certificate(cert="certs/client.crt", key="certs/client.key") client_tls.set_ca_certificate(cert="certs/ca.crt") client_tls.set_hostname_verification(hostname="scada-server.example.com") client_tls.set_resumption_interval(interval=datetime.timedelta(hours=6)) client = c104.Client(transport_security=client_tls) connection = client.add_connection(ip="192.168.1.100", port=2405, init=c104.Init.ALL) ``` -------------------------------- ### Assigning Information Objects to c104.Point Types Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Demonstrates how to assign specific information objects to various point types within the c104 library. Each point type requires a corresponding info object that stores its specific value and other protocol properties. ```python single_point.info = c104.SingleInfo(True) double_point.info = c104.DoubleInfo(c104.Double.ON) step_point.info = c104.StepInfo(c104.Int5(13)) binary_point.info = c104.BinaryInfo(c104.Byte32(12)) normalized_point.info = c104.NormalizedInfo(c104.NormalizedFloat(-0.734)) scaled_point.info = c104.ScaledInfo(c104.Int16(-24533)) short_point.info = c104.ShortInfo(12.34) counter_point.info = c104.BinaryCounterInfo(345678) pe_event_point.info = c104.ProtectionEventInfo(c104.EventState.ON) pe_start_point.info = c104.ProtectionStartInfo(c104.StartEvents.PhaseL1 | c104.StartEvents.PhaseL2) pe_circuit_point.info = c104.ProtectionCircuitInfo(c104.OutputCircuits.PhaseL1) pe_changed_point.info = c104.StatusAndChanged(c104.PackedSingle.I0) ``` -------------------------------- ### v1.17 Features and Fixes Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Details on the Select-And-Execute feature, bugfixes, and documentation improvements in version 1.17. ```APIDOC ## v1.17 Features and Fixes ### Description Version 1.17 introduces the optional Select-And-Execute feature, includes several bugfixes, and improves documentation. ### New Features: - **Select-And-Execute (Select-Before-Execute):** - **c104.CommandMode** (enum): New enumeration for command modes. - **point.command_mode** (property): Property to set the command mode for a point. - **point.selected_by** (property): Indicates which client selected the point. - **incomingmessage.is_select_command** (property): Boolean indicating if the message is a select command. - **on_receive callback**: The `previous_state` argument now contains the `selected_by` key. - **explain_bytes and explain_bytes_dict**: Added `select` field. ### Bugfixes: - **(1.17.1)** Fixed select-and-execute for C_SE_NA. - **(1.17.1)** Fixed armv7 build. - Fixed free command response state key if the command was never sent. ### Improvements: - Improved point transmission handling. - Improved documentation. ``` -------------------------------- ### Client Auto-Discovery: New Point Callback Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Callback to automatically create a new point when the server reports an unknown information object address (IOA) and type. This facilitates dynamic point registration. ```python # Auto-create points when server reports unknown IOA def on_new_point(client: c104.Client, station: c104.Station, io_address: int, point_type: c104.Type) -> None: print(f"Discovered new point: CA={station.common_address}, IOA={io_address}, Type={point_type}") point = station.add_point(io_address=io_address, type=point_type) client.on_new_point(callable=on_new_point) ``` -------------------------------- ### Add and Manage Stations and Points Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Dynamically manage stations and points, including adding, retrieving, and removing them. Supports various point types with specific configurations like report intervals. ```python import c104 server = c104.Server(ip="0.0.0.0", port=2404) # Add multiple stations station_1 = server.add_station(common_address=1) station_2 = server.add_station(common_address=2) # Add various point types to station 1 single_point = station_1.add_point(io_address=100, type=c104.Type.M_SP_TB_1) # Single point with timestamp single_point.value = True double_point = station_1.add_point(io_address=101, type=c104.Type.M_DP_TB_1) # Double point with timestamp double_point.value = c104.Double.ON step_point = station_1.add_point(io_address=102, type=c104.Type.M_ST_TB_1, report_ms=2000) # Step position step_point.value = c104.Int7(15) scaled_point = station_1.add_point(io_address=103, type=c104.Type.M_ME_TB_1) # Scaled measurement scaled_point.value = c104.Int16(-500) normalized_point = station_1.add_point(io_address=104, type=c104.Type.M_ME_TD_1) # Normalized measurement normalized_point.value = c104.NormalizedFloat(0.75) # Retrieve points point = station_1.get_point(io_address=100) if point: print(f"Point {point.io_address}: type={point.type}, value={point.value}") # List all points in a station for point in station_1.points: print(f" IOA {point.io_address}: {point.type}") # Remove a point (allows reassigning IOA to different type) station_1.remove_point(io_address=102) # Remove entire station server.remove_station(common_address=2) # Signal end of initialization to clients server.start() station_1.signal_initialized(cause=c104.Coi.LOCAL_POWER_ON) ``` -------------------------------- ### Setting Command Mode for a Point Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Configure the command mode for a point, which is relevant for Select-And-Execute functionality. This allows for more granular control over command transmission. ```python point.command_mode = c104.CommandMode.DIRECT_EXECUTION ``` -------------------------------- ### Registering a Timer Callback for a Point Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Register a callback function to be executed at a specified interval. The interval must be a positive integer and a multiple of the server or client's tick rate. ```python point.on_timer(callable=on_timer, interval_ms=1000) ``` -------------------------------- ### Raw Message Callbacks for Protocol Debugging Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Implement callbacks to log raw sent and received data for debugging purposes. Requires a c104.Connection object and the c104.explain_bytes function. ```python def on_receive_raw(connection: c104.Connection, data: bytes) -> None: print(f"RX [{data.hex()}]: {c104.explain_bytes(apdu=data)}") def on_send_raw(connection: c104.Connection, data: bytes) -> None: print(f"TX [{data.hex()}]: {c104.explain_bytes(apdu=data)}") connection.on_receive_raw(callable=on_receive_raw) connection.on_send_raw(callable=on_send_raw) ``` -------------------------------- ### Server Raw Protocol Logging Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Register a callback to log raw incoming protocol data. This is useful for debugging and analyzing network traffic. ```python def on_receive_raw(server: c104.Server, data: bytes) -> None: info = c104.explain_bytes_dict(apdu=data) print(f"RX: format={info['format']}, type={info.get('type', 'N/A')}") server.on_receive_raw(callable=on_receive_raw) server.start() ``` -------------------------------- ### Work with Quality Flags and Information Objects Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Utilize quality flags and create specialized information objects for various data types, including measurements, single/double points, and counters. These objects can be assigned to points on a server. ```python import c104 import datetime # Quality flags for measurements quality_good = c104.Quality() # No quality issues quality_invalid = c104.Quality.Invalid # Value is invalid quality_overflow = c104.Quality.Overflow # Measurement overflow quality_combined = c104.Quality.Blocked | c104.Quality.Substituted # Multiple flags print(f"Good quality: {quality_good.is_good()}") # True print(f"Invalid quality has issues: {quality_invalid.is_any()}") # True # Create information objects with quality and timestamps short_info = c104.ShortInfo( actual=23.45, quality=c104.Quality(), recorded_at=datetime.datetime.now(datetime.timezone.utc) ) single_info = c104.SingleInfo( on=True, quality=c104.Quality.NonTopical, recorded_at=datetime.datetime.now(datetime.timezone.utc) ) double_info = c104.DoubleInfo( state=c104.Double.ON, quality=c104.Quality() ) # Binary counter with sequence number counter_quality = c104.BinaryCounterQuality() counter_info = c104.BinaryCounterInfo( counter=123456, sequence=c104.UInt5(5), quality=counter_quality ) # Assign info objects to points server = c104.Server() station = server.add_station(common_address=1) point = station.add_point(io_address=10, type=c104.Type.M_ME_TF_1) point.info = short_info # Or use shortcuts point.value = 25.0 point.quality = c104.Quality.Overflow ``` -------------------------------- ### v1.16 Features and Improvements Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Overview of version 1.16, focusing on TLS support, stability improvements, and library updates. ```APIDOC ## v1.16 Features and Improvements ### Description Version 1.16 adds TLS support, enhances stability through smart pointers, and updates the underlying C library. ### New Features: - **TLS Support**: Added support for TLS (versions SSLv3.0, TLSv1.0, TLSv1.1, TLSv1.2). Note: TLSv1.3 is not supported. ### Improvements: - **Stability**: Fixed potential segmentation faults using smart pointers for synchronized reference counting between C++ and Python. - **CMake Structure**: Improved. - **Reconnect Behaviour**: Enhanced. - **lib60870-C Update**: Updated to the latest version. ``` -------------------------------- ### Connection Properties and States Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md New connection timestamp properties and changes in connection state management for `c104.Connection`. ```APIDOC ## Connection Properties and States ### Description This section details new properties for connection timestamps and modifications to connection state handling in `c104.Connection`. ### Properties Added: - **connection.connected_at** (typing.Optional[datetime.datetime]) - Timestamp when the connection was established. - **connection.disconnected_at** (typing.Optional[datetime.datetime]) - Timestamp when the connection was last disconnected. ### Connection Initialization: - **c104.Init.MUTED** - New option to connect to a server without activating message transmission. ### Connection State Changes: - Removed states: `OPEN_AWAIT_UNMUTE`, `OPEN_AWAIT_INTERROGATION`, `OPEN_AWAIT_CLOCK_SYNC`. - Connection flow: `CLOSED_AWAIT_OPEN` -> `OPEN_MUTED` -> (init commands) -> `OPEN` (if init != c104.Init.MUTED). ### Connection Establishment Wait: - **Instead of:** ```python while not connection.is_connected: time.sleep(1) ``` - **Use to wait for open state (connection established and init commands finished):** ```python while connection.state != c104.ConnectionState.OPEN: time.sleep(1) ``` ``` -------------------------------- ### Transmitting Point Data with Cause Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Demonstrates the updated 'transmit' method signature for sending point data. The 'cause' argument is now obligatory, and the 'qualifier' is managed within the info object. ```python point.transmit(cause=c104.Cot.UNKNOWN_COT) ``` -------------------------------- ### Set C++ Standard and Compiler Flags Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/CMakeLists.txt Configures the C++ standard to C++17 and enables verbose build output. Ensures position-independent code is generated. ```cmake set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) ``` -------------------------------- ### Configure TLS Renegotiation Time Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Configure the TLS renegotiation time using `TransportSecurity.set_renegotiation_time()`. ```python TransportSecurity.set_renegotiation_time() ``` -------------------------------- ### Updating Single Point Value Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Illustrates the simplified way to update the value of a single point. The 'value' property now acts as a shortcut to 'point.info.value'. ```python single_point.value = False ``` -------------------------------- ### Transmit Batch for Monitoring Direction Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Batch Transmission support is provided via the `Batch` class and `Server.transmit_batch()` for monitoring direction. ```python Server.transmit_batch(...) ``` -------------------------------- ### Explain Raw APDU Bytes as Dictionary Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Parses raw APDU bytes into a dictionary for structured information retrieval. Useful for debugging and understanding received data. ```python info = c104.explain_bytes_dict(apdu=raw_apdu) print(f"Format: {info['format']}") print(f"Type: {info.get('type', 'N/A')}") print(f"COT: {info.get('cot', 'N/A')}") print(f"Common Address: {info.get('commonAddress', 'N/A')}") print(f"First IOA: {info.get('firstInformationObjectAddress', 'N/A')}") print(f"Number of Objects: {info.get('numberOfObjects', 'N/A')}") print(f"Is Sequence: {info.get('sequence', False)}") ``` -------------------------------- ### Send Counter Interrogation Command Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Sending `Connection.counter_interrogation()` supports the full Qualifier of Counter Interrogation (Rqt and Frz). ```python Connection.counter_interrogation() ``` -------------------------------- ### Connecting to a Server without Activating Message Transmission Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Use c104.Init.MUTED to establish a connection to a server without immediately activating message transmission. This is useful for scenarios where you need to control when transmission begins. ```python connection.connect(init=c104.Init.MUTED) ``` -------------------------------- ### Parse Raw APDU Bytes Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Parse and explain raw APDU (Application Protocol Data Unit) bytes using the `explain_bytes` function. This is useful for analyzing the structure of protocol messages. ```python # Parse and explain raw APDU bytes raw_apdu = bytes.fromhex("680e00000200010101000a00000103") explanation = c104.explain_bytes(apdu=raw_apdu) print(f"APDU: {explanation}") ``` -------------------------------- ### Removed c104.Point Set Method Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Details the removal of the `point.set(...)` method and the recommended alternative. ```APIDOC ## Removed `point.set(...)` Method ### Description The `point.set(...)` method has been removed. Instead, set a new info object using `point.info = ...` to update all properties like time and quality, not just the value. ### Example ```python # Old usage (removed): # cl_double_command.set(value=c104.Double.ON, timestamp_ms=1711111111111) # New usage: cl_double_command.info = c104.DoubleCmd(state=c104.Double.ON, qualifier=c104.Qoc.LONG_PULSE, recorded_at=datetime.datetime.fromtimestamp(1711111111.111)) ``` ``` -------------------------------- ### Monitor IEC 104 Client Connection State Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Implement a callback function to monitor and log changes in the IEC 104 client's connection state. This is useful for debugging and understanding the connection lifecycle. ```python import c104 client = c104.Client() connection = client.add_connection(ip="192.168.1.100", port=2404, init=c104.Init.ALL) # Connection state change callback def on_state_change(connection: c104.Connection, state: c104.ConnectionState) -> None: states = { c104.ConnectionState.CLOSED: "Disconnected", c104.ConnectionState.CLOSED_AWAIT_OPEN: "Connecting...", c104.ConnectionState.CLOSED_AWAIT_RECONNECT: "Waiting to reconnect", c104.ConnectionState.OPEN: "Connected and active", c104.ConnectionState.OPEN_AWAIT_CLOSED: "Disconnecting...", c104.ConnectionState.OPEN_MUTED: "Connected but muted" } print(f"Connection {connection.ip}:{connection.port} - {states.get(state, state)}") connection.on_state_change(callable=on_state_change) ``` -------------------------------- ### Setting Point Value with Timestamp Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Allows setting a point's value along with its updated timestamp. This method also improves support for setting values using c104.Double and c104.Step instances. ```python point.set(value=new_value, timestamp_ms=update_time) ``` -------------------------------- ### Configuring Client Tick Rate Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Set the tick rate for the client, which determines the interval for certain operations. The minimum tick rate is 50ms. ```python client = c104.Client(tick_rate_ms=100) ``` -------------------------------- ### Enable Hostname Verification for Transport Security Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Use `TransportSecurity.set_hostname_verification()` to enable peer hostname verification in TLS connections. Debug modes now output SSL/TLS debug messages to stderr. ```python TransportSecurity.set_hostname_verification(hostname: str) ``` -------------------------------- ### v1.18 Features and Changes Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md New features and improvements in version 1.18, including command qualifiers and timestamp handling. ```APIDOC ## v1.18 Features and Changes ### Description Version 1.18 introduces support for command qualifiers, improved timestamp handling, and enhanced `c104.Point.set` method. ### New Features: - **Qualifier of Command**: Support added for single, double, and regulating step commands. - **c104.Point.set Method Signature Improved (Non-breaking):** - **timestamp_ms** (keyword argument): Allows setting a point's value along with an `updated_at_ms` timestamp. - **value** (argument): Improved to support instances of `c104.Double` and `c104.Step`. ### Improvements: - **Transmit Updated At Timestamp**: Fixed for time-aware points. - **GIL Handling**: Improved for `station.add_point`, `server.stop`, and `client.stop` methods. ``` -------------------------------- ### Manually Set Batch Originator Address Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Allows manually setting the `Batch.originator_address` to direct a batch to a specific originator. ```python Batch.originator_address ``` -------------------------------- ### Access and Update Protocol Parameters Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md The `Server.protocol_parameters` and `Connection.protocol_parameters` properties allow reading and updating protocol parameters like window size and timeouts. ```python Server.protocol_parameters ``` ```python Connection.protocol_parameters ``` -------------------------------- ### Fix Point Value Setter for Specific Types Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Corrected an issue with the `point.value` setter that was not functioning for `EventState`, `StartEvents`, `OutputCircuits`, and `PackedSingle` types. ```python Fix an issue with the point.value setter that was not functioning correctly for the following types: EventState, StartEvents, OutputCircuits, and PackedSingle ``` -------------------------------- ### Server Clock Synchronization Handler Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Define a callback to handle clock synchronization requests from clients. This function can be used to update system time or validate timestamps. ```python def on_clock_sync(server: c104.Server, ip: str, date_time: datetime.datetime) -> c104.ResponseState: print(f"Clock sync request from {ip}: {date_time}") # Could update system time or validate timestamp return c104.ResponseState.SUCCESS server.on_clock_sync(callable=on_clock_sync) ``` -------------------------------- ### Specify Supported Cipher Suites for TLS Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Use `TransportSecurity.set_ciphers()` to specify a list of supported cipher suites for TLS connections. ```python TransportSecurity.set_ciphers() ``` -------------------------------- ### Enable Debug Modes Source: https://context7.com/fraunhofer-fit-dien/iec104-python/llms.txt Enable specific debug modes for the IEC 104 library, such as client, server, connection, message, or point debugging. Modes can be combined using the bitwise OR operator. ```python import c104 # Enable debug modes (can be combined with |) c104.set_debug_mode(c104.Debug.Client | c104.Debug.Server | c104.Debug.Connection) # Add more debug modes c104.enable_debug(c104.Debug.Message | c104.Debug.Point) ``` -------------------------------- ### Handle Unexpected Messages with Callback Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md The `Connection.on_unexpected_message()` callback informs about unsupported messages or messages with type ID conflicts. ```python Connection.on_unexpected_message() ``` -------------------------------- ### c104.Point Quality Property Changes Source: https://github.com/fraunhofer-fit-dien/iec104-python/blob/main/docs/source/changelog.md Information regarding the updated signature and behavior of the `point.quality` property. ```APIDOC ## Point Quality Property Changes ### Changed Signature: `point.quality` - **Old Signature**: `c104.Quality` - **New Signature**: `typing.Union[None, c104.Quality, c104.BinaryCounterQuality]` ### Description The `point.quality` property is a shortcut to `point.info.quality` and returns point-specific types. For points without quality information, this will be None. Calling `point.quality.is_good()` can therefore result in an error if `point.quality` is `None`. ```