Metadata-Version: 2.4
Name: dertwin
Version: 0.1.1
Summary: Digital twin simulator for distributed energy resources — BESS, PV inverters, energy meters
Author-email: Oleksandr Spivak <oleksdertwin@gmail.com>
License: MIT License
        
        Copyright (c) 2026 Oleksandr Spivak
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/AlexSpivak/dertwin
Project-URL: Repository, https://github.com/AlexSpivak/dertwin
Project-URL: Bug Tracker, https://github.com/AlexSpivak/dertwin/issues
Keywords: modbus,digital-twin,bess,energy,ems,der,simulator
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pymodbus<4.0,>=3.5
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Dynamic: license-file

# 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

```bash
pip install dertwin
```

Bring your own site config and register maps:

```bash
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

```bash
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

```bash
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:

```bash
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):

```bash
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:

```json
{
  "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`](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`](dertwin/README.md).

---

## 🐳 Docker

```bash
# 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

```bash
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
- [x] 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`](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](CONTRIBUTING.md) for full guidelines.

---

## 📜 License

MIT License

---

## 👤 Author

Oleksandr Spivak
