diff --git a/DashboardClient/DashboardClient.cpp b/DashboardClient/DashboardClient.cpp index 345d6d4e..22d269af 100644 --- a/DashboardClient/DashboardClient.cpp +++ b/DashboardClient/DashboardClient.cpp @@ -1,7 +1,7 @@ - /* This Source Code Form is subject to the terms of the Mozilla Public +/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * * Copyright 2019-2022 (c) Christian von Arnim, ISW University of Stuttgart (for umati and VDW e.V.) * Copyright 2020 (c) Dominik Basner, Sotec GmbH (for VDW e.V.) * Copyright 2021 (c) Moritz Walker, ISW University of Stuttgart (for umati and VDW e.V.) @@ -14,516 +14,402 @@ #include #include "Converter/ModelToJson.hpp" -namespace Umati -{ - - namespace Dashboard - { - - DashboardClient::DashboardClient( - std::shared_ptr pDashboardDataClient, - std::shared_ptr pPublisher, - std::shared_ptr pTypeReader) - : m_pDashboardDataClient(pDashboardDataClient), m_pPublisher(pPublisher), m_pTypeReader(pTypeReader) - { - } - - - /** - * Receives a nodeId, a typeDefinition and an mqtt topic to hold for a machine. Available types are - * Identification, JobCurrentStateNumber, ProductionJobList, Stacklight, StateModelList, ToolList - */ - void DashboardClient::addDataSet( - const ModelOpcUa::NodeId_t &startNodeId, - const std::shared_ptr &pTypeDefinition, - const std::string &channel, - const std::string &onlineChannel - ) - { - try - { - std::shared_ptr pDataSetStorage = prepareDataSetStorage( - startNodeId, - pTypeDefinition, - channel, - onlineChannel); - LOG(INFO) << "DataSetStorage prepared for " << channel; - subscribeValues(pDataSetStorage->node, pDataSetStorage->values, pDataSetStorage->values_mutex); - LOG(INFO) << "Values subscribed for " << channel; - std::lock_guard l(m_dataSetMutex); - m_dataSets.push_back(pDataSetStorage); - } - catch (const Umati::Exceptions::OpcUaException &ex) - { - LOG(WARNING) << ex.what(); - } - catch (MachineObserver::Exceptions::MachineInvalidChildException &ex) - { - LOG(ERROR) << ex.what(); - throw Exceptions::OpcUaException(ex.what()); - } - catch (std::exception &ex) - { - LOG(ERROR) << ex.what(); - throw Exceptions::OpcUaException(ex.what()); - } - } - - std::shared_ptr - DashboardClient::prepareDataSetStorage(const ModelOpcUa::NodeId_t &startNodeId, - const std::shared_ptr &pTypeDefinition, - const std::string &channel, - const std::string &onlineChannel) - { - auto pDataSetStorage = std::make_shared(); - pDataSetStorage->startNodeId = startNodeId; - pDataSetStorage->channel = channel; - pDataSetStorage->onlineChannel = onlineChannel; - pDataSetStorage->node = TransformToNodeIds(startNodeId, pTypeDefinition); - return pDataSetStorage; - } - - void DashboardClient::Publish() - { - std::lock_guard l(m_dataSetMutex); - for (auto &pDataSetStorage : m_dataSets) - { - std::string jsonPayload = getJson(pDataSetStorage); - if (!jsonPayload.empty() && jsonPayload != "null") - { - LastMessage_t &lastMessage = m_latestMessages[pDataSetStorage->channel]; - - time_t now; - time(&now); - - if (jsonPayload != lastMessage.payload || difftime(now, lastMessage.lastSent) > 10) - { - m_pPublisher->Publish(pDataSetStorage->channel, jsonPayload); - lastMessage.payload = jsonPayload; - lastMessage.lastSent = now; - } - m_pPublisher->Publish(pDataSetStorage->onlineChannel, "1"); - } - else - { - LOG(INFO) << "pdatasetstorage for " << pDataSetStorage->startNodeId.Uri << ";" - << pDataSetStorage->startNodeId.Id << " is empty"; - } - } - } - - void DashboardClient::Unsubscribe(ModelOpcUa::NodeId_t nodeId){ - - std::vector monItemIds; - std::vector clientHandles; - for (auto values : m_subscribedValues){ - - auto value = values.get(); - - if(value){ - monItemIds.push_back(value->getMonitoredItemId()); - clientHandles.push_back(value->getClientHandle()); - }else{ - LOG(ERROR) << "Monitored Item is NULL for NodeId: " << nodeId.Id; - } - - } - m_subscribedValues.clear(); - - m_pDashboardDataClient->Unsubscribe(monItemIds, clientHandles); - - std::lock_guard l(m_dataSetMutex); - m_dataSets.clear(); - - } - - std::string DashboardClient::getJson(const std::shared_ptr &pDataSetStorage) - { - auto getValueCallback = [pDataSetStorage]( - const std::shared_ptr &pNode) -> nlohmann::json { - std::unique_lockvalues_mutex)> ul(pDataSetStorage->values_mutex); - auto it = pDataSetStorage->values.find(pNode); - if (it == pDataSetStorage->values.end()) { - LOG(DEBUG) << "Couldn't write value for " << pNode->SpecifiedBrowseName.Name << " | " << pNode->SpecifiedTypeNodeId.Uri << ";" << pNode->SpecifiedTypeNodeId.Id << "Try to search it with NodeId!"; - // In case we don't wnt to remove the duplicate pointers with FIX_1, we can simply check the - // Identity of the node via its node Id. - auto pSimpleNode = std::dynamic_pointer_cast(pNode); - if(pSimpleNode) { - LOG(DEBUG) << pSimpleNode->NodeId << "\n"; - auto values = pDataSetStorage->values; - for(auto it1 : values) { - auto pSimpleNode1 = std::dynamic_pointer_cast(it1.first); - if(pSimpleNode1->NodeId == pSimpleNode->NodeId) { - // DEBUG_BEGIN in case we want to see the different pointer addresses. - LOG(DEBUG) << pSimpleNode.get() << "\n"; - LOG(DEBUG) << pSimpleNode1.get() << "\n"; - LOG(DEBUG) << pNode->SpecifiedBrowseName.Name << " " << "found!"; - return it1.second; - } - } - } - LOG(DEBUG) << pNode->SpecifiedBrowseName.Name << " " << " not found!"; - return nullptr; - } - return it->second; - }; - - return Converter::ModelToJson(pDataSetStorage->node, getValueCallback).getJson().dump(2); - } - - std::shared_ptr DashboardClient::TransformToNodeIds( - ModelOpcUa::NodeId_t startNode, - const std::shared_ptr &pTypeDefinition) - { - auto ret = browsedNodes.insert(startNode); - - // FIX_BEGIN (FIX_1), removed duplicates to avoid different pointers to the same node - (*pTypeDefinition->SpecifiedChildNodes).sort(); - (*pTypeDefinition->SpecifiedChildNodes).unique(); - // FIX_END - std::list> foundChildNodes; - if(ret.second==true) { - for (auto &pChild : *pTypeDefinition->SpecifiedChildNodes) - { - switch (pChild->ModellingRule) - { - case ModelOpcUa::ModellingRule_t::Optional: - case ModelOpcUa::ModellingRule_t::Mandatory: - { - bool should_continue = OptionalAndMandatoryTransformToNodeId( - startNode, - foundChildNodes, - pChild); - if (should_continue) - { - continue; - } - break; - } - case ModelOpcUa::ModellingRule_t::OptionalPlaceholder: - case ModelOpcUa::ModellingRule_t::MandatoryPlaceholder: - { - bool should_continue = OptionalAndMandatoryPlaceholderTransformToNodeId( - startNode, - foundChildNodes, - pChild); - if (should_continue) - { - continue; - } - break; - } - case ModelOpcUa::ModellingRule_t::None: - { - LOG(INFO) << "modelling rule is none"; - LOG(ERROR) << "Unknown Modelling Rule None." << std::endl; - break; - } - default: - LOG(ERROR) << "Unknown Modelling Rule." << std::endl; - break; - } - } - } - auto pNode = std::make_shared( - startNode, - pTypeDefinition->SpecifiedTypeNodeId, - *pTypeDefinition, - foundChildNodes); - - pNode->ofBaseDataVariableType = pTypeDefinition->ofBaseDataVariableType; - return pNode; - } - - void LogOptionalAndMandatoryTransformToNodeIdError(const ModelOpcUa::NodeId_t &nodeId, const ModelOpcUa::QualifiedName_t &childBrowsName, const char *err) { - LOG(ERROR) << "Forwarding exception, cause:" - << "Could not find '" - << static_cast(nodeId) - << "'->'" - << static_cast(childBrowsName) - << "'" - << "Unknown ID caused exception: " << err; - } +namespace Umati { - std::string GetOptionalAndMandatoryTransformToNodeIdError(const ModelOpcUa::NodeId_t &nodeId, const ModelOpcUa::QualifiedName_t &childBrowseName, const char *err) { - return "In '" + static_cast(nodeId) - + "'->'" - + static_cast(childBrowseName) - + "':\n" + err; - } +namespace Dashboard { + +DashboardClient::DashboardClient( + std::shared_ptr pDashboardDataClient, std::shared_ptr pPublisher, std::shared_ptr pTypeReader) + : m_pDashboardDataClient(pDashboardDataClient), m_pPublisher(pPublisher), m_pTypeReader(pTypeReader) {} - /** - * @return if the switch case should break - */ - bool DashboardClient::OptionalAndMandatoryTransformToNodeId(const ModelOpcUa::NodeId_t &startNode, - std::list> &foundChildNodes, - const std::shared_ptr &pChild) - { - try - { - auto childNodeId = m_pDashboardDataClient->TranslateBrowsePathToNodeId(startNode, - pChild->SpecifiedBrowseName); - if (childNodeId.isNull()) - { - TransformToNodeIdNodeNotFoundLog(startNode, pChild); - return false; - } - auto nodeIds = TransformToNodeIds(childNodeId, pChild); - foundChildNodes.push_back(nodeIds); - } - catch (MachineObserver::Exceptions::MachineInvalidChildException &ex) - { - if (ex.hasInvalidMandatoryChild) - { - std::string err = GetOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); - LogOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); - throw MachineObserver::Exceptions::MachineInvalidChildException(err, ex.hasInvalidMandatoryChild); - } - return false; - } - catch (std::exception &ex) - { - if (pChild->ModellingRule != ModelOpcUa::ModellingRule_t::Optional) - { - std::string err = GetOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); - LogOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); - throw MachineObserver::Exceptions::MachineInvalidChildException(err, true); - } - return false; - } - return true; - } - - void DashboardClient::TransformToNodeIdNodeNotFoundLog(const ModelOpcUa::NodeId_t &startNode, - const std::shared_ptr &pChild) const - { - LOG(INFO) << "Could not find '" - << static_cast(startNode) - << "'->'" - << static_cast(pChild->SpecifiedBrowseName) - << "'"; - } - - bool DashboardClient::OptionalAndMandatoryPlaceholderTransformToNodeId(const ModelOpcUa::NodeId_t &startNode, - std::list> &foundChildNodes, - const std::shared_ptr &pChild) - { - try - { - const ModelOpcUa::StructurePlaceholderNode structurePChild(pChild); - auto pPlaceholderChild = std::make_shared(structurePChild); - - if (!pPlaceholderChild) - { - LOG(ERROR) << "Child " << pChild->SpecifiedBrowseName.Uri << ";" << pChild->SpecifiedBrowseName.Name - << " of " << startNode.Uri << ";" << startNode.Id - << " caused a Placeholder error: instance not a placeholder." << std::endl; - return true; - } - auto placeholderNode = BrowsePlaceholder(startNode, pPlaceholderChild); - foundChildNodes.push_back(placeholderNode); - } - catch (std::exception &ex) - { - LOG(ERROR) << "Child " << pChild->SpecifiedBrowseName.Uri << ";" << pChild->SpecifiedBrowseName.Name - << " of " << startNode.Uri << ";" << startNode.Id - << " caused an exception: Unknown ID caused exception: " << ex.what(); - if (pChild->ModellingRule != ModelOpcUa::ModellingRule_t::OptionalPlaceholder) - { - LOG(ERROR) << "Forwarding exception, cause:" - << "Could not find '" - << static_cast(startNode) - << "'->'" - << static_cast(pChild->SpecifiedBrowseName) - << "'" - << "Unknown ID caused exception: " << ex.what(); - throw ex; - } - return false; - } - return true; - } - - std::shared_ptr DashboardClient::BrowsePlaceholder( - ModelOpcUa::NodeId_t startNode, - std::shared_ptr pStructurePlaceholder) - { - if (!pStructurePlaceholder) - { - LOG(ERROR) << "Invalid Argument, pStructurePlaceholder is nullptr"; - throw std::invalid_argument("pStructurePlaceholder is nullptr."); - } - - auto pPlaceholderNode = std::make_shared( - *pStructurePlaceholder, - std::list>{}); - auto browseResults = m_pDashboardDataClient->Browse(startNode, pStructurePlaceholder->ReferenceType, - pStructurePlaceholder->SpecifiedTypeNodeId); - - preparePlaceholderNodesTypeId(pStructurePlaceholder, pPlaceholderNode, browseResults); - - return pPlaceholderNode; - } - - void DashboardClient::preparePlaceholderNodesTypeId( - const std::shared_ptr & pStructurePlaceholder, - std::shared_ptr &pPlaceholderNode, - std::list &browseResults) - { - for (auto &browseResult : browseResults) - { - if (browseResult.TypeDefinition.Id == NodeId_BaseObjectType.Id) { - auto ifs = m_pDashboardDataClient->Browse(browseResult.NodeId, - Dashboard::IDashboardDataClient::BrowseContext_t::HasInterface()); - browseResult.TypeDefinition = ifs.front().NodeId; - LOG(INFO) << "Updated TypeDefinition of " << browseResult.BrowseName.Name << " to " << browseResult.TypeDefinition - << " because the node implements an interface"; - } - auto possibleType = m_pTypeReader->m_typeMap->find(browseResult.TypeDefinition); // use subtype - if (possibleType != m_pTypeReader->m_typeMap->end()) - { - // LOG(INFO) << "Found type for " << typeName; - auto sharedPossibleType = possibleType->second; - ModelOpcUa::PlaceholderElement plElement; - plElement.BrowseName = browseResult.BrowseName; - plElement.pNode = TransformToNodeIds(browseResult.NodeId, sharedPossibleType); - plElement.TypeDefinition = browseResult.TypeDefinition; - pPlaceholderNode->addInstance(plElement); - } - else - { - LOG(WARNING) << "Could not find a possible type for " - << static_cast(browseResult.TypeDefinition) - << ". Continuing without a candidate."; - //LOG(WARNING) << "Pointer shows to end()"; - } - } - } - - void DashboardClient::subscribeValues( - const std::shared_ptr pNode, - std::map, nlohmann::json> &valueMap, - std::mutex &valueMap_mutex) - { - // LOG(INFO) << "subscribeValues " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; - - // Only Mandatory/Optional variables - if (isMandatoryOrOptionalVariable(pNode)) - { - subscribeValue(pNode, valueMap, valueMap_mutex); - - } - handleSubscribeChildNodes(pNode, valueMap, valueMap_mutex); - } - - void DashboardClient::handleSubscribeChildNodes(const std::shared_ptr &pNode, - std::map, nlohmann::json> &valueMap, - std::mutex &valueMap_mutex) - { - // LOG(INFO) << "handleSubscribeChildNodes " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; - if (pNode->ChildNodes.size() == 0) - { - // LOG(INFO) << "No children found for " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; - } - for (auto &pChildNode : pNode->ChildNodes) - { - switch (pChildNode->ModellingRule) - { - case ModelOpcUa::Mandatory: - case ModelOpcUa::Optional: - { - handleSubscribeChildNode(pChildNode, valueMap, valueMap_mutex); - break; - } - case ModelOpcUa::MandatoryPlaceholder: - case ModelOpcUa::OptionalPlaceholder: - { - handleSubscribePlaceholderChildNode(pChildNode, valueMap, valueMap_mutex); - break; - } - default: - { - LOG(ERROR) << "Unknown Modelling Rule." << std::endl; - break; - } - } - } - } - - void DashboardClient::handleSubscribeChildNode(const std::shared_ptr &pChildNode, - std::map, nlohmann::json> &valueMap, - std::mutex &valueMap_mutex) - { - // LOG(INFO) << "handleSubscribeChildNode " << pChildNode->SpecifiedBrowseName.Uri << ";" << pChildNode->SpecifiedBrowseName.Name; - - auto pSimpleChild = std::dynamic_pointer_cast(pChildNode); - if (!pSimpleChild) - { - LOG(ERROR) << "Simple node error, instance not a simple node." << std::endl; - return; - } - // recursive call - subscribeValues(pSimpleChild, valueMap, valueMap_mutex); - } - - void - DashboardClient::handleSubscribePlaceholderChildNode(const std::shared_ptr &pChildNode, - std::map, nlohmann::json> &valueMap, - std::mutex &valueMap_mutex) - { - // LOG(INFO) << "handleSubscribePlaceholderChildNode " << pChildNode->SpecifiedBrowseName.Uri << ";" << pChildNode->SpecifiedBrowseName.Name; - auto pPlaceholderChild = std::dynamic_pointer_cast(pChildNode); - if (!pPlaceholderChild) - { - LOG(ERROR) << "Placeholder error, instance not a placeholder." << std::endl; - return; - } - - auto placeholderElements = pPlaceholderChild->getInstances(); - - for (const auto &pPlaceholderElement : placeholderElements) - { - // recursive call - subscribeValues(pPlaceholderElement.pNode, valueMap, valueMap_mutex); - } - } - - void DashboardClient::subscribeValue(const std::shared_ptr &pNode, - std::map, nlohmann::json> &valueMap, - std::mutex &valueMap_mutex - ) - { /** - * Creates a lambda function which gets pNode as a copy and valueMap as a reference from this function, - * the input parameters of the lambda function is the nlohmann::json value and the body updates the value - * at position pNode with the received json value. - */ - // LOG(INFO) << "SubscribeValue " << pNode->SpecifiedBrowseName.Uri << ";" << pNode->SpecifiedBrowseName.Name << " | " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; - - auto callback = [pNode, &valueMap, &valueMap_mutex](nlohmann::json value) { - std::unique_lock::type>(valueMap_mutex); - valueMap[pNode] = value; - }; - try - { - for(auto value : m_subscribedValues){ - if(value && value.get()->getNodeId() == pNode.get()->NodeId) - return; - } - auto subscribedValue = m_pDashboardDataClient->Subscribe(pNode->NodeId, callback); - m_subscribedValues.push_back(subscribedValue); - } - catch (std::exception &ex) - { - LOG(ERROR) << "Subscribe thrown an error: " << ex.what(); - } - } - - bool - DashboardClient::isMandatoryOrOptionalVariable(const std::shared_ptr &pNode) - { - return (pNode->NodeClass == ModelOpcUa::NodeClass_t::Variable || - pNode->NodeClass == ModelOpcUa::NodeClass_t::VariableType) && - (pNode->ModellingRule == ModelOpcUa::ModellingRule_t::Mandatory || pNode->ModellingRule == ModelOpcUa::ModellingRule_t::Optional); - } - } // namespace Dashboard -} // namespace Umati +/** + * Receives a nodeId, a typeDefinition and an mqtt topic to hold for a machine. Available types are + * Identification, JobCurrentStateNumber, ProductionJobList, Stacklight, StateModelList, ToolList + */ +void DashboardClient::addDataSet( + const ModelOpcUa::NodeId_t &startNodeId, + const std::shared_ptr &pTypeDefinition, + const std::string &channel, + const std::string &onlineChannel) { + try { + std::shared_ptr pDataSetStorage = prepareDataSetStorage(startNodeId, pTypeDefinition, channel, onlineChannel); + LOG(INFO) << "DataSetStorage prepared for " << channel; + subscribeValues(pDataSetStorage->node, pDataSetStorage->values, pDataSetStorage->values_mutex); + LOG(INFO) << "Values subscribed for " << channel; + std::lock_guard l(m_dataSetMutex); + m_dataSets.push_back(pDataSetStorage); + } catch (const Umati::Exceptions::OpcUaException &ex) { + LOG(WARNING) << ex.what(); + } catch (MachineObserver::Exceptions::MachineInvalidChildException &ex) { + LOG(ERROR) << ex.what(); + throw Exceptions::OpcUaException(ex.what()); + } catch (std::exception &ex) { + LOG(ERROR) << ex.what(); + throw Exceptions::OpcUaException(ex.what()); + } +} + +std::shared_ptr DashboardClient::prepareDataSetStorage( + const ModelOpcUa::NodeId_t &startNodeId, + const std::shared_ptr &pTypeDefinition, + const std::string &channel, + const std::string &onlineChannel) { + auto pDataSetStorage = std::make_shared(); + pDataSetStorage->startNodeId = startNodeId; + pDataSetStorage->channel = channel; + pDataSetStorage->onlineChannel = onlineChannel; + pDataSetStorage->node = TransformToNodeIds(startNodeId, pTypeDefinition); + return pDataSetStorage; +} + +void DashboardClient::Publish() { + std::lock_guard l(m_dataSetMutex); + for (auto &pDataSetStorage : m_dataSets) { + std::string jsonPayload = getJson(pDataSetStorage); + if (!jsonPayload.empty() && jsonPayload != "null") { + LastMessage_t &lastMessage = m_latestMessages[pDataSetStorage->channel]; + + time_t now; + time(&now); + + if (jsonPayload != lastMessage.payload || difftime(now, lastMessage.lastSent) > 10) { + m_pPublisher->Publish(pDataSetStorage->channel, jsonPayload); + lastMessage.payload = jsonPayload; + lastMessage.lastSent = now; + } + m_pPublisher->Publish(pDataSetStorage->onlineChannel, "1"); + } else { + LOG(INFO) << "pdatasetstorage for " << pDataSetStorage->startNodeId.Uri << ";" << pDataSetStorage->startNodeId.Id << " is empty"; + } + } +} + +void DashboardClient::Unsubscribe(ModelOpcUa::NodeId_t nodeId) { + std::vector monItemIds; + std::vector clientHandles; + for (auto values : m_subscribedValues) { + auto value = values.get(); + + if (value) { + monItemIds.push_back(value->getMonitoredItemId()); + clientHandles.push_back(value->getClientHandle()); + } else { + LOG(ERROR) << "Monitored Item is NULL for NodeId: " << nodeId.Id; + } + } + m_subscribedValues.clear(); + + m_pDashboardDataClient->Unsubscribe(monItemIds, clientHandles); + + std::lock_guard l(m_dataSetMutex); + m_dataSets.clear(); +} + +std::string DashboardClient::getJson(const std::shared_ptr &pDataSetStorage) { + auto getValueCallback = [pDataSetStorage](const std::shared_ptr &pNode) -> nlohmann::json { + std::unique_lockvalues_mutex)> ul(pDataSetStorage->values_mutex); + auto it = pDataSetStorage->values.find(pNode); + if (it == pDataSetStorage->values.end()) { + LOG(DEBUG) << "Couldn't write value for " << pNode->SpecifiedBrowseName.Name << " | " << pNode->SpecifiedTypeNodeId.Uri << ";" + << pNode->SpecifiedTypeNodeId.Id << "Try to search it with NodeId!"; + // In case we don't wnt to remove the duplicate pointers with FIX_1, we can simply check the + // Identity of the node via its node Id. + auto pSimpleNode = std::dynamic_pointer_cast(pNode); + if (pSimpleNode) { + LOG(DEBUG) << pSimpleNode->NodeId << "\n"; + auto values = pDataSetStorage->values; + for (auto it1 : values) { + auto pSimpleNode1 = std::dynamic_pointer_cast(it1.first); + if (pSimpleNode1->NodeId == pSimpleNode->NodeId) { + // DEBUG_BEGIN in case we want to see the different pointer addresses. + LOG(DEBUG) << pSimpleNode.get() << "\n"; + LOG(DEBUG) << pSimpleNode1.get() << "\n"; + LOG(DEBUG) << pNode->SpecifiedBrowseName.Name << " " + << "found!"; + return it1.second; + } + } + } + LOG(DEBUG) << pNode->SpecifiedBrowseName.Name << " " + << " not found!"; + return nullptr; + } + return it->second; + }; + + return Converter::ModelToJson(pDataSetStorage->node, getValueCallback).getJson().dump(2); +} + +std::shared_ptr DashboardClient::TransformToNodeIds( + ModelOpcUa::NodeId_t startNode, const std::shared_ptr &pTypeDefinition) { + auto ret = browsedNodes.insert(startNode); + + // FIX_BEGIN (FIX_1), removed duplicates to avoid different pointers to the same node + (*pTypeDefinition->SpecifiedChildNodes).sort(); + (*pTypeDefinition->SpecifiedChildNodes).unique(); + // FIX_END + std::list> foundChildNodes; + if (ret.second == true) { + for (auto &pChild : *pTypeDefinition->SpecifiedChildNodes) { + switch (pChild->ModellingRule) { + case ModelOpcUa::ModellingRule_t::Optional: + case ModelOpcUa::ModellingRule_t::Mandatory: { + bool should_continue = OptionalAndMandatoryTransformToNodeId(startNode, foundChildNodes, pChild); + if (should_continue) { + continue; + } + break; + } + case ModelOpcUa::ModellingRule_t::OptionalPlaceholder: + case ModelOpcUa::ModellingRule_t::MandatoryPlaceholder: { + bool should_continue = OptionalAndMandatoryPlaceholderTransformToNodeId(startNode, foundChildNodes, pChild); + if (should_continue) { + continue; + } + break; + } + case ModelOpcUa::ModellingRule_t::None: { + LOG(INFO) << "modelling rule is none"; + LOG(ERROR) << "Unknown Modelling Rule None." << std::endl; + break; + } + default: + LOG(ERROR) << "Unknown Modelling Rule." << std::endl; + break; + } + } + } + auto pNode = std::make_shared(startNode, pTypeDefinition->SpecifiedTypeNodeId, *pTypeDefinition, foundChildNodes); + + pNode->ofBaseDataVariableType = pTypeDefinition->ofBaseDataVariableType; + return pNode; +} + +void LogOptionalAndMandatoryTransformToNodeIdError(const ModelOpcUa::NodeId_t &nodeId, const ModelOpcUa::QualifiedName_t &childBrowsName, const char *err) { + LOG(ERROR) << "Forwarding exception, cause:" + << "Could not find '" << static_cast(nodeId) << "'->'" << static_cast(childBrowsName) << "'" + << "Unknown ID caused exception: " << err; +} + +std::string GetOptionalAndMandatoryTransformToNodeIdError( + const ModelOpcUa::NodeId_t &nodeId, const ModelOpcUa::QualifiedName_t &childBrowseName, const char *err) { + return "In '" + static_cast(nodeId) + "'->'" + static_cast(childBrowseName) + "':\n" + err; +} + +/** + * @return if the switch case should break + */ +bool DashboardClient::OptionalAndMandatoryTransformToNodeId( + const ModelOpcUa::NodeId_t &startNode, + std::list> &foundChildNodes, + const std::shared_ptr &pChild) { + try { + auto childNodeId = m_pDashboardDataClient->TranslateBrowsePathToNodeId(startNode, pChild->SpecifiedBrowseName); + if (childNodeId.isNull()) { + TransformToNodeIdNodeNotFoundLog(startNode, pChild); + return false; + } + auto nodeIds = TransformToNodeIds(childNodeId, pChild); + foundChildNodes.push_back(nodeIds); + } catch (MachineObserver::Exceptions::MachineInvalidChildException &ex) { + if (ex.hasInvalidMandatoryChild) { + std::string err = GetOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); + LogOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); + throw MachineObserver::Exceptions::MachineInvalidChildException(err, ex.hasInvalidMandatoryChild); + } + return false; + } catch (std::exception &ex) { + if (pChild->ModellingRule != ModelOpcUa::ModellingRule_t::Optional) { + std::string err = GetOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); + LogOptionalAndMandatoryTransformToNodeIdError(startNode, pChild->SpecifiedBrowseName, ex.what()); + throw MachineObserver::Exceptions::MachineInvalidChildException(err, true); + } + return false; + } + return true; +} + +void DashboardClient::TransformToNodeIdNodeNotFoundLog(const ModelOpcUa::NodeId_t &startNode, const std::shared_ptr &pChild) const { + LOG(INFO) << "Could not find '" << static_cast(startNode) << "'->'" << static_cast(pChild->SpecifiedBrowseName) << "'"; +} + +bool DashboardClient::OptionalAndMandatoryPlaceholderTransformToNodeId( + const ModelOpcUa::NodeId_t &startNode, + std::list> &foundChildNodes, + const std::shared_ptr &pChild) { + try { + const ModelOpcUa::StructurePlaceholderNode structurePChild(pChild); + auto pPlaceholderChild = std::make_shared(structurePChild); + + if (!pPlaceholderChild) { + LOG(ERROR) << "Child " << pChild->SpecifiedBrowseName.Uri << ";" << pChild->SpecifiedBrowseName.Name << " of " << startNode.Uri << ";" << startNode.Id + << " caused a Placeholder error: instance not a placeholder." << std::endl; + return true; + } + auto placeholderNode = BrowsePlaceholder(startNode, pPlaceholderChild); + foundChildNodes.push_back(placeholderNode); + } catch (std::exception &ex) { + LOG(ERROR) << "Child " << pChild->SpecifiedBrowseName.Uri << ";" << pChild->SpecifiedBrowseName.Name << " of " << startNode.Uri << ";" << startNode.Id + << " caused an exception: Unknown ID caused exception: " << ex.what(); + if (pChild->ModellingRule != ModelOpcUa::ModellingRule_t::OptionalPlaceholder) { + LOG(ERROR) << "Forwarding exception, cause:" + << "Could not find '" << static_cast(startNode) << "'->'" << static_cast(pChild->SpecifiedBrowseName) << "'" + << "Unknown ID caused exception: " << ex.what(); + throw ex; + } + return false; + } + return true; +} + +std::shared_ptr DashboardClient::BrowsePlaceholder( + ModelOpcUa::NodeId_t startNode, std::shared_ptr pStructurePlaceholder) { + if (!pStructurePlaceholder) { + LOG(ERROR) << "Invalid Argument, pStructurePlaceholder is nullptr"; + throw std::invalid_argument("pStructurePlaceholder is nullptr."); + } + + auto pPlaceholderNode = std::make_shared(*pStructurePlaceholder, std::list>{}); + auto browseResults = m_pDashboardDataClient->Browse(startNode, pStructurePlaceholder->ReferenceType, pStructurePlaceholder->SpecifiedTypeNodeId); + + preparePlaceholderNodesTypeId(pStructurePlaceholder, pPlaceholderNode, browseResults); + + return pPlaceholderNode; +} + +void DashboardClient::preparePlaceholderNodesTypeId( + const std::shared_ptr &pStructurePlaceholder, + std::shared_ptr &pPlaceholderNode, + std::list &browseResults) { + for (auto &browseResult : browseResults) { + if (browseResult.TypeDefinition.Id == NodeId_BaseObjectType.Id) { + LOG(INFO) << "TypeDefinition is BaseObjectType " << static_cast(browseResult.NodeId) << "- Try to find an Interface"; + auto ifs = m_pDashboardDataClient->Browse(browseResult.NodeId, Dashboard::IDashboardDataClient::BrowseContext_t::HasInterface()); + if (ifs.empty()) { + LOG(WARNING) << "No Interface found!"; + } + browseResult.TypeDefinition = ifs.front().NodeId; + } + auto possibleType = m_pTypeReader->m_typeMap->find(browseResult.TypeDefinition); // use subtype + if (possibleType != m_pTypeReader->m_typeMap->end()) { + // LOG(INFO) << "Found type for " << typeName; + auto sharedPossibleType = possibleType->second; + ModelOpcUa::PlaceholderElement plElement; + plElement.BrowseName = browseResult.BrowseName; + plElement.pNode = TransformToNodeIds(browseResult.NodeId, sharedPossibleType); + plElement.TypeDefinition = browseResult.TypeDefinition; + pPlaceholderNode->addInstance(plElement); + } else { + LOG(WARNING) << "Could not find a possible type for " << static_cast(browseResult.TypeDefinition) << ". Continuing without a candidate."; + // LOG(WARNING) << "Pointer shows to end()"; + } + } +} + +void DashboardClient::subscribeValues( + const std::shared_ptr pNode, + std::map, nlohmann::json> &valueMap, + std::mutex &valueMap_mutex) { + // LOG(INFO) << "subscribeValues " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; + + // Only Mandatory/Optional variables + if (isMandatoryOrOptionalVariable(pNode)) { + subscribeValue(pNode, valueMap, valueMap_mutex); + } + handleSubscribeChildNodes(pNode, valueMap, valueMap_mutex); +} + +void DashboardClient::handleSubscribeChildNodes( + const std::shared_ptr &pNode, + std::map, nlohmann::json> &valueMap, + std::mutex &valueMap_mutex) { + // LOG(INFO) << "handleSubscribeChildNodes " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; + if (pNode->ChildNodes.size() == 0) { + // LOG(INFO) << "No children found for " << pNode->NodeId.Uri << ";" << pNode->NodeId.Id; + } + for (auto &pChildNode : pNode->ChildNodes) { + switch (pChildNode->ModellingRule) { + case ModelOpcUa::Mandatory: + case ModelOpcUa::Optional: { + handleSubscribeChildNode(pChildNode, valueMap, valueMap_mutex); + break; + } + case ModelOpcUa::MandatoryPlaceholder: + case ModelOpcUa::OptionalPlaceholder: { + handleSubscribePlaceholderChildNode(pChildNode, valueMap, valueMap_mutex); + break; + } + default: { + LOG(ERROR) << "Unknown Modelling Rule." << std::endl; + break; + } + } + } +} + +void DashboardClient::handleSubscribeChildNode( + const std::shared_ptr &pChildNode, + std::map, nlohmann::json> &valueMap, + std::mutex &valueMap_mutex) { + // LOG(INFO) << "handleSubscribeChildNode " << pChildNode->SpecifiedBrowseName.Uri << ";" << pChildNode->SpecifiedBrowseName.Name; + + auto pSimpleChild = std::dynamic_pointer_cast(pChildNode); + if (!pSimpleChild) { + LOG(ERROR) << "Simple node error, instance not a simple node." << std::endl; + return; + } + // recursive call + subscribeValues(pSimpleChild, valueMap, valueMap_mutex); +} + +void DashboardClient::handleSubscribePlaceholderChildNode( + const std::shared_ptr &pChildNode, + std::map, nlohmann::json> &valueMap, + std::mutex &valueMap_mutex) { + // LOG(INFO) << "handleSubscribePlaceholderChildNode " << pChildNode->SpecifiedBrowseName.Uri << ";" << pChildNode->SpecifiedBrowseName.Name; + auto pPlaceholderChild = std::dynamic_pointer_cast(pChildNode); + if (!pPlaceholderChild) { + LOG(ERROR) << "Placeholder error, instance not a placeholder." << std::endl; + return; + } + + auto placeholderElements = pPlaceholderChild->getInstances(); + + for (const auto &pPlaceholderElement : placeholderElements) { + // recursive call + subscribeValues(pPlaceholderElement.pNode, valueMap, valueMap_mutex); + } +} + +void DashboardClient::subscribeValue( + const std::shared_ptr &pNode, + std::map, nlohmann::json> &valueMap, + std::mutex &valueMap_mutex) { /** + * Creates a lambda function which gets pNode as a copy and valueMap as a reference from this function, + * the input parameters of the lambda function is the nlohmann::json value and the body updates the value + * at position pNode with the received json value. + */ + // LOG(INFO) << "SubscribeValue " << pNode->SpecifiedBrowseName.Uri << ";" << pNode->SpecifiedBrowseName.Name << " | " << pNode->NodeId.Uri << ";" << + // pNode->NodeId.Id; + + auto callback = [pNode, &valueMap, &valueMap_mutex](nlohmann::json value) { + std::unique_lock::type>valueMap_mutex; + valueMap[pNode] = value; + }; + try { + for (auto value : m_subscribedValues) { + if (value && value.get()->getNodeId() == pNode.get()->NodeId) return; + } + auto subscribedValue = m_pDashboardDataClient->Subscribe(pNode->NodeId, callback); + m_subscribedValues.push_back(subscribedValue); + } catch (std::exception &ex) { + LOG(ERROR) << "Subscribe thrown an error: " << ex.what(); + } +} + +bool DashboardClient::isMandatoryOrOptionalVariable(const std::shared_ptr &pNode) { + return (pNode->NodeClass == ModelOpcUa::NodeClass_t::Variable || pNode->NodeClass == ModelOpcUa::NodeClass_t::VariableType) && + (pNode->ModellingRule == ModelOpcUa::ModellingRule_t::Mandatory || pNode->ModellingRule == ModelOpcUa::ModellingRule_t::Optional); +} +} // namespace Dashboard +} // namespace Umati diff --git a/OpcUaClient/Converter/UaDataValueToJsonValue.cpp b/OpcUaClient/Converter/UaDataValueToJsonValue.cpp index b5d63f38..c2e2702e 100644 --- a/OpcUaClient/Converter/UaDataValueToJsonValue.cpp +++ b/OpcUaClient/Converter/UaDataValueToJsonValue.cpp @@ -156,7 +156,23 @@ void UaDataValueToJsonValue::setValueFromScalarVariant(UA_Variant &variant, nloh } case UA_DATATYPEKIND_VARIANT: { - LOG(ERROR) << "Not implemented conversion to OpcUaType_Variant. "; + // LOG(ERROR) << "Not implemented conversion to OpcUaType_Variant. "; + UA_Variant d(*(UA_Variant *)variant.data); + if (strcmp(d.type->typeName, "StatisticResultContentDataType") == 0) { + void *data = d.data; + for (size_t i = 0; i < d.type->membersSize; i++) { + void *dataPointer = (UA_Byte *)data + d.type->members[0].padding; + UA_DataValue dataVal; + UA_DataValue_init(&dataVal); + UA_Variant_setScalar(&dataVal.value, dataPointer, d.type->members[i].memberType); + auto json = UaDataValueToJsonValue(dataVal, m_pClient, nodeId, serializeStatusInformation).getValue(); + if (!json.is_null()) { + (*jsonValue)[std::string(d.type->members[i].memberName)] = json; + } + data = (UA_Byte *)data + d.type->members[i].memberType->memSize; + data = (UA_Byte *)data + d.type->members[i].padding; + } + } break; } @@ -201,9 +217,11 @@ void UaDataValueToJsonValue::setValueFromScalarVariant(UA_Variant &variant, nloh UA_DataValue dataVal; UA_DataValue_init(&dataVal); if (exObj.content.decoded.type->members[i].isArray) { - size_t arraySize = *((size_t *)data); - void **pointerToArrayPointer = (void **)((UA_Byte *)data + sizeof(size_t)); - void *pointerToArray = *(pointerToArrayPointer); + if (exObj.content.decoded.type->members[i].isOptional) { + } else { + size_t arraySize = *((size_t *)data); + void **pointerToArrayPointer = (void **)((UA_Byte *)data + sizeof(size_t)); + void *pointerToArray = *(pointerToArrayPointer); if (arraySize > 0) { UA_Variant_setArray( @@ -214,14 +232,21 @@ void UaDataValueToJsonValue::setValueFromScalarVariant(UA_Variant &variant, nloh (*jsonValue)[std::string(exObj.content.decoded.type->members[i].memberName)] = UaDataValueToJsonValue(dataVal, m_pClient, nodeId, serializeStatusInformation).getValue(); } - } else { - void *dataPointer = (UA_Byte *)data + exObj.content.decoded.type->members[i].padding; + } + } else { + void *dataPointer = (UA_Byte *)data + exObj.content.decoded.type->members[i].padding; + if (exObj.content.decoded.type->members[i].isOptional) { + void **pointerToPointer = (void **)((UA_Byte *)data + exObj.content.decoded.type->members[i].padding); + dataPointer = *(pointerToPointer); + } + if (dataPointer != nullptr) { UA_Variant_setScalar(&dataVal.value, dataPointer, exObj.content.decoded.type->members[i].memberType); auto json = UaDataValueToJsonValue(dataVal, m_pClient, nodeId, serializeStatusInformation).getValue(); if (!json.is_null()) { (*jsonValue)[std::string(exObj.content.decoded.type->members[i].memberName)] = json; } } + } if (exObj.content.decoded.type->members[i].isArray) { data = (UA_Byte *)data + sizeof(void *) + sizeof(size_t); } else if (exObj.content.decoded.type->members[i].isOptional) { @@ -396,9 +421,15 @@ void UaDataValueToJsonValue::setValueFromArrayVariant(UA_Variant &variant, nlohm CASENOTIMPLEMENTED(EXPANDEDNODEID, ExpandedNodeId); CASENOTIMPLEMENTED(STATUSCODE, StatusCode); CASENOTIMPLEMENTED(DATAVALUE, DataValue); - CASENOTIMPLEMENTED(VARIANT, Variant); + // CASENOTIMPLEMENTED(VARIANT, Variant); CASENOTIMPLEMENTED(DIAGNOSTICINFO, DiagnosticInfo); + case UA_DATATYPEKIND_VARIANT: { + UA_Variant *v = (UA_Variant *)variant.data; + getValueFromDataValueArray(&variant, UA_UInt32(0), jsonValue, v, serializeStatusInformation); + break; + } + case UA_DATATYPEKIND_OPTSTRUCT:; case UA_DATATYPEKIND_STRUCTURE: { @@ -409,7 +440,9 @@ void UaDataValueToJsonValue::setValueFromArrayVariant(UA_Variant &variant, nlohm VALUEFROMDATAARRAY(RANGE, Range); break; } else { - LOG(ERROR) << "Unknown data type. "; + LOG(ERROR) << "Unknown data type: " << variant.type->typeName; + UA_Variant *v = (UA_Variant *)variant.data; + getValueFromDataValueArray(&variant, UA_UInt32(0), jsonValue, v, serializeStatusInformation); break; } }