DNP3 (IEEE 1815) master and outstation for Dart and Flutter, built on the
dnp3 Rust library by Step Function I/O (v1.7.0)
through a zero-copy FFI bridge.
- Every transport: TCP client/server, TLS client/server, serial, UDP, and a master acting as TCP server (outstations dial in, identified by IP or link address).
- Complete master: associations, class/range/attribute reads, periodic polls, SBO/DO commands (CROB, g41v1–v4), time sync, restarts, dead bands, generic requests, device attributes, file transfer, reconnect policy in Dart, measurement back-pressure.
- Complete outstation: database of all point types with event classes, dead bands and every variation, native or Dart control handling, application callbacks (time, restart, freeze, attributes), address filters, FREEZE_AT_TIME scheduling.
- Fast: measurements are read in place from native buffers (zero-copy flyweights), database updates are batched into one native call, handles work from any isolate.
- Platforms: Android, iOS, macOS, Linux, Windows. (Web is impossible: no sockets, no FFI.)
Important
Licensing. This package's own code is MIT licensed, but the dnp3 Rust library it
links is distributed by Step Function I/O under a non-commercial license. Production
or commercial use requires a commercial license from Step Function I/O
(https://stepfunc.io). Make sure you are licensed before shipping an app built with
this package.
dart pub add dnp3 # or: flutter pub add dnp3The native library is built by the package's build hook (Dart native assets) for whatever
target dart or flutter builds for. You need Dart ≥ 3.10 / Flutter ≥ 3.38 and either:
- a Rust toolchain (https://rustup.rs); the hook runs
rustup target addfor the target automatically, or - prebuilt libraries: download
dnp3-native-prebuilt.zipfrom a GitHub release and point the environment variableDNP3_PREBUILT_DIR(or the hook user-defineprebuilt_dir) to the extracted folder. Layout:<dir>/<rust-triple>/<libdnp3_dart.so | .dylib | dnp3_dart.dll>.
import 'package:dnp3/dnp3.dart';
Future<void> main() async {
// outstation: TCP server with a few points
final server = OutstationServer.tcp('0.0.0.0:20000');
final outstation = server.addOutstation(
config: const OutstationConfig(outstationAddress: 1024, masterAddress: 1),
);
outstation.transaction((tx) => tx
..addAnalogInput(0, deadband: 0.5)
..addBinaryOutputStatus(0)
..updateAnalogInput(0, 42.0, time: Dnp3Time.now()));
await server.bind();
// master: TCP client polling the outstation
final channel = MasterChannel.tcpClient(
config: const MasterChannelConfig(masterAddress: 1),
endpoints: ['127.0.0.1:20000'],
);
final assoc = await channel.addAssociation(1024);
assoc.measurements.listen((fragment) {
for (final block in fragment.blocks.whereType<AnalogInputBlock>()) {
for (var i = 0; i < block.count; i++) {
print('AI ${block.indexAt(i)} = ${block.valueAt(i)}'); // read in place, no copy
}
}
});
await assoc.addPoll(const ReadRequest.events(), const Duration(seconds: 1));
await channel.enable();
await assoc.operate(CommandSet.crob(0, const Crob.latchOn()));
}| Example | What it shows |
|---|---|
example/example.dart |
Outstation and master in one process: read, decode, control. Runs to completion. |
example/outstation.dart |
TCP outstation with changing values, native control handling, logging. |
example/master.dart |
Interactive TCP master: polls, CROB / analog commands, device attributes. |
example/worker_isolate.dart |
Measurements delivered straight into a worker isolate. |
example/flutter_app |
Flutter UI: in-app outstation or remote master, live values, controls. |
dart run example/example.dart
dart run example/outstation.dart 127.0.0.1:20000 # terminal 1
dart run example/master.dart 127.0.0.1:20000 # terminal 2 (type: on 1, ao 2 12.5, attrs, quit)- Master API — channels, associations, reads, commands, polls, files, back-pressure, connection policy, master server, errors and logging.
- Outstation API — servers and clients, database transactions, control and application handlers.
- Architecture — how the FFI bridge works, isolates, zero-copy paths.
- Benchmarks — delivery, decoding, database and end-to-end numbers.
- Wire format — binary formats exchanged with the native library.
- API reference — every public member is documented.
- Changelog
| Platform | Targets built and tested in CI | Also mapped by the hook (untested) | Serial |
|---|---|---|---|
| Android | aarch64, armv7, x86_64, i686 (*-linux-android) |
— | ✓ |
| iOS | aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios |
— | ✗ |
| macOS | aarch64-apple-darwin, x86_64-apple-darwin |
— | ✓ |
| Linux | x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu |
armv7, i686, riscv64gc |
✓ |
| Windows | x86_64-pc-windows-msvc, aarch64-pc-windows-msvc |
i686-pc-windows-msvc |
✓ |
CI runs the Dart test suite on Linux x64/arm64, macOS and Windows, builds all native targets, and builds the Flutter example for Android, iOS, macOS, Linux and Windows. Android libraries are 16 KB page aligned.
Contributions are welcome: bug reports, documentation, tests, platform fixes and features. Start with CONTRIBUTING.md: it covers the development setup, the checks CI runs, how the Dart ↔ Rust ABI is kept in sync and how releases are made. Please follow the Code of Conduct.
Good first steps:
git clone https://github.com/gencto/dnp3_plugin && cd dnp3_plugin
dart pub get
dart test # builds native/ through the hook, runs the integration suite
(cd native && cargo test) # Rust unit tests- Questions and bugs: open an issue.
- Security vulnerabilities: please report privately, see SECURITY.md.
- Issues in the DNP3 stack itself (protocol behavior of the Rust library): see stepfunc/dnp3.
MIT for this package, see LICENSE. The linked dnp3 Rust library has its own
license, see the note at the top.