Skip to main content

dnp3py

CI codecov PyPI version Python versions License: MIT Ruff Checked with mypy

A pure Python implementation of the DNP3 (IEEE 1815-2012) protocol, including a MESA IEEE 1815.2 DER outstation simulator introduced in v0.2.0.

Features

  • Pure Python - No C/C++ dependencies, works anywhere Python runs
  • Level 2 Subset - RTU-class functionality for SCADA applications
  • Async I/O - Built on asyncio for efficient network communication
  • Type Safe - Full type annotations with strict mypy compliance
  • Well Tested - Comprehensive test suite with 98%+ code coverage
  • MESA IEEE 1815.2 Outstation - Profile-driven DER outstation simulator for meters, DERs, inverters, and batteries

Installation

pip install dnp3py

Or with pixi:

pixi add dnp3py

Quick Start

Outstation (Server)

import asyncio
from dnp3.database import Database, BinaryInputConfig, AnalogInputConfig
from dnp3.outstation import Outstation
from dnp3.transport_io import TcpServer

async def main():
    # Create database with points
    database = Database()
    database.add_binary_input(0, BinaryInputConfig())
    database.add_analog_input(0, AnalogInputConfig())

    # Update values
    database.update_binary_input(0, value=True)
    database.update_analog_input(0, value=25.5)

    # Create outstation
    outstation = Outstation(database=database)

    # Start TCP server
    server = TcpServer(host="0.0.0.0", port=20000)
    await server.start()

    # Handle connections...

asyncio.run(main())

Master (Client)

import asyncio
from dnp3.master import Master, DefaultSOEHandler
from dnp3.transport_io import TcpClientChannel

async def main():
    # Create master with event handler
    handler = DefaultSOEHandler()
    master = Master(handler=handler)

    # Connect to outstation
    channel = TcpClientChannel(host="localhost", port=20000)
    await channel.open()

    # Perform integrity poll
    request = master.build_integrity_poll()
    # Send request, receive response...

asyncio.run(main())

MESA IEEE 1815.2 Outstation

The dnp3.mesa module is a DER-oriented outstation built on mesa-tool's PicsProfile format, the same profile shape mesa-tool's Rust conformance control station uses. It supports meters, DERs (distributed energy resources), inverters, and batteries, plus counters, curves, and schedules. You describe the device by loading a PicsProfile JSON file; the module builds the DNP3 database and command handler automatically, scaling analog values from engineering units to DNP3 transmission integers on load.

Four bundled profiles ship inside the package (full, mandatory_1815, mandatory_1547, minimal_1547); full is the default. Profiles are authored as JSON; there is no spreadsheet ingestion path.

Quick start (CLI)

usage: python -m dnp3.mesa [-h] [--profile PROFILE]
                           [--profile-name {full,mandatory_1815,mandatory_1547,minimal_1547}]
                           [--host HOST] [--port PORT] [--address ADDRESS]
                           [--master-address MASTER_ADDRESS] [--meters METERS]
                           [--ders DERS] [--inverters INVERTERS]
                           [--batteries BATTERIES]

options:
  --profile PROFILE           Path to a PicsProfile JSON file (default: bundled full.json)
  --profile-name {full,mandatory_1815,mandatory_1547,minimal_1547}
                              Select a bundled profile by name instead of --profile
                              (mutually exclusive with --profile)
  --host HOST                 Listen address (default: 0.0.0.0)
  --port PORT                 Listen port (default: 20000)
  --address ADDRESS           DNP3 outstation address (default: 1)
  --master-address MASTER_ADDRESS
                              Expected master address (default: 0)
  --meters METERS             Number of meter instances to include
  --ders DERS                 Number of DER instances to include
  --inverters INVERTERS       Number of inverter instances to include
  --batteries BATTERIES       Number of battery instances to include

Run the simulator against the bundled full profile (the default, so --profile/--profile-name can be omitted):

python -m dnp3.mesa

Run against a conformance subset, or a custom profile:

python -m dnp3.mesa --profile-name minimal_1547
python -m dnp3.mesa --profile my_device_profile.json

The --meters, --ders, --inverters, and --batteries flags include only the first N instances of that equipment type, letting a single shared profile serve devices with different hardware configurations without editing the file:

