From 20ed59984e4ccb12f553943e8b65649e675a07ec Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 8 Sep 2025 15:34:45 +0200 Subject: [PATCH 01/10] Initial DCP support (wip) --- include/OMSimulator/Types.h | 5 + src/OMSimulatorLib/CMakeLists.txt | 19 + src/OMSimulatorLib/ComponentDCP.cpp | 593 +++++++++++++++++++++++++++ src/OMSimulatorLib/ComponentDCP.h | 126 ++++++ src/OMSimulatorLib/FMUInfo.cpp | 10 +- src/OMSimulatorLib/FMUInfo.h | 1 + src/OMSimulatorLib/System.cpp | 5 +- src/OMSimulatorLib/Values.cpp | 254 ++++++++++++ src/OMSimulatorLib/Values.h | 3 + src/OMSimulatorLib/Variable.cpp | 188 +++++++++ src/OMSimulatorLib/Variable.h | 17 + src/OMSimulatorPython/CMakeLists.txt | 1 + src/OMSimulatorPython/dcp.py | 228 ++++++++++ src/OMSimulatorPython/ssp.py | 3 + src/OMSimulatorPython/system.py | 2 + src/OMSimulatorPython/variable.py | 1 + 16 files changed, 1454 insertions(+), 2 deletions(-) create mode 100644 src/OMSimulatorLib/ComponentDCP.cpp create mode 100644 src/OMSimulatorLib/ComponentDCP.h create mode 100644 src/OMSimulatorPython/dcp.py diff --git a/include/OMSimulator/Types.h b/include/OMSimulator/Types.h index 8ef78dd92..f9f32d549 100644 --- a/include/OMSimulator/Types.h +++ b/include/OMSimulator/Types.h @@ -105,7 +105,12 @@ typedef enum { typedef enum { oms_component_none, oms_component_fmu, ///< FMU +<<<<<<< HEAD oms_component_fmu3, ///< FMU3 +======= + oms_component_fmu3, ///< FMU3 + oms_component_dcp, ///< DCP +>>>>>>> d03ad7e5 (Initial DCP support (wip)) oms_component_table, ///< lookup table oms_component_external ///< external model } oms_component_enu_t; diff --git a/src/OMSimulatorLib/CMakeLists.txt b/src/OMSimulatorLib/CMakeLists.txt index 3303b5d32..e5da2f420 100644 --- a/src/OMSimulatorLib/CMakeLists.txt +++ b/src/OMSimulatorLib/CMakeLists.txt @@ -25,6 +25,7 @@ set(OMSIMULATORLIB_SOURCES Clock.cpp Clocks.cpp Component.cpp + ComponentDCP.cpp ComponentFMU3CS.cpp ComponentFMUCS.cpp ComponentFMU3ME.cpp @@ -85,6 +86,15 @@ target_link_libraries(OMSimulatorLib oms::3rd::pugixml::header oms::3rd::json::header oms::3rd::ctpl::header) + oms::3rd::xerces + oms::3rd::ctpl::header + oms::3rd::libzip + oms::3rd::dcplib::core + oms::3rd::dcplib::master + oms::3rd::dcplib::slave + oms::3rd::dcplib::ethernet + oms::3rd::dcplib::zip + oms::3rd::dcplib::xml) target_link_libraries(OMSimulatorLib PUBLIC ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) @@ -116,6 +126,15 @@ target_link_libraries(OMSimulatorLib_static oms::3rd::pugixml::header oms::3rd::json::header oms::3rd::ctpl::header) + oms::3rd::xerces + oms::3rd::ctpl::header + oms::3rd::libzip + oms::3rd::dcplib::core + oms::3rd::dcplib::master + oms::3rd::dcplib::slave + oms::3rd::dcplib::ethernet + oms::3rd::dcplib::zip + oms::3rd::dcplib::xml) target_link_libraries(OMSimulatorLib_static PUBLIC ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp new file mode 100644 index 000000000..d249473ec --- /dev/null +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -0,0 +1,593 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-CurrentYear, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF GPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3, + * ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the Open Source Modelica + * Consortium (OSMC) Public License (OSMC-PL) are obtained + * from OSMC, either from the above address, + * from the URLs: http://www.ida.liu.se/projects/OpenModelica or + * http://www.openmodelica.org, and in the OpenModelica distribution. + * GNU version 3 is obtained from: http://www.gnu.org/copyleft/gpl.html. + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + +#include "ComponentDCP.h" + +#include "Flags.h" +#include "Logging.h" +#include "Model.h" +#include "OMSFileSystem.h" +#include "ssd/Tags.h" +#include "System.h" +#include "SystemWC.h" +#include "Scope.h" +#include "dcp/zip/DcpSlaveReader.hpp" +#include "dcp/log/OstreamLog.hpp" +#include "dcp/logic/DcpManagerMaster.hpp" + +#include +#include +#include +#include +#include + + +oms::ComponentDCP::ComponentDCP(const ComRef& cref, System* parentSystem, const std::string& dcpPath) + : oms::Component(cref, oms_component_dcp, parentSystem, dcpPath), fmuInfo(dcpPath) +{ +} + +void oms::ComponentDCP::dcp_initialize() +{ + manager->STC_initialize(1, DcpState::CONFIGURED); + intializationRuns++; +} + +void oms::ComponentDCP::dcp_configuration() +{ + std::cout << "Configure Slaves" << std::endl; + receivedAcks[1] = 0; + + manager->CFG_scope(1, 1, DcpScope::Initialization_Run_NonRealTime); + + uint8_t dataId = 0; + for(const auto &var : this->allVariables) { + if(var.isOutput()) { + manager->CFG_scope(1, dataId, DcpScope::Initialization_Run_NonRealTime); + manager->CFG_output(1, dataId, 0, var.getValueReferenceDCP()); + manager->CFG_steps(1, dataId, 1); + dataId++; + } + else if(var.isInput()) { + DcpDataType dataType = DcpDataType::uint8; + if(var.getNumericType() == oms_signal_numeric_type_FLOAT64) { + dataType = DcpDataType::float64; + } + else if(var.getNumericType() == oms_signal_numeric_type_FLOAT64) { + dataType = DcpDataType::float64; + } + else if(var.getNumericType() == oms_signal_numeric_type_FLOAT32) { + dataType = DcpDataType::float32; + } + else if(var.getNumericType() == oms_signal_numeric_type_INT64) { + dataType = DcpDataType::int64; + } + else if(var.getNumericType() == oms_signal_numeric_type_INT32) { + dataType = DcpDataType::int32; + } + else if(var.getNumericType() == oms_signal_numeric_type_INT16) { + dataType = DcpDataType::int16; + } + else if(var.getNumericType() == oms_signal_numeric_type_INT8) { + dataType = DcpDataType::int8; + } + else if(var.getNumericType() == oms_signal_numeric_type_UINT64) { + dataType = DcpDataType::uint64; + } + else if(var.getNumericType() == oms_signal_numeric_type_UINT32) { + dataType = DcpDataType::uint32; + } + else if(var.getNumericType() == oms_signal_numeric_type_UINT16) { + dataType = DcpDataType::uint16; + } + else if(var.getNumericType() == oms_signal_numeric_type_UINT8) { + dataType = DcpDataType::uint8; + } + + manager->CFG_scope(1, dataId, DcpScope::Initialization_Run_NonRealTime); + manager->CFG_input(1,dataId, 0, var.getValueReferenceDCP(), dataType); + manager->CFG_steps(1, dataId, 1); + dataId++; + } + // TODO: Handle parameters somehow + + } + + manager->CFG_steps(1, 1, 1); + manager->CFG_time_res(1, desc->TimeRes.resolutions.front().numerator, + desc->TimeRes.resolutions.front().denominator); + manager->CFG_source_network_information_UDP(1, 1, asio::ip::address_v4::from_string( + *desc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), *desc->TransportProtocols.UDP_IPv4->Control->port); + manager->CFG_target_network_information_UDP(1, 1, asio::ip::address_v4::from_string( + *desc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), *desc->TransportProtocols.UDP_IPv4->Control->port); +} + +void oms::ComponentDCP::dcp_configure() +{ + +} + +void oms::ComponentDCP::dcp_run(DcpState currentState) +{ + +} + +void oms::ComponentDCP::dcp_doStep() +{ + +} + +void oms::ComponentDCP::dcp_stop() +{ + +} + +void oms::ComponentDCP::dcp_deregister() +{ + +} + +void oms::ComponentDCP::dcp_sendOutputs(DcpState currentState, uint8_t sender) +{ + +} + +void oms::ComponentDCP::dcp_receiveAck(uint8_t sender, uint16_t) +{ + +} + +void oms::ComponentDCP::dcp_receiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode) +{ + +} + +void oms::ComponentDCP::dcp_dataReceived(uint16_t dataId, size_t length, uint8_t payload[]) +{ + +} + +void oms::ComponentDCP::dcp_receiveStateChangedNotification(uint8_t sender, DcpState state) +{ + std::chrono::milliseconds dura(250); + //std::this_thread::sleep_for(dura); + switch (state) { + case DcpState::CONFIGURATION: + dcp_configuration(); + break; + case DcpState::CONFIGURED: + if (intializationRuns < maxInitRuns) { + dcp_initialize(); + + } else { + dcp_run(DcpState::CONFIGURED); + } + break; + case DcpState::SYNCHRONIZED: + dcp_run(DcpState::SYNCHRONIZED); + break; + case DcpState::PREPARED: + dcp_configure(); + break; + + case DcpState::INITIALIZED: + dcp_sendOutputs(DcpState::INITIALIZED, sender); + break; + + case DcpState::RUNNING: + dcp_stop(); + break; + case DcpState::STOPPED: + dcp_deregister(); + break; + case DcpState::ALIVE: + //Do something? + break; + } +} + +oms::ComponentDCP::~ComponentDCP() +{ +} + +oms::Component* oms::ComponentDCP::NewComponent(const oms::ComRef& cref, oms::System* parentSystem, const std::string& dcpPath, std::string replaceComponent) +{ + if (!cref.isValidIdent()) + { + logError_InvalidIdent(cref); + return NULL; + } + + if (!parentSystem) + { + logError_InternalError; + return NULL; + } + // replaceComponent string will be used to avoid name conflicts when replacing a fmu with oms_replaceSubModel(), the default is "" + + filesystem::path temp_root(parentSystem->getModel().getTempDirectory()); + filesystem::path temp_temp = temp_root / "temp"; + filesystem::path relDCPPath = parentSystem->copyResources() ? (filesystem::path("resources") / (parentSystem->getUniqueID() + "_" + replaceComponent + std::string(cref) + ".dcp")) : filesystem::path(dcpPath); + filesystem::path absDCPPath = temp_root / relDCPPath; + + ComponentDCP* component = new ComponentDCP(cref, parentSystem, relDCPPath.generic_string()); + + /* parse the modeldescription.xml at top level to get the GUID to check whether instance already exist + * so we don't need to unpack the fmu, and also parse start values before instantiating fmu's + */ + std::string guid_ = ""; + filesystem::path slaveDescriptionPath; + /* + * check for modeldescription path from file system or temp directory + * because when importingSnapshot the path will be resources/0001_tank1.fmu + */ + + if (parentSystem->copyResources()) + slaveDescriptionPath = dcpPath; + else + slaveDescriptionPath = parentSystem->getModel().getTempDirectory() / filesystem::path(dcpPath); + + component->values.parseSlaveDescription(dcpPath, guid_); + + // update DCP info + // TODO: Is this needed? (dcp) + component->fmuInfo.update(oms_component_dcp, nullptr); + + // create a list of all variables using fmi4c variable structure + component->desc = getSlaveDescriptionFromDcpFile(1, 0, dcpPath).get(); + + int numVars = component->desc->Variables.size(); + component->allVariables.reserve(numVars); + component->exportVariables.reserve(numVars); + for (unsigned int i = 0; i < numVars; ++i) + { + oms::Variable v(component->desc, i); + //logInfo("vars: " + std::string(v.getCref().c_str())); + if (v.getIndex() != i) + { + + logError("Index mismatch " + std::to_string(v.getIndex()) + " != " + std::to_string(i) + ".\nPlease report the problem to the dev team: https://github.com/OpenModelica/OMSimulator/issues/new?assignees=&labels=&template=bug_report.md"); + delete component; + return NULL; + } + + component->allVariables.push_back(v); + component->exportVariables.push_back(true); + } + + // create some special variable maps + for (auto const& v : component->allVariables) + { + if (v.isInput()) + component->inputs.push_back(v.getIndex()); + else if (v.isOutput()) + { + component->outputs.push_back(v.getIndex()); + component->outputsGraph.addNode(Connector(oms_causality_output, v.getType(), v.getCref(), component->getFullCref())); + } + else if (v.isParameter()) + component->parameters.push_back(v.getIndex()); + else if (v.isCalculatedParameter()) + component->calculatedParameters.push_back(v.getIndex()); + + if (v.isInitialUnknown()) + component->initialUnknownsGraph.addNode(Connector(v.getCausality(), v.getType(), v.getCref(), component->getFullCref())); + + component->exportVariables.push_back(v.isInput() || v.isOutput()); + } + + // create connectors + while (component->connectors.size() > 0 && NULL == component->connectors.back()) + component->connectors.pop_back(); + + int j = 1; + int size = 1 + component->inputs.size(); + for (const auto& i : component->inputs) + component->connectors.push_back(new Connector(oms_causality_input, component->allVariables[i].getType(), component->allVariables[i].getCref(), component->getFullCref(), j++/(double)size)); + j = 1; + size = 1 + component->outputs.size(); + for (const auto& i : component->outputs) + component->connectors.push_back(new Connector(oms_causality_output, component->allVariables[i].getType(), component->allVariables[i].getCref(), component->getFullCref(), j++/(double)size)); + for (const auto& i : component->parameters) + component->connectors.push_back(new Connector(oms_causality_parameter, component->allVariables[i].getType(), component->allVariables[i].getCref(), component->getFullCref())); + for (const auto& i : component->calculatedParameters) + component->connectors.push_back(new Connector(oms_causality_calculatedParameter, component->allVariables[i].getType(), component->allVariables[i].getCref(), component->getFullCref())); + component->connectors.push_back(NULL); + component->element.setConnectors(&component->connectors[0]); + + if (oms_status_ok != component->initializeDependencyGraph_outputs()) + { + logError(std::string(cref) + ": Couldn't initialize dependency graph for simulation unknowns."); + delete component; + return NULL; + } + + // set units to connector + for (auto &connector : component->connectors) + { + if (connector) + { + oms::ComRef connectorCref = connector->getName(); + std::string unitName = component->values.getUnitFromModeldescription(connectorCref); + if (!unitName.empty()) + connector->connectorUnits[unitName] = component->values.modeldescriptionUnitDefinitions[unitName]; + + // get enumerationTypes + std::string enumType = component->values.getEnumerationTypeFromModeldescription(connectorCref); + if (!enumType.empty()) + connector->enumerationName[connectorCref] = enumType; + } + } + + return component; +} + +oms::Component* oms::ComponentDCP::NewComponent(const pugi::xml_node& node, oms::System* parentSystem, const std::string& sspVersion, const Snapshot& snapshot, std::string variantName) +{ + ComRef cref = ComRef(node.attribute("name").as_string()); + std::string type = node.attribute("type").as_string(); + std::string source = node.attribute("source").as_string(); + + if (type != "application/x-fmu-sharedlibrary" && !type.empty()) + { + logError("Unexpected component type: " + type); + return NULL; + } + + oms::ComponentDCP* component = dynamic_cast(oms::ComponentDCP::NewComponent(cref, parentSystem, source)); + if (!component) + return NULL; + + for (const auto& connector : component->connectors) + if (connector) + delete connector; + component->connectors.clear(); + for(pugi::xml_node_iterator it = node.begin(); it != node.end(); ++it) + { + std::string name = it->name(); + if(name == oms::ssp::Draft20180219::ssd::connectors) + { + // get the ssdNode to parse UnitDefinitions in "SystemStructure.ssd" + pugi::xml_node ssdNode = snapshot.getResourceNode(variantName); + component->values.importUnitDefinitions(ssdNode); + // import connectors + for(pugi::xml_node_iterator itConnectors = (*it).begin(); itConnectors != (*it).end(); ++itConnectors) + { + component->connectors.push_back(oms::Connector::NewConnector(*itConnectors, sspVersion, component->getFullCref())); + // set units to connector + if ((*itConnectors).child(oms::ssp::Version1_0::ssc::real_type)) + { + std::string unitName = (*itConnectors).child(oms::ssp::Version1_0::ssc::real_type).attribute("unit").as_string(); + if (!unitName.empty()) + component->connectors.back()->connectorUnits[unitName] = component->values.modeldescriptionUnitDefinitions[unitName]; + } + // set enumeration definitions + if ((*itConnectors).child(oms::ssp::Version1_0::ssc::enumeration_type)) + { + std::string enumTypeName = (*itConnectors).child(oms::ssp::Version1_0::ssc::enumeration_type).attribute("name").as_string(); + if (!enumTypeName.empty()) + component->connectors.back()->enumerationName[component->connectors.back()->getName().c_str()] = enumTypeName; + + // give priority to enum definitions in ssd over modeldescription.xml, it is possible the user might have manually change values in ssd file + component->values.importEnumerationDefinitions(ssdNode, enumTypeName); + } + } + } + else if(name == oms::ssp::Draft20180219::ssd::element_geometry) + { + oms::ssd::ElementGeometry geometry; + geometry.importFromSSD(*it); + component->setGeometry(geometry); + } + else if(name == oms::ssp::Version1_0::ssd::parameter_bindings) + { + // set parameter bindings associated with the component + Values resources; + std::string tempdir = parentSystem->getModel().getTempDirectory(); + resources.importFromSnapshot(*it, sspVersion, snapshot, variantName); + component->values.parameterResources.push_back(resources); + } + else + { + logError_WrongSchema(name); + delete component; + return NULL; + } + } + + component->connectors.push_back(NULL); + component->element.setConnectors(&component->connectors[0]); + + return component; +} + +oms_status_enu_t oms::ComponentDCP::addSignalsToResults(const char *regex) +{ + std::regex exp(regex); + for (unsigned int i=0; igetDcpDriver()); + manager->setAckReceivedListener( + std::bind(&oms::ComponentDCP::dcp_receiveAck, this, std::placeholders::_1, std::placeholders::_2)); + manager->setNAckReceivedListener( + std::bind(&oms::ComponentDCP::dcp_receiveNAck, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + manager->setStateChangedNotificationReceivedListener( + std::bind(&oms::ComponentDCP::dcp_receiveStateChangedNotification, this, std::placeholders::_1, + std::placeholders::_2)); + manager->setDataReceivedListener( + std::bind(&oms::ComponentDCP::dcp_dataReceived, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + manager->addLogListener(std::bind(&OstreamLog::logOstream, stdLog, std::placeholders::_1)); + manager->setGenerateLogString(true); + + return oms_status_ok; +} + +oms_status_enu_t oms::ComponentDCP::registerSignalsForResultFile(ResultWriter &resultFile) +{ + // TODO: Implement (dcp) + return oms_status_fatal; +} + +oms_status_enu_t oms::ComponentDCP::removeSignalsFromResults(const char *regex) +{ + // TODO: Implement (dcp) + return oms_status_fatal; +} + +oms_status_enu_t oms::ComponentDCP::reset() +{ + manager->stop(); + return oms_status_ok; +} + +oms_status_enu_t oms::ComponentDCP::terminate() +{ + manager->stop(); + return oms_status_ok; +} + +oms_status_enu_t oms::ComponentDCP::updateSignals(ResultWriter &resultWriter) +{ + // TODO: Implement (dcp) + return oms_status_fatal; +} + +void oms::ComponentDCP::getFilteredSignals(std::vector &filteredSignals) const +{ + // TODO: Implement (dcp) +} + +oms::Variable *oms::ComponentDCP::getVariable(const ComRef &cref) +{ + // TODO: Implement (dcp) + return nullptr; +} + +oms_status_enu_t oms::ComponentDCP::initializeDependencyGraph_outputs() +{ + // TODO: Does this work? (dcp) + + if (outputsGraph.getEdges().connections.size() > 0) + { + logError(std::string(getCref()) + ": " + getPath() + " is already initialized."); + return oms_status_error; + } + + // if (!startIndex) + // { + // logDebug(std::string(getCref()) + ": " + getPath() + " no dependencies"); + // return oms_status_ok; + // } + + // get the output dependencies + int i = 0; + for (const auto &it : values.modelStructureOutputs) + { + Variable& output = allVariables[outputs[i]]; + // no dependencies + if (it.second.empty() && values.modelStructureOutputDependencyExist[it.first]) + { + logDebug(std::string(getCref()) + ": " + getPath() + " output " + std::string(output) + " has no dependencies"); + } + // dependency attribute not provided in modeldescription.xml, all output depends on all inputs + else if (it.second.empty() && !values.modelStructureOutputDependencyExist[it.first]) + { + logDebug(std::string(getCref()) + ": " + getPath() + " output " + std::string(output) + " depends on all"); + for (const auto& j : inputs) + outputsGraph.addEdge(allVariables[j].makeConnector(this->getFullCref()), output.makeConnector(this->getFullCref())); + } + else + { + for (const auto &index : it.second) + { + if (index < 1 || index > allVariables.size()) + { + logWarning("Output " + std::string(output) + " has bad dependency on variable with index " + std::to_string(index) + " which couldn't be resolved"); + return logError(std::string(getCref()) + ": erroneous dependencies detected in modelDescription.xml"); + } + logDebug(std::string(getCref()) + ": " + getPath() + " output " + std::string(output) + " depends on " + std::string(allVariables[index - 1])); + outputsGraph.addEdge(allVariables[index - 1].makeConnector(this->getFullCref()), output.makeConnector(this->getFullCref())); + } + } + i = i + 1; + } + + return oms_status_ok; +} diff --git a/src/OMSimulatorLib/ComponentDCP.h b/src/OMSimulatorLib/ComponentDCP.h new file mode 100644 index 000000000..d2d0ad777 --- /dev/null +++ b/src/OMSimulatorLib/ComponentDCP.h @@ -0,0 +1,126 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-CurrentYear, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF GPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3, + * ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the Open Source Modelica + * Consortium (OSMC) Public License (OSMC-PL) are obtained + * from OSMC, either from the above address, + * from the URLs: http://www.ida.liu.se/projects/OpenModelica or + * http://www.openmodelica.org, and in the OpenModelica distribution. + * GNU version 3 is obtained from: http://www.gnu.org/copyleft/gpl.html. + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + +#ifndef _OMS_COMPONENT_DCP_H_ +#define _OMS_COMPONENT_DCP_H_ + + + +#include "Component.h" +#include "ComRef.h" +#include "ResultWriter.h" +#include "Snapshot.h" +#include "Values.h" +#include "Variable.h" + +#include +#include +#include +#include +#include +#include +#include +#include //Must be included first because of include conflict with Asio and windows.h +#include + +namespace oms +{ +class System; + +class ComponentDCP : public Component +{ +public: + ~ComponentDCP(); + + static Component* NewComponent(const ComRef& cref, System* parentSystem, const std::string& dcpPath, std::string replaceComponent = ""); + static Component* NewComponent(const pugi::xml_node& node, System* parentSystem, const std::string& sspVersion, const Snapshot& snapshot, std::string variantName); + const FMUInfo* getFMUInfo() const {return &(this->fmuInfo);} + + oms_status_enu_t addSignalsToResults(const char* regex); + oms_status_enu_t exportToSSD(pugi::xml_node& node, Snapshot& snapshot, std::string variantName) const; + oms_status_enu_t initialize(); + oms_status_enu_t instantiate(); + oms_status_enu_t stepUntil(double stopTime) { return logError_NotImplemented; } + oms_status_enu_t registerSignalsForResultFile(ResultWriter& resultFile); + oms_status_enu_t removeSignalsFromResults(const char* regex); + oms_status_enu_t reset(); + oms_status_enu_t terminate(); + oms_status_enu_t updateSignals(ResultWriter& resultWriter); + Variable* getVariable(const ComRef& cref); + void getFilteredSignals(std::vector& filteredSignals) const; + oms_status_enu_t initializeDependencyGraph_outputs(); + +protected: + ComponentDCP(const ComRef& cref, System* parentSystem, const std::string& fmuPath); + + // stop the compiler generating methods copying the object + ComponentDCP(ComponentDCP const& copy); ///< not implemented + ComponentDCP& operator=(ComponentDCP const& copy); ///< not implemented + +private: + FMUInfo fmuInfo; + SlaveDescription_t *desc; + + std::vector allVariables; + std::vector calculatedParameters; + std::vector derivatives; + std::vector inputs; + std::vector outputs; + std::vector parameters; + std::vector exportVariables; + Values values; ///< start values defined before instantiating the FMU and external inputs defined after initialization + + double time; + + void dcp_initialize(); + void dcp_configuration(); + void dcp_configure(); + void dcp_run(DcpState currentState); + void dcp_doStep(); + void dcp_stop(); + void dcp_deregister(); + void dcp_sendOutputs(DcpState currentState, uint8_t sender); + void dcp_receiveAck(uint8_t sender, uint16_t); + void dcp_receiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode); + void dcp_dataReceived(uint16_t dataId, size_t length, uint8_t payload[]); + void dcp_receiveStateChangedNotification(uint8_t sender, DcpState state); + + UdpDriver *driver; + DcpManagerMaster *manager; + uint8_t maxInitRuns = 0; + uint8_t intializationRuns = 1; + std::map receivedAcks; + + oms::ComRef getValidCref(ComRef cref); +}; +} + +#endif diff --git a/src/OMSimulatorLib/FMUInfo.cpp b/src/OMSimulatorLib/FMUInfo.cpp index 214b50bfe..1927875b9 100644 --- a/src/OMSimulatorLib/FMUInfo.cpp +++ b/src/OMSimulatorLib/FMUInfo.cpp @@ -90,6 +90,9 @@ void oms::FMUInfo::update(oms_component_enu_t componentType, fmiHandle* fmu) case oms_component_fmu3: // FMI 3.0 updateFMI3Info(fmu); break; + case oms_component_dcp: // DCP + updateDCPInfo(); + break; default: // Unsupported type logError("Unsupported component type for Variable constructor"); } @@ -196,4 +199,9 @@ void oms::FMUInfo::updateFMI3Info(fmiHandle* fmu) //providesPerElementDependencies //providesEvaluateDiscreteStates } -} \ No newline at end of file +} + +void oms::FMUInfo::updateDCPInfo() +{ + //Not sure if we need anything for DCP? +} diff --git a/src/OMSimulatorLib/FMUInfo.h b/src/OMSimulatorLib/FMUInfo.h index a7ddb9485..ac118047a 100644 --- a/src/OMSimulatorLib/FMUInfo.h +++ b/src/OMSimulatorLib/FMUInfo.h @@ -65,6 +65,7 @@ namespace oms private: void updateFMI2Info(fmiHandle *fmi4c); void updateFMI3Info(fmiHandle *fmi4c); + void updateDCPInfo(); // methods to copy the object FMUInfo(const FMUInfo& rhs); ///< not implemented diff --git a/src/OMSimulatorLib/System.cpp b/src/OMSimulatorLib/System.cpp index a68f7b770..a08640af8 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -38,6 +38,7 @@ #include "Component.h" #include "ComponentFMUCS.h" #include "ComponentFMU3CS.h" +#include "ComponentDCP.h" #include "ComponentFMUME.h" #include "ComponentFMU3ME.h" #include "ComponentTable.h" @@ -318,6 +319,8 @@ oms_status_enu_t oms::System::addSubModel(const oms::ComRef& cref, const std::st if (extension == ".fmu" && oms_system_wc == type && fmiVersion == "2.0") component = ComponentFMUCS::NewComponent(cref, this, path_.string()); + else if (extension == ".dcp") + component = ComponentDCP::NewComponent(cref, this, path_.string()); else if (extension == ".fmu" && oms_system_wc == type && fmiVersion == "3.0") component = ComponentFMU3CS::NewComponent(cref, this, path_.string()); else if (extension == ".fmu" && oms_system_sc == type && fmiVersion == "2.0") @@ -327,7 +330,7 @@ oms_status_enu_t oms::System::addSubModel(const oms::ComRef& cref, const std::st else if (extension == ".csv" || extension == ".mat") component = ComponentTable::NewComponent(cref, this, path_.string()); else - return logError("supported sub-model formats are \".fmu\", \".csv\", \".mat\""); + return logError("supported sub-model formats are \".fmu\", \".dcp\", \".csv\", \".mat\""); if (!component) return oms_status_error; diff --git a/src/OMSimulatorLib/Values.cpp b/src/OMSimulatorLib/Values.cpp index 610d4eda9..e0d55b7a0 100644 --- a/src/OMSimulatorLib/Values.cpp +++ b/src/OMSimulatorLib/Values.cpp @@ -38,6 +38,8 @@ #include "Logging.h" #include "ssd/Tags.h" #include "Util.h" +#include "XercesValidator.h" +#include "dcp/zip/DcpSlaveReader.hpp" #include #include @@ -1861,6 +1863,258 @@ oms_status_enu_t oms::Values::parseModelDescriptionFmi3(const filesystem::path& return oms_status_ok; } +oms_status_enu_t oms::Values::parseSlaveDescription(const std::string &dcpPath, std::string& guid_) +{ + std::shared_ptr desc = getSlaveDescriptionFromDcpFile(1, 0, dcpPath); + + guid_ = desc->uuid; + + for(const auto &def : desc->UnitDefinitions) { + std::map baseUnits; + baseUnits["kg"] = std::to_string(def.BaseUnit->kg); + baseUnits["m"] = std::to_string(def.BaseUnit->m); + baseUnits["s"] = std::to_string(def.BaseUnit->s); + baseUnits["A"] = std::to_string(def.BaseUnit->A); + baseUnits["K"] = std::to_string(def.BaseUnit->K); + baseUnits["mol"] = std::to_string(def.BaseUnit->mol); + baseUnits["cd"] = std::to_string(def.BaseUnit->cd); + baseUnits["rad"] = std::to_string(def.BaseUnit->rad); + baseUnits["factor"] = std::to_string(def.BaseUnit->factor); + baseUnits["offset"] = std::to_string(def.BaseUnit->offset); + modeldescriptionUnitDefinitions[def.name] = baseUnits; + } + + //Reading start values like this is madness, but it seems to be the + //only option with the way variables are structured in DCP and DCPLib... + for(const auto &var : desc->Variables) { + if(var.Input) { + if(var.Input->Float64) { + if(var.Input->Float64->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Input->Float64->start.get())[0]; + } + if(var.Input->Float64->unit.get()) { + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Input->Float64->unit.get()); + } + } + else if(var.Input->Float32) { + if(var.Input->Float32->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Input->Float32->start.get())[0]; + } + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Input->Float32->unit.get()); + } + else if(var.Input->Int64) { + if(var.Input->Int64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Int64->start.get())[0]; + } + } + else if(var.Input->Int32) { + if(var.Input->Int32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Int32->start.get())[0]; + } + } + else if(var.Input->Int16) { + if(var.Input->Int16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Int16->start.get())[0]; + } + } + else if(var.Input->Int8) { + if(var.Input->Int8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Int8->start.get())[0]; + } + } + else if(var.Input->Uint64) { + if(var.Input->Uint64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Uint64->start.get())[0]; + } + } + else if(var.Input->Uint32) { + if(var.Input->Uint32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Uint32->start.get())[0]; + } + } + else if(var.Input->Uint16) { + if(var.Input->Uint16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Uint16->start.get())[0]; + } + } + else if(var.Input->Uint8) { + if(var.Input->Uint8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Input->Uint8->start.get())[0]; + } + } + else if(var.Input->String) { + if(var.Input->String->start.get()) { + modelDescriptionStringStartValues[ComRef(var.name)] = (*var.Input->String->start.get())[0]; + } + } + //TODO: What about binary variables? + } + else if(var.Output) { + if(var.Output->Float64) { + if(var.Output->Float64->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Output->Float64->start.get())[0]; + } + if(var.Output->Float64->unit.get()) { + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Output->Float64->unit.get()); //Crash here! + } + } + else if(var.Output->Float32) { + if(var.Output->Float32->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Output->Float32->start.get())[0]; + } + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Input->Float32->unit.get()); + } + else if(var.Output->Int64) { + if(var.Output->Int64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Int64->start.get())[0]; + } + } + else if(var.Output->Int32) { + if(var.Output->Int32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Int32->start.get())[0]; + } + } + else if(var.Output->Int16) { + if(var.Output->Int16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Int16->start.get())[0]; + } + } + else if(var.Output->Int8) { + if(var.Output->Int8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Int8->start.get())[0]; + } + } + else if(var.Output->Uint64) { + if(var.Output->Uint64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Uint64->start.get())[0]; + } + } + else if(var.Output->Uint32) { + if(var.Output->Uint32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Uint32->start.get())[0]; + } + } + else if(var.Output->Uint16) { + if(var.Output->Uint16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Uint16->start.get())[0]; + } + } + else if(var.Output->Uint8) { + if(var.Output->Uint8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Output->Uint8->start.get())[0]; + } + } + else if(var.Output->String) { + if(var.Output->String->start.get()) { + modelDescriptionStringStartValues[ComRef(var.name)] = (*var.Output->String->start.get())[0]; + } + } + //TODO: What about binary variables? + + if(var.Output->Dependencies && var.Output->Dependencies->Initialization) { + std::vector deps; + for(const auto &dep : var.Output->Dependencies->Initialization->dependecies) { + deps.push_back(dep.vr); + } + modelStructureInitialUnknownsDependencyExist[var.valueReference] = true; + modelStructureInitialUnknowns[var.valueReference] = deps; + } + else if(var.Output->Dependencies && var.Output->Dependencies->Run) { + std::vector deps; + for(const auto &dep : var.Output->Dependencies->Run->dependecies) { + deps.push_back(dep.vr); + } + modelStructureOutputDependencyExist[var.valueReference] = true; + modelStructureOutputs[var.valueReference] = deps; + } + } + else if(var.Parameter) { + if(var.Parameter->Float64) { + if(var.Parameter->Float64->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Parameter->Float64->start.get())[0]; + } + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Input->Float64->unit.get()); + } + else if(var.Parameter->Float32) { + if(var.Parameter->Float32->start.get()) { + modelDescriptionRealStartValues[ComRef(var.name)] = (*var.Parameter->Float32->start.get())[0]; + } + modelDescriptionVariableUnits[ComRef(var.name)] = (*var.Input->Float32->unit.get()); + } + else if(var.Parameter->Int64) { + if(var.Parameter->Int64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Int64->start.get())[0]; + } + } + else if(var.Parameter->Int32) { + if(var.Parameter->Int32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Int32->start.get())[0]; + } + } + else if(var.Parameter->Int16) { + if(var.Parameter->Int16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Int16->start.get())[0]; + } + } + else if(var.Parameter->Int8) { + if(var.Parameter->Int8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Int8->start.get())[0]; + } + } + else if(var.Parameter->Uint64) { + if(var.Parameter->Uint64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Uint64->start.get())[0]; + } + } + else if(var.Parameter->Uint32) { + if(var.Parameter->Uint32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Uint32->start.get())[0]; + } + } + else if(var.Parameter->Uint16) { + if(var.Parameter->Uint16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Uint16->start.get())[0]; + } + } + else if(var.Parameter->Uint8) { + if(var.Parameter->Uint8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.Parameter->Uint8->start.get())[0]; + } + } + else if(var.Parameter->String) { + if(var.Parameter->String->start.get()) { + modelDescriptionStringStartValues[ComRef(var.name)] = (*var.Parameter->String->start.get())[0]; + } + } + //TODO: What about binary variables? + } + else if(var.StructuralParameter) { + if(var.StructuralParameter->Uint64) { + if(var.StructuralParameter->Uint64->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.StructuralParameter->Uint64->start.get())[0]; + } + } + else if(var.StructuralParameter->Uint32) { + if(var.StructuralParameter->Uint32->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.StructuralParameter->Uint32->start.get())[0]; + } + } + else if(var.StructuralParameter->Uint16) { + if(var.StructuralParameter->Uint16->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.StructuralParameter->Uint16->start.get())[0]; + } + } + else if(var.StructuralParameter->Uint8) { + if(var.StructuralParameter->Uint8->start.get()) { + modelDescriptionIntegerStartValues[ComRef(var.name)] = (*var.StructuralParameter->Uint8->start.get())[0]; + } + } + } + } + + return oms_status_ok; +} + void oms::Values::parseModelStructureDependencies(std::string &dependencies, std::vector &dependencyList) { std::stringstream ss(dependencies); diff --git a/src/OMSimulatorLib/Values.h b/src/OMSimulatorLib/Values.h index 0cad800d8..d2f9fd200 100644 --- a/src/OMSimulatorLib/Values.h +++ b/src/OMSimulatorLib/Values.h @@ -116,6 +116,7 @@ namespace oms oms_status_enu_t parseModelDescription(const filesystem::path& root, std::string& guid_); ///< path without the filename, i.e. modelDescription.xml oms_status_enu_t parseModelDescriptionFmi3(const filesystem::path& root, std::string& guid_); ///< path without the filename, i.e. modelDescription.xml + oms_status_enu_t parseSlaveDescription(const std::string &dcpPath, std::string &guid_); oms_status_enu_t rename(const oms::ComRef& oldCref, const oms::ComRef& newCref); oms_status_enu_t renameInResources(const oms::ComRef& oldCref, const oms::ComRef& newCref); @@ -164,6 +165,8 @@ namespace oms std::map modelDescriptionVariableUnits; ///< variable units read from modeldescription.xml std::map variableUnits; ///< variable units set by user + uint8_t dcpId; + struct unitDefinitionsToExport { std::string unitName; diff --git a/src/OMSimulatorLib/Variable.cpp b/src/OMSimulatorLib/Variable.cpp index fb4a315e2..223d485af 100644 --- a/src/OMSimulatorLib/Variable.cpp +++ b/src/OMSimulatorLib/Variable.cpp @@ -38,6 +38,7 @@ #include "Logging.h" #include "Util.h" #include +#include "dcp/xml/DcpSlaveDescriptionElements.hpp" oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t componentType) : der_index(0), state_index(0), is_state(false), is_der(false), is_continuous_time_state(false), is_continuous_time_der(false), index(index_), fmi2(false), fmi3(false), componentType(componentType) @@ -61,6 +62,11 @@ oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t compon } } +oms::Variable::Variable(SlaveDescription_t *desc, int index) +{ + configureDCPVariable(desc, index); +} + void oms::Variable::configureFMI2Variable(fmiHandle* fmi4c, int index_) { @@ -203,6 +209,188 @@ void oms::Variable::configureFMI3Variable(fmiHandle* fmi4c, int index_) } } +void oms::Variable::configureDCPVariable(SlaveDescription_t *desc, int index) +{ + this->index = index; + + Variable_t *var = &desc->Variables[index]; + + cref = var->name; + if(var->description != nullptr) { + description = var->description->data(); + trim(description); + } + else { + description = ""; + } + + dcpVr = var->valueReference; + + if(var->Input != nullptr) { + dcpCausality = dcpCausalityInput; + if(var->Input->Float64 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT64; + } + else if(var->Input->Float32 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT32; + } + else if(var->Input->Int64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT64; + } + else if(var->Input->Int32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT32; + } + else if(var->Input->Int16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT16; + } + else if(var->Input->Int8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT8; + } + else if(var->Input->Uint64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT64; + } + else if(var->Input->Uint32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT32; + } + else if(var->Input->Uint16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT16; + } + else if(var->Input->Uint8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT8; + } + else if(var->Input->String!= nullptr) { + type = oms_signal_type_string; + } + else if(var->Input->Binary!= nullptr) { + // TODO: Support binary variables + } + } + else if(var->Output != nullptr) { + dcpCausality = dcpCausalityOutput; + if(var->Output->Float64 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT64; + } + else if(var->Output->Float32 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT32; + } + else if(var->Output->Int64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT64; + } + else if(var->Output->Int32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT32; + } + else if(var->Output->Int16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT16; + } + else if(var->Output->Int8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT8; + } + else if(var->Output->Uint64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT64; + } + else if(var->Output->Uint32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT32; + } + else if(var->Output->Uint16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT16; + } + else if(var->Output->Uint8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT8; + } + else if(var->Output->String!= nullptr) { + type = oms_signal_type_string; + } + else if(var->Output->Binary!= nullptr) { + // TODO: Support binary variables + } + } + else if(var->Parameter != nullptr) { + dcpCausality = dcpCausalityParameter; + if(var->Parameter->Float64 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT64; + } + else if(var->Parameter->Float32 != nullptr) { + type = oms_signal_type_real; + numericType = oms_signal_numeric_type_FLOAT32; + } + else if(var->Parameter->Int64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT64; + } + else if(var->Parameter->Int32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT32; + } + else if(var->Parameter->Int16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT16; + } + else if(var->Parameter->Int8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_INT8; + } + else if(var->Parameter->Uint64!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT64; + } + else if(var->Parameter->Uint32!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT32; + } + else if(var->Parameter->Uint16!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT16; + } + else if(var->Parameter->Uint8!= nullptr) { + type = oms_signal_type_integer; + numericType = oms_signal_numeric_type_UINT8; + } + else if(var->Parameter->String!= nullptr) { + type = oms_signal_type_string; + } + else if(var->Parameter->Binary!= nullptr) { + // TODO: Support binary variables + } + } + else if(var->StructuralParameter != nullptr) { + dcpCausality = dcpCausalityStructuralParameter; + if(var->StructuralParameter->Uint64!= nullptr) { + dcpDataType = dcpUInt64; + } + else if(var->StructuralParameter->Uint32!= nullptr) { + dcpDataType = dcpUInt32; + } + else if(var->StructuralParameter->Uint16!= nullptr) { + dcpDataType = dcpUInt16; + } + else if(var->StructuralParameter->Uint8!= nullptr) { + dcpDataType = dcpUInt8; + } + } + dcpVariability = var->variability; +} + oms::Variable::~Variable() { } diff --git a/src/OMSimulatorLib/Variable.h b/src/OMSimulatorLib/Variable.h index 5c4f3e502..becb58bf4 100644 --- a/src/OMSimulatorLib/Variable.h +++ b/src/OMSimulatorLib/Variable.h @@ -39,17 +39,26 @@ #include "ComRef.h" #include "Connector.h" #include "OMSimulator/Types.h" +#include "dcp/model/DcpTypes.hpp" +#include "dcp/xml/DcpSlaveDescriptionElements.hpp" #include #include #include +// TODO: Maybe move this to separate file (dcp) +typedef enum {dcpCausalityInput, dcpCausalityOutput, dcpCausalityParameter, dcpCausalityLocal, dcpCausalityStructuralParameter } dcpCausality_t; +typedef enum {dcpFloat64, dcpFloat32, dcpInt64, dcpInt32, dcpInt16, dcpInt8, dcpUInt64, dcpUInt32, dcpUInt16, dcpUInt8, dcpString, dcpBinary} dcpDataType_t; + +class SlaveDescription_t; + namespace oms { class Variable { public: Variable(fmiHandle * fmi4c, int index, oms_component_enu_t componentType); + Variable(SlaveDescription_t *desc, int index); //For DCP components, component type is implicit ~Variable(); void markAsState(size_t der_index) { is_state = true; this->der_index = der_index; } @@ -91,6 +100,7 @@ namespace oms fmi2ValueReference getValueReference() const { return fmi2Vr; } fmi3ValueReference getValueReferenceFMI3() const { return fmi3Vr; } + valueReference_t getValueReferenceDCP() const { return dcpVr; } oms_signal_type_enu_t getType() const { return type; } oms_signal_numeric_type_enu_t getNumericType() const {return numericType;} const std::string& getDescription() const { return description; } @@ -110,6 +120,7 @@ namespace oms void configureFMI2Variable(fmiHandle *fmi4c, int index); void configureFMI3Variable(fmiHandle *fmi4c, int index); + void configureDCPVariable(SlaveDescription_t *desc, int index); ComRef cref; std::string description; @@ -127,6 +138,12 @@ namespace oms fmi3Variability fmi3Variability_; fmi3Initial fmi3InitialProperty; + // DCP specific members + valueReference_t dcpVr; + dcpCausality_t dcpCausality; + Variability dcpVariability; + dcpDataType_t dcpDataType; + bool is_state; bool is_der; bool is_continuous_time_state; diff --git a/src/OMSimulatorPython/CMakeLists.txt b/src/OMSimulatorPython/CMakeLists.txt index 7fb870274..14c7f4b08 100644 --- a/src/OMSimulatorPython/CMakeLists.txt +++ b/src/OMSimulatorPython/CMakeLists.txt @@ -33,6 +33,7 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" "${CMAKE_CURRENT_SOURCE_DIR}/connection.py" "${CMAKE_CURRENT_SOURCE_DIR}/connector.py" "${CMAKE_CURRENT_SOURCE_DIR}/cref.py" + "${CMAKE_CURRENT_SOURCE_DIR}/dcp.py" "${CMAKE_CURRENT_SOURCE_DIR}/elementgeometry.py" "${CMAKE_CURRENT_SOURCE_DIR}/enumeration.py" "${CMAKE_CURRENT_SOURCE_DIR}/fmu.py" diff --git a/src/OMSimulatorPython/dcp.py b/src/OMSimulatorPython/dcp.py new file mode 100644 index 000000000..787603b86 --- /dev/null +++ b/src/OMSimulatorPython/dcp.py @@ -0,0 +1,228 @@ +import zipfile +import logging +from pathlib import Path +from typing import Union + +from lxml import etree as ET +from OMSimulator.connector import Connector +from OMSimulator.unit import Unit +from OMSimulator.variable import Variable, SignalType +from OMSimulator import namespace + +logger = logging.getLogger(__name__) + +class DCP: + def __init__(self, dcp_path: Union[str, Path]): + '''Initialize the DCP by loading dcpSlaveDescription.xml from the DCP archive.''' + self._dcp_path = Path(dcp_path) + self._dcpMajorVersion = None + self._dcpMinorVersion = None + self._salveName = None + self._uuid = None + self._variableNamingConvention = None + self._variables = [] + + self._load_slave_description() + + @property + def dcpVersion(self): + return self._dcpVersion + + @property + def slaveName(self): + return self._slaveName + + @property + def fmuType(self): + return "dcp" + + @property + def uuid(self): + return self._uuid + + @property + def description(self): + return self._description + + @property + def variableNamingConvention(self): + return self._variableNamingConvention + + @property + def variables(self): + return self._variables + + def _load_slave_description(self): + '''Extract and parse the dcpSlaveDescription.xml from the DCP zip archive.''' + if not self._dcp_path.exists(): + raise FileNotFoundError(f'DCP file not found: {self._dcp_path}') + + try: + with zipfile.ZipFile(self._dcp_path, 'r') as dcp_zip: + # Find dcpSlaveDescription.dcpx in the DCP archive + slave_desc_name = 'v1.0/dcpSlaveDescription.dcpx' + if slave_desc_name not in dcp_zip.namelist(): + raise FileNotFoundError(f'Missing {slave_desc_name} in {self._dcp_path}') + + # Read and parse dcpSlaveDescription.dcpx + with dcp_zip.open(slave_desc_name) as slave_desc_file: + xml_content = slave_desc_file.read() + slave_description = ET.fromstring(xml_content) # May raise XMLSyntaxError + + # Parse slaveName, uuid, ... + self._dcpMajorVersion = slave_description.get('dcpMajorVersion') + self._dcpMinorVersion = slave_description.get('dcpMinorVersion') + self._slaveName = slave_description.get('dcpSlaveName') + self._uuid = slave_description.get('uuid') + self._variableNamingConvention = slave_description.get('variableNamingConvention') + + # Parse variables + self._parse_variables(slave_description) + + except ET.XMLSyntaxError as e: + raise ValueError(f'Error parsing {slave_desc_name}: {e}') + + def _parse_variables(self, slave_description): + '''Parses variables from the ModelVariables section of modelDescription.xml''' + scalar_variables = slave_description.xpath('//Variables/Variable') + for scalar_var in scalar_variables: + name = scalar_var.get('name') + description = scalar_var.get('description') + value_reference = scalar_var.get('valueReference') + variability = scalar_var.get('variability', 'continuous') + + causality = None + var_type = None + start = None + unit = None + + causality_element = scalar_var.xpath('./*') + if causality_element: + causality_element = causality_element[0] + causality = causality_element.tag.lower() + print("Causality: "+causality) + + # Find the first child (type node) and extract attributes + type_element = causality_element.xpath('./*') # Selects the first child element + if type_element: + type_element = type_element[0] # Get first match + var_type = type_element.tag + if var_type == "Float64": + var_type = "Real" #HACK, only FMI2 types are currently supported + start = type_element.get('start') + unit = type_element.get('unit') + + # Create and store the variable + variable = Variable(name, description, value_reference, causality, variability, var_type, unit, start) + + # Assign unit definitions if applicable + if unit: + for defined_unit in self._unitDefinitions: + if defined_unit.name == variable.unit: + variable.unitDefinition.append(defined_unit) + + self._variables.append(variable) + + # Raise an error if ModelVariables section is missing + if not scalar_variables: + raise Exception('ModelVariables section not found in modelDescription.xml') + + def _parse_units(self, slave_description): + '''Extracts unit definitions from modelDescription.xml.''' + self._unitDefinitions = [] + + unit_elements = slave_description.xpath('//UnitDefinitions/Unit') + for unit_elem in unit_elements: + name = unit_elem.get('name') + + base_units = { + key: value + for base_unit in unit_elem.xpath('./BaseUnit') + for key, value in base_unit.attrib.items() + } + + self._unitDefinitions.append(Unit(name, base_units)) + + def makeConnectors(self): + connectors = [] + for var in self.variables: + if var.isInput() or var.isOutput() or var.isParameter() or var.isCalculatedParameter(): + connector = Connector(var.name, var.causality, var.signal_type) + connector.setUnit(var.unit) + connector.description = var.description + connectors.append(connector) + return connectors + + def varExist(self, cref: str) -> bool: + return any(var.name == cref for var in self.variables) + + def export_units_to_ssd(self, node): + '''Exports all unit definitions to an SSD XML node.''' + for unit in self._unitDefinitions: + unit.exportToSSD(node) + + def exportSSVTemplate(self, filename: Path | None = None): + if filename is None: + filename = self.modelName + '.ssv' + + ssv_node = ET.Element(namespace.tag("ssv", "ParameterSet"), + nsmap={"ssc": "http://ssp-standard.org/SSP1/SystemStructureCommon", + "ssv": "http://ssp-standard.org/SSP1/SystemStructureParameterValues"}, + version = "2.0", + name = "parameters") + parameters_node = ET.SubElement(ssv_node, namespace.tag("ssv", "Parameters")) + ## extract variables with start values + for var in self.variables: + if var.isInput() or var.isParameter() or var.isCalculatedParameter(): + parameter_node = ET.SubElement(parameters_node, namespace.tag("ssv", "Parameter")) + parameter_node.set("name", str(var.name)) + if var.description: + parameter_node.set("description", var.description) + + match var.signal_type: + case SignalType.Real: + type_tag = "Real" + case SignalType.Boolean: # Check for boolean first, because it is a subclass of int + type_tag = "Boolean" + case SignalType.Integer: + type_tag = "Integer" + case SignalType.String: + type_tag = "String" + case _: + raise TypeError(f"Unsupported type: {type(var.signal_type)}") + parameter_type = ET.SubElement(parameter_node, namespace.tag("ssv", type_tag)) + parameter_type.set("value", str(var.modelDescriptionStartValue)) + if var.unit is not None: + parameter_type.set("unit", str(var.unit)) + + + xml = ET.tostring(ssv_node, encoding='utf-8', xml_declaration=True, pretty_print=True).decode('utf-8') + + ## write to filesystem + with open(Path(filename).resolve(), "w", encoding="utf-8") as file: + file.write(xml) + logger.info(f"SSV template '{filename}' successfully exported!") + + def exportSSMTemplate(self, filename : Path | None = None): + if filename is None: + filename = self.modelName + '.ssm' + + ssm_node = ET.Element(namespace.tag("ssm", "ParameterMapping"), + nsmap={"ssc": "http://ssp-standard.org/SSP1/SystemStructureCommon", + "ssm": "http://ssp-standard.org/SSP1/SystemStructureParameterMapping"}, + version = "2.0") + + for var in self.variables: + if var.isInput() or var.isParameter() or var.isCalculatedParameter(): + ssm_mapping_node = ET.SubElement(ssm_node, namespace.tag("ssm", "MappingEntry")) + ssm_mapping_node.set("source", "") + ssm_mapping_node.set("target", str(var.name)) + + + + xml = ET.tostring(ssm_node, encoding='utf-8', xml_declaration=True, pretty_print=True).decode('utf-8') + + ## write to filesystem + with open(Path(filename).resolve(), "w", encoding="utf-8") as file: + file.write(xml) + logger.info(f"SSM template '{filename}' successfully exported!") diff --git a/src/OMSimulatorPython/ssp.py b/src/OMSimulatorPython/ssp.py index 1001282cf..f83214dc1 100644 --- a/src/OMSimulatorPython/ssp.py +++ b/src/OMSimulatorPython/ssp.py @@ -43,6 +43,7 @@ from OMSimulator.ssv import SSV from OMSimulator.ssm import SSM from OMSimulator.componenttable import ResultReader +from OMSimulator.dcp import DCP from OMSimulator import SSD, CRef, namespace from lxml import etree as ET @@ -144,6 +145,8 @@ def _addResource(self, filename: str, new_name: str | None = None, validate=True self.resources[str(new_name)] = SSM(ssm_path = filePath) elif Path(filename).suffix == ".csv" or Path(filename).suffix == ".mat": self.resources[str(new_name)] = ResultReader(filePath = filePath) + elif Path(filename).suffix == ".dcp": + self.resources[str(new_name)] = DCP(dcp_path = filePath) ##TODO check for .ssv file and if ssv instances provided else: self.resources[str(new_name)] = new_name diff --git a/src/OMSimulatorPython/system.py b/src/OMSimulatorPython/system.py index 647ed9105..d4610b61a 100644 --- a/src/OMSimulatorPython/system.py +++ b/src/OMSimulatorPython/system.py @@ -697,6 +697,8 @@ def generateJson(self, resources: dict | None = None, tempdir : str | None = Non return json_string def processElements(self, elements_dict: dict, connections: list, data: dict, solver_groups : defaultdict, componentSolver : dict, solver_connections : defaultdict, resources :dict, tempdir : str, systemName = None): + print("Resources: ") + print(resources) """Processes the elements and connections in the system.""" for key, element in elements_dict.items(): if isinstance(element, Component): diff --git a/src/OMSimulatorPython/variable.py b/src/OMSimulatorPython/variable.py index 1a6b405b1..f976ef2b7 100644 --- a/src/OMSimulatorPython/variable.py +++ b/src/OMSimulatorPython/variable.py @@ -114,6 +114,7 @@ def __init__(self, name: Union[str, CRef], description : str, valueReference: Un self.name = CRef(name) self.description = description self.valueReference = int(valueReference) + print("Variable causality: "+causality) self.causality = causality if isinstance(causality, Causality) else Causality[causality] self.variability = variability self.signal_type = signal_type if isinstance(signal_type, SignalType) else SignalType[signal_type] From 4fd2ec071731eed05245e9dd3abcc1c744bc34bc Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 11 May 2026 11:54:28 +0200 Subject: [PATCH 02/10] Fix 3rdParty issues --- 3rdParty | 2 +- include/OMSimulator/Types.h | 4 ---- src/OMSimulatorLib/CMakeLists.txt | 5 ++--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/3rdParty b/3rdParty index d6ca45aee..d8069dce6 160000 --- a/3rdParty +++ b/3rdParty @@ -1 +1 @@ -Subproject commit d6ca45aeea2df2cb2cb283ee35e3f8269418bb72 +Subproject commit d8069dce6002ccb850a78155f33884952e87be90 diff --git a/include/OMSimulator/Types.h b/include/OMSimulator/Types.h index f9f32d549..f6cf2514f 100644 --- a/include/OMSimulator/Types.h +++ b/include/OMSimulator/Types.h @@ -105,12 +105,8 @@ typedef enum { typedef enum { oms_component_none, oms_component_fmu, ///< FMU -<<<<<<< HEAD - oms_component_fmu3, ///< FMU3 -======= oms_component_fmu3, ///< FMU3 oms_component_dcp, ///< DCP ->>>>>>> d03ad7e5 (Initial DCP support (wip)) oms_component_table, ///< lookup table oms_component_external ///< external model } oms_component_enu_t; diff --git a/src/OMSimulatorLib/CMakeLists.txt b/src/OMSimulatorLib/CMakeLists.txt index e5da2f420..0f1d2d47c 100644 --- a/src/OMSimulatorLib/CMakeLists.txt +++ b/src/OMSimulatorLib/CMakeLists.txt @@ -85,9 +85,8 @@ target_link_libraries(OMSimulatorLib oms::3rd::lua oms::3rd::pugixml::header oms::3rd::json::header - oms::3rd::ctpl::header) - oms::3rd::xerces oms::3rd::ctpl::header + oms::3rd::xerces oms::3rd::libzip oms::3rd::dcplib::core oms::3rd::dcplib::master @@ -125,7 +124,7 @@ target_link_libraries(OMSimulatorLib_static oms::3rd::lua oms::3rd::pugixml::header oms::3rd::json::header - oms::3rd::ctpl::header) + oms::3rd::ctpl::header oms::3rd::xerces oms::3rd::ctpl::header oms::3rd::libzip From a3b2fc2719772b6f3076964614fd66e4fe5b8d9b Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Tue, 12 May 2026 10:42:33 +0200 Subject: [PATCH 03/10] DCP simulation (wip) --- src/OMSimulatorLib/ComponentDCP.cpp | 20 ++++++++++++-------- src/OMSimulatorLib/ComponentDCP.h | 2 +- src/OMSimulatorLib/Model.cpp | 11 +++++++++++ src/OMSimulatorLib/OMSimulator.cpp | 2 ++ src/OMSimulatorLib/SystemSC3.cpp | 2 ++ src/OMSimulatorLib/SystemWC.cpp | 6 ++++++ src/OMSimulatorLib/Values.cpp | 2 +- src/OMSimulatorPython/dcp.py | 3 ++- src/OMSimulatorPython/system.py | 9 +++++++++ 9 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp index d249473ec..f8d890613 100644 --- a/src/OMSimulatorLib/ComponentDCP.cpp +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -469,8 +469,10 @@ oms_status_enu_t oms::ComponentDCP::exportToSSD(pugi::xml_node &node, Snapshot & oms_status_enu_t oms::ComponentDCP::initialize() { - // TODO: Implement (dcp) - return oms_status_fatal; + logInfo("Initialize DCP component " + std::string(getCref())); //DCP debug + + //Nothing to be done (I think?) + return oms_status_ok; } oms_status_enu_t oms::ComponentDCP::instantiate() @@ -501,14 +503,16 @@ oms_status_enu_t oms::ComponentDCP::instantiate() oms_status_enu_t oms::ComponentDCP::registerSignalsForResultFile(ResultWriter &resultFile) { - // TODO: Implement (dcp) - return oms_status_fatal; + logInfo("Enter registerSignalsForResultFile for " + std::string(getFullCref())); //DCP debug + + // Not sure if this is needed + return oms_status_ok; } oms_status_enu_t oms::ComponentDCP::removeSignalsFromResults(const char *regex) { - // TODO: Implement (dcp) - return oms_status_fatal; + // Not sure if this is needed + return oms_status_ok; } oms_status_enu_t oms::ComponentDCP::reset() @@ -525,8 +529,8 @@ oms_status_enu_t oms::ComponentDCP::terminate() oms_status_enu_t oms::ComponentDCP::updateSignals(ResultWriter &resultWriter) { - // TODO: Implement (dcp) - return oms_status_fatal; + //Not sure if this is needed + return oms_status_ok; } void oms::ComponentDCP::getFilteredSignals(std::vector &filteredSignals) const diff --git a/src/OMSimulatorLib/ComponentDCP.h b/src/OMSimulatorLib/ComponentDCP.h index d2d0ad777..073de62e0 100644 --- a/src/OMSimulatorLib/ComponentDCP.h +++ b/src/OMSimulatorLib/ComponentDCP.h @@ -68,7 +68,7 @@ class ComponentDCP : public Component oms_status_enu_t exportToSSD(pugi::xml_node& node, Snapshot& snapshot, std::string variantName) const; oms_status_enu_t initialize(); oms_status_enu_t instantiate(); - oms_status_enu_t stepUntil(double stopTime) { return logError_NotImplemented; } + oms_status_enu_t stepUntil(double stopTime) { return oms_status_ok; } oms_status_enu_t registerSignalsForResultFile(ResultWriter& resultFile); oms_status_enu_t removeSignalsFromResults(const char* regex); oms_status_enu_t reset(); diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index f3f6435e5..f015b6e96 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -48,6 +48,7 @@ #include "minizip.h" #include #include /* std::unique and std::find are defined here */ +#include //Only for debug output, can remove later oms::Model::Model(const oms::ComRef& cref, const std::string& tempDir) : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat") @@ -1193,6 +1194,8 @@ oms_status_enu_t oms::Model::instantiate() oms_status_enu_t oms::Model::initialize() { + logInfo("Enter initialize() for model " + std::string(getCref())); //DCP debug + if (!validState(oms_modelState_instantiated)) return logError_ModelInWrongState(getCref()); @@ -1242,6 +1245,8 @@ oms_status_enu_t oms::Model::initialize() return logError_Initialization(system->getFullCref()); } + logInfo("Signals registered for result file"); //DCP debug + // create result file if (!resultFile->create(resultFilename, startTime, stopTime)) { @@ -1253,6 +1258,8 @@ oms_status_enu_t oms::Model::initialize() return logError_Initialization(system->getFullCref()); } + logInfo("Result file created successfully"); //DCP debug + // dump results emit(startTime, true); } @@ -1266,6 +1273,8 @@ oms_status_enu_t oms::Model::initialize() oms_status_enu_t oms::Model::simulate() { + logInfo("Test print (logInfo)"); + clock.tic(); if (!validState(oms_modelState_simulation)) { @@ -1279,6 +1288,8 @@ oms_status_enu_t oms::Model::simulate() return logError("Model doesn't contain a system"); } + // DCPTODO: If system contains DCP components, start a DCP simulation where the system is wrapped as a DCP slave. + oms_status_enu_t status = system->stepUntil(stopTime); emit(stopTime, true); clock.toc(); diff --git a/src/OMSimulatorLib/OMSimulator.cpp b/src/OMSimulatorLib/OMSimulator.cpp index 7c9f55387..361ced354 100644 --- a/src/OMSimulatorLib/OMSimulator.cpp +++ b/src/OMSimulatorLib/OMSimulator.cpp @@ -941,6 +941,8 @@ oms_status_enu_t oms_instantiate(const char* cref_) oms_status_enu_t oms_initialize(const char* cref_) { + logInfo("Enter oms_initialize(\"" + std::string(cref_) + "\")"); + oms::ComRef cref(cref_); oms::Model* model = oms::Scope::GetInstance().getModel(cref); diff --git a/src/OMSimulatorLib/SystemSC3.cpp b/src/OMSimulatorLib/SystemSC3.cpp index c4cd4862e..fa73a8ab0 100644 --- a/src/OMSimulatorLib/SystemSC3.cpp +++ b/src/OMSimulatorLib/SystemSC3.cpp @@ -247,6 +247,8 @@ oms_status_enu_t oms::SystemSC3::instantiate() oms_status_enu_t oms::SystemSC3::initialize() { + logInfo("Enter initialize() for system " + std::string(getCref())); //DCP debug + // testFMI3ME(fmus[0]->getFMU()); // exit(0); clock.reset(); diff --git a/src/OMSimulatorLib/SystemWC.cpp b/src/OMSimulatorLib/SystemWC.cpp index a634b232a..e1a2525bc 100644 --- a/src/OMSimulatorLib/SystemWC.cpp +++ b/src/OMSimulatorLib/SystemWC.cpp @@ -230,6 +230,8 @@ unsigned int oms::SystemWC::getMaxOutputDerivativeOrder() oms_status_enu_t oms::SystemWC::initialize() { + logInfo("Enter initialize() for system " + std::string(getCref())); //DCP debug + clock.reset(); CallClock callClock(clock); @@ -247,6 +249,8 @@ oms_status_enu_t oms::SystemWC::initialize() if (oms_status_ok != component.second->initialize()) return oms_status_error; + logInfo("All components initialized"); //DCP debug + // prepare data structures for simulation if (solverMethod == oms_solver_wc_mav || solverMethod == oms_solver_wc_mav2) { @@ -298,6 +302,8 @@ oms_status_enu_t oms::SystemWC::initialize() // mark algebraic loops to be updated on next call forceLoopsToBeUpdated(); + logInfo("Initialization finished successfully"); //DCP debug + return oms_status_ok; } diff --git a/src/OMSimulatorLib/Values.cpp b/src/OMSimulatorLib/Values.cpp index e0d55b7a0..bec31aec4 100644 --- a/src/OMSimulatorLib/Values.cpp +++ b/src/OMSimulatorLib/Values.cpp @@ -38,7 +38,7 @@ #include "Logging.h" #include "ssd/Tags.h" #include "Util.h" -#include "XercesValidator.h" +//#include "XercesValidator.h" #include "dcp/zip/DcpSlaveReader.hpp" #include diff --git a/src/OMSimulatorPython/dcp.py b/src/OMSimulatorPython/dcp.py index 787603b86..8238ffc06 100644 --- a/src/OMSimulatorPython/dcp.py +++ b/src/OMSimulatorPython/dcp.py @@ -111,9 +111,10 @@ def _parse_variables(self, slave_description): var_type = "Real" #HACK, only FMI2 types are currently supported start = type_element.get('start') unit = type_element.get('unit') + declaredType = type_element.get('declaredType') # Create and store the variable - variable = Variable(name, description, value_reference, causality, variability, var_type, unit, start) + variable = Variable(name, description, value_reference, causality, variability, var_type, unit, start, declaredType) # Assign unit definitions if applicable if unit: diff --git a/src/OMSimulatorPython/system.py b/src/OMSimulatorPython/system.py index d4610b61a..bf70276c1 100644 --- a/src/OMSimulatorPython/system.py +++ b/src/OMSimulatorPython/system.py @@ -43,6 +43,7 @@ from OMSimulator.elementgeometry import ElementGeometry from OMSimulator.fmu import FMU from OMSimulator.ssm import SSM +from OMSimulator.dcp import DCP from OMSimulator.values import Values from OMSimulator import Capi, CRef, namespace, utils @@ -251,6 +252,14 @@ def addComponent(self, cref: CRef, resource: str, inst = None): componenttable = ComponentTable(first, resource, inst.connectors) self.elements[first] = componenttable return componenttable + elif isinstance(inst, DCP) or (inst is None and resource.endswith(".dcp")): + connectors = inst.makeConnectors() if inst else list() + unitDefinitions = list() + enumerationDefinitions = list() + component = Component(first, resource, connectors, unitDefinitions, enumerationDefinitions) + component.fmuType = inst.fmuType if inst else None + self.elements[first] = component + return component else: raise TypeError( f"Unknown component instance for '{first}' in '{self.name}'. " f"Please add the component from the top-level model.") From 74c87f6b32b1fa822d8ba963193bc35c378efe06 Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 18 May 2026 12:21:16 +0200 Subject: [PATCH 04/10] Generate DCP file for internal system --- src/OMSimulatorLib/ComRef.cpp | 13 ++ src/OMSimulatorLib/ComRef.h | 1 + src/OMSimulatorLib/ComponentDCP.cpp | 184 -------------------- src/OMSimulatorLib/ComponentDCP.h | 19 -- src/OMSimulatorLib/Model.cpp | 119 ++++++++++++- src/OMSimulatorLib/OMSimulator.cpp | 2 - src/OMSimulatorLib/System.cpp | 17 +- src/OMSimulatorLib/System.h | 2 + src/OMSimulatorLib/SystemWC.cpp | 6 - src/OMSimulatorLib/Util.h | 33 ++++ src/OMSimulatorLib/Variable.cpp | 52 +++++- src/OMSimulatorLib/Variable.h | 6 +- src/OMSimulatorPython/instantiated_model.py | 3 + 13 files changed, 232 insertions(+), 225 deletions(-) diff --git a/src/OMSimulatorLib/ComRef.cpp b/src/OMSimulatorLib/ComRef.cpp index 3e1881ab1..fcd401ce9 100644 --- a/src/OMSimulatorLib/ComRef.cpp +++ b/src/OMSimulatorLib/ComRef.cpp @@ -185,6 +185,19 @@ oms::ComRef oms::ComRef::front() const return *this; } +oms::ComRef oms::ComRef::back() const +{ + for (int i=size()-1; i>=0; --i) + { + if (cref[i] == '.') + return oms::ComRef(cref+i+1); + else if (cref[i] == ':') + break; + } + + return *this; +} + oms::ComRef oms::ComRef::pop_front() { for (int i=0; cref[i]; ++i) diff --git a/src/OMSimulatorLib/ComRef.h b/src/OMSimulatorLib/ComRef.h index c29056679..2b9d44d6e 100644 --- a/src/OMSimulatorLib/ComRef.h +++ b/src/OMSimulatorLib/ComRef.h @@ -67,6 +67,7 @@ namespace oms bool isRootOf(ComRef child) const; ComRef front() const; ///< returns the first part of the ComRef (including suffix if its the only part) + ComRef back() const; ///< returns the last part of the ComRef (including suffix if its the only part) ComRef pop_front(); ///< returns the first part of the ComRef and removed it from the current object std::string suffix() const; ///< returns the suffix as string diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp index f8d890613..21c1ff194 100644 --- a/src/OMSimulatorLib/ComponentDCP.cpp +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -55,165 +55,6 @@ oms::ComponentDCP::ComponentDCP(const ComRef& cref, System* parentSystem, const { } -void oms::ComponentDCP::dcp_initialize() -{ - manager->STC_initialize(1, DcpState::CONFIGURED); - intializationRuns++; -} - -void oms::ComponentDCP::dcp_configuration() -{ - std::cout << "Configure Slaves" << std::endl; - receivedAcks[1] = 0; - - manager->CFG_scope(1, 1, DcpScope::Initialization_Run_NonRealTime); - - uint8_t dataId = 0; - for(const auto &var : this->allVariables) { - if(var.isOutput()) { - manager->CFG_scope(1, dataId, DcpScope::Initialization_Run_NonRealTime); - manager->CFG_output(1, dataId, 0, var.getValueReferenceDCP()); - manager->CFG_steps(1, dataId, 1); - dataId++; - } - else if(var.isInput()) { - DcpDataType dataType = DcpDataType::uint8; - if(var.getNumericType() == oms_signal_numeric_type_FLOAT64) { - dataType = DcpDataType::float64; - } - else if(var.getNumericType() == oms_signal_numeric_type_FLOAT64) { - dataType = DcpDataType::float64; - } - else if(var.getNumericType() == oms_signal_numeric_type_FLOAT32) { - dataType = DcpDataType::float32; - } - else if(var.getNumericType() == oms_signal_numeric_type_INT64) { - dataType = DcpDataType::int64; - } - else if(var.getNumericType() == oms_signal_numeric_type_INT32) { - dataType = DcpDataType::int32; - } - else if(var.getNumericType() == oms_signal_numeric_type_INT16) { - dataType = DcpDataType::int16; - } - else if(var.getNumericType() == oms_signal_numeric_type_INT8) { - dataType = DcpDataType::int8; - } - else if(var.getNumericType() == oms_signal_numeric_type_UINT64) { - dataType = DcpDataType::uint64; - } - else if(var.getNumericType() == oms_signal_numeric_type_UINT32) { - dataType = DcpDataType::uint32; - } - else if(var.getNumericType() == oms_signal_numeric_type_UINT16) { - dataType = DcpDataType::uint16; - } - else if(var.getNumericType() == oms_signal_numeric_type_UINT8) { - dataType = DcpDataType::uint8; - } - - manager->CFG_scope(1, dataId, DcpScope::Initialization_Run_NonRealTime); - manager->CFG_input(1,dataId, 0, var.getValueReferenceDCP(), dataType); - manager->CFG_steps(1, dataId, 1); - dataId++; - } - // TODO: Handle parameters somehow - - } - - manager->CFG_steps(1, 1, 1); - manager->CFG_time_res(1, desc->TimeRes.resolutions.front().numerator, - desc->TimeRes.resolutions.front().denominator); - manager->CFG_source_network_information_UDP(1, 1, asio::ip::address_v4::from_string( - *desc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), *desc->TransportProtocols.UDP_IPv4->Control->port); - manager->CFG_target_network_information_UDP(1, 1, asio::ip::address_v4::from_string( - *desc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), *desc->TransportProtocols.UDP_IPv4->Control->port); -} - -void oms::ComponentDCP::dcp_configure() -{ - -} - -void oms::ComponentDCP::dcp_run(DcpState currentState) -{ - -} - -void oms::ComponentDCP::dcp_doStep() -{ - -} - -void oms::ComponentDCP::dcp_stop() -{ - -} - -void oms::ComponentDCP::dcp_deregister() -{ - -} - -void oms::ComponentDCP::dcp_sendOutputs(DcpState currentState, uint8_t sender) -{ - -} - -void oms::ComponentDCP::dcp_receiveAck(uint8_t sender, uint16_t) -{ - -} - -void oms::ComponentDCP::dcp_receiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode) -{ - -} - -void oms::ComponentDCP::dcp_dataReceived(uint16_t dataId, size_t length, uint8_t payload[]) -{ - -} - -void oms::ComponentDCP::dcp_receiveStateChangedNotification(uint8_t sender, DcpState state) -{ - std::chrono::milliseconds dura(250); - //std::this_thread::sleep_for(dura); - switch (state) { - case DcpState::CONFIGURATION: - dcp_configuration(); - break; - case DcpState::CONFIGURED: - if (intializationRuns < maxInitRuns) { - dcp_initialize(); - - } else { - dcp_run(DcpState::CONFIGURED); - } - break; - case DcpState::SYNCHRONIZED: - dcp_run(DcpState::SYNCHRONIZED); - break; - case DcpState::PREPARED: - dcp_configure(); - break; - - case DcpState::INITIALIZED: - dcp_sendOutputs(DcpState::INITIALIZED, sender); - break; - - case DcpState::RUNNING: - dcp_stop(); - break; - case DcpState::STOPPED: - dcp_deregister(); - break; - case DcpState::ALIVE: - //Do something? - break; - } -} - oms::ComponentDCP::~ComponentDCP() { } @@ -469,35 +310,12 @@ oms_status_enu_t oms::ComponentDCP::exportToSSD(pugi::xml_node &node, Snapshot & oms_status_enu_t oms::ComponentDCP::initialize() { - logInfo("Initialize DCP component " + std::string(getCref())); //DCP debug - //Nothing to be done (I think?) return oms_status_ok; } oms_status_enu_t oms::ComponentDCP::instantiate() { - //TODO (dcp): Host and port should not be hard-coded - std::string host = "127.0.0.1"; - int port = 8180; - - OstreamLog stdLog(std::cout); - - driver = new UdpDriver(host, port_t(port)); - manager = new DcpManagerMaster(driver->getDcpDriver()); - manager->setAckReceivedListener( - std::bind(&oms::ComponentDCP::dcp_receiveAck, this, std::placeholders::_1, std::placeholders::_2)); - manager->setNAckReceivedListener( - std::bind(&oms::ComponentDCP::dcp_receiveNAck, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3)); - manager->setStateChangedNotificationReceivedListener( - std::bind(&oms::ComponentDCP::dcp_receiveStateChangedNotification, this, std::placeholders::_1, - std::placeholders::_2)); - manager->setDataReceivedListener( - std::bind(&oms::ComponentDCP::dcp_dataReceived, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - manager->addLogListener(std::bind(&OstreamLog::logOstream, stdLog, std::placeholders::_1)); - manager->setGenerateLogString(true); - return oms_status_ok; } @@ -517,13 +335,11 @@ oms_status_enu_t oms::ComponentDCP::removeSignalsFromResults(const char *regex) oms_status_enu_t oms::ComponentDCP::reset() { - manager->stop(); return oms_status_ok; } oms_status_enu_t oms::ComponentDCP::terminate() { - manager->stop(); return oms_status_ok; } diff --git a/src/OMSimulatorLib/ComponentDCP.h b/src/OMSimulatorLib/ComponentDCP.h index 073de62e0..65013ac67 100644 --- a/src/OMSimulatorLib/ComponentDCP.h +++ b/src/OMSimulatorLib/ComponentDCP.h @@ -100,25 +100,6 @@ class ComponentDCP : public Component double time; - void dcp_initialize(); - void dcp_configuration(); - void dcp_configure(); - void dcp_run(DcpState currentState); - void dcp_doStep(); - void dcp_stop(); - void dcp_deregister(); - void dcp_sendOutputs(DcpState currentState, uint8_t sender); - void dcp_receiveAck(uint8_t sender, uint16_t); - void dcp_receiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode); - void dcp_dataReceived(uint16_t dataId, size_t length, uint8_t payload[]); - void dcp_receiveStateChangedNotification(uint8_t sender, DcpState state); - - UdpDriver *driver; - DcpManagerMaster *manager; - uint8_t maxInitRuns = 0; - uint8_t intializationRuns = 1; - std::map receivedAcks; - oms::ComRef getValidCref(ComRef cref); }; } diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index f015b6e96..30229e0a5 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -44,12 +44,18 @@ #include "Scope.h" #include "ssd/Tags.h" #include "System.h" +#include "Util.h" #include "minizip.h" #include #include /* std::unique and std::find are defined here */ #include //Only for debug output, can remove later +//#include "dcp/xml/DcpSlaveDescriptionWriter.hpp" +#include +#include +#include + oms::Model::Model(const oms::ComRef& cref, const std::string& tempDir) : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat") { @@ -1194,8 +1200,6 @@ oms_status_enu_t oms::Model::instantiate() oms_status_enu_t oms::Model::initialize() { - logInfo("Enter initialize() for model " + std::string(getCref())); //DCP debug - if (!validState(oms_modelState_instantiated)) return logError_ModelInWrongState(getCref()); @@ -1245,8 +1249,6 @@ oms_status_enu_t oms::Model::initialize() return logError_Initialization(system->getFullCref()); } - logInfo("Signals registered for result file"); //DCP debug - // create result file if (!resultFile->create(resultFilename, startTime, stopTime)) { @@ -1258,8 +1260,6 @@ oms_status_enu_t oms::Model::initialize() return logError_Initialization(system->getFullCref()); } - logInfo("Result file created successfully"); //DCP debug - // dump results emit(startTime, true); } @@ -1290,6 +1290,113 @@ oms_status_enu_t oms::Model::simulate() // DCPTODO: If system contains DCP components, start a DCP simulation where the system is wrapped as a DCP slave. + //Loop over system components and check if any of them is a DCP component. If yes, start a DCP simulation where the system is wrapped as a DCP slave. + bool dcpComponentDetected = false; + std::vector dcpComponents; + + for (const auto& component : system->getComponents()) + { + if (component.second->getType() == oms_component_dcp) + { + dcpComponentDetected = true; + dcpComponents.push_back(component.second); + } + } + + if(dcpComponentDetected) { + + //Create server description (DCP file) for the system + std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! + port_t port = 8002; // TODO: Port should not be hard coded! + double timeStep = 0.001; // TODO: Time step should not be hard coded! + std::string targetFile = "OMSimulatorSystem_slave_description.dcp"; + + SlaveDescription_t slaveDescription = make_SlaveDescription(1, 0, "OMSimulatorSystem", generateUuid().c_str()); + slaveDescription.OpMode.HardRealTime = make_HardRealTime_ptr(); + slaveDescription.OpMode.SoftRealTime = make_SoftRealTime_ptr(); + slaveDescription.OpMode.NonRealTime = make_NonRealTime_ptr(); + Resolution_t resolution = make_Resolution(); + resolution.numerator = 1; + resolution.denominator = denominator_t(1.0/timeStep); // TODO: Time step should not be hard coded! + resolution.fixed = false; + + slaveDescription.TimeRes.resolutions.push_back(resolution); + slaveDescription.TransportProtocols.UDP_IPv4 = make_UDP_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->Control = make_Control_ptr(host, port); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output = make_DAT_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter = make_DAT_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); + slaveDescription.CapabilityFlags.canAcceptConfigPdus = true; + slaveDescription.CapabilityFlags.canHandleReset = false; + slaveDescription.CapabilityFlags.canHandleVariableSteps = false; + slaveDescription.CapabilityFlags.canMonitorHeartbeat = false; + slaveDescription.CapabilityFlags.canProvideLogOnRequest = true; + slaveDescription.CapabilityFlags.canProvideLogOnNotification = true; + + int i = 0; + for(const auto& connection : system->getDcpConnections()) { + if(NULL == connection) { + break; //Vector is null terminated, break the loop + } + + //Get pointers to the connectors of the comnnection + oms::Connector* connector1 = system->getConnector(connection->getSignalA()); + oms::Connector* connector2 = system->getConnector(connection->getSignalB()); + if(!connector1) { + logError("Connector 1 not found for signal: " + std::string(connection->getSignalA())); + continue; + } + if(!connector2) { + logError("Connector 2 not found for signal: " + std::string(connection->getSignalB())); + continue; + } + + oms::Component* component1 = system->getComponent(connector1->getOwner().back()); + oms::Component* component2 = system->getComponent(connector2->getOwner().back()); + if(component1 && component1->getType() == oms_component_dcp) { + if(connector2 && connector2->isOutput()) { + std::shared_ptr causality = make_Output_ptr(); + slaveDescription.Variables.push_back(make_Variable_output(connector2->getFullName(), valueReference_t(i), causality)); + ++i; + } + else if(connector2 && connector2->isInput()) { + std::shared_ptr causality = make_CommonCausality_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); + slaveDescription.Variables.push_back(make_Variable_input(connector2->getFullName(), valueReference_t(i), causality)); + ++i; + } + } + else if(component2 && component2->getType() == oms_component_dcp) { + if(connector1 && connector1->isOutput()) { + std::shared_ptr causality = make_Output_ptr(); + slaveDescription.Variables.push_back(make_Variable_output(connector1->getFullName(), valueReference_t(i), causality)); + ++i; + } + else if(connector1 && connector1->isInput()) { + std::shared_ptr causality = make_CommonCausality_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); + slaveDescription.Variables.push_back(make_Variable_input(connector1->getFullName(), valueReference_t(i), causality)); + ++i; + } + } + } + + slaveDescription.Log = make_Log_ptr(); + slaveDescription.Log->categories.push_back(make_Category(1, "DCP_SERVER")); + slaveDescription.Log->templates.push_back(make_Template( + 1, 1, (uint8_t) DcpLogLevel::LVL_INFORMATION, "[Time = %float64]: output = %float64")); + + writeDcpSlaveFile(std::make_shared(slaveDescription), targetFile); + + //Start a DCP slave instance for the system + + //Start a DCP master instance to connect to the slave and run the simulation + } + + //This should be in an else branch, but for testing purposes we will run a normal simulation oms_status_enu_t status = system->stepUntil(stopTime); emit(stopTime, true); clock.toc(); diff --git a/src/OMSimulatorLib/OMSimulator.cpp b/src/OMSimulatorLib/OMSimulator.cpp index 361ced354..7c9f55387 100644 --- a/src/OMSimulatorLib/OMSimulator.cpp +++ b/src/OMSimulatorLib/OMSimulator.cpp @@ -941,8 +941,6 @@ oms_status_enu_t oms_instantiate(const char* cref_) oms_status_enu_t oms_initialize(const char* cref_) { - logInfo("Enter oms_initialize(\"" + std::string(cref_) + "\")"); - oms::ComRef cref(cref_); oms::Model* model = oms::Scope::GetInstance().getModel(cref); diff --git a/src/OMSimulatorLib/System.cpp b/src/OMSimulatorLib/System.cpp index a08640af8..edf226119 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -63,6 +63,7 @@ oms::System::System(const oms::ComRef& cref, oms_system_enu_t type, oms::Model* initialStepSize = Flags::InitialStepSize(); connections.push_back(NULL); + dcpConnections.push_back(NULL); connectors.push_back(NULL); element.setConnectors(&connectors[0]); @@ -1232,9 +1233,19 @@ oms_status_enu_t oms::System::addConnection(const oms::ComRef& crefA, const oms: { return logError("Unit mismatch in connection: " + std::string(crefA) + " -> " + std::string(crefB)); } - // connection are checked in the python side, directly add the connection - connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion); - connections.push_back(NULL); + + auto componentB = getComponent(headB); + if ((componentA && oms_component_dcp == componentA->getType()) || (componentB && oms_component_dcp == componentB->getType())) + { + //DCP connections should not be used by OMSimulator solver for data exchange, only for setting up the DCP simulation. + dcpConnections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion); + dcpConnections.push_back(NULL); + } + else { + // connection are checked in the python side, directly add the connection + connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion); + connections.push_back(NULL); + } return oms_status_ok; } diff --git a/src/OMSimulatorLib/System.h b/src/OMSimulatorLib/System.h index 3b0a7c6d1..666079d3a 100644 --- a/src/OMSimulatorLib/System.h +++ b/src/OMSimulatorLib/System.h @@ -131,6 +131,7 @@ namespace oms std::map& getSubSystems() {return subsystems;} std::map& getComponents() {return components;} std::vector& getConnections() {return connections;} + std::vector& getDcpConnections() {return dcpConnections;} oms_status_enu_t updateDependencyGraphs(); const DirectedGraph& getInitialUnknownsGraph() {return initializationGraph;} const DirectedGraph& getOutputsGraph() {return eventGraph;} @@ -244,6 +245,7 @@ namespace oms std::vector subelements; ///< last element is always NULL; don't free it std::vector busconnectors; std::vector connections; ///< last element is always NULL + std::vector dcpConnections; ///< dummy connections to be used for setting up a DCP simulation bool loopsNeedUpdate = true; std::vector algLoops; ///< vector of algebraic loop objects diff --git a/src/OMSimulatorLib/SystemWC.cpp b/src/OMSimulatorLib/SystemWC.cpp index e1a2525bc..a634b232a 100644 --- a/src/OMSimulatorLib/SystemWC.cpp +++ b/src/OMSimulatorLib/SystemWC.cpp @@ -230,8 +230,6 @@ unsigned int oms::SystemWC::getMaxOutputDerivativeOrder() oms_status_enu_t oms::SystemWC::initialize() { - logInfo("Enter initialize() for system " + std::string(getCref())); //DCP debug - clock.reset(); CallClock callClock(clock); @@ -249,8 +247,6 @@ oms_status_enu_t oms::SystemWC::initialize() if (oms_status_ok != component.second->initialize()) return oms_status_error; - logInfo("All components initialized"); //DCP debug - // prepare data structures for simulation if (solverMethod == oms_solver_wc_mav || solverMethod == oms_solver_wc_mav2) { @@ -302,8 +298,6 @@ oms_status_enu_t oms::SystemWC::initialize() // mark algebraic loops to be updated on next call forceLoopsToBeUpdated(); - logInfo("Initialization finished successfully"); //DCP debug - return oms_status_ok; } diff --git a/src/OMSimulatorLib/Util.h b/src/OMSimulatorLib/Util.h index 0cdb1c79d..482aa4613 100644 --- a/src/OMSimulatorLib/Util.h +++ b/src/OMSimulatorLib/Util.h @@ -42,6 +42,7 @@ #include #include #include +#include // trim from start (in place) // https://stackoverflow.com/a/217605/7534030 @@ -93,4 +94,36 @@ void reverseArray(T* array, unsigned int length) } } +static inline std::string generateUuid() +{ + std::random_device rd; + std::mt19937_64 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + std::uniform_int_distribution<> dis2(8, 11); + std::stringstream ss; + int i; + ss << std::hex; + for (i = 0; i < 8; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 4; i++) { + ss << dis(gen); + } + ss << "-4"; + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + ss << dis2(gen); + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 12; i++) { + ss << dis(gen); + } + return ss.str().c_str(); +} + #endif diff --git a/src/OMSimulatorLib/Variable.cpp b/src/OMSimulatorLib/Variable.cpp index 223d485af..5c7a91ee4 100644 --- a/src/OMSimulatorLib/Variable.cpp +++ b/src/OMSimulatorLib/Variable.cpp @@ -41,9 +41,8 @@ #include "dcp/xml/DcpSlaveDescriptionElements.hpp" oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t componentType) - : der_index(0), state_index(0), is_state(false), is_der(false), is_continuous_time_state(false), is_continuous_time_der(false), index(index_), fmi2(false), fmi3(false), componentType(componentType) + : der_index(0), state_index(0), is_state(false), is_der(false), is_continuous_time_state(false), is_continuous_time_der(false), index(index_), fmi2(false), fmi3(false), dcp(false), componentType(componentType) { - // Check the component type switch (componentType) { @@ -63,6 +62,7 @@ oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t compon } oms::Variable::Variable(SlaveDescription_t *desc, int index) +: dcp(true), fmi2(false), fmi3(false), index(index), componentType(oms_component_dcp) { configureDCPVariable(desc, index); } @@ -395,6 +395,34 @@ oms::Variable::~Variable() { } +bool oms::Variable::isInput() const +{ + if(isFmi2()) { + return fmi2CausalityInput == fmi2Causality_; + } + else if(isFmi3()) { + return fmi3CausalityInput == fmi3Causality_; + } + else if(isDcp()) { + return dcpCausalityInput == dcpCausality; + } + return false; +} + +bool oms::Variable::isOutput() const +{ + if(isFmi2()) { + return fmi2CausalityOutput == fmi2Causality_; + } + else if(isFmi3()) { + return fmi3CausalityOutput == fmi3Causality_; + } + else if(isDcp()) { + return dcpCausalityOutput == dcpCausality; + } + return false; +} + oms_causality_enu_t oms::Variable::getCausality() const { if (isFmi2()) @@ -418,7 +446,7 @@ oms_causality_enu_t oms::Variable::getCausality() const return oms_causality_undefined; } } - else + else if (isFmi3()) { // FMI3 switch (fmi3Causality_) @@ -439,6 +467,24 @@ oms_causality_enu_t oms::Variable::getCausality() const return oms_causality_undefined; } } + else // DCP + { + // DCP + switch (dcpCausality) + { + case dcpCausalityInput: + return oms_causality_input; + + case dcpCausalityOutput: + return oms_causality_output; + + case dcpCausalityParameter: + return oms_causality_parameter; + + default: + return oms_causality_undefined; + } + } } std::string oms::Variable::getCausalityString() const diff --git a/src/OMSimulatorLib/Variable.h b/src/OMSimulatorLib/Variable.h index becb58bf4..c8066c6d2 100644 --- a/src/OMSimulatorLib/Variable.h +++ b/src/OMSimulatorLib/Variable.h @@ -69,12 +69,13 @@ namespace oms bool isFmi2() const {return fmi2;} bool isFmi3() const {return fmi3;} + bool isDcp() const {return dcp;} // causality attribute bool isParameter() const { return isFmi2() ? (fmi2CausalityParameter == fmi2Causality_) : (fmi3CausalityParameter == fmi3Causality_);} bool isCalculatedParameter() const { return isFmi2() ? (fmi2CausalityCalculatedParameter == fmi2Causality_) : (fmi3CausalityCalculatedParameter ==fmi3Causality_);} - bool isInput() const { return isFmi2() ? (fmi2CausalityInput == fmi2Causality_) : (fmi3CausalityInput == fmi3Causality_);} - bool isOutput() const { return isFmi2() ? (fmi2CausalityOutput == fmi2Causality_) : (fmi3CausalityOutput == fmi3Causality_);} + bool isInput() const; + bool isOutput() const; bool isLocal() const { return isFmi2() ? (fmi2CausalityLocal == fmi2Causality_) : (fmi3CausalityLocal == fmi3Causality_);} bool isIndependent() const { return isFmi2() ? (fmi2CausalityIndependent == fmi2Causality_) : (fmi3CausalityIndependent == fmi3Causality_);} @@ -153,6 +154,7 @@ namespace oms unsigned int index; ///< index origin = 0 size_t state_index; ///< index origin = 0 size_t der_index; ///< index origin = 0 + bool dcp; bool fmi2; bool fmi3; friend bool operator==(const oms::Variable& v1, const oms::Variable& v2); diff --git a/src/OMSimulatorPython/instantiated_model.py b/src/OMSimulatorPython/instantiated_model.py index c6cebcd7b..490cc2722 100644 --- a/src/OMSimulatorPython/instantiated_model.py +++ b/src/OMSimulatorPython/instantiated_model.py @@ -212,6 +212,8 @@ def __init__(self, json_description, system: System, resources: dict): start_element = ".".join(connection['start element'] + [connection['start connector']]) end_element = ".".join(connection['end element'] + [connection['end connector']]) + print(f"Adding connection from {start_element} to {end_element}") + if start_element not in self.mappedCrefs: raise KeyError(f"No mapping found for {start_element}") if end_element not in self.mappedCrefs: @@ -249,6 +251,7 @@ def __init__(self, json_description, system: System, resources: dict): self.apiCall.append(f'oms_instantiate("{self.modelName}")') status = Capi.instantiate(self.modelName) + print(f"Model instantiated with status: {status}") if status != Status.ok: raise RuntimeError(f"Failed to instantiate model: {status}") self.fmuInstantitated = True From e700bbc2108b7a3ebc5bb2f9296abb604c2c51c3 Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 18 May 2026 13:01:45 +0200 Subject: [PATCH 05/10] Remove debug output --- src/OMSimulatorLib/ComponentDCP.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp index 21c1ff194..b8fac1978 100644 --- a/src/OMSimulatorLib/ComponentDCP.cpp +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -321,8 +321,6 @@ oms_status_enu_t oms::ComponentDCP::instantiate() oms_status_enu_t oms::ComponentDCP::registerSignalsForResultFile(ResultWriter &resultFile) { - logInfo("Enter registerSignalsForResultFile for " + std::string(getFullCref())); //DCP debug - // Not sure if this is needed return oms_status_ok; } From be6bac6121c1bb88e4b4fc7d3a7dd0d3adf1753f Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Tue, 19 May 2026 15:22:17 +0200 Subject: [PATCH 06/10] Start internal system as DCP slave --- src/OMSimulatorLib/CMakeLists.txt | 34 ++-- src/OMSimulatorLib/Model.cpp | 90 +---------- src/OMSimulatorLib/System.cpp | 165 +++++++++++++++++++- src/OMSimulatorLib/System.h | 22 +++ src/OMSimulatorLib/SystemSC3.cpp | 2 - src/OMSimulatorLib/Variable.cpp | 2 +- src/OMSimulatorLib/Variable.h | 4 +- src/OMSimulatorPython/instantiated_model.py | 1 - 8 files changed, 211 insertions(+), 109 deletions(-) diff --git a/src/OMSimulatorLib/CMakeLists.txt b/src/OMSimulatorLib/CMakeLists.txt index 0f1d2d47c..af84671e4 100644 --- a/src/OMSimulatorLib/CMakeLists.txt +++ b/src/OMSimulatorLib/CMakeLists.txt @@ -72,6 +72,20 @@ list(APPEND OMSIMULATORLIB_SOURCES "${CMAKE_CURRENT_BINARY_DIR}/Version.cpp") add_library(OMSimulatorLib SHARED ${OMSIMULATORLIB_SOURCES}) set_target_properties(OMSimulatorLib PROPERTIES OUTPUT_NAME OMSimulator) +add_library(dcplib INTERFACE) +# DCPLib headers live in the top-level 3rdParty directory +target_include_directories(dcplib + INTERFACE + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/core + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/ethernet + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/master + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/slave + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/xml + ${CMAKE_SOURCE_DIR}/3rdParty/DCPLib/include/zip + ${CMAKE_SOURCE_DIR}/3rdParty/asio/include) +target_link_libraries(OMSimulatorLib PRIVATE dcplib) +target_link_libraries(OMSimulatorLib PUBLIC ws2_32) + target_include_directories(OMSimulatorLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(OMSimulatorLib @@ -87,13 +101,7 @@ target_link_libraries(OMSimulatorLib oms::3rd::json::header oms::3rd::ctpl::header oms::3rd::xerces - oms::3rd::libzip - oms::3rd::dcplib::core - oms::3rd::dcplib::master - oms::3rd::dcplib::slave - oms::3rd::dcplib::ethernet - oms::3rd::dcplib::zip - oms::3rd::dcplib::xml) + oms::3rd::libzip) target_link_libraries(OMSimulatorLib PUBLIC ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) @@ -127,16 +135,14 @@ target_link_libraries(OMSimulatorLib_static oms::3rd::ctpl::header oms::3rd::xerces oms::3rd::ctpl::header - oms::3rd::libzip - oms::3rd::dcplib::core - oms::3rd::dcplib::master - oms::3rd::dcplib::slave - oms::3rd::dcplib::ethernet - oms::3rd::dcplib::zip - oms::3rd::dcplib::xml) + oms::3rd::libzip) + +target_link_libraries(OMSimulatorLib_static PRIVATE dcplib) target_link_libraries(OMSimulatorLib_static PUBLIC ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(OMSimulatorLib_static PUBLIC ws2_32) + if(MINGW) target_link_libraries(OMSimulatorLib_static PUBLIC shlwapi) endif() diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index 30229e0a5..9ed5affb8 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -52,7 +52,6 @@ #include //Only for debug output, can remove later //#include "dcp/xml/DcpSlaveDescriptionWriter.hpp" -#include #include #include @@ -1304,96 +1303,11 @@ oms_status_enu_t oms::Model::simulate() } if(dcpComponentDetected) { - - //Create server description (DCP file) for the system - std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! - port_t port = 8002; // TODO: Port should not be hard coded! - double timeStep = 0.001; // TODO: Time step should not be hard coded! - std::string targetFile = "OMSimulatorSystem_slave_description.dcp"; - - SlaveDescription_t slaveDescription = make_SlaveDescription(1, 0, "OMSimulatorSystem", generateUuid().c_str()); - slaveDescription.OpMode.HardRealTime = make_HardRealTime_ptr(); - slaveDescription.OpMode.SoftRealTime = make_SoftRealTime_ptr(); - slaveDescription.OpMode.NonRealTime = make_NonRealTime_ptr(); - Resolution_t resolution = make_Resolution(); - resolution.numerator = 1; - resolution.denominator = denominator_t(1.0/timeStep); // TODO: Time step should not be hard coded! - resolution.fixed = false; - - slaveDescription.TimeRes.resolutions.push_back(resolution); - slaveDescription.TransportProtocols.UDP_IPv4 = make_UDP_ptr(); - slaveDescription.TransportProtocols.UDP_IPv4->Control = make_Control_ptr(host, port); - slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output = make_DAT_ptr(); - slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); - slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter = make_DAT_ptr(); - slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); - slaveDescription.CapabilityFlags.canAcceptConfigPdus = true; - slaveDescription.CapabilityFlags.canHandleReset = false; - slaveDescription.CapabilityFlags.canHandleVariableSteps = false; - slaveDescription.CapabilityFlags.canMonitorHeartbeat = false; - slaveDescription.CapabilityFlags.canProvideLogOnRequest = true; - slaveDescription.CapabilityFlags.canProvideLogOnNotification = true; - - int i = 0; - for(const auto& connection : system->getDcpConnections()) { - if(NULL == connection) { - break; //Vector is null terminated, break the loop - } - - //Get pointers to the connectors of the comnnection - oms::Connector* connector1 = system->getConnector(connection->getSignalA()); - oms::Connector* connector2 = system->getConnector(connection->getSignalB()); - if(!connector1) { - logError("Connector 1 not found for signal: " + std::string(connection->getSignalA())); - continue; - } - if(!connector2) { - logError("Connector 2 not found for signal: " + std::string(connection->getSignalB())); - continue; - } - - oms::Component* component1 = system->getComponent(connector1->getOwner().back()); - oms::Component* component2 = system->getComponent(connector2->getOwner().back()); - if(component1 && component1->getType() == oms_component_dcp) { - if(connector2 && connector2->isOutput()) { - std::shared_ptr causality = make_Output_ptr(); - slaveDescription.Variables.push_back(make_Variable_output(connector2->getFullName(), valueReference_t(i), causality)); - ++i; - } - else if(connector2 && connector2->isInput()) { - std::shared_ptr causality = make_CommonCausality_ptr(); - causality->Float64->start = std::make_shared>(); - causality->Float64->start->push_back(0.0); - slaveDescription.Variables.push_back(make_Variable_input(connector2->getFullName(), valueReference_t(i), causality)); - ++i; - } - } - else if(component2 && component2->getType() == oms_component_dcp) { - if(connector1 && connector1->isOutput()) { - std::shared_ptr causality = make_Output_ptr(); - slaveDescription.Variables.push_back(make_Variable_output(connector1->getFullName(), valueReference_t(i), causality)); - ++i; - } - else if(connector1 && connector1->isInput()) { - std::shared_ptr causality = make_CommonCausality_ptr(); - causality->Float64->start = std::make_shared>(); - causality->Float64->start->push_back(0.0); - slaveDescription.Variables.push_back(make_Variable_input(connector1->getFullName(), valueReference_t(i), causality)); - ++i; - } - } - } - - slaveDescription.Log = make_Log_ptr(); - slaveDescription.Log->categories.push_back(make_Category(1, "DCP_SERVER")); - slaveDescription.Log->templates.push_back(make_Template( - 1, 1, (uint8_t) DcpLogLevel::LVL_INFORMATION, "[Time = %float64]: output = %float64")); - - writeDcpSlaveFile(std::make_shared(slaveDescription), targetFile); - //Start a DCP slave instance for the system + system->startDcpSlave(); //Start a DCP master instance to connect to the slave and run the simulation + //this->startDcpMaster(); } //This should be in an else branch, but for testing purposes we will run a normal simulation diff --git a/src/OMSimulatorLib/System.cpp b/src/OMSimulatorLib/System.cpp index edf226119..cf5dfbc03 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -50,13 +50,19 @@ #include "SystemSC.h" #include "SystemSC3.h" #include "SystemWC.h" +#include "Util.h" #include "Variable.h" #include "miniunz.h" +#include +#include +#include +#include + #include oms::System::System(const oms::ComRef& cref, oms_system_enu_t type, oms::Model* parentModel, oms::System* parentSystem, oms_solver_enu_t solverMethod) - : element(oms_element_system, cref), cref(cref), type(type), parentModel(parentModel), parentSystem(parentSystem), solverMethod(solverMethod) + : element(oms_element_system, cref), cref(cref), type(type), parentModel(parentModel), parentSystem(parentSystem), solverMethod(solverMethod), dcpLog(std::cout) { minimumStepSize = Flags::MinimumStepSize(); maximumStepSize = Flags::MaximumStepSize(); @@ -2796,3 +2802,160 @@ oms_status_enu_t oms::System::renameConnectors() return oms_status_ok; } + +oms_status_enu_t oms::System::startDcpSlave() +{ + std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! + port_t port = 8002; // TODO: Port should not be hard coded! + double dcpTimeStep = 0.001; // TODO: Time step should not be hard coded! + std::string targetFile = "OMSimulatorSystem_slave_description.dcp"; + + dcpInputs.clear(); + dcpOutputs.clear(); + + SlaveDescription_t slaveDescription = make_SlaveDescription(1, 0, "OMSimulatorSystem", generateUuid().c_str()); + slaveDescription.OpMode.HardRealTime = make_HardRealTime_ptr(); + slaveDescription.OpMode.SoftRealTime = make_SoftRealTime_ptr(); + slaveDescription.OpMode.NonRealTime = make_NonRealTime_ptr(); + Resolution_t resolution = make_Resolution(); + resolution.numerator = 1; + resolution.denominator = denominator_t(1.0/dcpTimeStep); // TODO: Time step should not be hard coded! + resolution.fixed = false; + + slaveDescription.TimeRes.resolutions.push_back(resolution); + slaveDescription.TransportProtocols.UDP_IPv4 = make_UDP_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->Control = make_Control_ptr(host, port); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output = make_DAT_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_input_output->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter = make_DAT_ptr(); + slaveDescription.TransportProtocols.UDP_IPv4->DAT_parameter->availablePortRanges.push_back(make_AvailablePortRange(2048, 65535)); + slaveDescription.CapabilityFlags.canAcceptConfigPdus = true; + slaveDescription.CapabilityFlags.canHandleReset = false; + slaveDescription.CapabilityFlags.canHandleVariableSteps = false; + slaveDescription.CapabilityFlags.canMonitorHeartbeat = false; + slaveDescription.CapabilityFlags.canProvideLogOnRequest = true; + slaveDescription.CapabilityFlags.canProvideLogOnNotification = true; + + int i = 0; + for(const auto& connection : this->getDcpConnections()) { + if(NULL == connection) { + break; //Vector is null terminated, break the loop + } + + //Get pointers to the connectors of the comnnection + oms::Connector* connector1 = this->getConnector(connection->getSignalA()); + oms::Connector* connector2 = this->getConnector(connection->getSignalB()); + if(!connector1) { + logError("Connector 1 not found for signal: " + std::string(connection->getSignalA())); + continue; + } + if(!connector2) { + logError("Connector 2 not found for signal: " + std::string(connection->getSignalB())); + continue; + } + + oms::Component* component1 = this->getComponent(connector1->getOwner().back()); + oms::Component* component2 = this->getComponent(connector2->getOwner().back()); + if(component1 && component1->getType() == oms_component_dcp) { + if(connector2 && connector2->isOutput()) { + std::shared_ptr causality = make_Output_ptr(); + slaveDescription.Variables.push_back(make_Variable_output(connector2->getFullName(), valueReference_t(i), causality)); + dcpOutputValueReferences.insert(std::make_pair(connector2->getFullName(), valueReference_t(i))); + ++i; + } + else if(connector2 && connector2->isInput()) { + std::shared_ptr causality = make_CommonCausality_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); + slaveDescription.Variables.push_back(make_Variable_input(connector2->getFullName(), valueReference_t(i), causality)); + dcpInputValueReferences.insert(std::make_pair(connector2->getFullName(), valueReference_t(i))); + + ++i; + } + } + else if(component2 && component2->getType() == oms_component_dcp) { + if(connector1 && connector1->isOutput()) { + std::shared_ptr causality = make_Output_ptr(); + slaveDescription.Variables.push_back(make_Variable_output(connector1->getFullName(), valueReference_t(i), causality)); + dcpOutputValueReferences.insert(std::make_pair(connector1->getFullName(), valueReference_t(i))); + ++i; + } + else if(connector1 && connector1->isInput()) { + std::shared_ptr causality = make_CommonCausality_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); + slaveDescription.Variables.push_back(make_Variable_input(connector1->getFullName(), valueReference_t(i), causality)); + dcpInputValueReferences.insert(std::make_pair(connector1->getFullName(), valueReference_t(i))); + ++i; + } + } + } + + slaveDescription.Log = make_Log_ptr(); + slaveDescription.Log->categories.push_back(make_Category(1, "DCP_SERVER")); + slaveDescription.Log->templates.push_back(make_Template( + 1, 1, (uint8_t) DcpLogLevel::LVL_INFORMATION, "[Time = %float64]: output = %float64")); + + writeDcpSlaveFile(std::make_shared(slaveDescription), targetFile); + + //Create UDP driver + dcpDriver = new UdpDriver(host, uint16_t(port)); + + //Create server DCP manager + dcpManager = new DcpManagerSlave(slaveDescription, dcpDriver->getDcpDriver()); + dcpManager->setInitializeCallback( std::bind(&dcpInitialize, this)); + dcpManager->setConfigureCallback( std::bind(&dcpConfigure, this)); + dcpManager->setSynchronizingStepCallback( std::bind(&dcpDoStep, this, std::placeholders::_1)); + dcpManager->setSynchronizedStepCallback( std::bind(&dcpDoStep, this, std::placeholders::_1)); + dcpManager->setRunningStepCallback( std::bind(&dcpDoStep, this, std::placeholders::_1)); + dcpManager->setRunningNRTStepCallback( std::bind(&dcpDoStep, this, std::placeholders::_1)); + dcpManager->setTimeResListener( std::bind(&dcpSetTimeRes, this, std::placeholders::_1, std::placeholders::_2)); + dcpManager->setStopCallback( std::bind(&dcpStop, this)); + + //For some reason we get linker errors when adding the log listener. Should be fixed later. + //dcpManager->addLogListener(std::bind(&OstreamLog::logOstream, dcpLog, std::placeholders::_1)); + dcpManager->setGenerateLogString(true); + + dcpManager->start(); + + return oms_status_enu_t(); +} + +void oms::System::dcpConfigure() +{ + for(const auto input : dcpInputValueReferences) + dcpInputs.insert(std::make_pair(input.first, dcpManager->getInput(input.second))); + + for(const auto output : dcpOutputValueReferences) + dcpOutputs.insert(std::make_pair(output.first, dcpManager->getOutput(output.second))); +} + +void oms::System::dcpInitialize() +{ + //Should already have been done +} + +void oms::System::dcpDoStep(uint64_t steps) +{ + //Read inputs + for(const auto input : dcpInputs) + this->setReal(input.first, *(input.second)); + + //Take some steps + this->stepUntil(steps*dcpTimeStep); + + //Write outputs + for(const auto output : dcpOutputs) + this->setReal(output.first, *(output.second)); +} + +void oms::System::dcpSetTimeRes(const uint32_t numerator, const uint32_t denominator) +{ + //Implement later if needed +} + +void oms::System::dcpStop() +{ + dcpDriver->getDcpDriver().stopReceiving(); + dcpDriver->getDcpDriver().disconnect(); +} diff --git a/src/OMSimulatorLib/System.h b/src/OMSimulatorLib/System.h index 666079d3a..b1f14beba 100644 --- a/src/OMSimulatorLib/System.h +++ b/src/OMSimulatorLib/System.h @@ -55,6 +55,12 @@ #include +#include + +class DcpManagerSlave; +class UdpDriver; +class OstreamLog; + namespace oms { class AlgLoop; @@ -197,6 +203,13 @@ namespace oms Values& getValues() { return values; } std::map fmuGuid; + oms_status_enu_t startDcpSlave(); + void dcpConfigure(); + void dcpInitialize(); + void dcpDoStep(uint64_t steps); + void dcpSetTimeRes(const uint32_t numerator, const uint32_t denominator); + void dcpStop(); + protected: // methods System(const ComRef& cref, oms_system_enu_t type, Model* parentModel, System* parentSystem, oms_solver_enu_t solverMethod); @@ -250,6 +263,15 @@ namespace oms bool loopsNeedUpdate = true; std::vector algLoops; ///< vector of algebraic loop objects std::string getFmiVersion(const std::string& path); + + DcpManagerSlave *dcpManager; + UdpDriver* dcpDriver; + OstreamLog dcpLog; + double dcpTimeStep; + std::map dcpInputs; + std::map dcpOutputs; + std::map dcpInputValueReferences; + std::map dcpOutputValueReferences; }; } diff --git a/src/OMSimulatorLib/SystemSC3.cpp b/src/OMSimulatorLib/SystemSC3.cpp index fa73a8ab0..c4cd4862e 100644 --- a/src/OMSimulatorLib/SystemSC3.cpp +++ b/src/OMSimulatorLib/SystemSC3.cpp @@ -247,8 +247,6 @@ oms_status_enu_t oms::SystemSC3::instantiate() oms_status_enu_t oms::SystemSC3::initialize() { - logInfo("Enter initialize() for system " + std::string(getCref())); //DCP debug - // testFMI3ME(fmus[0]->getFMU()); // exit(0); clock.reset(); diff --git a/src/OMSimulatorLib/Variable.cpp b/src/OMSimulatorLib/Variable.cpp index 5c7a91ee4..986892fa7 100644 --- a/src/OMSimulatorLib/Variable.cpp +++ b/src/OMSimulatorLib/Variable.cpp @@ -38,7 +38,7 @@ #include "Logging.h" #include "Util.h" #include -#include "dcp/xml/DcpSlaveDescriptionElements.hpp" +#include oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t componentType) : der_index(0), state_index(0), is_state(false), is_der(false), is_continuous_time_state(false), is_continuous_time_der(false), index(index_), fmi2(false), fmi3(false), dcp(false), componentType(componentType) diff --git a/src/OMSimulatorLib/Variable.h b/src/OMSimulatorLib/Variable.h index c8066c6d2..14f8ec2be 100644 --- a/src/OMSimulatorLib/Variable.h +++ b/src/OMSimulatorLib/Variable.h @@ -39,8 +39,8 @@ #include "ComRef.h" #include "Connector.h" #include "OMSimulator/Types.h" -#include "dcp/model/DcpTypes.hpp" -#include "dcp/xml/DcpSlaveDescriptionElements.hpp" +#include +#include #include #include diff --git a/src/OMSimulatorPython/instantiated_model.py b/src/OMSimulatorPython/instantiated_model.py index 490cc2722..282dbbd38 100644 --- a/src/OMSimulatorPython/instantiated_model.py +++ b/src/OMSimulatorPython/instantiated_model.py @@ -251,7 +251,6 @@ def __init__(self, json_description, system: System, resources: dict): self.apiCall.append(f'oms_instantiate("{self.modelName}")') status = Capi.instantiate(self.modelName) - print(f"Model instantiated with status: {status}") if status != Status.ok: raise RuntimeError(f"Failed to instantiate model: {status}") self.fmuInstantitated = True From f8b88f31b42d4afe61603abc61426a2bd9f5193c Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Fri, 29 May 2026 14:31:13 +0200 Subject: [PATCH 07/10] Update 3rdParty submodule --- 3rdParty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdParty b/3rdParty index d8069dce6..cb0107e8b 160000 --- a/3rdParty +++ b/3rdParty @@ -1 +1 @@ -Subproject commit d8069dce6002ccb850a78155f33884952e87be90 +Subproject commit cb0107e8bf1c6268f9ecc1e451423f27f5287b99 From dd9a7abefc94162c75ca09c366a3578bc4aba5da Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Fri, 29 May 2026 14:57:48 +0200 Subject: [PATCH 08/10] DCP simulation support --- 3rdParty | 2 +- include/OMSimulator/Types.h | 71 ++++ src/OMSimulatorLib/CMakeLists.txt | 1 + src/OMSimulatorLib/Component.cpp | 5 +- src/OMSimulatorLib/ComponentDCP.cpp | 24 +- src/OMSimulatorLib/ComponentDCP.h | 11 +- src/OMSimulatorLib/Model.cpp | 404 +++++++++++++++++++- src/OMSimulatorLib/Model.h | 61 +++ src/OMSimulatorLib/System.cpp | 62 ++- src/OMSimulatorLib/System.h | 8 +- src/OMSimulatorLib/Variable.cpp | 4 +- src/OMSimulatorLib/Variable.h | 4 +- src/OMSimulatorPython/dcp.py | 1 - src/OMSimulatorPython/instantiated_model.py | 2 - src/OMSimulatorPython/variable.py | 1 - 15 files changed, 605 insertions(+), 56 deletions(-) diff --git a/3rdParty b/3rdParty index cb0107e8b..0b1ba82ce 160000 --- a/3rdParty +++ b/3rdParty @@ -1 +1 @@ -Subproject commit cb0107e8bf1c6268f9ecc1e451423f27f5287b99 +Subproject commit 0b1ba82ce3b9aa0f4cfd18d21563184ede93589c diff --git a/include/OMSimulator/Types.h b/include/OMSimulator/Types.h index f6cf2514f..b657ca3a3 100644 --- a/include/OMSimulator/Types.h +++ b/include/OMSimulator/Types.h @@ -516,6 +516,77 @@ typedef struct { unsigned int maxOutputDerivativeOrder; } oms_fmu_info_t; +typedef struct { + /** + * The DCP major version that was used to generate the DCPX file + * and accompanying DCP slave. + */ + int dcpMajorVersion; + /** + * The DCP minor version that was used to generate the DCPX file + * and accompanying DCP slave. + */ + int dcpMinorVersion; + /** + * The name of the complete DCP slave. + */ + char* dcpSlaveName; + /** + * The universally unique identifier is a string that is used to uniquely + * identify a DCP slave in a global environment. The uuid acts as + * a fingerprint of relevant information. Typically, the uuid is assigned + * when the DCP slave description file is generated. It is used + * for verification during the registration process of a DCP slave. + */ + char* uuid; + /** + * Optional string that contains a brief description of the complete + *DCP slave. + */ + char* description; + /** + * Optional string that contains name and organization of the DCP + * slave author. + */ + char* author; + /** + * Optional development version number of the DCP slave. + */ + char* version; + /** + * Optional information on the intellectual property copyright for this + * DCP slave. + */ + char* copyright; + /** + * Optional information on the intellectual property licensing for this + * DCP slave. + */ + char* license; + /** + * Optional information about the tool the DCPX file was generated + * with. + */ + char* generationTool; + /** + * Optional date and time when the DCPX file was generated. The + * format is a subset of “xs:dateTime” and should be: “YYYY-MMDDThh: + * mm:ssZ" (with one “T” between date and time; “Z” characterizes + * the Zulu time zone, in other words Greenwich meantime). + */ + char* generationDateAndTime; + /** + * Defines whether the variable names in Variables/Variable/name + * and in TypeDefinitions/SimpleType/name follow a particular + * convention. + */ + int variableNamingConvention; + /** + * Absolute path to the DCP file. + */ + char* path; +} oms_dcp_info_t; + /* * FMU default experiment settings from modeldescription.xml */ diff --git a/src/OMSimulatorLib/CMakeLists.txt b/src/OMSimulatorLib/CMakeLists.txt index af84671e4..8628e853e 100644 --- a/src/OMSimulatorLib/CMakeLists.txt +++ b/src/OMSimulatorLib/CMakeLists.txt @@ -36,6 +36,7 @@ set(OMSIMULATORLIB_SOURCES Connector.cpp CSVReader.cpp CSVWriter.cpp + DCPInfo.cpp DirectedGraph.cpp Element.cpp Flags.cpp diff --git a/src/OMSimulatorLib/Component.cpp b/src/OMSimulatorLib/Component.cpp index 65eda3165..f166bbe22 100644 --- a/src/OMSimulatorLib/Component.cpp +++ b/src/OMSimulatorLib/Component.cpp @@ -152,9 +152,10 @@ oms::Model& oms::Component::getModel() const oms::Connector* oms::Component::getConnector(const ComRef& cref) { - for (auto &connector : connectors) - if (connector && connector->getName() == cref) + for (auto &connector : connectors) { + if (connector && connector->getName() == cref) return connector; + } return NULL; } diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp index b8fac1978..71d8c0db2 100644 --- a/src/OMSimulatorLib/ComponentDCP.cpp +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -40,7 +40,6 @@ #include "SystemWC.h" #include "Scope.h" #include "dcp/zip/DcpSlaveReader.hpp" -#include "dcp/log/OstreamLog.hpp" #include "dcp/logic/DcpManagerMaster.hpp" #include @@ -48,10 +47,11 @@ #include #include #include +#include oms::ComponentDCP::ComponentDCP(const ComRef& cref, System* parentSystem, const std::string& dcpPath) - : oms::Component(cref, oms_component_dcp, parentSystem, dcpPath), fmuInfo(dcpPath) + : oms::Component(cref, oms_component_dcp, parentSystem, dcpPath), dcpInfo(dcpPath) { } @@ -73,13 +73,8 @@ oms::Component* oms::ComponentDCP::NewComponent(const oms::ComRef& cref, oms::Sy return NULL; } // replaceComponent string will be used to avoid name conflicts when replacing a fmu with oms_replaceSubModel(), the default is "" - - filesystem::path temp_root(parentSystem->getModel().getTempDirectory()); - filesystem::path temp_temp = temp_root / "temp"; - filesystem::path relDCPPath = parentSystem->copyResources() ? (filesystem::path("resources") / (parentSystem->getUniqueID() + "_" + replaceComponent + std::string(cref) + ".dcp")) : filesystem::path(dcpPath); - filesystem::path absDCPPath = temp_root / relDCPPath; - - ComponentDCP* component = new ComponentDCP(cref, parentSystem, relDCPPath.generic_string()); + + ComponentDCP* component = new ComponentDCP(cref, parentSystem, dcpPath); /* parse the modeldescription.xml at top level to get the GUID to check whether instance already exist * so we don't need to unpack the fmu, and also parse start values before instantiating fmu's @@ -98,20 +93,15 @@ oms::Component* oms::ComponentDCP::NewComponent(const oms::ComRef& cref, oms::Sy component->values.parseSlaveDescription(dcpPath, guid_); - // update DCP info - // TODO: Is this needed? (dcp) - component->fmuInfo.update(oms_component_dcp, nullptr); - // create a list of all variables using fmi4c variable structure - component->desc = getSlaveDescriptionFromDcpFile(1, 0, dcpPath).get(); + component->dcpSlaveDescription = getSlaveDescriptionFromDcpFile(1, 0, dcpPath); - int numVars = component->desc->Variables.size(); + int numVars = component->dcpSlaveDescription->Variables.size(); component->allVariables.reserve(numVars); component->exportVariables.reserve(numVars); for (unsigned int i = 0; i < numVars; ++i) { - oms::Variable v(component->desc, i); - //logInfo("vars: " + std::string(v.getCref().c_str())); + oms::Variable v(component->dcpSlaveDescription, i); if (v.getIndex() != i) { diff --git a/src/OMSimulatorLib/ComponentDCP.h b/src/OMSimulatorLib/ComponentDCP.h index 65013ac67..4664f3861 100644 --- a/src/OMSimulatorLib/ComponentDCP.h +++ b/src/OMSimulatorLib/ComponentDCP.h @@ -36,6 +36,7 @@ #include "Component.h" #include "ComRef.h" +#include "DCPInfo.h" #include "ResultWriter.h" #include "Snapshot.h" #include "Values.h" @@ -51,6 +52,8 @@ #include //Must be included first because of include conflict with Asio and windows.h #include +class SlaveDescription_t; + namespace oms { class System; @@ -62,7 +65,8 @@ class ComponentDCP : public Component static Component* NewComponent(const ComRef& cref, System* parentSystem, const std::string& dcpPath, std::string replaceComponent = ""); static Component* NewComponent(const pugi::xml_node& node, System* parentSystem, const std::string& sspVersion, const Snapshot& snapshot, std::string variantName); - const FMUInfo* getFMUInfo() const {return &(this->fmuInfo);} + const DCPInfo* getDCPInfo() const {return &(this->dcpInfo);} + oms_status_enu_t addSignalsToResults(const char* regex); oms_status_enu_t exportToSSD(pugi::xml_node& node, Snapshot& snapshot, std::string variantName) const; @@ -86,9 +90,8 @@ class ComponentDCP : public Component ComponentDCP& operator=(ComponentDCP const& copy); ///< not implemented private: - FMUInfo fmuInfo; - SlaveDescription_t *desc; - + DCPInfo dcpInfo; + std::shared_ptr dcpSlaveDescription; std::vector allVariables; std::vector calculatedParameters; std::vector derivatives; diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index 9ed5affb8..613638c7e 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -36,7 +36,9 @@ #include "Model.h" #include "Component.h" +#include "Connection.h" #include "CSVWriter.h" +#include "ComponentDCP.h" #include "Flags.h" #include "MATWriter.h" #include "OMSFileSystem.h" @@ -51,12 +53,18 @@ #include /* std::unique and std::find are defined here */ #include //Only for debug output, can remove later -//#include "dcp/xml/DcpSlaveDescriptionWriter.hpp" #include #include +#include +#include +#include +#include +#include +#include +#include oms::Model::Model(const oms::ComRef& cref, const std::string& tempDir) - : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat") + : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat"), dcpLog(std::cout) { if (!Flags::SuppressPath()) logInfo("New model \"" + std::string(cref) + "\" with corresponding temp directory \"" + tempDir + "\""); @@ -1272,8 +1280,6 @@ oms_status_enu_t oms::Model::initialize() oms_status_enu_t oms::Model::simulate() { - logInfo("Test print (logInfo)"); - clock.tic(); if (!validState(oms_modelState_simulation)) { @@ -1291,23 +1297,56 @@ oms_status_enu_t oms::Model::simulate() //Loop over system components and check if any of them is a DCP component. If yes, start a DCP simulation where the system is wrapped as a DCP slave. bool dcpComponentDetected = false; - std::vector dcpComponents; + std::vector dcpComponentsList; for (const auto& component : system->getComponents()) { if (component.second->getType() == oms_component_dcp) { dcpComponentDetected = true; - dcpComponents.push_back(component.second); + dcpComponentsList.push_back(dynamic_cast(component.second)); } } if(dcpComponentDetected) { + //Start a DCP slave instance for the system - system->startDcpSlave(); + system->configureDcpSlave(); + + this->configureDcpMaster(); + + for(const auto dcpComponent : dcpComponentsList) + { + if (oms_status_ok != addDcpSlave(dcpComponent)) + { + clock.toc(); + return logError("Failed to add DCP slave for component: " + std::string(dcpComponent->getFullCref())); + } + } + + for(const auto dcpConnection : system->getDcpConnections()) + { + if(nullptr == dcpConnection) + { + continue; //Null-terminated + } + + if (oms_status_ok != addDcpConnection(dcpConnection)) + { + clock.toc(); + return logError("Failed to add DCP connection: " + std::string(dcpConnection->getSignalA() + " -> " + dcpConnection->getSignalB())); + } + } //Start a DCP master instance to connect to the slave and run the simulation - //this->startDcpMaster(); + + logInfo("Running a DCP simulation..."); + + system->startDcpSlave(); //Separate thread + + this->startDcpMaster(); + + return oms_status_ok; } //This should be in an else branch, but for testing purposes we will run a normal simulation @@ -1412,6 +1451,168 @@ oms_status_enu_t oms::Model::reset() return oms_status_ok; } +oms_status_enu_t oms::Model::configureDcpMaster() +{ + std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! + port_t port = 8000; // TODO: Port should not be hard coded! + std::string internalSystemFile = "OMSimulatorSystem_slave_description.dcp"; + + dcpDriver = new UdpDriver(host, port); + + std::shared_ptr slaveDescription = getSlaveDescriptionFromDcpFile(1, 0, internalSystemFile.c_str()); + dcpInternalSystemSlaveDescription = slaveDescription; + dcpSlaveDescriptions.push_back(slaveDescription); + dcpManager = new DcpManagerMaster(dcpDriver->getDcpDriver()); + uint8_t *netInfo = new uint8_t[6]; + *((uint16_t *) netInfo) = *slaveDescription->TransportProtocols.UDP_IPv4->Control->port; + *((uint32_t *) (netInfo + 2)) = asio::ip::address_v4::from_string(*slaveDescription->TransportProtocols.UDP_IPv4->Control->host).to_ulong(); + dcpDriver->getDcpDriver().setSlaveNetworkInformation(1, netInfo); + delete[] netInfo; + + dcpManager->setAckReceivedListener( + std::bind(&oms::Model::dcpReceiveAck, this, std::placeholders::_1, std::placeholders::_2)); + + dcpManager->setNAckReceivedListener( + std::bind(&oms::Model::dcpReceiveNAck, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + + dcpManager->setStateChangedNotificationReceivedListener( + std::bind(&oms::Model::dcpReceiveStateChangedNotification, this, std::placeholders::_1, + std::placeholders::_2)); + + //DCP log listener disabled for now due to unresolved linking issues + dcpManager->addLogListener(std::bind(&OstreamLog::logOstream, dcpLog, std::placeholders::_1)); + dcpManager->setGenerateLogString(true); + + dcpSlaveCount = 1; + + return oms_status_enu_t(); +} + +oms_status_enu_t oms::Model::addDcpSlave(ComponentDCP *component) +{ + const std::string &slaveDescriptionFile = component->getDCPInfo()->getPath(); + + ++dcpSlaveCount; + std::shared_ptr slaveDescription = getSlaveDescriptionFromDcpFile(1, 0, slaveDescriptionFile.c_str()); + if (!slaveDescription) + return logError("Failed to read slave description file: " + slaveDescriptionFile); + + uint8_t *netInfo = new uint8_t[6]; + *((uint16_t *) netInfo) = *slaveDescription->TransportProtocols.UDP_IPv4->Control->port; + *((uint32_t *) (netInfo + 2)) = asio::ip::address_v4::from_string(*slaveDescription->TransportProtocols.UDP_IPv4->Control->host).to_ulong(); + dcpDriver->getDcpDriver().setSlaveNetworkInformation(u_char(dcpSlaveCount), netInfo); + delete[] netInfo; + + dcpComponents.insert(std::make_pair(component, dcpSlaveCount)); + dcpComponentToSlaveDescriptionMap.insert(std::make_pair(component, slaveDescription)); + dcpSlaveDescriptions.push_back(slaveDescription); + + return oms_status_enu_t(); +} + +oms_status_enu_t oms::Model::addDcpConnection(oms::Connection *connection) +{ + oms::Connector* connector1 = system->getConnector(connection->getSignalA()); + oms::Connector* connector2 = system->getConnector(connection->getSignalB()); + + + oms::Component* component1 = (system->getComponent(connector1->getOwner().back())); + oms::Component* component2 = (system->getComponent(connector2->getOwner().back())); + + DcpConnection dcpConnection; + if (oms_component_dcp == component1->getType()) { + dcpConnection.fromServer = dcpComponents[dynamic_cast(component1)]; + std::shared_ptr slaveDescription = dcpComponentToSlaveDescriptionMap[dynamic_cast(component1)]; + bool foundVr = false; + for(const auto variable : slaveDescription->Variables) { + if (variable.name == connector1->getName()) + { + dcpConnection.fromVr = variable.valueReference; + foundVr = true; + break; + } + } + if (!foundVr) { + return oms_status_error; + } + } + else { + dcpConnection.fromServer = 1; // 1 is the internal system + bool foundVr = false; + for(const auto variable : dcpInternalSystemSlaveDescription->Variables) { + if (variable.name == connector1->getFullName()) + { + dcpConnection.fromVr = variable.valueReference; + foundVr = true; + break; + } + } + if (!foundVr) { + return oms_status_error; + } + } + + if (oms_component_dcp == component2->getType()) { + dcpConnection.toServers.push_back(dcpComponents[dynamic_cast(component2)]); + std::shared_ptr slaveDescription = dcpComponentToSlaveDescriptionMap[dynamic_cast(component2)]; + bool foundVr = false; + for(const auto variable : slaveDescription->Variables) { + if (variable.name == connector2->getName()) + { + dcpConnection.toVrs.push_back(variable.valueReference); + foundVr = true; + break; + } + } + if (!foundVr) { + return oms_status_error; + } + } + else { + dcpConnection.toServers.push_back(1); // 1 is the internal system + bool foundVr = false; + for(const auto variable : dcpInternalSystemSlaveDescription->Variables) { + if (variable.name == connector2->getFullName()) + { + dcpConnection.toVrs.push_back(variable.valueReference); + foundVr = true; + break; + } + } + if (!foundVr) { + return oms_status_error; + } + } + + dcpConnections.push_back(dcpConnection); + + return oms_status_enu_t(); +} + +oms_status_enu_t oms::Model::startDcpMaster() +{ + dcpComStep = 0.001; // TODO: Should not be hard-coded + + std::thread b(&DcpManagerMaster::start, dcpManager); + + std::chrono::seconds dura(1); + std::this_thread::sleep_for(dura); + DcpOpMode mode = DcpOpMode::NRT; //Todo: Support real-time mode as well + + //dcpManager->STC_register(u_char(1), DcpState::ALIVE, convertToUUID(dcpInternalSystemSlaveDescription->uuid), mode, 1, 0); + + int i=1; + for(const auto &desc : dcpSlaveDescriptions) { + dcpManager->STC_register(u_char(i), DcpState::ALIVE, convertToUUID(desc->uuid), mode, 1, 0); + ++i; + } + + b.join(); + + return oms_status_enu_t(); +} + oms_status_enu_t oms::Model::setLoggingInterval(double loggingInterval) { if (loggingInterval < 0.0) @@ -1654,3 +1855,190 @@ oms_status_enu_t oms::Model::importSignalFilter(const std::string& filename, con return oms_status_ok; } + +void oms::Model::dcpReceiveAck(uint8_t sender, uint16_t pduSeqId) { + (void)pduSeqId; + dcpReceivedAcks[sender]++; + if (dcpReceivedAcks[sender] == dcpNumOfCmd[sender]) { + dcpManager->STC_prepare(sender, DcpState::CONFIGURATION); + } +} + +void oms::Model::dcpReceiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode) { + (void)sender; + (void)pduSeqId; + (void)errorCode; + std::exit(1); +} + +void oms::Model::dcpDataReceived(uint16_t dataId, size_t length, uint8_t payload[]) +{ + (void)dataId; + (void)length; + (void)payload; +} + +void oms::Model::dcpReceiveStateChangedNotification(uint8_t sender, DcpState state) { + switch (state) { + case DcpState::CONFIGURATION: + dcpConfiguration(); + break; + case DcpState::CONFIGURED: + if (dcpIntializationRuns < dcpMaxInitRuns) { + dcpInitialize(); + + } else { + dcpRun(DcpState::CONFIGURED, sender); + } + break; + case DcpState::SYNCHRONIZED: + dcpRun(DcpState::SYNCHRONIZED, sender); + break; + + case DcpState::PREPARED: + dcpConfigure(); + break; + + case DcpState::INITIALIZED: + dcpSendOutputs(DcpState::INITIALIZED, sender); + break; + + case DcpState::RUNNING: { + if(dcpTime > dcpStopTime) { + dcpStop(); + } + else { + dcpDoStep(); + } + break; + } + case DcpState::COMPUTED: { + dcpSendOutputs(DcpState::COMPUTED, sender); + break; + } + case DcpState::STOPPED: + dcpDeregister(sender); + break; + case DcpState::ALIVE: + dcpSlavesDeregistered++; + if(dcpSlavesDeregistered == dcpSlaveDescriptions.size()) { + //simulation finished + logInfo("DCP simulation finishsed!"); + std::exit(0); + } + break; + } +} + +void oms::Model::dcpInitialize() +{ + dcpSlavesWaitingForInitialize++; + if(dcpSlavesWaitingForInitialize < dcpSlaveDescriptions.size()) { + return; + } + for(size_t i=0; iSTC_initialize(u_char(i), DcpState::CONFIGURED); + } + dcpIntializationRuns++; +} + +void oms::Model::dcpConfiguration() +{ + dcpSlavesWaitingForConfiguration++; + if(dcpSlavesWaitingForConfiguration < dcpSlaveDescriptions.size()) { + return; + } + //Count received acks so that we know when all configuration messages are acknowledged + for(size_t i=1; i<=dcpSlaveDescriptions.size(); ++i) { + dcpReceivedAcks[dcpId_t(i)] = 0; + dcpNumOfCmd[dcpId_t(i)] = 0; + + dcpManager->CFG_time_res(uint8_t(i), 1, uint32_t(std::floor(1.0/dcpComStep))); + dcpNumOfCmd[dcpId_t(i)]++; + } + + uint16_t port = 60001; + for(size_t i=0; iCFG_scope(fromServerId, dataId, DcpScope::Initialization_Run_NonRealTime); + dcpManager->CFG_output(fromServerId, dataId, 0, dcpConnections[i].fromVr); + dcpManager->CFG_steps(fromServerId, dataId, 1); + dcpNumOfCmd[fromServerId] += 3; + + for(size_t j=0; jCFG_scope(toServerId, dataId, DcpScope::Initialization_Run_NonRealTime); + dcpManager->CFG_input(toServerId, dataId, 0, dcpConnections[i].toVrs[j], DcpDataType::float64); + //dcpManager->CFG_steps(toServerId, dataId, 1); + auto toDesc = dcpSlaveDescriptions[toServerId-1]; + auto fromDesc = dcpSlaveDescriptions[fromServerId-1]; + + dcpManager->CFG_source_network_information_UDP(toServerId, + dataId, + asio::ip::address_v4::from_string(*toDesc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), + port); + dcpManager->CFG_target_network_information_UDP(fromServerId, + dataId, + asio::ip::address_v4::from_string(*toDesc->TransportProtocols.UDP_IPv4->Control->host).to_ulong(), + port); + dcpNumOfCmd[toServerId] += 3; + dcpNumOfCmd[fromServerId] += 1; + } + ++port; + } +} + +void oms::Model::dcpConfigure() +{ + dcpSlavesWaitingForConfigure++; + if(dcpSlavesWaitingForConfigure < dcpSlaveDescriptions.size()) { + return; + } + for(size_t i=0; iSTC_configure(u_char(i+1), DcpState::PREPARED); + } +} + +void oms::Model::dcpRun(DcpState currentState, uint8_t sender) +{ + std::time_t now = std::time(nullptr); + dcpManager->STC_run(sender, currentState, now + 2); +} + +void oms::Model::dcpDoStep() +{ + dcpSlavesWaitingForStep++; + if(dcpSlavesWaitingForStep < dcpSlaveDescriptions.size()) { + return; + } + dcpSlavesWaitingForStep = 0; + for(size_t i=0; iSTC_do_step(u_char(i+1),DcpState::RUNNING,1); + } + + dcpTime += dcpComStep; +} + +void oms::Model::dcpStop() +{ + dcpSlavesStopped++; + if(dcpSlavesStopped < dcpSlaveDescriptions.size()) { + return; + } + std::chrono::seconds dura(1); + std::this_thread::sleep_for(dura); + for(size_t i=0; iSTC_stop(u_char(i+1), DcpState::RUNNING); + } +} + +void oms::Model::dcpDeregister(uint8_t sender) +{ + dcpManager->STC_deregister(sender, DcpState::STOPPED); +} + +void oms::Model::dcpSendOutputs(DcpState currentState, uint8_t sender) +{ + dcpManager->STC_send_outputs(sender, currentState); +} \ No newline at end of file diff --git a/src/OMSimulatorLib/Model.h b/src/OMSimulatorLib/Model.h index bccbc5140..159037e9e 100644 --- a/src/OMSimulatorLib/Model.h +++ b/src/OMSimulatorLib/Model.h @@ -48,9 +48,28 @@ #include +#include +#include +//#include +//#include +#include + +struct DcpConnection +{ + size_t fromServer, fromVr; + std::vector toServers, toVrs; +}; + +//DCP related forward declarations +class DcpManagerMaster; +class UdpDriver; +class SlaveDescription_t; + namespace oms { class Component; + class ComponentDCP; + class Connection; class System; class Model @@ -111,6 +130,11 @@ namespace oms oms_status_enu_t terminate(); oms_status_enu_t reset(); + oms_status_enu_t configureDcpMaster(); + oms_status_enu_t addDcpSlave(ComponentDCP *component); + oms_status_enu_t addDcpConnection(oms::Connection *connection); + oms_status_enu_t startDcpMaster(); + oms_modelState_enu_t getModelState() const {return modelState;} oms_status_enu_t setStartTime(double value); @@ -187,6 +211,43 @@ namespace oms ctpl::thread_pool* pool = nullptr; ComRef new_root_cref; + + DcpManagerMaster *dcpManager; + UdpDriver* dcpDriver; + int dcpSlaveCount; + std::map dcpComponents; + std::shared_ptr dcpInternalSystemSlaveDescription; + std::map> dcpComponentToSlaveDescriptionMap; + std::vector> dcpSlaveDescriptions; + std::vector dcpConnections; + OstreamLog dcpLog; + double dcpComStep; + double dcpTime = 0; //TODO: Start time should not be hard-coded + double dcpStopTime = 10; //TODO: Should not be hard-coded + std::map dcpReceivedAcks; + std::map dcpNumOfCmd; + uint8_t dcpMaxInitRuns = 0; + uint8_t dcpIntializationRuns = 1; + uint8_t dcpSlavesWaitingForInitialize = 0; + uint8_t dcpSlavesWaitingForConfiguration = 0; + uint8_t dcpSlavesWaitingForConfigure = 0; + uint8_t dcpSlavesWaitingForStep = 0; + size_t dcpSlavesStopped = 0; + size_t dcpSlavesDeregistered = 0; + uint8_t serversRunPastStopTime = 0; + + void dcpInitialize(); + void dcpConfiguration(); + void dcpConfigure(); + void dcpRun(DcpState currentState, uint8_t sender); + void dcpDoStep(); + void dcpStop(); + void dcpDeregister(uint8_t sender); + void dcpSendOutputs(DcpState currentState, uint8_t sender); + void dcpReceiveAck(uint8_t sender, uint16_t); + void dcpReceiveNAck(uint8_t sender, uint16_t pduSeqId, DcpError errorCode); + void dcpDataReceived(uint16_t dataId, size_t length, uint8_t payload[]); + void dcpReceiveStateChangedNotification(uint8_t sender, DcpState state); }; } diff --git a/src/OMSimulatorLib/System.cpp b/src/OMSimulatorLib/System.cpp index cf5dfbc03..0b3e61e70 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -57,6 +57,8 @@ #include #include #include +#include +#include #include #include @@ -83,6 +85,9 @@ oms::System::System(const oms::ComRef& cref, oms_system_enu_t type, oms::Model* oms::System::~System() { + if (dcpThread.joinable()) + dcpThread.join(); + for (const auto& connector : connectors) if (connector) delete connector; @@ -1113,7 +1118,7 @@ oms::Connector* oms::System::getConnector(const oms::ComRef& cref) return subsystem->second->getConnector(tail); auto component = components.find(head); - if (component != components.end()) + if (component != components.end()) return component->second->getConnector(tail); for (auto& connector : connectors) @@ -2803,7 +2808,7 @@ oms_status_enu_t oms::System::renameConnectors() return oms_status_ok; } -oms_status_enu_t oms::System::startDcpSlave() +oms_status_enu_t oms::System::configureDcpSlave() { std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! port_t port = 8002; // TODO: Port should not be hard coded! @@ -2859,6 +2864,8 @@ oms_status_enu_t oms::System::startDcpSlave() if(component1 && component1->getType() == oms_component_dcp) { if(connector2 && connector2->isOutput()) { std::shared_ptr causality = make_Output_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); slaveDescription.Variables.push_back(make_Variable_output(connector2->getFullName(), valueReference_t(i), causality)); dcpOutputValueReferences.insert(std::make_pair(connector2->getFullName(), valueReference_t(i))); ++i; @@ -2876,6 +2883,8 @@ oms_status_enu_t oms::System::startDcpSlave() else if(component2 && component2->getType() == oms_component_dcp) { if(connector1 && connector1->isOutput()) { std::shared_ptr causality = make_Output_ptr(); + causality->Float64->start = std::make_shared>(); + causality->Float64->start->push_back(0.0); slaveDescription.Variables.push_back(make_Variable_output(connector1->getFullName(), valueReference_t(i), causality)); dcpOutputValueReferences.insert(std::make_pair(connector1->getFullName(), valueReference_t(i))); ++i; @@ -2912,19 +2921,33 @@ oms_status_enu_t oms::System::startDcpSlave() dcpManager->setTimeResListener( std::bind(&dcpSetTimeRes, this, std::placeholders::_1, std::placeholders::_2)); dcpManager->setStopCallback( std::bind(&dcpStop, this)); - //For some reason we get linker errors when adding the log listener. Should be fixed later. + dcpManager->addLogListener([this](auto&& arg) { + dcpLog.logOstream(std::forward(arg)); +}); + //dcpManager->addLogListener(std::bind(&OstreamLog::logOstream, dcpLog, std::placeholders::_1)); dcpManager->setGenerateLogString(true); + return oms_status_enu_t(); +} + +oms_status_enu_t oms::System::startDcpSlave() +{ + if (dcpThread.joinable()) + dcpThread.join(); + + dcpThread = std::thread([this]() { dcpManager->start(); + }); - return oms_status_enu_t(); + return oms_status_ok; } void oms::System::dcpConfigure() { - for(const auto input : dcpInputValueReferences) + for(const auto input : dcpInputValueReferences) { dcpInputs.insert(std::make_pair(input.first, dcpManager->getInput(input.second))); + } for(const auto output : dcpOutputValueReferences) dcpOutputs.insert(std::make_pair(output.first, dcpManager->getOutput(output.second))); @@ -2937,25 +2960,36 @@ void oms::System::dcpInitialize() void oms::System::dcpDoStep(uint64_t steps) { - //Read inputs - for(const auto input : dcpInputs) - this->setReal(input.first, *(input.second)); + //Read inputs from DCP pointers + for(const auto input : dcpInputs) { + ComRef cref = input.first; + cref.pop_front(); + cref.pop_front(); + this->setReal(cref, *(input.second)); + } //Take some steps - this->stepUntil(steps*dcpTimeStep); + dcpTime += steps*dcpTimeStep; + this->stepUntil(dcpTime); - //Write outputs - for(const auto output : dcpOutputs) - this->setReal(output.first, *(output.second)); + //Write outputs to DCP pointers + for(const auto output : dcpOutputs) { + ComRef cref = output.first; + cref.pop_front(); + cref.pop_front(); + double value; + this->getReal(cref, value); + *(output.second) = value; + } } void oms::System::dcpSetTimeRes(const uint32_t numerator, const uint32_t denominator) { + //Implement later if needed } void oms::System::dcpStop() { - dcpDriver->getDcpDriver().stopReceiving(); - dcpDriver->getDcpDriver().disconnect(); + //Nothing to do } diff --git a/src/OMSimulatorLib/System.h b/src/OMSimulatorLib/System.h index b1f14beba..175fe1149 100644 --- a/src/OMSimulatorLib/System.h +++ b/src/OMSimulatorLib/System.h @@ -59,7 +59,7 @@ class DcpManagerSlave; class UdpDriver; -class OstreamLog; +//class OstreamLog; namespace oms { @@ -203,6 +203,7 @@ namespace oms Values& getValues() { return values; } std::map fmuGuid; + oms_status_enu_t configureDcpSlave(); oms_status_enu_t startDcpSlave(); void dcpConfigure(); void dcpInitialize(); @@ -259,6 +260,8 @@ namespace oms std::vector busconnectors; std::vector connections; ///< last element is always NULL std::vector dcpConnections; ///< dummy connections to be used for setting up a DCP simulation + std::thread dcpThread; + bool loopsNeedUpdate = true; std::vector algLoops; ///< vector of algebraic loop objects @@ -267,7 +270,8 @@ namespace oms DcpManagerSlave *dcpManager; UdpDriver* dcpDriver; OstreamLog dcpLog; - double dcpTimeStep; + double dcpTime = 0; //TODO: Start time should not be hard-coded + double dcpTimeStep = 0.001; //TODO: Time step should not be hard-coded std::map dcpInputs; std::map dcpOutputs; std::map dcpInputValueReferences; diff --git a/src/OMSimulatorLib/Variable.cpp b/src/OMSimulatorLib/Variable.cpp index 986892fa7..fd2a64c67 100644 --- a/src/OMSimulatorLib/Variable.cpp +++ b/src/OMSimulatorLib/Variable.cpp @@ -61,7 +61,7 @@ oms::Variable::Variable(fmiHandle* fmi4c, int index_, oms_component_enu_t compon } } -oms::Variable::Variable(SlaveDescription_t *desc, int index) +oms::Variable::Variable(std::shared_ptr desc, int index) : dcp(true), fmi2(false), fmi3(false), index(index), componentType(oms_component_dcp) { configureDCPVariable(desc, index); @@ -209,7 +209,7 @@ void oms::Variable::configureFMI3Variable(fmiHandle* fmi4c, int index_) } } -void oms::Variable::configureDCPVariable(SlaveDescription_t *desc, int index) +void oms::Variable::configureDCPVariable(std::shared_ptr desc, int index) { this->index = index; diff --git a/src/OMSimulatorLib/Variable.h b/src/OMSimulatorLib/Variable.h index 14f8ec2be..8a19886ca 100644 --- a/src/OMSimulatorLib/Variable.h +++ b/src/OMSimulatorLib/Variable.h @@ -58,7 +58,7 @@ namespace oms { public: Variable(fmiHandle * fmi4c, int index, oms_component_enu_t componentType); - Variable(SlaveDescription_t *desc, int index); //For DCP components, component type is implicit + Variable(std::shared_ptr desc, int index); //For DCP components, component type is implicit ~Variable(); void markAsState(size_t der_index) { is_state = true; this->der_index = der_index; } @@ -121,7 +121,7 @@ namespace oms void configureFMI2Variable(fmiHandle *fmi4c, int index); void configureFMI3Variable(fmiHandle *fmi4c, int index); - void configureDCPVariable(SlaveDescription_t *desc, int index); + void configureDCPVariable(std::shared_ptr desc, int index); ComRef cref; std::string description; diff --git a/src/OMSimulatorPython/dcp.py b/src/OMSimulatorPython/dcp.py index 8238ffc06..060953421 100644 --- a/src/OMSimulatorPython/dcp.py +++ b/src/OMSimulatorPython/dcp.py @@ -100,7 +100,6 @@ def _parse_variables(self, slave_description): if causality_element: causality_element = causality_element[0] causality = causality_element.tag.lower() - print("Causality: "+causality) # Find the first child (type node) and extract attributes type_element = causality_element.xpath('./*') # Selects the first child element diff --git a/src/OMSimulatorPython/instantiated_model.py b/src/OMSimulatorPython/instantiated_model.py index 282dbbd38..c6cebcd7b 100644 --- a/src/OMSimulatorPython/instantiated_model.py +++ b/src/OMSimulatorPython/instantiated_model.py @@ -212,8 +212,6 @@ def __init__(self, json_description, system: System, resources: dict): start_element = ".".join(connection['start element'] + [connection['start connector']]) end_element = ".".join(connection['end element'] + [connection['end connector']]) - print(f"Adding connection from {start_element} to {end_element}") - if start_element not in self.mappedCrefs: raise KeyError(f"No mapping found for {start_element}") if end_element not in self.mappedCrefs: diff --git a/src/OMSimulatorPython/variable.py b/src/OMSimulatorPython/variable.py index f976ef2b7..1a6b405b1 100644 --- a/src/OMSimulatorPython/variable.py +++ b/src/OMSimulatorPython/variable.py @@ -114,7 +114,6 @@ def __init__(self, name: Union[str, CRef], description : str, valueReference: Un self.name = CRef(name) self.description = description self.valueReference = int(valueReference) - print("Variable causality: "+causality) self.causality = causality if isinstance(causality, Causality) else Causality[causality] self.variability = variability self.signal_type = signal_type if isinstance(signal_type, SignalType) else SignalType[signal_type] From 3e38e4f4b473171c07566c9d3d06a9a6f4f04966 Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 1 Jun 2026 10:01:20 +0200 Subject: [PATCH 09/10] Remove unused code and some comments --- src/OMSimulatorLib/ComponentDCP.cpp | 6 +++--- src/OMSimulatorLib/FMUInfo.cpp | 8 -------- src/OMSimulatorLib/FMUInfo.h | 1 - src/OMSimulatorLib/Model.cpp | 2 -- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/OMSimulatorLib/ComponentDCP.cpp b/src/OMSimulatorLib/ComponentDCP.cpp index 71d8c0db2..ba71766a4 100644 --- a/src/OMSimulatorLib/ComponentDCP.cpp +++ b/src/OMSimulatorLib/ComponentDCP.cpp @@ -339,18 +339,18 @@ oms_status_enu_t oms::ComponentDCP::updateSignals(ResultWriter &resultWriter) void oms::ComponentDCP::getFilteredSignals(std::vector &filteredSignals) const { - // TODO: Implement (dcp) + // Not sure if needed? } oms::Variable *oms::ComponentDCP::getVariable(const ComRef &cref) { - // TODO: Implement (dcp) + // Not sure if needed? return nullptr; } oms_status_enu_t oms::ComponentDCP::initializeDependencyGraph_outputs() { - // TODO: Does this work? (dcp) + // Not sure if this is correct if (outputsGraph.getEdges().connections.size() > 0) { diff --git a/src/OMSimulatorLib/FMUInfo.cpp b/src/OMSimulatorLib/FMUInfo.cpp index 1927875b9..b1be2c187 100644 --- a/src/OMSimulatorLib/FMUInfo.cpp +++ b/src/OMSimulatorLib/FMUInfo.cpp @@ -90,9 +90,6 @@ void oms::FMUInfo::update(oms_component_enu_t componentType, fmiHandle* fmu) case oms_component_fmu3: // FMI 3.0 updateFMI3Info(fmu); break; - case oms_component_dcp: // DCP - updateDCPInfo(); - break; default: // Unsupported type logError("Unsupported component type for Variable constructor"); } @@ -200,8 +197,3 @@ void oms::FMUInfo::updateFMI3Info(fmiHandle* fmu) //providesEvaluateDiscreteStates } } - -void oms::FMUInfo::updateDCPInfo() -{ - //Not sure if we need anything for DCP? -} diff --git a/src/OMSimulatorLib/FMUInfo.h b/src/OMSimulatorLib/FMUInfo.h index ac118047a..a7ddb9485 100644 --- a/src/OMSimulatorLib/FMUInfo.h +++ b/src/OMSimulatorLib/FMUInfo.h @@ -65,7 +65,6 @@ namespace oms private: void updateFMI2Info(fmiHandle *fmi4c); void updateFMI3Info(fmiHandle *fmi4c); - void updateDCPInfo(); // methods to copy the object FMUInfo(const FMUInfo& rhs); ///< not implemented diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index 613638c7e..590804a85 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -1293,8 +1293,6 @@ oms_status_enu_t oms::Model::simulate() return logError("Model doesn't contain a system"); } - // DCPTODO: If system contains DCP components, start a DCP simulation where the system is wrapped as a DCP slave. - //Loop over system components and check if any of them is a DCP component. If yes, start a DCP simulation where the system is wrapped as a DCP slave. bool dcpComponentDetected = false; std::vector dcpComponentsList; From dc088ac037b73b1fc229ccb3e61b4ef5ea13401a Mon Sep 17 00:00:00 2001 From: Robert Braun Date: Mon, 1 Jun 2026 15:52:07 +0200 Subject: [PATCH 10/10] Select DCP ports from Python --- include/OMSimulator/OMSimulator.h | 1 + src/OMSimulatorLib/Model.cpp | 50 +++++++++++++++------ src/OMSimulatorLib/Model.h | 8 +++- src/OMSimulatorLib/OMSimulator.cpp | 14 +++++- src/OMSimulatorLib/System.cpp | 26 ++++++++--- src/OMSimulatorLib/System.h | 6 ++- src/OMSimulatorPython/capi.py | 7 +++ src/OMSimulatorPython/instantiated_model.py | 8 ++++ 8 files changed, 96 insertions(+), 24 deletions(-) diff --git a/include/OMSimulator/OMSimulator.h b/include/OMSimulator/OMSimulator.h index 764ecfa5e..cb62d4677 100644 --- a/include/OMSimulator/OMSimulator.h +++ b/include/OMSimulator/OMSimulator.h @@ -146,6 +146,7 @@ OMSAPI oms_status_enu_t OMSCALL oms_setConnectorGeometry(const char* cref, const OMSAPI oms_status_enu_t OMSCALL oms_setConnectorNumericType(const char* cref, const oms_signal_numeric_type_enu_t numericType); OMSAPI oms_status_enu_t OMSCALL oms_setElementGeometry(const char* cref, const ssd_element_geometry_t* geometry); OMSAPI oms_status_enu_t OMSCALL oms_setFixedStepSize(const char* cref, double stepSize); +OMSAPI oms_status_enu_t OMSCALL oms_setDcpPorts(const char* cref, int masterPort, int slavePort); OMSAPI oms_status_enu_t OMSCALL oms_setInteger(const char* cref, int value); OMSAPI oms_status_enu_t OMSCALL oms_setLogFile(const char* filename); OMSAPI void OMSCALL oms_setLoggingCallback(void (*cb)(oms_message_type_enu_t type, const char* message)); diff --git a/src/OMSimulatorLib/Model.cpp b/src/OMSimulatorLib/Model.cpp index 590804a85..8c8cd5023 100644 --- a/src/OMSimulatorLib/Model.cpp +++ b/src/OMSimulatorLib/Model.cpp @@ -64,7 +64,7 @@ #include oms::Model::Model(const oms::ComRef& cref, const std::string& tempDir) - : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat"), dcpLog(std::cout) + : cref(cref), tempDir(tempDir), resultFilename(std::string(cref) + "_res.mat"), dcpLog(std::cout), dcpMasterPort(8000), dcpSlavePort(8001) { if (!Flags::SuppressPath()) logInfo("New model \"" + std::string(cref) + "\" with corresponding temp directory \"" + tempDir + "\""); @@ -1309,7 +1309,7 @@ oms_status_enu_t oms::Model::simulate() if(dcpComponentDetected) { //Start a DCP slave instance for the system - system->configureDcpSlave(); + system->configureDcpSlave(dcpSlavePort); this->configureDcpMaster(); @@ -1340,7 +1340,11 @@ oms_status_enu_t oms::Model::simulate() logInfo("Running a DCP simulation..."); - system->startDcpSlave(); //Separate thread + oms_status_enu_t status = system->startDcpSlave(); //Separate thread + + if(status != oms_status_ok) { + return status; + } this->startDcpMaster(); @@ -1451,11 +1455,10 @@ oms_status_enu_t oms::Model::reset() oms_status_enu_t oms::Model::configureDcpMaster() { - std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! - port_t port = 8000; // TODO: Port should not be hard coded! + std::string host = "127.0.0.1"; std::string internalSystemFile = "OMSimulatorSystem_slave_description.dcp"; - dcpDriver = new UdpDriver(host, port); + dcpDriver = new UdpDriver(host, dcpMasterPort); std::shared_ptr slaveDescription = getSlaveDescriptionFromDcpFile(1, 0, internalSystemFile.c_str()); dcpInternalSystemSlaveDescription = slaveDescription; @@ -1590,27 +1593,46 @@ oms_status_enu_t oms::Model::addDcpConnection(oms::Connection *connection) oms_status_enu_t oms::Model::startDcpMaster() { - dcpComStep = 0.001; // TODO: Should not be hard-coded + dcpComStep = system->getMaximumStepSize(); - std::thread b(&DcpManagerMaster::start, dcpManager); + std::thread b(&Model::startDcpMasterThread, this); std::chrono::seconds dura(1); std::this_thread::sleep_for(dura); DcpOpMode mode = DcpOpMode::NRT; //Todo: Support real-time mode as well - //dcpManager->STC_register(u_char(1), DcpState::ALIVE, convertToUUID(dcpInternalSystemSlaveDescription->uuid), mode, 1, 0); - - int i=1; - for(const auto &desc : dcpSlaveDescriptions) { - dcpManager->STC_register(u_char(i), DcpState::ALIVE, convertToUUID(desc->uuid), mode, 1, 0); - ++i; + if(dcpMasterThreadReturnValue == oms_status_ok) { + int i=1; + for(const auto &desc : dcpSlaveDescriptions) { + dcpManager->STC_register(u_char(i), DcpState::ALIVE, convertToUUID(desc->uuid), mode, 1, 0); + ++i; + } } b.join(); + return dcpMasterThreadReturnValue; +} + +oms_status_enu_t oms::Model::setDcpPorts(int masterPort, int slavePort) +{ + this->dcpMasterPort = masterPort; + this->dcpSlavePort = slavePort; return oms_status_enu_t(); } +void oms::Model::startDcpMasterThread() +{ + dcpMasterThreadReturnValue = oms_status_ok; + try { + dcpManager->start(); + } + catch (std::exception& e) { + logError(e.what()); + dcpMasterThreadReturnValue = oms_status_error; + } +} + oms_status_enu_t oms::Model::setLoggingInterval(double loggingInterval) { if (loggingInterval < 0.0) diff --git a/src/OMSimulatorLib/Model.h b/src/OMSimulatorLib/Model.h index 159037e9e..fedfb30b6 100644 --- a/src/OMSimulatorLib/Model.h +++ b/src/OMSimulatorLib/Model.h @@ -133,7 +133,9 @@ namespace oms oms_status_enu_t configureDcpMaster(); oms_status_enu_t addDcpSlave(ComponentDCP *component); oms_status_enu_t addDcpConnection(oms::Connection *connection); + void startDcpMasterThread(); oms_status_enu_t startDcpMaster(); + oms_status_enu_t setDcpPorts(int masterPort, int slavePort); oms_modelState_enu_t getModelState() const {return modelState;} @@ -142,7 +144,7 @@ namespace oms oms_status_enu_t setStopTime(double value); double getStopTime() const {return stopTime;} double getTime() const; - + oms_status_enu_t setLoggingInterval(double loggingInterval); double getLoggingInterval() const {return loggingInterval;} oms_status_enu_t setResultFile(const std::string& filename, int bufferSize); @@ -214,6 +216,8 @@ namespace oms DcpManagerMaster *dcpManager; UdpDriver* dcpDriver; + port_t dcpMasterPort; + port_t dcpSlavePort; int dcpSlaveCount; std::map dcpComponents; std::shared_ptr dcpInternalSystemSlaveDescription; @@ -235,6 +239,8 @@ namespace oms size_t dcpSlavesStopped = 0; size_t dcpSlavesDeregistered = 0; uint8_t serversRunPastStopTime = 0; + oms_status_enu_t dcpMasterThreadReturnValue; + void dcpInitialize(); void dcpConfiguration(); diff --git a/src/OMSimulatorLib/OMSimulator.cpp b/src/OMSimulatorLib/OMSimulator.cpp index 7c9f55387..65e11f934 100644 --- a/src/OMSimulatorLib/OMSimulator.cpp +++ b/src/OMSimulatorLib/OMSimulator.cpp @@ -1697,7 +1697,19 @@ oms_status_enu_t oms_setFixedStepSize(const char* cref, double stepSize) return logError_SystemNotInModel(model->getCref(), front); } -oms_status_enu_t oms_setVariableStepSize(const char* cref, double initialStepSize, double minimumStepSize, double maximumStepSize) +OMSAPI oms_status_enu_t OMSCALL oms_setDcpPorts(const char *cref, int masterPort, int slavePort) +{ + oms::ComRef tail(cref); + oms::ComRef front = tail.pop_front(); + + oms::Model* model = oms::Scope::GetInstance().getModel(front); + if (!model) + return logError_ModelNotInScope(front); + + return model->setDcpPorts(masterPort, slavePort); +} + +oms_status_enu_t oms_setVariableStepSize(const char *cref, double initialStepSize, double minimumStepSize, double maximumStepSize) { oms::ComRef tail(cref); oms::ComRef front = tail.pop_front(); diff --git a/src/OMSimulatorLib/System.cpp b/src/OMSimulatorLib/System.cpp index 0b3e61e70..50f161e94 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -2808,11 +2808,10 @@ oms_status_enu_t oms::System::renameConnectors() return oms_status_ok; } -oms_status_enu_t oms::System::configureDcpSlave() +oms_status_enu_t oms::System::configureDcpSlave(port_t port) { - std::string host = "127.0.0.1"; // TODO: Port should not be hard coded! - port_t port = 8002; // TODO: Port should not be hard coded! - double dcpTimeStep = 0.001; // TODO: Time step should not be hard coded! + std::string host = "127.0.0.1"; + dcpTimeStep = this->getMaximumStepSize(); std::string targetFile = "OMSimulatorSystem_slave_description.dcp"; dcpInputs.clear(); @@ -2937,10 +2936,25 @@ oms_status_enu_t oms::System::startDcpSlave() dcpThread.join(); dcpThread = std::thread([this]() { - dcpManager->start(); + startDcpSlaveThread(); }); - return oms_status_ok; + std::chrono::seconds dura(1); + std::this_thread::sleep_for(dura); + + return dcpSlaveThreadReturnValue; +} + +void oms::System::startDcpSlaveThread() +{ + dcpSlaveThreadReturnValue = oms_status_ok; + try { + dcpManager->start(); + } + catch (std::exception& e) { + logError(e.what()); + dcpSlaveThreadReturnValue = oms_status_error; + } } void oms::System::dcpConfigure() diff --git a/src/OMSimulatorLib/System.h b/src/OMSimulatorLib/System.h index 175fe1149..269e87736 100644 --- a/src/OMSimulatorLib/System.h +++ b/src/OMSimulatorLib/System.h @@ -203,8 +203,9 @@ namespace oms Values& getValues() { return values; } std::map fmuGuid; - oms_status_enu_t configureDcpSlave(); + oms_status_enu_t configureDcpSlave(port_t port); oms_status_enu_t startDcpSlave(); + void startDcpSlaveThread(); void dcpConfigure(); void dcpInitialize(); void dcpDoStep(uint64_t steps); @@ -271,11 +272,12 @@ namespace oms UdpDriver* dcpDriver; OstreamLog dcpLog; double dcpTime = 0; //TODO: Start time should not be hard-coded - double dcpTimeStep = 0.001; //TODO: Time step should not be hard-coded + double dcpTimeStep = 0.001; std::map dcpInputs; std::map dcpOutputs; std::map dcpInputValueReferences; std::map dcpOutputValueReferences; + oms_status_enu_t dcpSlaveThreadReturnValue; }; } diff --git a/src/OMSimulatorPython/capi.py b/src/OMSimulatorPython/capi.py index 81432932b..87f9fca6f 100644 --- a/src/OMSimulatorPython/capi.py +++ b/src/OMSimulatorPython/capi.py @@ -163,6 +163,8 @@ def __init__(self): self.obj.oms_setTolerance.restype = ctypes.c_int self.obj.oms_setFixedStepSize.argtypes = [ctypes.c_char_p, ctypes.c_double] self.obj.oms_setFixedStepSize.restype = ctypes.c_int + self.obj.oms_setDcpPorts.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int] + self.obj.oms_setDcpPorts.restype = ctypes.c_int self.obj.oms_setVariableStepSize.argtypes = [ctypes.c_char_p, ctypes.c_double, ctypes.c_double, ctypes.c_double] self.obj.oms_setVariableStepSize.restype = ctypes.c_int self.obj.oms_setLogFile.argtypes = [ctypes.c_char_p] @@ -370,6 +372,11 @@ def setFixedStepSize(self, cref, stepSize) -> Status: status = self.obj.oms_setFixedStepSize(cref.encode(), stepSize) return Status(status) + def setDcpPorts(self, cref, masterPort, slavePort) -> Status: + '''Set the ports for DCP simulation.''' + status = self.obj.oms_setDcpPorts(cref.encode(), masterPort, slavePort) + return Status(status) + def setVariableStepSize(self, cref, initialStepSize, minStepSize, maxStepSize) -> Status: '''Set the variable step size parameters for the simulation.''' status = self.obj.oms_setVariableStepSize(cref.encode(), initialStepSize, minStepSize, maxStepSize) diff --git a/src/OMSimulatorPython/instantiated_model.py b/src/OMSimulatorPython/instantiated_model.py index c6cebcd7b..e097888dd 100644 --- a/src/OMSimulatorPython/instantiated_model.py +++ b/src/OMSimulatorPython/instantiated_model.py @@ -601,6 +601,14 @@ def setFixedStepSize(self, stepSize: float): if status != Status.ok: raise RuntimeError(f"Failed to set fixed step size: {status}") + def setDcpPorts(self, masterPort: int, slavePort: int): + if self.fmuInstantitated is False: + raise RunTimeError("FMU must be instantiated before setting DCP ports") + + status = Capi.setDcpPorts(f"{self.modelName}.root", masterPort, slavePort) + if status != Status.ok: + raise RuntimeError(f"Failed to set DCP ports: {status}") + def setVariableStepSize(self, initialStepSize: float, minimumStepSize: float, maximumStepSize: float): if self.fmuInstantitated is False: raise RuntimeError("FMU must be instantiated before setting variable step size")