diff --git a/CMakeLists.txt b/CMakeLists.txt
index edc49b19..4f48190f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ set(AGENT_VERSION_MAJOR 2)
set(AGENT_VERSION_MINOR 2)
set(AGENT_VERSION_PATCH 0)
set(AGENT_VERSION_BUILD 1)
-set(AGENT_VERSION_RC "_RC3")
+set(AGENT_VERSION_RC "_RC4")
# This minimum version is to support Visual Studio 2017 and C++ feature checking and FetchContent
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
diff --git a/samples/dyn_load.xml b/samples/dyn_load.xml
new file mode 100644
index 00000000..d8cd7a5f
--- /dev/null
+++ b/samples/dyn_load.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/empty.xml b/samples/empty.xml
new file mode 100644
index 00000000..5e336730
--- /dev/null
+++ b/samples/empty.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp
index 931afd9d..d962648f 100644
--- a/src/mtconnect/agent.cpp
+++ b/src/mtconnect/agent.cpp
@@ -73,7 +73,7 @@ namespace mtconnect {
static const string g_available("AVAILABLE");
// Agent public methods
- Agent::Agent(boost::asio::io_context &context, const string &deviceXmlPath,
+ Agent::Agent(config::AsyncContext &context, const string &deviceXmlPath,
const ConfigOptions &options)
: m_options(options),
m_context(context),
@@ -83,7 +83,7 @@ namespace mtconnect {
m_deviceXmlPath(deviceXmlPath),
m_circularBuffer(GetOption(options, config::BufferSize).value_or(17),
GetOption(options, config::CheckpointFrequency).value_or(1000)),
- m_pretty(GetOption(options, mtconnect::configuration::Pretty).value_or(false))
+ m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty))
{
using namespace asset;
@@ -97,8 +97,8 @@ namespace mtconnect {
m_assetStorage = make_unique(
GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024));
- m_versionDeviceXml =
- GetOption(options, mtconnect::configuration::VersionDeviceXmlUpdates).value_or(false);
+ m_versionDeviceXml = IsOptionSet(options, mtconnect::configuration::VersionDeviceXmlUpdates);
+ m_createUniqueIds = IsOptionSet(options, config::CreateUniqueIds);
auto jsonVersion =
uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2));
@@ -145,6 +145,9 @@ namespace mtconnect {
for (auto device : devices)
addDevice(device);
+ if (m_versionDeviceXml && m_createUniqueIds)
+ versionDeviceXml();
+
loadCachedProbe();
m_initialized = true;
@@ -360,7 +363,6 @@ namespace mtconnect {
return false;
}
- // Fir the DeviceAdded event for each device
bool changed = false;
for (auto device : devices)
{
@@ -387,6 +389,46 @@ namespace mtconnect {
}
}
+ void Agent::loadDevice(const string &deviceXml, const optional source)
+ {
+ m_context.pause([=](config::AsyncContext &context) {
+ try
+ {
+ auto printer = dynamic_cast(m_printers["xml"].get());
+ auto device = m_xmlParser->parseDevice(deviceXml, printer);
+
+ if (device)
+ {
+ bool changed = receiveDevice(device, true);
+ if (changed)
+ loadCachedProbe();
+
+ if (source)
+ {
+ auto s = findSource(*source);
+ if (s)
+ {
+ const auto &name = device->getComponentName();
+ s->setOptions({{config::Device, *name}});
+ }
+ }
+ }
+ }
+ catch (runtime_error &e)
+ {
+ LOG(error) << "Error loading device: " + deviceXml;
+ LOG(error) << "Error detail: " << e.what();
+ cerr << e.what() << endl;
+ }
+ catch (exception &f)
+ {
+ LOG(error) << "Error loading device: " + deviceXml;
+ LOG(error) << "Error detail: " << f.what();
+ cerr << f.what() << endl;
+ }
+ });
+ }
+
bool Agent::receiveDevice(device_model::DevicePtr device, bool version)
{
NAMED_SCOPE("Agent::receiveDevice");
@@ -442,7 +484,16 @@ namespace mtconnect {
if (auto odi = oldDev->getAssetCount(), ndi = device->getAssetCount(); odi && !ndi)
device->addDataItem(odi, errors);
+ if (errors.size() > 0)
+ {
+ LOG(error) << "Error adding device required data items for " << *device->getUuid() << ':';
+ for (const auto &e : errors)
+ LOG(error) << " " << e->what();
+ return false;
+ }
+
verifyDevice(device);
+ createUniqueIds(device);
LOG(info) << "Checking if device " << *uuid << " has changed";
if (*device != *oldDev)
@@ -508,26 +559,37 @@ namespace mtconnect {
void Agent::versionDeviceXml()
{
+ NAMED_SCOPE("Agent::versionDeviceXml");
+
+ using namespace std::chrono;
+
if (m_versionDeviceXml)
{
// update with a new version of the device.xml, saving the old one
// with a date time stamp
- auto ext = "."s + getCurrentTime(LOCAL);
+ auto ext =
+ date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now()));
fs::path file(m_deviceXmlPath);
fs::path backup(m_deviceXmlPath + ext);
if (!fs::exists(backup))
fs::rename(file, backup);
- printer::XmlPrinter printer(true);
-
- std::list list;
- copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list),
- [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; });
- auto probe = printer.printProbe(0, 0, 0, 0, 0, list);
-
- ofstream devices(file.string());
- devices << probe;
- devices.close();
+ auto printer = getPrinter("xml");
+ if (printer != nullptr)
+ {
+ std::list list;
+ copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list),
+ [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; });
+ auto probe = printer->printProbe(0, 0, 0, 0, 0, list, nullptr, true, true);
+
+ ofstream devices(file.string());
+ devices << probe;
+ devices.close();
+ }
+ else
+ {
+ LOG(error) << "Cannot find xml printer";
+ }
}
}
@@ -661,7 +723,8 @@ namespace mtconnect {
auto devices = m_xmlParser->parseFile(
configXmlPath, dynamic_cast(m_printers["xml"].get()));
- if (!m_schemaVersion && m_xmlParser->getSchemaVersion())
+ if (!m_schemaVersion && m_xmlParser->getSchemaVersion() &&
+ !m_xmlParser->getSchemaVersion()->empty())
{
m_schemaVersion = m_xmlParser->getSchemaVersion();
m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion);
@@ -809,38 +872,32 @@ namespace mtconnect {
}
else
{
- // Check if we are already initialized. If so, the device will need to be
- // verified, additional data items added, and initial values set.
- // if (!m_initialized)
- {
- m_deviceIndex.push_back(device);
+ m_deviceIndex.push_back(device);
- // TODO: Redo Resolve Reference with entity
- // device->resolveReferences();
- verifyDevice(device);
+ // TODO: Redo Resolve Reference with entity
+ // device->resolveReferences();
+ verifyDevice(device);
+ createUniqueIds(device);
- if (m_observationsInitialized)
- {
- initializeDataItems(device);
+ if (m_observationsInitialized)
+ {
+ initializeDataItems(device);
- // Check for single valued constrained data items.
- if (m_agentDevice && device != m_agentDevice)
+ // Check for single valued constrained data items.
+ if (m_agentDevice && device != m_agentDevice)
+ {
+ entity::Properties props {{"VALUE", uuid}};
+ if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2))
{
- entity::Properties props {{"VALUE", uuid}};
- if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2))
- {
- const auto &hash = device->getProperty("hash");
- if (hash.index() != EMPTY)
- props.insert_or_assign("hash", hash);
- }
-
- auto d = m_agentDevice->getDeviceDataItem("device_added");
- m_loopback->receive(d, props);
+ const auto &hash = device->getProperty("hash");
+ if (hash.index() != EMPTY)
+ props.insert_or_assign("hash", hash);
}
+
+ auto d = m_agentDevice->getDeviceDataItem("device_added");
+ m_loopback->receive(d, props);
}
}
- // else
- // LOG(warning) << "Adding device " << uuid << " after initialialization not supported yet";
}
if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2))
@@ -875,6 +932,7 @@ namespace mtconnect {
if (changed)
{
+ createUniqueIds(device);
if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2))
device->addHash();
@@ -910,6 +968,28 @@ namespace mtconnect {
}
}
+ void Agent::createUniqueIds(DevicePtr device)
+ {
+ if (m_createUniqueIds && !dynamic_pointer_cast(device))
+ {
+ std::unordered_map idMap;
+
+ device->createUniqueIds(idMap);
+ device->updateReferences(idMap);
+
+ // Update the data item map.
+ for (auto &id : idMap)
+ {
+ auto di = device->getDeviceDataItem(id.second);
+ if (auto it = m_dataItemMap.find(id.first); it != m_dataItemMap.end())
+ {
+ m_dataItemMap.erase(it);
+ m_dataItemMap.emplace(id.second, di);
+ }
+ }
+ }
+ }
+
void Agent::loadCachedProbe()
{
NAMED_SCOPE("Agent::loadCachedProbe");
@@ -1026,30 +1106,22 @@ namespace mtconnect {
void AgentPipelineContract::deliverCommand(entity::EntityPtr entity)
{
- static auto pattern = regex("\\*[ ]*([^:]+):[ ]*(.+)");
+ auto command = entity->get("command");
auto value = entity->getValue();
- smatch match;
- if (std::regex_match(value, match, pattern))
- {
- auto device = entity->maybeGet("device");
- auto command = boost::algorithm::to_lower_copy(match[1].str());
- auto param = match[2].str();
- auto source = entity->maybeGet("source");
+ auto device = entity->maybeGet("device");
+ auto source = entity->maybeGet("source");
- if (!device || !source)
- {
- LOG(error) << "Invalid command: " << command << ", device or source not specified";
- }
- else
- {
- LOG(debug) << "Processing command: " << command << ": " << value;
- m_agent->receiveCommand(*device, command, param, *source);
- }
+ if ((command != "devicemodel" && !device) || !source)
+ {
+ LOG(error) << "Invalid command: " << command << ", device or source not specified";
}
else
{
- LOG(warning) << "Cannot parse command: " << value;
+ LOG(debug) << "Processing command: " << command << ": " << value;
+ if (!device)
+ device.emplace("");
+ m_agent->receiveCommand(*device, command, value, *source);
}
}
@@ -1306,8 +1378,7 @@ namespace mtconnect {
{"description", mem_fn(&Device::setDescriptionValue)},
{"nativename",
[](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }},
- {"calibration",
- [](DevicePtr device, const string &value) {
+ {"calibration", [](DevicePtr device, const string &value) {
istringstream line(value);
// Look for name|factor|offset triples
@@ -1328,8 +1399,7 @@ namespace mtconnect {
di->setConverter(conv);
}
}
- }},
- };
+ }}};
static std::unordered_map adapterDataItems {
{"adapterversion", "_adapter_software_version"},
@@ -1349,6 +1419,10 @@ namespace mtconnect {
deviceChanged(device, oldUuid, oldName);
}
}
+ else if (command == "devicemodel")
+ {
+ loadDevice(value, source);
+ }
else
{
auto action = deviceCommands.find(command);
diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp
index 8483855c..4b4cbc6d 100644
--- a/src/mtconnect/agent.hpp
+++ b/src/mtconnect/agent.hpp
@@ -40,6 +40,7 @@
#include "mtconnect/buffer/checkpoint.hpp"
#include "mtconnect/buffer/circular_buffer.hpp"
#include "mtconnect/config.hpp"
+#include "mtconnect/configuration/async_context.hpp"
#include "mtconnect/configuration/hook_manager.hpp"
#include "mtconnect/configuration/service.hpp"
#include "mtconnect/device_model/agent_device.hpp"
@@ -90,7 +91,7 @@ namespace mtconnect {
/// - VersionDeviceXmlUpdates
/// - JsonVersion
/// - DisableAgentDevice
- Agent(boost::asio::io_context &context, const std::string &deviceXmlPath,
+ Agent(configuration::AsyncContext &context, const std::string &deviceXmlPath,
const ConfigOptions &options);
/// Destructor for the Agent.
@@ -167,14 +168,15 @@ namespace mtconnect {
// Source and Sink
/// @brief Find a source by name
- /// @param[in] name the name to find
+ /// @param[in] name the identity to find
/// @return A shared pointer to the source if found, otherwise nullptr
source::SourcePtr findSource(const std::string &name) const
{
for (auto &s : m_sources)
- if (s->getIdentity() == name)
+ {
+ if (s->getIdentity() == name || s->getName() == name)
return s;
-
+ }
return nullptr;
}
/// @brief Find a sink by name
@@ -257,10 +259,16 @@ namespace mtconnect {
/// @param[in] oldName The old name
void deviceChanged(DevicePtr device, const std::string &oldUuid, const std::string &oldName);
/// @brief Reload the devices from a device file after updates
- /// @param deviceFile The device file to load
+ /// @param[in] deviceFile The device file to load
/// @return true if successful
bool reloadDevices(const std::string &deviceFile);
+ /// @brief receive and parse a single device from a source
+ /// @param[in] deviceXml the device xml as a string
+ /// @param[in] source the source loading the device
+ void loadDevice(const std::string &deviceXml,
+ const std::optional source = std::nullopt);
+
/// @name Message when source has connected and disconnected
///@{
@@ -403,6 +411,14 @@ namespace mtconnect {
std::string devicesAndPath(const std::optional &path,
const DevicePtr device) const;
+ /// @brief Creates unique ids for the device model and maps to the originals
+ ///
+ /// Also updates the agents data item map by adding the new ids. Duplicate original
+ /// ids will be last in wins.
+ ///
+ /// @param[in] device device to modify
+ void createUniqueIds(DevicePtr device);
+
protected:
friend class AgentPipelineContract;
@@ -427,7 +443,7 @@ namespace mtconnect {
protected:
ConfigOptions m_options;
- boost::asio::io_context &m_context;
+ configuration::AsyncContext &m_context;
boost::asio::io_context::strand m_strand;
std::shared_ptr m_loopback;
@@ -493,7 +509,8 @@ namespace mtconnect {
// Xml Config
std::optional m_schemaVersion;
std::string m_deviceXmlPath;
- bool m_versionDeviceXml = false;
+ bool m_versionDeviceXml {false};
+ bool m_createUniqueIds {false};
int32_t m_intSchemaVersion = IntDefaultSchemaVersion();
// Circular Buffer
diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp
index 3b546fc1..c0f8e1d6 100644
--- a/src/mtconnect/configuration/agent_config.cpp
+++ b/src/mtconnect/configuration/agent_config.cpp
@@ -102,7 +102,7 @@ namespace mtconnect::configuration {
boost::log::trivial::logger_type *gAgentLogger = nullptr;
AgentConfiguration::AgentConfiguration()
- : m_context {make_unique()}, m_monitorTimer(m_context->getContext())
+ : m_context {make_unique()}, m_monitorTimer(m_context->get())
{
NAMED_SCOPE("AgentConfiguration::AgentConfiguration");
using namespace source;
@@ -645,6 +645,7 @@ namespace mtconnect::configuration {
{configuration::MaxAssets, int(DEFAULT_MAX_ASSETS)},
{configuration::CheckpointFrequency, 1000},
{configuration::LegacyTimeout, 600s},
+ {configuration::CreateUniqueIds, false},
{configuration::ReconnectInterval, 10000ms},
{configuration::IgnoreTimestamps, false},
{configuration::ConversionRequired, true},
@@ -896,10 +897,14 @@ namespace mtconnect::configuration {
adapterOptions, ptree {});
m_agent->addSource(source, false);
}
- else
+ else if (m_agent->getDevices().size() > 1)
{
throw runtime_error("Adapters must be defined if more than one device is present");
}
+ else
+ {
+ LOG(warning) << "Starting with no devices or adapters";
+ }
}
#ifdef WITH_PYTHON
diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp
index 7ec330fa..32fcffba 100644
--- a/src/mtconnect/configuration/agent_config.hpp
+++ b/src/mtconnect/configuration/agent_config.hpp
@@ -112,9 +112,9 @@ namespace mtconnect {
/// @param[in] agent the agent the configuration will take ownership of
void setAgent(std::unique_ptr &agent) { m_agent = std::move(agent); }
/// @brief get the agent associated with the configuration
- const Agent *getAgent() const { return m_agent.get(); }
+ Agent *getAgent() const { return m_agent.get(); }
/// @brief get the boost asio io context
- auto &getContext() { return m_context->getContext(); }
+ auto &getContext() { return m_context->get(); }
/// @brief get a pointer to the async io manager
auto &getAsyncContext() { return *m_context.get(); }
diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp
index 31f35c3f..0446685e 100644
--- a/src/mtconnect/configuration/async_context.hpp
+++ b/src/mtconnect/configuration/async_context.hpp
@@ -40,8 +40,12 @@ namespace mtconnect::configuration {
AsyncContext(const AsyncContext &) = delete;
~AsyncContext() {}
+ /// @brief Testing only: method to remove the run guard from the context
+ void removeGuard() { m_guard.reset(); }
+
/// @brief get the boost asio context reference
- auto &getContext() { return m_context; }
+ auto &get() { return m_context; }
+
/// @brief operator() returns a reference to the io context
/// @return the io context
operator boost::asio::io_context &() { return m_context; }
@@ -127,6 +131,44 @@ namespace mtconnect::configuration {
m_context.restart();
}
+ /// @name Cover methods for asio io_context
+ /// @{
+
+ /// @brief io_context::run_for
+ template
+ auto run_for(const std::chrono::duration &rel_time)
+ {
+ return m_context.run_for(rel_time);
+ }
+
+ /// @brief io_context::run
+ auto run() { return m_context.run(); }
+
+ /// @brief io_context::run_one
+ auto run_one() { return m_context.run_one(); }
+
+ /// @brief io_context::run_one_for
+ template
+ auto run_one_for(const std::chrono::duration &rel_time)
+ {
+ return m_context.run_one_for(rel_time);
+ }
+
+ /// @brief io_context::run_one_until
+ template
+ auto run_one_until(const std::chrono::time_point &abs_time)
+ {
+ return m_context.run_one_for(abs_time);
+ }
+
+ /// @brief io_context::poll
+ auto poll() { return m_context.poll(); }
+
+ /// @brief io_context::poll
+ auto get_executor() BOOST_ASIO_NOEXCEPT { return m_context.get_executor(); }
+
+ /// @}
+
private:
void operator=(const AsyncContext &) {}
diff --git a/src/mtconnect/configuration/config_options.hpp b/src/mtconnect/configuration/config_options.hpp
index 7116d858..a012d399 100644
--- a/src/mtconnect/configuration/config_options.hpp
+++ b/src/mtconnect/configuration/config_options.hpp
@@ -68,6 +68,7 @@ namespace mtconnect {
DECLARE_CONFIGURATION(TlsOnly);
DECLARE_CONFIGURATION(TlsPrivateKey);
DECLARE_CONFIGURATION(TlsVerifyClientCertificate);
+ DECLARE_CONFIGURATION(CreateUniqueIds);
DECLARE_CONFIGURATION(VersionDeviceXmlUpdates);
DECLARE_CONFIGURATION(WorkerThreads);
///@}
diff --git a/src/mtconnect/device_model/component.hpp b/src/mtconnect/device_model/component.hpp
index fb91fce6..a554e4c3 100644
--- a/src/mtconnect/device_model/component.hpp
+++ b/src/mtconnect/device_model/component.hpp
@@ -43,6 +43,7 @@ namespace mtconnect {
using DevicePtr = std::shared_ptr;
using DataItemPtr = std::shared_ptr;
+ using WeakDataItemPtr = std::weak_ptr;
/// @brief MTConnect Component Entity
class AGENT_LIB_API Component : public entity::Entity
@@ -245,6 +246,27 @@ namespace mtconnect {
return nullptr;
}
+ /// @brief Get the component topic path as a list
+ ///
+ /// Recurses to root and then appends getTopicName
+ /// @param[in,out] pth the path list to append to
+ void path(std::list &pth)
+ {
+ auto p = getParent();
+ if (p)
+ p->path(pth);
+
+ pth.push_back(getTopicName());
+ }
+
+ std::optional createUniqueId(std::unordered_map &idMap,
+ const boost::uuids::detail::sha1 &sha1) override
+ {
+ auto newId = Entity::createUniqueId(idMap, sha1);
+ m_id = *newId;
+ return newId;
+ }
+
protected:
void setParent(ComponentPtr parent) { m_parent = parent; }
void setDevice(DevicePtr device) { m_device = device; }
diff --git a/src/mtconnect/device_model/data_item/data_item.cpp b/src/mtconnect/device_model/data_item/data_item.cpp
index 89842b47..85ac2381 100644
--- a/src/mtconnect/device_model/data_item/data_item.cpp
+++ b/src/mtconnect/device_model/data_item/data_item.cpp
@@ -243,7 +243,8 @@ namespace mtconnect {
bool DataItem::hasName(const string &name) const
{
- return m_id == name || (m_name && *m_name == name) || (m_source && *m_source == name);
+ return m_id == name || (m_name && *m_name == name) || (m_source && *m_source == name) ||
+ (m_originalId && *m_originalId == name);
}
// Sort by: Device, Component, Category, DataItem
@@ -297,21 +298,12 @@ namespace mtconnect {
}
}
- inline void path(std::list &pth, ComponentPtr c)
- {
- auto p = c->getParent();
- if (p)
- path(pth, p);
-
- pth.push_back(c->getTopicName());
- }
-
void DataItem::makeTopic()
{
std::list pth;
auto comp = m_component.lock();
- path(pth, comp);
+ comp->path(pth);
{
auto cp = m_composition.lock();
if (cp)
diff --git a/src/mtconnect/device_model/data_item/data_item.hpp b/src/mtconnect/device_model/data_item/data_item.hpp
index 3138a8a9..35662d97 100644
--- a/src/mtconnect/device_model/data_item/data_item.hpp
+++ b/src/mtconnect/device_model/data_item/data_item.hpp
@@ -40,6 +40,7 @@ namespace mtconnect {
}
namespace device_model {
class Composition;
+ struct UpdateDataItemId;
/// @brief DataItem related entities
namespace data_item {
@@ -221,15 +222,44 @@ namespace mtconnect {
bool operator<(const DataItem &another) const;
bool operator==(const DataItem &another) const { return m_id == another.m_id; }
+ /// @brief Return the category as a char *
const char *getCategoryText() const { return m_categoryText; }
+ std::optional createUniqueId(
+ std::unordered_map &idMap,
+ const boost::uuids::detail::sha1 &sha1) override
+ {
+ m_originalId.emplace(m_id);
+ auto pref = m_id == m_preferredName;
+ m_id = *Entity::createUniqueId(idMap, sha1);
+ if (pref)
+ m_preferredName = m_id;
+ m_observatonProperties.insert_or_assign("dataItemId", m_id);
+ return m_id;
+ }
+
+ /// @brief Get a reference to the optional original id
+ /// @returns optional original id
+ const auto &getOriginalId() const { return m_originalId; }
+
+ void updateReferences(const std::unordered_map idMap) override
+ {
+ Entity::updateReferences(idMap);
+ if (hasProperty("compositionId"))
+ m_observatonProperties.insert_or_assign("compositionId",
+ get("compositionId"));
+ }
+
protected:
double simpleFactor(const std::string &units);
std::map buildAttributes() const;
+ friend struct device_model::UpdateDataItemId;
+
protected:
// Unique ID for each component
std::string m_id;
+ std::optional m_originalId;
// Name for itself
std::optional m_name;
diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp
index d2fbf3b7..1de61472 100644
--- a/src/mtconnect/device_model/device.cpp
+++ b/src/mtconnect/device_model/device.cpp
@@ -153,6 +153,10 @@ namespace mtconnect {
if (auto it = m_dataItems.get().find(name); it != m_dataItems.get().end())
return it->lock();
+ if (auto it = m_dataItems.get().find(name);
+ it != m_dataItems.get().end())
+ return it->lock();
+
if (auto it = m_dataItems.get().find(name); it != m_dataItems.get().end())
return it->lock();
@@ -161,5 +165,65 @@ namespace mtconnect {
return nullptr;
}
+
+ struct UpdateDataItemId
+ {
+ const string &m_id;
+ UpdateDataItemId(const string &id) : m_id(id) {}
+
+ void operator()(WeakDataItemPtr &ptr)
+ {
+ auto di = ptr.lock();
+ di->m_id = m_id;
+ }
+ };
+
+ void Device::createUniqueIds(std::unordered_map &idMap)
+ {
+ boost::uuids::detail::sha1 sha;
+ sha.process_bytes(m_uuid->data(), m_uuid->size());
+
+ Component::createUniqueId(idMap, sha);
+
+ for (auto it = m_dataItems.begin(); it != m_dataItems.end(); it++)
+ {
+ auto di = it->lock();
+ const auto &oldId = di->getOriginalId();
+ if (oldId)
+ {
+ auto id = idMap.find(*oldId);
+ if (id != idMap.end())
+ {
+ m_dataItems.modify(it, UpdateDataItemId(id->second));
+ }
+ else
+ {
+ LOG(error) << "Cannot find id " << *oldId << " in data item map";
+ }
+ }
+ else
+ {
+ LOG(error) << "DataItem id for " << di->getId() << " was not made unique";
+ }
+ }
+
+ for (auto p : m_componentsById)
+ {
+ auto comp = p.second.lock();
+ if (comp)
+ {
+ auto orig = comp->maybeGet("originalId");
+ if (orig && m_componentsById.count(*orig) == 0)
+ {
+ m_componentsById.emplace(*orig, comp);
+ }
+ if (m_componentsById.count(comp->getId()) == 0)
+ {
+ m_componentsById.emplace(comp->getId(), comp);
+ }
+ }
+ }
+ }
+
} // namespace device_model
} // namespace mtconnect
diff --git a/src/mtconnect/device_model/device.hpp b/src/mtconnect/device_model/device.hpp
index 97e62af9..1e61ec31 100644
--- a/src/mtconnect/device_model/device.hpp
+++ b/src/mtconnect/device_model/device.hpp
@@ -53,6 +53,9 @@ namespace mtconnect {
/// @brief multi-index tag: Data items indexed by id
struct ById
{};
+ /// @brief multi-index tag: Data items indexed by optional original id
+ struct ByOriginalId
+ {};
/// @brief multi-index tag: Data items index by Source
struct BySource
{};
@@ -66,6 +69,29 @@ namespace mtconnect {
using result_type = std::string;
const result_type &operator()(const WeakDataItemPtr d) const { return d.lock()->getId(); }
};
+ /// @brief multi-index data item id extractor
+ ///
+ /// falls back to id if orginal id is not given
+
+ struct ExtractOriginalId
+ {
+ using result_type = std::string;
+ const result_type operator()(const WeakDataItemPtr &d) const
+ {
+ const static result_type none {};
+ if (d.expired())
+ return none;
+ else
+ {
+ auto di = d.lock();
+ auto id = di->getOriginalId();
+ if (id)
+ return *id;
+ else
+ return di->getId();
+ }
+ }
+ };
/// @brief multi-index data item name extractor
///
/// falls back to id if name is not given
@@ -130,6 +156,7 @@ namespace mtconnect {
using DataItemIndex = mic::multi_index_container<
WeakDataItemPtr,
mic::indexed_by, ExtractId>,
+ mic::hashed_unique, ExtractOriginalId>,
mic::hashed_non_unique, ExtractSource>,
mic::hashed_non_unique, ExtractName>,
mic::ordered_non_unique, ExtractType>>>;
@@ -165,7 +192,13 @@ namespace mtconnect {
/// @brief Add a data item to the device
/// @param[in] dataItem shared pointer to the data item
void addDeviceDataItem(DataItemPtr dataItem);
- /// @brief get a data item by source, name, and id
+ /// @brief get a data item by multiple indexes
+ ///
+ /// Looks for a data item using the following idexes in the following order:
+ /// 1. id
+ /// 2. original id (set when creating unique ids)
+ /// 3. name
+ /// 4. source
/// @param[in] name the source, name, or id of the data item
/// @return shared pointer to the data item if found
DataItemPtr getDeviceDataItem(const std::string &name) const;
@@ -236,6 +269,12 @@ namespace mtconnect {
/// @return the uuid of the device
const std::string getTopicName() const override { return *m_uuid; }
+ /// @brief Converts all the ids to unique ids by hasing the topics
+ ///
+ /// Converts the id attribute to a unique value and caches the original value
+ /// in case it is required later
+ void createUniqueIds(std::unordered_map &idMap);
+
protected:
void cachePointers(DataItemPtr dataItem);
diff --git a/src/mtconnect/entity/entity.cpp b/src/mtconnect/entity/entity.cpp
index 557e7ac0..777eb306 100644
--- a/src/mtconnect/entity/entity.cpp
+++ b/src/mtconnect/entity/entity.cpp
@@ -15,6 +15,8 @@
// limitations under the License.
//
+#include
+
#include
#include "factory.hpp"
@@ -131,7 +133,7 @@ namespace mtconnect::entity {
for (const auto &e : m_properties)
{
// Skip hash
- if (!skip.contains(e.first))
+ if (!skip.contains(e.first) && !isHidden(e.first))
{
const auto &value = e.second;
sha1.process_bytes(e.first.c_str(), e.first.size());
@@ -141,4 +143,105 @@ namespace mtconnect::entity {
}
}
+ struct UniqueIdVisitor
+ {
+ std::unordered_map &m_idMap;
+ const boost::uuids::detail::sha1 &m_sha1;
+ UniqueIdVisitor(std::unordered_map &idMap,
+ const boost::uuids::detail::sha1 &sha1)
+ : m_idMap(idMap), m_sha1(sha1)
+ {}
+
+ void operator()(EntityPtr &p) { p->createUniqueId(m_idMap, m_sha1); }
+
+ void operator()(EntityList &l)
+ {
+ for (auto &e : l)
+ e->createUniqueId(m_idMap, m_sha1);
+ }
+
+ template
+ void operator()(const T &)
+ {}
+ };
+
+ std::optional Entity::createUniqueId(
+ std::unordered_map &idMap, const boost::uuids::detail::sha1 &sha1)
+ {
+ optional res;
+
+ auto it = m_properties.find("id");
+ if (it != m_properties.end())
+ {
+ std::string newId, oldId;
+ auto origId = maybeGet("originalId");
+ if (!origId)
+ {
+ oldId = std::get(it->second);
+ m_properties.emplace("originalId", oldId);
+ newId = makeUniqueId(sha1, oldId);
+ it->second = newId;
+ }
+ else
+ {
+ oldId = *origId;
+ newId = std::get(it->second);
+ }
+ idMap.emplace(oldId, newId);
+ res.emplace(newId);
+ }
+
+ UniqueIdVisitor visitor(idMap, sha1);
+
+ // Recurse properties
+ for (auto &p : m_properties)
+ {
+ std::visit(visitor, p.second);
+ }
+
+ return res;
+ }
+
+ struct ReferenceIdVisitor
+ {
+ const std::unordered_map &m_idMap;
+ ReferenceIdVisitor(const std::unordered_map &idMap) : m_idMap(idMap) {}
+
+ void operator()(EntityPtr &p) { p->updateReferences(m_idMap); }
+
+ void operator()(EntityList &l)
+ {
+ for (auto &e : l)
+ e->updateReferences(m_idMap);
+ }
+
+ template
+ void operator()(T &)
+ {}
+ };
+
+ void Entity::updateReferences(std::unordered_map idMap)
+ {
+ using namespace boost::algorithm;
+ for (auto &prop : m_properties)
+ {
+ if (prop.first != "originalId" && (iends_with(prop.first, "idref") ||
+ (prop.first.length() > 2 && iends_with(prop.first, "id"))))
+ {
+ auto it = idMap.find(std::get(prop.second));
+ if (it != idMap.end())
+ {
+ prop.second = it->second;
+ }
+ }
+ }
+
+ ReferenceIdVisitor visitor(idMap);
+
+ // Recurse all
+ for (auto &p : m_properties)
+ {
+ std::visit(visitor, p.second);
+ }
+ }
} // namespace mtconnect::entity
diff --git a/src/mtconnect/entity/entity.hpp b/src/mtconnect/entity/entity.hpp
index f5eebb56..beca10ba 100644
--- a/src/mtconnect/entity/entity.hpp
+++ b/src/mtconnect/entity/entity.hpp
@@ -106,6 +106,18 @@ namespace mtconnect {
/// @return the identity
virtual const entity::Value &getIdentity() const { return getProperty("id"); }
+ /// @brief create unique ids recursively
+ /// @param[in,out] idMap old entity id to new entity id map
+ /// @param[in] sha the root sha1
+ /// @returns optional string value of the new id
+ virtual std::optional createUniqueId(
+ std::unordered_map &idMap,
+ const boost::uuids::detail::sha1 &sha1);
+
+ /// @brief update all id references to the new ids recursively
+ /// @param[in] idMap map of old ids to new ids
+ virtual void updateReferences(const std::unordered_map idMap);
+
/// @brief checks if there is entity is a list that has additional properties
/// @return `true` if this a a list with additional attributes
bool hasListWithAttribute() const
@@ -122,6 +134,15 @@ namespace mtconnect {
/// @brief checkis if this is an entity only containing a list
/// @returns `true` if this entity contains a list
bool isList() const { return m_properties.count("LIST") > 0; }
+ /// @brief check if the property is hidden for normal serialization
+ /// @param[in] name the name of the property
+ ///
+ /// Used for serializing properties for configuration purposes where additional attibutes
+ /// may be included in the document. The only property is `originalId` for now.
+ ///
+ /// This may be extended to include a property set in the future.
+ /// @returns `true` if the property is hidden
+ bool isHidden(const std::string &name) const { return name == "originalId"; }
/// @brief get the name of the entity
/// @return name
const auto &getName() const { return m_name; }
diff --git a/src/mtconnect/entity/factory.hpp b/src/mtconnect/entity/factory.hpp
index db769f4c..e1d91bc3 100644
--- a/src/mtconnect/entity/factory.hpp
+++ b/src/mtconnect/entity/factory.hpp
@@ -359,6 +359,10 @@ namespace mtconnect {
/// @brief scan requirements and register factories refrenced in the requirements
void registerEntityRequirements()
{
+ if (!m_simpleProperties.count("originalId"))
+ {
+ m_requirements.emplace_back("originalId", false);
+ }
for (auto &r : m_requirements)
{
m_properties.emplace(r.getName());
diff --git a/src/mtconnect/entity/json_printer.hpp b/src/mtconnect/entity/json_printer.hpp
index 795ba776..3cb57f16 100644
--- a/src/mtconnect/entity/json_printer.hpp
+++ b/src/mtconnect/entity/json_printer.hpp
@@ -106,7 +106,8 @@ namespace mtconnect::entity {
/// @param version the supported MTConnect serialization version
/// - Version 1 has a repreated objects in arrays for collections of objects
/// - Version 2 combines arrays of objects by type
- JsonPrinter(T &writer, uint32_t version) : m_version(version), m_writer(writer) {};
+ JsonPrinter(T &writer, uint32_t version, bool includeHidden = false)
+ : m_version(version), m_writer(writer), m_includeHidden(includeHidden) {};
/// @brief create a json object from an entity
///
@@ -134,8 +135,11 @@ namespace mtconnect::entity {
for (auto &prop : entity->getProperties())
{
- visitor.m_key = &prop.first;
- visit(visitor, prop.second);
+ if (m_includeHidden || !entity->isHidden(prop.first))
+ {
+ visitor.m_key = &prop.first;
+ visit(visitor, prop.second);
+ }
}
}
@@ -268,6 +272,7 @@ namespace mtconnect::entity {
protected:
uint32_t m_version;
T &m_writer;
+ bool m_includeHidden {false};
};
/// @brief Serialization wrapper to turn an entity into a json string.
@@ -275,7 +280,8 @@ namespace mtconnect::entity {
{
public:
/// @brief Create a printer for with a JSON vesion and flag to pretty print
- JsonEntityPrinter(uint32_t version, bool pretty = false) : m_version(version), m_pretty(pretty)
+ JsonEntityPrinter(uint32_t version, bool pretty = false, bool includeHidden = false)
+ : m_version(version), m_pretty(pretty), m_includeHidden(includeHidden)
{}
/// @brief wrapper around the JsonPrinter print method that creates the correct printer
@@ -287,7 +293,7 @@ namespace mtconnect::entity {
using namespace rapidjson;
StringBuffer output;
RenderJson(output, m_pretty, [&](auto &writer) {
- JsonPrinter printer(writer, m_version);
+ JsonPrinter printer(writer, m_version, m_includeHidden);
printer.printEntity(entity);
});
@@ -306,7 +312,7 @@ namespace mtconnect::entity {
using namespace rapidjson;
StringBuffer output;
RenderJson(output, m_pretty, [&](auto &writer) {
- JsonPrinter printer(writer, m_version);
+ JsonPrinter printer(writer, m_version, m_includeHidden);
printer.print(entity);
});
@@ -316,5 +322,6 @@ namespace mtconnect::entity {
protected:
uint32_t m_version;
bool m_pretty;
+ bool m_includeHidden {false};
};
} // namespace mtconnect::entity
diff --git a/src/mtconnect/entity/xml_parser.cpp b/src/mtconnect/entity/xml_parser.cpp
index 1190994b..e027e362 100644
--- a/src/mtconnect/entity/xml_parser.cpp
+++ b/src/mtconnect/entity/xml_parser.cpp
@@ -234,8 +234,8 @@ namespace mtconnect::entity {
return nullptr;
}
- EntityPtr XmlParser::parse(FactoryPtr factory, const string &document, const string &version,
- ErrorList &errors, bool parseNamespaces)
+ EntityPtr XmlParser::parse(FactoryPtr factory, const string &document, ErrorList &errors,
+ bool parseNamespaces)
{
NAMED_SCOPE("entity.xml_parser");
EntityPtr entity;
diff --git a/src/mtconnect/entity/xml_parser.hpp b/src/mtconnect/entity/xml_parser.hpp
index 5c8c346f..8e66a5f3 100644
--- a/src/mtconnect/entity/xml_parser.hpp
+++ b/src/mtconnect/entity/xml_parser.hpp
@@ -49,12 +49,10 @@ namespace mtconnect {
/// @brief Parse a string document to an entity
/// @param factory The factory to use to create the top level entity
/// @param document the document as a string
- /// @param version version to parse
/// @param errors errors that occurred during the parsing
/// @param parseNamespaces `true` if namespaces should be parsed
/// @return a shared pointer to an entity if successful
- static EntityPtr parse(FactoryPtr factory, const std::string &document,
- const std::string &version, ErrorList &errors,
+ static EntityPtr parse(FactoryPtr factory, const std::string &document, ErrorList &errors,
bool parseNamespaces = true);
};
} // namespace entity
diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp
index 4eadbd19..1ba68e0c 100644
--- a/src/mtconnect/entity/xml_printer.cpp
+++ b/src/mtconnect/entity/xml_printer.cpp
@@ -207,10 +207,13 @@ namespace mtconnect {
for (const auto &prop : properties)
{
auto &key = prop.first;
- if (islower(key.getName()[0]) || attrs.count(key) > 0)
- attributes.emplace_back(prop);
- else
- elements.emplace_back(prop);
+ if (m_includeHidden || !entity->isHidden(key))
+ {
+ if (islower(key.getName()[0]) || attrs.count(key) > 0)
+ attributes.emplace_back(prop);
+ else
+ elements.emplace_back(prop);
+ }
}
// Reorder elements if they need to be specially ordered.
diff --git a/src/mtconnect/entity/xml_printer.hpp b/src/mtconnect/entity/xml_printer.hpp
index 959fc8ec..9d556bbf 100644
--- a/src/mtconnect/entity/xml_printer.hpp
+++ b/src/mtconnect/entity/xml_printer.hpp
@@ -34,7 +34,7 @@ namespace mtconnect {
class AGENT_LIB_API XmlPrinter
{
public:
- XmlPrinter() = default;
+ XmlPrinter(bool includeHidden = false) : m_includeHidden(includeHidden) {}
/// @brief convert an entity to a XML document using `libxml2`
/// @param writer libxml2 `xmlTextWriterPtr`
@@ -42,6 +42,9 @@ namespace mtconnect {
/// @param namespaces a set of namespaces to use in the document
void print(xmlTextWriterPtr writer, const EntityPtr entity,
const std::unordered_set &namespaces);
+
+ protected:
+ bool m_includeHidden {false};
};
} // namespace entity
} // namespace mtconnect
diff --git a/src/mtconnect/parser/xml_parser.cpp b/src/mtconnect/parser/xml_parser.cpp
index b45e60bd..14b4199c 100644
--- a/src/mtconnect/parser/xml_parser.cpp
+++ b/src/mtconnect/parser/xml_parser.cpp
@@ -190,30 +190,32 @@ namespace mtconnect::parser {
devices = xmlXPathEval(BAD_CAST path.c_str(), xpathCtx);
- if (!devices)
- throw(string) xpathCtx->lastError.message;
-
- xmlNodeSetPtr nodeset = devices->nodesetval;
-
- if (!nodeset || !nodeset->nodeNr)
- throw(string) "Could not find Device in XML configuration";
-
- entity::ErrorList errors;
- for (int i = 0; i != nodeset->nodeNr; ++i)
+ if (!devices || !devices->nodesetval || !devices->nodesetval->nodeNr)
+ {
+ LOG(warning) << "No devices in Devices.xml file – expecting dynamic configuration";
+ }
+ else
{
- auto device =
- entity::XmlParser::parseXmlNode(Device::getRoot(), nodeset->nodeTab[i], errors);
- if (device)
- deviceList.emplace_back(dynamic_pointer_cast(device));
+ xmlNodeSetPtr nodeset = devices->nodesetval;
- if (!errors.empty())
+ entity::ErrorList errors;
+ for (int i = 0; i != nodeset->nodeNr; ++i)
{
- for (auto &e : errors)
- LOG(warning) << "Error parsing device: " << e->what();
+ auto device =
+ entity::XmlParser::parseXmlNode(Device::getRoot(), nodeset->nodeTab[i], errors);
+ if (device)
+ deviceList.emplace_back(dynamic_pointer_cast(device));
+
+ if (!errors.empty())
+ {
+ for (auto &e : errors)
+ LOG(warning) << "Error parsing device: " << e->what();
+ }
}
}
- xmlXPathFreeObject(devices);
+ if (devices)
+ xmlXPathFreeObject(devices);
xmlXPathFreeContext(xpathCtx);
}
catch (string e)
@@ -241,6 +243,41 @@ namespace mtconnect::parser {
return deviceList;
}
+ DevicePtr XmlParser::parseDevice(const std::string &deviceXml, printer::XmlPrinter *aPrinter)
+ {
+ DevicePtr device;
+
+ using namespace boost::adaptors;
+ using namespace boost::range;
+
+ std::unique_lock lock(m_mutex);
+
+ try
+ {
+ entity::ErrorList errors;
+ auto entity = entity::XmlParser::parse(Device::getRoot(), deviceXml, errors);
+ if (errors.size() > 0)
+ {
+ LOG(warning) << "Errors parsing Device: " << deviceXml;
+ for (auto &e : errors)
+ {
+ LOG(warning) << " " << e->what();
+ }
+ }
+ else
+ {
+ device = dynamic_pointer_cast(entity);
+ }
+ }
+ catch (string e)
+ {
+ LOG(fatal) << "Cannot parse XML document: " << e;
+ throw;
+ }
+
+ return device;
+ }
+
XmlParser::~XmlParser()
{
std::unique_lock lock(m_mutex);
diff --git a/src/mtconnect/parser/xml_parser.hpp b/src/mtconnect/parser/xml_parser.hpp
index 6590d708..eff7ac9c 100644
--- a/src/mtconnect/parser/xml_parser.hpp
+++ b/src/mtconnect/parser/xml_parser.hpp
@@ -49,8 +49,15 @@ namespace mtconnect::parser {
/// @brief Parses a file and returns a list of devices
/// @param[in] aPath to the file
/// @param[in] aPrinter the printer to obtain and set namespaces
+ /// @returns a list of device pointers
std::list parseFile(const std::string &aPath,
printer::XmlPrinter *aPrinter);
+ /// @brief Parses a single device fragment
+ /// @param[in] deviceXml device xml of a single device
+ /// @param[in] aPrinter the printer to obtain and set namespaces
+ /// @returns a shared device pointer if successful
+ device_model::DevicePtr parseDevice(const std::string &deviceXml,
+ printer::XmlPrinter *aPrinter);
/// @brief Just loads the document, assumed it has already been parsed before.
/// @param aDoc the XML document to parse
diff --git a/src/mtconnect/pipeline/guard.hpp b/src/mtconnect/pipeline/guard.hpp
index 6b252605..5c66c935 100644
--- a/src/mtconnect/pipeline/guard.hpp
+++ b/src/mtconnect/pipeline/guard.hpp
@@ -170,7 +170,7 @@ namespace mtconnect {
{
return check(matches(entity), entity);
}
-
+
/// @brief set the alternative action if this guard does not match
auto &operator||(Guard other)
{
@@ -194,7 +194,7 @@ namespace mtconnect {
{
return check(matches(entity), entity);
}
-
+
/// @brief set the alternative action if this guard does not match
auto &operator||(Guard other)
{
@@ -244,7 +244,7 @@ namespace mtconnect {
{
return B::check(matches(entity), entity);
}
-
+
/// @brief set the alternative action if this guard does not match
auto &operator||(Guard other)
{
diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp
index 20a0044f..1ee84923 100644
--- a/src/mtconnect/pipeline/pipeline.hpp
+++ b/src/mtconnect/pipeline/pipeline.hpp
@@ -155,8 +155,7 @@ namespace mtconnect {
return true;
}
-
-
+
/// @brief add a transform after the target.
/// @param[in] target the named transforms to insert this transform after
/// @param[in] transform the transform to add before
@@ -183,7 +182,7 @@ namespace mtconnect {
return true;
}
-
+
/// @brief splices the transform as the first option in targets next list.
/// @param[in] target the named transforms to insert this transform after
/// @param[in] transform the transform to add before
@@ -284,10 +283,7 @@ namespace mtconnect {
/// @brief Sends the entity through the pipeline
/// @param[in] entity the entity to send through the pipeline
/// @return the entity returned from the transform
- entity::EntityPtr run(entity::EntityPtr &&entity)
- {
- return m_start->next(std::move(entity));
- }
+ entity::EntityPtr run(entity::EntityPtr &&entity) { return m_start->next(std::move(entity)); }
/// @brief Bind the transform to the start
/// @param[in] transform the transform to bind
@@ -301,7 +297,7 @@ namespace mtconnect {
/// @brief check if the pipeline has a pipeline context
/// @returns `true` if there is a context
bool hasContext() const { return bool(m_context); }
-
+
/// @brief check if the pipeline has a pipeline contract
/// @returns `true` if there is a contract
bool hasContract() const { return bool(m_context) && bool(m_context->m_contract); }
diff --git a/src/mtconnect/pipeline/pipeline_context.hpp b/src/mtconnect/pipeline/pipeline_context.hpp
index 99d506a0..bd451f15 100644
--- a/src/mtconnect/pipeline/pipeline_context.hpp
+++ b/src/mtconnect/pipeline/pipeline_context.hpp
@@ -73,7 +73,7 @@ namespace mtconnect::pipeline {
using SharedState = std::unordered_map;
SharedState m_sharedState;
};
-
+
/// @brief Alias for a shared pointer to the pipeline context
using PipelineContextPtr = std::shared_ptr;
} // namespace mtconnect::pipeline
diff --git a/src/mtconnect/pipeline/shdr_token_mapper.cpp b/src/mtconnect/pipeline/shdr_token_mapper.cpp
index 5c022236..188848c5 100644
--- a/src/mtconnect/pipeline/shdr_token_mapper.cpp
+++ b/src/mtconnect/pipeline/shdr_token_mapper.cpp
@@ -270,7 +270,7 @@ namespace mtconnect {
auto body = *token++;
XmlParser parser;
- res = parser.parse(Asset::getRoot(), body, "2.0", errors);
+ res = parser.parse(Asset::getRoot(), body, errors);
if (auto asset = dynamic_pointer_cast(res))
{
asset->setAssetId(assetId);
diff --git a/src/mtconnect/printer/json_printer.cpp b/src/mtconnect/printer/json_printer.cpp
index 2e36fde6..9ab0afbf 100644
--- a/src/mtconnect/printer/json_printer.cpp
+++ b/src/mtconnect/printer/json_printer.cpp
@@ -124,12 +124,13 @@ namespace mtconnect::printer {
}
std::string JsonPrinter::printErrors(const uint64_t instanceId, const unsigned int bufferSize,
- const uint64_t nextSeq, const ProtoErrorList &list) const
+ const uint64_t nextSeq, const ProtoErrorList &list,
+ bool pretty) const
{
defaultSchemaVersion();
StringBuffer output;
- RenderJson(output, m_pretty, [&](auto &writer) {
+ RenderJson(output, m_pretty || pretty, [&](auto &writer) {
AutoJsonObject obj(writer);
{
AutoJsonObject obj(writer, "MTConnectError");
@@ -180,13 +181,14 @@ namespace mtconnect::printer {
const uint64_t nextSeq, const unsigned int assetBufferSize,
const unsigned int assetCount,
const std::list &devices,
- const std::map *count) const
+ const std::map *count,
+ bool includeHidden, bool pretty) const
{
defaultSchemaVersion();
StringBuffer output;
- RenderJson(output, m_pretty, [&](auto &writer) {
- entity::JsonPrinter printer(writer, m_jsonVersion);
+ RenderJson(output, m_pretty || pretty, [&](auto &writer) {
+ entity::JsonPrinter printer(writer, m_jsonVersion, includeHidden);
AutoJsonObject top(writer);
AutoJsonObject obj(writer, "MTConnectDevices");
@@ -206,13 +208,13 @@ namespace mtconnect::printer {
}
std::string JsonPrinter::printAssets(const uint64_t instanceId, const unsigned int bufferSize,
- const unsigned int assetCount,
- const asset::AssetList &asset) const
+ const unsigned int assetCount, const asset::AssetList &asset,
+ bool pretty) const
{
defaultSchemaVersion();
StringBuffer output;
- RenderJson(output, m_pretty, [&](auto &writer) {
+ RenderJson(output, m_pretty || pretty, [&](auto &writer) {
entity::JsonPrinter printer(writer, m_jsonVersion);
AutoJsonObject top(writer);
@@ -406,12 +408,13 @@ namespace mtconnect::printer {
std::string JsonPrinter::printSample(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const uint64_t firstSeq,
- const uint64_t lastSeq, ObservationList &observations) const
+ const uint64_t lastSeq, ObservationList &observations,
+ bool pretty) const
{
defaultSchemaVersion();
StringBuffer output;
- RenderJson(output, m_pretty, [&](auto &writer) {
+ RenderJson(output, m_pretty || pretty, [&](auto &writer) {
AutoJsonObject top(writer);
AutoJsonObject obj(writer, "MTConnectStreams");
obj.AddPairs("jsonVersion", m_jsonVersion, "schemaVersion", *m_schemaVersion);
diff --git a/src/mtconnect/printer/json_printer.hpp b/src/mtconnect/printer/json_printer.hpp
index 8b417e09..e3045389 100644
--- a/src/mtconnect/printer/json_printer.hpp
+++ b/src/mtconnect/printer/json_printer.hpp
@@ -31,19 +31,22 @@ namespace mtconnect::printer {
~JsonPrinter() override = default;
std::string printErrors(const uint64_t instanceId, const unsigned int bufferSize,
- const uint64_t nextSeq, const ProtoErrorList &list) const override;
+ const uint64_t nextSeq, const ProtoErrorList &list,
+ bool pretty = false) const override;
std::string printProbe(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const unsigned int assetBufferSize,
const unsigned int assetCount, const std::list &devices,
- const std::map *count = nullptr) const override;
+ const std::map *count = nullptr,
+ bool includeHidden = false, bool pretty = false) const override;
std::string printSample(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const uint64_t firstSeq, const uint64_t lastSeq,
- observation::ObservationList &results) const override;
+ observation::ObservationList &results,
+ bool pretty = false) const override;
std::string printAssets(const uint64_t anInstanceId, const unsigned int bufferSize,
- const unsigned int assetCount,
- const asset::AssetList &asset) const override;
+ const unsigned int assetCount, const asset::AssetList &asset,
+ bool pretty = false) const override;
std::string mimeType() const override { return "application/mtconnect+json"; }
uint32_t getJsonVersion() const { return m_jsonVersion; }
diff --git a/src/mtconnect/printer/printer.hpp b/src/mtconnect/printer/printer.hpp
index 56810ba6..14a856f5 100644
--- a/src/mtconnect/printer/printer.hpp
+++ b/src/mtconnect/printer/printer.hpp
@@ -61,7 +61,7 @@ namespace mtconnect {
/// @return the error document
virtual std::string printError(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const std::string &errorCode,
- const std::string &errorText) const
+ const std::string &errorText, bool pretty = false) const
{
return printErrors(instanceId, bufferSize, nextSeq, {{errorCode, errorText}});
}
@@ -72,7 +72,8 @@ namespace mtconnect {
/// @param[in] list the list of errors
/// @return the MTConnect Error document
virtual std::string printErrors(const uint64_t instanceId, const unsigned int bufferSize,
- const uint64_t nextSeq, const ProtoErrorList &list) const = 0;
+ const uint64_t nextSeq, const ProtoErrorList &list,
+ bool pretty = false) const = 0;
/// @brief Generate an MTConnect Devices document
/// @param[in] instanceId the instance id
/// @param[in] bufferSize the buffer size
@@ -82,11 +83,12 @@ namespace mtconnect {
/// @param[in] devices a list of devices
/// @param[in] count optional asset count and type association
/// @return the MTConnect Devices document
- virtual std::string printProbe(
- const uint64_t instanceId, const unsigned int bufferSize, const uint64_t nextSeq,
- const unsigned int assetBufferSize, const unsigned int assetCount,
- const std::list &devices,
- const std::map *count = nullptr) const = 0;
+ virtual std::string printProbe(const uint64_t instanceId, const unsigned int bufferSize,
+ const uint64_t nextSeq, const unsigned int assetBufferSize,
+ const unsigned int assetCount,
+ const std::list &devices,
+ const std::map *count = nullptr,
+ bool includeHidden = false, bool pretty = false) const = 0;
/// @brief Print a MTConnect Streams document
/// @param[in] instanceId the instance id
/// @param[in] bufferSize the buffer size
@@ -97,8 +99,8 @@ namespace mtconnect {
/// @return the MTConnect Streams document
virtual std::string printSample(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const uint64_t firstSeq,
- const uint64_t lastSeq,
- observation::ObservationList &results) const = 0;
+ const uint64_t lastSeq, observation::ObservationList &results,
+ bool pretty = false) const = 0;
/// @brief Generate an MTConnect Assets document
/// @param[in] anInstanceId the instance id
/// @param[in] bufferSize the buffer size
@@ -106,8 +108,8 @@ namespace mtconnect {
/// @param[in] asset the list of assets
/// @return the MTConnect Assets document
virtual std::string printAssets(const uint64_t anInstanceId, const unsigned int bufferSize,
- const unsigned int assetCount,
- asset::AssetList const &asset) const = 0;
+ const unsigned int assetCount, asset::AssetList const &asset,
+ bool pretty = false) const = 0;
/// @brief get the mime type for the documents
/// @return the mime type
virtual std::string mimeType() const = 0;
diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp
index ec0ffea5..887d59ba 100644
--- a/src/mtconnect/printer/xml_printer.cpp
+++ b/src/mtconnect/printer/xml_printer.cpp
@@ -340,13 +340,14 @@ namespace mtconnect::printer {
}
std::string XmlPrinter::printErrors(const uint64_t instanceId, const unsigned int bufferSize,
- const uint64_t nextSeq, const ProtoErrorList &list) const
+ const uint64_t nextSeq, const ProtoErrorList &list,
+ bool pretty) const
{
string ret;
try
{
- XmlWriter writer(m_pretty);
+ XmlWriter writer(m_pretty || pretty);
initXmlDoc(writer, eERROR, instanceId, bufferSize, 0, 0, nextSeq, nextSeq - 1);
@@ -377,20 +378,21 @@ namespace mtconnect::printer {
string XmlPrinter::printProbe(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const unsigned int assetBufferSize,
const unsigned int assetCount, const list &deviceList,
- const std::map *count) const
+ const std::map *count, bool includeHidden,
+ bool pretty) const
{
string ret;
try
{
- XmlWriter writer(m_pretty);
+ XmlWriter writer(m_pretty || pretty);
initXmlDoc(writer, eDEVICES, instanceId, bufferSize, assetBufferSize, assetCount, nextSeq, 0,
nextSeq - 1, count);
{
AutoElement devices(writer, "Devices");
- entity::XmlPrinter printer;
+ entity::XmlPrinter printer(includeHidden);
for (auto &device : deviceList)
printer.print(writer, device, m_deviceNsSet);
@@ -413,13 +415,14 @@ namespace mtconnect::printer {
string XmlPrinter::printSample(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const uint64_t firstSeq,
- const uint64_t lastSeq, ObservationList &observations) const
+ const uint64_t lastSeq, ObservationList &observations,
+ bool pretty) const
{
string ret;
try
{
- XmlWriter writer(m_pretty);
+ XmlWriter writer(m_pretty || pretty);
initXmlDoc(writer, eSTREAMS, instanceId, bufferSize, 0, 0, nextSeq, firstSeq, lastSeq);
@@ -492,12 +495,13 @@ namespace mtconnect::printer {
}
string XmlPrinter::printAssets(const uint64_t instanceId, const unsigned int bufferSize,
- const unsigned int assetCount, const AssetList &asset) const
+ const unsigned int assetCount, const AssetList &asset,
+ bool pretty) const
{
string ret;
try
{
- XmlWriter writer(m_pretty);
+ XmlWriter writer(m_pretty || pretty);
initXmlDoc(writer, eASSETS, instanceId, 0u, bufferSize, assetCount, 0ull);
{
@@ -578,6 +582,8 @@ namespace mtconnect::printer {
}
string rootName = "MTConnect" + xmlType;
+ if (!m_schemaVersion)
+ defaultSchemaVersion();
string xmlns = "urn:mtconnect.org:" + rootName + ":" + *m_schemaVersion;
string location;
diff --git a/src/mtconnect/printer/xml_printer.hpp b/src/mtconnect/printer/xml_printer.hpp
index 955dcf90..bb4e0c2d 100644
--- a/src/mtconnect/printer/xml_printer.hpp
+++ b/src/mtconnect/printer/xml_printer.hpp
@@ -44,20 +44,22 @@ namespace mtconnect {
~XmlPrinter() override = default;
std::string printErrors(const uint64_t instanceId, const unsigned int bufferSize,
- const uint64_t nextSeq, const ProtoErrorList &list) const override;
+ const uint64_t nextSeq, const ProtoErrorList &list,
+ bool pretty = false) const override;
std::string printProbe(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const unsigned int assetBufferSize,
const unsigned int assetCount, const std::list &devices,
- const std::map *count = nullptr) const override;
+ const std::map *count = nullptr,
+ bool includeHidden = false, bool pretty = false) const override;
std::string printSample(const uint64_t instanceId, const unsigned int bufferSize,
const uint64_t nextSeq, const uint64_t firstSeq,
- const uint64_t lastSeq,
- observation::ObservationList &results) const override;
+ const uint64_t lastSeq, observation::ObservationList &results,
+ bool pretty = false) const override;
std::string printAssets(const uint64_t anInstanceId, const unsigned int bufferSize,
- const unsigned int assetCount,
- const asset::AssetList &asset) const override;
+ const unsigned int assetCount, const asset::AssetList &asset,
+ bool pretty = false) const override;
std::string mimeType() const override { return "text/xml"; }
/// @brief Add a Devices XML device namespace
diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp
index 66fe262d..9dfb7e6c 100644
--- a/src/mtconnect/sink/rest_sink/rest_service.cpp
+++ b/src/mtconnect/sink/rest_sink/rest_service.cpp
@@ -105,6 +105,7 @@ namespace mtconnect {
{"to", QUERY, "Sequence number at to stop reporting observations"},
{"from", QUERY, "Sequence number at to start reporting observations"},
{"interval", QUERY, "Time in ms between publishing data–starts streaming"},
+ {"pretty", QUERY, "Instructs the result to be pretty printed"},
{"heartbeat", QUERY,
"Time in ms between publishing a empty document when no data has changed"}});
@@ -400,31 +401,36 @@ namespace mtconnect {
// Probe
auto handler = [&](SessionPtr session, const RequestPtr request) -> bool {
auto device = request->parameter("device");
+ auto pretty = *request->parameter("pretty");
+
auto printer = printerForAccepts(request->m_accepts);
if (device && !ends_with(request->m_path, string("probe")) &&
m_sinkContract->findDeviceByUUIDorName(*device) == nullptr)
return false;
- respond(session, probeRequest(printer, device));
+ respond(session, probeRequest(printer, device, pretty));
return true;
};
- m_server->addRouting({boost::beast::http::verb::get, "/probe", handler})
+ m_server->addRouting({boost::beast::http::verb::get, "/probe?pretty={bool:false}", handler})
.document("MTConnect probe request",
"Provides metadata service for the MTConnect Devices information model for all "
"devices.");
- m_server->addRouting({boost::beast::http::verb::get, "/{device}/probe", handler})
+ m_server
+ ->addRouting(
+ {boost::beast::http::verb::get, "/{device}/probe?pretty={bool:false}", handler})
.document("MTConnect probe request",
"Provides metadata service for the MTConnect Devices information model for "
"device identified by `device` matching `name` or `uuid`.");
// Must be last
- m_server->addRouting({boost::beast::http::verb::get, "/", handler})
+ m_server->addRouting({boost::beast::http::verb::get, "/?pretty={bool:false}", handler})
.document("MTConnect probe request",
"Provides metadata service for the MTConnect Devices information model for all "
"devices.");
- m_server->addRouting({boost::beast::http::verb::get, "/{device}", handler})
+ m_server
+ ->addRouting({boost::beast::http::verb::get, "/{device}??pretty={bool:false}", handler})
.document("MTConnect probe request",
"Provides metadata service for the MTConnect Devices information model for "
"device identified by `device` matching `name` or `uuid`.");
@@ -466,7 +472,9 @@ namespace mtconnect {
return true;
};
- string qp("type={string}&removed={bool:false}&count={integer:100}&device={string}");
+ string qp(
+ "type={string}&removed={bool:false}&"
+ "count={integer:100}&device={string}&pretty={bool:false}");
m_server->addRouting({boost::beast::http::verb::get, "/assets?" + qp, handler})
.document("MTConnect assets request", "Returns up to `count` assets");
m_server->addRouting({boost::beast::http::verb::get, "/asset?" + qp, handler})
@@ -568,19 +576,23 @@ namespace mtconnect {
{
streamCurrentRequest(session, printerForAccepts(request->m_accepts), *interval,
request->parameter("device"),
- request->parameter("path"));
+ request->parameter("path"),
+ *request->parameter("pretty"));
}
else
{
respond(session, currentRequest(printerForAccepts(request->m_accepts),
request->parameter("device"),
request->parameter("at"),
- request->parameter("path")));
+ request->parameter("path"),
+ *request->parameter("pretty")));
}
return true;
};
- string qp("path={string}&at={unsigned_integer}&interval={integer}");
+ string qp(
+ "path={string}&at={unsigned_integer}&"
+ "interval={integer}&pretty={bool:false}");
m_server->addRouting({boost::beast::http::verb::get, "/current?" + qp, handler})
.document("MTConnect current request",
"Gets a stapshot of the state of all the observations for all devices "
@@ -602,7 +614,7 @@ namespace mtconnect {
session, printerForAccepts(request->m_accepts), *interval,
*request->parameter("heartbeat"), *request->parameter("count"),
request->parameter("device"), request->parameter("from"),
- request->parameter("path"));
+ request->parameter("path"), *request->parameter("pretty"));
}
else
{
@@ -610,7 +622,8 @@ namespace mtconnect {
sampleRequest(
printerForAccepts(request->m_accepts), *request->parameter("count"),
request->parameter("device"), request->parameter("from"),
- request->parameter("to"), request->parameter("path")));
+ request->parameter("to"), request->parameter("path"),
+ *request->parameter("pretty")));
}
return true;
};
@@ -618,7 +631,8 @@ namespace mtconnect {
string qp(
"path={string}&from={unsigned_integer}&"
"interval={integer}&count={integer:100}&"
- "heartbeat={integer:10000}&to={unsigned_integer}");
+ "heartbeat={integer:10000}&to={unsigned_integer}&"
+ "pretty={bool:false}");
m_server->addRouting({boost::beast::http::verb::get, "/sample?" + qp, handler})
.document("MTConnect sample request",
"Gets a time series of at maximum `count` observations for all devices "
@@ -685,7 +699,7 @@ namespace mtconnect {
// -------------------------------------------
ResponsePtr RestService::probeRequest(const Printer *printer,
- const std::optional &device)
+ const std::optional &device, bool pretty)
{
NAMED_SCOPE("RestService::probeRequest");
@@ -709,14 +723,14 @@ namespace mtconnect {
m_sinkContract->getCircularBuffer().getSequence(),
uint32_t(m_sinkContract->getAssetStorage()->getMaxAssets()),
uint32_t(m_sinkContract->getAssetStorage()->getCount()), deviceList,
- &counts),
+ &counts, false, pretty),
printer->mimeType());
}
ResponsePtr RestService::currentRequest(const Printer *printer,
const std::optional &device,
const std::optional &at,
- const std::optional &path)
+ const std::optional &path, bool pretty)
{
using namespace rest_sink;
DevicePtr dev {nullptr};
@@ -732,7 +746,8 @@ namespace mtconnect {
}
// Check if there is a frequency to stream data or not
- return make_unique(rest_sink::status::ok, fetchCurrentData(printer, filter, at),
+ return make_unique(rest_sink::status::ok,
+ fetchCurrentData(printer, filter, at, pretty),
printer->mimeType());
}
@@ -740,7 +755,7 @@ namespace mtconnect {
const std::optional &device,
const std::optional &from,
const std::optional &to,
- const std::optional &path)
+ const std::optional &path, bool pretty)
{
using namespace rest_sink;
DevicePtr dev {nullptr};
@@ -761,7 +776,8 @@ namespace mtconnect {
return make_unique(
rest_sink::status::ok,
- fetchSampleData(printer, filter, count, from, to, end, endOfBuffer), printer->mimeType());
+ fetchSampleData(printer, filter, count, from, to, end, endOfBuffer, nullptr, pretty),
+ printer->mimeType());
}
struct AsyncSampleResponse
@@ -787,13 +803,14 @@ namespace mtconnect {
ChangeObserver m_observer;
chrono::system_clock::time_point m_last;
boost::asio::steady_timer m_timer;
+ bool m_pretty {false};
};
void RestService::streamSampleRequest(rest_sink::SessionPtr session, const Printer *printer,
const int interval, const int heartbeatIn,
const int count, const std::optional &device,
const std::optional &from,
- const std::optional &path)
+ const std::optional &path, bool pretty)
{
NAMED_SCOPE("RestService::streamSampleRequest");
@@ -814,6 +831,7 @@ namespace mtconnect {
asyncResponse->m_printer = printer;
asyncResponse->m_heartbeat = std::chrono::milliseconds(heartbeatIn);
asyncResponse->m_service = getptr();
+ asyncResponse->m_pretty = pretty;
checkPath(asyncResponse->m_printer, path, dev, asyncResponse->m_filter);
@@ -968,7 +986,8 @@ namespace mtconnect {
// sent.
content = fetchSampleData(asyncResponse->m_printer, asyncResponse->m_filter,
asyncResponse->m_count, asyncResponse->m_sequence, nullopt, end,
- asyncResponse->m_endOfBuffer, &asyncResponse->m_observer);
+ asyncResponse->m_endOfBuffer, &asyncResponse->m_observer,
+ asyncResponse->m_pretty);
// Even if we are at the end of the buffer, or within range. If we are filtering,
// we will need to make sure we are not spinning when there are no valid events
@@ -1003,12 +1022,13 @@ namespace mtconnect {
const Printer *m_printer {nullptr};
FilterSetOpt m_filter;
boost::asio::steady_timer m_timer;
+ bool m_pretty {false};
};
void RestService::streamCurrentRequest(SessionPtr session, const Printer *printer,
const int interval,
const std::optional &device,
- const std::optional &path)
+ const std::optional &path, bool pretty)
{
checkRange(printer, interval, 0, numeric_limits().max(), "interval");
DevicePtr dev {nullptr};
@@ -1026,6 +1046,7 @@ namespace mtconnect {
asyncResponse->m_interval = chrono::milliseconds {interval};
asyncResponse->m_printer = printer;
asyncResponse->m_service = getptr();
+ asyncResponse->m_pretty = pretty;
asyncResponse->m_session->beginStreaming(
printer->mimeType(), boost::asio::bind_executor(m_strand, [this, asyncResponse]() {
@@ -1061,7 +1082,8 @@ namespace mtconnect {
}
asyncResponse->m_session->writeChunk(
- fetchCurrentData(asyncResponse->m_printer, asyncResponse->m_filter, nullopt),
+ fetchCurrentData(asyncResponse->m_printer, asyncResponse->m_filter, nullopt,
+ asyncResponse->m_pretty),
boost::asio::bind_executor(m_strand, [this, asyncResponse]() {
asyncResponse->m_timer.expires_from_now(asyncResponse->m_interval);
asyncResponse->m_timer.async_wait(boost::asio::bind_executor(
@@ -1072,7 +1094,7 @@ namespace mtconnect {
ResponsePtr RestService::assetRequest(const Printer *printer, const int32_t count,
const bool removed,
const std::optional &type,
- const std::optional &device)
+ const std::optional &device, bool pretty)
{
using namespace rest_sink;
@@ -1088,14 +1110,14 @@ namespace mtconnect {
m_sinkContract->getAssetStorage()->getAssets(list, count, !removed, uuid, type);
return make_unique(
status::ok,
- printer->printAssets(m_instanceId,
- uint32_t(m_sinkContract->getAssetStorage()->getMaxAssets()),
- uint32_t(m_sinkContract->getAssetStorage()->getCount()), list),
+ printer->printAssets(
+ m_instanceId, uint32_t(m_sinkContract->getAssetStorage()->getMaxAssets()),
+ uint32_t(m_sinkContract->getAssetStorage()->getCount()), list, pretty),
printer->mimeType());
}
ResponsePtr RestService::assetIdsRequest(const Printer *printer,
- const std::list &ids)
+ const std::list &ids, bool pretty)
{
using namespace rest_sink;
@@ -1109,16 +1131,16 @@ namespace mtconnect {
auto message = str.str().substr(0, str.str().size() - 2);
return make_unique(status::not_found,
- printError(printer, "ASSET_NOT_FOUND", message),
+ printError(printer, "ASSET_NOT_FOUND", message, pretty),
printer->mimeType());
}
else
{
return make_unique(
status::ok,
- printer->printAssets(m_instanceId,
- uint32_t(m_sinkContract->getAssetStorage()->getMaxAssets()),
- uint32_t(m_sinkContract->getAssetStorage()->getCount()), list),
+ printer->printAssets(
+ m_instanceId, uint32_t(m_sinkContract->getAssetStorage()->getMaxAssets()),
+ uint32_t(m_sinkContract->getAssetStorage()->getCount()), list, pretty),
printer->mimeType());
}
}
@@ -1304,13 +1326,13 @@ namespace mtconnect {
}
string RestService::printError(const Printer *printer, const string &errorCode,
- const string &text) const
+ const string &text, bool pretty) const
{
LOG(debug) << "Returning error " << errorCode << ": " << text;
if (printer)
return printer->printError(
m_instanceId, m_sinkContract->getCircularBuffer().getBufferSize(),
- m_sinkContract->getCircularBuffer().getSequence(), errorCode, text);
+ m_sinkContract->getCircularBuffer().getSequence(), errorCode, text, pretty);
else
return errorCode + ": " + text;
}
@@ -1385,7 +1407,7 @@ namespace mtconnect {
// -------------------------------------------
string RestService::fetchCurrentData(const Printer *printer, const FilterSetOpt &filterSet,
- const optional &at)
+ const optional &at, bool pretty)
{
ObservationList observations;
SequenceNumber_t firstSeq, seq;
@@ -1409,14 +1431,14 @@ namespace mtconnect {
}
return printer->printSample(m_instanceId, m_sinkContract->getCircularBuffer().getBufferSize(),
- seq, firstSeq, seq - 1, observations);
+ seq, firstSeq, seq - 1, observations, pretty);
}
string RestService::fetchSampleData(const Printer *printer, const FilterSetOpt &filterSet,
int count, const std::optional &from,
const std::optional &to,
SequenceNumber_t &end, bool &endOfBuffer,
- ChangeObserver *observer)
+ ChangeObserver *observer, bool pretty)
{
std::unique_ptr observations;
SequenceNumber_t firstSeq, lastSeq;
@@ -1449,7 +1471,7 @@ namespace mtconnect {
}
return printer->printSample(m_instanceId, m_sinkContract->getCircularBuffer().getBufferSize(),
- end, firstSeq, lastSeq, *observations);
+ end, firstSeq, lastSeq, *observations, pretty);
}
} // namespace sink::rest_sink
diff --git a/src/mtconnect/sink/rest_sink/rest_service.hpp b/src/mtconnect/sink/rest_sink/rest_service.hpp
index 2368641a..83e27864 100644
--- a/src/mtconnect/sink/rest_sink/rest_service.hpp
+++ b/src/mtconnect/sink/rest_sink/rest_service.hpp
@@ -91,65 +91,75 @@ namespace mtconnect {
///@{
/// @brief Handler for a probe request
- /// @param p printer for doc generation
- /// @param device optional device name or uuid
+ /// @param[in] p printer for doc generation
+ /// @param[in] device optional device name or uuid
+ /// @param[in] pretty `true` to ensure response is formatted
/// @return MTConnect Devices response
ResponsePtr probeRequest(const printer::Printer *p,
- const std::optional &device = std::nullopt);
+ const std::optional &device = std::nullopt,
+ bool pretty = false);
/// @brief Handler for a current request
- /// @param p printer for doc generation
- /// @param device optional device name or uuid
- /// @param at optional sequence number to take the snapshot
- /// @param path an xpath to filter
+ /// @param[in] p printer for doc generation
+ /// @param[in] device optional device name or uuid
+ /// @param[in] at optional sequence number to take the snapshot
+ /// @param[in] path an xpath to filter
+ /// @param[in] pretty `true` to ensure response is formatted
/// @return MTConnect Streams response
ResponsePtr currentRequest(const printer::Printer *p,
const std::optional &device = std::nullopt,
const std::optional &at = std::nullopt,
- const std::optional &path = std::nullopt);
+ const std::optional &path = std::nullopt,
+ bool pretty = false);
/// @brief Handler for a sample request
- /// @param p printer for doc generation
- /// @param count maximum number of observations
- /// @param device optional device name or uuid
- /// @param from optional starting sequence number
- /// @param to optional ending sequence number
- /// @param path an xpath for filtering
+ /// @param[in] p printer for doc generation
+ /// @param[in] count maximum number of observations
+ /// @param[in] device optional device name or uuid
+ /// @param[in] from optional starting sequence number
+ /// @param[in] to optional ending sequence number
+ /// @param[in] path an xpath for filtering
+ /// @param[in] pretty `true` to ensure response is formatted
/// @return MTConnect Streams response
ResponsePtr sampleRequest(const printer::Printer *p, const int count = 100,
const std::optional &device = std::nullopt,
const std::optional &from = std::nullopt,
const std::optional &to = std::nullopt,
- const std::optional &path = std::nullopt);
+ const std::optional &path = std::nullopt,
+ bool pretty = false);
/// @brief Handler for a streaming sample
- /// @param session session to stream data to
- /// @param p printer for doc generation
- /// @param interval the minimum interval between sending documents in ms
- /// @param heartbeat how often to send an empty document if no activity in ms
- /// @param count the maxumum number of observations
- /// @param device optional device name or uuid
- /// @param from optional starting sequence number
- /// @param path optional path for filtering
+ /// @param[in] session session to stream data to
+ /// @param[in] p printer for doc generation
+ /// @param[in] interval the minimum interval between sending documents in ms
+ /// @param[in] heartbeat how often to send an empty document if no activity in ms
+ /// @param[in] count the maxumum number of observations
+ /// @param[in] device optional device name or uuid
+ /// @param[in] from optional starting sequence number
+ /// @param[in] path optional path for filtering
+ /// @param[in] pretty `true` to ensure response is formatted
void streamSampleRequest(SessionPtr session, const printer::Printer *p, const int interval,
const int heartbeat, const int count = 100,
const std::optional &device = std::nullopt,
const std::optional &from = std::nullopt,
- const std::optional &path = std::nullopt);
+ const std::optional &path = std::nullopt,
+ bool pretty = false);
/// @brief Handler for a streaming current
- /// @param session session to stream data to
- /// @param p printer for doc generation
- /// @param interval the minimum interval between sending documents in ms
- /// @param device optional device name or uuid
- /// @param path optional path for filtering
+ /// @param[in] session session to stream data to
+ /// @param[in] p printer for doc generation
+ /// @param[in] interval the minimum interval between sending documents in ms
+ /// @param[in] device optional device name or uuid
+ /// @param[in] path optional path for filtering
+ /// @param[in] pretty `true` to ensure response is formatted
void streamCurrentRequest(SessionPtr session, const printer::Printer *p, const int interval,
const std::optional &device = std::nullopt,
- const std::optional &path = std::nullopt);
+ const std::optional &path = std::nullopt,
+ bool pretty = false);
/// @brief Handler for put/post observation
- /// @param p printer for response generation
- /// @param device device
- /// @param observations key/value pairs for the observations
- /// @param time optional timestamp
+ /// @param[in] p printer for response generation
+ /// @param[in] device device
+ /// @param[in] observations key/value pairs for the observations
+ /// @param[in] time optional timestamp
/// @return `` if succeeds
ResponsePtr putObservationRequest(const printer::Printer *p, const std::string &device,
const QueryMap observations,
@@ -181,21 +191,25 @@ namespace mtconnect {
///@{
/// @brief Asset request handler for assets by type or device
- /// @param p printer for the response document
- /// @param count maximum number of assets to return
- /// @param removed `true` if response should include removed assets
- /// @param type optional type of asset to filter
- /// @param device optional device name or uuid
+ /// @param[in] p printer for the response document
+ /// @param[in] count maximum number of assets to return
+ /// @param[in] removed `true` if response should include removed assets
+ /// @param[in] type optional type of asset to filter
+ /// @param[in] device optional device name or uuid
+ /// @param[in] pretty `true` to ensure response is formatted
/// @return MTConnect Assets response document
ResponsePtr assetRequest(const printer::Printer *p, const int32_t count, const bool removed,
const std::optional &type = std::nullopt,
- const std::optional &device = std::nullopt);
+ const std::optional &device = std::nullopt,
+ bool pretty = false);
/// @brief Asset request handler using a list of asset ids
- /// @param p printer for the response document
- /// @param ids list of asset ids
+ /// @param[in] p printer for the response document
+ /// @param[in] ids list of asset ids
+ /// @param[in] pretty `true` to ensure response is formatted
/// @return MTConnect Assets response document
- ResponsePtr assetIdsRequest(const printer::Printer *p, const std::list &ids);
+ ResponsePtr assetIdsRequest(const printer::Printer *p, const std::list &ids,
+ bool pretty = false);
/// @brief Asset request handler to update an asset
/// @param p printer for the response document
@@ -242,7 +256,7 @@ namespace mtconnect {
/// @param text descriptive error text
/// @return MTConnect Error document
std::string printError(const printer::Printer *printer, const std::string &errorCode,
- const std::string &text) const;
+ const std::string &text, bool pretty = false) const;
/// @name For testing only
///@{
@@ -281,14 +295,15 @@ namespace mtconnect {
// Current Data Collection
std::string fetchCurrentData(const printer::Printer *printer, const FilterSetOpt &filterSet,
- const std::optional &at);
+ const std::optional &at, bool pretty = false);
// Sample data collection
std::string fetchSampleData(const printer::Printer *printer, const FilterSetOpt &filterSet,
int count, const std::optional &from,
const std::optional &to, SequenceNumber_t &end,
bool &endOfBuffer,
- observation::ChangeObserver *observer = nullptr);
+ observation::ChangeObserver *observer = nullptr,
+ bool pretty = false);
// Verification methods
template
diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp
index a1728ade..cfafe518 100644
--- a/src/mtconnect/sink/rest_sink/server.cpp
+++ b/src/mtconnect/sink/rest_sink/server.cpp
@@ -272,7 +272,7 @@ namespace mtconnect::sink::rest_sink {
visit(
overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); },
[&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); },
- [&obj](double d) { obj.Add(d); }},
+ [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }},
param.m_default);
}
}
diff --git a/src/mtconnect/sink/rest_sink/server.hpp b/src/mtconnect/sink/rest_sink/server.hpp
index c7c84557..07c5c4b5 100644
--- a/src/mtconnect/sink/rest_sink/server.hpp
+++ b/src/mtconnect/sink/rest_sink/server.hpp
@@ -245,6 +245,7 @@ namespace mtconnect::sink::rest_sink {
/// @name Swagger Support
/// @{
+ ///
/// @brief Add swagger routings to the Agent
void addSwaggerRoutings();
/// @brief generate swagger API from routings
diff --git a/src/mtconnect/source/adapter/adapter_pipeline.cpp b/src/mtconnect/source/adapter/adapter_pipeline.cpp
index ce6f83c8..b902164f 100644
--- a/src/mtconnect/source/adapter/adapter_pipeline.cpp
+++ b/src/mtconnect/source/adapter/adapter_pipeline.cpp
@@ -69,9 +69,10 @@ namespace mtconnect {
"Message", Properties {{"VALUE", data}, {"topic", topic}, {"source", source}});
run(std::move(entity));
};
- handler->m_command = [this](const std::string &data, const std::string &source) {
- auto entity =
- make_shared("Command", Properties {{"VALUE", data}, {"source", source}});
+ handler->m_command = [this](const std::string &command, const std::string &value,
+ const std::string &source) {
+ auto entity = make_shared(
+ "Command", Properties {{"command", command}, {"VALUE", value}, {"source", source}});
run(std::move(entity));
};
diff --git a/src/mtconnect/source/adapter/adapter_pipeline.hpp b/src/mtconnect/source/adapter/adapter_pipeline.hpp
index 3948c862..6f1660cf 100644
--- a/src/mtconnect/source/adapter/adapter_pipeline.hpp
+++ b/src/mtconnect/source/adapter/adapter_pipeline.hpp
@@ -26,6 +26,8 @@ namespace mtconnect::source::adapter {
struct Handler
{
using ProcessData = std::function;
+ using ProcessCommand = std::function;
using ProcessMessage = std::function;
using Connect = std::function;
@@ -33,7 +35,7 @@ namespace mtconnect::source::adapter {
/// @brief Process Data Messages
ProcessData m_processData;
/// @brief Process an adapter command
- ProcessData m_command;
+ ProcessCommand m_command;
/// @brief Process a message with a topic
ProcessMessage m_processMessage;
diff --git a/src/mtconnect/source/adapter/shdr/connector.cpp b/src/mtconnect/source/adapter/shdr/connector.cpp
index bb144eb1..f8570448 100644
--- a/src/mtconnect/source/adapter/shdr/connector.cpp
+++ b/src/mtconnect/source/adapter/shdr/connector.cpp
@@ -289,19 +289,12 @@ namespace mtconnect::source::adapter::shdr {
LOG(trace) << "(" << m_server << ":" << m_port << ") Received line: " << line;
// Check for heartbeats
- if (line[0] == '*')
+ if (line[0] == '*' && !line.compare(0, 6, "* PONG"))
{
- if (!line.compare(0, 6, "* PONG"))
- {
- LOG(debug) << "(Port:" << m_localPort << ") Received a PONG for " << m_server << " on port "
- << m_port;
- if (!m_heartbeats)
- startHeartbeats(line);
- }
- else
- {
- protocolCommand(line);
- }
+ LOG(debug) << "(Port:" << m_localPort << ") Received a PONG for " << m_server << " on port "
+ << m_port;
+ if (!m_heartbeats)
+ startHeartbeats(line);
}
else
{
diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp
index 0496ff38..1b119b6d 100644
--- a/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp
+++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.cpp
@@ -19,6 +19,9 @@
#include "shdr_adapter.hpp"
#include
+#include
+#include
+#include
#include
#include
@@ -112,29 +115,25 @@ namespace mtconnect::source::adapter::shdr {
{
if (data == *m_terminator)
{
- if (m_handler && m_handler->m_processData)
- m_handler->m_processData(m_body.str(), getIdentity());
+ forwardData(m_body.str());
m_terminator.reset();
m_body.str("");
}
else
{
- m_body << endl << data;
+ m_body << std::endl << data;
}
-
- return;
}
-
- if (size_t multi = data.find("--multiline--"); multi != string::npos)
+ else if (size_t multi = data.find("--multiline--"); multi != std::string::npos)
{
m_body.str("");
m_body << data.substr(0, multi);
m_terminator = data.substr(multi);
- return;
}
-
- if (m_handler && m_handler->m_processData)
- m_handler->m_processData(data, getIdentity());
+ else
+ {
+ forwardData(data);
+ }
}
catch (std::exception &e)
{
@@ -164,18 +163,30 @@ namespace mtconnect::source::adapter::shdr {
{
NAMED_SCOPE("ShdrAdapter::protocolCommand");
- static auto pattern = regex("\\*[ ]*([^:]+):[ ]*(.+)");
- smatch match;
-
using namespace boost::algorithm;
+ namespace qi = boost::spirit::qi;
+ namespace ascii = boost::spirit::ascii;
+ namespace phoenix = boost::phoenix;
- if (std::regex_match(data, match, pattern))
- {
- auto command = to_lower_copy(match[1].str());
- auto value = match[2].str();
+ using ascii::space;
+ using qi::char_;
+ using qi::lexeme;
+ using qi::lit;
+ string command;
+ auto f = [&command](const auto &s) { command = string(s.begin(), s.end()); };
+
+ auto it = data.begin();
+ bool res =
+ qi::phrase_parse(it, data.end(), (lit("*") >> lexeme[+(char_ - ':')][f] >> ':'), space);
+
+ if (res)
+ {
+ string value(it, data.end());
ConfigOptions options;
+ boost::to_lower(command);
+
if (command == "conversionrequired")
options[configuration::ConversionRequired] = is_true(value);
else if (command == "relativetime")
@@ -190,7 +201,11 @@ namespace mtconnect::source::adapter::shdr {
if (options.size() > 0)
setOptions(options);
else if (m_handler && m_handler->m_command)
- m_handler->m_command(data, getIdentity());
+ m_handler->m_command(command, value, getIdentity());
+ }
+ else
+ {
+ LOG(warning) << "protocolCommand: Cannot parse command: " << data;
}
}
} // namespace mtconnect::source::adapter::shdr
diff --git a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp
index 169d61fe..5187aa6c 100644
--- a/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp
+++ b/src/mtconnect/source/adapter/shdr/shdr_adapter.hpp
@@ -118,15 +118,25 @@ namespace mtconnect {
/// @brief Change the options for the adapter
/// @param[in] options the set of options
- void setOptions(const ConfigOptions &options)
+ void setOptions(const ConfigOptions &options) override
{
for (auto &o : options)
m_options.insert_or_assign(o.first, o.second);
+ bool started = m_pipeline.started();
m_pipeline.build(m_options);
- if (!m_pipeline.started())
+ if (!m_pipeline.started() && started)
m_pipeline.start();
}
+ protected:
+ void forwardData(const std::string &data)
+ {
+ if (data[0] == '*')
+ protocolCommand(data);
+ else if (m_handler && m_handler->m_processData)
+ m_handler->m_processData(data, getIdentity());
+ }
+
protected:
ShdrPipeline m_pipeline;
diff --git a/src/mtconnect/source/loopback_source.cpp b/src/mtconnect/source/loopback_source.cpp
index 05acd1de..9befdcf1 100644
--- a/src/mtconnect/source/loopback_source.cpp
+++ b/src/mtconnect/source/loopback_source.cpp
@@ -112,7 +112,7 @@ namespace mtconnect::source {
entity::ErrorList &errors)
{
// Parse the asset
- auto entity = entity::XmlParser::parse(asset::Asset::getRoot(), document, "1.7", errors);
+ auto entity = entity::XmlParser::parse(asset::Asset::getRoot(), document, errors);
if (!entity)
{
LOG(warning) << "Asset could not be parsed";
diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp
index 38c6a71c..cdacfb9f 100644
--- a/src/mtconnect/source/source.hpp
+++ b/src/mtconnect/source/source.hpp
@@ -87,6 +87,10 @@ namespace mtconnect {
/// @return the asio strand
boost::asio::io_context::strand &getStrand();
+ /// @brief changes the options in the source
+ /// @param[in] options the options to update
+ virtual void setOptions(const ConfigOptions &options) {}
+
protected:
std::string m_name;
boost::asio::io_context::strand m_strand;
diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp
index 4ef4862a..07e48f10 100644
--- a/src/mtconnect/utilities.hpp
+++ b/src/mtconnect/utilities.hpp
@@ -21,9 +21,11 @@
#pragma once
#include
+#include
#include
#include
#include
+#include
#include
#include
@@ -714,15 +716,67 @@ namespace mtconnect {
/// @param s the version
inline int32_t IntSchemaVersion(const std::string &s)
{
- int major, minor;
+ int major {0}, minor {0};
char c;
std::stringstream vstr(s);
vstr >> major >> c >> minor;
- return SCHEMA_VERSION(major, minor);
+ if (major == 0)
+ {
+ return IntDefaultSchemaVersion();
+ }
+ else
+ {
+ return SCHEMA_VERSION(major, minor);
+ }
}
/// @brief Retrieve the best Host IP address from the network interfaces.
/// @param[in] context the boost asio io_context for resolving the address
/// @param[in] onlyV4 only consider IPV4 addresses if `true`
std::string GetBestHostAddress(boost::asio::io_context &context, bool onlyV4 = false);
+
+ /// @brief Function to create a unique id given a sha1 namespace and an id.
+ ///
+ /// Creates a base 64 encoded version of the string and removes any illegal characters
+ /// for an ID. If the first character is not a legal start character, maps the first 2 characters
+ /// to the legal ID start char set.
+ ///
+ /// @param[in] sha the sha1 namespace to use as context
+ /// @param[in] id the id to use transform
+ /// @returns Returns the first 16 characters of the base 64 encoded sha1
+ inline std::string makeUniqueId(const boost::uuids::detail::sha1 &sha, const std::string &id)
+ {
+ using namespace std;
+
+ boost::uuids::detail::sha1 sha1(sha);
+
+ constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
+ constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; };
+ constexpr auto isIDChar = [isIDStartChar](unsigned char c) -> bool {
+ return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-';
+ };
+
+ sha1.process_bytes(id.data(), id.length());
+ unsigned int digest[5];
+ sha1.get_digest(digest);
+
+ string s(32, ' ');
+ auto len = boost::beast::detail::base64::encode(s.data(), digest, sizeof(digest));
+
+ s.erase(len - 1);
+ std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar));
+
+ // Check if the character is legal.
+ if (!isIDStartChar(s[0]))
+ {
+ // Change the start character to a legal character
+ uint32_t c = s[0] + s[1];
+ s.erase(0, 1);
+ s[0] = startc[c % startc.size()];
+ }
+
+ s.erase(16);
+
+ return s;
+ }
} // namespace mtconnect
diff --git a/test/adapter_test.cpp b/test/adapter_test.cpp
index c30b4ea5..1ac8c8e8 100644
--- a/test/adapter_test.cpp
+++ b/test/adapter_test.cpp
@@ -75,3 +75,50 @@ Another Line...
--multiline--ABC---)DOC";
EXPECT_EQ(exp, data);
}
+
+TEST(AdapterTest, should_forward_multiline_command)
+{
+ asio::io_context ioc;
+ asio::io_context::strand strand(ioc);
+ ConfigOptions options {{configuration::Host, "localhost"s}, {configuration::Port, 7878}};
+ boost::property_tree::ptree tree;
+ pipeline::PipelineContextPtr context = make_shared();
+ auto adapter = make_unique(ioc, context, options, tree);
+
+ auto handler = make_unique();
+ string command, value;
+ handler->m_command = [&](const string &c, const string &v, const string &s) {
+ command = c;
+ value = v;
+ };
+ adapter->setHandler(handler);
+
+ adapter->processData("* deviceModel: --multiline--ABC1234");
+ EXPECT_TRUE(adapter->getTerminator());
+ EXPECT_EQ("--multiline--ABC1234", *adapter->getTerminator());
+ adapter->processData("");
+ adapter->processData(" ");
+ adapter->processData("");
+ adapter->processData("--multiline--ABC1234");
+
+ const auto exp = R"DOC(
+
+)DOC";
+ EXPECT_EQ("devicemodel", command);
+ EXPECT_EQ(exp, value);
+}
+
+TEST(AdapterTest, should_set_options_from_commands)
+{
+ asio::io_context ioc;
+ asio::io_context::strand strand(ioc);
+ ConfigOptions options {{configuration::Host, "localhost"s}, {configuration::Port, 7878}};
+ boost::property_tree::ptree tree;
+ pipeline::PipelineContextPtr context = make_shared();
+ auto adapter = make_unique(ioc, context, options, tree);
+
+ adapter->processData("* shdrVersion: 3");
+
+ auto v = GetOption(adapter->getOptions(), "ShdrVersion");
+ ASSERT_EQ(int64_t(3), *v);
+}
diff --git a/test/agent_adapter_test.cpp b/test/agent_adapter_test.cpp
index 386e2787..c5f2a7ae 100644
--- a/test/agent_adapter_test.cpp
+++ b/test/agent_adapter_test.cpp
@@ -121,7 +121,8 @@ class AgentAdapterTest : public testing::Test
}
m_agentTestHelper->getAgent()->stop();
- m_agentTestHelper->m_ioContext.run_for(100s);
+ m_agentTestHelper->m_ioContext.removeGuard();
+ m_agentTestHelper->m_ioContext.run_for(10s);
m_agentTestHelper.reset();
m_adapter.reset();
diff --git a/test/agent_test_helper.hpp b/test/agent_test_helper.hpp
index bb83eac2..2f3291bc 100644
--- a/test/agent_test_helper.hpp
+++ b/test/agent_test_helper.hpp
@@ -127,6 +127,7 @@ class AgentTestHelper
if (m_agent)
m_agent->stop();
m_agent.reset();
+ m_ioContext.stop();
}
auto session() { return m_session; }
@@ -309,7 +310,7 @@ class AgentTestHelper
std::unique_ptr m_agent;
std::stringstream m_out;
mtconnect::sink::rest_sink::RequestPtr m_request;
- boost::asio::io_context m_ioContext;
+ mtconnect::configuration::AsyncContext m_ioContext;
boost::asio::io_context::strand m_strand;
boost::asio::ip::tcp::socket m_socket;
mtconnect::sink::rest_sink::Response m_response;
diff --git a/test/asset_test.cpp b/test/asset_test.cpp
index 82d3aebf..49e383f3 100644
--- a/test/asset_test.cpp
+++ b/test/asset_test.cpp
@@ -74,7 +74,7 @@ TEST_F(AssetTest, TestExtendedAsset)
ErrorList errors;
entity::XmlParser parser;
- auto entity = parser.parse(Asset::getRoot(), doc, "1.7", errors);
+ auto entity = parser.parse(Asset::getRoot(), doc, errors);
ASSERT_EQ(0, errors.size());
auto asset = dynamic_cast(entity.get());
ASSERT_NE(nullptr, asset);
@@ -102,7 +102,7 @@ TEST_F(AssetTest, asset_should_parse_and_load_if_asset_id_is_missing_from_xml)
ErrorList errors;
entity::XmlParser parser;
- auto entity = parser.parse(Asset::getRoot(), doc, "1.7", errors);
+ auto entity = parser.parse(Asset::getRoot(), doc, errors);
ASSERT_EQ(0, errors.size());
auto asset = dynamic_cast(entity.get());
ASSERT_NE(nullptr, asset);
diff --git a/test/component_parameters_test.cpp b/test/component_parameters_test.cpp
index 61fc0c16..5b5c5294 100644
--- a/test/component_parameters_test.cpp
+++ b/test/component_parameters_test.cpp
@@ -80,7 +80,7 @@ TEST_F(ComponentParametersTest, should_parse_simple_parameter_set)
ErrorList errors;
entity::XmlParser parser;
- auto entity = parser.parse(Asset::getRoot(), doc, "2.2", errors);
+ auto entity = parser.parse(Asset::getRoot(), doc, errors);
ASSERT_EQ(0, errors.size());
auto asset = dynamic_cast(entity.get());
@@ -133,7 +133,7 @@ TEST_F(ComponentParametersTest, should_parse_simple_parameter_set)
hdoc.insert(68, string(" hash=\"" + hash1 + "\""));
ASSERT_EQ(content, hdoc);
- auto entity2 = parser.parse(Asset::getRoot(), content, "2.2", errors);
+ auto entity2 = parser.parse(Asset::getRoot(), content, errors);
ASSERT_EQ(hash1, entity2->hash());
@@ -162,7 +162,7 @@ TEST_F(ComponentParametersTest, should_parse_two_parameter_sets)
ErrorList errors;
entity::XmlParser parser;
- auto entity = parser.parse(Asset::getRoot(), doc, "2.2", errors);
+ auto entity = parser.parse(Asset::getRoot(), doc, errors);
ASSERT_EQ(0, errors.size());
auto asset = dynamic_cast(entity.get());
diff --git a/test/config_test.cpp b/test/config_test.cpp
index 00d7b690..86630978 100644
--- a/test/config_test.cpp
+++ b/test/config_test.cpp
@@ -75,8 +75,12 @@ namespace {
fs::path createTempDirectory(const string &ext)
{
fs::path root {fs::path(TEST_BIN_ROOT_DIR) / ("config_test_" + ext)};
- if (!fs::exists(root))
- fs::create_directory(root);
+ if (fs::exists(root))
+ {
+ fs::remove_all(root);
+ }
+
+ fs::create_directory(root);
chdir(root.string().c_str());
m_config->updateWorkingDirectory();
// m_config->setDebug(false);
@@ -993,7 +997,7 @@ Port = 0
ASSERT_TRUE(dataItem);
ASSERT_EQ("SPINDLE_SPEED", dataItem->getType());
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, &devices, agent](boost::system::error_code ec) {
if (ec)
@@ -1012,7 +1016,7 @@ Port = 0
}
});
- boost::asio::steady_timer timer2(context.getContext());
+ boost::asio::steady_timer timer2(context.get());
timer2.expires_from_now(6s);
timer2.async_wait([this, agent, &chg](boost::system::error_code ec) {
if (!ec)
@@ -1072,7 +1076,7 @@ Port = 0
ASSERT_TRUE(dataItem);
ASSERT_EQ("SPINDLE_SPEED", dataItem->getType());
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, &devices](boost::system::error_code ec) {
if (ec)
@@ -1085,7 +1089,7 @@ Port = 0
}
});
- boost::asio::steady_timer timer2(context.getContext());
+ boost::asio::steady_timer timer2(context.get());
timer2.expires_from_now(6s);
timer2.async_wait([this, &chg](boost::system::error_code ec) {
if (!ec)
@@ -1138,7 +1142,7 @@ Port = 0
auto instance = rest->instanceId();
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, &config](boost::system::error_code ec) {
if (ec)
@@ -1154,7 +1158,7 @@ Port = 0
auto th = thread([this, agent, instance, &context]() {
this_thread::sleep_for(5s);
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, agent, instance](boost::system::error_code ec) {
if (!ec)
@@ -1215,7 +1219,7 @@ Port = 0
DataItemPtr di;
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, &devices](boost::system::error_code ec) {
if (ec)
@@ -1229,7 +1233,7 @@ Port = 0
}
});
- boost::asio::steady_timer timer2(context.getContext());
+ boost::asio::steady_timer timer2(context.get());
timer2.expires_from_now(6s);
timer2.async_wait([this](boost::system::error_code ec) {
if (!ec)
@@ -1332,7 +1336,7 @@ Port = 0
ASSERT_NE(nullptr, printer);
ASSERT_EQ("1.2", *printer->getSchemaVersion());
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, &devices, agent](boost::system::error_code ec) {
if (ec)
@@ -1355,7 +1359,7 @@ Port = 0
auto th = thread([this, agent, instance, &context]() {
this_thread::sleep_for(5s);
- boost::asio::steady_timer timer1(context.getContext());
+ boost::asio::steady_timer timer1(context.get());
timer1.expires_from_now(1s);
timer1.async_wait([this, agent, instance](boost::system::error_code ec) {
if (!ec)
@@ -1386,4 +1390,423 @@ Port = 0
th.join();
}
+ TEST_F(ConfigTest, should_add_a_new_device_when_deviceModel_received_from_adapter)
+ {
+ using namespace mtconnect::source::adapter;
+
+ fs::path root {createTempDirectory("6")};
+
+ fs::path devices(root / "Devices.xml");
+ fs::path config {root / "agent.cfg"};
+ {
+ ofstream cfg(config.string());
+ cfg << R"DOC(
+VersionDeviceXmlUpdates = true
+Port = 0
+
+Adapters {
+ Device {
+ }
+}
+)DOC";
+ cfg << "Devices = " << devices << endl;
+ }
+
+ copyFile("empty.xml", devices, 0min);
+
+ boost::program_options::variables_map options;
+ boost::program_options::variable_value value(boost::optional(config.string()), false);
+ options.insert(make_pair("config-file"s, value));
+
+ m_config->initialize(options);
+ auto &asyncContext = m_config->getAsyncContext();
+
+ auto agent = m_config->getAgent();
+ const auto &printer = agent->getPrinter("xml");
+ ASSERT_NE(nullptr, printer);
+
+ auto sp = agent->findSource("_localhost_7878");
+ ASSERT_TRUE(sp);
+
+ auto adapter = dynamic_pointer_cast(sp);
+ ASSERT_TRUE(adapter);
+
+ auto validate = [&](boost::system::error_code ec) {
+ using namespace std::filesystem;
+ using namespace std::chrono;
+ using namespace boost::algorithm;
+
+ if (!ec)
+ {
+ // Check for backup file
+ auto ext = date::format(".%Y-%m-%dT%H+", date::floor(system_clock::now()));
+ auto dit = directory_iterator(".");
+ std::list entries;
+ copy_if(dit, end(dit), back_inserter(entries),
+ [&ext](const auto &de) { return contains(de.path().string(), ext); });
+ ASSERT_EQ(1, entries.size());
+
+ auto device = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device) << "Cannot find LinuxCNC device";
+
+ const auto &components = device->getChildren();
+ ASSERT_EQ(1, components->size());
+
+ auto cont = device->getComponentById("cont");
+ ASSERT_TRUE(cont) << "Cannot find Component with id cont";
+
+ auto exec = device->getDeviceDataItem("exec");
+ ASSERT_TRUE(exec) << "Cannot find DataItem with id exec";
+
+ auto pipeline = dynamic_cast(adapter->getPipeline());
+ ASSERT_EQ("LinuxCNC", pipeline->getDevice());
+ }
+ m_config->stop();
+ };
+
+ boost::asio::steady_timer timer2(asyncContext.get());
+
+ auto send = [this, &adapter, &timer2, validate](boost::system::error_code ec) {
+ if (ec)
+ {
+ m_config->stop();
+ }
+ else
+ {
+ adapter->processData("* deviceModel: --multiline--AAAAA");
+ adapter->processData(R"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)");
+ adapter->processData("--multiline--AAAAA");
+
+ timer2.expires_from_now(500ms);
+ timer2.async_wait(validate);
+ }
+ };
+
+ boost::asio::steady_timer timer1(asyncContext.get());
+ timer1.expires_from_now(100ms);
+ timer1.async_wait(send);
+
+ m_config->start();
+ }
+
+ TEST_F(ConfigTest, should_update_a_device_when_received_from_adapter)
+ {
+ using namespace mtconnect::source::adapter;
+
+ fs::path root {createTempDirectory("7")};
+
+ fs::path devices(root / "Devices.xml");
+ fs::path config {root / "agent.cfg"};
+ {
+ ofstream cfg(config.string());
+ cfg << R"DOC(
+VersionDeviceXmlUpdates = true
+CreateUniqueIds = true
+Port = 0
+)DOC";
+ cfg << "Devices = " << devices << endl;
+ }
+
+ copyFile("dyn_load.xml", devices, 0min);
+
+ boost::program_options::variables_map options;
+ boost::program_options::variable_value value(boost::optional(config.string()), false);
+ options.insert(make_pair("config-file"s, value));
+
+ m_config->initialize(options);
+ auto &asyncContext = m_config->getAsyncContext();
+
+ auto agent = m_config->getAgent();
+ auto device = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device);
+
+ const auto &printer = agent->getPrinter("xml");
+ ASSERT_NE(nullptr, printer);
+
+ auto sp = agent->findSource("_localhost_7878");
+ ASSERT_TRUE(sp);
+
+ auto adapter = dynamic_pointer_cast(sp);
+ ASSERT_TRUE(adapter);
+
+ auto validate = [&](boost::system::error_code ec) {
+ using namespace std::filesystem;
+ using namespace std::chrono;
+ using namespace boost::algorithm;
+
+ if (!ec)
+ {
+ // Check for backup file
+ auto ext = date::format(".%Y-%m-%dT%H+", date::floor(system_clock::now()));
+ auto dit = directory_iterator(".");
+ std::list entries;
+ copy_if(dit, end(dit), back_inserter(entries),
+ [&ext](const auto &de) { return contains(de.path().string(), ext); });
+ ASSERT_EQ(2, entries.size());
+
+ auto device = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device) << "Cannot find LinuxCNC device";
+
+ const auto &components = device->getChildren();
+ ASSERT_EQ(1, components->size());
+
+ auto cont = device->getComponentById("cont");
+ ASSERT_TRUE(cont) << "Cannot find Component with id cont";
+
+ auto devDIs = device->getDataItems();
+ ASSERT_TRUE(devDIs);
+ ASSERT_EQ(5, devDIs->size());
+
+ auto dataItems = cont->getDataItems();
+ ASSERT_TRUE(dataItems);
+ ASSERT_EQ(2, dataItems->size());
+
+ auto it = dataItems->begin();
+ ASSERT_EQ("exc", (*it)->get("originalId"));
+ it++;
+ ASSERT_EQ("mode", (*it)->get("originalId"));
+
+ auto estop = device->getDeviceDataItem("estop");
+ ASSERT_TRUE(estop) << "Cannot find DataItem with id estop";
+
+ auto exec = device->getDeviceDataItem("exc");
+ ASSERT_TRUE(exec) << "Cannot find DataItem with id exc";
+
+ auto pipeline = dynamic_cast(adapter->getPipeline());
+ ASSERT_EQ("LinuxCNC", pipeline->getDevice());
+ }
+ m_config->stop();
+ };
+
+ boost::asio::steady_timer timer2(asyncContext.get());
+
+ auto send = [this, &adapter, &timer2, validate](boost::system::error_code ec) {
+ if (ec)
+ {
+ m_config->stop();
+ }
+ else
+ {
+ adapter->processData("* deviceModel: --multiline--AAAAA");
+ adapter->processData(R"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)");
+ adapter->processData("--multiline--AAAAA");
+
+ timer2.expires_from_now(500ms);
+ timer2.async_wait(validate);
+ }
+ };
+
+ boost::asio::steady_timer timer1(asyncContext.get());
+ timer1.expires_from_now(100ms);
+ timer1.async_wait(send);
+
+ m_config->start();
+ }
+
+ TEST_F(ConfigTest, should_update_the_ids_of_all_entities)
+ {
+ fs::path root {createTempDirectory("8")};
+
+ fs::path devices(root / "Devices.xml");
+ fs::path config {root / "agent.cfg"};
+ {
+ ofstream cfg(config.string());
+ cfg << R"DOC(
+VersionDeviceXmlUpdates = true
+CreateUniqueIds = true
+Port = 0
+)DOC";
+ cfg << "Devices = " << devices << endl;
+ }
+
+ copyFile("dyn_load.xml", devices, 0min);
+
+ boost::program_options::variables_map options;
+ boost::program_options::variable_value value(boost::optional(config.string()), false);
+ options.insert(make_pair("config-file"s, value));
+
+ m_config->initialize(options);
+
+ auto agent = m_config->getAgent();
+ auto device = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device);
+
+ auto deviceId = device->getId();
+
+ ASSERT_NE("d", deviceId);
+ ASSERT_EQ("d", device->get("originalId"));
+
+ // Get the data item by its old id
+ auto exec = device->getDeviceDataItem("exec");
+ ASSERT_TRUE(exec);
+ ASSERT_TRUE(exec->getOriginalId());
+ ASSERT_EQ("exec", *exec->getOriginalId());
+
+ // Re-initialize the agent with the modified device.xml with the unique ids aready created
+ // This tests if the originalId in the device xml file does the ritght thing when mapping ids
+ m_config = std::make_unique();
+ m_config->setDebug(true);
+ m_config->initialize(options);
+
+ auto agent2 = m_config->getAgent();
+
+ auto device2 = agent2->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device2);
+ ASSERT_EQ(deviceId, device2->getId());
+
+ auto exec2 = device->getDeviceDataItem("exec");
+ ASSERT_TRUE(exec2);
+ ASSERT_EQ(exec->getId(), exec2->getId());
+ ASSERT_TRUE(exec2->getOriginalId());
+ ASSERT_EQ("exec", *exec2->getOriginalId());
+ }
+
+ TEST_F(ConfigTest, should_add_a_new_device_with_duplicate_ids)
+ {
+ using namespace mtconnect::source::adapter;
+
+ fs::path root {createTempDirectory("9")};
+
+ fs::path devices(root / "Devices.xml");
+ fs::path config {root / "agent.cfg"};
+ {
+ ofstream cfg(config.string());
+ cfg << R"DOC(
+VersionDeviceXmlUpdates = true
+CreateUniqueIds = true
+Port = 0
+)DOC";
+ cfg << "Devices = " << devices << endl;
+ }
+
+ copyFile("dyn_load.xml", devices, 0min);
+
+ boost::program_options::variables_map options;
+ boost::program_options::variable_value value(boost::optional(config.string()), false);
+ options.insert(make_pair("config-file"s, value));
+
+ m_config->initialize(options);
+ auto &asyncContext = m_config->getAsyncContext();
+
+ auto agent = m_config->getAgent();
+ auto device = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device);
+
+ const auto &printer = agent->getPrinter("xml");
+ ASSERT_NE(nullptr, printer);
+
+ auto sp = agent->findSource("_localhost_7878");
+ ASSERT_TRUE(sp);
+
+ auto adapter = dynamic_pointer_cast(sp);
+ ASSERT_TRUE(adapter);
+
+ auto validate = [&](boost::system::error_code ec) {
+ using namespace std::filesystem;
+ using namespace std::chrono;
+ using namespace boost::algorithm;
+
+ if (!ec)
+ {
+ // Check for backup file
+ auto ext = date::format(".%Y-%m-%dT%H+", date::floor(system_clock::now()));
+ auto dit = directory_iterator(".");
+ std::list entries;
+ copy_if(dit, end(dit), back_inserter(entries),
+ [&ext](const auto &de) { return contains(de.path().string(), ext); });
+ ASSERT_EQ(2, entries.size());
+
+ ASSERT_EQ(3, agent->getDevices().size());
+
+ auto device1 = agent->getDeviceByName("LinuxCNC");
+ ASSERT_TRUE(device1) << "Cannot find LinuxCNC device";
+
+ auto device2 = agent->getDeviceByName("AnotherCNC");
+ ASSERT_TRUE(device2) << "Cannot find LinuxCNC device";
+
+ auto pipeline = dynamic_cast(adapter->getPipeline());
+ ASSERT_EQ("AnotherCNC", pipeline->getDevice());
+ }
+ m_config->stop();
+ };
+
+ boost::asio::steady_timer timer2(asyncContext.get());
+
+ auto send = [this, &adapter, &timer2, validate](boost::system::error_code ec) {
+ if (ec)
+ {
+ m_config->stop();
+ }
+ else
+ {
+ auto pipeline = dynamic_cast(adapter->getPipeline());
+ ASSERT_EQ("LinuxCNC", pipeline->getDevice());
+
+ adapter->processData("* deviceModel: --multiline--AAAAA");
+ adapter->processData(R"(
+
+
+
+
+
+
+
+
+
+