Skip to main content

Digital twin simulator for distributed energy resources — BESS, PV inverters, energy meters

Project description

DER Twin

Digital Twin infrastructure for modern energy systems.

DER Twin is a lightweight simulator for Distributed Energy Resources (DER) — BESS, PV inverters, energy meters, and grid models — exposed via Modbus TCP. Use it for EMS development, protocol testing, integration validation, and control algorithm sandboxing without touching real hardware.


⚡ Quickstart

Option A — pip install

pip install dertwin

Bring your own site config and register maps:

dertwin -c path/to/your/config.json

You should see:

INFO | Building site: my-site
INFO | Starting Modbus server | 0.0.0.0:55001 | unit=1
INFO | Simulation engine started | step=0.100s

The simulator is now accepting Modbus TCP connections on the ports defined in your config.

Option B — Run from source

git clone https://github.com/AlexSpivak/dertwin.git
cd dertwin
python -m venv .venv && source .venv/bin/activate
pip install -e .
python -m dertwin.main -c configs/simple_config.json

Option C — Run with Docker

git clone https://github.com/AlexSpivak/dertwin.git
cd dertwin
python generate_compose.py configs/simple_config.json
docker compose up --build

generate_compose.py reads the config and generates a docker-compose.yml with the correct ports automatically. No manual port configuration needed.


🔌 Connect an EMS

With the simulator running, start the example EMS from a second terminal:

cd examples
python main_simple.py

You'll see the EMS connecting over Modbus and cycling the BESS between 40–60% SOC:

[EMS] Connected to BESS
[EMS] Starting in CHARGE mode
[EMS] STATUS=1 | SOC= 42.30% | P=  -20.00 kW | MODE=charge
[EMS] STATUS=1 | SOC= 44.10% | P=  -20.00 kW | MODE=charge
...
[EMS] Reached 60% → switching to DISCHARGE

For a full multi-device site (dual BESS + PV + energy meter + external models):

python -m dertwin.main -c configs/full_site_config.json
# in another terminal:
python examples/main_full.py

🧱 Features

  • Async Modbus TCP server built on pymodbus
  • Config-driven site topology — add devices by editing JSON
  • Irradiance, ambient temperature, grid frequency, and grid voltage models
  • Multi-device support across independent ports
  • External model events (voltage sags, frequency deviations)
  • Simulation start time control (start_time_h) — start at noon, peak load, etc.
  • Docker support with auto-generated Compose files
  • Deterministic simulation with seeded random models
  • Fully tested with pytest

📦 Repo Structure

dertwin/
├── configs/
│   ├── register_maps/       # Modbus register definitions (YAML)
│   ├── simple_config.json   # Single BESS — good starting point
│   ├── demo_config.json     # Full three-device site
│   └── full_site_config.json# Dual BESS + PV + meter + external models
├── dertwin/
│   ├── core/                # Clock, engine, register map loader
│   ├── controllers/         # Site and device orchestration
│   ├── devices/             # BESS, PV, energy meter, external models
│   ├── protocol/            # Modbus TCP server
│   ├── telemetry/           # Telemetry dataclasses
│   └── main.py
├── examples/
│   ├── simple/              # Single BESS EMS example
│   ├── full/                # Multi-device EMS example
│   └── protocol/            # Shared Modbus client
├── tests/                   # Full test suite
├── generate_compose.py      # Docker Compose generator
└── Dockerfile

⚙️ Configuration

Sites are defined in JSON. Each asset declares its type, parameters, and Modbus protocol binding:

{
  "site_name": "my-site",
  "step": 0.1,
  "real_time": true,
  "start_time_h": 12.0,
  "register_map_root": "register_maps",
  "external_models": {
    "irradiance": { "peak": 1000.0, "sunrise": 6.0, "sunset": 18.0 },
    "grid_frequency": { "nominal_hz": 50.0, "noise_std": 0.002, "seed": 42 }
  },
  "assets": [
    {
      "type": "bess",
      "capacity_kwh": 100.0,
      "initial_soc": 60.0,
      "protocols": [{ "kind": "modbus_tcp", "ip": "0.0.0.0", "port": 55001, "unit_id": 1, "register_map": "bess_modbus.yaml" }]
    }
  ]
}

