pydnp3-stepfunc 1.6.0.10
Python bindings for the stepfunc/dnp3 Rust library via cffi
License: MIT
Requires Python: >=3.8
Keywords: dnp3, scada, ics, industrial-control-systems, modbus, protocol, master, outstation, rtu, power-systems

# pydnp3-stepfunc

Python bindings for the [stepfunc/dnp3](https://github.com/stepfunc/dnp3) Rust library via cffi.

[![Tests](https://github.com/f0rw4rd/pydnp3-stepfunc/actions/workflows/tests.yml/badge.svg)](https://github.com/f0rw4rd/pydnp3-stepfunc/actions/workflows/tests.yml)

This library provides a Pythonic API for DNP3 (Distributed Network Protocol 3) master
and outstation operations, built on the production-quality stepfunc/dnp3 implementation.
It supports TCP, TLS, serial, and UDP transports with full Secure Authentication support.

## Features

- **Master station** - Integrity polls, class-based reads, specific group/variation reads
- **Control operations** - Select-Before-Operate (SBO), Direct Operate, analog outputs
- **Device attributes** - Read Group 0 device attribute strings
- **Outstation server** - Create test outstations with configurable databases
- **Time synchronization** - LAN and non-LAN time sync modes
- **Restart commands** - Cold and warm restart with delay reporting
- **Multiple transports** - TCP, TLS, serial, UDP channel creation
- **Logging** - Configurable log levels, text/JSON output, custom callbacks
- **Thread-safe** - All handlers use locking for concurrent access

## Installation

### Prerequisites

You need the compiled stepfunc/dnp3 C FFI library (`libdnp3_ffi.so` and `dnp3.h`).

```bash
# Clone and build the Rust library
git clone https://github.com/stepfunc/dnp3.git stepfunc-dnp3
cd stepfunc-dnp3
cargo build --release -p dnp3-ffi

# Generate C bindings
cargo build --release -p dnp3-bindings
```

### Install pydnp3-stepfunc

```bash
# Install from local checkout
pip install -e .

# Or with dev dependencies
pip install -e ".[dev]"
```

The library locates `dnp3.h` and `libdnp3_ffi.so` automatically from a sibling
`stepfunc-dnp3/` directory. You can also set environment variables:

```bash
export DNP3_HEADER_PATH=/path/to/dnp3.h
export DNP3_LIB_PATH=/path/to/libdnp3_ffi.so
```

## Quick Start

### Master - Read Data from Outstation

```python
from dnp3 import Runtime, MasterChannelConfig, ReadHandler
from dnp3.channel import create_tcp_channel
from dnp3.logging import configure_logging, LogLevel

configure_logging(LogLevel.INFO)

with Runtime() as runtime:
    handler = ReadHandler()
    channel = create_tcp_channel(
        runtime,
        endpoints=["127.0.0.1:20000"],
        config=MasterChannelConfig(address=1),
    )
    assoc = channel.add_association(address=1024, read_handler=handler)
    channel.enable()

    # Integrity poll (all classes)
    channel.read(assoc)

    for bi in handler.binary_inputs:
        print(f"BI {bi.index}: {bi.value} (flags={bi.flags.value:#04x})")
    for ai in handler.analog_inputs:
        print(f"AI {ai.index}: {ai.value}")

    channel.destroy()
```

### Master - Control Operations

```python
from dnp3 import OpType, CommandMode

# Select-Before-Operate
channel.select_before_operate(assoc, index=3, op_type=OpType.LATCH_ON)

# Direct Operate
channel.direct_operate(assoc, index=5, op_type=OpType.LATCH_OFF)

# Analog output
channel.operate(
    assoc,
    mode=CommandMode.DIRECT_OPERATE,
    analog_double_commands=[(0, 42.5)],
)
```

### Outstation Server

```python
from dnp3 import Runtime
from dnp3.outstation import OutstationServer, OutstationConfig

with Runtime() as runtime:
    server = OutstationServer(runtime, address="0.0.0.0:20000")
    config = OutstationConfig(outstation_address=10, master_address=1)
    outstation = server.add_outstation(config=config)
    server.bind()

    # Server is now accepting master connections...
    input("Press Enter to stop")
    server.destroy()
```

### Examples

See the [examples/](examples/) directory for complete working scripts:

- [**master_poll.py**](examples/master_poll.py) - Connect and read data points
- [**master_control.py**](examples/master_control.py) - SBO and direct operate
- [**master_attributes.py**](examples/master_attributes.py) - Read device attributes (Group 0)
- [**outstation_server.py**](examples/outstation_server.py) - Test outstation with sample data

## API Summary

### Core Classes

| Class | Description |
|-------|-------------|
| `Runtime` | Manages the Tokio async runtime (context manager) |
| `MasterChannel` | Master station channel with read/control methods |
| `MasterChannelConfig` | Channel configuration (address, decode level, buffer sizes) |
| `AssociationConfig` | Association settings (unsolicited, integrity, timeouts) |
| `ReadHandler` | Collects data from read operations |
| `OutstationServer` | TCP outstation server for testing |

### Channel Factories

| Function | Description |
|----------|-------------|
| `create_tcp_channel()` | TCP master channel |
| `create_tls_channel()` | TLS-encrypted master channel |
| `create_serial_channel()` | Serial port master channel |
| `create_udp_channel()` | UDP master channel |

### Data Types

| Type | Description |
|------|-------------|
| `BinaryInput` | Binary input point (index, value, flags, timestamp) |
| `AnalogInput` | Analog input point |
| `Counter` | Counter point |
| `BinaryOutputStatus` | Binary output status |
| `AnalogOutputStatus` | Analog output status |
| `Group12Var1` | Control Relay Output Block (CROB) |
| `Variation` | DNP3 group/variation enum |

### MasterChannel Methods

| Method | Description |
|--------|-------------|
| `read()` | One-shot read (by class or variation) |
| `read_attributes()` | Read device attributes (Group 0) |
| `add_poll()` | Add periodic poll |
| `select_before_operate()` | SBO binary output control |
| `direct_operate()` | Direct operate binary output |
| `operate()` | Generic control (CROB + analog) |
| `synchronize_time()` | Time sync (LAN/non-LAN) |
| `cold_restart()` / `warm_restart()` | Restart commands |
| `check_link_status()` | Link status check |

## Building from Source

```bash
# 1. Build the Rust library (in a sibling directory)
git clone https://github.com/stepfunc/dnp3.git ../stepfunc-dnp3
cd ../stepfunc-dnp3
cargo build --release -p dnp3-ffi

# 2. Install this package
cd ../pydnp3-stepfunc
pip install -e ".[dev]"

# 3. Run tests
pytest tests/
```

## License

This Python wrapper is MIT licensed. See [LICENSE](LICENSE).

The underlying stepfunc/dnp3 library uses a non-commercial license
(evaluation, research, and training use only). See the
[stepfunc/dnp3 LICENSE](https://github.com/stepfunc/dnp3/blob/main/LICENSE.txt)
for details.