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.0.tar.gz (412.3 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.0-py3-none-any.whl (282.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dnp3py-0.3.0.tar.gz
  • Upload date:
  • Size: 412.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for dnp3py-0.3.0.tar.gz
Algorithm Hash digest
SHA256 04567f19366306bf60b768060718f026f8c29e3e8e3eb8a2d4f809cd0acfd291
MD5 861763185204de8e2426a9c93bbf9f0c
BLAKE2b-256 da2addff9b4ca6484e86127fe312010a1a59d4d5f579271aafe742b20f17ac66

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: dnp3py-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 282.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for dnp3py-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c799f16e81c36497383d0823cf1636268c6b96f397fea88b901ae0bc211979a
MD5 259bffa58685ce4f854118655d3fda97
BLAKE2b-256 17bb682d49e2cc3c8e9c5ad4f1fd0605e6a380d688f669149e35afe166a32f35

See more details on using hashes here.

Provenance

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

0.3.1

2 files

This release

0.3.0 This release

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