real_time: true — engine runs its own loop, use for dertwin CLI and EMS examples
real_time: false — caller drives the clock via step_once(), use for tests
start_time_h — sets simulation clock on startup (e.g. 12.0 for noon). All external models start from this time.
register_map_root — path to register map directory, resolved relative to the working directory where you run dertwin
ip: "0.0.0.0" — required when running inside Docker so port mapping works. Use 127.0.0.1 for local-only.

Register map fields:

Field Required Description
name yes Human-readable label, used in logs and the EMS client
internal_name yes Maps to the device's internal telemetry or command field — must match the attribute name in the corresponding telemetry class (see dertwin/telemetry/README.md)
address yes Modbus register address
type yes uint16, int16, uint32, int32
scale yes Multiplier applied on read, divisor applied on write
count yes Number of registers (1 for 16-bit, 2 for 32-bit)
func yes Function code: 0x04 input read, 0x03 holding read, 0x06 single write, 0x10 multi-register write
direction yes read or write
unit no Physical unit label (V, kW, Hz, etc.)
description no Free-text note
options no Enum mapping for status/mode registers

name and internal_name can differ — name is what the EMS client sees, internal_name is what the device simulator uses internally. For example, on_grid_power_setpoint (name) maps to active_power_setpoint (internal_name) on the BESS device.

For detailed architecture and per-package docs, see dertwin/README.md.


🐳 Docker

# Generate docker-compose.yml from any config
python generate_compose.py configs/full_site_config.json

# Ports are read automatically from the config — no manual editing
docker compose up --build

# Override config at runtime without rebuilding
docker run \
  -v /path/to/my/configs:/app/configs:ro \
  -e CONFIG_PATH=/app/configs/my_site.json \
  -p 55001:55001 \
  dertwin-simulator

🧪 Tests

pytest

The test suite covers device physics, register encoding, external models, and full end-to-end site integration via Modbus TCP. See tests/ for structure.


📈 Roadmap

  • Scenario engine — scripted event sequences
  • REST API + web dashboard
  • IEC 61850 support
  • MQTT integration
  • Published PyPI package

🧠 Use Cases

  • EMS algorithm development and validation
  • SCADA/HMI integration testing
  • Protocol compliance testing
  • DER fleet orchestration prototyping
  • Frequency and voltage response simulation

🤝 Contributing

Contributions are welcome. Before diving in, read dertwin/README.md — it covers the simulator architecture, how devices are modeled, the engine and clock design, and how to add new device types or protocols.

See CONTRIBUTING.md for full guidelines.


📜 License

MIT License


👤 Author

Oleksandr Spivak

Project details


Download files

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

Source Distribution

dertwin-0.1.1.tar.gz (30.7 kB view details)

Uploaded Source

Built Distribution

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

dertwin-0.1.1-py3-none-any.whl (38.0 kB view details)

Uploaded Python 3

File details

Details for the file dertwin-0.1.1.tar.gz.

File metadata

  • Download URL: dertwin-0.1.1.tar.gz
  • Upload date:
  • Size: 30.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for dertwin-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c8ac267bb37b2ec5e4db724d63a731e3c062ee975e3e758752b18ef300aea753
MD5 bc6131040582c0b870fcb1055c105be1
BLAKE2b-256 a1b0ae25feb9a53ebffa5f01756481e957e1734172a2955577890a06e1b9df76

See more details on using hashes here.

File details

Details for the file dertwin-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: dertwin-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 38.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for dertwin-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f0263355cbaa756ece39c3e6a0dc7bd772c4f26ec360d9c276f79f387bcf305e
MD5 25019031fe973584e5f64019c6e399b2
BLAKE2b-256 846c89a0fc12dedf075c42e1e9a1bb40ccb9104d798bd43857f7271a5ab2a6b6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page