Skip to content

Commit

Permalink
uploading with functional co-simulation setting
Browse files Browse the repository at this point in the history
  • Loading branch information
sagilar committed Apr 15, 2024
1 parent fe1f538 commit 54a1916
Show file tree
Hide file tree
Showing 58 changed files with 9,151 additions and 3,346 deletions.
3 changes: 2 additions & 1 deletion co-simulation/coe.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"endTime": 15.0,
"reportProgress": true,
"liveLogInterval": 0,
"logLevels": {}
"logLevels": {},
"loggingOn": true
}
48 changes: 30 additions & 18 deletions co-simulation/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Co-sim properties
timestep=0.5
max_time = 15.0
max_time = 15.0 + 5.0

rabbitmq_host = "localhost"
rabbitmq_port = 5672
Expand Down Expand Up @@ -45,39 +45,51 @@ def publish():

msg = {}
msg['time']= dt.isoformat()
msg['controller_event'] = "moveDiscreteCommand"
msg['controller_event_args_0'] = 4.0
msg['controller_event_args_1'] = 18.0
msg['controller_event_args_2'] = 4.0
msg['controller_event_args_3'] = 0.0
#msg['controller_event'] = "moveDiscreteCommand"
#msg['controller_event_args_0'] = 4.0
#msg['controller_event_args_1'] = 18.0
#msg['controller_event_args_2'] = 4.0
#msg['controller_event_args_3'] = 0.0


for i in range(int(max_time/timestep)):
msg['time']= datetime.now(tz = datetime.now().astimezone().tzinfo).isoformat(timespec='milliseconds')
print("i:" + str(i))
if (i==2):
msg['controller_event'] = "moveDiscreteCommand"
msg['controller_event_args_0'] = 4.0
msg['controller_event_args_1'] = 18.0
msg['controller_event_args_2'] = 4.0

if (i==11):
msg['controller_event'] = "closeGripperCommand"
msg['controller_event_args_0'] = 0.0
msg['controller_event_args_1'] = 0.0
msg['controller_event_args_2'] = 0.0
msg['controller_event_args_1'] = 23.0
msg['controller_event_args_2'] = 2.0

if (i==16):
elif (i==11):
msg['controller_event'] = "moveDiscreteCommand"
msg['controller_event_args_0'] = 0.0
msg['controller_event_args_0'] = 3.0
msg['controller_event_args_1'] = 20.0
msg['controller_event_args_2'] = 3.0

if (i==25):
msg['controller_event'] = "closeGripperCommand"
elif (i==16):
msg['controller_event'] = "moveDiscreteCommand"
msg['controller_event_args_0'] = 8.0
msg['controller_event_args_1'] = 10.0
msg['controller_event_args_2'] = 0.0

elif (i==25):
msg['controller_event'] = "moveDiscreteCommand"
msg['controller_event_args_0'] = 11.0
msg['controller_event_args_1'] = 16.0
msg['controller_event_args_2'] = 4.0
else:
msg['controller_event'] = ""
msg['controller_event_args_0'] = 0.0
msg['controller_event_args_1'] = 0.0
msg['controller_event_args_2'] = 0.0
msg['controller_event_args_3'] = 0.0
msg['controller_event_args_4'] = 0.0
msg['controller_event_args_5'] = 0.0
msg['controller_event_args_6'] = 0.0
msg['controller_event_args_7'] = 0.0
msg['controller_event_args_8'] = 0.0
msg['controller_event_args_9'] = 0.0

