From 1a19ee47940193c2ec8cce4ee2a2112b15cf0452 Mon Sep 17 00:00:00 2001 From: Clemens Boos Date: Mon, 27 Nov 2023 11:54:43 +0100 Subject: [PATCH 1/6] Add demo FMUs for FMI-LS-BUS This includes: - A demo FMU for a CAN node with triggered output clocks - A demo FMU for a simple CAN bus simulation Each demo includes a script to pack it into a source-code FMU --- ls-bus-guide/demos/.gitignore | 2 + .../demos/can-bus-simulation/PackFmu.py | 41 + .../description/buildDescription.xml | 12 + .../description/fmi-ls-manifest.xml | 8 + .../description/modelDescription.xml | 101 ++ .../description/terminalsAndIcons.xml | 41 + .../demos/can-bus-simulation/src/App.c | 481 +++++++++ .../demos/can-bus-simulation/src/App.h | 110 ++ .../demos/can-bus-simulation/src/Fmu.c | 982 ++++++++++++++++++ .../can-bus-simulation/src/FmuIdentifier.h | 6 + .../demos/can-bus-simulation/src/Instance.h | 43 + .../demos/can-bus-simulation/src/Logging.c | 66 ++ .../demos/can-bus-simulation/src/Logging.h | 12 + .../can-node-triggered-output/PackFmu.py | 41 + .../description/buildDescription.xml | 12 + .../description/fmi-ls-manifest.xml | 8 + .../description/modelDescription.xml | 77 ++ .../description/terminalsAndIcons.xml | 24 + .../demos/can-node-triggered-output/src/App.c | 235 +++++ .../demos/can-node-triggered-output/src/App.h | 110 ++ .../demos/can-node-triggered-output/src/Fmu.c | 982 ++++++++++++++++++ .../src/FmuIdentifier.h | 6 + .../can-node-triggered-output/src/Instance.h | 43 + .../can-node-triggered-output/src/Logging.c | 66 ++ .../can-node-triggered-output/src/Logging.h | 12 + 25 files changed, 3521 insertions(+) create mode 100644 ls-bus-guide/demos/.gitignore create mode 100644 ls-bus-guide/demos/can-bus-simulation/PackFmu.py create mode 100644 ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml create mode 100644 ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml create mode 100644 ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml create mode 100644 ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/App.c create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/App.h create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/Fmu.c create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/FmuIdentifier.h create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/Instance.h create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/Logging.c create mode 100644 ls-bus-guide/demos/can-bus-simulation/src/Logging.h create mode 100644 ls-bus-guide/demos/can-node-triggered-output/PackFmu.py create mode 100644 ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml create mode 100644 ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml create mode 100644 ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml create mode 100644 ls-bus-guide/demos/can-node-triggered-output/description/terminalsAndIcons.xml create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/App.c create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/App.h create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/Fmu.c create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/FmuIdentifier.h create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/Instance.h create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/Logging.c create mode 100644 ls-bus-guide/demos/can-node-triggered-output/src/Logging.h diff --git a/ls-bus-guide/demos/.gitignore b/ls-bus-guide/demos/.gitignore new file mode 100644 index 0000000..eef2d09 --- /dev/null +++ b/ls-bus-guide/demos/.gitignore @@ -0,0 +1,2 @@ +# Do not store FMU artifacts in here +*.fmu diff --git a/ls-bus-guide/demos/can-bus-simulation/PackFmu.py b/ls-bus-guide/demos/can-bus-simulation/PackFmu.py new file mode 100644 index 0000000..16eb9d5 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/PackFmu.py @@ -0,0 +1,41 @@ +import urllib.request +import zipfile + +from pathlib import Path + + +# Output FMU path +FMU_PATH = Path('DemoCanBusSimulation.fmu') + +# Repository to fetch the LS-BUS headers from +LS_BUS_REPO = 'modelica/fmi-ls-bus' +LS_BUS_REV = '473bd5b80730c47373bf41f1c31d44f50de82dd0' +LS_BUS_HEADERS = [ 'fmi3LsBus.h', 'fmi3LsBusCan.h' ] + + +def main(): + demo_dir = Path(__file__).parent + + with zipfile.ZipFile(FMU_PATH, 'w') as fmu: + # Add LS-BUS headers from GitHub repository + for ls_bus_header in LS_BUS_HEADERS: + with urllib.request.urlopen(f'https://raw.githubusercontent.com/{LS_BUS_REPO}/{LS_BUS_REV}/headers/{ls_bus_header}') as f: + fmu.writestr(f'sources/{ls_bus_header}', f.read()) + + # Add LS-BUS utility headers + for file in (demo_dir.parent.parent / 'headers').iterdir(): + fmu.write(file, f'sources/{file.name}') + + # Add source files + for file in (demo_dir / 'src').iterdir(): + fmu.write(file, f'sources/{file.name}') + + # Add description files + fmu.write(demo_dir / 'description' / 'modelDescription.xml', 'modelDescription.xml') + fmu.write(demo_dir / 'description' / 'buildDescription.xml', 'sources/buildDescription.xml') + fmu.write(demo_dir / 'description' / 'terminalsAndIcons.xml', 'terminalsAndIcons/terminalsAndIcons.xml') + fmu.write(demo_dir / 'description' / 'fmi-ls-manifest.xml', 'extra/org.fmi-standard.fmi-ls-bus/fmi-ls-manifest.xml') + + +if __name__ == '__main__': + main() diff --git a/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml b/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml new file mode 100644 index 0000000..62cf3e3 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml b/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml new file mode 100644 index 0000000..a5a9253 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml @@ -0,0 +1,8 @@ + + diff --git a/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml b/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml new file mode 100644 index 0000000..502cf62 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml b/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml new file mode 100644 index 0000000..52a9538 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-bus-simulation/src/App.c b/ls-bus-guide/demos/can-bus-simulation/src/App.c new file mode 100644 index 0000000..cf4c493 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/App.c @@ -0,0 +1,481 @@ +#include "App.h" + +#include + +#include "fmi3LsBusUtilCan.h" + +#include "Logging.h" + + +/** + * \brief Identifies variables of this FMU with their value references. + */ +typedef enum +{ + FMU_VAR_NODE1_RX_DATA = 0, + FMU_VAR_NODE1_TX_DATA = 1, + FMU_VAR_NODE1_RX_CLOCK = 2, + FMU_VAR_NODE1_TX_CLOCK = 3, + + FMU_VAR_NODE2_RX_DATA = 4, + FMU_VAR_NODE2_TX_DATA = 5, + FMU_VAR_NODE2_RX_CLOCK = 6, + FMU_VAR_NODE2_TX_CLOCK = 7 +} FmuVariables; + + +enum { FrameQueueSize = 128 /**< The maximum size of the frame queue for a single node. */ }; + + +/** + * \brief Describes a single frame queued to be transmitted. + */ +typedef struct +{ + fmi3LsBusCanId Id; + fmi3LsBusCanIde Ide; + fmi3LsBusCanRtr Rtr; + fmi3LsBusCanDataLength DataLength; + fmi3Byte Data[64]; +} FrameQueueEntry; + + +/** + * \brief A queue for frames transmitted by a single node. + * \note This is implemented as a ring buffer. + */ +typedef struct +{ + FrameQueueEntry Entries[FrameQueueSize]; + fmi3UInt8 WritePos; + fmi3UInt8 ReadPos; +} FrameQueue; + + +#define FRAME_QUEUE_INCREMENT(i, n) (((i) + 1) % (n)) +#define FRAME_QUEUE_DECREMENT(i, n) ((i) > 0 ? (i)-1 : (n)-1) + + +/** + * \brief Type identifying bus nodes. + */ +typedef uint32_t NodeIdType; + + +enum { NumNodes = 2 /**< Number of terminals for attaching nodes to this bus simulation. */ }; + + +/** + * \brief Data for a single terminal of this bus FMU. + */ +typedef struct +{ + fmi3Byte RxBuffer[2048]; + fmi3LsBusUtilBufferInfo RxBufferInfo; + fmi3Clock RxClock; + + fmi3Byte TxBuffer[2048]; + fmi3LsBusUtilBufferInfo TxBufferInfo; + fmi3Clock TxClock; + + fmi3IntervalQualifier TxClockQualifier; + + FrameQueue FrameQueue; + + fmi3LsBusCanBaudrate BaudRate; +} NodeData; + + +/** + * \brief Instance-specific data of this FMU. + */ +struct AppType +{ + fmi3LsBusCanBaudrate BusBaudRate; + + NodeData Nodes[NumNodes]; + + fmi3UInt64 TxClockCounter; + fmi3UInt64 TxClockResolution; + + bool DiscreteStatesEvaluated; +}; + + +/** + * \brief Enqueues a frame in this queue. + * \param queue The queue. + * \param transmitOp A transmit operation to enqueue. + * \return Whether the frame could be successfully enqueued. + */ +static bool FrameQueue_Enqueue(FrameQueue* queue, const fmi3LsBusCanOperationCanTransmit* transmitOp) +{ + if (FRAME_QUEUE_INCREMENT(queue->WritePos, FrameQueueSize) == queue->ReadPos) + { + return false; + } + + queue->Entries[queue->WritePos].Id = transmitOp->id; + queue->Entries[queue->WritePos].Ide = transmitOp->ide; + queue->Entries[queue->WritePos].Rtr = transmitOp->rtr; + queue->Entries[queue->WritePos].DataLength = transmitOp->dataLength; + + queue->WritePos = FRAME_QUEUE_INCREMENT(queue->WritePos, FrameQueueSize); + + return true; +} + +/** + * \brief Returns the frame at the front of the queue. + * \param queue The queue. + * \return The frame at the front of the queue or NULL, if the queue is empty. + */ +static const FrameQueueEntry* FrameQueue_Peek(const FrameQueue* queue) +{ + if (queue->ReadPos == queue->WritePos) + { + return NULL; + } + + return &queue->Entries[queue->ReadPos]; +} + +/** + * \brief Removes the frame at the front of the queue. + * \param queue The queue. + * \return The frame at the front of the queue or NULL, if the queue is empty. + */ +static const FrameQueueEntry* FrameQueue_Pop(FrameQueue* queue) +{ + if (queue->ReadPos == queue->WritePos) + { + return NULL; + } + + const FrameQueueEntry* entry = &queue->Entries[queue->ReadPos]; + queue->ReadPos = FRAME_QUEUE_INCREMENT(queue->ReadPos, FrameQueueSize); + return entry; +} + + +AppType* App_Instantiate(void) +{ + AppType* app = calloc(1, sizeof(AppType)); + if (app == NULL) + { + return NULL; + } + + // Initialize transmit and receive buffers + for (NodeIdType i = 0; i < NumNodes; i++) + { + FMI3_LS_BUS_BUFFER_INFO_INIT(&app->Nodes[i].RxBufferInfo, app->Nodes[i].RxBuffer, + sizeof(app->Nodes[i].RxBuffer)); + FMI3_LS_BUS_BUFFER_INFO_INIT(&app->Nodes[i].TxBufferInfo, app->Nodes[i].TxBuffer, + sizeof(app->Nodes[i].TxBuffer)); + + app->Nodes[i].TxClockQualifier = fmi3IntervalNotYetKnown; + } + + return app; +} + + +void App_Free(AppType* instance) +{ + free(instance); +} + + +bool App_DoStep(FmuInstance* instance, fmi3Float64 currentTime, fmi3Float64 targetTime) +{ + return false; +} + + +static bool App_GetNextFrame(const FmuInstance* instance, const FrameQueueEntry** nextFrame, NodeIdType* originator) +{ + *nextFrame = NULL; + for (NodeIdType i = 0; i < NumNodes; i++) + { + const FrameQueueEntry* queueTop = FrameQueue_Peek(&instance->App->Nodes[i].FrameQueue); + if (queueTop != NULL && (*nextFrame == NULL || queueTop->Id < (*nextFrame)->Id)) + { + *nextFrame = queueTop; + *originator = i; + } + } + return *nextFrame != NULL; +} + + +void App_EvaluateDiscreteStates(FmuInstance* instance) +{ + if (instance->App->DiscreteStatesEvaluated) + { + return; + } + + // Process received frames for each node + for (NodeIdType i = 0; i < NumNodes; i++) + { + if (instance->App->Nodes[i].RxClock == fmi3ClockActive) + { + // Read all bus operations from the RX buffer + fmi3LsBusOperationHeader* operation = NULL; + while (FMI3_LS_BUS_READ_NEXT_OPERATION(&instance->App->Nodes[i].RxBufferInfo, operation)) + { + if (operation->type == FMI3_LS_BUS_CAN_OP_CAN_TRANSMIT) + { + const fmi3LsBusCanOperationCanTransmit* transmitOp = (fmi3LsBusCanOperationCanTransmit*)operation; + LogFmuMessage(instance, fmi3OK, "Info", "Received CAN frame with ID %u and length %u from node %u", + transmitOp->id, transmitOp->dataLength, i + 1); + FrameQueue_Enqueue(&instance->App->Nodes[0].FrameQueue, transmitOp); + } + else if (operation->type == FMI3_LS_BUS_CAN_OP_CONFIGURATION) + { + const fmi3LsBusOperationCanConfiguration* configOp = (fmi3LsBusOperationCanConfiguration*)operation; + if (configOp->parameterType == FMI3_LS_BUS_CAN_CONFIG_PARAM_TYPE_CAN_BAUDRATE) + { + LogFmuMessage(instance, fmi3OK, "Info", + "Nodes %u configured baud rate %u", i, configOp->baudrate); + instance->App->Nodes[i].BaudRate = configOp->baudrate; + } + } + } + + // Deactivate RX clock and clear RX buffer since all operations should have been processed + instance->App->Nodes[i].RxClock = fmi3ClockInactive; + FMI3_LS_BUS_BUFFER_INFO_RESET(&instance->App->Nodes[i].RxBufferInfo); + } + } + + // Check baud rate configured by nodes and enable bus communication when configured correctly + if (instance->App->Nodes[0].BaudRate > 0 && instance->App->Nodes[1].BaudRate > 0) + { + if (instance->App->Nodes[0].BaudRate == instance->App->Nodes[1].BaudRate) + { + instance->App->BusBaudRate = instance->App->Nodes[0].BaudRate; + } + else + { + LogFmuMessage(instance, fmi3Warning, "Warning", + "Nodes have configured differing baud rates, bus communication is disabled."); + instance->App->BusBaudRate = 0; + } + } + else + { + LogFmuMessage(instance, fmi3OK, "Info", + "Not all nodes have configured a baud rate yet, bus communication is disabled."); + instance->App->BusBaudRate = 0; + } + + // Transmit frames + if (instance->App->Nodes[0].TxClock == fmi3ClockActive && instance->App->Nodes[1].TxClock == fmi3ClockActive) + { + const FrameQueueEntry* nextFrame; + NodeIdType nextFrameOriginator; + if (App_GetNextFrame(instance, &nextFrame, &nextFrameOriginator)) + { + FrameQueue_Pop(&instance->App->Nodes[nextFrameOriginator].FrameQueue); + for (NodeIdType i = 0; i < NumNodes; i++) + { + // The originator of the frame receives a confirm, everyone else receives the frame + if (i == nextFrameOriginator) + { + FMI3_LS_BUS_CAN_CREATE_OP_CONFIRM(&instance->App->Nodes[i].TxBufferInfo, nextFrame->Id); + } + else + { + FMI3_LS_BUS_CAN_CREATE_OP_CAN_TRANSMIT(&instance->App->Nodes[i].TxBufferInfo, nextFrame->Id, + nextFrame->Ide, nextFrame->Rtr, nextFrame->DataLength, + nextFrame->Data); + } + } + } + else + { + LogFmuMessage(instance, fmi3Warning, "Warning", + "TX clock has been triggered but no frames to transmit."); + } + + // Reset the TX clock state + instance->App->Nodes[0].TxClockQualifier = fmi3IntervalNotYetKnown; + instance->App->Nodes[1].TxClockQualifier = fmi3IntervalNotYetKnown; + } + else if (instance->App->Nodes[0].TxClock == fmi3ClockActive || instance->App->Nodes[1].TxClock == fmi3ClockActive) + { + // Since both TX countdown clocks are reporting the same intervals, they should trigger in the same instant + LogFmuMessage(instance, fmi3Error, "Error", + "TX clocks for nodes ticked independently, this is not supported by this FMU."); + } + + // Schedule next transmission + const FrameQueueEntry* nextFrame; + NodeIdType nextFrameOriginator; + if (App_GetNextFrame(instance, &nextFrame, &nextFrameOriginator)) + { + instance->App->Nodes[0].TxClockQualifier = fmi3IntervalChanged; + instance->App->Nodes[1].TxClockQualifier = fmi3IntervalChanged; + instance->App->TxClockCounter = 44 + nextFrame->DataLength; + instance->App->TxClockResolution = instance->App->BusBaudRate; + } + + instance->App->DiscreteStatesEvaluated = true; +} + + +void App_UpdateDiscreteStates(FmuInstance* instance) +{ + if ((instance->App->Nodes[0].TxClock == fmi3ClockActive || instance->App->Nodes[1].TxClock == fmi3ClockActive) && + !instance->App->DiscreteStatesEvaluated) + { + LogFmuMessage(instance, fmi3Warning, "Warning", + "Discrete states should have been evaluated as part of fmi3EvaluateDiscreteStates or fmi3GetBinary"); + } + else if (!instance->App->DiscreteStatesEvaluated) + { + App_EvaluateDiscreteStates(instance); + } + + // Deactivate all TX clocks and clear TX buffers + instance->App->Nodes[0].TxClock = fmi3ClockInactive; + FMI3_LS_BUS_BUFFER_INFO_RESET(&instance->App->Nodes[0].TxBufferInfo); + instance->App->Nodes[1].TxClock = fmi3ClockInactive; + FMI3_LS_BUS_BUFFER_INFO_RESET(&instance->App->Nodes[1].TxBufferInfo); + + instance->App->DiscreteStatesEvaluated = false; +} + + +bool App_SetBoolean(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Boolean value) +{ + return false; +} + + +bool App_SetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary value, size_t valueLength) +{ + if (instance->App->DiscreteStatesEvaluated) + { + LogFmuMessage(instance, fmi3Error, "Error", + "Discrete states have already been evaluated, cannot set input variable"); + return false; + } + + if (valueReference == FMU_VAR_NODE1_RX_DATA) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 1 RX buffer of %llu bytes", valueLength); + FMI3_LS_BUS_BUFFER_WRITE(&instance->App->Nodes[0].RxBufferInfo, value, valueLength); + return true; + } + + if (valueReference == FMU_VAR_NODE2_RX_DATA) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 2 RX buffer of %llu bytes", valueLength); + FMI3_LS_BUS_BUFFER_WRITE(&instance->App->Nodes[1].RxBufferInfo, value, valueLength); + return true; + } + + return false; +} + + +bool App_GetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary* value, size_t* valueLength) +{ + if ((valueReference == FMU_VAR_NODE1_TX_DATA || valueReference == FMU_VAR_NODE2_TX_DATA) && + !instance->App->DiscreteStatesEvaluated) + { + App_EvaluateDiscreteStates(instance); + } + + if (valueReference == FMU_VAR_NODE1_TX_DATA) + { + *value = FMI3_LS_BUS_BUFFER_START(&instance->App->Nodes[0].TxBufferInfo); + *valueLength = FMI3_LS_BUS_BUFFER_LENGTH(&instance->App->Nodes[0].TxBufferInfo); + LogFmuMessage(instance, fmi3OK, "Trace", "Get node 1 TX buffer of %llu bytes", *valueLength); + return true; + } + + if (valueReference == FMU_VAR_NODE2_TX_DATA) + { + *value = FMI3_LS_BUS_BUFFER_START(&instance->App->Nodes[1].TxBufferInfo); + *valueLength = FMI3_LS_BUS_BUFFER_LENGTH(&instance->App->Nodes[1].TxBufferInfo); + LogFmuMessage(instance, fmi3OK, "Trace", "Get node 2 TX buffer of %llu bytes", *valueLength); + return true; + } + + return false; +} + + +bool App_SetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock value) +{ + if (instance->App->DiscreteStatesEvaluated) + { + LogFmuMessage(instance, fmi3Error, "Error", + "Discrete states have already been evaluated, cannot set input variable"); + return false; + } + + if (valueReference == FMU_VAR_NODE1_RX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 1 RX clock to %u", value); + instance->App->Nodes[0].RxClock = value; + return true; + } + + if (valueReference == FMU_VAR_NODE1_TX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 1 TX clock to %u", value); + instance->App->Nodes[0].TxClock = value; + return true; + } + + if (valueReference == FMU_VAR_NODE2_RX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 2 RX clock to %u", value); + instance->App->Nodes[1].RxClock = value; + return true; + } + + if (valueReference == FMU_VAR_NODE2_TX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set node 2 TX clock to %u", value); + instance->App->Nodes[1].TxClock = value; + return true; + } + + return false; +} + + +bool App_GetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock* value) +{ + return false; +} + + +bool App_GetIntervalFraction(FmuInstance* instance, + fmi3ValueReference valueReference, + fmi3UInt64* counter, + fmi3UInt64* resolution, + fmi3IntervalQualifier* qualifier) +{ + if (valueReference == FMU_VAR_NODE1_TX_CLOCK || valueReference == FMU_VAR_NODE2_TX_CLOCK) + { + const NodeIdType node = valueReference == FMU_VAR_NODE1_TX_CLOCK ? 0 : 1; + + *qualifier = instance->App->Nodes[node].TxClockQualifier; + if (instance->App->Nodes[node].TxClockQualifier == fmi3IntervalChanged) + { + *counter = instance->App->TxClockCounter; + *resolution = instance->App->TxClockResolution; + instance->App->Nodes[node].TxClockQualifier = fmi3IntervalUnchanged; + } + return true; + } + + return false; +} diff --git a/ls-bus-guide/demos/can-bus-simulation/src/App.h b/ls-bus-guide/demos/can-bus-simulation/src/App.h new file mode 100644 index 0000000..21cc2c8 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/App.h @@ -0,0 +1,110 @@ +#ifndef APP_H +#define APP_H + +#include + +#include "Instance.h" + + +/** + * \brief Instantiates the FMU application. + * \note Called from fmi3InstantiateCoSimulation. + * \return The instance data of the application. + */ +AppType* App_Instantiate(void); + +/** + * \brief Frees the FMU application. + * \note Called from fmi3FreeInstance. + * \param[in] instance The instance data of the application. + */ +void App_Free(AppType* instance); + +/** + * \brief Advances the simulation time of the FMU application in step mode. + * \note Called from fmi3DoStep. + * \param[in] instance The instance data of the application. + * \param[in] currentTime The time at the start of the step. + * \param[in] targetTime The time at the end of the step. + */ +bool App_DoStep(FmuInstance* instance, fmi3Float64 currentTime, fmi3Float64 targetTime); + +/** + * \brief Evaluates the discrete states of the FMU application in event mode. + * This is used to calculate outputs based on inputs which changed in this super-dense time step. + * \note Called either explicitly from fmi3EvaluateDiscreteStates, or as part of any App_Get* function or + * App_UpdateDiscreteStates, if the required states have not been evaluated yet. + * \param[in] instance The instance data of the application. + */ +void App_EvaluateDiscreteStates(FmuInstance* instance); + +/** + * \brief Signals the end of the current super-dense time step, disabling all clocks. + * \note Called as part of fmi3UpdateDiscreteStates. + * \param[in] instance The instance data of the application. + */ +void App_UpdateDiscreteStates(FmuInstance* instance); + +/** + * \brief Sets the value of a boolean input variable. + * \note Called as part of fmi3SetBoolean. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[in] value The new value of the variable. + */ +bool App_SetBoolean(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Boolean value); + +/** + * \brief Sets the value of a binary input variable. + * \note Called as part of fmi3SetBinary. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[in] value The new value of the variable. + * \param[in] valueLength The new length of the variable. + */ +bool App_SetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary value, size_t valueLength); + +/** + * \brief Gets the value of a binary output variable. + * \note Called as part of fmi3GetBinary. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[out] value The new value of the variable. + * \param[out] valueLength The new length of the variable. + */ +bool App_GetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary* value, size_t* valueLength); + +/** + * \brief Sets the value of an input clock. + * \note Called as part of fmi3SetClock. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[in] value The new value of the clock. + */ +bool App_SetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock value); + +/** + * \brief Sets the value of a triggered output clock. + * \note Called as part of fmi3GetClock. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[out] value The new value of the clock. + */ +bool App_GetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock* value); + +/** + * \brief Gets the current interval of a countdown clock. + * \note Called as part of fmi3GetIntervalFraction and fmi3GetIntervalDecimal. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[out] counter The numerator of the new clock interval. + * \param[out] resolution The denominator of the new clock interval. + * \param[out] qualifier The qualifier of the indicated clock interval. + */ +bool App_GetIntervalFraction(FmuInstance* instance, + fmi3ValueReference valueReference, + fmi3UInt64* counter, + fmi3UInt64* resolution, + fmi3IntervalQualifier* qualifier); + +#endif diff --git a/ls-bus-guide/demos/can-bus-simulation/src/Fmu.c b/ls-bus-guide/demos/can-bus-simulation/src/Fmu.c new file mode 100644 index 0000000..0e04af8 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/Fmu.c @@ -0,0 +1,982 @@ +#include + +#include "FmuIdentifier.h" +#include "fmi3Functions.h" + +#include "App.h" +#include "Instance.h" +#include "Logging.h" + + +/** + * Helper macros + */ + +/** + * Returns fmi3OK when the given value is zero. + * Returns fmi3Error and terminates with an error if the given value is nonzero. + * \param x The value to check. + * \param instance The FMU instance. + */ +#define INVALID_CALL_IF_NONZERO(x, instance) \ + (((x) == 0) ? fmi3OK : TerminateWithError((instance), "%s: Invalid call", __func__)) + +/** + * Returns fmi3Error and terminates with an error stating that the current function is not supported. + * \param instance The FMU instance. + */ +#define ERROR_NOT_SUPPORTED(instance) (TerminateWithError((instance), "%s: Not supported", __func__)) + +/** + * Returns fmiDiscard and emits a warning stating that the current function is not supported. + * \param instance The FMU instance. + */ +#define DISCARD_NOT_SUPPORTED(instance) (LogFmuMessage((instance), fmi3Discard, "logStatusDiscard", "%s: Not supported", __func__), fmi3Discard) + +/** + * \brief Asserts that the FMU is in the given state. + * \param instance The FMU instance. + * \param state The state. + */ +#define ASSERT_FMU_STATE(instance, state) \ + do \ + { \ + if (((FmuInstance*)(instance))->State != (state)) \ + { \ + TerminateWithError(instance, "Must be in state %s to call %s", #state, __func__); \ + return fmi3Error; \ + } \ + } \ + while (0) + +/** + * FMI 3.0 functions - Inquire version numbers and set debug logging + */ + +FMI3_Export const char* fmi3GetVersion(void) +{ + return fmi3Version; +} + + +/** + * FMI 3.0 functions - Creation and destruction of FMU instances + */ + +FMI3_Export fmi3Instance fmi3InstantiateCoSimulation(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3Boolean eventModeUsed, + fmi3Boolean earlyReturnAllowed, + const fmi3ValueReference requiredIntermediateVariables[], + size_t nRequiredIntermediateVariables, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + fmi3IntermediateUpdateCallback intermediateUpdate) +{ + (void)instantiationToken; + (void)resourcePath; + (void)visible; + (void)earlyReturnAllowed; + (void)requiredIntermediateVariables; + (void)nRequiredIntermediateVariables; + (void)intermediateUpdate; + + FmuInstance* fmuInstance = malloc(sizeof(FmuInstance)); + if (fmuInstance == NULL) + { + return NULL; + } + + fmuInstance->InstanceName = _strdup(instanceName); + fmuInstance->InstanceEnvironment = instanceEnvironment; + fmuInstance->LogMessageCallback = NULL; + fmuInstance->State = FMU_STATE_INSTANTIATED; + + if (loggingOn) + { + fmuInstance->LogMessageCallback = logMessage; + } + + if (!eventModeUsed) + { + LogFmuMessage(fmuInstance, fmi3Error, "logStatusError", + "Event mode is must be supported by the importer to use this FMU."); + free(fmuInstance); + return NULL; + } + + fmuInstance->App = App_Instantiate(); + if (fmuInstance->App == NULL) + { + LogFmuMessage(fmuInstance, fmi3Error, "logStatusError", "Instantiation failed."); + free(fmuInstance); + return NULL; + } + + LogFmuMessage(fmuInstance, fmi3OK, "Trace", "Instance %s instantiated.", fmuInstance->InstanceName); + + return fmuInstance; +} + +FMI3_Export void fmi3FreeInstance(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Instance %s freed.", fmuInstance->InstanceName); + + App_Free(fmuInstance->App); + + free(fmuInstance->InstanceName); + free(fmuInstance); +} + + +/** + * FMI 3.0 Functions - Enter and exit initialization mode, terminate and reset + */ + +FMI3_Export fmi3Status fmi3EnterInitializationMode(fmi3Instance instance, + fmi3Boolean toleranceDefined, + fmi3Float64 tolerance, + fmi3Float64 startTime, + fmi3Boolean stopTimeDefined, + fmi3Float64 stopTime) +{ + (void)toleranceDefined; + (void)tolerance; + (void)startTime; + (void)stopTimeDefined; + (void)stopTime; + + ASSERT_FMU_STATE(instance, FMU_STATE_INSTANTIATED); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering initialization mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_INITIALIZATION_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_INITIALIZATION_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering event mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_EVENT_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3EnterEventMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_STEP_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering event mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_EVENT_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3Terminate(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_TERMINATED; + LogFmuMessage(instance, fmi3OK, "Trace", "Terminating."); + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Getting and setting variable values + */ + +FMI3_Export fmi3Status fmi3GetBinary(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + size_t valueSizes[], + fmi3Binary values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3GetBinary: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetBinary(fmuInstance, valueReferences[i], &values[i], &valueSizes[i])) + { + TerminateWithError(instance, "fmi3GetBinary: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3GetClock(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Clock values[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetClock(fmuInstance, valueReferences[i], &values[i])) + { + TerminateWithError(instance, "fmi3GetClock: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3SetBoolean(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Boolean values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3SetBoolean: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetBoolean(fmuInstance, valueReferences[i], values[i])) + { + TerminateWithError(instance, "fmi3SetBoolean: Invalid call with value reference %u and value %u", + valueReferences[i], values[i]); + return fmi3Error; + } + } + + return fmi3Error; +} + +FMI3_Export fmi3Status fmi3SetBinary(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const size_t valueSizes[], + const fmi3Binary values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3SetBinary: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetBinary(fmuInstance, valueReferences[i], values[i], valueSizes[i])) + { + TerminateWithError(instance, "fmi3SetBinary: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3SetClock(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Clock values[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetClock(fmuInstance, valueReferences[i], values[i])) + { + TerminateWithError(instance, "fmi3SetClock: Invalid call with value reference %u and value %u", + valueReferences[i], values[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Entering and exiting the Configuration or Reconfiguration Mode + */ + +FMI3_Export fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_INSTANTIATED); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering configuration mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_CONFIGURATION_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_CONFIGURATION_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Exiting configuration mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_INSTANTIATED; + + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Clock related functions + */ + +FMI3_Export fmi3Status fmi3GetIntervalDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 intervals[], + fmi3IntervalQualifier qualifiers[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + fmi3UInt64 counter; + fmi3UInt64 resolution; + if (!App_GetIntervalFraction(fmuInstance, valueReferences[i], &counter, &resolution, &qualifiers[i])) + { + TerminateWithError(instance, "fmi3GetIntervalFraction: Invalid call with value reference %u", + valueReferences[i]); + return fmi3Error; + } + intervals[i] = (fmi3Float64)counter / (fmi3Float64)resolution; + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3GetIntervalFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 counters[], + fmi3UInt64 resolutions[], + fmi3IntervalQualifier qualifiers[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetIntervalFraction(fmuInstance, valueReferences[i], &counters[i], &resolutions[i], &qualifiers[i])) + { + TerminateWithError(instance, "fmi3GetIntervalFraction: Invalid call with value reference %u", + valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) +{ + App_EvaluateDiscreteStates(instance); + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3UpdateDiscreteStates(fmi3Instance instance, + fmi3Boolean* discreteStatesNeedUpdate, + fmi3Boolean* terminateSimulation, + fmi3Boolean* nominalsOfContinuousStatesChanged, + fmi3Boolean* valuesOfContinuousStatesChanged, + fmi3Boolean* nextEventTimeDefined, + fmi3Float64* nextEventTime) +{ + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Ending super dense time step."); + + App_UpdateDiscreteStates(fmuInstance); + + *discreteStatesNeedUpdate = fmi3False; + *terminateSimulation = fmi3False; + *nominalsOfContinuousStatesChanged = fmi3False; + *valuesOfContinuousStatesChanged = fmi3False; + *nextEventTimeDefined = fmi3False; + *nextEventTime = 0.0; + + return fmi3OK; +} + + +/** + * FMI 3.0 functions for co-simulation - Simulating the FMU + */ + +FMI3_Export fmi3Status fmi3EnterStepMode(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + if (fmuInstance->State != FMU_STATE_EVENT_MODE) + { + TerminateWithError(instance, "Must be in state EVENT_MODE to enter step mode."); + return fmi3Error; + } + + fmuInstance->State = FMU_STATE_STEP_MODE; + LogFmuMessage(instance, fmi3OK, "Trace", "Entering step mode."); + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3DoStep(fmi3Instance instance, + fmi3Float64 currentCommunicationPoint, + fmi3Float64 communicationStepSize, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + fmi3Boolean* eventHandlingNeeded, + fmi3Boolean* terminateSimulation, + fmi3Boolean* earlyReturn, + fmi3Float64* lastSuccessfulTime) +{ + (void)noSetFMUStatePriorToCurrentPoint; + + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Doing step at %f of %f seconds.", currentCommunicationPoint, + communicationStepSize); + + const fmi3Float64 targetTime = currentCommunicationPoint + communicationStepSize; + *eventHandlingNeeded = App_DoStep(fmuInstance, currentCommunicationPoint, targetTime); + *lastSuccessfulTime = targetTime; + + *terminateSimulation = fmi3False; + *earlyReturn = fmi3False; + + return fmi3OK; +} + + +/** + * Unused FMI 3.0 functions - Inquire version numbers and set debug logging + */ + +FMI3_Export fmi3Status fmi3SetDebugLogging(fmi3Instance instance, + fmi3Boolean loggingOn, + size_t nCategories, + const fmi3String categories[]) +{ + return DISCARD_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Creation and destruction of FMU instances + */ + +FMI3_Export fmi3Instance fmi3InstantiateModelExchange(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage) +{ + return NULL; +} + +FMI3_Export fmi3Instance fmi3InstantiateScheduledExecution(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + fmi3ClockUpdateCallback clockUpdate, + fmi3LockPreemptionCallback lockPreemption, + fmi3UnlockPreemptionCallback unlockPreemption) +{ + return NULL; +} + + +/** + * Unused FMI 3.0 functions - Enter and exit initialization mode, terminate and reset + */ + +FMI3_Export fmi3Status fmi3Reset(fmi3Instance instance) +{ + return DISCARD_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting and setting variable values + */ + +FMI3_Export fmi3Status fmi3GetFloat32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetFloat64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetBoolean(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Boolean values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetString(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3String values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetFloat32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetFloat64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetString(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3String values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Getting Variable Dependency Information + */ + +FMI3_Export fmi3Status fmi3GetNumberOfVariableDependencies(fmi3Instance instance, + fmi3ValueReference valueReference, + size_t* nDependencies) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetVariableDependencies(fmi3Instance instance, + fmi3ValueReference dependent, + size_t elementIndicesOfDependent[], + fmi3ValueReference independents[], + size_t elementIndicesOfIndependents[], + fmi3DependencyKind dependencyKinds[], + size_t nDependencies) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting and setting the internal FMU state + */ + +FMI3_Export fmi3Status fmi3GetFMUState(fmi3Instance instance, fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetFMUState(fmi3Instance instance, fmi3FMUState FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3FreeFMUState(fmi3Instance instance, fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SerializedFMUStateSize(fmi3Instance instance, fmi3FMUState FMUState, size_t* size) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SerializeFMUState(fmi3Instance instance, + fmi3FMUState FMUState, + fmi3Byte serializedState[], + size_t size) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3DeserializeFMUState(fmi3Instance instance, + const fmi3Byte serializedState[], + size_t size, + fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting partial derivatives + */ + +FMI3_Export fmi3Status fmi3GetDirectionalDerivative(fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetAdjointDerivative(fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Clock related functions + */ + +FMI3_Export fmi3Status fmi3GetShiftDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 shifts[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetShiftFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 counters[], + fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetIntervalDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 intervals[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetIntervalFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 counters[], + const fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetShiftDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 shifts[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetShiftFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 counters[], + const fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Model Exchange + */ + +FMI3_Export fmi3Status fmi3EnterContinuousTimeMode(fmi3Instance instance) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3CompletedIntegratorStep(fmi3Instance instance, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + fmi3Boolean* enterEventMode, + fmi3Boolean* terminateSimulation) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetTime(fmi3Instance instance, fmi3Float64 time) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetContinuousStates(fmi3Instance instance, + const fmi3Float64 continuousStates[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetContinuousStateDerivatives(fmi3Instance instance, + fmi3Float64 derivatives[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetEventIndicators(fmi3Instance instance, + fmi3Float64 eventIndicators[], + size_t nEventIndicators) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetContinuousStates(fmi3Instance instance, + fmi3Float64 continuousStates[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNominalsOfContinuousStates(fmi3Instance instance, + fmi3Float64 nominals[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNumberOfEventIndicators(fmi3Instance instance, size_t* nEventIndicators) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNumberOfContinuousStates(fmi3Instance instance, size_t* nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Co-Simulation + */ + +FMI3_Export fmi3Status fmi3GetOutputDerivatives(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int32 orders[], + fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Scheduled Execution + */ + +FMI3_Export fmi3Status fmi3ActivateModelPartition(fmi3Instance instance, + fmi3ValueReference clockReference, + fmi3Float64 activationTime) +{ + return ERROR_NOT_SUPPORTED(instance); +} diff --git a/ls-bus-guide/demos/can-bus-simulation/src/FmuIdentifier.h b/ls-bus-guide/demos/can-bus-simulation/src/FmuIdentifier.h new file mode 100644 index 0000000..97785bc --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/FmuIdentifier.h @@ -0,0 +1,6 @@ +#ifndef FMU_IDENTIFIER_H +#define FMU_IDENTIFIER_H + +#define FMI3_FUNCTION_PREFIX DemoCanBusSimulation_ + +#endif diff --git a/ls-bus-guide/demos/can-bus-simulation/src/Instance.h b/ls-bus-guide/demos/can-bus-simulation/src/Instance.h new file mode 100644 index 0000000..ef3c505 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/Instance.h @@ -0,0 +1,43 @@ +#ifndef INSTANCE_H +#define INSTANCE_H + +#include "fmi3FunctionTypes.h" + + +struct AppType; + +/** + * \brief Instance-specific data of this FMU. + */ +typedef struct AppType AppType; + + +/** + * \brief Describes the current state of an FMU instance. + */ +typedef enum +{ + FMU_STATE_INSTANTIATED, + FMU_STATE_INITIALIZATION_MODE, + FMU_STATE_CONFIGURATION_MODE, + FMU_STATE_STEP_MODE, + FMU_STATE_EVENT_MODE, + FMU_STATE_TERMINATED +} FmuState; + + +/** + * \brief Instance-specific data of any FMU. + */ +typedef struct +{ + char* InstanceName; + void* InstanceEnvironment; + fmi3LogMessageCallback LogMessageCallback; + FmuState State; + + AppType* App; +} FmuInstance; + + +#endif diff --git a/ls-bus-guide/demos/can-bus-simulation/src/Logging.c b/ls-bus-guide/demos/can-bus-simulation/src/Logging.c new file mode 100644 index 0000000..644830b --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/Logging.c @@ -0,0 +1,66 @@ +#include "Logging.h" + +#include +#include + +#include "Instance.h" + + +enum { LOG_BUFFER_SIZE = 4096 }; + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +void LogFmuMessageV(fmi3Instance instance, + fmi3Status status, + fmi3String category, + fmi3String message, + va_list args) +{ + const FmuInstance* fmuInstance = instance; + if (!fmuInstance->LogMessageCallback) + { + return; + } + + fmi3Char buffer[LOG_BUFFER_SIZE]; + vsnprintf(buffer, LOG_BUFFER_SIZE, message, args); + + fmuInstance->LogMessageCallback(fmuInstance->InstanceEnvironment, status, category, buffer); +} + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +void LogFmuMessage(fmi3Instance instance, + fmi3Status status, + fmi3String category, + fmi3String message, + ...) +{ + va_list args; + va_start(args, message); + LogFmuMessageV(instance, status, category, message, args); + va_end(args); +} + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +fmi3Status TerminateWithError(fmi3Instance instance, + fmi3String message, + ...) +{ + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_TERMINATED; + + va_list args; + va_start(args, message); + LogFmuMessageV(instance, fmi3Error, "logStatusError", message, args); + va_end(args); + + return fmi3Error; +} diff --git a/ls-bus-guide/demos/can-bus-simulation/src/Logging.h b/ls-bus-guide/demos/can-bus-simulation/src/Logging.h new file mode 100644 index 0000000..df1604b --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/src/Logging.h @@ -0,0 +1,12 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include "fmi3FunctionTypes.h" + + +void LogFmuMessage(fmi3Instance instance, fmi3Status status, fmi3String category, fmi3String message, ...); + +fmi3Status TerminateWithError(fmi3Instance instance, fmi3String message, ...); + + +#endif diff --git a/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py b/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py new file mode 100644 index 0000000..b6803c2 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py @@ -0,0 +1,41 @@ +import urllib.request +import zipfile + +from pathlib import Path + + +# Output FMU path +FMU_PATH = Path('DemoCanNodeTriggeredOutput.fmu') + +# Repository to fetch the LS-BUS headers from +LS_BUS_REPO = 'modelica/fmi-ls-bus' +LS_BUS_REV = '473bd5b80730c47373bf41f1c31d44f50de82dd0' +LS_BUS_HEADERS = [ 'fmi3LsBus.h', 'fmi3LsBusCan.h' ] + + +def main(): + demo_dir = Path(__file__).parent + + with zipfile.ZipFile(FMU_PATH, 'w') as fmu: + # Add LS-BUS headers from GitHub repository + for ls_bus_header in LS_BUS_HEADERS: + with urllib.request.urlopen(f'https://raw.githubusercontent.com/{LS_BUS_REPO}/{LS_BUS_REV}/headers/{ls_bus_header}') as f: + fmu.writestr(f'sources/{ls_bus_header}', f.read()) + + # Add LS-BUS utility headers + for file in (demo_dir.parent.parent / 'headers').iterdir(): + fmu.write(file, f'sources/{file.name}') + + # Add source files + for file in (demo_dir / 'src').iterdir(): + fmu.write(file, f'sources/{file.name}') + + # Add description files + fmu.write(demo_dir / 'description' / 'modelDescription.xml', 'modelDescription.xml') + fmu.write(demo_dir / 'description' / 'buildDescription.xml', 'sources/buildDescription.xml') + fmu.write(demo_dir / 'description' / 'terminalsAndIcons.xml', 'terminalsAndIcons/terminalsAndIcons.xml') + fmu.write(demo_dir / 'description' / 'fmi-ls-manifest.xml', 'extra/org.fmi-standard.fmi-ls-bus/fmi-ls-manifest.xml') + + +if __name__ == '__main__': + main() diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml b/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml new file mode 100644 index 0000000..d3673f4 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml b/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml new file mode 100644 index 0000000..a5a9253 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml @@ -0,0 +1,8 @@ + + diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml b/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml new file mode 100644 index 0000000..735b0a1 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/terminalsAndIcons.xml b/ls-bus-guide/demos/can-node-triggered-output/description/terminalsAndIcons.xml new file mode 100644 index 0000000..460a175 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/description/terminalsAndIcons.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/App.c b/ls-bus-guide/demos/can-node-triggered-output/src/App.c new file mode 100644 index 0000000..a36d799 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/App.c @@ -0,0 +1,235 @@ +#include "App.h" + +#include + +#include "fmi3LsBusUtilCan.h" + +#include "Logging.h" + + +/** + * \brief Identifies variables of this FMU with their value references. + */ +typedef enum +{ + FMU_VAR_RX_DATA = 0, + FMU_VAR_TX_DATA = 1, + FMU_VAR_RX_CLOCK = 2, + FMU_VAR_TX_CLOCK = 3, + + FMU_VAR_CAN_BUS_NOTIFICATIONS = 128 +} FmuVariables; + + +static const fmi3Float64 TransmitInterval = 0.3; + + +/** + * \brief Instance-specific data of this FMU. + */ +struct AppType +{ + fmi3Byte RxBuffer[2048]; + fmi3LsBusUtilBufferInfo RxBufferInfo; + fmi3Clock RxClock; + + fmi3Byte TxBuffer[2048]; + fmi3LsBusUtilBufferInfo TxBufferInfo; + fmi3Clock TxClock; + + fmi3Float64 NextTransmitTime; +}; + + +AppType* App_Instantiate(void) +{ + AppType* app = calloc(1, sizeof(AppType)); + if (app == NULL) + { + return NULL; + } + + // Initialize transmit and receive buffers + FMI3_LS_BUS_BUFFER_INFO_INIT(&app->RxBufferInfo, app->RxBuffer, sizeof(app->RxBuffer)); + FMI3_LS_BUS_BUFFER_INFO_INIT(&app->TxBufferInfo, app->TxBuffer, sizeof(app->TxBuffer)); + + // Create bus configuration operations + FMI3_LS_BUS_CAN_CREATE_OP_CONFIGURATION_CAN_BAUDRATE(&app->TxBufferInfo, 100000); + FMI3_LS_BUS_CAN_CREATE_OP_CONFIGURATION_ARBITRATION_LOST_BEHAVIOR( + &app->TxBufferInfo, + FMI3_LS_BUS_CAN_CONFIG_PARAM_ARBITRATION_LOST_BEHAVIOR_BUFFER_AND_RETRANSMIT); + app->TxClock = fmi3ClockActive; + + // Schedule next transmission + app->NextTransmitTime = TransmitInterval; + + return app; +} + + +void App_Free(AppType* instance) +{ + free(instance); +} + + +bool App_DoStep(FmuInstance* instance, fmi3Float64 currentTime, fmi3Float64 targetTime) +{ + // Send transmit operations with the given interval until we reach the target time + while (instance->App->NextTransmitTime <= targetTime) + { + // We are transmitting with a constant CAN ID and payload + const fmi3LsBusCanId id = 0x1; + const fmi3Byte data[4] = {1, 2, 3, 4}; + + LogFmuMessage(instance, fmi3OK, "Info", "Transmitting CAN frame with ID %u at internal time %f", id, + instance->App->NextTransmitTime); + + // Create a CAN transmit operation + FMI3_LS_BUS_CAN_CREATE_OP_CAN_TRANSMIT(&instance->App->TxBufferInfo, id, 0, 0, sizeof data, data); + + // Check that operation was created successfully + if (!instance->App->TxBufferInfo.status) + { + LogFmuMessage(instance, fmi3Warning, "Warning", "Failed to transmit CAN frame: Insufficient buffer space"); + break; + } + + // Schedule next transmission + instance->App->NextTransmitTime = instance->App->NextTransmitTime + TransmitInterval; + } + + // If we create bus transmit operations, we have to enter event mode and set the TX clock after this step + if (FMI3_LS_BUS_BUFFER_LENGTH(&instance->App->TxBufferInfo) > 0) + { + instance->App->TxClock = fmi3ClockActive; + return true; + } + + return false; +} + + +void App_EvaluateDiscreteStates(FmuInstance* instance) +{ + (void)instance; +} + + +void App_UpdateDiscreteStates(FmuInstance* instance) +{ + // We only process bus operations when the RX clock is set, otherwise the contents of the buffer are not well-defined + if (instance->App->RxClock == fmi3ClockActive) + { + // Read all bus operations from the RX buffer + fmi3LsBusOperationHeader* operation = NULL; + while (FMI3_LS_BUS_READ_NEXT_OPERATION(&instance->App->RxBufferInfo, operation)) + { + if (operation->type == FMI3_LS_BUS_CAN_OP_CAN_TRANSMIT) + { + const fmi3LsBusCanOperationCanTransmit* transmitOp = (fmi3LsBusCanOperationCanTransmit*)operation; + LogFmuMessage(instance, fmi3OK, "Info", "Received CAN frame with ID %u and length %u", transmitOp->id, + transmitOp->dataLength); + } + else if (operation->type == FMI3_LS_BUS_CAN_OP_CONFIGURATION || + operation->type == FMI3_LS_BUS_CAN_OP_STATUS || + operation->type == FMI3_LS_BUS_CAN_OP_CONFIRM || + operation->type == FMI3_LS_BUS_CAN_OP_CAN_BUS_ERROR || + operation->type == FMI3_LS_BUS_CAN_OP_ARBITRATION_LOST) + { + // Ignore + } + else + { + LogFmuMessage(instance, fmi3OK, "Warning", "Received unknown bus operation"); + } + } + } + + // Deactivate RX clock and clear RX buffer since all operations should have been processed + instance->App->RxClock = fmi3ClockInactive; + FMI3_LS_BUS_BUFFER_INFO_RESET(&instance->App->RxBufferInfo); + + // Deactivate TX clock and clear TX buffer since both should have been retrieved by this time + instance->App->TxClock = fmi3ClockInactive; + FMI3_LS_BUS_BUFFER_INFO_RESET(&instance->App->TxBufferInfo); +} + + +bool App_SetBoolean(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Boolean value) +{ + if (valueReference == FMU_VAR_CAN_BUS_NOTIFICATIONS) + { + LogFmuMessage(instance, fmi3OK, "Info", "Set Can_BusNotifications to %u", value); + return true; + } + + return false; +} + + +bool App_SetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary value, size_t valueLength) +{ + if (valueReference == FMU_VAR_RX_DATA) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set RX buffer of %llu bytes", valueLength); + FMI3_LS_BUS_BUFFER_WRITE(&instance->App->RxBufferInfo, value, valueLength); + return true; + } + + return false; +} + + +bool App_GetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary* value, size_t* valueLength) +{ + if (valueReference == FMU_VAR_TX_DATA) + { + *value = FMI3_LS_BUS_BUFFER_START(&instance->App->TxBufferInfo); + *valueLength = FMI3_LS_BUS_BUFFER_LENGTH(&instance->App->TxBufferInfo); + LogFmuMessage(instance, fmi3OK, "Trace", "Get TX buffer of %llu bytes", *valueLength); + return true; + } + + return false; +} + + +bool App_SetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock value) +{ + if (valueReference == FMU_VAR_RX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Set RX clock to %u", value); + instance->App->RxClock = value; + return true; + } + + return false; +} + + +bool App_GetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock* value) +{ + if (valueReference == FMU_VAR_TX_CLOCK) + { + LogFmuMessage(instance, fmi3OK, "Trace", "Get TX clock of %d", instance->App->TxClock); + *value = instance->App->TxClock; + + // Reset clock since GetClock may only return fmi3ClockActive once per activation + instance->App->TxClock = fmi3ClockInactive; + + return true; + } + + return false; +} + + +bool App_GetIntervalFraction(FmuInstance* instance, + fmi3ValueReference valueReference, + fmi3UInt64* counter, + fmi3UInt64* resolution, + fmi3IntervalQualifier* qualifier) +{ + return false; +} diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/App.h b/ls-bus-guide/demos/can-node-triggered-output/src/App.h new file mode 100644 index 0000000..21cc2c8 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/App.h @@ -0,0 +1,110 @@ +#ifndef APP_H +#define APP_H + +#include + +#include "Instance.h" + + +/** + * \brief Instantiates the FMU application. + * \note Called from fmi3InstantiateCoSimulation. + * \return The instance data of the application. + */ +AppType* App_Instantiate(void); + +/** + * \brief Frees the FMU application. + * \note Called from fmi3FreeInstance. + * \param[in] instance The instance data of the application. + */ +void App_Free(AppType* instance); + +/** + * \brief Advances the simulation time of the FMU application in step mode. + * \note Called from fmi3DoStep. + * \param[in] instance The instance data of the application. + * \param[in] currentTime The time at the start of the step. + * \param[in] targetTime The time at the end of the step. + */ +bool App_DoStep(FmuInstance* instance, fmi3Float64 currentTime, fmi3Float64 targetTime); + +/** + * \brief Evaluates the discrete states of the FMU application in event mode. + * This is used to calculate outputs based on inputs which changed in this super-dense time step. + * \note Called either explicitly from fmi3EvaluateDiscreteStates, or as part of any App_Get* function or + * App_UpdateDiscreteStates, if the required states have not been evaluated yet. + * \param[in] instance The instance data of the application. + */ +void App_EvaluateDiscreteStates(FmuInstance* instance); + +/** + * \brief Signals the end of the current super-dense time step, disabling all clocks. + * \note Called as part of fmi3UpdateDiscreteStates. + * \param[in] instance The instance data of the application. + */ +void App_UpdateDiscreteStates(FmuInstance* instance); + +/** + * \brief Sets the value of a boolean input variable. + * \note Called as part of fmi3SetBoolean. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[in] value The new value of the variable. + */ +bool App_SetBoolean(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Boolean value); + +/** + * \brief Sets the value of a binary input variable. + * \note Called as part of fmi3SetBinary. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[in] value The new value of the variable. + * \param[in] valueLength The new length of the variable. + */ +bool App_SetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary value, size_t valueLength); + +/** + * \brief Gets the value of a binary output variable. + * \note Called as part of fmi3GetBinary. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the variable. + * \param[out] value The new value of the variable. + * \param[out] valueLength The new length of the variable. + */ +bool App_GetBinary(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Binary* value, size_t* valueLength); + +/** + * \brief Sets the value of an input clock. + * \note Called as part of fmi3SetClock. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[in] value The new value of the clock. + */ +bool App_SetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock value); + +/** + * \brief Sets the value of a triggered output clock. + * \note Called as part of fmi3GetClock. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[out] value The new value of the clock. + */ +bool App_GetClock(FmuInstance* instance, fmi3ValueReference valueReference, fmi3Clock* value); + +/** + * \brief Gets the current interval of a countdown clock. + * \note Called as part of fmi3GetIntervalFraction and fmi3GetIntervalDecimal. + * \param[in] instance The instance data of the application. + * \param[in] valueReference The value reference of the clock. + * \param[out] counter The numerator of the new clock interval. + * \param[out] resolution The denominator of the new clock interval. + * \param[out] qualifier The qualifier of the indicated clock interval. + */ +bool App_GetIntervalFraction(FmuInstance* instance, + fmi3ValueReference valueReference, + fmi3UInt64* counter, + fmi3UInt64* resolution, + fmi3IntervalQualifier* qualifier); + +#endif diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/Fmu.c b/ls-bus-guide/demos/can-node-triggered-output/src/Fmu.c new file mode 100644 index 0000000..0e04af8 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/Fmu.c @@ -0,0 +1,982 @@ +#include + +#include "FmuIdentifier.h" +#include "fmi3Functions.h" + +#include "App.h" +#include "Instance.h" +#include "Logging.h" + + +/** + * Helper macros + */ + +/** + * Returns fmi3OK when the given value is zero. + * Returns fmi3Error and terminates with an error if the given value is nonzero. + * \param x The value to check. + * \param instance The FMU instance. + */ +#define INVALID_CALL_IF_NONZERO(x, instance) \ + (((x) == 0) ? fmi3OK : TerminateWithError((instance), "%s: Invalid call", __func__)) + +/** + * Returns fmi3Error and terminates with an error stating that the current function is not supported. + * \param instance The FMU instance. + */ +#define ERROR_NOT_SUPPORTED(instance) (TerminateWithError((instance), "%s: Not supported", __func__)) + +/** + * Returns fmiDiscard and emits a warning stating that the current function is not supported. + * \param instance The FMU instance. + */ +#define DISCARD_NOT_SUPPORTED(instance) (LogFmuMessage((instance), fmi3Discard, "logStatusDiscard", "%s: Not supported", __func__), fmi3Discard) + +/** + * \brief Asserts that the FMU is in the given state. + * \param instance The FMU instance. + * \param state The state. + */ +#define ASSERT_FMU_STATE(instance, state) \ + do \ + { \ + if (((FmuInstance*)(instance))->State != (state)) \ + { \ + TerminateWithError(instance, "Must be in state %s to call %s", #state, __func__); \ + return fmi3Error; \ + } \ + } \ + while (0) + +/** + * FMI 3.0 functions - Inquire version numbers and set debug logging + */ + +FMI3_Export const char* fmi3GetVersion(void) +{ + return fmi3Version; +} + + +/** + * FMI 3.0 functions - Creation and destruction of FMU instances + */ + +FMI3_Export fmi3Instance fmi3InstantiateCoSimulation(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3Boolean eventModeUsed, + fmi3Boolean earlyReturnAllowed, + const fmi3ValueReference requiredIntermediateVariables[], + size_t nRequiredIntermediateVariables, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + fmi3IntermediateUpdateCallback intermediateUpdate) +{ + (void)instantiationToken; + (void)resourcePath; + (void)visible; + (void)earlyReturnAllowed; + (void)requiredIntermediateVariables; + (void)nRequiredIntermediateVariables; + (void)intermediateUpdate; + + FmuInstance* fmuInstance = malloc(sizeof(FmuInstance)); + if (fmuInstance == NULL) + { + return NULL; + } + + fmuInstance->InstanceName = _strdup(instanceName); + fmuInstance->InstanceEnvironment = instanceEnvironment; + fmuInstance->LogMessageCallback = NULL; + fmuInstance->State = FMU_STATE_INSTANTIATED; + + if (loggingOn) + { + fmuInstance->LogMessageCallback = logMessage; + } + + if (!eventModeUsed) + { + LogFmuMessage(fmuInstance, fmi3Error, "logStatusError", + "Event mode is must be supported by the importer to use this FMU."); + free(fmuInstance); + return NULL; + } + + fmuInstance->App = App_Instantiate(); + if (fmuInstance->App == NULL) + { + LogFmuMessage(fmuInstance, fmi3Error, "logStatusError", "Instantiation failed."); + free(fmuInstance); + return NULL; + } + + LogFmuMessage(fmuInstance, fmi3OK, "Trace", "Instance %s instantiated.", fmuInstance->InstanceName); + + return fmuInstance; +} + +FMI3_Export void fmi3FreeInstance(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Instance %s freed.", fmuInstance->InstanceName); + + App_Free(fmuInstance->App); + + free(fmuInstance->InstanceName); + free(fmuInstance); +} + + +/** + * FMI 3.0 Functions - Enter and exit initialization mode, terminate and reset + */ + +FMI3_Export fmi3Status fmi3EnterInitializationMode(fmi3Instance instance, + fmi3Boolean toleranceDefined, + fmi3Float64 tolerance, + fmi3Float64 startTime, + fmi3Boolean stopTimeDefined, + fmi3Float64 stopTime) +{ + (void)toleranceDefined; + (void)tolerance; + (void)startTime; + (void)stopTimeDefined; + (void)stopTime; + + ASSERT_FMU_STATE(instance, FMU_STATE_INSTANTIATED); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering initialization mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_INITIALIZATION_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_INITIALIZATION_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering event mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_EVENT_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3EnterEventMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_STEP_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering event mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_EVENT_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3Terminate(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_TERMINATED; + LogFmuMessage(instance, fmi3OK, "Trace", "Terminating."); + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Getting and setting variable values + */ + +FMI3_Export fmi3Status fmi3GetBinary(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + size_t valueSizes[], + fmi3Binary values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3GetBinary: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetBinary(fmuInstance, valueReferences[i], &values[i], &valueSizes[i])) + { + TerminateWithError(instance, "fmi3GetBinary: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3GetClock(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Clock values[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetClock(fmuInstance, valueReferences[i], &values[i])) + { + TerminateWithError(instance, "fmi3GetClock: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3SetBoolean(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Boolean values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3SetBoolean: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetBoolean(fmuInstance, valueReferences[i], values[i])) + { + TerminateWithError(instance, "fmi3SetBoolean: Invalid call with value reference %u and value %u", + valueReferences[i], values[i]); + return fmi3Error; + } + } + + return fmi3Error; +} + +FMI3_Export fmi3Status fmi3SetBinary(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const size_t valueSizes[], + const fmi3Binary values[], + size_t nValues) +{ + FmuInstance* fmuInstance = instance; + + if (nValueReferences != nValues) + { + TerminateWithError(instance, "fmi3SetBinary: Invalid call"); + return fmi3Error; + } + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetBinary(fmuInstance, valueReferences[i], values[i], valueSizes[i])) + { + TerminateWithError(instance, "fmi3SetBinary: Invalid call with value reference %u", valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3SetClock(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Clock values[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_SetClock(fmuInstance, valueReferences[i], values[i])) + { + TerminateWithError(instance, "fmi3SetClock: Invalid call with value reference %u and value %u", + valueReferences[i], values[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Entering and exiting the Configuration or Reconfiguration Mode + */ + +FMI3_Export fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_INSTANTIATED); + + LogFmuMessage(instance, fmi3OK, "Trace", "Entering configuration mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_CONFIGURATION_MODE; + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) +{ + ASSERT_FMU_STATE(instance, FMU_STATE_CONFIGURATION_MODE); + + LogFmuMessage(instance, fmi3OK, "Trace", "Exiting configuration mode."); + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_INSTANTIATED; + + return fmi3OK; +} + + +/** + * FMI 3.0 functions - Clock related functions + */ + +FMI3_Export fmi3Status fmi3GetIntervalDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 intervals[], + fmi3IntervalQualifier qualifiers[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + fmi3UInt64 counter; + fmi3UInt64 resolution; + if (!App_GetIntervalFraction(fmuInstance, valueReferences[i], &counter, &resolution, &qualifiers[i])) + { + TerminateWithError(instance, "fmi3GetIntervalFraction: Invalid call with value reference %u", + valueReferences[i]); + return fmi3Error; + } + intervals[i] = (fmi3Float64)counter / (fmi3Float64)resolution; + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3GetIntervalFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 counters[], + fmi3UInt64 resolutions[], + fmi3IntervalQualifier qualifiers[]) +{ + FmuInstance* fmuInstance = instance; + + for (size_t i = 0; i < nValueReferences; i++) + { + if (!App_GetIntervalFraction(fmuInstance, valueReferences[i], &counters[i], &resolutions[i], &qualifiers[i])) + { + TerminateWithError(instance, "fmi3GetIntervalFraction: Invalid call with value reference %u", + valueReferences[i]); + return fmi3Error; + } + } + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) +{ + App_EvaluateDiscreteStates(instance); + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3UpdateDiscreteStates(fmi3Instance instance, + fmi3Boolean* discreteStatesNeedUpdate, + fmi3Boolean* terminateSimulation, + fmi3Boolean* nominalsOfContinuousStatesChanged, + fmi3Boolean* valuesOfContinuousStatesChanged, + fmi3Boolean* nextEventTimeDefined, + fmi3Float64* nextEventTime) +{ + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Ending super dense time step."); + + App_UpdateDiscreteStates(fmuInstance); + + *discreteStatesNeedUpdate = fmi3False; + *terminateSimulation = fmi3False; + *nominalsOfContinuousStatesChanged = fmi3False; + *valuesOfContinuousStatesChanged = fmi3False; + *nextEventTimeDefined = fmi3False; + *nextEventTime = 0.0; + + return fmi3OK; +} + + +/** + * FMI 3.0 functions for co-simulation - Simulating the FMU + */ + +FMI3_Export fmi3Status fmi3EnterStepMode(fmi3Instance instance) +{ + FmuInstance* fmuInstance = instance; + if (fmuInstance->State != FMU_STATE_EVENT_MODE) + { + TerminateWithError(instance, "Must be in state EVENT_MODE to enter step mode."); + return fmi3Error; + } + + fmuInstance->State = FMU_STATE_STEP_MODE; + LogFmuMessage(instance, fmi3OK, "Trace", "Entering step mode."); + + return fmi3OK; +} + +FMI3_Export fmi3Status fmi3DoStep(fmi3Instance instance, + fmi3Float64 currentCommunicationPoint, + fmi3Float64 communicationStepSize, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + fmi3Boolean* eventHandlingNeeded, + fmi3Boolean* terminateSimulation, + fmi3Boolean* earlyReturn, + fmi3Float64* lastSuccessfulTime) +{ + (void)noSetFMUStatePriorToCurrentPoint; + + FmuInstance* fmuInstance = instance; + + LogFmuMessage(instance, fmi3OK, "Trace", "Doing step at %f of %f seconds.", currentCommunicationPoint, + communicationStepSize); + + const fmi3Float64 targetTime = currentCommunicationPoint + communicationStepSize; + *eventHandlingNeeded = App_DoStep(fmuInstance, currentCommunicationPoint, targetTime); + *lastSuccessfulTime = targetTime; + + *terminateSimulation = fmi3False; + *earlyReturn = fmi3False; + + return fmi3OK; +} + + +/** + * Unused FMI 3.0 functions - Inquire version numbers and set debug logging + */ + +FMI3_Export fmi3Status fmi3SetDebugLogging(fmi3Instance instance, + fmi3Boolean loggingOn, + size_t nCategories, + const fmi3String categories[]) +{ + return DISCARD_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Creation and destruction of FMU instances + */ + +FMI3_Export fmi3Instance fmi3InstantiateModelExchange(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage) +{ + return NULL; +} + +FMI3_Export fmi3Instance fmi3InstantiateScheduledExecution(fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourcePath, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + fmi3ClockUpdateCallback clockUpdate, + fmi3LockPreemptionCallback lockPreemption, + fmi3UnlockPreemptionCallback unlockPreemption) +{ + return NULL; +} + + +/** + * Unused FMI 3.0 functions - Enter and exit initialization mode, terminate and reset + */ + +FMI3_Export fmi3Status fmi3Reset(fmi3Instance instance) +{ + return DISCARD_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting and setting variable values + */ + +FMI3_Export fmi3Status fmi3GetFloat32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetFloat64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Int64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetUInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetBoolean(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Boolean values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetString(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3String values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetFloat32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetFloat64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt8(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt8 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt16(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt16 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt32(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt32 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetUInt64(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetString(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3String values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Getting Variable Dependency Information + */ + +FMI3_Export fmi3Status fmi3GetNumberOfVariableDependencies(fmi3Instance instance, + fmi3ValueReference valueReference, + size_t* nDependencies) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetVariableDependencies(fmi3Instance instance, + fmi3ValueReference dependent, + size_t elementIndicesOfDependent[], + fmi3ValueReference independents[], + size_t elementIndicesOfIndependents[], + fmi3DependencyKind dependencyKinds[], + size_t nDependencies) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting and setting the internal FMU state + */ + +FMI3_Export fmi3Status fmi3GetFMUState(fmi3Instance instance, fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetFMUState(fmi3Instance instance, fmi3FMUState FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3FreeFMUState(fmi3Instance instance, fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SerializedFMUStateSize(fmi3Instance instance, fmi3FMUState FMUState, size_t* size) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SerializeFMUState(fmi3Instance instance, + fmi3FMUState FMUState, + fmi3Byte serializedState[], + size_t size) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3DeserializeFMUState(fmi3Instance instance, + const fmi3Byte serializedState[], + size_t size, + fmi3FMUState* FMUState) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Getting partial derivatives + */ + +FMI3_Export fmi3Status fmi3GetDirectionalDerivative(fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetAdjointDerivative(fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Clock related functions + */ + +FMI3_Export fmi3Status fmi3GetShiftDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3Float64 shifts[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3GetShiftFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + fmi3UInt64 counters[], + fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetIntervalDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 intervals[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetIntervalFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 counters[], + const fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetShiftDecimal(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Float64 shifts[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + +FMI3_Export fmi3Status fmi3SetShiftFraction(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3UInt64 counters[], + const fmi3UInt64 resolutions[]) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Model Exchange + */ + +FMI3_Export fmi3Status fmi3EnterContinuousTimeMode(fmi3Instance instance) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3CompletedIntegratorStep(fmi3Instance instance, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + fmi3Boolean* enterEventMode, + fmi3Boolean* terminateSimulation) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetTime(fmi3Instance instance, fmi3Float64 time) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3SetContinuousStates(fmi3Instance instance, + const fmi3Float64 continuousStates[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetContinuousStateDerivatives(fmi3Instance instance, + fmi3Float64 derivatives[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetEventIndicators(fmi3Instance instance, + fmi3Float64 eventIndicators[], + size_t nEventIndicators) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetContinuousStates(fmi3Instance instance, + fmi3Float64 continuousStates[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNominalsOfContinuousStates(fmi3Instance instance, + fmi3Float64 nominals[], + size_t nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNumberOfEventIndicators(fmi3Instance instance, size_t* nEventIndicators) +{ + return ERROR_NOT_SUPPORTED(instance); +} + +FMI3_Export fmi3Status fmi3GetNumberOfContinuousStates(fmi3Instance instance, size_t* nContinuousStates) +{ + return ERROR_NOT_SUPPORTED(instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Co-Simulation + */ + +FMI3_Export fmi3Status fmi3GetOutputDerivatives(fmi3Instance instance, + const fmi3ValueReference valueReferences[], + size_t nValueReferences, + const fmi3Int32 orders[], + fmi3Float64 values[], + size_t nValues) +{ + return INVALID_CALL_IF_NONZERO(nValueReferences, instance); +} + + +/** + * Unused FMI 3.0 functions - Functions for Scheduled Execution + */ + +FMI3_Export fmi3Status fmi3ActivateModelPartition(fmi3Instance instance, + fmi3ValueReference clockReference, + fmi3Float64 activationTime) +{ + return ERROR_NOT_SUPPORTED(instance); +} diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/FmuIdentifier.h b/ls-bus-guide/demos/can-node-triggered-output/src/FmuIdentifier.h new file mode 100644 index 0000000..29cf654 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/FmuIdentifier.h @@ -0,0 +1,6 @@ +#ifndef FMU_IDENTIFIER_H +#define FMU_IDENTIFIER_H + +#define FMI3_FUNCTION_PREFIX DemoCanNodeTriggeredOutput_ + +#endif diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/Instance.h b/ls-bus-guide/demos/can-node-triggered-output/src/Instance.h new file mode 100644 index 0000000..ef3c505 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/Instance.h @@ -0,0 +1,43 @@ +#ifndef INSTANCE_H +#define INSTANCE_H + +#include "fmi3FunctionTypes.h" + + +struct AppType; + +/** + * \brief Instance-specific data of this FMU. + */ +typedef struct AppType AppType; + + +/** + * \brief Describes the current state of an FMU instance. + */ +typedef enum +{ + FMU_STATE_INSTANTIATED, + FMU_STATE_INITIALIZATION_MODE, + FMU_STATE_CONFIGURATION_MODE, + FMU_STATE_STEP_MODE, + FMU_STATE_EVENT_MODE, + FMU_STATE_TERMINATED +} FmuState; + + +/** + * \brief Instance-specific data of any FMU. + */ +typedef struct +{ + char* InstanceName; + void* InstanceEnvironment; + fmi3LogMessageCallback LogMessageCallback; + FmuState State; + + AppType* App; +} FmuInstance; + + +#endif diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/Logging.c b/ls-bus-guide/demos/can-node-triggered-output/src/Logging.c new file mode 100644 index 0000000..644830b --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/Logging.c @@ -0,0 +1,66 @@ +#include "Logging.h" + +#include +#include + +#include "Instance.h" + + +enum { LOG_BUFFER_SIZE = 4096 }; + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +void LogFmuMessageV(fmi3Instance instance, + fmi3Status status, + fmi3String category, + fmi3String message, + va_list args) +{ + const FmuInstance* fmuInstance = instance; + if (!fmuInstance->LogMessageCallback) + { + return; + } + + fmi3Char buffer[LOG_BUFFER_SIZE]; + vsnprintf(buffer, LOG_BUFFER_SIZE, message, args); + + fmuInstance->LogMessageCallback(fmuInstance->InstanceEnvironment, status, category, buffer); +} + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +void LogFmuMessage(fmi3Instance instance, + fmi3Status status, + fmi3String category, + fmi3String message, + ...) +{ + va_list args; + va_start(args, message); + LogFmuMessageV(instance, status, category, message, args); + va_end(args); +} + + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((__format__(__printf__, 4, 5)) +#endif +fmi3Status TerminateWithError(fmi3Instance instance, + fmi3String message, + ...) +{ + FmuInstance* fmuInstance = instance; + fmuInstance->State = FMU_STATE_TERMINATED; + + va_list args; + va_start(args, message); + LogFmuMessageV(instance, fmi3Error, "logStatusError", message, args); + va_end(args); + + return fmi3Error; +} diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/Logging.h b/ls-bus-guide/demos/can-node-triggered-output/src/Logging.h new file mode 100644 index 0000000..df1604b --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/src/Logging.h @@ -0,0 +1,12 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include "fmi3FunctionTypes.h" + + +void LogFmuMessage(fmi3Instance instance, fmi3Status status, fmi3String category, fmi3String message, ...); + +fmi3Status TerminateWithError(fmi3Instance instance, fmi3String message, ...); + + +#endif From 3dca381aec352c14e5ab03446fa9fe679babf1b8 Mon Sep 17 00:00:00 2001 From: Clemens Boos Date: Mon, 27 Nov 2023 14:06:51 +0100 Subject: [PATCH 2/6] Update CAN demo FMUs for updated headers --- ls-bus-guide/demos/can-bus-simulation/src/App.c | 2 +- ls-bus-guide/demos/can-node-triggered-output/src/App.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ls-bus-guide/demos/can-bus-simulation/src/App.c b/ls-bus-guide/demos/can-bus-simulation/src/App.c index cf4c493..edb8978 100644 --- a/ls-bus-guide/demos/can-bus-simulation/src/App.c +++ b/ls-bus-guide/demos/can-bus-simulation/src/App.c @@ -234,7 +234,7 @@ void App_EvaluateDiscreteStates(FmuInstance* instance) } else if (operation->type == FMI3_LS_BUS_CAN_OP_CONFIGURATION) { - const fmi3LsBusOperationCanConfiguration* configOp = (fmi3LsBusOperationCanConfiguration*)operation; + const fmi3LsBusCanOperationConfiguration* configOp = (fmi3LsBusCanOperationConfiguration*)operation; if (configOp->parameterType == FMI3_LS_BUS_CAN_CONFIG_PARAM_TYPE_CAN_BAUDRATE) { LogFmuMessage(instance, fmi3OK, "Info", diff --git a/ls-bus-guide/demos/can-node-triggered-output/src/App.c b/ls-bus-guide/demos/can-node-triggered-output/src/App.c index a36d799..5a328d9 100644 --- a/ls-bus-guide/demos/can-node-triggered-output/src/App.c +++ b/ls-bus-guide/demos/can-node-triggered-output/src/App.c @@ -86,7 +86,7 @@ bool App_DoStep(FmuInstance* instance, fmi3Float64 currentTime, fmi3Float64 targ instance->App->NextTransmitTime); // Create a CAN transmit operation - FMI3_LS_BUS_CAN_CREATE_OP_CAN_TRANSMIT(&instance->App->TxBufferInfo, id, 0, 0, sizeof data, data); + FMI3_LS_BUS_CAN_CREATE_OP_CAN_TRANSMIT(&instance->App->TxBufferInfo, id, FMI3_LS_BUS_FALSE, FMI3_LS_BUS_FALSE, sizeof data, data); // Check that operation was created successfully if (!instance->App->TxBufferInfo.status) @@ -134,7 +134,7 @@ void App_UpdateDiscreteStates(FmuInstance* instance) else if (operation->type == FMI3_LS_BUS_CAN_OP_CONFIGURATION || operation->type == FMI3_LS_BUS_CAN_OP_STATUS || operation->type == FMI3_LS_BUS_CAN_OP_CONFIRM || - operation->type == FMI3_LS_BUS_CAN_OP_CAN_BUS_ERROR || + operation->type == FMI3_LS_BUS_CAN_OP_BUS_ERROR || operation->type == FMI3_LS_BUS_CAN_OP_ARBITRATION_LOST) { // Ignore From 8fd1aa72042d007f205484dee023639787f29bac Mon Sep 17 00:00:00 2001 From: Benedikt Menne Date: Mon, 27 Nov 2023 14:16:12 +0100 Subject: [PATCH 3/6] Add demo section --- ls-bus-guide/4____network_abstraction.adoc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ls-bus-guide/4____network_abstraction.adoc b/ls-bus-guide/4____network_abstraction.adoc index c781019..5d83200 100644 --- a/ls-bus-guide/4____network_abstraction.adoc +++ b/ls-bus-guide/4____network_abstraction.adoc @@ -304,8 +304,13 @@ fmi3Status fmi3UpdateDiscreteStates(...) * FMI3_LS_BUS_BUFFER_INFO_RESET allows to reset the `fmi3LsBusUtilBufferInfo` instance after processing ==== -===== Examples -#TODO# +==== Demos [[low-cut-can-demos]] +The following list contains demos, which illustrate both the Bus Simulation as such and Network FMUs of various designs: + +* link:./demos/can-bus-simulation[CAN Bus Simulation]: Represents an exemplary Bus Simulation FMU for CAN. +This Bus Simulation can be used in combination with the other Network FMUs, listed behind this point. + +* link:./demos/can-node-triggered-output[CAN Triggered Output]: This demo Network FMU shows sending and receiving multiple CAN Transmit operations using `triggered` output clocks. ==== Sequence Diagrams [[low-cut-can-sequence-diagrams]] This section contains sample sequences to clarify the facts in the CAN, CAN FD, CAN XL part. From 57a5bab659e7ff51fb4a52edd07f524a290c7597 Mon Sep 17 00:00:00 2001 From: Clemens Boos Date: Mon, 27 Nov 2023 14:42:51 +0100 Subject: [PATCH 4/6] Add README for demo FMUs --- ls-bus-guide/demos/README.md | 12 ++++++++ .../demos/can-bus-simulation/README.md | 28 +++++++++++++++++++ .../demos/can-node-triggered-output/README.md | 24 ++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 ls-bus-guide/demos/README.md create mode 100644 ls-bus-guide/demos/can-bus-simulation/README.md create mode 100644 ls-bus-guide/demos/can-node-triggered-output/README.md diff --git a/ls-bus-guide/demos/README.md b/ls-bus-guide/demos/README.md new file mode 100644 index 0000000..691295d --- /dev/null +++ b/ls-bus-guide/demos/README.md @@ -0,0 +1,12 @@ +# Demo FMUs for FMI-LS-BUS + +This directory contains demo FMUs implementing the FMI-LS-BUS. + +## Demos for CAN + +The following demos are provided for the CAN bus: + +- `can-bus-simulation`: + A bus simulation FMU for connection two CAN nodes. +- `can-node-triggered-outputn`: + A CAN node sending a periodic frame using a triggered output clock. \ No newline at end of file diff --git a/ls-bus-guide/demos/can-bus-simulation/README.md b/ls-bus-guide/demos/can-bus-simulation/README.md new file mode 100644 index 0000000..6e0be40 --- /dev/null +++ b/ls-bus-guide/demos/can-bus-simulation/README.md @@ -0,0 +1,28 @@ +# CAN Bus Simulation FMU + +This directory contains a demo FMU implementing a bus simulation FMU for CAN. +The FMU provides terminals to connect exactly two nodes. + +Currently, the bus simulation provides the following capabilities: +- Forwarding of CAN frames and sending confirmations +- Basic timing simulation based on baud rate +- Basic frame priorization + +The FMU requires a simulator with support for triggered and countdown clocks, event mode and variable step size. + +## Contents + +This directory contains the source and description files of the FMU: +- `src`: Source code of the FMU +- `description`: Description files of the FMU +- `PackFmu.py`: Script to generate a source-code FMU + +## Building the FMU + +A script `PackFMU.py` is provided, which packages the demo source files, as well as all required FMI-LS-BUS headers, into a source code FMU. +This FMU can then be loaded by an importer or precompiled using, e.g., FMPy. + +## Running the FMU + +To run the FMU, a supported simulator and two CAN node FMUs (e.g., from the demo `can-node-triggered-output`) are required. +The node FMUs must be connected to the `Node1` and `Node2` terminals. diff --git a/ls-bus-guide/demos/can-node-triggered-output/README.md b/ls-bus-guide/demos/can-node-triggered-output/README.md new file mode 100644 index 0000000..39b5c84 --- /dev/null +++ b/ls-bus-guide/demos/can-node-triggered-output/README.md @@ -0,0 +1,24 @@ +# CAN Node FMU with Triggered output + +This directory contains a demo FMU a CAN node which uses a triggered output clock. + +The CAN node periodically sends a CAN frame with ID `0x1` and 4 bytes of payload every 300ms. + +The FMU requires a simulator with support for triggered clocks, event mode and variable step size. + +## Contents + +This directory contains the source and description files of the FMU: +- `src`: Source code of the FMU +- `description`: Description files of the FMU +- `PackFmu.py`: Script to generate a source-code FMU + +## Building the FMU + +A script `PackFMU.py` is provided, which packages the demo source files, as well as all required FMI-LS-BUS headers, into a source code FMU. +This FMU can then be loaded by an importer or precompiled using, e.g., FMPy. + +## Running the FMU + +To run the FMU, a supported simulator and optionally a bus simulation FMU is required. +It can either be directly connected to another node FMU (or even another instance of this FMU) or to a bus simulation FMU using the `CanChannel` terminal. From 8bb7b573eb91e3dc99cad0dde08730bbdae8462f Mon Sep 17 00:00:00 2001 From: Benedikt Menne Date: Wed, 29 Nov 2023 14:40:22 +0100 Subject: [PATCH 5/6] Add license and readme files to demo FMUs --- ls-bus-guide/demos/can-bus-simulation/PackFmu.py | 5 +++++ ls-bus-guide/demos/can-node-triggered-output/PackFmu.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ls-bus-guide/demos/can-bus-simulation/PackFmu.py b/ls-bus-guide/demos/can-bus-simulation/PackFmu.py index 16eb9d5..097067b 100644 --- a/ls-bus-guide/demos/can-bus-simulation/PackFmu.py +++ b/ls-bus-guide/demos/can-bus-simulation/PackFmu.py @@ -15,6 +15,7 @@ def main(): demo_dir = Path(__file__).parent + root_dir = Path(__file__).parent.parent.parent.parent with zipfile.ZipFile(FMU_PATH, 'w') as fmu: # Add LS-BUS headers from GitHub repository @@ -36,6 +37,10 @@ def main(): fmu.write(demo_dir / 'description' / 'terminalsAndIcons.xml', 'terminalsAndIcons/terminalsAndIcons.xml') fmu.write(demo_dir / 'description' / 'fmi-ls-manifest.xml', 'extra/org.fmi-standard.fmi-ls-bus/fmi-ls-manifest.xml') + # Add additional documentation files + fmu.write(root_dir / 'LICENSE.txt', 'documentation/licenses/LICENSE.txt') + fmu.write(demo_dir / 'README.md', 'documentation/README.md') + if __name__ == '__main__': main() diff --git a/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py b/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py index b6803c2..a08ada0 100644 --- a/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py +++ b/ls-bus-guide/demos/can-node-triggered-output/PackFmu.py @@ -15,6 +15,7 @@ def main(): demo_dir = Path(__file__).parent + root_dir = Path(__file__).parent.parent.parent.parent with zipfile.ZipFile(FMU_PATH, 'w') as fmu: # Add LS-BUS headers from GitHub repository @@ -36,6 +37,10 @@ def main(): fmu.write(demo_dir / 'description' / 'terminalsAndIcons.xml', 'terminalsAndIcons/terminalsAndIcons.xml') fmu.write(demo_dir / 'description' / 'fmi-ls-manifest.xml', 'extra/org.fmi-standard.fmi-ls-bus/fmi-ls-manifest.xml') + # Add additional documentation files + fmu.write(root_dir / 'LICENSE.txt', 'documentation/licenses/LICENSE.txt') + fmu.write(demo_dir / 'README.md', 'documentation/README.md') + if __name__ == '__main__': main() From 93acab3043e62d57e723fa766d4a3c7f16aba780 Mon Sep 17 00:00:00 2001 From: Clemens Boos Date: Thu, 30 Nov 2023 06:44:54 +0100 Subject: [PATCH 6/6] Demos: Update description files - Fix inconsistent model identifier - Use "::" in names as it is currently defined in the standard - Use fixed schema location --- .../description/buildDescription.xml | 2 +- .../description/fmi-ls-manifest.xml | 2 +- .../description/modelDescription.xml | 20 +++++++++---------- .../description/terminalsAndIcons.xml | 16 +++++++-------- .../description/buildDescription.xml | 2 +- .../description/fmi-ls-manifest.xml | 2 +- .../description/modelDescription.xml | 12 +++++------ 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml b/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml index 62cf3e3..b4a1d93 100644 --- a/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml +++ b/ls-bus-guide/demos/can-bus-simulation/description/buildDescription.xml @@ -2,7 +2,7 @@ - + diff --git a/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml b/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml index a5a9253..da36fd6 100644 --- a/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml +++ b/ls-bus-guide/demos/can-bus-simulation/description/fmi-ls-manifest.xml @@ -5,4 +5,4 @@ fmi-ls:fmi-ls-version="1.0.0" fmi-ls:fmi-ls-description="Layered Standard for the simulation of bus communication on a signal or network abstraction based level." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../Fmi3LsBus/schemas/fmi3LayeredStandardBusManifest.xsd"/> + xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/modelica/fmi-ls-bus/473bd5b80730c47373bf41f1c31d44f50de82dd0/schema/fmi3LayeredStandardBusManifest.xsd"/> diff --git a/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml b/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml index 502cf62..4e6768b 100644 --- a/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml +++ b/ls-bus-guide/demos/can-bus-simulation/description/modelDescription.xml @@ -1,14 +1,14 @@ - - - - + + - - - - + + diff --git a/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml b/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml index 52a9538..e772cdb 100644 --- a/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml +++ b/ls-bus-guide/demos/can-bus-simulation/description/terminalsAndIcons.xml @@ -8,16 +8,16 @@ name="Node1" description="CAN bus terminal definition"> diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml b/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml index d3673f4..d852a95 100644 --- a/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml +++ b/ls-bus-guide/demos/can-node-triggered-output/description/buildDescription.xml @@ -2,7 +2,7 @@ - + diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml b/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml index a5a9253..da36fd6 100644 --- a/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml +++ b/ls-bus-guide/demos/can-node-triggered-output/description/fmi-ls-manifest.xml @@ -5,4 +5,4 @@ fmi-ls:fmi-ls-version="1.0.0" fmi-ls:fmi-ls-description="Layered Standard for the simulation of bus communication on a signal or network abstraction based level." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../Fmi3LsBus/schemas/fmi3LayeredStandardBusManifest.xsd"/> + xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/modelica/fmi-ls-bus/473bd5b80730c47373bf41f1c31d44f50de82dd0/schema/fmi3LayeredStandardBusManifest.xsd"/> diff --git a/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml b/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml index 735b0a1..bdbe86c 100644 --- a/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml +++ b/ls-bus-guide/demos/can-node-triggered-output/description/modelDescription.xml @@ -1,14 +1,14 @@ - - - - + +