Metadata-Version: 2.4
Name: dnp3-simulation
Version: 0.1.0
Summary: Lightweight DNP3 binary TCP simulator with separate client/server commands.
Author: Jenil Vekariya
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: requires-python

# DNP3 Simulation

Python DNP3 binary TCP simulator with separate server and client commands.

This project is useful when you want to test DNP3 master/client behavior without a real outstation device. It sends and receives real DNP3-style binary TCP frames for the supported function codes.

## Supported Function Codes

| Function Code | Name | Support |
| --- | --- | --- |
| FC 1 | Read | Read binary output status points |
| FC 2 | Write | Write binary output status points |
| FC 5 | Direct Operate | Operate binary output points and return response |
| FC 6 | Direct Operate No Acknowledge | Operate binary output points without application response |
| FC 13 | Cold Restart | Reset point values to startup defaults |
| FC 14 | Warm Restart | Restart while preserving point values by default |

## Implemented Binary DNP3 Subset

- DNP3 TCP binary frames beginning with `05 64`
- Link-layer length, control, source, destination, and CRC blocks
- Single-fragment transport/application messages
- `Group 10 Variation 2` for Binary Output Status read/write
- `Group 12 Variation 1` for Control Relay Output Block direct operate
- Restart response with time-delay object

This is a compact simulator subset, not a full DNP3 stack. It does not yet implement fragmentation, unsolicited responses, secure authentication, serial transport, or every DNP3 object group.

## Project Files

```text
simulation/
  config/
    outstation.json
    points.json
  dnp3_sim/
    client.py
    server_main.py
    server.py
    simulator.py
    dnp3_binary.py
    config.py
    models.py
  docs/
    DNP3_SIMULATION_PROJECT_ARCHITECTURE.md
  tests/
    test_dnp3_binary.py
    test_simulator.py
  pyproject.toml
  setup.py
```

Important files:

- `dnp3_sim/server_main.py`: server command entry point
- `dnp3_sim/client.py`: client command entry point
- `dnp3_sim/dnp3_binary.py`: DNP3 binary frame encode/decode logic
- `dnp3_sim/simulator.py`: simulator behavior for FC 1, 2, 5, 6, 13, 14
- `config/outstation.json`: server host, port, addresses, restart timing
- `config/points.json`: simulated binary output points

## Setup

Use a Python virtual environment. This avoids system permission problems with editable installs.

```bash
cd /home/jenilvekariya/simulation
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
```

After this, these commands are available:

```bash
dnp3-sim-server
dnp3-sim-client
dnp3-sim
```

For development and tests:

```bash
source .venv/bin/activate
python -m pip install -e ".[dev]"
python -m pytest -q
```

Expected test result:

```text
13 passed
```

## Run The Server

Start the server on all network interfaces:

```bash
source .venv/bin/activate
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Start the server on localhost only:

```bash
dnp3-sim-server --host 127.0.0.1 --port 20000
```

Use a custom config file:

```bash
dnp3-sim-server \
  --outstation config/outstation.json \
  --points config/points.json \
  --host 0.0.0.0 \
  --port 21000
```

You can also run without installing the package:

```bash
python3 -m dnp3_sim.server_main --host 0.0.0.0 --port 20000
```

Stop the server with `Ctrl+C`.

## Configure Host And Port

You can configure host and port in two ways.

Command line:

```bash
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Config file:

```json
{
  "outstation": {
    "transport": {
      "type": "tcp",
      "host": "0.0.0.0",
      "port": 20000
    }
  }
}
```

The command-line `--host` and `--port` values override the config file.

## Configure Points

Edit `config/points.json`.

Example:

```json
{
  "binary_outputs": [
    {
      "index": 0,
      "name": "breaker_1",
      "startup_value": false,
      "commandable": true,
      "allowed_operations": ["latch_on", "latch_off", "pulse_on", "pulse_off"]
    },
    {
      "index": 1,
      "name": "pump_1",
      "startup_value": false,
      "commandable": true,
      "allowed_operations": ["latch_on", "latch_off"]
    }
  ]
}
```

Restart the server after changing config files.

## Client Commands

Open another terminal and activate the same virtual environment:

```bash
cd /home/jenilvekariya/simulation
source .venv/bin/activate
```

### Read

Read all binary output status points:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000
```

Read one point:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000 --index 0
```

This sends FC 1 Read and returns decoded point values.

### Write

Write point `0` to on:

```bash
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value on
```

Write point `0` to off:

```bash
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value off
```

This sends FC 2 Write using Binary Output Status.

### Direct Operate

Send FC 5 Direct Operate:

```bash
dnp3-sim-client direct \
  --host 127.0.0.1 \
  --port 20000 \
  --function-code 5 \
  --index 0 \
  --operation latch_on
```

Supported operations:

```text
latch_on
latch_off
pulse_on
pulse_off
```

### Direct Operate No Acknowledge