channel.basic_publish(exchange='fmi_digital_twin',
routing_key='data.to_cosim',
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
42 changes: 42 additions & 0 deletions co-simulation/controllerFMU/controllerFMU/modelDescription.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version='1.0' encoding='utf-8'?>
<fmiModelDescription fmiVersion="2.0" modelName="unifmu" guid="77236337-210e-4e9c-8f2c-c1a0677db21b" author="Santiago Gil" generationDateAndTime="2020-10-23T19:51:25Z" variableNamingConvention="flat" generationTool="unifmu">
<CoSimulation modelIdentifier="unifmu" needsExecutionTool="true" canNotUseMemoryManagementFunctions="false" canHandleVariableCommunicationStepSize="true" />
<LogCategories>
<Category name="logStatusWarning" />
<Category name="logStatusDiscard" />
<Category name="logStatusError" />
<Category name="logStatusFatal" />
<Category name="logStatusPending" />
<Category name="logAll" />
</LogCategories>
<ModelVariables>
<!--Index of variable = "1"-->
<ScalarVariable name="controller_event" valueReference="0" variability="discrete" causality="output">
<String />
</ScalarVariable>
<ScalarVariable name="controller_event_args_0" valueReference="1" causality="output" variability="continuous">
<Real />
</ScalarVariable>
<ScalarVariable name="controller_event_args_1" valueReference="2" causality="output" variability="continuous">
<Real />
</ScalarVariable>
<ScalarVariable name="controller_event_args_2" valueReference="3" causality="output" variability="continuous">
<Real />
</ScalarVariable>

</ModelVariables>
<ModelStructure>
<Outputs>
<Unknown index="1" dependencies="" />
<Unknown index="2" dependencies="" />
<Unknown index="3" dependencies="" />
<Unknown index="4" dependencies="" />
</Outputs>
<InitialUnknowns>
<Unknown index="1" dependencies="" />
<Unknown index="2" dependencies="" />
<Unknown index="3" dependencies="" />
<Unknown index="4" dependencies="" />
</InitialUnknowns>
</ModelStructure>
</fmiModelDescription>
115 changes: 115 additions & 0 deletions co-simulation/controllerFMU/controllerFMU/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
**This FMU was generated using UniFMU.
For general instructions on how to use the tool access the repository https://github.com/INTO-CPS-Association/unifmu**

# Implementing the model

The `resources/model.py` file defines the functional relationship between inputs and outputs of the FMU.

## Declaring inputs and outputs

By default, each input, output or parameter declared in the `modelDescription.xml` file is represented as attributes on the instance of the `Model` class.
For instance if a variable `a` is declared in the `modelDescription.xml` file, it an attribute of the same name should be declared in the `Model` class:

```xml
<ScalarVariable name="a" valueReference="0" variability="continuous" causality="input">
<Real start="0.0" />
</ScalarVariable>
```

```python
def __init__(self) -> None:
self.a = 0.0

self.reference_to_attribute = {
0: "a",
}
```

The FMI C-API uses numerical indices rather than names to which variables to read or write to.
As such a mapping between the indices declared by the `valueReference` attribute of the xml and the attributes must be defined.
By default the mapping between a value reference and its corresponding Python attribute is defined by adding an entry to the `reference_to_attributes` variable of the `Model` class.

## Defining the behavior

The `Model` class declares several methods that can be used to define the behavior of the FMU.
Methods prefixed with `fmi2` mirror the methods declared in the C-API defined by the FMI specification.

For instance, to update an output `b` to be twice the value of `a` the `fmi2DoStep` method could be defined as:

```python
def fmi2DoStep(self, current_time, step_size, no_step_prior):
self.b = self.a * 2
return Fmi2Status.ok
```

# Testing and debugging the model

The `model.py` is _plain_ Python code, which means we can test the model using test cases and debugging tools.
A small test program can be written and placed in the `model.py` the slave as seen below:

```python
if __name__ == "__main__":
m = Model()

assert m.a == 0.0
assert m.b == 0.0

m.a = 1.0
m.fmiDoStep(0.0, 1.0, False)

assert m.b == 2.0
```

The program can be executed in your IDE with or from the command line by running the `resources/model.py` script.

# Runtime dependencies

The environment that invokes the Python code must provide all the dependencies, otherwise the simulation will fail when instantiating or simulation the model.
For instance, if the `resources/model.py` imports a third-party package such as `numpy`

```python
import numpy as np
```

this must also be available to the Python interpreter specified by the `launch.toml` file, in this case the system's `python3` interpreter:

```toml
linux = ["python3", "backend.py"]
```

One way to address a missing dependency is to install using package manager such as `pip`

```
python3 -m pip install numpy
```

**Any Python FMU generated UniFMU requires the `protobuf` package.
The easiest way to install this is using pip:**

```
python3 -m pip install protobuf
```

# File structure

An overview of the role of each file is provided in the tree below:

```python
📦model
┣ 📂binaries
┃ ┣ 📂darwin64
┃ ┃ ┗ 📜unifmu.dylib # binary for macOS
┃ ┣ 📂linux64
┃ ┃ ┗ 📜unifmu.so # binary for Linux
┃ ┗ 📂win64
┃ ┃ ┗ 📜unifmu.dll # binary For Windows
┣ 📂resources
┃ ┣ 📂schemas
┃ ┃ ┗ 📜unifmu_fmi2_pb2.py # schema defining structure of messages sent over RPC
┃ ┣ 📜backend.py # receives messages and dispatched function calls to "model.py"
┃ ┣ 📜launch.toml* # specifies command used to start FMU
┃ ┗ 📜model.py* # implementation of FMU
┗ 📜modelDescription.xml* # definition of inputs and outputs
```

\* denotes files that would typically be modified by the implementor of the FMU
142 changes: 142 additions & 0 deletions co-simulation/controllerFMU/controllerFMU/resources/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import logging
import os
import sys
from schemas.unifmu_fmi2_pb2 import (
Fmi2Command,
Fmi2ExtHandshakeReturn,
Fmi2ExtSerializeSlaveReturn,
Fmi2StatusReturn,
Fmi2GetRealReturn,
Fmi2GetIntegerReturn,
Fmi2GetBooleanReturn,
Fmi2GetStringReturn,
Fmi2FreeInstanceReturn,
)

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__file__)

