diff --git a/include/OMSimulator/Types.h b/include/OMSimulator/Types.h index a51d29131..ab6c1bfdd 100644 --- a/include/OMSimulator/Types.h +++ b/include/OMSimulator/Types.h @@ -101,7 +101,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 193eca2ac..3d824b3d2 100644 --- a/src/OMSimulatorLib/FMUInfo.cpp +++ b/src/OMSimulatorLib/FMUInfo.cpp @@ -86,6 +86,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"); } @@ -192,4 +195,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 d13ee756f..e29ae9a20 100644 --- a/src/OMSimulatorLib/FMUInfo.h +++ b/src/OMSimulatorLib/FMUInfo.h @@ -61,6 +61,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 9b249bf2a..91a2477a4 100644 --- a/src/OMSimulatorLib/System.cpp +++ b/src/OMSimulatorLib/System.cpp @@ -34,6 +34,7 @@ #include "Component.h" #include "ComponentFMUCS.h" #include "ComponentFMU3CS.h" +#include "ComponentDCP.h" #include "ComponentFMUME.h" #include "ComponentFMU3ME.h" #include "ComponentTable.h" @@ -314,6 +315,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") @@ -323,7 +326,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 ffefd17c2..1647082a0 100644 --- a/src/OMSimulatorLib/Values.cpp +++ b/src/OMSimulatorLib/Values.cpp @@ -35,6 +35,8 @@ #include "Logging.h" #include "ssd/Tags.h" #include "Util.h" +#include "XercesValidator.h" +#include "dcp/zip/DcpSlaveReader.hpp" #include #include @@ -1858,6 +1860,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 6347638f3..5550b7942 100644 --- a/src/OMSimulatorLib/Values.h +++ b/src/OMSimulatorLib/Values.h @@ -112,6 +112,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); @@ -160,6 +161,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 8b2fa7f08..f11408043 100644 --- a/src/OMSimulatorLib/Variable.cpp +++ b/src/OMSimulatorLib/Variable.cpp @@ -34,6 +34,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) @@ -57,6 +58,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_) { @@ -199,6 +205,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 a4a14fcbc..69c633874 100644 --- a/src/OMSimulatorLib/Variable.h +++ b/src/OMSimulatorLib/Variable.h @@ -35,17 +35,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; } @@ -87,6 +96,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; } @@ -106,6 +116,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; @@ -123,6 +134,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 b67a90d46..d1cc7488f 100644 --- a/src/OMSimulatorPython/ssp.py +++ b/src/OMSimulatorPython/ssp.py @@ -11,6 +11,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 @@ -112,6 +113,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[Path(filename).name] = new_name diff --git a/src/OMSimulatorPython/system.py b/src/OMSimulatorPython/system.py index 7d15aa189..93618a10a 100644 --- a/src/OMSimulatorPython/system.py +++ b/src/OMSimulatorPython/system.py @@ -641,6 +641,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 71cb535ee..7bc48fe10 100644 --- a/src/OMSimulatorPython/variable.py +++ b/src/OMSimulatorPython/variable.py @@ -82,6 +82,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]