Send FC 6 Direct Operate No Acknowledge:

```bash
dnp3-sim-client direct \
  --host 127.0.0.1 \
  --port 20000 \
  --function-code 6 \
  --index 0 \
  --operation latch_off \
  --timeout 0.2
```

FC 6 updates the point but intentionally returns no normal application response.

### Cold Restart

```bash
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type cold
```

Cold restart resets point values to their configured startup values.

### Warm Restart

```bash
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type warm
```

Warm restart preserves point values by default.

### Send Raw Hex

You can send a raw DNP3 frame:

```bash
dnp3-sim-client send-hex --host 127.0.0.1 --port 20000 "05 64 ..."
```

The client validates the frame size and sends the bytes as-is.

## Example Full Workflow

Terminal 1:

```bash
cd /home/jenilvekariya/simulation
source .venv/bin/activate
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Terminal 2:

```bash
cd /home/jenilvekariya/simulation
source .venv/bin/activate

dnp3-sim-client read --host 127.0.0.1 --port 20000
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value on
dnp3-sim-client read --host 127.0.0.1 --port 20000 --index 0
dnp3-sim-client direct --host 127.0.0.1 --port 20000 --function-code 5 --index 0 --operation latch_off
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type cold
```

## Client Output

The client prints JSON with transmitted and received DNP3 packet hex.

Example fields:

```json
{
  "tx_hex": "05 64 ...",
  "rx_hex": "05 64 ...",
  "link": {
    "control": 68,
    "destination": 1,
    "source": 1024
  },
  "application": {
    "function_code": 129,
    "payload_hex": "00 00 ..."
  }
}
```

Notes:

- `tx_hex` is the DNP3 request frame.
- `rx_hex` is the DNP3 response frame.
- DNP3 response function code is `129`, which is `0x81`.
- FC 6 normally shows no response.

## Capture PCAP

Use this when you want to inspect packets in Wireshark.

Terminal 1, start server:

```bash
source .venv/bin/activate
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Terminal 2, start capture:

```bash
sudo tcpdump -i lo -s 0 -w dnp3_simulation.pcap 'tcp port 20000'
```

Terminal 3, send binary packets:

```bash
source .venv/bin/activate
dnp3-sim-client read --host 127.0.0.1 --port 20000
dnp3-sim-client direct --host 127.0.0.1 --port 20000 --function-code 5 --index 0 --operation latch_on
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type cold
```

Stop capture with `Ctrl+C`.

Open `dnp3_simulation.pcap` in Wireshark and use:

```text
tcp.port == 20000
```

DNP3 frames start with:

```text
05 64
```

If client and server are on different machines, capture on the real network interface instead of `lo`.

## JSON Helper Mode

The server also accepts JSON-lines requests on the same TCP port. This is only for quick debugging; binary DNP3 mode is the main interface.

Start the server first, then run these commands from another terminal.

Examples:

```bash
python3 -m dnp3_sim send --function-code 1
python3 -m dnp3_sim send --function-code 2 --index 0 --value on
python3 -m dnp3_sim send --function-code 5 --index 0 --operation latch_on
python3 -m dnp3_sim send --function-code 6 --index 0 --operation latch_off
python3 -m dnp3_sim send --function-code 13
python3 -m dnp3_sim send --function-code 14
python3 -m dnp3_sim status
```

JSON request examples:

```json
{"function_code": 1}
```

```json
{"function_code": 2, "writes": [{"point_type": "binary_output", "index": 0, "value": true}]}
```

```json
{"function_code": 5, "controls": [{"point_type": "binary_output", "index": 0, "operation": "latch_on"}]}
```

```json
{"function_code": 13}
```

## Troubleshooting

### `pip install -e .` fails with `build_editable`

Use the virtualenv setup:

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
```

### Command Not Found

Activate the virtual environment:

```bash
source /home/jenilvekariya/simulation/.venv/bin/activate
```

Then try:

```bash
dnp3-sim-client --help
dnp3-sim-server --help
```

### Address Already In Use

Another process is using the port.

Check:

```bash
ss -ltnp | grep ':20000'
```

Use another port:

```bash
dnp3-sim-server --host 0.0.0.0 --port 21000
dnp3-sim-client read --host 127.0.0.1 --port 21000
```

### Cannot Connect From Another Machine

Start the server on all interfaces:

```bash
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Then connect to the server machine IP:

```bash
dnp3-sim-client read --host SERVER_IP --port 20000
```

Also check firewall rules for TCP port `20000`.

## Development

Run tests:

```bash
source .venv/bin/activate
python -m pytest -q
```

Run a module directly:

```bash
python3 -m dnp3_sim.server_main --host 0.0.0.0 --port 20000
python3 -m dnp3_sim.client read --host 127.0.0.1 --port 20000
```

Architecture details:

```text
docs/DNP3_SIMULATION_PROJECT_ARCHITECTURE.md
```