try:
import zmq
except ImportError:
logger.fatal(
"unable to import the python library 'zmq' required by the schemaless backend. "
"please ensure that the library is present in the python environment launching the script. "
"the missing dependencies can be installed using 'python -m pip install unifmu[python-backend]'"
)
sys.exit(-1)

from model import Model

if __name__ == "__main__":

slave = Model()

# initializing message queue
context = zmq.Context()
socket = context.socket(zmq.REQ)
dispatcher_endpoint = os.environ["UNIFMU_DISPATCHER_ENDPOINT"]
logger.info(f"dispatcher endpoint received: {dispatcher_endpoint}")
socket.connect(dispatcher_endpoint)

state = Fmi2ExtHandshakeReturn().SerializeToString()
socket.send(state)

command = Fmi2Command()

while True:

msg = socket.recv()
command.ParseFromString(msg)

group = command.WhichOneof("command")

data = getattr(command, command.WhichOneof("command"))
if group == "Fmi2SetupExperiment":
result = Fmi2StatusReturn()
result.status = slave.fmi2SetupExperiment(
data.start_time, data.stop_time, data.tolerance
)

elif group == "Fmi2SetDebugLogging":
result = Fmi2StatusReturn()
result.status = slave.fmiSetDebugLogging(
data.categores, data.logging_on
)

elif group == "Fmi2DoStep":
result = Fmi2StatusReturn()
result.status = slave.fmi2DoStep(
data.current_time, data.step_size, data.no_step_prior
)
elif group == "Fmi2EnterInitializationMode":
result = Fmi2StatusReturn()
result.status = slave.fmi2EnterInitializationMode()
elif group == "Fmi2ExitInitializationMode":
result = Fmi2StatusReturn()
result.status = slave.fmi2ExitInitializationMode()
elif group == "Fmi2ExtSerializeSlave":
result = Fmi2ExtSerializeSlaveReturn()
(result.status, result.state) = slave.fmi2ExtSerialize()
elif group == "Fmi2ExtDeserializeSlave":
result = Fmi2StatusReturn()
result.status = slave.fmi2ExtDeserialize(data.state)
elif group == "Fmi2GetReal":
result = Fmi2GetRealReturn()
result.status, result.values[:] = slave.fmi2GetReal(
command.Fmi2GetReal.references
)
elif group == "Fmi2GetInteger":
result = Fmi2GetIntegerReturn()
result.status, result.values[:] = slave.fmi2GetInteger(
command.Fmi2GetInteger.references
)
elif group == "Fmi2GetBoolean":
result = Fmi2GetBooleanReturn()
result.status, result.values[:] = slave.fmi2GetBoolean(
command.Fmi2GetBoolean.references
)
elif group == "Fmi2GetString":
result = Fmi2GetStringReturn()
result.status, result.values[:] = slave.fmi2GetString(
command.Fmi2GetString.references
)
elif group == "Fmi2SetReal":
result = Fmi2StatusReturn()
result.status = slave.fmi2SetReal(
command.Fmi2SetReal.references, command.Fmi2SetReal.values
)

elif group == "Fmi2SetInteger":
result = Fmi2StatusReturn()

result.status = slave.fmi2SetInteger(
command.Fmi2SetInteger.references, command.Fmi2SetInteger.values
)

elif group == "Fmi2SetBoolean":
result = Fmi2StatusReturn()
result.status = slave.fmi2SetBoolean(
command.Fmi2SetBoolean.references, command.Fmi2SetBoolean.values
)
elif group == "Fmi2SetString":
result = Fmi2StatusReturn()
result.status = slave.fmi2SetString(
command.Fmi2SetString.references, command.Fmi2SetString.values
)
elif group == "Fmi2Terminate":
result = Fmi2StatusReturn()
result.status = slave.fmi2Terminate()
elif group == "Fmi2Reset":
result = Fmi2StatusReturn()
result.status = slave.fmi2Reset()
elif group == "Fmi2FreeInstance":
result = Fmi2FreeInstanceReturn()
logger.info(f"Fmi2FreeInstance received, shutting down")
sys.exit(0)
else:
logger.error(f"unrecognized command '{group}' received, shutting down")
sys.exit(-1)

state = result.SerializeToString()
socket.send(state)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
linux = ["python3", "backend.py"]
macos = ["python3", "backend.py"]
windows = ["python", "backend.py"]
Loading

0 comments on commit 54a1916

Please sign in to comment.