The federate from page 1 runs, but its config was written by
hand and its broker started manually. oedisi build takes a “wiring diagram” JSON and
generates both.
Interface¶
component_definition.json in our component’s directory tells OEDISI its behavior.
{
"directory": "power_component",
"execute_function": "python power_component.py",
"static_inputs": [
{ "type": "", "port_id": "node_ids" },
{ "type": "", "port_id": "equipment_ids" },
{ "type": "", "port_id": "base_power" },
{ "type": "", "port_id": "multiplier" },
{ "type": "", "port_id": "number_of_timesteps" },
{ "type": "", "port_id": "step_size_seconds" },
{ "type": "", "port_id": "start_time" }
],
"dynamic_inputs": [],
"dynamic_outputs": [{ "type": "PowersReal", "port_id": "power" }],
"capabilities": { "broker_config": true }
}execute_function- the command that starts the federate. It runs with the component’s build directory as the working directory, which is how page 1’sopen("static_inputs.json")finds the right file.static_inputs- the parameters. Eachport_idbecomes a key in the generatedstatic_inputs.json, so these names must match the fields ofPowerComponentConfig."type": ""means untyped: counts, dates, file names.dynamic_inputs- subscriptions. Empty here. Page 3 has one.dynamic_outputs- publications.port_idMUST be the name passed toregister_publication, andtypenames a model from the data types reference.capabilities.broker_config-truebecausePowerComponentConfigsubclassesHELICSFederateConfigand can therefore read HELICS broker settings fromstatic_inputs.json.
Component types¶
oedisi build requires a list of all components, taking the form of a
components.json mapping component name to a definition file:
{
"PowerComponent": "power_component/component_definition.json",
"Recorder": "Components/recorder/component_definition.json"
}Paths are relative to wherever oedisi build runs.
The published power needs somewhere to go. The shared component repository has a
recorder federate that subscribes to any measurement and writes CSV and Feather, so make
it reachable:
ln -s "$OEDISI_COMPONENTS" ComponentsWiring diagram¶
The wiring diagram lists component instances and the links between them. Create
system_power.json:
{
"name": "tutorial_power",
"components": [
{
"name": "power",
"type": "PowerComponent",
"parameters": {
"node_ids": ["113.1", "113.2", "114.1"],
"equipment_ids": ["Load.load1", "Load.load1", "Load.load2"],
"base_power": [10.0, 12.5, 7.5],
"multiplier": 1.2,
"number_of_timesteps": 4
}
},
{
"name": "power_recorder",
"type": "Recorder",
"parameters": {
"feather_filename": "power.feather",
"csv_filename": "power.csv"
}
}
],
"links": [
{
"source": "power",
"source_port": "power",
"target": "power_recorder",
"target_port": "subscription"
}
]
}Here, we give each “instance” of a component “type” (PowerComponent) a unique name.
parameters is combined with the HELICS information and saved to the component as static_inputs.json.
A link connects an output port_id to an input port_id, so the recorder can listen to our PowerComponent.
Build¶
oedisi build --system system_power.json --component-dict components.json \
--target-directory build_powerbuild_power/ holds one directory per component: a copy of the component’s code plus two
generated files.
build_power/
├── power/
│ ├── power_component.py
│ ├── component_definition.json
│ ├── static_inputs.json ← generated
│ └── input_mapping.json ← generated
├── power_recorder/
│ └── ...
└── system_runner.jsonstatic_inputs.json is the file written by hand on page 1:
{"name": "power", "node_ids": ["113.1", "113.2", "114.1"], ...}The name came from the component’s name in the wiring diagram. Nothing inside the
component sets it, which is what allows one component to appear under several names in one
federation.
input_mapping.json names the subscriptions. The power component has no inputs, so its
copy is {}. The recorder’s input_mapping.json is the interesting one:
{"subscription": "power/power"}The recorder’s subscription port was linked to the power component’s power port, so
the key is power/power, following the {component name}/{port id} convention from page
The recorder does not know what is upstream of it. It opens whatever key this file names, so wiring something else into it changes only this file.
system_runner.json is the run plan, including a broker sized to the federation:
{
"name": "tutorial_power",
"federates": [
{ "directory": "power", "name": "power", "exec": "python power_component.py" },
{ "directory": "power_recorder", "name": "power_recorder",
"exec": "python -m src.recorder.record_subscription" },
{ "directory": ".", "name": "broker", "exec": "helics_broker -f 2 --loglevel=warning" }
]
}Run¶
oedisi run --runner build_power/system_runner.jsonEach federate’s output goes to build_power/<name>.log, and the recorder wrote its files
inside its own build directory:
cat build_power/power_recorder/power.csv113.1,113.2,114.1,time
12.0,15.0,9.0,2017-01-01 00:00:00.000000
12.0,15.0,9.0,2017-01-01 00:15:00.000000
12.0,15.0,9.0,2017-01-01 00:30:00.000000
12.0,15.0,9.0,2017-01-01 00:45:00.000000The columns are the ids from the published PowersReal, and the time column is its
time field.
Next: give the component an input.