# Include only the first meter; exclude DERs, inverters, and batteries.
python -m dnp3.mesa --profile-name full --meters 1 --ders 0 --inverters 0 --batteries 0

Programmatic API

import asyncio
from pathlib import Path
from dnp3.mesa.outstation import create_mesa_outstation

async def main():
    outstation = create_mesa_outstation(
        profile_path=Path("my_device_profile.json"),
        host="0.0.0.0",
        port=20000,
        address=1,
        master_address=0,
        entity_overrides={"meters": 1, "ders": 0},  # optional
    )
    await outstation.run()

asyncio.run(main())

create_mesa_outstation returns a MesaOutstation dataclass. Call await outstation.run() to start the TCP server; call await outstation.stop() to shut it down cleanly.

For a full description of the PicsProfile format, the bundled profiles, the engineering-to-transmission scaling contract, and CTR/curve/schedule handling, see docs/mesa-outstation.md.

Supported Features

Function Codes

  • READ, WRITE
  • SELECT, OPERATE, DIRECT_OPERATE
  • COLD_RESTART, WARM_RESTART
  • ENABLE_UNSOLICITED, DISABLE_UNSOLICITED
  • DELAY_MEASURE

Object Groups

Group Description
1, 2 Binary Input (static, event)
10, 11, 12 Binary Output (static, event, CROB)
20, 21, 22 Counter (static, frozen, event)
30, 32 Analog Input (static, event)
40, 41, 42 Analog Output (static, command, event)
50, 51, 52 Time objects
60 Class data

Development

Setup

# Clone repository
git clone https://github.com/craig8/dnp3py.git
cd dnp3py

# Install with pixi
pixi install
pixi run dev-install

# Set up pre-commit hooks (enforces quality checks before commits)
pixi run pre-commit-install

# Run tests
pixi run test

# Run with coverage
pixi run test-cov

# Lint and type check
pixi run check

# Test with specific Python version
pixi run -e py310 test
pixi run -e py312 test

# Test all Python versions (via nox)
pixi run nox

Project Structure

dnp3py/
├── src/dnp3/
│   ├── core/           # CRC, types, enums, flags
│   ├── datalink/       # Data link layer (frames, parsing)
│   ├── transport/      # Transport layer (segmentation)
│   ├── application/    # Application layer (messages)
│   ├── objects/        # DNP3 object definitions
│   ├── database/       # Point database and events
│   ├── outstation/     # Outstation implementation
│   ├── master/         # Master implementation
│   ├── mesa/           # MESA IEEE 1815.2 DER outstation
│   │   └── data/profiles/  # Bundled PicsProfile JSON files (full.json default)
│   └── transport_io/   # TCP/simulator channels
└── tests/
    ├── unit/           # Unit tests
    └── integration/    # Integration tests

License

MIT License - see LICENSE for details.

Acknowledgments

This implementation follows the IEEE 1815-2012 standard for DNP3.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dnp3py-0.3.2.tar.gz (431.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dnp3py-0.3.2-py3-none-any.whl (286.8 kB view details)

Uploaded Python 3

File details

Details for the file dnp3py-0.3.2.tar.gz.

File metadata

  • Download URL: dnp3py-0.3.2.tar.gz
  • Upload date:
  • Size: 431.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dnp3py-0.3.2.tar.gz
Algorithm Hash digest
SHA256 e61ec2c0f02a03e53c9b12dbe0ab5752ceabed6018144cf5f0d1e08de761a75b
MD5 82e094954da1c9609d1ea96f9dd52f2b
BLAKE2b-256 e59d2f27ae3da424c2424ef5de6aba6b1fc73bb32d21bc02f65e5606599a2e6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnp3py-0.3.2.tar.gz:

Publisher: release.yml on craigpnnl/dnp3py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dnp3py-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: dnp3py-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 286.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dnp3py-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 56cd87695087c028563aa1f02e08322623d5a0e39a7c0e4500ef7b68ef667ac1
MD5 c4c20f6920006fac9ce022f432299ffa
BLAKE2b-256 7691556500114c5793b2f0f029416df16d7a58b0a5b88f5d2b3b851a5bf7a361

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnp3py-0.3.2-py3-none-any.whl:

Publisher: release.yml on craigpnnl/dnp3py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.0

2 files

This release

0.3.2 This release

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.2

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page