Software-in-the-Loop
Software-in-the-Loop (SiL) simulation lets Vessim interact with real systems while the simulation is running. Use it to:
- Test real applications — validate energy-aware schedulers, autoscalers, or routing logic against simulated energy scenarios.
- Use real data sources — pull live metrics from sensors, monitoring systems, or APIs into the simulation.
SiL extras are not installed by default. Enable them with:
pip install vessim[sil]
Architecture
Vessim's SiL has two communication directions:
- Real-time data → Vessim:
SilSignal(and subclasses likePrometheusSignalorWatttimeSignal) fetches data from external sources in a background thread and feeds it into the simulation. - Vessim → external world: the
Apicontroller exposes the simulation state via a REST API. External programs can read it and send back control commands. Feel free to implement other types of APIs.
Real-time signals
A SilSignal polls its data source on a background thread, so the simulation never blocks on a network request.
PrometheusSignal
PrometheusSignal pulls a value from a Prometheus query. This is typical for using real server metrics as the load of a simulated actor:
import vessim as vs
power_signal = vs.PrometheusSignal(
prometheus_url="http://localhost:9090",
query='(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m]))) * 1000',
)
server = vs.Actor(name="server", signal=power_signal, consumer=True)
WatttimeSignal
WatttimeSignal fetches live grid carbon intensity from the WattTime API:
carbon = vs.WatttimeSignal(
username="my_user",
password="my_password",
region="CAISO_NORTH",
update_interval=300,
)
Custom SiL signals
Subclass SilSignal and implement _fetch_current_value:
class MySensorSignal(vs.SilSignal):
def _fetch_current_value(self) -> float:
# response = requests.get("http://192.168.1.50/power")
# return float(response.text)
return 42.0
The Api controller
Api starts a small FastAPI server that bridges the simulation and the outside world:
environment.add_controller(vs.Api(export_prometheus=True, broker_port=8700))
Once the simulation is running, the following endpoints are available at http://localhost:8700:
| Method & path | Purpose |
|---|---|
GET / |
List of microgrids and their actors. |
GET /<microgrid> |
Current state of a microgrid (power flows, battery SoC, …). |
PUT /<microgrid> |
Send a control command (e.g. mutate a dispatchable property). |
GET /metrics |
Prometheus-format metrics (when export_prometheus=True). |
Read the state:
curl http://localhost:8700/datacenter
{
"microgrid": "datacenter",
"time": "2023-01-01T00:00:00",
"p_delta": -500.0,
"p_grid": -500.0,
"dispatch": { "battery": { "soc": 0.8 } },
"actors": { "server": { "power": -500.0 } }
}
Send a control command:
curl -X PUT http://localhost:8700/datacenter \
-H "Content-Type: application/json" \
-d '{"dispatchable": {"name": "battery", "property": "min_soc", "value": 0.5}}'
When export_prometheus=True, the /metrics endpoint can be scraped by Prometheus and visualized in Grafana. See examples/sil/ for a ready-made compose file.
Walkthrough: real CPU load to simulated microgrid
This example wires up a real Prometheus + node_exporter stack so that the actual CPU usage of the host drives the simulated server's power draw. The full source is at examples/sil_example.py.
import vessim as vs
def main():
# Live mode: sim_start is captured when run() is called, simulation
# advances at 1× wall-clock, and Trace data replays starting from "now".
environment = vs.Environment.live(step_size=1)
# Server load is driven by real host CPU usage scraped via Prometheus
server = vs.Actor(
name="server",
consumer=True,
signal=vs.PrometheusSignal(
prometheus_url="http://localhost:9090",
query='(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m]))) * 1000',
),
)
solar = vs.Actor(
name="solar",
signal=vs.Trace.from_csv(
"datasets/solar_example.csv",
column="Berlin",
scale=2000,
),
)
battery = vs.SimpleBattery(name="battery", capacity=20, initial_soc=0.5)
environment.add_microgrid(
name="your_computer",
actors=[server, solar],
dispatchables=[battery],
)
environment.add_controller(vs.Api(export_prometheus=True))
environment.run()
if __name__ == "__main__":
main()
Running it
- Install the SiL extras:
pip install 'vessim[sil]' - Start Prometheus + node_exporter:
Wait ~15 s for the first scrapes to land.
docker compose -f examples/sil/docker-compose.yml up -d - Run the simulation:
python examples/sil_example.py - Hit the API in another terminal:
Drive your CPU up (e.g.
curl http://localhost:8700/your_computeryes > /dev/null) and watch the server power follow. - Optionally start Grafana to visualize the live
/metricsendpoint:docker compose -f examples/sil/docker-compose.grafana.yml up -d