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.1.tar.gz (430.6 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.1-py3-none-any.whl (286.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dnp3py-0.3.1.tar.gz
  • Upload date:
  • Size: 430.6 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.1.tar.gz
Algorithm Hash digest
SHA256 a623485b1b9bb1703caaa06e5b6196f3f813cc42845bba0e5b6a6677c0e6dd23
MD5 47c4098494f6c7b06e0846f159829b67
BLAKE2b-256 865551b978ce73323556585f91a38193aaae4d3b27b357bb4205a3543a9d17c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnp3py-0.3.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: dnp3py-0.3.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 bca6af4480a1c6e0caf48121fe5a8e0657bc8ddb93f9f1271f349c15fddbe09a
MD5 e6865db68c8d074706b2b13bdb2f827b
BLAKE2b-256 6b672fb3f2fc18d4703b4fbdc689a541b581a45b81a25e84524c909ec703535e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnp3py-0.3.1-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

0.3.2

2 files

This release

0.3.1 This release

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