From db486a3ceac251063df66dd5185b92fbdb101528 Mon Sep 17 00:00:00 2001 From: openDAQ-dev Date: Mon, 16 Oct 2023 18:06:51 +0200 Subject: [PATCH 001/127] Initial commit --- external/streaming_protocol/CMakeLists.txt | 9 + .../CMakeLists.txt | 9 + .../common.h | 21 + .../module_dll.h | 20 + .../websocket_streaming_client_module_impl.h | 47 ++ .../src/CMakeLists.txt | 43 ++ .../src/module_dll.cpp | 8 + ...websocket_streaming_client_module_impl.cpp | 148 ++++++ .../tests/CMakeLists.txt | 22 + .../tests/test_app.cpp | 21 + ...test_websocket_streaming_client_module.cpp | 259 ++++++++++ .../CMakeLists.txt | 9 + .../common.h | 21 + .../module_dll.h | 20 + .../websocket_streaming_server_impl.h | 48 ++ .../websocket_streaming_server_module_impl.h | 35 ++ .../src/CMakeLists.txt | 48 ++ .../src/module_dll.cpp | 9 + .../src/websocket_streaming_server_impl.cpp | 66 +++ ...websocket_streaming_server_module_impl.cpp | 41 ++ .../tests/CMakeLists.txt | 28 ++ .../tests/test_app.cpp | 22 + ...test_websocket_streaming_server_module.cpp | 142 ++++++ .../websocket_streaming/CMakeLists.txt | 9 + .../websocket_streaming/async_packet_reader.h | 51 ++ .../websocket_streaming/input_signal.h | 50 ++ .../websocket_streaming/output_signal.h | 64 +++ .../signal_descriptor_converter.h | 53 ++ .../include/websocket_streaming/signal_info.h | 38 ++ .../websocket_streaming/streaming_client.h | 108 +++++ .../websocket_streaming/streaming_server.h | 74 +++ .../websocket_client_device_factory.h | 32 ++ .../websocket_client_device_impl.h | 51 ++ .../websocket_client_signal_factory.h | 35 ++ .../websocket_client_signal_impl.h | 51 ++ .../websocket_streaming/websocket_streaming.h | 57 +++ .../websocket_streaming_factory.h | 38 ++ .../websocket_streaming_impl.h | 53 ++ .../websocket_streaming_init.h | 20 + .../websocket_streaming_server.h | 48 ++ .../websocket_streaming/src/CMakeLists.txt | 87 ++++ .../src/async_packet_reader.cpp | 78 +++ .../websocket_streaming/src/input_signal.cpp | 68 +++ .../websocket_streaming/src/output_signal.cpp | 229 +++++++++ .../src/signal_descriptor_converter.cpp | 451 ++++++++++++++++++ .../src/streaming_client.cpp | 297 ++++++++++++ .../src/streaming_server.cpp | 133 ++++++ .../src/websocket_client_device_impl.cpp | 151 ++++++ .../src/websocket_client_signal_impl.cpp | 93 ++++ .../src/websocket_streaming_impl.cpp | 145 ++++++ .../src/websocket_streaming_init.cpp | 13 + .../src/websocket_streaming_server.cpp | 74 +++ .../websocket_streaming/tests/CMakeLists.txt | 30 ++ .../tests/streaming_test_helpers.h | 113 +++++ .../test_signal_descriptor_converter.cpp | 346 ++++++++++++++ .../tests/test_signal_generator.cpp | 153 ++++++ .../tests/test_streaming.cpp | 138 ++++++ .../tests/test_websocket_client_device.cpp | 166 +++++++ 58 files changed, 4693 insertions(+) create mode 100644 external/streaming_protocol/CMakeLists.txt create mode 100644 modules/websocket_streaming_client_module/CMakeLists.txt create mode 100644 modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h create mode 100644 modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h create mode 100644 modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h create mode 100644 modules/websocket_streaming_client_module/src/CMakeLists.txt create mode 100644 modules/websocket_streaming_client_module/src/module_dll.cpp create mode 100644 modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp create mode 100644 modules/websocket_streaming_client_module/tests/CMakeLists.txt create mode 100644 modules/websocket_streaming_client_module/tests/test_app.cpp create mode 100644 modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp create mode 100644 modules/websocket_streaming_server_module/CMakeLists.txt create mode 100644 modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h create mode 100644 modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h create mode 100644 modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h create mode 100644 modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h create mode 100644 modules/websocket_streaming_server_module/src/CMakeLists.txt create mode 100644 modules/websocket_streaming_server_module/src/module_dll.cpp create mode 100644 modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp create mode 100644 modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp create mode 100644 modules/websocket_streaming_server_module/tests/CMakeLists.txt create mode 100644 modules/websocket_streaming_server_module/tests/test_app.cpp create mode 100644 modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp create mode 100644 shared/libraries/websocket_streaming/CMakeLists.txt create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h create mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h create mode 100644 shared/libraries/websocket_streaming/src/CMakeLists.txt create mode 100644 shared/libraries/websocket_streaming/src/async_packet_reader.cpp create mode 100644 shared/libraries/websocket_streaming/src/input_signal.cpp create mode 100644 shared/libraries/websocket_streaming/src/output_signal.cpp create mode 100644 shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp create mode 100644 shared/libraries/websocket_streaming/src/streaming_client.cpp create mode 100644 shared/libraries/websocket_streaming/src/streaming_server.cpp create mode 100644 shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp create mode 100644 shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp create mode 100644 shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp create mode 100644 shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp create mode 100644 shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp create mode 100644 shared/libraries/websocket_streaming/tests/CMakeLists.txt create mode 100644 shared/libraries/websocket_streaming/tests/streaming_test_helpers.h create mode 100644 shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp create mode 100644 shared/libraries/websocket_streaming/tests/test_signal_generator.cpp create mode 100644 shared/libraries/websocket_streaming/tests/test_streaming.cpp create mode 100644 shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt new file mode 100644 index 0000000..e91bc16 --- /dev/null +++ b/external/streaming_protocol/CMakeLists.txt @@ -0,0 +1,9 @@ +set(STREAMING_PROTOCOL_ALWAYS_FETCH_DEPS ON CACHE BOOL "" FORCE) + +opendaq_dependency( + NAME streaming_protocol + REQUIRED_VERSION 0.10.7 + GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git + GIT_REF v0.10.7 + EXPECT_TARGET daq::streaming_protocol +) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt new file mode 100644 index 0000000..60727f6 --- /dev/null +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.2) +set_cmake_folder_context(TARGET_FOLDER_NAME) +project(WebsocketStreamingClientModule VERSION 2.0.0 LANGUAGES C CXX) + +add_subdirectory(src) + +if (OPENDAQ_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h new file mode 100644 index 0000000..cff630b --- /dev/null +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h @@ -0,0 +1,21 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#define BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE BEGIN_NAMESPACE_OPENDAQ_MODULE(websocket_streaming_client_module) +#define END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE END_NAMESPACE_OPENDAQ_MODULE diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h new file mode 100644 index 0000000..42b8a08 --- /dev/null +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h @@ -0,0 +1,20 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +DECLARE_MODULE_EXPORTS(WebsocketStreamingClientModule) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h new file mode 100644 index 0000000..bea814b --- /dev/null +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE + +class WebsocketStreamingClientModule final : public Module +{ +public: + WebsocketStreamingClientModule(ContextPtr context); + + ListPtr onGetAvailableDevices() override; + DictPtr onGetAvailableDeviceTypes() override; + DevicePtr onCreateDevice(const StringPtr& connectionString, + const ComponentPtr& parent, + const PropertyObjectPtr& config) override; + bool onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; + bool onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const StreamingInfoPtr& config) override; + StreamingPtr onCreateStreaming(const StringPtr& connectionString, const StreamingInfoPtr& config) override; + +private: + static StringPtr tryCreateWebsocketConnectionString(const StreamingInfoPtr& config); + static DeviceTypePtr createDeviceType(); + + std::mutex sync; + size_t deviceIndex; + discovery::DiscoveryClient discoveryClient; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_client_module/src/CMakeLists.txt b/modules/websocket_streaming_client_module/src/CMakeLists.txt new file mode 100644 index 0000000..2f21ce1 --- /dev/null +++ b/modules/websocket_streaming_client_module/src/CMakeLists.txt @@ -0,0 +1,43 @@ +# Windows NSIS package manager limits lenght of variables +# "openDAQ_[MODULE_NAME]_[SUFFIX]" with 60 characters max +# The suffix with max lenght is [Development_was_installed] +# so the shorter module name [ws_stream_cl_module] should be used instead of full name: +# [websocket_streaming_client_module] +set(LIB_NAME ws_stream_cl_module) +set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME}) + +set(SRC_Include common.h + module_dll.h + websocket_streaming_client_module_impl.h +) + +set(SRC_Srcs module_dll.cpp + websocket_streaming_client_module_impl.cpp +) + +prepend_include(${TARGET_FOLDER_NAME} SRC_Include) + +source_group("module" FILES ${MODULE_HEADERS_DIR}/websocket_streaming_client_module_impl.h + ${MODULE_HEADERS_DIR}/module_dll.h + module_dll.cpp + websocket_streaming_client_module_impl.cpp +) + + +add_library(${LIB_NAME} SHARED ${SRC_Include} + ${SRC_Srcs} +) +add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) + +target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq + PRIVATE daq::discovery + daq::opendaq_websocket_streaming +) + +target_include_directories(${LIB_NAME} PUBLIC $ + $ + $ +) + +opendaq_set_module_properties(${LIB_NAME} ${PROJECT_VERSION_MAJOR}) +create_version_header(${LIB_NAME}) diff --git a/modules/websocket_streaming_client_module/src/module_dll.cpp b/modules/websocket_streaming_client_module/src/module_dll.cpp new file mode 100644 index 0000000..26f176a --- /dev/null +++ b/modules/websocket_streaming_client_module/src/module_dll.cpp @@ -0,0 +1,8 @@ +#include +#include + +#include + +using namespace daq::modules::websocket_streaming_client_module; + +DEFINE_MODULE_EXPORTS(WebsocketStreamingClientModule) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp new file mode 100644 index 0000000..e544d06 --- /dev/null +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE + +static const char* WebsocketDeviceTypeId = "daq.ws"; +static const char* WebsocketDevicePrefix = "daq.ws://"; +static const char* WebsocketStreamingPrefix = "daq.wss://"; +static const char* WebsocketStreamingID = "daq.wss"; + +using namespace discovery; +using namespace daq::websocket_streaming; + +WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr context) + : Module("openDAQ websocket client module", + daq::VersionInfo(WS_STREAM_CL_MODULE_MAJOR_VERSION, WS_STREAM_CL_MODULE_MINOR_VERSION, WS_STREAM_CL_MODULE_PATCH_VERSION), + std::move(context)) + , deviceIndex(0) + , discoveryClient([](MdnsDiscoveredDevice discoveredDevice) + { + return fmt::format("daq.ws://{}:{}{}", + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/")); + }, + {"WS"}) +{ + discoveryClient.initMdnsClient("_streaming-ws._tcp.local."); +} + +ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() +{ + auto availableDevices = discoveryClient.discoverDevices(); + for (auto device : availableDevices) + { + device.asPtr().setDeviceType(createDeviceType()); + } + return availableDevices; +} + +DictPtr WebsocketStreamingClientModule::onGetAvailableDeviceTypes() +{ + auto result = Dict(); + + auto deviceType = createDeviceType(); + result.set(deviceType.getId(), deviceType); + + return result; +} + +DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connectionString, + const ComponentPtr& parent, + const PropertyObjectPtr& config) +{ + if (!connectionString.assigned()) + throw ArgumentNullException(); + + if (!onAcceptsConnectionParameters(connectionString, config)) + throw InvalidParameterException(); + + if (!context.assigned()) + throw InvalidParameterException{"Context is not available."}; + + // We don't create any streaming objects here since the + // internal streaming object is always created within the device + + std::scoped_lock lock(sync); + + std::string localId = fmt::format("websocket_pseudo_device{}", deviceIndex++); + return WebsocketClientDevice(context, parent, localId, connectionString); +} + +bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& /*config*/) +{ + std::string connStr = connectionString; + auto found = connStr.find(WebsocketDevicePrefix); + return (found == 0); +} + +bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const StreamingInfoPtr& config) +{ + if (connectionString.assigned()) + { + std::string connStr = connectionString; + auto found = connStr.find(WebsocketStreamingPrefix); + return (found == 0); + } + else if (config.assigned()) + { + if (config.getProtocolId() == WebsocketStreamingID) + { + try + { + auto generatedConnectionString = tryCreateWebsocketConnectionString(config); + return true; + } + catch (const std::exception& e) + { + LOG_W("Failed to interpret streaming info config: {}", e.what()) + } + } + } + return false; +} + +StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& connectionString, const StreamingInfoPtr& config) +{ + StringPtr streamingConnectionString = connectionString; + + if (!streamingConnectionString.assigned() && !config.assigned()) + throw ArgumentNullException(); + + if (!onAcceptsStreamingConnectionParameters(streamingConnectionString, config)) + throw InvalidParameterException(); + + if (!streamingConnectionString.assigned()) + streamingConnectionString = tryCreateWebsocketConnectionString(config); + + return WebsocketStreaming(streamingConnectionString, context); +} + +StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(const StreamingInfoPtr &config) +{ + auto address = config.getPrimaryAddress(); + if (address.toStdString().empty()) + throw InvalidParameterException("Device address is not set"); + + const auto propertyObj = config.asPtr(); + auto port = propertyObj.getPropertyValue("Port").template asPtr(); + + auto connectionString = String(fmt::format("daq.wss://{}:{}", address, port)); + + return connectionString; +} + +DeviceTypePtr WebsocketStreamingClientModule::createDeviceType() +{ + return DeviceType(WebsocketDeviceTypeId, + "Websocket enabled device", + "Pseudo device, provides only signals of the remote device as flat list"); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_client_module/tests/CMakeLists.txt b/modules/websocket_streaming_client_module/tests/CMakeLists.txt new file mode 100644 index 0000000..89d9447 --- /dev/null +++ b/modules/websocket_streaming_client_module/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +set(MODULE_NAME ws_stream_cl_module) +set(TEST_APP test_${MODULE_NAME}) + +set(TEST_SOURCES test_websocket_streaming_client_module.cpp + test_app.cpp +) + +add_executable(${TEST_APP} ${TEST_SOURCES} +) + +target_link_libraries(${TEST_APP} PRIVATE daq::test_utils + ${SDK_TARGET_NAMESPACE}::${MODULE_NAME} +) + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY bin +) + +if (OPENDAQ_ENABLE_COVERAGE) + setup_target_for_coverage(${TEST_APP}coverage ${TEST_APP} ${TEST_APP}coverage) +endif() diff --git a/modules/websocket_streaming_client_module/tests/test_app.cpp b/modules/websocket_streaming_client_module/tests/test_app.cpp new file mode 100644 index 0000000..f7351c2 --- /dev/null +++ b/modules/websocket_streaming_client_module/tests/test_app.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include +#include +#include + +int main(int argc, char** args) +{ + daq::daqInitializeCoreObjectsTesting(); + daqInitModuleManagerLibrary(); + + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DaqMemCheckListener()); + + auto res = RUN_ALL_TESTS(); + + return res; +} diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp new file mode 100644 index 0000000..c66607c --- /dev/null +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using WebsocketStreamingClientModuleTest = testing::Test; +using namespace daq; + +static ModulePtr CreateModule() +{ + ModulePtr module; + createModule(&module, NullContext()); + return module; +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateModule) +{ + IModule* module = nullptr; + ErrCode errCode = createModule(&module, NullContext()); + ASSERT_TRUE(OPENDAQ_SUCCEEDED(errCode)); + + ASSERT_NE(module, nullptr); + module->releaseRef(); +} + +TEST_F(WebsocketStreamingClientModuleTest, ModuleName) +{ + auto module = CreateModule(); + ASSERT_EQ(module.getName(), "openDAQ websocket client module"); +} + +TEST_F(WebsocketStreamingClientModuleTest, VersionAvailable) +{ + auto module = CreateModule(); + ASSERT_TRUE(module.getVersionInfo().assigned()); +} + +TEST_F(WebsocketStreamingClientModuleTest, VersionCorrect) +{ + auto module = CreateModule(); + auto version = module.getVersionInfo(); + + ASSERT_EQ(version.getMajor(), WS_STREAM_CL_MODULE_MAJOR_VERSION); + ASSERT_EQ(version.getMinor(), WS_STREAM_CL_MODULE_MINOR_VERSION); + ASSERT_EQ(version.getPatch(), WS_STREAM_CL_MODULE_PATCH_VERSION); +} + +TEST_F(WebsocketStreamingClientModuleTest, EnumerateDevices) +{ + auto module = CreateModule(); + + ListPtr deviceInfo; + ASSERT_NO_THROW(deviceInfo = module.getAvailableDevices()); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringNull) +{ + auto module = CreateModule(); + ASSERT_THROW(module.acceptsConnectionParameters(nullptr), ArgumentNullException); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringEmpty) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringInvalid) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("drfrfgt")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringWrongPrefix) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("daq.opcua://device8")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringCorrect) +{ + auto module = CreateModule(); + + ASSERT_TRUE(module.acceptsConnectionParameters("daq.ws://device8")); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringNull) +{ + auto module = CreateModule(); + + DevicePtr device; + ASSERT_THROW(device = module.createDevice(nullptr, nullptr), ArgumentNullException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringEmpty) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createDevice("", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringInvalid) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createDevice("fdfdfdfdde", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringInvalidId) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createDevice("daqref://devicett3axxr1", nullptr), InvalidParameterException); + ASSERT_THROW(module.createDevice("daq.opcua://devicett3axxr1", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionFailed) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createDevice("daq.ws://127.0.0.1", nullptr), NotFoundException); +} + + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringNull) +{ + auto module = CreateModule(); + ASSERT_THROW(module.acceptsStreamingConnectionParameters(nullptr), ArgumentNullException); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringEmpty) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringInvalid) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("drfrfgt")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringWrongPrefix) +{ + auto module = CreateModule(); + + bool accepts = true; + ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("daq.opcua://device8")); + ASSERT_FALSE(accepts); +} + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorrect) +{ + auto module = CreateModule(); + + ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.wss://device8")); +} + + +TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) +{ + auto module = CreateModule(); + + StreamingInfoConfigPtr streamingInfoConfig = StreamingInfo("daq.wss"); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); + + streamingInfoConfig.setPrimaryAddress("123.123.123.123"); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); + + streamingInfoConfig.addProperty(IntProperty("Port", 1234)); + ASSERT_TRUE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) +{ + auto module = CreateModule(); + + DevicePtr device; + ASSERT_THROW(device = module.createStreaming(nullptr, nullptr), ArgumentNullException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithConnectionStringEmpty) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createStreaming("", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingConnectionStringInvalid) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createStreaming("fdfdfdfdde", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingConnectionStringInvalidId) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createStreaming("daqref://devicett3axxr1", nullptr), InvalidParameterException); + ASSERT_THROW(module.createStreaming("daq.opcua://devicett3axxr1", nullptr), InvalidParameterException); + ASSERT_THROW(module.createStreaming("daq.ws://devicett3axxr1", nullptr), InvalidParameterException); +} + +TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) +{ + const auto module = CreateModule(); + + DictPtr functionBlockTypes; + ASSERT_NO_THROW(functionBlockTypes = module.getAvailableFunctionBlockTypes()); + ASSERT_EQ(functionBlockTypes.getCount(), 0u); + + DictPtr deviceTypes; + ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); + ASSERT_EQ(deviceTypes.getCount(), 1u); + ASSERT_TRUE(deviceTypes.hasKey("daq.ws")); + ASSERT_EQ(deviceTypes.get("daq.ws").getId(), "daq.ws"); + + DictPtr serverTypes; + ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); + ASSERT_EQ(serverTypes.getCount(), 0u); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateFunctionBlockIdNull) +{ + auto module = CreateModule(); + + FunctionBlockPtr functionBlock; + ASSERT_THROW(functionBlock = module.createFunctionBlock(nullptr, nullptr, "fb"), ArgumentNullException); +} + +TEST_F(WebsocketStreamingClientModuleTest, CreateFunctionBlockIdEmpty) +{ + auto module = CreateModule(); + + ASSERT_THROW(module.createFunctionBlock("", nullptr, "fb"), NotFoundException); +} diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt new file mode 100644 index 0000000..4c2113e --- /dev/null +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.2) +set_cmake_folder_context(TARGET_FOLDER_NAME) +project(WebsocketStreamingServerModule VERSION 2.0.0 LANGUAGES CXX) + +add_subdirectory(src) + +if (OPENDAQ_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h new file mode 100644 index 0000000..0a7659d --- /dev/null +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h @@ -0,0 +1,21 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#define BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE BEGIN_NAMESPACE_OPENDAQ_MODULE(websocket_streaming_server_module) +#define END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE END_NAMESPACE_OPENDAQ_MODULE diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h new file mode 100644 index 0000000..d1b4c6e --- /dev/null +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h @@ -0,0 +1,20 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +DECLARE_MODULE_EXPORTS(WebsocketStreamingServerModule) diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h new file mode 100644 index 0000000..2ae0cac --- /dev/null +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE + +class WebsocketStreamingServerImpl : public daq::Server +{ +public: + explicit WebsocketStreamingServerImpl(daq::DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context); + static PropertyObjectPtr createDefaultConfig(); + static ServerTypePtr createType(); + +protected: + void onStopServer() override; + + daq::websocket_streaming::WebsocketStreamingServer websocketStreamingServer; + PropertyObjectPtr config; +}; + +OPENDAQ_DECLARE_CLASS_FACTORY_WITH_INTERFACE( + INTERNAL_FACTORY, WebsocketStreamingServer, daq::IServer, + DevicePtr, rootDevice, + PropertyObjectPtr, config, + const ContextPtr&, context +) + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h new file mode 100644 index 0000000..8593b32 --- /dev/null +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h @@ -0,0 +1,35 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE + +class WebsocketStreamingServerModule final : public Module +{ +public: + WebsocketStreamingServerModule(ContextPtr context); + + DictPtr onGetAvailableServerTypes() override; + ServerPtr onCreateServer(StringPtr serverType, PropertyObjectPtr serverConfig, DevicePtr rootDevice) override; + +private: + std::mutex sync; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/src/CMakeLists.txt b/modules/websocket_streaming_server_module/src/CMakeLists.txt new file mode 100644 index 0000000..bd34790 --- /dev/null +++ b/modules/websocket_streaming_server_module/src/CMakeLists.txt @@ -0,0 +1,48 @@ +# Windows NSIS package manager limits lenght of variables +# "openDAQ_[MODULE_NAME]_[SUFFIX]" with 60 characters max +# The suffix with max lenght is [Development_was_installed] +# so the shorter module name [ws_stream_srv_module] should be used instead of full name: +# [ws_stream_srv_module] +set(LIB_NAME ws_stream_srv_module) +set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME}) + +set(SRC_Include common.h + module_dll.h + websocket_streaming_server_module_impl.h + websocket_streaming_server_impl.h +) + +set(SRC_Srcs module_dll.cpp + websocket_streaming_server_module_impl.cpp + websocket_streaming_server_impl.cpp +) + +prepend_include(${TARGET_FOLDER_NAME} SRC_Include) + +source_group("module" FILES ${MODULE_HEADERS_DIR}/websocket_streaming_server_module_impl.h + ${MODULE_HEADERS_DIR}/websocket_streaming_server_impl.h + ${MODULE_HEADERS_DIR}/module_dll.h + module_dll.cpp + websocket_streaming_server_module_impl.cpp + streaming_server_impl.cpp +) + + +add_library(${LIB_NAME} SHARED ${SRC_Include} + ${SRC_Srcs} +) + +add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) + +target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq + PRIVATE daq::opendaq_websocket_streaming +) + +target_include_directories(${LIB_NAME} PUBLIC $ + $ + $ +) + +opendaq_set_module_properties(${LIB_NAME} ${PROJECT_VERSION_MAJOR}) +create_version_header(${LIB_NAME}) + diff --git a/modules/websocket_streaming_server_module/src/module_dll.cpp b/modules/websocket_streaming_server_module/src/module_dll.cpp new file mode 100644 index 0000000..86c098b --- /dev/null +++ b/modules/websocket_streaming_server_module/src/module_dll.cpp @@ -0,0 +1,9 @@ +#include +#include + +#include + +using namespace daq::modules::websocket_streaming_server_module; + +DEFINE_MODULE_EXPORTS(WebsocketStreamingServerModule) + diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp new file mode 100644 index 0000000..862c988 --- /dev/null +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE + +using namespace daq; + +WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context) + : Server(nullptr, rootDevice, nullptr, nullptr) + , websocketStreamingServer(rootDevice, context) + , config(config) +{ + const uint16_t port = config.getPropertyValue("WebsocketStreamingPort"); + + websocketStreamingServer.setStreamingPort(port); + websocketStreamingServer.start(); +} + +PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig() +{ + constexpr Int minPortValue = 0; + constexpr Int maxPortValue = 65535; + + auto defaultConfig = PropertyObject(); + + const auto websocketPortProp = + IntPropertyBuilder("WebsocketStreamingPort", 7414).setMinValue(minPortValue).setMaxValue(maxPortValue).build(); + + defaultConfig.addProperty(websocketPortProp); + + return defaultConfig; +} + +ServerTypePtr WebsocketStreamingServerImpl::createType() +{ + auto configurationCallback = [](IBaseObject* input, IBaseObject** output) -> ErrCode + { + PropertyObjectPtr propObjPtr; + ErrCode errCode = wrapHandlerReturn(&WebsocketStreamingServerImpl::createDefaultConfig, propObjPtr); + *output = propObjPtr.detach(); + return errCode; + }; + + return ServerType( + "openDAQ WebsocketTcp Streaming", + "openDAQ WebsocketTcp Streaming server", + "Publishes device signals as a flat list and streams data over WebsocketTcp protocol", + configurationCallback); +} + +void WebsocketStreamingServerImpl::onStopServer() +{ + websocketStreamingServer.stop(); +} + +OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( + INTERNAL_FACTORY, WebsocketStreamingServer, daq::IServer, + daq::DevicePtr, rootDevice, + PropertyObjectPtr, config, + const ContextPtr&, context +) + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp new file mode 100644 index 0000000..21696f7 --- /dev/null +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE + +WebsocketStreamingServerModule::WebsocketStreamingServerModule(ContextPtr context) + : Module("openDAQ Websocket streaming server module", + daq::VersionInfo(WS_STREAM_SRV_MODULE_MAJOR_VERSION, WS_STREAM_SRV_MODULE_MINOR_VERSION, WS_STREAM_SRV_MODULE_PATCH_VERSION), + std::move(context)) +{ +} + +DictPtr WebsocketStreamingServerModule::onGetAvailableServerTypes() +{ + auto result = Dict(); + + auto serverType = WebsocketStreamingServerImpl::createType(); + result.set(serverType.getId(), serverType); + + return result; +} + +ServerPtr WebsocketStreamingServerModule::onCreateServer(StringPtr serverType, + PropertyObjectPtr serverConfig, + DevicePtr rootDevice) +{ + if (!context.assigned()) + throw InvalidParameterException{"Context parameter cannot be null."}; + + if (!serverConfig.assigned()) + serverConfig = WebsocketStreamingServerImpl::createDefaultConfig(); + + ServerPtr server(WebsocketStreamingServer_Create(rootDevice, serverConfig, context)); + return server; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/tests/CMakeLists.txt b/modules/websocket_streaming_server_module/tests/CMakeLists.txt new file mode 100644 index 0000000..633b362 --- /dev/null +++ b/modules/websocket_streaming_server_module/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +set(MODULE_NAME ws_stream_srv_module) +set(TEST_APP test_${MODULE_NAME}) + +set(TEST_SOURCES test_websocket_streaming_server_module.cpp + test_app.cpp +) + +add_executable(${TEST_APP} ${TEST_SOURCES} +) + +target_link_libraries(${TEST_APP} PRIVATE daq::test_utils + daq::opendaq_mocks + ${SDK_TARGET_NAMESPACE}::${MODULE_NAME} + Taskflow::Taskflow +) + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +if (MSVC) # Ignoring warning for the Taskflow + target_compile_options(${TEST_APP} PRIVATE /wd4324) +endif() + +if (OPENDAQ_ENABLE_COVERAGE) + setup_target_for_coverage(${TEST_APP}coverage ${TEST_APP} ${TEST_APP}coverage) +endif() diff --git a/modules/websocket_streaming_server_module/tests/test_app.cpp b/modules/websocket_streaming_server_module/tests/test_app.cpp new file mode 100644 index 0000000..64dd0cc --- /dev/null +++ b/modules/websocket_streaming_server_module/tests/test_app.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char** args) +{ + daq::daqInitializeCoreObjectsTesting(); + daqInitModuleManagerLibrary(); + daqInitOpenDaqLibrary(); + + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DaqMemCheckListener()); + + auto res = RUN_ALL_TESTS(); + + return res; +} diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp new file mode 100644 index 0000000..48e1e53 --- /dev/null +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WebsocketStreamingServerModuleTest : public testing::Test +{ +public: + void TearDown() override + { + } +}; + +using namespace daq; + +static ModulePtr CreateModule(ContextPtr context = NullContext()) +{ + ModulePtr module; + createWebsocketStreamingServerModule(&module, context); + return module; +} + +static InstancePtr CreateTestInstance() +{ + const auto logger = Logger(); + const auto moduleManager = ModuleManager("[[none]]"); + const auto context = Context(Scheduler(logger), logger, TypeManager(), moduleManager); + + const ModulePtr deviceModule(MockDeviceModule_Create(context)); + moduleManager.addModule(deviceModule); + + const ModulePtr fbModule(MockFunctionBlockModule_Create(context)); + moduleManager.addModule(fbModule); + + const ModulePtr daqWebsocketStreamingServerModule = CreateModule(context); + moduleManager.addModule(daqWebsocketStreamingServerModule); + + auto instance = InstanceCustom(context, "localInstance"); + for (const auto& deviceInfo : instance.getAvailableDevices()) + instance.addDevice(deviceInfo.getConnectionString()); + + for (const auto& [id, _] : instance.getAvailableFunctionBlockTypes()) + instance.addFunctionBlock(id); + + return instance; +} + +static PropertyObjectPtr CreateServerConfig(const InstancePtr& instance) +{ + auto config = instance.getAvailableServerTypes().get("openDAQ WebsocketTcp Streaming").createDefaultConfig(); + config.setPropertyValue("WebsocketStreamingPort", 0); + return config; +} + +TEST_F(WebsocketStreamingServerModuleTest, CreateModule) +{ + IModule* module = nullptr; + ErrCode errCode = createModule(&module, NullContext()); + ASSERT_TRUE(OPENDAQ_SUCCEEDED(errCode)); + + ASSERT_NE(module, nullptr); + module->releaseRef(); +} + +TEST_F(WebsocketStreamingServerModuleTest, ModuleName) +{ + auto module = CreateModule(); + ASSERT_EQ(module.getName(), "openDAQ Websocket streaming server module"); +} + +TEST_F(WebsocketStreamingServerModuleTest, VersionAvailable) +{ + auto module = CreateModule(); + ASSERT_TRUE(module.getVersionInfo().assigned()); +} + +TEST_F(WebsocketStreamingServerModuleTest, VersionCorrect) +{ + auto module = CreateModule(); + auto version = module.getVersionInfo(); + + ASSERT_EQ(version.getMajor(), WS_STREAM_SRV_MODULE_MAJOR_VERSION); + ASSERT_EQ(version.getMinor(), WS_STREAM_SRV_MODULE_MINOR_VERSION); + ASSERT_EQ(version.getPatch(), WS_STREAM_SRV_MODULE_PATCH_VERSION); +} + +TEST_F(WebsocketStreamingServerModuleTest, GetAvailableComponentTypes) +{ + const auto module = CreateModule(); + + DictPtr functionBlockTypes; + ASSERT_NO_THROW(functionBlockTypes = module.getAvailableFunctionBlockTypes()); + ASSERT_EQ(functionBlockTypes.getCount(), 0u); + + DictPtr deviceTypes; + ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); + ASSERT_EQ(deviceTypes.getCount(), 0u); + + DictPtr serverTypes; + ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); + ASSERT_EQ(serverTypes.getCount(), 1u); + ASSERT_TRUE(serverTypes.hasKey("openDAQ WebsocketTcp Streaming")); + ASSERT_EQ(serverTypes.get("openDAQ WebsocketTcp Streaming").getId(), "openDAQ WebsocketTcp Streaming"); +} + +TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) +{ + auto module = CreateModule(); + + DictPtr serverTypes = module.getAvailableServerTypes(); + ASSERT_TRUE(serverTypes.hasKey("openDAQ WebsocketTcp Streaming")); + auto config = serverTypes.get("openDAQ WebsocketTcp Streaming").createDefaultConfig(); + ASSERT_TRUE(config.assigned()); + + ASSERT_TRUE(config.hasProperty("WebsocketStreamingPort")); + ASSERT_EQ(config.getPropertyValue("WebsocketStreamingPort"), 7414); +} + +TEST_F(WebsocketStreamingServerModuleTest, CreateServer) +{ + auto device = CreateTestInstance(); + auto module = CreateModule(device.getContext()); + auto config = CreateServerConfig(device); + + ASSERT_NO_THROW(module.createServer("openDAQ WebsocketTcp Streaming", device.getRootDevice(), config)); +} + +TEST_F(WebsocketStreamingServerModuleTest, CreateServerFromInstance) +{ + auto device = CreateTestInstance(); + auto config = CreateServerConfig(device); + + ASSERT_NO_THROW(device.addServer("openDAQ WebsocketTcp Streaming", config)); +} diff --git a/shared/libraries/websocket_streaming/CMakeLists.txt b/shared/libraries/websocket_streaming/CMakeLists.txt new file mode 100644 index 0000000..0feb5a7 --- /dev/null +++ b/shared/libraries/websocket_streaming/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.2) +set_cmake_folder_context(TARGET_FOLDER_NAME ${SDK_TARGET_NAMESPACE}_websocket_streaming) +project(OpenDaqStreaming CXX) + +add_subdirectory(src) + +if (OPENDAQ_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h new file mode 100644 index 0000000..0bb5033 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -0,0 +1,51 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "websocket_streaming/websocket_streaming.h" +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class AsyncPacketReader +{ +public: + using OnPacketCallback = std::function& packets)>; + + AsyncPacketReader(); + ~AsyncPacketReader(); + + void startReading(const DevicePtr& device, const ContextPtr& context); + void stopReading(); + void onPacket(const OnPacketCallback& callback); + void setLoopFrequency(uint32_t freqency); + +protected: + void startReadThread(); + void createReaders(); + + DevicePtr device; + ContextPtr context; + OnPacketCallback onPacketCallback; + std::thread readThread; + bool readThreadStarted = false; + std::chrono::milliseconds sleepTime; + std::vector> signalReaders; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h new file mode 100644 index 0000000..228746c --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -0,0 +1,50 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class InputSignal; +using InputSignalPtr = std::shared_ptr; + +class InputSignal +{ +public: + InputSignal(); + + PacketPtr asPacket(uint64_t packetOffset, const uint8_t* data, size_t size); + PacketPtr createDecriptorChangedPacket(); + void setDataDescriptor(const daq::streaming_protocol::SubscribedSignal& dataSignal); + void setDomainDescriptor(const daq::streaming_protocol::SubscribedSignal& timeSignal); + bool hasDescriptors(); + DataDescriptorPtr getSignalDescriptor(); + DataDescriptorPtr getDomainSignalDescriptor(); + +protected: + DataDescriptorPtr currentDataDescriptor; + DataDescriptorPtr currentDomainDataDescriptor; + + std::string name; + std::string description; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h new file mode 100644 index 0000000..e29918c --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -0,0 +1,64 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "websocket_streaming/websocket_streaming.h" +#include +#include "streaming_protocol/BaseSynchronousSignal.hpp" +#include +#include +#include "streaming_protocol/Logging.hpp" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class OutputSignal; +using OutputSignalPtr = std::shared_ptr; + +class OutputSignal +{ +public: + using SignalStreamPtr = std::shared_ptr; + + OutputSignal(const daq::stream::StreamPtr& stream, const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb); + OutputSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb); + + virtual void write(const PacketPtr& packet); + virtual void write(const void* data, size_t sampleCount); + SignalPtr getCoreSignal(); + +protected: + DataDescriptorPtr getValueDescriptor(); + DataDescriptorPtr getDomainDescriptor(); + uint64_t getRuleDelta(); + uint64_t getTickResolution(); + void createSignalStream(); + void createStreamedSignal(); + void writeEventPacket(const EventPacketPtr& packet); + void writeDataPacket(const DataPacketPtr& packet); + void writeDescriptorChangedPacket(const EventPacketPtr& packet); + void writePropertyChangedPacket(const EventPacketPtr& packet); + + SignalPtr signal; + SignalConfigPtr streamedSignal; + daq::streaming_protocol::StreamWriterPtr writer; + SignalStreamPtr stream; + size_t sampleSize; + daq::streaming_protocol::LogCallback logCallback; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h new file mode 100644 index 0000000..50fed95 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -0,0 +1,53 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#include "websocket_streaming/websocket_streaming.h" +#include "websocket_streaming/signal_info.h" +#include +#include +#include +#include +#include "streaming_protocol/SubscribedSignal.hpp" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class SignalDescriptorConverter +{ +public: + /** + * @param subscribedSignal The object holding everything about thee signal on the consumer side + * @throws ConversionFailedException + */ + static SubscribedSignalInfo ToDataDescriptor(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + /** + * @throws ConversionFailedException + */ + static void ToStreamedSignal(const daq::SignalPtr& signal, daq::streaming_protocol::BaseSignalPtr stream, const SignalProps& sigProps); + +private: + static daq::DataRulePtr GetRule(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + static void SetTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::BaseSignalPtr signal); + static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); + static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); + static void EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra); + static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor); + static nlohmann::json DictToJson(const DictPtr& dict); + static DictPtr JsonToDict(const nlohmann::json& json); +}; +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h new file mode 100644 index 0000000..e7462bf --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h @@ -0,0 +1,38 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "websocket_streaming/websocket_streaming.h" +#include + +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +struct SignalProps +{ + std::optional name; + std::optional description; +}; + +struct SubscribedSignalInfo +{ + DataDescriptorPtr dataDescriptor; + SignalProps signalProps; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h new file mode 100644 index 0000000..ee12131 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -0,0 +1,108 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include "websocket_streaming/websocket_streaming.h" +#include "websocket_streaming/input_signal.h" +#include "websocket_streaming/signal_info.h" +#include "stream/WebsocketClientStream.hpp" +#include "streaming_protocol/ProtocolHandler.hpp" +#include "streaming_protocol/SubscribedSignal.hpp" +#include "opendaq/signal_factory.h" +#include "streaming_protocol/Logging.hpp" +#include +#include +#include + +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class StreamingClient; +using StreamingClientPtr = std::shared_ptr; + +class StreamingClient +{ +public: + using OnPacketCallback = std::function; + using OnSignalCallback = std::function; + using OnFindSignalCallback = std::function; + using OnDomainDescriptorCallback = + std::function; + using OnAvailableSignalsCallback = std::function& signalIds)>; + + StreamingClient(const ContextPtr& context, const std::string& connectionString); + StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target = "/"); + ~StreamingClient(); + + bool connect(); + void disconnect(); + void onPacket(const OnPacketCallback& callack); + void onNewSignal(const OnSignalCallback& callback); + void onSignalUpdated(const OnSignalCallback& callback); + void onDomainDescriptor(const OnDomainDescriptorCallback& callback); + void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); + void onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback); + void onFindSignal(const OnFindSignalCallback& callback); + std::string getHost(); + uint16_t getPort(); + std::string getTarget(); + bool isConnected(); + void setConnectTimeout(std::chrono::milliseconds timeout); + + +protected: + void parseConnectionString(const std::string& url); + void onSignalMeta(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + const std::string& method, + const nlohmann::json& params); + void onProtocolMeta(daq::streaming_protocol::ProtocolHandler& protocolHandler, const std::string& method, const nlohmann::json& params); + void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size); + void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + void publishSignal(const std::string& signalId); + void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); + + LoggerPtr logger; + LoggerComponentPtr loggerComponent; + daq::streaming_protocol::LogCallback logCallback; + + std::string host; + uint16_t port; + std::string target; + bool connected = false; + boost::asio::io_context ioContext; + daq::streaming_protocol::SignalContainer signalContainer; + daq::streaming_protocol::ProtocolHanlderPtr protocolHandler; + std::unordered_map signals; + OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; + OnSignalCallback onNewSignalCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; + OnDomainDescriptorCallback onDomainDescriptorCallback = [](const StringPtr&, const DataDescriptorPtr&) {}; + OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; + OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; + OnFindSignalCallback onFindSignalCallback = [](const StringPtr& signalId) { return nullptr; }; + OnSignalCallback onSignalUpdatedCallback = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; + std::thread clientThread; + std::mutex clientMutex; + std::condition_variable conditionVariable; + std::chrono::milliseconds connectTimeout{1000}; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h new file mode 100644 index 0000000..c25e33a --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -0,0 +1,74 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "websocket_streaming/websocket_streaming.h" +#include "stream/WebsocketServer.hpp" +#include "websocket_streaming/output_signal.h" +#include "streaming_protocol/StreamWriter.h" +#include "streaming_protocol/Logging.hpp" +#include +#include +#include + +#include +#include + +#include +#include +#include + + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class StreamingServer; +using StreamingServerPtr = std::shared_ptr; + +class StreamingServer +{ +public: + using OnAcceptCallback = std::function(const daq::streaming_protocol::StreamWriterPtr& writer)>; + + StreamingServer(const ContextPtr& context); + ~StreamingServer(); + + void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); + void stop(); + void onAccept(const OnAcceptCallback& callback); + void unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, const std::string& signalId, const PacketPtr& packet); + void broadcastPacket(const std::string& signalId, const PacketPtr &packet); + +protected: + using SignalMap = std::unordered_map; + using ClientMap = std::unordered_map; + + void onAcceptInternal(const daq::stream::StreamPtr& stream); + void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); + void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals); + + uint16_t port; + boost::asio::io_context ioContext; + boost::asio::executor_work_guard work; + daq::stream::WebsocketServerUniquePtr server; + std::thread serverThread; + ClientMap clients; + OnAcceptCallback onAcceptCallback; + LoggerPtr logger; + LoggerComponentPtr loggerComponent; + daq::streaming_protocol::LogCallback logCallback; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h new file mode 100644 index 0000000..5d021f3 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h @@ -0,0 +1,32 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "websocket_streaming/websocket_client_device_impl.h" +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +inline DevicePtr WebsocketClientDevice(const ContextPtr& context, + const ComponentPtr& parent, + const StringPtr& localId, + const StringPtr& connectionString) +{ + DevicePtr obj(createWithImplementation(context, parent, localId, connectionString)); + return obj; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h new file mode 100644 index 0000000..af83c28 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -0,0 +1,51 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "websocket_streaming/streaming_client.h" +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class WebsocketClientDeviceImpl : public Device +{ +public: + explicit WebsocketClientDeviceImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const StringPtr& localId, + const StringPtr& connectionString); + +protected: + DeviceInfoPtr onGetInfo() override; + void createWebsocketStreaming(); + void activateStreaming(); + void registerSignalAttributes(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); + void onNewSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void onDomainDescriptor(const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor); + void initializeDeviceSignals(const std::vector& signalIds); + + DeviceInfoConfigPtr deviceInfo; + std::map deviceSignals; + std::map> deviceSignalsAttributes; + StreamingPtr websocketStreaming; + StringPtr connectionString; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h new file mode 100644 index 0000000..3f230c0 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h @@ -0,0 +1,35 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "websocket_streaming/websocket_client_signal_impl.h" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +inline SignalPtr WebsocketClientSignal(const ContextPtr& ctx, + const ComponentPtr& parent, + const DataDescriptorPtr& descriptor, + const DataDescriptorPtr& domainDescriptor, + const StringPtr& streamingId) +{ + SignalPtr obj(createWithImplementation(ctx, + parent, + descriptor, + domainDescriptor, + streamingId)); + return obj; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h new file mode 100644 index 0000000..99e9502 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -0,0 +1,51 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#include "websocket_streaming/websocket_streaming.h" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class WebsocketClientSignalImpl final : public SignalRemote +{ +public: + explicit WebsocketClientSignalImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const DataDescriptorPtr& descriptor, + const DataDescriptorPtr& domainDescriptor, + const StringPtr& streamingId); + + // ISignal + ErrCode INTERFACE_FUNC getDescriptor(IDataDescriptor** descriptor) override; + ErrCode INTERFACE_FUNC getDomainSignal(ISignal** signal) override; + + StringPtr onGetRemoteId() const override; + Bool onTriggerEvent(EventPacketPtr eventPacket) override; + +protected: + EventPacketPtr createDataDescriptorChangedEventPacket() override; + +private: + static StringPtr CreateLocalId(const StringPtr& streamingId); + + StringPtr streamingId; + DataDescriptorPtr mirroredDataDescriptor; + SignalConfigPtr domainSignalArtificial; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h new file mode 100644 index 0000000..779423a --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -0,0 +1,57 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + + +#define BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING \ + namespace daq::websocket_streaming \ + { +#define END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING } + +#include + +namespace daq::streaming_protocol +{ + static const uint16_t WEBSOCKET_LISTENING_PORT = 7414; + class SubscribedSignal; + using SubscribedSignalPtr = std::shared_ptr; + + class ProtocolHandler; + using ProtocolHanlderPtr = std::shared_ptr; + + class StreamWriter; + using StreamWriterPtr = std::shared_ptr; + + class BaseSignal; + using BaseSignalPtr = std::shared_ptr; + + class BaseSynchronousSignal; + using BaseSynchronousSignalPtr = std::shared_ptr; +} + +namespace daq::stream +{ + class Stream; + using StreamPtr = std::shared_ptr; + + class WebsocketServer; + using WebsocketServerPtr = std::shared_ptr; + using WebsocketServerUniquePtr = std::unique_ptr; + + class WebsocketClientStream; + using WebsocketClientStreamPtr = std::shared_ptr; +} diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h new file mode 100644 index 0000000..5c137a6 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h @@ -0,0 +1,38 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "websocket_streaming/websocket_streaming_impl.h" +#include "websocket_streaming/streaming_client.h" +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +inline StreamingPtr WebsocketStreaming(const StringPtr& connectionString, + const ContextPtr& context) +{ + StreamingPtr obj(createWithImplementation(connectionString, context)); + return obj; +} + +inline StreamingPtr WebsocketStreaming(const StreamingClientPtr& streamingClient, + const StringPtr& connectionString, + const ContextPtr& context) +{ + StreamingPtr obj(createWithImplementation(streamingClient, connectionString, context)); + return obj; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h new file mode 100644 index 0000000..15fd3f3 --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -0,0 +1,53 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "websocket_streaming/streaming_client.h" +#include + +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class WebsocketStreamingImpl : public Streaming +{ +public: + explicit WebsocketStreamingImpl(const StringPtr& connectionString, + const ContextPtr& context); + + explicit WebsocketStreamingImpl(StreamingClientPtr streamingClient, + const StringPtr& connectionString, + const ContextPtr& context); +protected: + void onSetActive(bool active) override; + StringPtr onAddSignal(const SignalRemotePtr& signal) override; + void onRemoveSignal(const SignalRemotePtr& signal) override; + + void prepareStreamingClient(); + void handleEventPacket(const StringPtr& signalId, const EventPacketPtr &eventPacket); + void handleCachedEventPackets(const StringPtr& signalStreamingId, const SignalRemotePtr& signal); + void handleDataPacket(const StringPtr& signalId, const PacketPtr& dataPacket); + void onPacket(const StringPtr& signalId, const PacketPtr& packet); + void onAvailableSignals(const std::vector& signalIds); + StringPtr getSignalStreamingId(const SignalRemotePtr& signal); + + daq::websocket_streaming::StreamingClientPtr streamingClient; + std::vector availableSignalIds; + std::map> cachedEventPackets; +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h new file mode 100644 index 0000000..965fdac --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h @@ -0,0 +1,20 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +void daqInitStreamingLibrary(); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h new file mode 100644 index 0000000..46291de --- /dev/null +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include "websocket_streaming/streaming_server.h" +#include "websocket_streaming/async_packet_reader.h" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +class WebsocketStreamingServer +{ +public: + WebsocketStreamingServer(const InstancePtr& instance); + WebsocketStreamingServer(const DevicePtr& device, const ContextPtr& context); + ~WebsocketStreamingServer(); + + void setStreamingPort(uint16_t port); + void start(); + void stop(); + +protected: + DevicePtr device; + ContextPtr context; + + uint16_t streamingPort = 0; + daq::websocket_streaming::StreamingServer streamingServer; + daq::websocket_streaming::AsyncPacketReader packetReader; + +private: + void stopInternal(); +}; + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt new file mode 100644 index 0000000..3c9c3fa --- /dev/null +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -0,0 +1,87 @@ +set(LIB_NAME ${SDK_TARGET_NAME}_websocket_streaming) + +set(LIB_MAJOR_VERSION 0) +set(LIB_MINOR_VERSION 4) +set(LIB_PATCH_VERSION 0) + +set(SRC_Cpp signal_descriptor_converter.cpp + streaming_client.cpp + streaming_server.cpp + input_signal.cpp + output_signal.cpp + async_packet_reader.cpp + websocket_streaming_init.cpp + websocket_streaming_server.cpp + websocket_client_device_impl.cpp + websocket_client_signal_impl.cpp + websocket_streaming_impl.cpp +) + +set(SRC_PublicHeaders + websocket_streaming.h + signal_info.h + signal_descriptor_converter.h + streaming_client.h + streaming_server.h + input_signal.h + output_signal.h + async_packet_reader.h + websocket_streaming_init.h + websocket_streaming_server.h + websocket_client_device_impl.h + websocket_client_device_factory.h + websocket_client_signal_impl.h + websocket_client_signal_factory.h + websocket_streaming_impl.h + websocket_streaming_factory.h +) + +set(INCLUDE_DIR ../include/websocket_streaming) +prepend_include(${INCLUDE_DIR} SRC_PublicHeaders) + + +set(SRC_PrivateHeaders +) + + +add_library(${LIB_NAME} STATIC ${SRC_Cpp} + ${SRC_PublicHeaders} + ${SRC_PrivateHeaders} +) + +add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) + +if(BUILD_64Bit OR BUILD_ARM) + set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) +else() + set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE OFF) +endif() + +target_include_directories(${LIB_NAME} PUBLIC $ + $ + + $ +) + +# links target with daq::streaming_dev (which is a static lib) +# because daq::streaming_dev is also linked into opcuatms_client lib +# and both target & opcuatms_client are lately linked into integration test binary, +# to avoid multiple definition linker errors both target & opcuatms_client should use daq::streaming_dev +target_link_libraries(${LIB_NAME} PUBLIC daq::streaming_protocol + daq::opendaq + PRIVATE + daq::streaming_dev +) + +# Fix daq::streaming_protocol` not properly propagating linking requirements on Windows +if (WIN32) + target_link_libraries(${LIB_NAME} + PUBLIC + ws2_32 + wsock32 + ) +endif() + +set_target_properties(${LIB_NAME} PROPERTIES PUBLIC_HEADER "${SRC_PublicHeaders}") + +opendaq_set_output_lib_name(${LIB_NAME} ${LIB_MAJOR_VERSION}) diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp new file mode 100644 index 0000000..7cb5d54 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -0,0 +1,78 @@ +#include "websocket_streaming/async_packet_reader.h" +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +AsyncPacketReader::AsyncPacketReader() +{ + setLoopFrequency(50); + onPacketCallback = [](const SignalPtr& signal, const ListPtr& packets) {}; +} + +AsyncPacketReader::~AsyncPacketReader() +{ + stopReading(); +} + +void AsyncPacketReader::startReading(const DevicePtr& device, const ContextPtr& context) +{ + this->device = device; + this->context = context; + + readThreadStarted = true; + this->readThread = std::thread([this]() { this->startReadThread(); }); +} + +void AsyncPacketReader::stopReading() +{ + readThreadStarted = false; + if (readThread.joinable()) + readThread.join(); + + signalReaders.clear(); +} + +void AsyncPacketReader::onPacket(const OnPacketCallback& callback) +{ + onPacketCallback = callback; +} + +void AsyncPacketReader::setLoopFrequency(uint32_t freqency) +{ + uint64_t sleepMs = 1000.0 / freqency; + this->sleepTime = std::chrono::milliseconds(sleepMs); +} + +void AsyncPacketReader::startReadThread() +{ + createReaders(); + + while (readThreadStarted) + { + for (const auto& [signal, reader] : signalReaders) + { + if (reader.getAvailableCount() == 0) + continue; + + const auto& packets = reader.readAll(); + onPacketCallback(signal, packets); + } + + std::this_thread::sleep_for(sleepTime); + } +} + +void AsyncPacketReader::createReaders() +{ + signalReaders.clear(); + auto signals = device.getSignalsRecursive(); + + for (const auto& signal : signals) + { + auto reader = PacketReader(signal); + signalReaders.push_back(std::pair({signal, reader})); + } +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp new file mode 100644 index 0000000..b5b426b --- /dev/null +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +using namespace daq; +using namespace daq::streaming_protocol; + +InputSignal::InputSignal() +{ +} + +PacketPtr InputSignal::asPacket(uint64_t packetOffset, const uint8_t* data, size_t size) +{ + assert(!currentDataDescriptor.isStructDescriptor()); + + auto sampleType = currentDataDescriptor.getSampleType(); + if (currentDataDescriptor.getPostScaling().assigned()) + sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); + + const auto sampleSize = getSampleSize(sampleType); + const auto sampleCount = size / sampleSize; + + auto domainPacket = DataPacket(currentDomainDataDescriptor, sampleCount, (Int) packetOffset); + auto dataPacket = DataPacketWithDomain(domainPacket, currentDataDescriptor, sampleCount); + std::memcpy(dataPacket.getRawData(), data, sampleCount*sampleSize); + return dataPacket; +} + +PacketPtr InputSignal::createDecriptorChangedPacket() +{ + return DataDescriptorChangedEventPacket(currentDataDescriptor, currentDomainDataDescriptor); +} + +void InputSignal::setDataDescriptor(const daq::streaming_protocol::SubscribedSignal &dataSignal) +{ + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(dataSignal); + auto descriptor = sInfo.dataDescriptor; + currentDataDescriptor = descriptor; +} + +void InputSignal::setDomainDescriptor(const daq::streaming_protocol::SubscribedSignal& timeSignal) +{ + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(timeSignal); + auto descriptor = sInfo.dataDescriptor; + currentDomainDataDescriptor = descriptor; +} + +bool InputSignal::hasDescriptors() +{ + return currentDataDescriptor.assigned() && currentDomainDataDescriptor.assigned(); +} + +DataDescriptorPtr InputSignal::getSignalDescriptor() +{ + return currentDataDescriptor; +} + +DataDescriptorPtr InputSignal::getDomainSignalDescriptor() +{ + return currentDomainDataDescriptor; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp new file mode 100644 index 0000000..627c50a --- /dev/null +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -0,0 +1,229 @@ +#include "websocket_streaming/output_signal.h" +#include "websocket_streaming/signal_descriptor_converter.h" +#include +#include +#include "streaming_protocol/StreamWriter.h" +#include "streaming_protocol/SynchronousSignal.hpp" +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +using namespace daq::streaming_protocol; +using namespace daq::stream; + +OutputSignal::OutputSignal(const StreamPtr& stream, const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb) + : OutputSignal(std::make_shared(stream), signal, logCb) +{ +} + +OutputSignal::OutputSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb) + : signal(signal) + , writer(writer) + , logCallback(logCb) +{ + createSignalStream(); + createStreamedSignal(); +} + +void OutputSignal::write(const PacketPtr& packet) +{ + const auto type = packet.getType(); + + switch (type) + { + case PacketType::Data: + writeDataPacket(packet); + break; + case PacketType::Event: + writeEventPacket(packet); + break; + default: + STREAMING_PROTOCOL_LOG_E("Failed to write a packet of unsupported type."); + } +} + +void OutputSignal::write(const void* data, size_t sampleCount) +{ + stream->addData(data, sampleCount); +} + +DataDescriptorPtr OutputSignal::getValueDescriptor() +{ + auto dataDescriptor = signal.getDescriptor(); + if (!dataDescriptor.assigned()) + throw InvalidParameterException("Signal descriptor not set."); + + if (dataDescriptor.isStructDescriptor()) + throw InvalidParameterException("Signal cannot be a struct."); + + return dataDescriptor; +} + +DataDescriptorPtr OutputSignal::getDomainDescriptor() +{ + auto domainSignal = signal.getDomainSignal(); + if (!domainSignal.assigned()) + throw InvalidParameterException("Domain signal not set."); + + auto domainDataDescriptor = domainSignal.getDescriptor(); + if (!domainDataDescriptor.assigned()) + throw InvalidParameterException("Domain signal descriptor not set."); + + if (domainDataDescriptor.isStructDescriptor()) + throw InvalidParameterException("Signal cannot be a struct."); + + return domainDataDescriptor; +} + +uint64_t OutputSignal::getRuleDelta() +{ + auto valueDescriptor = getDomainDescriptor(); + + auto dataRule = valueDescriptor.getRule(); + if (dataRule.getType() != DataRuleType::Linear) + throw InvalidParameterException("Invalid data rule."); + + return dataRule.getParameters().get("delta"); +} + +uint64_t OutputSignal::getTickResolution() +{ + auto valueDescriptor = getDomainDescriptor(); + auto resolution = valueDescriptor.getTickResolution(); + return resolution.getDenominator() / resolution.getNumerator(); +} + +void OutputSignal::createSignalStream() +{ + const auto valueDescriptor = getValueDescriptor(); + auto sampleType = valueDescriptor.getSampleType(); + if (valueDescriptor.getPostScaling().assigned()) + sampleType = valueDescriptor.getPostScaling().getInputSampleType(); + const auto id = signal.getGlobalId(); + const auto outputRate = getRuleDelta(); // from streaming library side, output rate is defined as number of tics between two samples + const auto resolution = getTickResolution(); + sampleSize = getSampleSize(sampleType); + + switch (sampleType) + { + case daq::SampleType::Int8: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::UInt8: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::Int16: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::UInt16: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::Int32: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::UInt32: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::Int64: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::UInt64: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::Float32: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::Float64: + stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + break; + case daq::SampleType::ComplexFloat32: + case daq::SampleType::ComplexFloat64: + case daq::SampleType::Binary: + case daq::SampleType::Invalid: + case daq::SampleType::String: + case daq::SampleType::RangeInt64: + default: + throw InvalidTypeException(); + } + + SignalProps sigProps; + sigProps.name = signal.getName(); + sigProps.description = signal.getDescription(); + SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); + stream->subscribe(); +} + +void OutputSignal::createStreamedSignal() +{ + const auto context = signal.getContext(); + + auto domainSignal = Signal(context, nullptr, "domain"); + streamedSignal = Signal(context, nullptr, signal.getLocalId()); + streamedSignal.setDomainSignal(domainSignal); +} + +void OutputSignal::writeEventPacket(const EventPacketPtr& packet) +{ + const auto eventId = packet.getEventId(); + + if (eventId == event_packet_id::DATA_DESCRIPTOR_CHANGED) + writeDescriptorChangedPacket(packet); + else if (eventId == event_packet_id::PROPERTY_CHANGED) + writePropertyChangedPacket(packet); + else + { + STREAMING_PROTOCOL_LOG_E("Event type {} is not supported by streaming.", eventId); + } +} + +void OutputSignal::writeDataPacket(const DataPacketPtr& packet) +{ + const auto domainPacket = packet.getDomainPacket(); + if (domainPacket.assigned()) + stream->setTimeStart(domainPacket.getOffset()); + + stream->addData(packet.getRawData(), packet.getSampleCount()); +} + +void OutputSignal::writeDescriptorChangedPacket(const EventPacketPtr& packet) +{ + const auto params = packet.getParameters(); + const auto valueDescriptor = params.get(event_packet_param::DATA_DESCRIPTOR); + const auto domainDescriptor = params.get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); + + if (valueDescriptor.assigned()) + streamedSignal.setDescriptor(valueDescriptor); + SignalConfigPtr domainSignal = streamedSignal.getDomainSignal(); + if (domainSignal.assigned() && domainDescriptor.assigned()) + domainSignal.setDescriptor(domainDescriptor); + + SignalDescriptorConverter::ToStreamedSignal(streamedSignal, stream, SignalProps{}); + stream->writeSignalMetaInformation(); +} + +void OutputSignal::writePropertyChangedPacket(const EventPacketPtr& packet) +{ + const auto params = packet.getParameters(); + const auto name = params.get(event_packet_param::NAME); + const auto value = params.get(event_packet_param::VALUE); + + SignalProps sigProps; + if (name == "Name") + sigProps.name = value; + else if (name == "Description") + sigProps.description = value; + + SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); + stream->writeSignalMetaInformation(); +} + +SignalPtr OutputSignal::getCoreSignal() +{ + return signal; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp new file mode 100644 index 0000000..8c68447 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -0,0 +1,451 @@ +/* + * Blueberry d.o.o. ("COMPANY") CONFIDENTIAL + * Unpublished Copyright (c) 2021-2022 Blueberry d.o.o., All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains the property of + * COMPANY. The intellectual and technical concepts contained herein are + * proprietary to COMPANY and are protected by copyright law and as trade + * secrets and may also be covered by U.S. and Foreign Patents, patents in + * process, etc. + * Dissemination of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from COMPANY. + * Access to the source code contained herein is hereby forbidden to anyone + * except current COMPANY employees, managers or contractors who have executed + * Confidentiality and Non-disclosure agreements explicitly covering such + * access. + * + * The copyright notice above does not evidence any actual or intended + * publication or disclosure of this source code, which includes information + * that is confidential and/or proprietary, and is a trade secret of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC + * DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS + * WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF + * APPLICABLE LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF + * THIS SOURCE CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY + * RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, + * USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "streaming_protocol/BaseSignal.hpp" +#include "streaming_protocol/BaseSynchronousSignal.hpp" +#include "streaming_protocol/SubscribedSignal.hpp" +#include "streaming_protocol/Types.h" + +#include "websocket_streaming/signal_descriptor_converter.h" + +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +/** + * @todo Only scalar values are supported for now. No structs. No dimensions. + */ +SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +{ + SubscribedSignalInfo sInfo; + + auto dataDescriptor = DataDescriptorBuilder().setRule(GetRule(subscribedSignal)); + + if (subscribedSignal.isTimeSignal()) + { + uint64_t numerator = 1; + uint64_t denominator = subscribedSignal.timeBaseFrequency(); + auto resolution = Ratio(static_cast(numerator), static_cast(denominator)); + dataDescriptor.setTickResolution(resolution); + } + + auto extra = subscribedSignal.interpretationObject(); + DecodeInterpretationObject(extra, dataDescriptor); + + sInfo.dataDescriptor = dataDescriptor.build(); + if (extra.count("sig_name") > 0) + sInfo.signalProps.name = extra["sig_name"]; + if (extra.count("sig_desc") > 0) + sInfo.signalProps.description = extra["sig_desc"]; + + return sInfo; +} + +void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, + daq::streaming_protocol::BaseSignalPtr stream, + const SignalProps& sigProps) +{ + auto dataDescriptor = signal.getDescriptor(); + if (!dataDescriptor.assigned()) + return; + + auto domainDescriptor = signal.getDomainSignal().getDescriptor(); + + stream->setMemberName(dataDescriptor.getName()); + + // Data type of stream can not be changed. Complain upon change! + daq::SampleType daqSampleType = dataDescriptor.getSampleType(); + if (dataDescriptor.getPostScaling().assigned()) + daqSampleType = dataDescriptor.getPostScaling().getInputSampleType(); + + daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); + if (requestedSampleType != stream->getSampleType()) + throw ConversionFailedException(); + + UnitPtr unit = dataDescriptor.getUnit(); + stream->setUnit(unit.getId(), unit.getSymbol()); + + // causes an error + // DataRulePtr rule = domainDescriptor.getRule(); + // SetTimeRule(rule, stream); + + if (domainDescriptor.assigned()) + { + auto resolution = domainDescriptor.getTickResolution(); + stream->setTimeTicksPerSecond(resolution.getDenominator() / resolution.getNumerator()); + } + + nlohmann::json extra; + EncodeInterpretationObject(dataDescriptor, extra); + + if (sigProps.name.has_value()) + extra["sig_name"] = sigProps.name.value(); + if (sigProps.description.has_value()) + extra["sig_desc"] = sigProps.description.value(); + + stream->setDataInterpretationObject(extra); + + nlohmann::json domainExtra; + if (domainDescriptor.assigned()) + EncodeInterpretationObject(domainDescriptor, domainExtra); + stream->setTimeInterpretationObject(domainExtra); +} + +/** + * @throws ConversionFailedException + */ +daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +{ + switch (subscribedSignal.ruleType()) + { + case daq::streaming_protocol::RULETYPE_CONSTANT: + { + uint64_t start = subscribedSignal.time(); + return ConstantDataRule(start); + } + break; + case daq::streaming_protocol::RULETYPE_EXPLICIT: + { + return ExplicitDataRule(); + } + break; + case daq::streaming_protocol::RULETYPE_LINEAR: + { + uint64_t start = subscribedSignal.time(); + return LinearDataRule(subscribedSignal.linearDelta(), start); + } + break; + default: + throw ConversionFailedException(); + } +} + +/** + * @throws ConversionFailedException + */ +void SignalDescriptorConverter::SetTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::BaseSignalPtr signal) +{ + daq::streaming_protocol::RuleType signalTimeRule = signal->getTimeRule(); + if ((rule == nullptr) && (signalTimeRule != daq::streaming_protocol::RULETYPE_EXPLICIT)) + { + // no rule is interpreted as explicit rule + + // changing signal time rule is not allowed + throw ConversionFailedException(); + } + switch (rule.getType()) + { + case DataRuleType::Linear: + { + daq::streaming_protocol::BaseSynchronousSignalPtr baseSyncSignal = + std::dynamic_pointer_cast(signal); + if (!baseSyncSignal) + { + // this is not a synchronous signal. + // changing signal time rule type is not allowed + throw ConversionFailedException(); + } + NumberPtr delta = rule.getParameters().get("delta"); + NumberPtr start = rule.getParameters().get("start"); + baseSyncSignal->setOutputRate(delta); + baseSyncSignal->setTimeStart(start); + } + break; + case daq::DataRuleType::Explicit: + // changing signal time rule is not allowed + if (signalTimeRule != daq::streaming_protocol::RULETYPE_EXPLICIT) + { + throw ConversionFailedException(); + } + break; + case daq::DataRuleType::Constant: + default: + throw ConversionFailedException(); + } +} + +/** + * @throws ConversionFailedException + */ +daq::SampleType SignalDescriptorConverter::Convert(daq::streaming_protocol::SampleType dataType) +{ + switch (dataType) + { + case daq::streaming_protocol::SampleType::SAMPLETYPE_S8: + return daq::SampleType::Int8; + case daq::streaming_protocol::SampleType::SAMPLETYPE_S16: + return daq::SampleType::Int16; + case daq::streaming_protocol::SampleType::SAMPLETYPE_S32: + return daq::SampleType::Int32; + case daq::streaming_protocol::SampleType::SAMPLETYPE_S64: + return daq::SampleType::Int64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_U8: + return daq::SampleType::UInt8; + case daq::streaming_protocol::SampleType::SAMPLETYPE_U16: + return daq::SampleType::UInt16; + case daq::streaming_protocol::SampleType::SAMPLETYPE_U32: + return daq::SampleType::UInt32; + case daq::streaming_protocol::SampleType::SAMPLETYPE_U64: + return daq::SampleType::UInt64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_COMPLEX32: + return daq::SampleType::ComplexFloat32; + case daq::streaming_protocol::SampleType::SAMPLETYPE_COMPLEX64: + return daq::SampleType::ComplexFloat64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_REAL32: + return daq::SampleType::Float64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_REAL64: + return daq::SampleType::Float64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD32: + case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD64: + default: + throw ConversionFailedException(); + } +} + +/** + * @throws ConversionFailedException + */ +daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::SampleType sampleType) +{ + switch (sampleType) + { + case daq::SampleType::Int8: + return daq::streaming_protocol::SampleType::SAMPLETYPE_S8; + break; + case daq::SampleType::Int16: + return daq::streaming_protocol::SampleType::SAMPLETYPE_S16; + break; + case daq::SampleType::Int32: + return daq::streaming_protocol::SampleType::SAMPLETYPE_S32; + break; + case daq::SampleType::Int64: + return daq::streaming_protocol::SampleType::SAMPLETYPE_S64; + break; + case daq::SampleType::ComplexFloat32: + return daq::streaming_protocol::SampleType::SAMPLETYPE_COMPLEX32; + break; + case daq::SampleType::ComplexFloat64: + return daq::streaming_protocol::SampleType::SAMPLETYPE_COMPLEX64; + break; + case daq::SampleType::Float32: + return daq::streaming_protocol::SampleType::SAMPLETYPE_REAL32; + break; + case daq::SampleType::Float64: + return daq::streaming_protocol::SampleType::SAMPLETYPE_REAL64; + break; + case daq::SampleType::UInt8: + return daq::streaming_protocol::SampleType::SAMPLETYPE_U8; + break; + case daq::SampleType::UInt16: + return daq::streaming_protocol::SampleType::SAMPLETYPE_U16; + break; + case daq::SampleType::UInt32: + return daq::streaming_protocol::SampleType::SAMPLETYPE_U32; + break; + case daq::SampleType::UInt64: + return daq::streaming_protocol::SampleType::SAMPLETYPE_U64; + break; + case daq::SampleType::Binary: + case daq::SampleType::Invalid: + case daq::SampleType::String: + case daq::SampleType::RangeInt64: + default: + throw ConversionFailedException(); + } +} + +void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra) +{ + extra["sampleType"] = dataDescriptor.getSampleType(); + + if (dataDescriptor.getName().assigned()) + extra["name"] = dataDescriptor.getName(); + + if (dataDescriptor.getMetadata().assigned()) + { + auto meta = extra["metadata"]; + for (const auto& [key, value] : dataDescriptor.getMetadata()) + meta[key.getCharPtr()] = value; + extra["metadata"] = meta; + } + + if (dataDescriptor.getUnit().assigned()) + { + auto unit1 = dataDescriptor.getUnit(); + extra["unit"]["id"] = unit1.getId(); + extra["unit"]["name"] = unit1.getName(); + extra["unit"]["symbol"] = unit1.getSymbol(); + extra["unit"]["quantity"] = unit1.getQuantity(); + } + + if (dataDescriptor.getValueRange().assigned()) + { + auto range = dataDescriptor.getValueRange(); + extra["range"]["low"] = range.getLowValue(); + extra["range"]["high"] = range.getHighValue(); + } + + if (dataDescriptor.getOrigin().assigned()) + extra["origin"] = dataDescriptor.getOrigin(); + + if (dataDescriptor.getRule().assigned()) + { + auto rule = dataDescriptor.getRule(); + extra["rule"]["type"] = rule.getType(); + extra["rule"]["parameters"] = DictToJson(rule.getParameters()); + } + + if (dataDescriptor.getPostScaling().assigned()) + { + auto scaling = dataDescriptor.getPostScaling(); + extra["scaling"]["inputType"] = scaling.getInputSampleType(); + extra["scaling"]["outputType"] = scaling.getOutputSampleType(); + extra["scaling"]["scalingType"] = scaling.getType(); + extra["scaling"]["parameters"] = DictToJson(scaling.getParameters()); + } +} + +void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor) +{ + if (extra.count("name") > 0) + dataDescriptor.setName(extra["name"]); + + if (extra.count("metadata") > 0) + { + auto meta = JsonToDict(extra["metadata"]); + dataDescriptor.setMetadata(meta); + } + + if (extra.count("unit") > 0) + { + auto unitObj = extra["unit"]; + auto unit = Unit(unitObj["symbol"], unitObj["id"], unitObj["name"], unitObj["quantity"]); + dataDescriptor.setUnit(unit); + } + + if (extra.count("range") > 0) + { + auto rangeObj = extra["range"]; + auto low = std::stoi(std::string(rangeObj["low"])); + auto high = std::stoi(std::string(rangeObj["high"])); + auto range = Range(low, high); + dataDescriptor.setValueRange(range); + } + + if (extra.count("origin") > 0) + dataDescriptor.setOrigin(extra["origin"]); + + if (extra.count("rule") > 0) + { + auto params = JsonToDict(extra["rule"]["parameters"]); + params.freeze(); + + auto rule = DataRuleBuilder().setType(extra["rule"]["type"]).setParameters(params).build(); + dataDescriptor.setRule(rule); + } + + if (extra.count("scaling") > 0) + { + auto params = JsonToDict(extra["scaling"]["parameters"]); + params.freeze(); + + auto scaling = ScalingBuilder() + .setInputDataType(extra["scaling"]["inputType"]) + .setOutputDataType(extra["scaling"]["outputType"]) + .setScalingType(extra["scaling"]["scalingType"]) + .setParameters(params) + .build(); + dataDescriptor.setPostScaling(scaling); + } + + if (extra.count("sampleType") > 0) + { + SampleType sampleType = (SampleType) extra["sampleType"]; + dataDescriptor.setSampleType(sampleType); + } +} + +nlohmann::json SignalDescriptorConverter::DictToJson(const DictPtr& dict) +{ + nlohmann::json json; + + for (const auto& [key, value] : dict) + { + if (value.asPtrOrNull().assigned()) + json[key.getCharPtr()] = value.asPtr().toVector(); + else if (value.asPtrOrNull().assigned()) + json[key.getCharPtr()] = DictToJson(value); + else if (value.asPtrOrNull().assigned()) + json[key.getCharPtr()] = (Float) value; + else if (value.asPtrOrNull().assigned()) + json[key.getCharPtr()] = (Int) value; + else + json[key.getCharPtr()] = value; + } + + return json; +} + +DictPtr SignalDescriptorConverter::JsonToDict(const nlohmann::json& json) +{ + auto dict = Dict(); + auto items = json.items(); + + for (const auto& entry : items) + { + if (entry.value().is_array()) + { + auto vect = entry.value().get>(); + dict[entry.key()] = ListPtr::FromVector(vect); + } + else if (entry.value().is_object()) + dict[entry.key()] = JsonToDict(entry.value()); + else if (entry.value().is_number_float()) + dict[entry.key()] = entry.value().get(); + else if (entry.value().is_number_integer()) + dict[entry.key()] = entry.value().get(); + else + dict[entry.key()] = entry.value(); + } + + return dict; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp new file mode 100644 index 0000000..0f69821 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -0,0 +1,297 @@ +#include "websocket_streaming/streaming_client.h" +#include +#include +#include +#include +#include "stream/WebsocketClientStream.hpp" +#include "streaming_protocol/SignalContainer.hpp" +#include "opendaq/custom_log.h" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +using namespace daq::stream; +using namespace daq::streaming_protocol; + +StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString) + : logger(context.getLogger()) + , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) + , logCallback( [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { + this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); + }) + , signalContainer(logCallback) +{ + parseConnectionString(connectionString); +} + +StreamingClient::StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target) + : logger(context.getLogger()) + , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) + , logCallback( [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { + this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); + }) + , host(host) + , port(port) + , target(target) + , signalContainer(logCallback) +{ +} + +StreamingClient::~StreamingClient() +{ + disconnect(); +} + +bool StreamingClient::connect() +{ + if (connected) + return true; + if (host.empty() || port == 0) + return false; + + connected = false; + + auto signalMetaCallback = [this](const SubscribedSignal& subscribedSignal, const std::string& method, const nlohmann::json& params) + { onSignalMeta(subscribedSignal, method, params); }; + + auto protocolMetaCallback = [this](ProtocolHandler& protocolHandler, const std::string& method, const nlohmann::json& params) + { onProtocolMeta(protocolHandler, method, params); }; + + auto messageCallback = [this](const SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size) + { onMessage(subscribedSignal, timeStamp, data, size); }; + + signalContainer.setSignalMetaCb(signalMetaCallback); + signalContainer.setDataAsRawCb(messageCallback); + + auto clientStream = std::make_unique(ioContext, host, std::to_string(port), target); + protocolHandler = std::make_shared(ioContext, signalContainer, protocolMetaCallback, logCallback); + std::unique_lock lock(clientMutex); + protocolHandler->startWithSyncInit(std::move(clientStream)); + + ioContext.restart(); + clientThread = std::thread([this]() { ioContext.run(); }); + + conditionVariable.wait_for(lock, connectTimeout, [this]() { return connected; }); + return connected; +} + +void StreamingClient::disconnect() +{ + if (clientThread.joinable()) + { + ioContext.stop(); + clientThread.join(); + connected = false; + } +} + +void StreamingClient::onPacket(const OnPacketCallback& callack) +{ + onPacketCallback = callack; +} + +void StreamingClient::onNewSignal(const OnSignalCallback& callback) +{ + onNewSignalCallback = callback; +} + +void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) +{ + onSignalUpdatedCallback = callback; +} + +void StreamingClient::onDomainDescriptor(const OnDomainDescriptorCallback& callback) +{ + onDomainDescriptorCallback = callback; +} + +void StreamingClient::onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback) +{ + onAvailableStreamingSignalsCb = callback; +} + +void StreamingClient::onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback) +{ + onAvailableDeviceSignalsCb = callback; +} + +void StreamingClient::onFindSignal(const OnFindSignalCallback& callback) +{ + onFindSignalCallback = callback; +} + +std::string StreamingClient::getHost() +{ + return host; +} + +uint16_t StreamingClient::getPort() +{ + return port; +} + +std::string StreamingClient::getTarget() +{ + return target; +} + +bool StreamingClient::isConnected() +{ + return connected; +} + +void StreamingClient::setConnectTimeout(std::chrono::milliseconds timeout) +{ + this->connectTimeout = timeout; +} + +void StreamingClient::parseConnectionString(const std::string& url) +{ + // this is not great but it is convenient until we have a way to pass configuration parameters to a client device + + host = ""; + port = 7414; + target = "/"; + + std::smatch match; + std::string suffix; + + auto regexHostname = std::regex("^(.*:\\/\\/)?([^:\\/\\s]+)"); + if (std::regex_search(url, match, regexHostname)) + host = match[2]; + else + return; + + auto regexPort = std::regex("^:(\\d+)"); + suffix = match.suffix().str(); + if (std::regex_search(suffix, match, regexPort)) + { + port = std::stoi(match[1]); + suffix = match.suffix().str(); + } + + auto regexTarget = std::regex("^\\/?[^\\?]+"); + if (std::regex_search(suffix, match, regexTarget)) + target = match[0]; + else + return; +} + +void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, const std::string& method, const nlohmann::json& params) +{ + if (method == daq::streaming_protocol::META_METHOD_SIGNAL) + onSignal(subscribedSignal, params); +} + +void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& protocolHandler, + const std::string& method, + const nlohmann::json& params) +{ + if (method == daq::streaming_protocol::META_METHOD_AVAILABLE) + { + std::vector signalIds; + auto availableSignals = params.find(META_SIGNALIDS); + + if (availableSignals != params.end() && availableSignals->is_array()) + { + for (const auto& arrayItem : *availableSignals) + signalIds.push_back(arrayItem); + + onAvailableDeviceSignalsCb(signalIds); + onAvailableStreamingSignalsCb(signalIds); + } + + std::unique_lock lock(clientMutex); + connected = true; + conditionVariable.notify_all(); + } +} + +void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + uint64_t timeStamp, + const uint8_t* data, + size_t size) +{ + std::string id = subscribedSignal.signalId(); + const auto& signalIter = signals.find(id); + if (signalIter == signals.end()) + return; + + auto packet = signalIter->second->asPacket(timeStamp, data, size); + onPacketCallback(id, packet); +} + +void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +{ + const auto id = subscribedSignal.signalId(); + + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + if (signals.count(id) == 0) + { + auto descriptor = sInfo.dataDescriptor; + onNewSignalCallback(id, sInfo); + + auto inputSignal = std::make_shared(); + inputSignal->setDataDescriptor(subscribedSignal); + signals[id] = inputSignal; + } + else + { + onSignalUpdatedCallback(id, sInfo); + signals[id]->setDataDescriptor(subscribedSignal); + } +} + +void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +{ + std::string tableId = subscribedSignal.tableId(); + + if (signals.count(tableId) == 0) + return; + + auto inputSignal = signals[tableId]; + inputSignal->setDomainDescriptor(subscribedSignal); + + // Sets the descriptors when first connecting + if (!connected) + { + auto domainDescriptor = inputSignal->getDomainSignalDescriptor(); + onDomainDescriptorCallback(tableId, domainDescriptor); + } +} + +void StreamingClient::publishSignal(const std::string& signalId) +{ + if (signals.count(signalId) == 0) + return; + + auto inputSignal = signals[signalId]; + if (!inputSignal->hasDescriptors()) + return; + + auto eventPacket = inputSignal->createDecriptorChangedPacket(); + onPacketCallback(signalId, eventPacket); +} + +void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params) +{ + try + { + if (subscribedSignal.isTimeSignal()) + setTimeSignal(subscribedSignal); + else + setDataSignal(subscribedSignal); + + // signal meta information is always received by pairs of META_METHOD_SIGNAL: + // first is meta for data signal, second is meta for artificial time signal. + // we call "publishSignal" which generates event packet only after both meta are received + // and all signal descriptors are updated. + if (subscribedSignal.isTimeSignal()) + publishSignal(subscribedSignal.tableId()); + } + catch (const DaqException& e) + { + LOG_W("Failed to interpret received input signal: {}.", e.what()); + } +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp new file mode 100644 index 0000000..6c84822 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -0,0 +1,133 @@ +#include + +#include +#include +#include + +#include "websocket_streaming/streaming_server.h" +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +using namespace daq::streaming_protocol; +using namespace daq::stream; + +StreamingServer::StreamingServer(const ContextPtr& context) + : work(ioContext.get_executor()) + , logger(context.getLogger()) +{ + if (!this->logger.assigned()) + throw ArgumentNullException("Logger must not be null"); + loggerComponent = this->logger.addComponent("StreamingServer"); + logCallback = [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { + this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); + }; +} + +StreamingServer::~StreamingServer() +{ + stop(); + logger.removeComponent("StreamingServer"); +} + +void StreamingServer::start(uint16_t port) +{ + this->port = port; + + ioContext.restart(); + + auto acceptFunc = [this](StreamPtr stream) { this->onAcceptInternal(stream); }; + + this->server = std::make_unique(ioContext, acceptFunc, port); + this->server->start(); + this->serverThread = std::thread([this]() { this->ioContext.run(); }); +} + +void StreamingServer::stop() +{ + ioContext.stop(); + + if (!serverThread.joinable()) + return; + + this->server->stop(); + serverThread.join(); + this->server.reset(); +} + +void StreamingServer::onAccept(const OnAcceptCallback& callback) +{ + onAcceptCallback = callback; +} + +void StreamingServer::unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, + const std::string& signalId, + const PacketPtr& packet) +{ + auto& signals = clients[client]; + if (signals.count(signalId) > 0) + signals[signalId]->write(packet); +} + +void StreamingServer::broadcastPacket(const std::string& signalId, const PacketPtr& packet) +{ + for (auto& [_, client] : clients) + if (client.count(signalId) > 0) + client[signalId]->write(packet); +} + +void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) +{ + auto writer = std::make_shared(stream); + writeProtocolInfo(writer); + + auto signals = List(); + if (onAcceptCallback) + signals = onAcceptCallback(writer); + + auto outputSignals = std::unordered_map(); + for (const auto& signal : signals) + { + // TODO: We skip domain signals for now. + if (!signal.getDomainSignal().assigned()) + continue; + + try + { + const auto outputSignal = std::make_shared(writer, signal, logCallback); + outputSignals.insert({signal.getGlobalId(), outputSignal}); + } + catch (const DaqException&) + { + LOG_W("Failed to create an ouput websocket signal."); + } + } + + clients.insert({writer, outputSignals}); + writeSignalsAvailable(writer, signals); +} + +void StreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json msg; + msg[METHOD] = META_METHOD_APIVERSION; + msg[PARAMS][VERSION] = OPENDAQ_LT_STREAM_VERSION; + writer->writeMetaInformation(0, msg); +} + +void StreamingServer::writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals) +{ + std::vector signalIds; + + for (const auto& signal : signals) + signalIds.push_back(signal.getGlobalId()); + + nlohmann::json msg; + msg[METHOD] = META_METHOD_AVAILABLE; + msg[PARAMS][META_SIGNALIDS] = signalIds; + writer->writeMetaInformation(0, msg); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp new file mode 100644 index 0000000..edc8fd1 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -0,0 +1,151 @@ +#include "websocket_streaming/websocket_client_device_impl.h" +#include +#include +#include +#include "websocket_streaming/websocket_client_signal_factory.h" +#include "websocket_streaming/websocket_streaming_factory.h" +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +WebsocketClientDeviceImpl::WebsocketClientDeviceImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const StringPtr& localId, + const StringPtr& connectionString) + : Device(ctx, parent, localId) + , connectionString(connectionString) +{ + if (!this->connectionString.assigned()) + throw ArgumentNullException("connectionString cannot be null"); + createWebsocketStreaming(); + activateStreaming(); +} + +DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() +{ + if (deviceInfo != nullptr) + return deviceInfo; + + deviceInfo = DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); + deviceInfo.freeze(); + return deviceInfo; +} + +void WebsocketClientDeviceImpl::activateStreaming() +{ + auto self = this->borrowPtr(); + const auto signals = self.getSignals(); + websocketStreaming.addSignals(signals); + websocketStreaming.setActive(true); + + for (const auto& signal : signals) + { + auto signalConfigPtr = signal.asPtr(); + signalConfigPtr.setActiveStreamingSource(websocketStreaming.getConnectionString()); + } +} + +void WebsocketClientDeviceImpl::createWebsocketStreaming() +{ + auto streamingClient = std::make_shared(context, connectionString); + + auto newSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) + { + this->onNewSignal(signalId, sInfo); + }; + streamingClient->onNewSignal(newSignalCallback); + + auto signalUpdatedCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) + { + this->onSignalUpdated(signalId, sInfo); + }; + streamingClient->onSignalUpdated(signalUpdatedCallback); + + auto domainDescriptorCallback = [this](const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor) + { + this->onDomainDescriptor(signalId, domainDescriptor); + }; + streamingClient->onDomainDescriptor(domainDescriptorCallback); + + auto availableSignalsCallback = [this](const std::vector& signalIds) + { + this->initializeDeviceSignals(signalIds); + }; + streamingClient->onAvailableDeviceSignals(availableSignalsCallback); + + websocketStreaming = WebsocketStreaming(streamingClient, connectionString, context); +} + +void WebsocketClientDeviceImpl::onNewSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +{ + if (!sInfo.dataDescriptor.assigned()) + return; + + registerSignalAttributes(signalId, sInfo); +} + +void WebsocketClientDeviceImpl::onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +{ + if (!sInfo.dataDescriptor.assigned()) + return; + + if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) + updateSignal(signalIt->second, sInfo); +} + +void WebsocketClientDeviceImpl::onDomainDescriptor(const StringPtr& signalId, + const DataDescriptorPtr& domainDescriptor) +{ + if (!domainDescriptor.assigned()) + return; + + // Sets domain descriptor for data signal attributes + if (auto iter = deviceSignalsAttributes.find(signalId); iter != deviceSignalsAttributes.end()) + { + auto& signalAttributes = iter->second; + signalAttributes.first = domainDescriptor; + } +} + +void WebsocketClientDeviceImpl::initializeDeviceSignals(const std::vector& signalIds) +{ + // Adds to device only signals published by server explicitly and in the same order these were published + for (const auto& signalId : signalIds) + { + + if (auto iter = deviceSignalsAttributes.find(signalId); iter == deviceSignalsAttributes.end()) + { + // TODO Streaming didn't provide META info for Time Signals - + // - for these signals device provides incomplete mirrored signals (without descriptor) + auto signal = WebsocketClientSignal(this->context, this->signals, nullptr, nullptr, signalId); + this->addSignal(signal); + deviceSignals.insert({signalId, signal}); + } + else + { + // Streaming provided META info for the Signal - device has complete signal (with descriptors) + auto [domainDescriptor, sInfo] = iter->second; + + auto signal = WebsocketClientSignal(this->context, this->signals, sInfo.dataDescriptor, domainDescriptor, signalId); + this->addSignal(signal); + deviceSignals.insert({signalId, signal}); + updateSignal(signal, sInfo); + } + } +} + +void WebsocketClientDeviceImpl::registerSignalAttributes(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +{ + deviceSignalsAttributes.insert({signalId, {nullptr, sInfo}}); +} + +void WebsocketClientDeviceImpl::updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo) +{ + auto protectedObject = signal.asPtr(); + if (sInfo.signalProps.name.has_value()) + protectedObject.setProtectedPropertyValue("Name", sInfo.signalProps.name.value()); + if (sInfo.signalProps.description.has_value()) + protectedObject.setProtectedPropertyValue("Description", sInfo.signalProps.description.value()); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp new file mode 100644 index 0000000..346963b --- /dev/null +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include "websocket_streaming/websocket_client_signal_impl.h" + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +static constexpr char delimeter = '#'; + +WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const DataDescriptorPtr& descriptor, + const DataDescriptorPtr& domainDescriptor, + const StringPtr& streamingId) + : SignalRemote(ctx, parent, CreateLocalId(streamingId)) + , streamingId(streamingId) + , mirroredDataDescriptor(descriptor) +{ + if (domainDescriptor.assigned()) + domainSignalArtificial = SignalWithDescriptor(ctx, + domainDescriptor, + parent, + CreateLocalId(streamingId+"_time_artificial")); +} + +StringPtr WebsocketClientSignalImpl::CreateLocalId(const StringPtr& streamingId) +{ + std::string localId = streamingId; + + const char slash = '/'; + std::replace(localId.begin(), localId.end(), slash, delimeter); + + return String(localId); +} + +StringPtr WebsocketClientSignalImpl::onGetRemoteId() const +{ + return streamingId; +} + +ErrCode WebsocketClientSignalImpl::getDescriptor(IDataDescriptor** descriptor) +{ + OPENDAQ_PARAM_NOT_NULL(descriptor); + + std::scoped_lock lock(this->sync); + + *descriptor = mirroredDataDescriptor.addRefAndReturn(); + return OPENDAQ_SUCCESS; +} + +ErrCode WebsocketClientSignalImpl::getDomainSignal(ISignal** signal) +{ + OPENDAQ_PARAM_NOT_NULL(signal); + + std::scoped_lock lock(this->sync); + + *signal = domainSignalArtificial.addRefAndReturn(); + return OPENDAQ_SUCCESS; +} + +Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) +{ + if (eventPacket.assigned() && eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) + { + const auto params = eventPacket.getParameters(); + DataDescriptorPtr newSignalDescriptor = params[event_packet_param::DATA_DESCRIPTOR]; + DataDescriptorPtr newDomainDescriptor = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; + + if (newSignalDescriptor.assigned()) + { + mirroredDataDescriptor = newSignalDescriptor; + } + + if (domainSignalArtificial.assigned() && newDomainDescriptor.assigned()) + { + domainSignalArtificial.setDescriptor(newDomainDescriptor); + } + } + + // No new duplicated event packets have been created so returns true to forward original packet + return True; +} + +EventPacketPtr WebsocketClientSignalImpl::createDataDescriptorChangedEventPacket() +{ + DataDescriptorPtr domainDescriptor; + if (domainSignalArtificial.assigned()) + domainDescriptor = domainSignalArtificial.getDescriptor(); + return DataDescriptorChangedEventPacket(mirroredDataDescriptor, domainDescriptor); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp new file mode 100644 index 0000000..925c816 --- /dev/null +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -0,0 +1,145 @@ +#include "websocket_streaming/websocket_streaming_impl.h" +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +WebsocketStreamingImpl::WebsocketStreamingImpl(const StringPtr& connectionString, + const ContextPtr& context) + : WebsocketStreamingImpl(std::make_shared(context, connectionString), + connectionString, + context) +{ +} + +WebsocketStreamingImpl::WebsocketStreamingImpl(StreamingClientPtr streamingClient, + const StringPtr& connectionString, + const ContextPtr& context) + : Streaming(connectionString, context) + , streamingClient(streamingClient) +{ + prepareStreamingClient(); + if (!this->streamingClient->connect()) + throw NotFoundException("Failed to connect to websocket server url: {}", connectionString); +} + +void WebsocketStreamingImpl::onSetActive(bool active) +{ +} + +StringPtr WebsocketStreamingImpl::onAddSignal(const SignalRemotePtr& signal) +{ + StringPtr signalStreamingId = this->getSignalStreamingId(signal); + if ( !signalStreamingId.assigned() ) + throw NotFoundException("Signal with id {} is not available in Websocket streaming", signal.getRemoteId()); + + handleCachedEventPackets(signalStreamingId, signal); + return signalStreamingId; +} + +void WebsocketStreamingImpl::onRemoveSignal(const SignalRemotePtr& /*signal*/) +{ +} + +void WebsocketStreamingImpl::prepareStreamingClient() +{ + auto packetCallback = [this](const StringPtr& signalId, const PacketPtr& packet) + { + this->onPacket(signalId, packet); + }; + streamingClient->onPacket(packetCallback); + + auto availableSignalsCallback = [this](const std::vector& signalIds) + { + this->onAvailableSignals(signalIds); + }; + streamingClient->onAvailableStreamingSignals(availableSignalsCallback); +} + +void WebsocketStreamingImpl::handleEventPacket(const StringPtr& signalId, const EventPacketPtr& eventPacket) +{ + if (auto it = streamingSignals.find(signalId); it != streamingSignals.end()) + { + SignalRemotePtr signal = it->second; + Bool forwardPacket = signal.triggerEvent(eventPacket); + auto signalConfig = signal.asPtr(); + auto sourceStreamingConnectionString = signalConfig.getActiveStreamingSource(); + if (sourceStreamingConnectionString == connectionString && isActive && forwardPacket) + { + signalConfig.sendPacket(eventPacket); + } + } + else + { + cachedEventPackets[signalId].push_back(eventPacket); + } +} + +void WebsocketStreamingImpl::handleCachedEventPackets(const StringPtr& signalStreamingId, + const SignalRemotePtr& signal) +{ + if (auto it = cachedEventPackets.find(signalStreamingId); it != cachedEventPackets.end()) + { + for (const auto& eventPacket : it->second) + signal.triggerEvent(eventPacket); + cachedEventPackets.erase(it); + } +} + +void WebsocketStreamingImpl::handleDataPacket(const StringPtr& signalId, const PacketPtr& dataPacket) +{ + if (auto it = streamingSignals.find(signalId); it != streamingSignals.end() && isActive) + { + auto signal = (it->second).asPtr(); + auto sourceStreamingConnectionString = signal.getActiveStreamingSource(); + if (sourceStreamingConnectionString == connectionString) + { + signal.sendPacket(dataPacket); + } + } +} + +void WebsocketStreamingImpl::onPacket(const StringPtr& signalId, const PacketPtr& packet) +{ + if (!packet.assigned()) + return; + + const auto eventPacket = packet.asPtrOrNull(); + if (eventPacket.assigned()) + { + handleEventPacket(signalId, eventPacket); + } + else + { + handleDataPacket(signalId, packet); + } +} + +void WebsocketStreamingImpl::onAvailableSignals(const std::vector& signalIds) +{ + availableSignalIds = signalIds; +} + +StringPtr WebsocketStreamingImpl::getSignalStreamingId(const SignalRemotePtr &signal) +{ + std::string signalFullId = signal.getRemoteId().toStdString(); + const auto it = std::find_if( + availableSignalIds.begin(), + availableSignalIds.end(), + [signalFullId](std::string idEnding) + { + if (idEnding.size() > signalFullId.size()) + return false; + return std::equal(idEnding.rbegin(), idEnding.rend(), signalFullId.rbegin()); + } + ); + + if (it != availableSignalIds.end()) + return String(*it); + else + return nullptr; +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp new file mode 100644 index 0000000..e3f0ade --- /dev/null +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include + +using namespace daq::streaming_protocol; + +void daqInitStreamingLibrary() +{ + auto writer = StreamWriter(nullptr); + auto dummySignal = SynchronousSignal("id", 100, 1000, writer, Logging::logCallback()); +} diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp new file mode 100644 index 0000000..73517eb --- /dev/null +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include "websocket_streaming/websocket_streaming_server.h" +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +WebsocketStreamingServer::~WebsocketStreamingServer() +{ + stopInternal(); +} + +WebsocketStreamingServer::WebsocketStreamingServer(const InstancePtr& instance) + : WebsocketStreamingServer(instance.getRootDevice(), instance.getContext()) +{ +} + +WebsocketStreamingServer::WebsocketStreamingServer(const DevicePtr& device, const ContextPtr& context) + : device(device) + , context(context) + , streamingServer(context) +{ +} + +void WebsocketStreamingServer::setStreamingPort(uint16_t port) +{ + this->streamingPort = port; +} + +void WebsocketStreamingServer::start() +{ + if (!device.assigned()) + throw InvalidStateException("Device is not set."); + if (!context.assigned()) + throw InvalidStateException("Context is not set."); + if (streamingPort == 0) + return; + + streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignalsRecursive(); }); + streamingServer.start(streamingPort); + + packetReader.setLoopFrequency(50); + packetReader.onPacket([this](const SignalPtr& signal, const ListPtr& packets) { + const auto signalId = signal.getGlobalId(); + for (const auto& packet : packets) + streamingServer.broadcastPacket(signalId, packet); + }); + packetReader.startReading(device, context); + + StreamingInfoConfigPtr streamingInfo = StreamingInfo("daq.wss"); + streamingInfo.addProperty(IntProperty("Port", streamingPort)); + ErrCode errCode = this->device.asPtr()->addStreamingOption(streamingInfo); + checkErrorInfo(errCode); +} + +void WebsocketStreamingServer::stop() +{ + ErrCode errCode = + this->device.asPtr()->removeStreamingOption(String("daq.wss")); + checkErrorInfo(errCode); + stopInternal(); +} + +void WebsocketStreamingServer::stopInternal() +{ + packetReader.stopReading(); + streamingServer.stop(); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt new file mode 100644 index 0000000..226bb17 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +set(MODULE_NAME ${SDK_TARGET_NAME}_websocket_streaming) +set(TEST_APP test_${MODULE_NAME}) + +add_executable(${TEST_APP} + streaming_test_helpers.h + test_signal_descriptor_converter.cpp + test_streaming.cpp + test_websocket_client_device.cpp + test_signal_generator.cpp +) + +target_link_libraries(${TEST_APP} PRIVATE + ${SDK_TARGET_NAMESPACE}::${MODULE_NAME} + daq::opendaq + daq::opendaq_mocks + daq::streaming_protocol + GTest::GTest GTest::Main +) + +set_target_properties(${TEST_APP} PROPERTIES DEBUG_POSTFIX _debug) +target_include_directories(${TEST_APP} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY bin +) + +if(OPENDAQ_ENABLE_COVERAGE) + setup_target_for_coverage(${MODULE_NAME}coverage ${TEST_APP} ${MODULE_NAME}coverage) +endif() diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h new file mode 100644 index 0000000..fb1ffc9 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -0,0 +1,113 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "streaming_protocol/Unit.hpp" + +namespace streaming_test_helpers +{ + inline daq::InstancePtr createServerInstance() + { + const auto moduleManager = daq::ModuleManager("[[none]]"); + auto context = Context(nullptr, daq::Logger(), daq::TypeManager(), moduleManager); + const daq::ModulePtr deviceModule(MockDeviceModule_Create(context)); + moduleManager.addModule(deviceModule); + + const daq::ModulePtr fbModule(MockFunctionBlockModule_Create(context)); + moduleManager.addModule(fbModule); + + auto instance = InstanceCustom(context, "localInstance"); + instance.addDevice("daq_client_device"); + instance.addDevice("mock_phys_device"); + instance.addFunctionBlock("mock_fb_uid"); + + return instance; + } + + inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx) + { + const size_t nanosecondsInSecond = 1000000000; + auto delta = nanosecondsInSecond / 1000; + + auto descriptor = daq::DataDescriptorBuilder() + .setSampleType(daq::SampleType::UInt64) + .setRule(daq::LinearDataRule(delta, 100)) + .setTickResolution(daq::Ratio(1, nanosecondsInSecond)) + .setOrigin(daq::streaming_protocol::UNIX_EPOCH_DATE_UTC_TIME) + .setUnit(daq::Unit("s", daq::streaming_protocol::Unit::UNIT_ID_SECONDS)) + .setName("Time") + .build(); + + return SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + } + + inline daq::SignalPtr createTestSignal(const daq::ContextPtr& ctx) + { + auto meta = daq::Dict(); + meta["color"] = "green"; + meta["used"] = "0"; + + auto descriptor = daq::DataDescriptorBuilder() + .setSampleType(daq::SampleType::Float64) + .setUnit(daq::Unit("V", 1, "voltage", "quantity")) + .setValueRange(daq::Range(0, 10)) + .setRule(daq::ExplicitDataRule()) + .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) + .setName("TestSignal") + .setMetadata(meta) + .build(); + + auto timeSignal = createTimeSignal(ctx); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + signal.setDomainSignal(timeSignal); + signal.setName("TestName"); + signal.setDescription("TestDescription"); + return signal; + } + + inline daq::SignalPtr createTestSignalWithoutDomain(const daq::ContextPtr& ctx) + { + auto meta = daq::Dict(); + meta["color"] = "green"; + meta["used"] = "0"; + + auto descriptor = daq::DataDescriptorBuilder() + .setSampleType(daq::SampleType::Float64) + .setUnit(daq::Unit("V", 1, "voltage", "quantity")) + .setValueRange(daq::Range(0, 10)) + .setRule(daq::ExplicitDataRule()) + .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) + .setName("TestSignal") + .setMetadata(meta) + .build(); + + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + return signal; + } +} diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp new file mode 100644 index 0000000..82f551d --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -0,0 +1,346 @@ +#include + +#include "gtest/gtest.h" + +#include "nlohmann/json.hpp" + +#include "streaming_protocol/Defines.h" +#include "streaming_protocol/SubscribedSignal.hpp" +#include "streaming_protocol/SynchronousSignal.hpp" +#include "streaming_protocol/iWriter.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "websocket_streaming/signal_descriptor_converter.h" +#include +#include +#include +#include +#include +#include +#include +#include "streaming_protocol/Logging.hpp" +#include + +namespace bsp = daq::streaming_protocol; + +BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING + +static uint64_t TimeTicksPerSecond = 1000000000; + +class DummayWriter : public bsp::iWriter +{ +public: + virtual int writeMetaInformation(unsigned int signalNumber, const nlohmann::json& data) override + { + metaInformations.emplace_back(signalNumber, data); + return 0; + } + virtual int writeSignalData(unsigned int, const void*, size_t) override + { + return 0; + } + + virtual std::string id() const override + { + static unsigned int id = 0; + return std::to_string(id); + ++id; + } + + std::vector> metaInformations; +}; + +SignalPtr createTimeSignal(const ContextPtr& ctx, uint64_t timeTicksPerSecond = TimeTicksPerSecond) +{ + uint64_t timeTickStart = 10000000; + uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), timeTicksPerSecond); + + auto descriptor = DataDescriptorBuilder() + .setSampleType(SampleType::Float64) + .setRule(LinearDataRule(outputRateInTicks, timeTickStart)) + .setTickResolution(Ratio(1, timeTicksPerSecond)) + .setName("Time") + .build(); + + return SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); +} + +SignalPtr createSineSignal(const ContextPtr& ctx) +{ + std::string signalId = "Sine"; + std::string memberName = "member name"; + int32_t unitId = 42; + std::string unitDisplayName = "some unit"; + + UnitPtr unit = Unit("V", unitId, "voltage"); + auto descriptor = + DataDescriptorBuilder().setSampleType(SampleType::Float64).setName(memberName).setName(memberName).setUnit(unit).build(); + + auto timeSignal = createTimeSignal(ctx); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, signalId); + signal.setDomainSignal(timeSignal); + return signal; +} + +SignalPtr createSineSignalWithPostScaling(const ContextPtr& ctx) +{ + std::string signalId = "Sine"; + std::string memberName = "member name"; + int32_t unitId = 42; + std::string unitDisplayName = "some unit"; + + double scale = 1; + double offset = 0; + UnitPtr unit = Unit("V", unitId, "voltage"); + + auto descriptor = DataDescriptorBuilder() + .setSampleType(SampleType::Float64) + .setName(memberName) + .setPostScaling(LinearScaling(scale, offset, SampleType::Int16, ScaledSampleType::Float64)) + .setUnit(unit) + .build(); + + auto timeSignal = createTimeSignal(ctx); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, signalId); + signal.setDomainSignal(timeSignal); + return signal; +} + +SignalPtr createStepSignal(const ContextPtr& ctx) +{ + auto dataDescriptor = DataDescriptorBuilder() + .setSampleType(SampleType::UInt8) + .setName("ByteStepValue") + .setUnit(Unit("V", 1, "voltage", "quantity")) + .setValueRange(Range(0, 10)) + .setRule(ExplicitDataRule()) + .setOrigin("1970-01-01T00:00:00") + .build(); + + auto meta = Dict(); + meta["color"] = "red"; + meta["used"] = "1"; + + auto descriptor = DataDescriptorBuilder().setSampleType(SampleType::Float64).setName("ByteStep").setMetadata(meta).build(); + + auto timeSignal = createTimeSignal(ctx); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, "time"); + signal.setDomainSignal(timeSignal); + return signal; +} + +TEST(SignalConverter, synchronousSignal) +{ + auto signal = createSineSignal(NullContext()); + auto domainDescriptor = signal.getDomainSignal().getDescriptor(); + auto dataDescriptor = signal.getDescriptor(); + auto start = domainDescriptor.getRule().getParameters().get("start"); + auto unit = dataDescriptor.getUnit(); + + DummayWriter dummyWriter; + // 1kHz + uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); + auto syncSigna1 = std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); + syncSigna1->setTimeStart(start); + + SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); + ASSERT_EQ(syncSigna1->getTimeDelta(), outputRateInTicks); + ASSERT_EQ(syncSigna1->getTimeStart(), start); + ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); + ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); + ASSERT_EQ(syncSigna1->getMemberName(), dataDescriptor.getName()); +} + +TEST(SignalConverter, TickResolution) +{ + auto signal = createSineSignal(NullContext()); + auto domainDescriptor = signal.getDomainSignal().getDescriptor(); + auto start = domainDescriptor.getRule().getParameters().get("start"); + + DummayWriter dummyWriter; + // 1kHz + uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); + auto syncSigna1 = + std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); + syncSigna1->setTimeStart(start); + + SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); + syncSigna1->writeSignalMetaInformation(); + + auto getTickResolution = [](const nlohmann::json& data) -> RatioPtr { + using namespace daq::streaming_protocol; + auto timeResolution = data[PARAMS][META_DEFINITION][META_RESOLUTION]; + return Ratio(timeResolution[META_NUMERATOR], timeResolution[META_DENOMINATOR]); + }; + + auto lastMetaInformation = *(--dummyWriter.metaInformations.end()); + ASSERT_EQ(getTickResolution(lastMetaInformation.second), Ratio(1, TimeTicksPerSecond)); + + auto newDomainSignal = createTimeSignal(NullContext(), 1000); + signal.asPtr(true).setDomainSignal(newDomainSignal); + + SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); + syncSigna1->writeSignalMetaInformation(); + + lastMetaInformation = *(--dummyWriter.metaInformations.end()); + ASSERT_EQ(getTickResolution(lastMetaInformation.second), Ratio(1, 1000)); +} + +TEST(SignalConverter, synchronousSignalWithPostScaling) +{ + auto signal = createSineSignalWithPostScaling(NullContext()); + auto domainDescriptor = signal.getDomainSignal().getDescriptor(); + auto valueDescriptor = signal.getDescriptor(); + auto start = domainDescriptor.getRule().getParameters().get("start"); + auto unit = valueDescriptor.getUnit(); + + DummayWriter dummyWriter; + // 1kHz + uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); + auto syncSigna1 = std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); + syncSigna1->setTimeStart(start); + + SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); + ASSERT_EQ(syncSigna1->getTimeDelta(), outputRateInTicks); + ASSERT_EQ(syncSigna1->getTimeStart(), start); + ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); + ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); + ASSERT_EQ(syncSigna1->getMemberName(), valueDescriptor.getName()); +} + +TEST(SignalConverter, DISABLED_subscribedDataSignal) +{ + std::string method; + int result; + unsigned int signalNumber = 3; + std::string tableId = "table id"; + std::string signalId = "signal id"; + std::string memberName = "This is the measured value"; + + nlohmann::json interpretationObject = "just a string"; + + int32_t unitId = 42; + std::string unitDisplayName = "some unit"; + + bsp::SubscribedSignal subscribedSignal(signalNumber, bsp::Logging::logCallback()); + + // some meta information is to be processed to have the signal described: + // -subscribe + // -signal + nlohmann::json subscribeParams; + method = bsp::META_METHOD_SUBSCRIBE; + subscribeParams[bsp::META_SIGNALID] = signalId; + result = subscribedSignal.processSignalMetaInformation(method, subscribeParams); + ASSERT_EQ(result, 0); + + nlohmann::json signalParams; + method = bsp::META_METHOD_SIGNAL; + signalParams[bsp::META_TABLEID] = tableId; + signalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; + signalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_REAL64; + signalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_EXPLICIT; + + signalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_UNIT_ID] = unitId; + signalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_DISPLAY_NAME] = unitDisplayName; + signalParams[bsp::META_INTERPRETATION] = interpretationObject; + result = subscribedSignal.processSignalMetaInformation(method, signalParams); + ASSERT_EQ(result, 0); + ASSERT_FALSE(subscribedSignal.isTimeSignal()); + + auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; + ASSERT_EQ(dataDescriptor.getName(), signalId); + + ASSERT_EQ(dataDescriptor.getName(), memberName); + daq::SampleType sampleType; + daq::ErrCode err = dataDescriptor->getSampleType(&sampleType); + ASSERT_EQ(err, OPENDAQ_SUCCESS); + ASSERT_EQ(sampleType, daq::SampleType::Float64); + + daq::UnitPtr unit; + dataDescriptor->getUnit(&unit); + ASSERT_EQ(unit.getId(), unitId); + ASSERT_EQ(unit.getSymbol(), unitDisplayName); + daq::DataRulePtr rule; + dataDescriptor->getRule(&rule); + ASSERT_EQ(daq::DataRuleType::Explicit, rule.getType()); +} + +TEST(SignalConverter, DISABLED_subscribedTimeSignal) +{ + std::string method; + int result; + unsigned int signalNumber = 3; + std::string tableId = "table id"; + std::string signalId = "signal id"; + std::string memberName = "This is the time"; + + uint64_t ticksPerSecond = 10000000; + uint64_t startTime = 100000; + uint64_t linearDelta = 1000; + int32_t unitId = bsp::Unit::UNIT_ID_SECONDS; + std::string unitDisplayName = "s"; + + bsp::SubscribedSignal subscribedSignal(signalNumber, bsp::Logging::logCallback()); + + // some meta information is to be processed to have the signal described: + // -subscribe + // -signal + // -set the time + nlohmann::json subscribeParams; + method = bsp::META_METHOD_SUBSCRIBE; + + subscribeParams[bsp::META_SIGNALID] = signalId; + result = subscribedSignal.processSignalMetaInformation(method, subscribeParams); + ASSERT_EQ(result, 0); + + nlohmann::json timeSignalParams; + method = bsp::META_METHOD_SIGNAL; + + timeSignalParams[bsp::META_TABLEID] = tableId; + timeSignalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; + timeSignalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_REAL64; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_LINEAR; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_RULETYPE_LINEAR][bsp::META_DELTA] = linearDelta; + timeSignalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_UINT64; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_UNIT_ID] = unitId; + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_DISPLAY_NAME] = unitDisplayName; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_ABSOLUTE_REFERENCE] = bsp::UNIX_EPOCH; + timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_RESOLUTION][bsp::META_NUMERATOR] = 1; + timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; + result = subscribedSignal.processSignalMetaInformation(method, timeSignalParams); + ASSERT_EQ(result, 0); + subscribedSignal.setTime(startTime); + ASSERT_TRUE(subscribedSignal.isTimeSignal()); + + auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; + ASSERT_EQ(dataDescriptor.getName(), signalId); + daq::SampleType sampleType; + ErrCode err = dataDescriptor->getSampleType(&sampleType); + ASSERT_EQ(err, OPENDAQ_SUCCESS); + ASSERT_EQ(sampleType, daq::SampleType::UInt64); + + daq::UnitPtr unit; + dataDescriptor->getUnit(&unit); + ASSERT_EQ(unit.getId(), unitId); + ASSERT_EQ(unit.getSymbol(), unitDisplayName); + daq::DataRulePtr rule; + dataDescriptor->getRule(&rule); + ASSERT_EQ(daq::DataRuleType::Linear, rule.getType()); + DictPtr params = rule.getParameters(); + ASSERT_EQ(params.getCount(), 2u); + uint64_t resultDelta = params.get("delta"); + uint64_t resultStart = params.get("start"); + ASSERT_EQ(resultDelta, linearDelta); + ASSERT_EQ(resultStart, startTime); +} + +END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp new file mode 100644 index 0000000..fc15d1b --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace daq; + +class SignalGeneratorTest : public testing::Test +{ +public: + ContextPtr context; + SignalConfigPtr signal; + SignalGenerator::GenerateSampleFunc stepFunction10; + SignalGenerator::GenerateSampleFunc stepFunction100; + + void SetUp() override + { + context = NullContext(); + signal = createSignal(); + initFunctions(); + } + + void initFunctions() + { + stepFunction10 = [](uint64_t tick, void* sampleOut) + { + int* intOut = (int*) sampleOut; + *intOut = tick % 10; + }; + + stepFunction100 = [](uint64_t tick, void* sampleOut) + { + int* intOut = (int*) sampleOut; + *intOut = tick % 100; + }; + } + + std::vector calculateExpectedSamples(uint64_t startTick, size_t sampleCount, const SignalGenerator::GenerateSampleFunc& function) + { + auto samples = std::vector(sampleCount); + + for (size_t i = 0; i < sampleCount; i++) + function(startTick + i, samples.data() + i); + + return samples; + } + + bool compareSamples(int* expected, void* packetData, size_t sampleCount) + { + return std::memcmp(expected, packetData, sampleCount * sizeof(int)) == 0; + } + +private: + SignalConfigPtr createTimeSignal() + { + const size_t nanosecondsInSecond = 1000000000; + auto delta = nanosecondsInSecond / 1000; + + auto descriptor = DataDescriptorBuilder() + .setSampleType(SampleType::UInt64) + .setRule(LinearDataRule(delta, 0)) + .setTickResolution(Ratio(1, nanosecondsInSecond)) + .setOrigin("1970-01-01T00:00:00") + .setName("Time") + .build(); + + return SignalWithDescriptor(context, descriptor, nullptr, "Time"); + } + + SignalConfigPtr createSignal() + { + auto descriptor = DataDescriptorBuilder().setSampleType(SampleType::Int32).setName("Step").build(); + + auto domainSignal = createTimeSignal(); + auto signal = SignalWithDescriptor(context, descriptor, nullptr, "ByteStep"); + signal.setDomainSignal(domainSignal); + return signal; + } +}; + + +TEST_F(SignalGeneratorTest, CreateSignal) +{ + auto reader = PacketReader(signal); + auto packets = reader.readAll(); + ASSERT_EQ(packets.getCount(), 1u); + ASSERT_EQ(packets[0].getType(), PacketType::Event); + + EventPacketPtr eventPacket = packets[0]; + ASSERT_EQ(eventPacket.getEventId(), event_packet_id::DATA_DESCRIPTOR_CHANGED); +} + +TEST_F(SignalGeneratorTest, StepSignal) +{ + const size_t packetSize = 100; + + auto expectedSamples1 = calculateExpectedSamples(0, packetSize, stepFunction10); + auto expectedSamples2 = calculateExpectedSamples(packetSize, packetSize, stepFunction10); + + auto reader = PacketReader(signal); + + auto generator = SignalGenerator(signal); + generator.setFunction(stepFunction10); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); + + auto packets = reader.readAll(); + ASSERT_EQ(packets.getCount(), 3u); + + auto packet1 = packets[1].asPtr(); + ASSERT_EQ(packet1.getSampleCount(), packetSize); + ASSERT_TRUE(compareSamples(expectedSamples1.data(), packet1.getData(), packetSize)); + + auto packet2 = packets[2].asPtr(); + ASSERT_EQ(packet2.getSampleCount(), packetSize); + ASSERT_TRUE(compareSamples(expectedSamples2.data(), packet2.getData(), packetSize)); +} + +TEST_F(SignalGeneratorTest, ChangeFunction) +{ + const size_t packetSize = 100; + + auto expectedSamples1 = calculateExpectedSamples(0, packetSize, stepFunction10); + auto expectedSamples2 = calculateExpectedSamples(packetSize, packetSize, stepFunction100); + + auto reader = PacketReader(signal); + + auto updateFunction = [this](SignalGenerator& generator, uint64_t packetOffset) + { + if (packetOffset > 0) + generator.setFunction(stepFunction100); + }; + + auto generator = SignalGenerator(signal); + generator.setFunction(stepFunction10); + generator.setUpdateFunction(updateFunction); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); + + auto packets = reader.readAll(); + ASSERT_EQ(packets.getCount(), 3u); + + auto packet1 = packets[1].asPtr(); + ASSERT_EQ(packet1.getSampleCount(), packetSize); + ASSERT_TRUE(compareSamples(expectedSamples1.data(), packet1.getData(), packetSize)); + + auto packet2 = packets[2].asPtr(); + ASSERT_EQ(packet2.getSampleCount(), packetSize); + ASSERT_TRUE(compareSamples(expectedSamples2.data(), packet2.getData(), packetSize)); +} diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp new file mode 100644 index 0000000..4df6d07 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -0,0 +1,138 @@ +#include +#include +#include + +#include +#include +#include "streaming_test_helpers.h" + + +using namespace daq; +using namespace daq::websocket_streaming; + +class StreamingTest : public testing::Test +{ +public: + const uint16_t StreamingPort = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT; + SignalPtr testDoubleSignal; + ContextPtr context; + + void SetUp() override + { + context = NullContext(); + testDoubleSignal = streaming_test_helpers::createTestSignal(context); + } + + void TearDown() override + { + } + + PacketPtr createDataPacket(const std::vector data, Int packetOffset = 0) + { + auto sampleCount = data.size(); + auto dataDescriptor = testDoubleSignal.getDescriptor(); + auto domainDescriptor = testDoubleSignal.getDomainSignal().getDescriptor(); + auto domainPacket = DataPacket(domainDescriptor, sampleCount, packetOffset); + return DataPacketWithDomain(domainPacket, dataDescriptor, sampleCount); + } +}; + +TEST_F(StreamingTest, Connect) +{ + auto server = std::make_shared(context); + server->start(StreamingPort); + + auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + + ASSERT_FALSE(client.isConnected()); + client.connect(); + + ASSERT_TRUE(client.isConnected()); + + client.disconnect(); + ASSERT_FALSE(client.isConnected()); +} + +TEST_F(StreamingTest, ConnectTimeout) +{ + auto server = std::make_shared(context); + server->start(StreamingPort); + + auto client = StreamingClient(context, "127.0.0.1", 7000); + + client.connect(); + ASSERT_FALSE(client.isConnected()); +} + +TEST_F(StreamingTest, ConnectTwice) +{ + auto server = std::make_shared(context); + server->start(StreamingPort); + + auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + + ASSERT_TRUE(client.connect()); + client.disconnect(); + + ASSERT_TRUE(client.connect()); + ASSERT_TRUE(client.isConnected()); + client.disconnect(); + + ASSERT_FALSE(client.isConnected()); +} + +TEST_F(StreamingTest, ParseConnectString) +{ + auto client = std::make_shared(NullContext(), "daq.wss://127.0.0.1"); + ASSERT_EQ(client->getPort(), daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); + ASSERT_EQ(client->getHost(), "127.0.0.1"); + ASSERT_EQ(client->getTarget(), "/"); + + client = std::make_shared(NullContext(), "daq.wss://localhost/path/other"); + ASSERT_EQ(client->getPort(), daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); + ASSERT_EQ(client->getHost(), "localhost"); + ASSERT_EQ(client->getTarget(), "/path/other"); + + client = std::make_shared(NullContext(), "daq.wss://localhost:3000/path/other"); + ASSERT_EQ(client->getPort(), 3000u); + ASSERT_EQ(client->getHost(), "localhost"); + ASSERT_EQ(client->getTarget(), "/path/other"); +} + +TEST_F(StreamingTest, SimpePacket) +{ + std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; + auto packet = createDataPacket(data, 100); + + auto server = std::make_shared(context); + server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testDoubleSignal); + return signals; + }); + server->start(StreamingPort); + + std::vector receivedPackets; + auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + + auto onPacket = [&receivedPackets](const StringPtr& signalId, const PacketPtr& packet) + { + receivedPackets.push_back(packet); + }; + + auto findSignal = [&](const StringPtr& signalId) { return testDoubleSignal; }; + + client.onPacket(onPacket); + client.onFindSignal(findSignal); + client.connect(); + ASSERT_TRUE(client.isConnected()); + + std::string signalId = testDoubleSignal.getGlobalId(); + server->broadcastPacket(signalId, packet); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + ASSERT_EQ(receivedPackets.size(), 2u); + ASSERT_EQ(receivedPackets[0].asPtr().getEventId(), event_packet_id::DATA_DESCRIPTOR_CHANGED); + ASSERT_TRUE(BaseObjectPtr::Equals(packet, receivedPackets[1])); +} diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp new file mode 100644 index 0000000..65b3c58 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include +#include "streaming_test_helpers.h" + +using namespace daq; +using namespace std::chrono_literals; +using namespace daq::websocket_streaming; + +class WebsocketClientDeviceTest : public testing::Test +{ +public: + const uint16_t STREAMING_PORT = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT; + const std::string HOST = "127.0.0.1"; + ContextPtr context; + + void SetUp() override + { + context = NullContext(); + } + + void TearDown() override + { + } +}; + +TEST_F(WebsocketClientDeviceTest, CreateWithInvalidParameters) +{ + ASSERT_THROW(WebsocketClientDevice(context, nullptr, "device", nullptr), ArgumentNullException); +} + +TEST_F(WebsocketClientDeviceTest, CreateSuccess) +{ + // Create server side device + auto serverInstance = streaming_test_helpers::createServerInstance(); + + // Setup and start streaming server + auto server = WebsocketStreamingServer(serverInstance); + server.setStreamingPort(STREAMING_PORT); + server.start(); + + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST)); +} + +TEST_F(WebsocketClientDeviceTest, DeviceInfo) +{ + // Create server side device + auto serverInstance = streaming_test_helpers::createServerInstance(); + + // Setup and start streaming server + auto server = WebsocketStreamingServer(serverInstance); + server.setStreamingPort(STREAMING_PORT); + server.start(); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // get DeviceInfo and check fields + DeviceInfoPtr clientDeviceInfo; + ASSERT_NO_THROW(clientDeviceInfo = clientDevice.getInfo()); + ASSERT_EQ(clientDeviceInfo.getName(), "WebsocketClientPseudoDevice"); + ASSERT_EQ(clientDeviceInfo.getConnectionString(), HOST); +} + +TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) +{ + // Create server signal + auto testSignal = streaming_test_helpers::createTestSignal(context); + + // Setup and start server which will publish created signal + auto server = std::make_shared(context); + server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testSignal); + return signals; + }); + server->start(STREAMING_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // Check the mirrored signal + ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); + ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); + ASSERT_TRUE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), + testSignal.getDescriptor())); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), + testSignal.getDomainSignal().getDescriptor())); + + ASSERT_EQ(clientDevice.getSignals()[0].getName(), "TestName"); + ASSERT_EQ(clientDevice.getSignals()[0].getDescription(), "TestDescription"); + + // Publish signal changes + auto descriptor = DataDescriptorBuilderCopy(testSignal.getDescriptor()).build(); + std::string signalId = testSignal.getGlobalId(); + server->broadcastPacket(signalId, DataDescriptorChangedEventPacket(descriptor, testSignal.getDomainSignal().getDescriptor())); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + server->broadcastPacket(signalId, PropertyChangedEventPacket("Name", "NewName")); + server->broadcastPacket(signalId, PropertyChangedEventPacket("Description", "NewDescription")); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + ASSERT_EQ(clientDevice.getSignals()[0].getName(), "NewName"); + ASSERT_EQ(clientDevice.getSignals()[0].getDescription(), "NewDescription"); + + ASSERT_THROW(clientDevice.getSignals()[0].setName("ClientName"), AccessDeniedException); + + // Check if the mirrored signal changed + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), + descriptor)); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), + testSignal.getDomainSignal().getDescriptor())); +} + +TEST_F(WebsocketClientDeviceTest, SingleSignalWithoutDomain) +{ + // Create server signal + auto testSignal = streaming_test_helpers::createTestSignalWithoutDomain(context); + + // Setup and start server which will publish created signal + auto server = std::make_shared(context); + server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testSignal); + return signals; + }); + server->start(STREAMING_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // The mirrored signal exists but does not have descriptor + ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); + ASSERT_FALSE(clientDevice.getSignals()[0].getDescriptor().assigned()); + ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); +} + +TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) +{ + // Create server side device + auto serverInstance = streaming_test_helpers::createServerInstance(); + + // Setup and start streaming server + auto server = WebsocketStreamingServer(serverInstance); + server.setStreamingPort(STREAMING_PORT); + server.start(); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // There should not be any difference if we get signals recursively or not, + // since client device doesn't know anything about hierarchy + const size_t expectedSignalCount = serverInstance.getSignalsRecursive().getCount(); + ListPtr signals; + ASSERT_NO_THROW(signals = clientDevice.getSignals()); + ASSERT_EQ(signals.getCount(), expectedSignalCount); + ASSERT_NO_THROW(signals = clientDevice.getSignalsRecursive()); + ASSERT_EQ(signals.getCount(), expectedSignalCount); +} From c7287f3499dc99dbb0cbe30defc70b33691169e3 Mon Sep 17 00:00:00 2001 From: JakaMohorkoDS Date: Wed, 18 Oct 2023 10:47:19 +0200 Subject: [PATCH 002/127] Fix remaining licences --- .../src/signal_descriptor_converter.cpp | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 8c68447..fe76bf6 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -1,31 +1,3 @@ -/* - * Blueberry d.o.o. ("COMPANY") CONFIDENTIAL - * Unpublished Copyright (c) 2021-2022 Blueberry d.o.o., All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains the property of - * COMPANY. The intellectual and technical concepts contained herein are - * proprietary to COMPANY and are protected by copyright law and as trade - * secrets and may also be covered by U.S. and Foreign Patents, patents in - * process, etc. - * Dissemination of this information or reproduction of this material is - * strictly forbidden unless prior written permission is obtained from COMPANY. - * Access to the source code contained herein is hereby forbidden to anyone - * except current COMPANY employees, managers or contractors who have executed - * Confidentiality and Non-disclosure agreements explicitly covering such - * access. - * - * The copyright notice above does not evidence any actual or intended - * publication or disclosure of this source code, which includes information - * that is confidential and/or proprietary, and is a trade secret of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC - * DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS - * WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF - * APPLICABLE LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF - * THIS SOURCE CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY - * RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, - * USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ - #include #include #include From 3447a51ca9788783bc4e7662eb3de0851aaf8f51 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 25 Oct 2023 17:27:06 +0200 Subject: [PATCH 003/127] Fix WebSocket streaming compatibility issues: * Use "definition" signal meta-data json-object to Encode/Decode signal descriptor fields * Change the order of protocol meta-data messages - send/handle list of "available" signals first and meta-data of subscribed signals second * Set the default data value Range {-15.0, 15.0} for signals of non-openDAQ ("fusion") devices, to be applied for auto-scaling the renderer window size * Set-up Unit objects for descriptors of mock signals used in tests * Client sends subscribe requests for all available signals --- .../websocket_streaming/streaming_client.h | 2 + .../websocket_client_device_impl.h | 4 +- .../websocket_client_signal_factory.h | 4 -- .../websocket_client_signal_impl.h | 13 +++- .../src/signal_descriptor_converter.cpp | 61 +++++++++++++++---- .../src/streaming_client.cpp | 33 +++++++++- .../src/streaming_server.cpp | 3 +- .../src/websocket_client_device_impl.cpp | 49 +++++---------- .../src/websocket_client_signal_impl.cpp | 29 ++++++--- .../test_signal_descriptor_converter.cpp | 48 +++++++-------- 10 files changed, 155 insertions(+), 91 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index ee12131..7e51ad8 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -32,6 +32,7 @@ #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -103,6 +104,7 @@ class StreamingClient std::mutex clientMutex; std::condition_variable conditionVariable; std::chrono::milliseconds connectTimeout{1000}; + std::unordered_map, std::future>> signalInitializedStatus; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index af83c28..f3d4949 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -34,16 +34,14 @@ class WebsocketClientDeviceImpl : public Device DeviceInfoPtr onGetInfo() override; void createWebsocketStreaming(); void activateStreaming(); - void registerSignalAttributes(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); void onNewSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onDomainDescriptor(const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor); - void initializeDeviceSignals(const std::vector& signalIds); + void createDeviceSignals(const std::vector& signalIds); DeviceInfoConfigPtr deviceInfo; std::map deviceSignals; - std::map> deviceSignalsAttributes; StreamingPtr websocketStreaming; StringPtr connectionString; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h index 3f230c0..d7cdd0d 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h @@ -20,14 +20,10 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING inline SignalPtr WebsocketClientSignal(const ContextPtr& ctx, const ComponentPtr& parent, - const DataDescriptorPtr& descriptor, - const DataDescriptorPtr& domainDescriptor, const StringPtr& streamingId) { SignalPtr obj(createWithImplementation(ctx, parent, - descriptor, - domainDescriptor, streamingId)); return obj; } diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 99e9502..5866ae3 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -21,13 +21,17 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING -class WebsocketClientSignalImpl final : public SignalRemote +DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) +{ + virtual void assignDomainSignal(const DataDescriptorPtr& domainDescriptor) = 0; + virtual void assignDescriptor(const DataDescriptorPtr& descriptor) = 0; +}; + +class WebsocketClientSignalImpl final : public SignalRemoteBase { public: explicit WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, - const DataDescriptorPtr& descriptor, - const DataDescriptorPtr& domainDescriptor, const StringPtr& streamingId); // ISignal @@ -37,6 +41,9 @@ class WebsocketClientSignalImpl final : public SignalRemote 0) sInfo.signalProps.description = extra["sig_desc"]; + // --- meta "interpretation" end --- return sInfo; } @@ -60,6 +91,8 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, auto domainDescriptor = signal.getDomainSignal().getDescriptor(); + // *** meta "definition" start *** + // set/verify fields which will be lately encoded into signal "definition" object stream->setMemberName(dataDescriptor.getName()); // Data type of stream can not be changed. Complain upon change! @@ -72,9 +105,10 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, throw ConversionFailedException(); UnitPtr unit = dataDescriptor.getUnit(); - stream->setUnit(unit.getId(), unit.getSymbol()); + if (unit.assigned()) + stream->setUnit(unit.getId(), unit.getSymbol()); - // causes an error + // causes an error, so openDAQ utilizes fields of "interpretation" object to encode time rule // DataRulePtr rule = domainDescriptor.getRule(); // SetTimeRule(rule, stream); @@ -83,7 +117,9 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, auto resolution = domainDescriptor.getTickResolution(); stream->setTimeTicksPerSecond(resolution.getDenominator() / resolution.getNumerator()); } + // *** meta "definition" end *** + // --- meta "interpretation" start --- nlohmann::json extra; EncodeInterpretationObject(dataDescriptor, extra); @@ -93,7 +129,11 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, extra["sig_desc"] = sigProps.description.value(); stream->setDataInterpretationObject(extra); + // --- meta "interpretation" end --- + // openDAQ does not encode meta "definition" object directly for domain signal + // domain signal "definition" is hardcoded on library level + // so openDAQ uses "interpretation" object to transmit metadata which describes domain signal nlohmann::json domainExtra; if (domainDescriptor.assigned()) EncodeInterpretationObject(domainDescriptor, domainExtra); @@ -157,7 +197,7 @@ void SignalDescriptorConverter::SetTimeRule(const daq::DataRulePtr& rule, daq::s NumberPtr delta = rule.getParameters().get("delta"); NumberPtr start = rule.getParameters().get("start"); baseSyncSignal->setOutputRate(delta); - baseSyncSignal->setTimeStart(start); + baseSyncSignal->setTimeStart(start); // this call performs write which causes protocol error } break; case daq::DataRuleType::Explicit: @@ -201,7 +241,7 @@ daq::SampleType SignalDescriptorConverter::Convert(daq::streaming_protocol::Samp case daq::streaming_protocol::SampleType::SAMPLETYPE_COMPLEX64: return daq::SampleType::ComplexFloat64; case daq::streaming_protocol::SampleType::SAMPLETYPE_REAL32: - return daq::SampleType::Float64; + return daq::SampleType::Float32; case daq::streaming_protocol::SampleType::SAMPLETYPE_REAL64: return daq::SampleType::Float64; case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD32: @@ -265,8 +305,8 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra) { - extra["sampleType"] = dataDescriptor.getSampleType(); - + // put signal name into interpretation object + // required to replace the time signal name hardcoded inside the StreamingClient library if (dataDescriptor.getName().assigned()) extra["name"] = dataDescriptor.getName(); @@ -316,6 +356,8 @@ void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorP void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor) { + // overwrite signal name when corresponding field is present in interpretation object + // required to replace the time signal name hardcoded inside the StreamingClient library if (extra.count("name") > 0) dataDescriptor.setName(extra["name"]); @@ -365,12 +407,9 @@ void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& .setParameters(params) .build(); dataDescriptor.setPostScaling(scaling); - } - if (extra.count("sampleType") > 0) - { - SampleType sampleType = (SampleType) extra["sampleType"]; - dataDescriptor.setSampleType(sampleType); + // overwrite sample type when scaling is present + dataDescriptor.setSampleType(convertScaledToSampleType(scaling.getOutputSampleType())); } } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 0f69821..556789f 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -71,6 +71,19 @@ bool StreamingClient::connect() clientThread = std::thread([this]() { ioContext.run(); }); conditionVariable.wait_for(lock, connectTimeout, [this]() { return connected; }); + + const auto timeout = std::chrono::seconds(1); + auto timeoutExpired = std::chrono::system_clock::now() + timeout; + + for (const auto& [id, promiseFuturePair] : signalInitializedStatus) + { + auto status = promiseFuturePair.second.wait_until(timeoutExpired); + if (status != std::future_status::ready) + { + LOG_W("singal {} has incomplete descriptors", id); + } + } + return connected; } @@ -194,7 +207,16 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p if (availableSignals != params.end() && availableSignals->is_array()) { for (const auto& arrayItem : *availableSignals) - signalIds.push_back(arrayItem); + { + std::string signalId = arrayItem; + signalIds.push_back(signalId); + + std::promise signalInitPromise; + std::future signalInitFuture = signalInitPromise.get_future(); + signalInitializedStatus.insert({signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))}); + } + + protocolHandler.subscribe(signalIds); onAvailableDeviceSignalsCb(signalIds); onAvailableStreamingSignalsCb(signalIds); @@ -249,13 +271,20 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig return; auto inputSignal = signals[tableId]; + const bool assignDomainDescriptor = + inputSignal->getSignalDescriptor().assigned() && !inputSignal->getDomainSignalDescriptor().assigned() ? true : false; inputSignal->setDomainDescriptor(subscribedSignal); // Sets the descriptors when first connecting - if (!connected) + if (assignDomainDescriptor) { auto domainDescriptor = inputSignal->getDomainSignalDescriptor(); onDomainDescriptorCallback(tableId, domainDescriptor); + + if (auto iterator = signalInitializedStatus.find(tableId); iterator != signalInitializedStatus.end()) + { + iterator->second.first.set_value(); + } } } diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 6c84822..03c739b 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -87,6 +87,8 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) if (onAcceptCallback) signals = onAcceptCallback(writer); + writeSignalsAvailable(writer, signals); + auto outputSignals = std::unordered_map(); for (const auto& signal : signals) { @@ -106,7 +108,6 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) } clients.insert({writer, outputSignals}); - writeSignalsAvailable(writer, signals); } void StreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index edc8fd1..021ff57 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -45,6 +45,7 @@ void WebsocketClientDeviceImpl::activateStreaming() } } +/// connects to streaming server and waits till the list of available signals received void WebsocketClientDeviceImpl::createWebsocketStreaming() { auto streamingClient = std::make_shared(context, connectionString); @@ -69,7 +70,7 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() auto availableSignalsCallback = [this](const std::vector& signalIds) { - this->initializeDeviceSignals(signalIds); + this->createDeviceSignals(signalIds); }; streamingClient->onAvailableDeviceSignals(availableSignalsCallback); @@ -81,7 +82,11 @@ void WebsocketClientDeviceImpl::onNewSignal(const StringPtr& signalId, const Sub if (!sInfo.dataDescriptor.assigned()) return; - registerSignalAttributes(signalId, sInfo); + if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) + { + signalIt->second.asPtr()->assignDescriptor(sInfo.dataDescriptor); + updateSignal(signalIt->second, sInfo); + } } void WebsocketClientDeviceImpl::onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) @@ -99,46 +104,24 @@ void WebsocketClientDeviceImpl::onDomainDescriptor(const StringPtr& signalId, if (!domainDescriptor.assigned()) return; - // Sets domain descriptor for data signal attributes - if (auto iter = deviceSignalsAttributes.find(signalId); iter != deviceSignalsAttributes.end()) - { - auto& signalAttributes = iter->second; - signalAttributes.first = domainDescriptor; - } + // Sets domain descriptor for data signal + if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) + signalIt->second.asPtr()->assignDomainSignal(domainDescriptor); } -void WebsocketClientDeviceImpl::initializeDeviceSignals(const std::vector& signalIds) +void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector& signalIds) { // Adds to device only signals published by server explicitly and in the same order these were published for (const auto& signalId : signalIds) { - - if (auto iter = deviceSignalsAttributes.find(signalId); iter == deviceSignalsAttributes.end()) - { - // TODO Streaming didn't provide META info for Time Signals - - // - for these signals device provides incomplete mirrored signals (without descriptor) - auto signal = WebsocketClientSignal(this->context, this->signals, nullptr, nullptr, signalId); - this->addSignal(signal); - deviceSignals.insert({signalId, signal}); - } - else - { - // Streaming provided META info for the Signal - device has complete signal (with descriptors) - auto [domainDescriptor, sInfo] = iter->second; - - auto signal = WebsocketClientSignal(this->context, this->signals, sInfo.dataDescriptor, domainDescriptor, signalId); - this->addSignal(signal); - deviceSignals.insert({signalId, signal}); - updateSignal(signal, sInfo); - } + // TODO Streaming didn't provide META info for Time Signals - + // - for these the mirrored signals will stay incomplete (without descriptors set) + auto signal = WebsocketClientSignal(this->context, this->signals, signalId); + this->addSignal(signal); + deviceSignals.insert({signalId, signal}); } } -void WebsocketClientDeviceImpl::registerSignalAttributes(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) -{ - deviceSignalsAttributes.insert({signalId, {nullptr, sInfo}}); -} - void WebsocketClientDeviceImpl::updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo) { auto protectedObject = signal.asPtr(); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index 346963b..ce3fed4 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -10,18 +10,10 @@ static constexpr char delimeter = '#'; WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, - const DataDescriptorPtr& descriptor, - const DataDescriptorPtr& domainDescriptor, const StringPtr& streamingId) - : SignalRemote(ctx, parent, CreateLocalId(streamingId)) + : SignalRemoteBase(ctx, parent, CreateLocalId(streamingId)) , streamingId(streamingId) - , mirroredDataDescriptor(descriptor) { - if (domainDescriptor.assigned()) - domainSignalArtificial = SignalWithDescriptor(ctx, - domainDescriptor, - parent, - CreateLocalId(streamingId+"_time_artificial")); } StringPtr WebsocketClientSignalImpl::CreateLocalId(const StringPtr& streamingId) @@ -67,6 +59,8 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) DataDescriptorPtr newSignalDescriptor = params[event_packet_param::DATA_DESCRIPTOR]; DataDescriptorPtr newDomainDescriptor = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; + std::scoped_lock lock(this->sync); + if (newSignalDescriptor.assigned()) { mirroredDataDescriptor = newSignalDescriptor; @@ -82,6 +76,23 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) return True; } +void WebsocketClientSignalImpl::assignDomainSignal(const DataDescriptorPtr& domainDescriptor) +{ + std::scoped_lock lock(this->sync); + + domainSignalArtificial = SignalWithDescriptor(this->context, + domainDescriptor, + this->parent.getRef(), + CreateLocalId(streamingId+"_time_artificial")); +} + +void WebsocketClientSignalImpl::assignDescriptor(const DataDescriptorPtr& descriptor) +{ + std::scoped_lock lock(this->sync); + + mirroredDataDescriptor = descriptor; +} + EventPacketPtr WebsocketClientSignalImpl::createDataDescriptorChangedEventPacket() { DataDescriptorPtr domainDescriptor; diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 82f551d..5b93264 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -213,7 +213,7 @@ TEST(SignalConverter, synchronousSignalWithPostScaling) ASSERT_EQ(syncSigna1->getMemberName(), valueDescriptor.getName()); } -TEST(SignalConverter, DISABLED_subscribedDataSignal) +TEST(SignalConverter, subscribedDataSignal) { std::string method; int result; @@ -253,24 +253,22 @@ TEST(SignalConverter, DISABLED_subscribedDataSignal) ASSERT_FALSE(subscribedSignal.isTimeSignal()); auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; - ASSERT_EQ(dataDescriptor.getName(), signalId); ASSERT_EQ(dataDescriptor.getName(), memberName); - daq::SampleType sampleType; - daq::ErrCode err = dataDescriptor->getSampleType(&sampleType); - ASSERT_EQ(err, OPENDAQ_SUCCESS); - ASSERT_EQ(sampleType, daq::SampleType::Float64); - daq::UnitPtr unit; - dataDescriptor->getUnit(&unit); + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Float64); + + auto unit = dataDescriptor.getUnit(); + ASSERT_TRUE(unit.assigned()); ASSERT_EQ(unit.getId(), unitId); ASSERT_EQ(unit.getSymbol(), unitDisplayName); - daq::DataRulePtr rule; - dataDescriptor->getRule(&rule); + + auto rule = dataDescriptor.getRule(); + ASSERT_TRUE(rule.assigned()); ASSERT_EQ(daq::DataRuleType::Explicit, rule.getType()); } -TEST(SignalConverter, DISABLED_subscribedTimeSignal) +TEST(SignalConverter, subscribedTimeSignal) { std::string method; int result; @@ -303,7 +301,6 @@ TEST(SignalConverter, DISABLED_subscribedTimeSignal) timeSignalParams[bsp::META_TABLEID] = tableId; timeSignalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; - timeSignalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_REAL64; timeSignalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_LINEAR; @@ -312,28 +309,29 @@ TEST(SignalConverter, DISABLED_subscribedTimeSignal) timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_UNIT_ID] = unitId; timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_DISPLAY_NAME] = unitDisplayName; + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_QUANTITY] = bsp::META_TIME; - timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_ABSOLUTE_REFERENCE] = bsp::UNIX_EPOCH; - timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_RESOLUTION][bsp::META_NUMERATOR] = 1; - timeSignalParams[bsp::META_DEFINITION][bsp::META_TIME][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; + timeSignalParams[bsp::META_DEFINITION][bsp::META_ABSOLUTE_REFERENCE] = bsp::UNIX_EPOCH; + timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_NUMERATOR] = 1; + timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; result = subscribedSignal.processSignalMetaInformation(method, timeSignalParams); ASSERT_EQ(result, 0); subscribedSignal.setTime(startTime); ASSERT_TRUE(subscribedSignal.isTimeSignal()); auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; - ASSERT_EQ(dataDescriptor.getName(), signalId); - daq::SampleType sampleType; - ErrCode err = dataDescriptor->getSampleType(&sampleType); - ASSERT_EQ(err, OPENDAQ_SUCCESS); - ASSERT_EQ(sampleType, daq::SampleType::UInt64); - - daq::UnitPtr unit; - dataDescriptor->getUnit(&unit); + + ASSERT_EQ(dataDescriptor.getName(), memberName); + + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::UInt64); + + auto unit = dataDescriptor.getUnit(); + ASSERT_TRUE(unit.assigned()); ASSERT_EQ(unit.getId(), unitId); ASSERT_EQ(unit.getSymbol(), unitDisplayName); - daq::DataRulePtr rule; - dataDescriptor->getRule(&rule); + + auto rule = dataDescriptor.getRule(); + ASSERT_TRUE(rule.assigned()); ASSERT_EQ(daq::DataRuleType::Linear, rule.getType()); DictPtr params = rule.getParameters(); ASSERT_EQ(params.getCount(), 2u); From 1c6ab49a382c053da4b397818b46d6174d054377 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 2 Nov 2023 23:02:44 +0100 Subject: [PATCH 004/127] Add integration tests which use the websocket streaming library signal generator --- external/streaming_protocol/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index e91bc16..83b05cc 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -1,5 +1,9 @@ set(STREAMING_PROTOCOL_ALWAYS_FETCH_DEPS ON CACHE BOOL "" FORCE) +if (OPENDAQ_ENABLE_WS_SIGGEN_INTEGRATION_TESTS) + set(STREAMING_PROTOCOL_TOOLS ON CACHE BOOL "" FORCE) +endif() + opendaq_dependency( NAME streaming_protocol REQUIRED_VERSION 0.10.7 From 870bfdc7467ff484f1af23db12ab3bf7bf8f1a36 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 7 Nov 2023 17:28:46 +0100 Subject: [PATCH 005/127] Fix websocket streaming compatibility issues: * Remove order dependency for receiving of time/value signal meta-data in ws streaming * Fix websocket streaming for scenarios when tableId does not equal to signalId * Use ws streaming protocol absoluteReference meta as signal origin --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/input_signal.h | 7 +- .../websocket_streaming/streaming_client.h | 11 +- .../websocket_client_device_impl.h | 2 +- .../websocket_streaming/src/input_signal.cpp | 24 ++- .../src/signal_descriptor_converter.cpp | 2 + .../src/streaming_client.cpp | 176 ++++++++++++------ .../src/websocket_client_device_impl.cpp | 8 +- 8 files changed, 158 insertions(+), 76 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 83b05cc..eceabe5 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.7 + REQUIRED_VERSION 0.10.9 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.7 + GIT_REF v0.10.9 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 228746c..4037793 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -33,11 +33,13 @@ class InputSignal PacketPtr asPacket(uint64_t packetOffset, const uint8_t* data, size_t size); PacketPtr createDecriptorChangedPacket(); - void setDataDescriptor(const daq::streaming_protocol::SubscribedSignal& dataSignal); - void setDomainDescriptor(const daq::streaming_protocol::SubscribedSignal& timeSignal); + void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); + void setDomainDescriptor(const DataDescriptorPtr& domainDescriptor); bool hasDescriptors(); DataDescriptorPtr getSignalDescriptor(); DataDescriptorPtr getDomainSignalDescriptor(); + void setTableId(std::string id); + std::string getTableId(); protected: DataDescriptorPtr currentDataDescriptor; @@ -45,6 +47,7 @@ class InputSignal std::string name; std::string description; + std::string tableId; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 7e51ad8..2299379 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -56,7 +56,7 @@ class StreamingClient bool connect(); void disconnect(); void onPacket(const OnPacketCallback& callack); - void onNewSignal(const OnSignalCallback& callback); + void onDataDescriptor(const OnSignalCallback& callback); void onSignalUpdated(const OnSignalCallback& callback); void onDomainDescriptor(const OnDomainDescriptorCallback& callback); void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); @@ -78,8 +78,12 @@ class StreamingClient void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size); void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - void publishSignal(const std::string& signalId); + void publishSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); + void setSignalInitSatisfied(const std::string& signalId); + void setDomainDescriptor(const std::string& signalId, + const InputSignalPtr& inputSignal, + const DataDescriptorPtr& domainDescriptor); LoggerPtr logger; LoggerComponentPtr loggerComponent; @@ -94,7 +98,7 @@ class StreamingClient daq::streaming_protocol::ProtocolHanlderPtr protocolHandler; std::unordered_map signals; OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; - OnSignalCallback onNewSignalCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; + OnSignalCallback onDataDescriptorCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; OnDomainDescriptorCallback onDomainDescriptorCallback = [](const StringPtr&, const DataDescriptorPtr&) {}; OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; @@ -105,6 +109,7 @@ class StreamingClient std::condition_variable conditionVariable; std::chrono::milliseconds connectTimeout{1000}; std::unordered_map, std::future>> signalInitializedStatus; + std::unordered_map cachedDomainDescriptors; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index f3d4949..cd40949 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -35,7 +35,7 @@ class WebsocketClientDeviceImpl : public Device void createWebsocketStreaming(); void activateStreaming(); void updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); - void onNewSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void onDataDescriptor(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onDomainDescriptor(const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor); void createDeviceSignals(const std::vector& signalIds); diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index b5b426b..23d3c46 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -16,8 +16,6 @@ InputSignal::InputSignal() PacketPtr InputSignal::asPacket(uint64_t packetOffset, const uint8_t* data, size_t size) { - assert(!currentDataDescriptor.isStructDescriptor()); - auto sampleType = currentDataDescriptor.getSampleType(); if (currentDataDescriptor.getPostScaling().assigned()) sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); @@ -36,18 +34,14 @@ PacketPtr InputSignal::createDecriptorChangedPacket() return DataDescriptorChangedEventPacket(currentDataDescriptor, currentDomainDataDescriptor); } -void InputSignal::setDataDescriptor(const daq::streaming_protocol::SubscribedSignal &dataSignal) +void InputSignal::setDataDescriptor(const DataDescriptorPtr& dataDescriptor) { - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(dataSignal); - auto descriptor = sInfo.dataDescriptor; - currentDataDescriptor = descriptor; + currentDataDescriptor = dataDescriptor; } -void InputSignal::setDomainDescriptor(const daq::streaming_protocol::SubscribedSignal& timeSignal) +void InputSignal::setDomainDescriptor(const DataDescriptorPtr& domainDescriptor) { - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(timeSignal); - auto descriptor = sInfo.dataDescriptor; - currentDomainDataDescriptor = descriptor; + currentDomainDataDescriptor = domainDescriptor; } bool InputSignal::hasDescriptors() @@ -65,4 +59,14 @@ DataDescriptorPtr InputSignal::getDomainSignalDescriptor() return currentDomainDataDescriptor; } +void InputSignal::setTableId(std::string id) +{ + tableId = id; +} + +std::string InputSignal::getTableId() +{ + return tableId; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index e321b47..0aa2aaa 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -55,6 +55,8 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre dataDescriptor.setUnit(unit); } + + dataDescriptor.setOrigin(subscribedSignal.timeBaseEpochAsString()); // *** meta "definition" end *** if (!subscribedSignal.isTimeSignal()) diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 556789f..2aeb8af 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -80,7 +80,7 @@ bool StreamingClient::connect() auto status = promiseFuturePair.second.wait_until(timeoutExpired); if (status != std::future_status::ready) { - LOG_W("singal {} has incomplete descriptors", id); + LOG_W("signal {} has incomplete descriptors", id); } } @@ -102,9 +102,9 @@ void StreamingClient::onPacket(const OnPacketCallback& callack) onPacketCallback = callack; } -void StreamingClient::onNewSignal(const OnSignalCallback& callback) +void StreamingClient::onDataDescriptor(const OnSignalCallback& callback) { - onNewSignalCallback = callback; + onDataDescriptorCallback = callback; } void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) @@ -213,7 +213,17 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p std::promise signalInitPromise; std::future signalInitFuture = signalInitPromise.get_future(); - signalInitializedStatus.insert({signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))}); + signalInitializedStatus.insert_or_assign(signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))); + + if (auto signalIt = signals.find(signalId); signalIt == signals.end()) + { + auto inputSignal = std::make_shared(); + signals.insert({signalId, inputSignal}); + } + else + { + LOG_E("Received duplicate of available signal. ID is {}.", signalId); + } } protocolHandler.subscribe(signalIds); @@ -235,31 +245,35 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& { std::string id = subscribedSignal.signalId(); const auto& signalIter = signals.find(id); - if (signalIter == signals.end()) - return; - - auto packet = signalIter->second->asPacket(timeStamp, data, size); - onPacketCallback(id, packet); + if (signalIter != signals.end() && + signalIter->second->hasDescriptors() && + !signalIter->second->getSignalDescriptor().isStructDescriptor()) + { + auto packet = signalIter->second->asPacket(timeStamp, data, size); + onPacketCallback(id, packet); + } } void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) { const auto id = subscribedSignal.signalId(); - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); - if (signals.count(id) == 0) + if (auto signalIt = signals.find(id); signalIt != signals.end()) { - auto descriptor = sInfo.dataDescriptor; - onNewSignalCallback(id, sInfo); + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + if (!signalIt->second->getSignalDescriptor().assigned()) + onDataDescriptorCallback(id, sInfo); + else + onSignalUpdatedCallback(id, sInfo); + signalIt->second->setDataDescriptor(sInfo.dataDescriptor); - auto inputSignal = std::make_shared(); - inputSignal->setDataDescriptor(subscribedSignal); - signals[id] = inputSignal; - } - else - { - onSignalUpdatedCallback(id, sInfo); - signals[id]->setDataDescriptor(subscribedSignal); + const auto tableId = subscribedSignal.tableId(); + signalIt->second->setTableId(tableId); + if (auto domainDescIt = cachedDomainDescriptors.find(tableId); domainDescIt != cachedDomainDescriptors.end()) + { + if (!signalIt->second->getDomainSignalDescriptor().assigned()) + setDomainDescriptor(signalIt->first, signalIt->second, domainDescIt->second); + } } } @@ -267,60 +281,114 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig { std::string tableId = subscribedSignal.tableId(); - if (signals.count(tableId) == 0) - return; - - auto inputSignal = signals[tableId]; - const bool assignDomainDescriptor = - inputSignal->getSignalDescriptor().assigned() && !inputSignal->getDomainSignalDescriptor().assigned() ? true : false; - inputSignal->setDomainDescriptor(subscribedSignal); - - // Sets the descriptors when first connecting - if (assignDomainDescriptor) + // check if the value signal with tableId is known + // and the descriptors for it are already received + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto signalIt = std::find_if(signals.begin(), + signals.end(), + [tableId](const std::pair& pair) + { + return tableId == pair.second->getTableId(); + }); + if (signalIt != signals.end()) { - auto domainDescriptor = inputSignal->getDomainSignalDescriptor(); - onDomainDescriptorCallback(tableId, domainDescriptor); - - if (auto iterator = signalInitializedStatus.find(tableId); iterator != signalInitializedStatus.end()) - { - iterator->second.first.set_value(); - } + // value signal with tableId is known, set domain descriptor for it + setDomainDescriptor(signalIt->first, signalIt->second, sInfo.dataDescriptor); + } + else + { + // value signal with tableId is unknown, save domain descriptor + cachedDomainDescriptors.insert_or_assign(tableId, sInfo.dataDescriptor); } } -void StreamingClient::publishSignal(const std::string& signalId) +void StreamingClient::publishSignal(const SubscribedSignal& subscribedSignal) { - if (signals.count(signalId) == 0) - return; - - auto inputSignal = signals[signalId]; - if (!inputSignal->hasDescriptors()) - return; + std::string tableId = subscribedSignal.tableId(); + auto signalIt = std::find_if(signals.begin(), + signals.end(), + [tableId](const std::pair& pair) + { + return tableId == pair.second->getTableId(); + }); + if (signalIt != signals.end()) + { + auto inputSignal = signalIt->second; + auto signalId = signalIt->first; - auto eventPacket = inputSignal->createDecriptorChangedPacket(); - onPacketCallback(signalId, eventPacket); + // signal meta information is always received by pairs of META_METHOD_SIGNAL: + // one is meta for data signal, another is meta for time signal. + // we generate event packet only after both meta are received + // and all signal descriptors are assigned. + if (!inputSignal->hasDescriptors()) + return; + + auto eventPacket = inputSignal->createDecriptorChangedPacket(); + onPacketCallback(signalId, eventPacket); + } } void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params) { try { + { + LOG_I("Signal #{}; signalId {}; tableId {}; name {}; value type {}; Json parameters: \n\n{}\n", + subscribedSignal.signalNumber(), + subscribedSignal.signalId(), + subscribedSignal.tableId(), + subscribedSignal.memberName(), + subscribedSignal.dataValueType(), + params.dump()); + } + if (subscribedSignal.isTimeSignal()) setTimeSignal(subscribedSignal); else setDataSignal(subscribedSignal); - // signal meta information is always received by pairs of META_METHOD_SIGNAL: - // first is meta for data signal, second is meta for artificial time signal. - // we call "publishSignal" which generates event packet only after both meta are received - // and all signal descriptors are updated. - if (subscribedSignal.isTimeSignal()) - publishSignal(subscribedSignal.tableId()); + publishSignal(subscribedSignal); } catch (const DaqException& e) { - LOG_W("Failed to interpret received input signal: {}.", e.what()); + LOG_W("Failed to interpret received input signal: {}.", e.what()); } } +void StreamingClient::setSignalInitSatisfied(const std::string& signalId) +{ + if (auto iterator = signalInitializedStatus.find(signalId); iterator != signalInitializedStatus.end()) + { + try + { + iterator->second.first.set_value(); + } + catch (std::future_error& e) + { + if (e.code() == std::make_error_code(std::future_errc::promise_already_satisfied)) + { + LOG_D("signal {} is already initialized", signalId); + } + else + { + LOG_E("signal {} initialization error {}", signalId, e.what()); + } + } + } +} + +void StreamingClient::setDomainDescriptor(const std::string& signalId, + const InputSignalPtr& inputSignal, + const DataDescriptorPtr& domainDescriptor) +{ + // Sets the descriptors of pseudo device signal when first connecting + if (!inputSignal->getDomainSignalDescriptor().assigned()) + { + onDomainDescriptorCallback(signalId, domainDescriptor); + setSignalInitSatisfied(signalId); + } + + inputSignal->setDomainDescriptor(domainDescriptor); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 021ff57..08feb8e 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -50,11 +50,11 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() { auto streamingClient = std::make_shared(context, connectionString); - auto newSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) + auto dataDescriptorCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { - this->onNewSignal(signalId, sInfo); + this->onDataDescriptor(signalId, sInfo); }; - streamingClient->onNewSignal(newSignalCallback); + streamingClient->onDataDescriptor(dataDescriptorCallback); auto signalUpdatedCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { @@ -77,7 +77,7 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() websocketStreaming = WebsocketStreaming(streamingClient, connectionString, context); } -void WebsocketClientDeviceImpl::onNewSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +void WebsocketClientDeviceImpl::onDataDescriptor(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { if (!sInfo.dataDescriptor.assigned()) return; From b71af397fea67c3529a47d290a1bec997be8e07c Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 13 Nov 2023 15:28:11 +0100 Subject: [PATCH 006/127] Use signal name instead of descriptor name for ws streaming metadata --- .../include/websocket_streaming/signal_info.h | 1 + .../websocket_streaming/streaming_client.h | 4 ++-- .../websocket_client_device_impl.h | 4 ++-- .../websocket_streaming/src/output_signal.cpp | 8 ++++++++ .../src/signal_descriptor_converter.cpp | 16 +++++++--------- .../src/streaming_client.cpp | 6 +++--- .../src/websocket_client_device_impl.cpp | 18 +++++++++++------- .../tests/test_signal_descriptor_converter.cpp | 16 ++++++++-------- 8 files changed, 42 insertions(+), 31 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h index e7462bf..be83e91 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h @@ -33,6 +33,7 @@ struct SubscribedSignalInfo { DataDescriptorPtr dataDescriptor; SignalProps signalProps; + std::string signalName; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 2299379..ca4d7d1 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -56,7 +56,7 @@ class StreamingClient bool connect(); void disconnect(); void onPacket(const OnPacketCallback& callack); - void onDataDescriptor(const OnSignalCallback& callback); + void onSignalInit(const OnSignalCallback& callback); void onSignalUpdated(const OnSignalCallback& callback); void onDomainDescriptor(const OnDomainDescriptorCallback& callback); void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); @@ -98,7 +98,7 @@ class StreamingClient daq::streaming_protocol::ProtocolHanlderPtr protocolHandler; std::unordered_map signals; OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; - OnSignalCallback onDataDescriptorCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; + OnSignalCallback onSignalInitCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; OnDomainDescriptorCallback onDomainDescriptorCallback = [](const StringPtr&, const DataDescriptorPtr&) {}; OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index cd40949..c21016d 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -34,8 +34,8 @@ class WebsocketClientDeviceImpl : public Device DeviceInfoPtr onGetInfo() override; void createWebsocketStreaming(); void activateStreaming(); - void updateSignal(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); - void onDataDescriptor(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void updateSignalProperties(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); + void onSignalInit(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onDomainDescriptor(const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor); void createDeviceSignals(const std::vector& signalIds); diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 627c50a..d3efe57 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -164,6 +164,8 @@ void OutputSignal::createStreamedSignal() auto domainSignal = Signal(context, nullptr, "domain"); streamedSignal = Signal(context, nullptr, signal.getLocalId()); streamedSignal.setDomainSignal(domainSignal); + streamedSignal.setName(signal.getName()); + streamedSignal.setDescription(signal.getDescription()); } void OutputSignal::writeEventPacket(const EventPacketPtr& packet) @@ -213,9 +215,15 @@ void OutputSignal::writePropertyChangedPacket(const EventPacketPtr& packet) SignalProps sigProps; if (name == "Name") + { sigProps.name = value; + streamedSignal.setName(value); + } else if (name == "Description") + { sigProps.description = value; + streamedSignal.setDescription(value); + } SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); stream->writeSignalMetaInformation(); diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 0aa2aaa..2340784 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -44,7 +44,7 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre daq::SampleType daqSampleType = Convert(streamingSampleType); dataDescriptor.setSampleType(daqSampleType); - dataDescriptor.setName(subscribedSignal.memberName()); + sInfo.signalName = subscribedSignal.memberName(); if (subscribedSignal.unitId() != daq::streaming_protocol::Unit::UNIT_ID_NONE) { @@ -95,7 +95,7 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, // *** meta "definition" start *** // set/verify fields which will be lately encoded into signal "definition" object - stream->setMemberName(dataDescriptor.getName()); + stream->setMemberName(signal.getName()); // Data type of stream can not be changed. Complain upon change! daq::SampleType daqSampleType = dataDescriptor.getSampleType(); @@ -307,10 +307,9 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra) { - // put signal name into interpretation object - // required to replace the time signal name hardcoded inside the StreamingClient library + // put descriptor name into interpretation object if (dataDescriptor.getName().assigned()) - extra["name"] = dataDescriptor.getName(); + extra["desc_name"] = dataDescriptor.getName(); if (dataDescriptor.getMetadata().assigned()) { @@ -358,10 +357,9 @@ void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorP void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor) { - // overwrite signal name when corresponding field is present in interpretation object - // required to replace the time signal name hardcoded inside the StreamingClient library - if (extra.count("name") > 0) - dataDescriptor.setName(extra["name"]); + // sets descriptor name when corresponding field is present in interpretation object + if (extra.count("desc_name") > 0) + dataDescriptor.setName(extra["desc_name"]); if (extra.count("metadata") > 0) { diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 2aeb8af..ed40553 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -102,9 +102,9 @@ void StreamingClient::onPacket(const OnPacketCallback& callack) onPacketCallback = callack; } -void StreamingClient::onDataDescriptor(const OnSignalCallback& callback) +void StreamingClient::onSignalInit(const OnSignalCallback& callback) { - onDataDescriptorCallback = callback; + onSignalInitCallback = callback; } void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) @@ -262,7 +262,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig { auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); if (!signalIt->second->getSignalDescriptor().assigned()) - onDataDescriptorCallback(id, sInfo); + onSignalInitCallback(id, sInfo); else onSignalUpdatedCallback(id, sInfo); signalIt->second->setDataDescriptor(sInfo.dataDescriptor); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 08feb8e..84c344a 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -50,11 +50,11 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() { auto streamingClient = std::make_shared(context, connectionString); - auto dataDescriptorCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) + auto signalInitCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { - this->onDataDescriptor(signalId, sInfo); + this->onSignalInit(signalId, sInfo); }; - streamingClient->onDataDescriptor(dataDescriptorCallback); + streamingClient->onSignalInit(signalInitCallback); auto signalUpdatedCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { @@ -77,15 +77,19 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() websocketStreaming = WebsocketStreaming(streamingClient, connectionString, context); } -void WebsocketClientDeviceImpl::onDataDescriptor(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +void WebsocketClientDeviceImpl::onSignalInit(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { if (!sInfo.dataDescriptor.assigned()) return; if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) { + // sets signal name as it appeared in metadata "name" + auto protectedObject = signalIt->second.asPtr(); + protectedObject.setProtectedPropertyValue("Name", sInfo.signalName); + signalIt->second.asPtr()->assignDescriptor(sInfo.dataDescriptor); - updateSignal(signalIt->second, sInfo); + updateSignalProperties(signalIt->second, sInfo); } } @@ -95,7 +99,7 @@ void WebsocketClientDeviceImpl::onSignalUpdated(const StringPtr& signalId, const return; if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) - updateSignal(signalIt->second, sInfo); + updateSignalProperties(signalIt->second, sInfo); } void WebsocketClientDeviceImpl::onDomainDescriptor(const StringPtr& signalId, @@ -122,7 +126,7 @@ void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector(); if (sInfo.signalProps.name.has_value()) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 5b93264..6605b71 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -153,7 +153,7 @@ TEST(SignalConverter, synchronousSignal) ASSERT_EQ(syncSigna1->getTimeStart(), start); ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); - ASSERT_EQ(syncSigna1->getMemberName(), dataDescriptor.getName()); + ASSERT_EQ(syncSigna1->getMemberName(), signal.getName()); } TEST(SignalConverter, TickResolution) @@ -210,7 +210,7 @@ TEST(SignalConverter, synchronousSignalWithPostScaling) ASSERT_EQ(syncSigna1->getTimeStart(), start); ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); - ASSERT_EQ(syncSigna1->getMemberName(), valueDescriptor.getName()); + ASSERT_EQ(syncSigna1->getMemberName(), signal.getName()); } TEST(SignalConverter, subscribedDataSignal) @@ -252,9 +252,9 @@ TEST(SignalConverter, subscribedDataSignal) ASSERT_EQ(result, 0); ASSERT_FALSE(subscribedSignal.isTimeSignal()); - auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; - - ASSERT_EQ(dataDescriptor.getName(), memberName); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto dataDescriptor = subscribedSignalInfo.dataDescriptor; + ASSERT_EQ(subscribedSignalInfo.signalName, memberName); ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Float64); @@ -319,9 +319,9 @@ TEST(SignalConverter, subscribedTimeSignal) subscribedSignal.setTime(startTime); ASSERT_TRUE(subscribedSignal.isTimeSignal()); - auto dataDescriptor = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal).dataDescriptor; - - ASSERT_EQ(dataDescriptor.getName(), memberName); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto dataDescriptor = subscribedSignalInfo.dataDescriptor; + ASSERT_EQ(subscribedSignalInfo.signalName, memberName); ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::UInt64); From 6952c7423086110695d2d7a4608ad5ac1c090f2b Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Sat, 21 Oct 2023 10:11:11 +0200 Subject: [PATCH 007/127] Add automatic signal subscribing: * Signal/Streaming framework: Automatically subscribing mirrored signals when signal has at least one connection to the InputPort and unsubscribing when the connection count decreases to zero. * Native streaming: Subscribing domain signals automatically by client side when value signal is being subscribed. Adding/Removing reader on server side when signal is being subscribed/usubscribed * Streaming framework refactoring: SignalRemote renamed to MirroredSignalConfig for interfaces and smart pointers IMirroredSignalConfig inherits ISignalConfig Streaming source management methods moved from ISignalConfig to IMirroredSignalConfig Streaming and MirroredSignal implement additional private interfaces Mirrored signal gets initial DataDescriptorChanged EventPacket directly from active streaming source --- .../websocket_streaming/input_signal.h | 3 +- .../websocket_streaming/streaming_client.h | 2 + .../websocket_client_signal_impl.h | 9 +- .../websocket_streaming_impl.h | 14 +-- .../websocket_streaming/src/input_signal.cpp | 2 +- .../src/streaming_client.cpp | 9 ++ .../src/websocket_client_device_impl.cpp | 4 +- .../src/websocket_client_signal_impl.cpp | 21 ++-- .../src/websocket_streaming_impl.cpp | 95 +++++++------------ 9 files changed, 69 insertions(+), 90 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 4037793..4cd3198 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -20,6 +20,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -32,7 +33,7 @@ class InputSignal InputSignal(); PacketPtr asPacket(uint64_t packetOffset, const uint8_t* data, size_t size); - PacketPtr createDecriptorChangedPacket(); + EventPacketPtr createDecriptorChangedPacket(); void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); void setDomainDescriptor(const DataDescriptorPtr& domainDescriptor); bool hasDescriptors(); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index ca4d7d1..4a76d3f 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,7 @@ class StreamingClient std::string getTarget(); bool isConnected(); void setConnectTimeout(std::chrono::milliseconds timeout); + EventPacketPtr getDataDescriptorChangedEventPacket(const StringPtr& signalStringId); protected: diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 5866ae3..1da25b5 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -15,7 +15,7 @@ */ #pragma once -#include +#include #include "websocket_streaming/websocket_streaming.h" @@ -27,7 +27,7 @@ DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) virtual void assignDescriptor(const DataDescriptorPtr& descriptor) = 0; }; -class WebsocketClientSignalImpl final : public SignalRemoteBase +class WebsocketClientSignalImpl final : public MirroredSignalBase { public: explicit WebsocketClientSignalImpl(const ContextPtr& ctx, @@ -44,15 +44,14 @@ class WebsocketClientSignalImpl final : public SignalRemoteBase& signalIds); - StringPtr getSignalStreamingId(const SignalRemotePtr& signal); + StringPtr getSignalStreamingId(const MirroredSignalConfigPtr& signal); daq::websocket_streaming::StreamingClientPtr streamingClient; std::vector availableSignalIds; - std::map> cachedEventPackets; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 23d3c46..7dc95dd 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -29,7 +29,7 @@ PacketPtr InputSignal::asPacket(uint64_t packetOffset, const uint8_t* data, size return dataPacket; } -PacketPtr InputSignal::createDecriptorChangedPacket() +EventPacketPtr InputSignal::createDecriptorChangedPacket() { return DataDescriptorChangedEventPacket(currentDataDescriptor, currentDomainDataDescriptor); } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index ed40553..add1535 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -6,6 +6,7 @@ #include "stream/WebsocketClientStream.hpp" #include "streaming_protocol/SignalContainer.hpp" #include "opendaq/custom_log.h" +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -157,6 +158,14 @@ void StreamingClient::setConnectTimeout(std::chrono::milliseconds timeout) this->connectTimeout = timeout; } +EventPacketPtr StreamingClient::getDataDescriptorChangedEventPacket(const StringPtr& signalStringId) +{ + if (auto it = signals.find(signalStringId); it == signals.end()) + return DataDescriptorChangedEventPacket(nullptr, nullptr); + else + return it->second->createDecriptorChangedPacket(); +} + void StreamingClient::parseConnectionString(const std::string& url) { // this is not great but it is convenient until we have a way to pass configuration parameters to a client device diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 84c344a..9fc2bff 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -40,8 +40,8 @@ void WebsocketClientDeviceImpl::activateStreaming() for (const auto& signal : signals) { - auto signalConfigPtr = signal.asPtr(); - signalConfigPtr.setActiveStreamingSource(websocketStreaming.getConnectionString()); + auto mirroredSignalConfigPtr = signal.template asPtr(); + mirroredSignalConfigPtr.setActiveStreamingSource(websocketStreaming.getConnectionString()); } } diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index ce3fed4..3e6f6b0 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -11,7 +11,7 @@ static constexpr char delimeter = '#'; WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& streamingId) - : SignalRemoteBase(ctx, parent, CreateLocalId(streamingId)) + : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId)) , streamingId(streamingId) { } @@ -35,7 +35,7 @@ ErrCode WebsocketClientSignalImpl::getDescriptor(IDataDescriptor** descriptor) { OPENDAQ_PARAM_NOT_NULL(descriptor); - std::scoped_lock lock(this->sync); + std::scoped_lock lock(signalMutex); *descriptor = mirroredDataDescriptor.addRefAndReturn(); return OPENDAQ_SUCCESS; @@ -45,7 +45,7 @@ ErrCode WebsocketClientSignalImpl::getDomainSignal(ISignal** signal) { OPENDAQ_PARAM_NOT_NULL(signal); - std::scoped_lock lock(this->sync); + std::scoped_lock lock(signalMutex); *signal = domainSignalArtificial.addRefAndReturn(); return OPENDAQ_SUCCESS; @@ -59,8 +59,7 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) DataDescriptorPtr newSignalDescriptor = params[event_packet_param::DATA_DESCRIPTOR]; DataDescriptorPtr newDomainDescriptor = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; - std::scoped_lock lock(this->sync); - + std::scoped_lock lock(signalMutex); if (newSignalDescriptor.assigned()) { mirroredDataDescriptor = newSignalDescriptor; @@ -78,7 +77,7 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) void WebsocketClientSignalImpl::assignDomainSignal(const DataDescriptorPtr& domainDescriptor) { - std::scoped_lock lock(this->sync); + std::scoped_lock lock(signalMutex); domainSignalArtificial = SignalWithDescriptor(this->context, domainDescriptor, @@ -88,17 +87,9 @@ void WebsocketClientSignalImpl::assignDomainSignal(const DataDescriptorPtr& doma void WebsocketClientSignalImpl::assignDescriptor(const DataDescriptorPtr& descriptor) { - std::scoped_lock lock(this->sync); + std::scoped_lock lock(signalMutex); mirroredDataDescriptor = descriptor; } -EventPacketPtr WebsocketClientSignalImpl::createDataDescriptorChangedEventPacket() -{ - DataDescriptorPtr domainDescriptor; - if (domainSignalArtificial.assigned()) - domainDescriptor = domainSignalArtificial.getDescriptor(); - return DataDescriptorChangedEventPacket(mirroredDataDescriptor, domainDescriptor); -} - END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 925c816..870e69c 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -1,8 +1,8 @@ #include "websocket_streaming/websocket_streaming_impl.h" -#include #include -#include +#include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -29,18 +29,27 @@ void WebsocketStreamingImpl::onSetActive(bool active) { } -StringPtr WebsocketStreamingImpl::onAddSignal(const SignalRemotePtr& signal) +StringPtr WebsocketStreamingImpl::onAddSignal(const MirroredSignalConfigPtr& signal) { - StringPtr signalStreamingId = this->getSignalStreamingId(signal); - if ( !signalStreamingId.assigned() ) - throw NotFoundException("Signal with id {} is not available in Websocket streaming", signal.getRemoteId()); + return getSignalStreamingId(signal); +} + +void WebsocketStreamingImpl::onRemoveSignal(const MirroredSignalConfigPtr& /*signal*/) +{ +} - handleCachedEventPackets(signalStreamingId, signal); - return signalStreamingId; +void WebsocketStreamingImpl::onSubscribeSignal(const MirroredSignalConfigPtr& /*signal*/) +{ +} + +void WebsocketStreamingImpl::onUnsubscribeSignal(const MirroredSignalConfigPtr& /*signal*/) +{ } -void WebsocketStreamingImpl::onRemoveSignal(const SignalRemotePtr& /*signal*/) +EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const MirroredSignalConfigPtr& signal) { + StringPtr signalStreamingId = getSignalStreamingId(signal); + return streamingClient->getDataDescriptorChangedEventPacket(signalStreamingId); } void WebsocketStreamingImpl::prepareStreamingClient() @@ -58,62 +67,30 @@ void WebsocketStreamingImpl::prepareStreamingClient() streamingClient->onAvailableStreamingSignals(availableSignalsCallback); } -void WebsocketStreamingImpl::handleEventPacket(const StringPtr& signalId, const EventPacketPtr& eventPacket) -{ - if (auto it = streamingSignals.find(signalId); it != streamingSignals.end()) - { - SignalRemotePtr signal = it->second; - Bool forwardPacket = signal.triggerEvent(eventPacket); - auto signalConfig = signal.asPtr(); - auto sourceStreamingConnectionString = signalConfig.getActiveStreamingSource(); - if (sourceStreamingConnectionString == connectionString && isActive && forwardPacket) - { - signalConfig.sendPacket(eventPacket); - } - } - else - { - cachedEventPackets[signalId].push_back(eventPacket); - } -} - -void WebsocketStreamingImpl::handleCachedEventPackets(const StringPtr& signalStreamingId, - const SignalRemotePtr& signal) +void WebsocketStreamingImpl::handleEventPacket(const MirroredSignalConfigPtr& signal, const EventPacketPtr& eventPacket) { - if (auto it = cachedEventPackets.find(signalStreamingId); it != cachedEventPackets.end()) - { - for (const auto& eventPacket : it->second) - signal.triggerEvent(eventPacket); - cachedEventPackets.erase(it); - } -} - -void WebsocketStreamingImpl::handleDataPacket(const StringPtr& signalId, const PacketPtr& dataPacket) -{ - if (auto it = streamingSignals.find(signalId); it != streamingSignals.end() && isActive) - { - auto signal = (it->second).asPtr(); - auto sourceStreamingConnectionString = signal.getActiveStreamingSource(); - if (sourceStreamingConnectionString == connectionString) - { - signal.sendPacket(dataPacket); - } - } + Bool forwardPacket = signal.template asPtr()->triggerEvent(eventPacket); + if (forwardPacket) + signal.sendPacket(eventPacket); } void WebsocketStreamingImpl::onPacket(const StringPtr& signalId, const PacketPtr& packet) { - if (!packet.assigned()) + if (!packet.assigned() || !this->isActive) return; - const auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned()) - { - handleEventPacket(signalId, eventPacket); - } - else + if (auto it = streamingSignalsRefs.find(signalId); it != streamingSignalsRefs.end()) { - handleDataPacket(signalId, packet); + auto signalRef = it->second; + MirroredSignalConfigPtr signal = signalRef.assigned() ? signalRef.getRef() : nullptr; + if (signal.assigned() && signal.getActiveStreamingSource() == connectionString) + { + const auto eventPacket = packet.asPtrOrNull(); + if (eventPacket.assigned()) + handleEventPacket(signal, eventPacket); + else + signal.sendPacket(packet); + } } } @@ -122,7 +99,7 @@ void WebsocketStreamingImpl::onAvailableSignals(const std::vector& availableSignalIds = signalIds; } -StringPtr WebsocketStreamingImpl::getSignalStreamingId(const SignalRemotePtr &signal) +StringPtr WebsocketStreamingImpl::getSignalStreamingId(const MirroredSignalConfigPtr &signal) { std::string signalFullId = signal.getRemoteId().toStdString(); const auto it = std::find_if( @@ -139,7 +116,7 @@ StringPtr WebsocketStreamingImpl::getSignalStreamingId(const SignalRemotePtr &si if (it != availableSignalIds.end()) return String(*it); else - return nullptr; + throw NotFoundException("Signal with id {} is not available in Websocket streaming", signal.getRemoteId()); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 54af0c24361c70699641a74c6b33ea7beff53fee Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 16 Nov 2023 14:43:31 +0100 Subject: [PATCH 008/127] Add streaming modifications: * Drop received descriptor changed event packets by native streaming client if descriptors was not actually changed * Make access to cached signal descriptors thread safe in streaming clients --- .../websocket_streaming/input_signal.h | 14 ++++++++------ .../websocket_streaming/src/input_signal.cpp | 19 +++++++++++++------ .../src/streaming_client.cpp | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 4cd3198..c0372f9 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -32,15 +32,15 @@ class InputSignal public: InputSignal(); - PacketPtr asPacket(uint64_t packetOffset, const uint8_t* data, size_t size); - EventPacketPtr createDecriptorChangedPacket(); + PacketPtr createDataPacket(uint64_t packetOffset, const uint8_t* data, size_t size) const; + EventPacketPtr createDecriptorChangedPacket() const; void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); void setDomainDescriptor(const DataDescriptorPtr& domainDescriptor); - bool hasDescriptors(); - DataDescriptorPtr getSignalDescriptor(); - DataDescriptorPtr getDomainSignalDescriptor(); + bool hasDescriptors() const; + DataDescriptorPtr getSignalDescriptor() const; + DataDescriptorPtr getDomainSignalDescriptor() const; void setTableId(std::string id); - std::string getTableId(); + std::string getTableId() const; protected: DataDescriptorPtr currentDataDescriptor; @@ -49,6 +49,8 @@ class InputSignal std::string name; std::string description; std::string tableId; + + mutable std::mutex descriptorsSync; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 7dc95dd..4e529d3 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -14,8 +14,9 @@ InputSignal::InputSignal() { } -PacketPtr InputSignal::asPacket(uint64_t packetOffset, const uint8_t* data, size_t size) +PacketPtr InputSignal::createDataPacket(uint64_t packetOffset, const uint8_t* data, size_t size) const { + std::scoped_lock lock(descriptorsSync); auto sampleType = currentDataDescriptor.getSampleType(); if (currentDataDescriptor.getPostScaling().assigned()) sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); @@ -29,33 +30,39 @@ PacketPtr InputSignal::asPacket(uint64_t packetOffset, const uint8_t* data, size return dataPacket; } -EventPacketPtr InputSignal::createDecriptorChangedPacket() +EventPacketPtr InputSignal::createDecriptorChangedPacket() const { + std::scoped_lock lock(descriptorsSync); return DataDescriptorChangedEventPacket(currentDataDescriptor, currentDomainDataDescriptor); } void InputSignal::setDataDescriptor(const DataDescriptorPtr& dataDescriptor) { + std::scoped_lock lock(descriptorsSync); currentDataDescriptor = dataDescriptor; } void InputSignal::setDomainDescriptor(const DataDescriptorPtr& domainDescriptor) { + std::scoped_lock lock(descriptorsSync); currentDomainDataDescriptor = domainDescriptor; } -bool InputSignal::hasDescriptors() +bool InputSignal::hasDescriptors() const { + std::scoped_lock lock(descriptorsSync); return currentDataDescriptor.assigned() && currentDomainDataDescriptor.assigned(); } -DataDescriptorPtr InputSignal::getSignalDescriptor() +DataDescriptorPtr InputSignal::getSignalDescriptor() const { + std::scoped_lock lock(descriptorsSync); return currentDataDescriptor; } -DataDescriptorPtr InputSignal::getDomainSignalDescriptor() +DataDescriptorPtr InputSignal::getDomainSignalDescriptor() const { + std::scoped_lock lock(descriptorsSync); return currentDomainDataDescriptor; } @@ -64,7 +71,7 @@ void InputSignal::setTableId(std::string id) tableId = id; } -std::string InputSignal::getTableId() +std::string InputSignal::getTableId() const { return tableId; } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index add1535..9c55f7c 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -258,7 +258,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& signalIter->second->hasDescriptors() && !signalIter->second->getSignalDescriptor().isStructDescriptor()) { - auto packet = signalIter->second->asPacket(timeStamp, data, size); + auto packet = signalIter->second->createDataPacket(timeStamp, data, size); onPacketCallback(id, packet); } } From 5a731baad0bc9cb4116f1288a17ce87d2365ebed Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Fri, 17 Nov 2023 17:34:08 +0100 Subject: [PATCH 009/127] Add 'streamed' setting for ISignal: * local signals - setting is ignored * mirrored signals - subscription status is bound to 'streamed' setting and the existence of listeners --- .../websocket_streaming/src/websocket_streaming_impl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 870e69c..4846fc9 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -83,7 +83,9 @@ void WebsocketStreamingImpl::onPacket(const StringPtr& signalId, const PacketPtr { auto signalRef = it->second; MirroredSignalConfigPtr signal = signalRef.assigned() ? signalRef.getRef() : nullptr; - if (signal.assigned() && signal.getActiveStreamingSource() == connectionString) + if (signal.assigned() && + signal.getStreamed() && + signal.getActiveStreamingSource() == connectionString) { const auto eventPacket = packet.asPtrOrNull(); if (eventPacket.assigned()) From aea4a0c0afba1a3f800e2ef207fb140244e19e73 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Mon, 20 Nov 2023 13:39:47 +0100 Subject: [PATCH 010/127] Increase streaming-lt version --- external/streaming_protocol/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index eceabe5..718d5a8 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.9 + REQUIRED_VERSION 0.10.10 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.9 + GIT_REF v0.10.10 EXPECT_TARGET daq::streaming_protocol ) From 556ed21d28cd0854cf65dc16b670e43d418379e6 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 23 Nov 2023 16:12:06 +0100 Subject: [PATCH 011/127] Integrate control interface into websocket streaming: * 'WebsocketControlPort' property added to websocket streaming server config * streaming server utilizes control server implementation from streaming-protocol-lt library * server subscribes signals only if request received from client --- external/streaming_protocol/CMakeLists.txt | 4 +- .../src/websocket_streaming_server_impl.cpp | 11 +- ...test_websocket_streaming_server_module.cpp | 4 + .../websocket_streaming/output_signal.h | 3 + .../websocket_streaming/streaming_server.h | 18 ++- .../websocket_streaming/websocket_streaming.h | 1 + .../websocket_streaming_server.h | 2 + .../websocket_streaming/src/output_signal.cpp | 24 ++- .../src/streaming_server.cpp | 152 +++++++++++++++++- .../src/websocket_streaming_server.cpp | 16 +- .../tests/test_websocket_client_device.cpp | 8 +- 11 files changed, 228 insertions(+), 15 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 718d5a8..0195b87 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.10 + REQUIRED_VERSION 0.10.11 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.10 + GIT_REF v0.10.11 EXPECT_TARGET daq::streaming_protocol ) diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 862c988..949ec55 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -13,9 +13,11 @@ WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, , websocketStreamingServer(rootDevice, context) , config(config) { - const uint16_t port = config.getPropertyValue("WebsocketStreamingPort"); + const uint16_t streamingPort = config.getPropertyValue("WebsocketStreamingPort"); + const uint16_t controlPort = config.getPropertyValue("WebsocketControlPort"); - websocketStreamingServer.setStreamingPort(port); + websocketStreamingServer.setStreamingPort(streamingPort); + websocketStreamingServer.setControlPort(controlPort); websocketStreamingServer.start(); } @@ -28,9 +30,12 @@ PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig() const auto websocketPortProp = IntPropertyBuilder("WebsocketStreamingPort", 7414).setMinValue(minPortValue).setMaxValue(maxPortValue).build(); - defaultConfig.addProperty(websocketPortProp); + const auto websocketControlPortProp = + IntPropertyBuilder("WebsocketControlPort", 7438).setMinValue(minPortValue).setMaxValue(maxPortValue).build(); + defaultConfig.addProperty(websocketControlPortProp); + return defaultConfig; } diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index 48e1e53..204077b 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -57,6 +57,7 @@ static PropertyObjectPtr CreateServerConfig(const InstancePtr& instance) { auto config = instance.getAvailableServerTypes().get("openDAQ WebsocketTcp Streaming").createDefaultConfig(); config.setPropertyValue("WebsocketStreamingPort", 0); + config.setPropertyValue("WebsocketControlPort", 0); return config; } @@ -122,6 +123,9 @@ TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) ASSERT_TRUE(config.hasProperty("WebsocketStreamingPort")); ASSERT_EQ(config.getPropertyValue("WebsocketStreamingPort"), 7414); + + ASSERT_TRUE(config.hasProperty("WebsocketControlPort")); + ASSERT_EQ(config.getPropertyValue("WebsocketControlPort"), 7438); } TEST_F(WebsocketStreamingServerModuleTest, CreateServer) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index e29918c..558511d 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -40,6 +40,8 @@ class OutputSignal virtual void write(const PacketPtr& packet); virtual void write(const void* data, size_t sampleCount); SignalPtr getCoreSignal(); + void setSubscribed(bool subscribed); + bool isSubscribed(); protected: DataDescriptorPtr getValueDescriptor(); @@ -58,6 +60,7 @@ class OutputSignal daq::streaming_protocol::StreamWriterPtr writer; SignalStreamPtr stream; size_t sampleSize; + bool subscribed; daq::streaming_protocol::LogCallback logCallback; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index c25e33a..2d94d8a 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -19,6 +19,7 @@ #include "stream/WebsocketServer.hpp" #include "websocket_streaming/output_signal.h" #include "streaming_protocol/StreamWriter.h" +#include "streaming_protocol/ControlServer.hpp" #include "streaming_protocol/Logging.hpp" #include #include @@ -41,15 +42,21 @@ class StreamingServer { public: using OnAcceptCallback = std::function(const daq::streaming_protocol::StreamWriterPtr& writer)>; + using OnSubscribeCallback = std::function; + using OnUnsubscribeCallback = std::function; StreamingServer(const ContextPtr& context); ~StreamingServer(); - void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); + void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT, + uint16_t controlPort = daq::streaming_protocol::HTTP_CONTROL_PORT); void stop(); void onAccept(const OnAcceptCallback& callback); + void onSubscribe(const OnSubscribeCallback& callback); + void onUnsubscribe(const OnUnsubscribeCallback& callback); void unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, const std::string& signalId, const PacketPtr& packet); void broadcastPacket(const std::string& signalId, const PacketPtr &packet); + void sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet); protected: using SignalMap = std::unordered_map; @@ -58,14 +65,23 @@ class StreamingServer void onAcceptInternal(const daq::stream::StreamPtr& stream); void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals); + void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); + bool isSignalSubscribed(const std::string& signalId) const; + int onControlCommand(const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage); uint16_t port; boost::asio::io_context ioContext; boost::asio::executor_work_guard work; daq::stream::WebsocketServerUniquePtr server; + std::unique_ptr controlServer; std::thread serverThread; ClientMap clients; OnAcceptCallback onAcceptCallback; + OnSubscribeCallback onSubscribeCallback; + OnUnsubscribeCallback onUnsubscribeCallback; LoggerPtr logger; LoggerComponentPtr loggerComponent; daq::streaming_protocol::LogCallback logCallback; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h index 779423a..f1f2225 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -27,6 +27,7 @@ namespace daq::streaming_protocol { static const uint16_t WEBSOCKET_LISTENING_PORT = 7414; + static const uint16_t HTTP_CONTROL_PORT = 7438; class SubscribedSignal; using SubscribedSignalPtr = std::shared_ptr; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index 46291de..321f2b3 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -30,6 +30,7 @@ class WebsocketStreamingServer ~WebsocketStreamingServer(); void setStreamingPort(uint16_t port); + void setControlPort(uint16_t port); void start(); void stop(); @@ -38,6 +39,7 @@ class WebsocketStreamingServer ContextPtr context; uint16_t streamingPort = 0; + uint16_t controlPort = 0; daq::websocket_streaming::StreamingServer streamingServer; daq::websocket_streaming::AsyncPacketReader packetReader; diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index d3efe57..1b160cb 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -23,6 +23,7 @@ OutputSignal::OutputSignal(const daq::streaming_protocol::StreamWriterPtr& write daq::streaming_protocol::LogCallback logCb) : signal(signal) , writer(writer) + , subscribed(false) , logCallback(logCb) { createSignalStream(); @@ -154,7 +155,6 @@ void OutputSignal::createSignalStream() sigProps.name = signal.getName(); sigProps.description = signal.getDescription(); SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); - stream->subscribe(); } void OutputSignal::createStreamedSignal() @@ -234,4 +234,26 @@ SignalPtr OutputSignal::getCoreSignal() return signal; } +void OutputSignal::setSubscribed(bool subscribed) +{ + if (subscribed) + { + // first provide signal meta information to client and only then + // toggle the member variable which will enable streaming signal data + stream->subscribe(); + this->subscribed = subscribed; + } + else + { + // first toggle the member variable to disable streaming signal data + this->subscribed = subscribed; + stream->unsubscribe(); + } +} + +bool OutputSignal::isSubscribed() +{ + return subscribed; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 03c739b..9fa2ade 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -32,7 +32,7 @@ StreamingServer::~StreamingServer() logger.removeComponent("StreamingServer"); } -void StreamingServer::start(uint16_t port) +void StreamingServer::start(uint16_t port, uint16_t controlPort) { this->port = port; @@ -42,6 +42,21 @@ void StreamingServer::start(uint16_t port) this->server = std::make_unique(ioContext, acceptFunc, port); this->server->start(); + + auto controlCommandCb = [this](const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage) + { + return onControlCommand(streamId, command, signalIds, errorMessage); + }; + this->controlServer = + std::make_unique(ioContext, + controlPort, + controlCommandCb, + logCallback); + this->controlServer->start(); + this->serverThread = std::thread([this]() { this->ioContext.run(); }); } @@ -62,6 +77,16 @@ void StreamingServer::onAccept(const OnAcceptCallback& callback) onAcceptCallback = callback; } +void StreamingServer::onSubscribe(const OnSubscribeCallback& callback) +{ + onSubscribeCallback = callback; +} + +void StreamingServer::onUnsubscribe(const OnUnsubscribeCallback& callback) +{ + onUnsubscribeCallback = callback; +} + void StreamingServer::unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, const std::string& signalId, const PacketPtr& packet) @@ -78,17 +103,26 @@ void StreamingServer::broadcastPacket(const std::string& signalId, const PacketP client[signalId]->write(packet); } +void StreamingServer::sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet) +{ + for (auto& [_, client] : clients) + if (auto signalIter = client.find(signalId); signalIter != client.end()) + { + if (signalIter->second->isSubscribed()) + signalIter->second->write(packet); + } +} + void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) { auto writer = std::make_shared(stream); writeProtocolInfo(writer); + writeInit(writer); auto signals = List(); if (onAcceptCallback) signals = onAcceptCallback(writer); - writeSignalsAvailable(writer, signals); - auto outputSignals = std::unordered_map(); for (const auto& signal : signals) { @@ -107,7 +141,89 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) } } + LOG_I("New client connected. Stream Id: {}", writer->id()); clients.insert({writer, outputSignals}); + + writeSignalsAvailable(writer, signals); +} + +int StreamingServer::onControlCommand(const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage) +{ + if (signalIds.empty()) + { + LOG_W("Signal list is empty, reject command", streamId); + errorMessage = "Signal list is empty"; + return -1; + } + + auto clientIter = std::find_if(std::begin(clients), + std::end(clients), + [&streamId](const auto& pair) + { + return pair.first->id() == streamId; + }); + + if (clientIter == std::end(clients)) + { + LOG_W("Unknown streamId: {}, reject command", streamId); + errorMessage = "Unknown streamId: '" + streamId + "'"; + return -1; + } + + if (command == "subscribe" || command == "unsubscribe") + { + size_t unknownSignalsCount = 0; + std::string message = "Command '" + command + "' failed for unknown signals:\n"; + for (const auto& signalId : signalIds) + { + if (auto signalIter = clientIter->second.find(signalId); signalIter != clientIter->second.end()) + { + // wasn't subscribed by requester client + if (command == "subscribe" && !signalIter->second->isSubscribed()) + { + // wasn't subscribed by any client + if (!isSignalSubscribed(signalId) && onSubscribeCallback) + { + onSubscribeCallback(signalId); + } + signalIter->second->setSubscribed(true); + } + // was subscribed by requester client + if (command == "unsubscribe" && signalIter->second->isSubscribed()) + { + signalIter->second->setSubscribed(false); + // became not subscribed by any client + if (!isSignalSubscribed(signalId) && onUnsubscribeCallback) + { + onUnsubscribeCallback(signalId); + } + } + } + else + { + unknownSignalsCount++; + message.append(signalId + "\n"); + } + } + + if (unknownSignalsCount > 0) + { + LOG_W("{}", message); + errorMessage = message; + return -1; + } + } + else + { + LOG_W("Unknown control command: {}", command); + errorMessage = "Unknown command: " + command; + return -1; + } + return 0; + } void StreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) @@ -131,4 +247,34 @@ void StreamingServer::writeSignalsAvailable(const daq::streaming_protocol::Strea writer->writeMetaInformation(0, msg); } +void StreamingServer::writeInit(const streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json initMeta; + initMeta[METHOD] = META_METHOD_INIT; + initMeta[PARAMS][META_STREAMID] = writer->id(); + + nlohmann::json jsonRpcHttp; + jsonRpcHttp["httpMethod"] = "POST"; + jsonRpcHttp["httpPath"] = "/"; + jsonRpcHttp["httpVersion"] = "1.1"; + jsonRpcHttp["port"] = std::to_string(controlServer->getPort()); + + nlohmann::json commandInterfaces; + commandInterfaces["jsonrpc-http"] = jsonRpcHttp; + + initMeta[PARAMS][COMMANDINTERFACES] = commandInterfaces; + writer->writeMetaInformation(0, initMeta); +} + +bool StreamingServer::isSignalSubscribed(const std::string& signalId) const +{ + bool result = false; + for (const auto& [_, signals] : clients) + { + if (auto iter = signals.find(signalId); iter != signals.end()) + result = result || iter->second->isSubscribed(); + } + return result; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 73517eb..34df432 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -31,26 +31,36 @@ void WebsocketStreamingServer::setStreamingPort(uint16_t port) this->streamingPort = port; } +void WebsocketStreamingServer::setControlPort(uint16_t port) +{ + this->controlPort = port; +} + void WebsocketStreamingServer::start() { if (!device.assigned()) throw InvalidStateException("Device is not set."); if (!context.assigned()) throw InvalidStateException("Context is not set."); - if (streamingPort == 0) + if (streamingPort == 0 || controlPort == 0) return; streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignalsRecursive(); }); - streamingServer.start(streamingPort); + // TODO implement subscribe/unsubscribe callbacks + streamingServer.onSubscribe([](const std::string& signalId) {} ); + streamingServer.onUnsubscribe([](const std::string& signalId) {} ); + streamingServer.start(streamingPort, controlPort); packetReader.setLoopFrequency(50); packetReader.onPacket([this](const SignalPtr& signal, const ListPtr& packets) { const auto signalId = signal.getGlobalId(); for (const auto& packet : packets) - streamingServer.broadcastPacket(signalId, packet); + streamingServer.sendPacketToSubscribers(signalId, packet); }); packetReader.startReading(device, context); + // The control port is published thru the streaming protocol itself + // so here the streaming port only is added to the StreamingInfo object StreamingInfoConfigPtr streamingInfo = StreamingInfo("daq.wss"); streamingInfo.addProperty(IntProperty("Port", streamingPort)); ErrCode errCode = this->device.asPtr()->addStreamingOption(streamingInfo); diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 65b3c58..fc3fbaa 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -14,6 +14,7 @@ class WebsocketClientDeviceTest : public testing::Test { public: const uint16_t STREAMING_PORT = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT; + const uint16_t CONTROL_PORT = daq::streaming_protocol::HTTP_CONTROL_PORT; const std::string HOST = "127.0.0.1"; ContextPtr context; @@ -40,6 +41,7 @@ TEST_F(WebsocketClientDeviceTest, CreateSuccess) // Setup and start streaming server auto server = WebsocketStreamingServer(serverInstance); server.setStreamingPort(STREAMING_PORT); + server.setControlPort(CONTROL_PORT); server.start(); DevicePtr clientDevice; @@ -54,6 +56,7 @@ TEST_F(WebsocketClientDeviceTest, DeviceInfo) // Setup and start streaming server auto server = WebsocketStreamingServer(serverInstance); server.setStreamingPort(STREAMING_PORT); + server.setControlPort(CONTROL_PORT); server.start(); // Create the client device @@ -78,7 +81,7 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) signals.pushBack(testSignal); return signals; }); - server->start(STREAMING_PORT); + server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); @@ -131,7 +134,7 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithoutDomain) signals.pushBack(testSignal); return signals; }); - server->start(STREAMING_PORT); + server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); @@ -150,6 +153,7 @@ TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) // Setup and start streaming server auto server = WebsocketStreamingServer(serverInstance); server.setStreamingPort(STREAMING_PORT); + server.setControlPort(CONTROL_PORT); server.start(); // Create the client device From 845ee414e086beda25ab9a14036c19db6c24392a Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 22 Nov 2023 15:42:51 +0100 Subject: [PATCH 012/127] Add name and description getter/setters to component TODO: Add OPC UA support --- .../include/websocket_streaming/websocket_client_signal_impl.h | 2 +- .../websocket_streaming/src/websocket_client_signal_impl.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 1da25b5..b85ac92 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -27,7 +27,7 @@ DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) virtual void assignDescriptor(const DataDescriptorPtr& descriptor) = 0; }; -class WebsocketClientSignalImpl final : public MirroredSignalBase +class WebsocketClientSignalImpl final : public MirroredSignalBase { public: explicit WebsocketClientSignalImpl(const ContextPtr& ctx, diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index 3e6f6b0..8f59114 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -11,7 +11,7 @@ static constexpr char delimeter = '#'; WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& streamingId) - : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId)) + : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId), nullptr, ComponentStandardProps::AddReadOnly) , streamingId(streamingId) { } From 07104af469466a994cd3dab1be60ea373a5ec317 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 30 Nov 2023 23:59:36 +0100 Subject: [PATCH 013/127] Add support for raw tcp socket streaming connection: * pseudo device can be alternatively connected using raw tcp sockets by prefix 'daq.tcp://' --- .../websocket_streaming_client_module_impl.h | 3 ++- ...websocket_streaming_client_module_impl.cpp | 22 +++++++++++++++---- ...test_websocket_streaming_client_module.cpp | 4 +++- .../websocket_streaming/streaming_client.h | 5 +++-- .../src/streaming_client.cpp | 14 +++++++++--- .../src/websocket_client_device_impl.cpp | 3 ++- .../src/websocket_streaming_impl.cpp | 2 +- .../tests/test_streaming.cpp | 9 ++++---- 8 files changed, 45 insertions(+), 17 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index bea814b..dff1499 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -37,7 +37,8 @@ class WebsocketStreamingClientModule final : public Module private: static StringPtr tryCreateWebsocketConnectionString(const StreamingInfoPtr& config); - static DeviceTypePtr createDeviceType(); + static DeviceTypePtr createWebsocketDeviceType(); + static DeviceTypePtr createTcpsocketDeviceType(); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index e544d06..bc18743 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -9,7 +9,9 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE static const char* WebsocketDeviceTypeId = "daq.ws"; +static const char* TcpsocketDeviceTypeId = "daq.tcp"; static const char* WebsocketDevicePrefix = "daq.ws://"; +static const char* TcpsocketDevicePrefix = "daq.tcp://"; static const char* WebsocketStreamingPrefix = "daq.wss://"; static const char* WebsocketStreamingID = "daq.wss"; @@ -38,7 +40,7 @@ ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() auto availableDevices = discoveryClient.discoverDevices(); for (auto device : availableDevices) { - device.asPtr().setDeviceType(createDeviceType()); + device.asPtr().setDeviceType(createWebsocketDeviceType()); } return availableDevices; } @@ -47,8 +49,11 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableDevi { auto result = Dict(); - auto deviceType = createDeviceType(); - result.set(deviceType.getId(), deviceType); + auto websocketDeviceType = createWebsocketDeviceType(); + auto tcpsocketDeviceType = createTcpsocketDeviceType(); + + result.set(websocketDeviceType.getId(), websocketDeviceType); + result.set(tcpsocketDeviceType.getId(), tcpsocketDeviceType); return result; } @@ -79,6 +84,8 @@ bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringP { std::string connStr = connectionString; auto found = connStr.find(WebsocketDevicePrefix); + if (found != 0) + found = connStr.find(TcpsocketDevicePrefix); return (found == 0); } @@ -138,11 +145,18 @@ StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(con return connectionString; } -DeviceTypePtr WebsocketStreamingClientModule::createDeviceType() +DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType() { return DeviceType(WebsocketDeviceTypeId, "Websocket enabled device", "Pseudo device, provides only signals of the remote device as flat list"); } +DeviceTypePtr WebsocketStreamingClientModule::createTcpsocketDeviceType() +{ + return DeviceType(TcpsocketDeviceTypeId, + "Tcpsocket enabled device", + "Pseudo device, provides only signals of the remote device as flat list"); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index c66607c..53a34b9 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -234,9 +234,11 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); - ASSERT_EQ(deviceTypes.getCount(), 1u); + ASSERT_EQ(deviceTypes.getCount(), 2u); ASSERT_TRUE(deviceTypes.hasKey("daq.ws")); ASSERT_EQ(deviceTypes.get("daq.ws").getId(), "daq.ws"); + ASSERT_TRUE(deviceTypes.hasKey("daq.tcp")); + ASSERT_EQ(deviceTypes.get("daq.tcp").getId(), "daq.tcp"); DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 4a76d3f..c2a4e96 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -50,8 +50,8 @@ class StreamingClient std::function; using OnAvailableSignalsCallback = std::function& signalIds)>; - StreamingClient(const ContextPtr& context, const std::string& connectionString); - StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target = "/"); + StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection = false); + StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target, bool useRawTcpConnection = false); ~StreamingClient(); bool connect(); @@ -112,6 +112,7 @@ class StreamingClient std::chrono::milliseconds connectTimeout{1000}; std::unordered_map, std::future>> signalInitializedStatus; std::unordered_map cachedDomainDescriptors; + bool useRawTcpConnection; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 9c55f7c..6866573 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -4,6 +4,7 @@ #include #include #include "stream/WebsocketClientStream.hpp" +#include "stream/TcpClientStream.hpp" #include "streaming_protocol/SignalContainer.hpp" #include "opendaq/custom_log.h" #include @@ -13,18 +14,19 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING using namespace daq::stream; using namespace daq::streaming_protocol; -StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString) +StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection) : logger(context.getLogger()) , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) , logCallback( [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); }) , signalContainer(logCallback) + , useRawTcpConnection(useRawTcpConnection) { parseConnectionString(connectionString); } -StreamingClient::StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target) +StreamingClient::StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target, bool useRawTcpConnection) : logger(context.getLogger()) , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) , logCallback( [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { @@ -34,6 +36,7 @@ StreamingClient::StreamingClient(const ContextPtr& context, const std::string& h , port(port) , target(target) , signalContainer(logCallback) + , useRawTcpConnection(useRawTcpConnection) { } @@ -63,7 +66,12 @@ bool StreamingClient::connect() signalContainer.setSignalMetaCb(signalMetaCallback); signalContainer.setDataAsRawCb(messageCallback); - auto clientStream = std::make_unique(ioContext, host, std::to_string(port), target); + std::unique_ptr clientStream; + if (useRawTcpConnection) + clientStream = std::make_unique(ioContext, host, std::to_string(port)); + else + clientStream = std::make_unique(ioContext, host, std::to_string(port), target); + protocolHandler = std::make_shared(ioContext, signalContainer, protocolMetaCallback, logCallback); std::unique_lock lock(clientMutex); protocolHandler->startWithSyncInit(std::move(clientStream)); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 9fc2bff..f411c0a 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -48,7 +48,8 @@ void WebsocketClientDeviceImpl::activateStreaming() /// connects to streaming server and waits till the list of available signals received void WebsocketClientDeviceImpl::createWebsocketStreaming() { - auto streamingClient = std::make_shared(context, connectionString); + bool useRawTcpConnection = connectionString.toStdString().find("daq.tcp://") == 0; + auto streamingClient = std::make_shared(context, connectionString, useRawTcpConnection); auto signalInitCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 4846fc9..36e797f 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -22,7 +22,7 @@ WebsocketStreamingImpl::WebsocketStreamingImpl(StreamingClientPtr streamingClien { prepareStreamingClient(); if (!this->streamingClient->connect()) - throw NotFoundException("Failed to connect to websocket server url: {}", connectionString); + throw NotFoundException("Failed to connect to streaming server url: {}", connectionString); } void WebsocketStreamingImpl::onSetActive(bool active) diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 4df6d07..88279c8 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -14,6 +14,7 @@ class StreamingTest : public testing::Test { public: const uint16_t StreamingPort = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT; + const std::string StreamingTarget = "/"; SignalPtr testDoubleSignal; ContextPtr context; @@ -42,7 +43,7 @@ TEST_F(StreamingTest, Connect) auto server = std::make_shared(context); server->start(StreamingPort); - auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); ASSERT_FALSE(client.isConnected()); client.connect(); @@ -58,7 +59,7 @@ TEST_F(StreamingTest, ConnectTimeout) auto server = std::make_shared(context); server->start(StreamingPort); - auto client = StreamingClient(context, "127.0.0.1", 7000); + auto client = StreamingClient(context, "127.0.0.1", 7000, StreamingTarget); client.connect(); ASSERT_FALSE(client.isConnected()); @@ -69,7 +70,7 @@ TEST_F(StreamingTest, ConnectTwice) auto server = std::make_shared(context); server->start(StreamingPort); - auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); ASSERT_TRUE(client.connect()); client.disconnect(); @@ -113,7 +114,7 @@ TEST_F(StreamingTest, SimpePacket) server->start(StreamingPort); std::vector receivedPackets; - auto client = StreamingClient(context, "127.0.0.1", StreamingPort); + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); auto onPacket = [&receivedPackets](const StringPtr& signalId, const PacketPtr& packet) { From cdcb58c94a9b3a73056a2d29bf0df736539c73e1 Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Mon, 4 Dec 2023 11:00:18 +0100 Subject: [PATCH 014/127] Rework how interface inheritance and queryInterface work to hopefully prevent ICEs and out-of-heap-space errors --- modules/websocket_streaming_client_module/src/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/src/CMakeLists.txt | 2 +- shared/libraries/websocket_streaming/src/CMakeLists.txt | 5 +++-- shared/libraries/websocket_streaming/tests/CMakeLists.txt | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/CMakeLists.txt b/modules/websocket_streaming_client_module/src/CMakeLists.txt index 2f21ce1..b5260b5 100644 --- a/modules/websocket_streaming_client_module/src/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/src/CMakeLists.txt @@ -31,7 +31,7 @@ add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq PRIVATE daq::discovery - daq::opendaq_websocket_streaming + daq::websocket_streaming ) target_include_directories(${LIB_NAME} PUBLIC $ diff --git a/modules/websocket_streaming_server_module/src/CMakeLists.txt b/modules/websocket_streaming_server_module/src/CMakeLists.txt index bd34790..2f25750 100644 --- a/modules/websocket_streaming_server_module/src/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/src/CMakeLists.txt @@ -35,7 +35,7 @@ add_library(${LIB_NAME} SHARED ${SRC_Include} add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq - PRIVATE daq::opendaq_websocket_streaming + PRIVATE daq::websocket_streaming ) target_include_directories(${LIB_NAME} PUBLIC $ diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index 3c9c3fa..5baff6d 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -1,4 +1,5 @@ -set(LIB_NAME ${SDK_TARGET_NAME}_websocket_streaming) +set(BASE_NAME websocket_streaming) +set(LIB_NAME ${SDK_TARGET_NAME}_${BASE_NAME}) set(LIB_MAJOR_VERSION 0) set(LIB_MINOR_VERSION 4) @@ -49,7 +50,7 @@ add_library(${LIB_NAME} STATIC ${SRC_Cpp} ${SRC_PrivateHeaders} ) -add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) +add_library(${SDK_TARGET_NAMESPACE}::${BASE_NAME} ALIAS ${LIB_NAME}) if(BUILD_64Bit OR BUILD_ARM) set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index 226bb17..63b015b 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -set(MODULE_NAME ${SDK_TARGET_NAME}_websocket_streaming) +set(MODULE_NAME websocket_streaming) set(TEST_APP test_${MODULE_NAME}) add_executable(${TEST_APP} From e61b22cc54808eb1e822d60a968ca2505710676f Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 28 Nov 2023 18:14:07 +0100 Subject: [PATCH 015/127] Utilize signal subscribing within the websocket streaming: * subscribe/unsubscribe client signals which connected/disconnected to/from InputPort * temporarily subscribe all available signals during streaming connection initialization --- .../websocket_streaming/async_packet_reader.h | 19 ++- .../websocket_streaming/streaming_client.h | 6 +- .../websocket_streaming/streaming_server.h | 4 +- .../src/async_packet_reader.cpp | 88 ++++++++++-- .../src/streaming_client.cpp | 125 +++++++++++------- .../src/streaming_server.cpp | 4 +- .../src/websocket_streaming_impl.cpp | 6 +- .../src/websocket_streaming_server.cpp | 10 +- .../tests/test_streaming.cpp | 15 ++- 9 files changed, 198 insertions(+), 79 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index 0bb5033..84c5433 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -19,6 +19,7 @@ #include "websocket_streaming/websocket_streaming.h" #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -27,17 +28,22 @@ class AsyncPacketReader public: using OnPacketCallback = std::function& packets)>; - AsyncPacketReader(); + AsyncPacketReader(const DevicePtr& device, const ContextPtr& context); ~AsyncPacketReader(); - void startReading(const DevicePtr& device, const ContextPtr& context); - void stopReading(); + void start(); + void stop(); void onPacket(const OnPacketCallback& callback); void setLoopFrequency(uint32_t freqency); + void startReadSignal(const SignalPtr& signal); + void stopReadSignal(const SignalPtr& signal); protected: void startReadThread(); void createReaders(); + void addReader(SignalPtr signalToRead); + void removeReader(SignalPtr signalToRead); + void updateReaders(); DevicePtr device; ContextPtr context; @@ -46,6 +52,13 @@ class AsyncPacketReader bool readThreadStarted = false; std::chrono::milliseconds sleepTime; std::vector> signalReaders; + + // second element of pair is true for adding signal reader request, false for removing + std::queue> readerControlQueue; + + LoggerPtr logger; + LoggerComponentPtr loggerComponent; + std::mutex readersSync; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index c2a4e96..60d709b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -69,7 +69,8 @@ class StreamingClient bool isConnected(); void setConnectTimeout(std::chrono::milliseconds timeout); EventPacketPtr getDataDescriptorChangedEventPacket(const StringPtr& signalStringId); - + void subscribeSignals(const std::vector& signalIds); + void unsubscribeSignals(const std::vector& signalIds); protected: void parseConnectionString(const std::string& url); @@ -80,12 +81,13 @@ class StreamingClient void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size); void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - void publishSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + void publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); void setSignalInitSatisfied(const std::string& signalId); void setDomainDescriptor(const std::string& signalId, const InputSignalPtr& inputSignal, const DataDescriptorPtr& domainDescriptor); + std::pair findSignalByTableId(const std::string& tableId); LoggerPtr logger; LoggerComponentPtr loggerComponent; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 2d94d8a..5958509 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -42,8 +42,8 @@ class StreamingServer { public: using OnAcceptCallback = std::function(const daq::streaming_protocol::StreamWriterPtr& writer)>; - using OnSubscribeCallback = std::function; - using OnUnsubscribeCallback = std::function; + using OnSubscribeCallback = std::function; + using OnUnsubscribeCallback = std::function; StreamingServer(const ContextPtr& context); ~StreamingServer(); diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index 7cb5d54..b7bf299 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -1,9 +1,14 @@ #include "websocket_streaming/async_packet_reader.h" #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING -AsyncPacketReader::AsyncPacketReader() +AsyncPacketReader::AsyncPacketReader(const DevicePtr& device, const ContextPtr& context) + : device(device) + , context(context) + , logger(context.getLogger()) + , loggerComponent(logger.getOrAddComponent("WebsocketStreamingPacketReader")) { setLoopFrequency(50); onPacketCallback = [](const SignalPtr& signal, const ListPtr& packets) {}; @@ -11,23 +16,27 @@ AsyncPacketReader::AsyncPacketReader() AsyncPacketReader::~AsyncPacketReader() { - stopReading(); + stop(); } -void AsyncPacketReader::startReading(const DevicePtr& device, const ContextPtr& context) +void AsyncPacketReader::start() { - this->device = device; - this->context = context; - readThreadStarted = true; - this->readThread = std::thread([this]() { this->startReadThread(); }); + this->readThread = std::thread([this]() + { + this->startReadThread(); + LOG_I("Reading thread finished"); + }); } -void AsyncPacketReader::stopReading() +void AsyncPacketReader::stop() { readThreadStarted = false; if (readThread.joinable()) + { readThread.join(); + LOG_I("Reading thread joined"); + } signalReaders.clear(); } @@ -45,10 +54,9 @@ void AsyncPacketReader::setLoopFrequency(uint32_t freqency) void AsyncPacketReader::startReadThread() { - createReaders(); - while (readThreadStarted) { + updateReaders(); for (const auto& [signal, reader] : signalReaders) { if (reader.getAvailableCount() == 0) @@ -69,10 +77,66 @@ void AsyncPacketReader::createReaders() for (const auto& signal : signals) { - auto reader = PacketReader(signal); - signalReaders.push_back(std::pair({signal, reader})); + addReader(signal); } } +void AsyncPacketReader::startReadSignal(const SignalPtr& signal) +{ + std::scoped_lock lock(readersSync); + readerControlQueue.push({signal, true}); +} + +void AsyncPacketReader::stopReadSignal(const SignalPtr& signal) +{ + std::scoped_lock lock(readersSync); + readerControlQueue.push({signal, false}); +} + +void AsyncPacketReader::updateReaders() +{ + std::scoped_lock lock(readersSync); + while (!readerControlQueue.empty()) + { + auto [signal, doRead] = readerControlQueue.front(); + if (doRead) + addReader(signal); + else + removeReader(signal); + readerControlQueue.pop(); + } +} + +void AsyncPacketReader::addReader(SignalPtr signalToRead) +{ + auto it = std::find_if(signalReaders.begin(), + signalReaders.end(), + [&signalToRead](const std::pair& element) + { + return element.first == signalToRead; + }); + if (it != signalReaders.end()) + return; + + LOG_I("Add reader for signal {}", signalToRead.getGlobalId()); + auto reader = PacketReader(signalToRead); + signalReaders.push_back(std::pair({signalToRead, reader})); +} + +void AsyncPacketReader::removeReader(SignalPtr signalToRead) +{ + auto it = std::find_if(signalReaders.begin(), + signalReaders.end(), + [&signalToRead](const std::pair& element) + { + return element.first == signalToRead; + }); + if (it == signalReaders.end()) + return; + + LOG_I("Remove reader for signal {}", signalToRead.getGlobalId()); + signalReaders.erase(it); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 6866573..a8969f2 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -81,16 +81,37 @@ bool StreamingClient::connect() conditionVariable.wait_for(lock, connectTimeout, [this]() { return connected; }); - const auto timeout = std::chrono::seconds(1); - auto timeoutExpired = std::chrono::system_clock::now() + timeout; - - for (const auto& [id, promiseFuturePair] : signalInitializedStatus) + if (connected) { - auto status = promiseFuturePair.second.wait_until(timeoutExpired); - if (status != std::future_status::ready) + std::vector signalIds; + for (const auto& [signalId,_] : signals) { - LOG_W("signal {} has incomplete descriptors", id); + signalIds.push_back(signalId); + std::promise signalInitPromise; + std::future signalInitFuture = signalInitPromise.get_future(); + signalInitializedStatus.insert_or_assign(signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))); } + + // signal meta-information (signal description, tableId, related signals, etc.) + // is published only for subscribed signals. + // as workaround we temporarily subscribe all signals to receive signal meta-info + // and initialize signal descriptors + protocolHandler->subscribe(signalIds); + + const auto timeout = std::chrono::seconds(1); + auto timeoutExpired = std::chrono::system_clock::now() + timeout; + + for (const auto& [id, promiseFuturePair] : signalInitializedStatus) + { + auto status = promiseFuturePair.second.wait_until(timeoutExpired); + if (status != std::future_status::ready) + { + LOG_W("signal {} has incomplete descriptors", id); + } + } + + // unsubscribe previously subscribed signals + protocolHandler->unsubscribe(signalIds); } return connected; @@ -228,10 +249,6 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p std::string signalId = arrayItem; signalIds.push_back(signalId); - std::promise signalInitPromise; - std::future signalInitFuture = signalInitPromise.get_future(); - signalInitializedStatus.insert_or_assign(signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))); - if (auto signalIt = signals.find(signalId); signalIt == signals.end()) { auto inputSignal = std::make_shared(); @@ -243,8 +260,6 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p } } - protocolHandler.subscribe(signalIds); - onAvailableDeviceSignalsCb(signalIds); onAvailableStreamingSignalsCb(signalIds); } @@ -255,6 +270,16 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p } } +void StreamingClient::subscribeSignals(const std::vector& signalIds) +{ + protocolHandler->subscribe(signalIds); +} + +void StreamingClient::unsubscribeSignals(const std::vector& signalIds) +{ + protocolHandler->unsubscribe(signalIds); +} + void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, @@ -288,8 +313,8 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig signalIt->second->setTableId(tableId); if (auto domainDescIt = cachedDomainDescriptors.find(tableId); domainDescIt != cachedDomainDescriptors.end()) { - if (!signalIt->second->getDomainSignalDescriptor().assigned()) - setDomainDescriptor(signalIt->first, signalIt->second, domainDescIt->second); + setDomainDescriptor(signalIt->first, signalIt->second, domainDescIt->second); + cachedDomainDescriptors.erase(domainDescIt); } } } @@ -297,20 +322,13 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) { std::string tableId = subscribedSignal.tableId(); - - // check if the value signal with tableId is known - // and the descriptors for it are already received auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); - auto signalIt = std::find_if(signals.begin(), - signals.end(), - [tableId](const std::pair& pair) - { - return tableId == pair.second->getTableId(); - }); - if (signalIt != signals.end()) + + // check if the input value signal with tableId is known + if (auto [id, inputSignal] = findSignalByTableId(tableId); inputSignal != nullptr) { - // value signal with tableId is known, set domain descriptor for it - setDomainDescriptor(signalIt->first, signalIt->second, sInfo.dataDescriptor); + // value input signal with tableId is known, set domain descriptor for it + setDomainDescriptor(id, inputSignal, sInfo.dataDescriptor); } else { @@ -319,30 +337,31 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig } } -void StreamingClient::publishSignal(const SubscribedSignal& subscribedSignal) +void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal) +{ + // signal meta information is always received by pairs of META_METHOD_SIGNAL: + // one is meta for data signal, another is meta for time signal. + // we generate event packet only after both meta are received + // and all signal descriptors are assigned. + if (!signal->hasDescriptors()) + return; + + auto eventPacket = signal->createDecriptorChangedPacket(); + onPacketCallback(signalId, eventPacket); +} + +std::pair StreamingClient::findSignalByTableId(const std::string& tableId) { - std::string tableId = subscribedSignal.tableId(); auto signalIt = std::find_if(signals.begin(), signals.end(), - [tableId](const std::pair& pair) + [&tableId](const std::pair& pair) { return tableId == pair.second->getTableId(); }); if (signalIt != signals.end()) - { - auto inputSignal = signalIt->second; - auto signalId = signalIt->first; - - // signal meta information is always received by pairs of META_METHOD_SIGNAL: - // one is meta for data signal, another is meta for time signal. - // we generate event packet only after both meta are received - // and all signal descriptors are assigned. - if (!inputSignal->hasDescriptors()) - return; - - auto eventPacket = inputSignal->createDecriptorChangedPacket(); - onPacketCallback(signalId, eventPacket); - } + return {signalIt->first, signalIt->second}; + else + return {std::string(), nullptr}; } void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params) @@ -359,12 +378,28 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& params.dump()); } + DataDescriptorPtr dataDescriptor, domainDescriptor; + std::string tableId = subscribedSignal.tableId(); + if (auto [_,signal] = findSignalByTableId(tableId); signal != nullptr) + { + dataDescriptor = signal->getSignalDescriptor(); + domainDescriptor = signal->getDomainSignalDescriptor(); + } + if (subscribedSignal.isTimeSignal()) setTimeSignal(subscribedSignal); else setDataSignal(subscribedSignal); - publishSignal(subscribedSignal); + if (auto [id, signal] = findSignalByTableId(tableId); signal != nullptr) + { + if (dataDescriptor != signal->getSignalDescriptor() || + domainDescriptor != signal->getDomainSignalDescriptor()) + { + // descriptors changed - generate event packet and send it to signal listeners + publishSignalChanges(id, signal); + } + } } catch (const DaqException& e) { diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 9fa2ade..4c60fba 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -187,7 +187,7 @@ int StreamingServer::onControlCommand(const std::string& streamId, // wasn't subscribed by any client if (!isSignalSubscribed(signalId) && onSubscribeCallback) { - onSubscribeCallback(signalId); + onSubscribeCallback(signalIter->second->getCoreSignal()); } signalIter->second->setSubscribed(true); } @@ -198,7 +198,7 @@ int StreamingServer::onControlCommand(const std::string& streamId, // became not subscribed by any client if (!isSignalSubscribed(signalId) && onUnsubscribeCallback) { - onUnsubscribeCallback(signalId); + onUnsubscribeCallback(signalIter->second->getCoreSignal()); } } } diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 36e797f..4e0dd45 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -38,12 +38,14 @@ void WebsocketStreamingImpl::onRemoveSignal(const MirroredSignalConfigPtr& /*sig { } -void WebsocketStreamingImpl::onSubscribeSignal(const MirroredSignalConfigPtr& /*signal*/) +void WebsocketStreamingImpl::onSubscribeSignal(const MirroredSignalConfigPtr& signal) { + streamingClient->subscribeSignals({getSignalStreamingId(signal).toStdString()}); } -void WebsocketStreamingImpl::onUnsubscribeSignal(const MirroredSignalConfigPtr& /*signal*/) +void WebsocketStreamingImpl::onUnsubscribeSignal(const MirroredSignalConfigPtr& signal) { + streamingClient->unsubscribeSignals({getSignalStreamingId(signal).toStdString()}); } EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const MirroredSignalConfigPtr& signal) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 34df432..6823960 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -23,6 +23,7 @@ WebsocketStreamingServer::WebsocketStreamingServer(const DevicePtr& device, cons : device(device) , context(context) , streamingServer(context) + , packetReader(device, context) { } @@ -46,9 +47,8 @@ void WebsocketStreamingServer::start() return; streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignalsRecursive(); }); - // TODO implement subscribe/unsubscribe callbacks - streamingServer.onSubscribe([](const std::string& signalId) {} ); - streamingServer.onUnsubscribe([](const std::string& signalId) {} ); + streamingServer.onSubscribe([this](const daq::SignalPtr& signal) { packetReader.startReadSignal(signal); } ); + streamingServer.onUnsubscribe([this](const daq::SignalPtr& signal) { packetReader.stopReadSignal(signal); } ); streamingServer.start(streamingPort, controlPort); packetReader.setLoopFrequency(50); @@ -57,7 +57,7 @@ void WebsocketStreamingServer::start() for (const auto& packet : packets) streamingServer.sendPacketToSubscribers(signalId, packet); }); - packetReader.startReading(device, context); + packetReader.start(); // The control port is published thru the streaming protocol itself // so here the streaming port only is added to the StreamingInfo object @@ -77,7 +77,7 @@ void WebsocketStreamingServer::stop() void WebsocketStreamingServer::stopInternal() { - packetReader.stopReading(); + packetReader.stop(); streamingServer.stop(); } diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 88279c8..bed498a 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -15,6 +15,7 @@ class StreamingTest : public testing::Test public: const uint16_t StreamingPort = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT; const std::string StreamingTarget = "/"; + const uint16_t ControlPort = daq::streaming_protocol::HTTP_CONTROL_PORT; SignalPtr testDoubleSignal; ContextPtr context; @@ -41,7 +42,7 @@ class StreamingTest : public testing::Test TEST_F(StreamingTest, Connect) { auto server = std::make_shared(context); - server->start(StreamingPort); + server->start(StreamingPort, ControlPort); auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); @@ -57,7 +58,7 @@ TEST_F(StreamingTest, Connect) TEST_F(StreamingTest, ConnectTimeout) { auto server = std::make_shared(context); - server->start(StreamingPort); + server->start(StreamingPort, ControlPort); auto client = StreamingClient(context, "127.0.0.1", 7000, StreamingTarget); @@ -68,7 +69,7 @@ TEST_F(StreamingTest, ConnectTimeout) TEST_F(StreamingTest, ConnectTwice) { auto server = std::make_shared(context); - server->start(StreamingPort); + server->start(StreamingPort, ControlPort); auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); @@ -111,7 +112,7 @@ TEST_F(StreamingTest, SimpePacket) signals.pushBack(testDoubleSignal); return signals; }); - server->start(StreamingPort); + server->start(StreamingPort, ControlPort); std::vector receivedPackets; auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); @@ -128,9 +129,11 @@ TEST_F(StreamingTest, SimpePacket) client.connect(); ASSERT_TRUE(client.isConnected()); - std::string signalId = testDoubleSignal.getGlobalId(); - server->broadcastPacket(signalId, packet); + client.subscribeSignals({testDoubleSignal.getGlobalId()}); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + std::string signalId = testDoubleSignal.getGlobalId(); + server->sendPacketToSubscribers(signalId, packet); std::this_thread::sleep_for(std::chrono::milliseconds(250)); ASSERT_EQ(receivedPackets.size(), 2u); From cde718c975d2a2540f86c6e299b1898d80d59d2d Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 30 Nov 2023 22:00:52 +0100 Subject: [PATCH 016/127] Add subscribe/unsubscribe completion acknowledgement Events --- .../websocket_streaming/websocket_streaming_impl.h | 2 +- .../src/websocket_streaming_impl.cpp | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 3a4d799..a7d2f76 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -47,7 +47,7 @@ class WebsocketStreamingImpl : public Streaming StringPtr getSignalStreamingId(const MirroredSignalConfigPtr& signal); daq::websocket_streaming::StreamingClientPtr streamingClient; - std::vector availableSignalIds; + std::vector availableSignalIds; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 4e0dd45..deffaea 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -100,25 +100,23 @@ void WebsocketStreamingImpl::onPacket(const StringPtr& signalId, const PacketPtr void WebsocketStreamingImpl::onAvailableSignals(const std::vector& signalIds) { - availableSignalIds = signalIds; + for (const auto& signalId : signalIds) + availableSignalIds.push_back(String(signalId)); } -StringPtr WebsocketStreamingImpl::getSignalStreamingId(const MirroredSignalConfigPtr &signal) +StringPtr WebsocketStreamingImpl::getSignalStreamingId(const MirroredSignalConfigPtr& signal) { - std::string signalFullId = signal.getRemoteId().toStdString(); const auto it = std::find_if( availableSignalIds.begin(), availableSignalIds.end(), - [signalFullId](std::string idEnding) + [&signal](const StringPtr& signalStreamingId) { - if (idEnding.size() > signalFullId.size()) - return false; - return std::equal(idEnding.rbegin(), idEnding.rend(), signalFullId.rbegin()); + return signal.template asPtr()->hasMatchingId(signalStreamingId); } ); if (it != availableSignalIds.end()) - return String(*it); + return *it; else throw NotFoundException("Signal with id {} is not available in Websocket streaming", signal.getRemoteId()); } From c1343ca27575837b3b5e73dc5919c77ce60b4d35 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 4 Dec 2023 23:34:16 +0100 Subject: [PATCH 017/127] Modify the management of readers for streaming servers: * Immediate addition of a reader upon signal subscription. * Immediate removal of a reader upon signal unsubscription. --- .../websocket_streaming/async_packet_reader.h | 4 --- .../src/async_packet_reader.cpp | 34 ++++++------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index 84c5433..f25e7f0 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -43,7 +43,6 @@ class AsyncPacketReader void createReaders(); void addReader(SignalPtr signalToRead); void removeReader(SignalPtr signalToRead); - void updateReaders(); DevicePtr device; ContextPtr context; @@ -53,9 +52,6 @@ class AsyncPacketReader std::chrono::milliseconds sleepTime; std::vector> signalReaders; - // second element of pair is true for adding signal reader request, false for removing - std::queue> readerControlQueue; - LoggerPtr logger; LoggerComponentPtr loggerComponent; std::mutex readersSync; diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index b7bf299..77edf50 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -56,14 +56,16 @@ void AsyncPacketReader::startReadThread() { while (readThreadStarted) { - updateReaders(); - for (const auto& [signal, reader] : signalReaders) { - if (reader.getAvailableCount() == 0) - continue; - - const auto& packets = reader.readAll(); - onPacketCallback(signal, packets); + std::scoped_lock lock(readersSync); + for (const auto& [signal, reader] : signalReaders) + { + if (reader.getAvailableCount() == 0) + continue; + + const auto& packets = reader.readAll(); + onPacketCallback(signal, packets); + } } std::this_thread::sleep_for(sleepTime); @@ -84,27 +86,13 @@ void AsyncPacketReader::createReaders() void AsyncPacketReader::startReadSignal(const SignalPtr& signal) { std::scoped_lock lock(readersSync); - readerControlQueue.push({signal, true}); + addReader(signal); } void AsyncPacketReader::stopReadSignal(const SignalPtr& signal) { std::scoped_lock lock(readersSync); - readerControlQueue.push({signal, false}); -} - -void AsyncPacketReader::updateReaders() -{ - std::scoped_lock lock(readersSync); - while (!readerControlQueue.empty()) - { - auto [signal, doRead] = readerControlQueue.front(); - if (doRead) - addReader(signal); - else - removeReader(signal); - readerControlQueue.pop(); - } + removeReader(signal); } void AsyncPacketReader::addReader(SignalPtr signalToRead) From bbb59c34d9a55b7ba3f867909935f98f7996a343 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 6 Dec 2023 11:37:26 +0100 Subject: [PATCH 018/127] Add subscription completion acknowledgement processing for websocket streaming --- .../websocket_streaming/output_signal.h | 1 + .../websocket_streaming/streaming_client.h | 16 +++++- .../websocket_streaming/streaming_server.h | 2 + .../websocket_streaming/src/output_signal.cpp | 26 +++++---- .../src/streaming_client.cpp | 48 ++++++++++++++--- .../src/streaming_server.cpp | 42 ++++++++------- .../src/websocket_streaming_impl.cpp | 19 +++++++ .../tests/test_streaming.cpp | 53 ++++++++++++++++++- 8 files changed, 167 insertions(+), 40 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index 558511d..7a1d63e 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -61,6 +61,7 @@ class OutputSignal SignalStreamPtr stream; size_t sampleSize; bool subscribed; + std::mutex subscribedSync; daq::streaming_protocol::LogCallback logCallback; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 60d709b..a95c414 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -49,6 +49,7 @@ class StreamingClient using OnDomainDescriptorCallback = std::function; using OnAvailableSignalsCallback = std::function& signalIds)>; + using OnSubsciptionAckCallback = std::function; StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection = false); StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target, bool useRawTcpConnection = false); @@ -63,6 +64,7 @@ class StreamingClient void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); void onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback); void onFindSignal(const OnFindSignalCallback& callback); + void onSubscriptionAck(const OnSubsciptionAckCallback& callback); std::string getHost(); uint16_t getPort(); std::string getTarget(); @@ -108,11 +110,23 @@ class StreamingClient OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; OnFindSignalCallback onFindSignalCallback = [](const StringPtr& signalId) { return nullptr; }; OnSignalCallback onSignalUpdatedCallback = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; + OnSubsciptionAckCallback onSubscriptionAckCallback = [](const StringPtr& signalId, bool subscribed) {}; std::thread clientThread; std::mutex clientMutex; std::condition_variable conditionVariable; std::chrono::milliseconds connectTimeout{1000}; - std::unordered_map, std::future>> signalInitializedStatus; + + // signal meta-information (signal description, tableId, related signals, etc.) + // is published only for subscribed signals. + // as workaround we temporarily subscribe all signals to receive signal meta-info + // at initialization stage. + // To manage this the 'signalInitializedStatus' is used, it is map of 4-element tuples, where: + // 1-st is std::promise + // 2-nd is std::future + // 3-rd: boolean flag indicating that initial subscription completion ack is filtered-out + // 4-th: boolean flag indicating that initial unsubscription completion ack is filtered-out + std::unordered_map, std::future, bool, bool>> signalInitializedStatus; + std::unordered_map cachedDomainDescriptors; bool useRawTcpConnection; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 5958509..08b0096 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -67,6 +67,8 @@ class StreamingServer void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals); void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); bool isSignalSubscribed(const std::string& signalId) const; + void subscribeHandler(const std::string& signalId, OutputSignalPtr signal); + void unsubscribeHandler(const std::string& signalId, OutputSignalPtr signal); int onControlCommand(const std::string& streamId, const std::string& command, const daq::streaming_protocol::SignalIds& signalIds, diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 1b160cb..acb5b1c 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -234,25 +234,31 @@ SignalPtr OutputSignal::getCoreSignal() return signal; } +// bool subscribed; / void setSubscribed(bool); / bool isSubscribed() : +// To prevent client side streaming protocol error the server should not send data before +// the subscribe acknowledgment is sent neither after the unsubscribe acknowledgment is sent. +// The `subscribed` member variable functions as a flag that enables/disables data streaming +// within the OutputSignal. +// Mutex locking ensures that data is sent only when the specified conditions are satisfied. + void OutputSignal::setSubscribed(bool subscribed) { - if (subscribed) + if (this->subscribed != subscribed) { - // first provide signal meta information to client and only then - // toggle the member variable which will enable streaming signal data - stream->subscribe(); - this->subscribed = subscribed; - } - else - { - // first toggle the member variable to disable streaming signal data + std::scoped_lock lock(subscribedSync); + this->subscribed = subscribed; - stream->unsubscribe(); + if (subscribed) + stream->subscribe(); + else + stream->unsubscribe(); } } bool OutputSignal::isSubscribed() { + std::scoped_lock lock(subscribedSync); + return subscribed; } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index a8969f2..5979a23 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -89,7 +89,15 @@ bool StreamingClient::connect() signalIds.push_back(signalId); std::promise signalInitPromise; std::future signalInitFuture = signalInitPromise.get_future(); - signalInitializedStatus.insert_or_assign(signalId, std::make_pair(std::move(signalInitPromise), std::move(signalInitFuture))); + signalInitializedStatus.insert_or_assign( + signalId, + std::make_tuple( + std::move(signalInitPromise), + std::move(signalInitFuture), + false, + false + ) + ); } // signal meta-information (signal description, tableId, related signals, etc.) @@ -101,9 +109,9 @@ bool StreamingClient::connect() const auto timeout = std::chrono::seconds(1); auto timeoutExpired = std::chrono::system_clock::now() + timeout; - for (const auto& [id, promiseFuturePair] : signalInitializedStatus) + for (const auto& [id, params] : signalInitializedStatus) { - auto status = promiseFuturePair.second.wait_until(timeoutExpired); + auto status = std::get<1>(params).wait_until(timeoutExpired); if (status != std::future_status::ready) { LOG_W("signal {} has incomplete descriptors", id); @@ -119,12 +127,10 @@ bool StreamingClient::connect() void StreamingClient::disconnect() { + ioContext.stop(); if (clientThread.joinable()) - { - ioContext.stop(); clientThread.join(); - connected = false; - } + connected = false; } void StreamingClient::onPacket(const OnPacketCallback& callack) @@ -162,6 +168,11 @@ void StreamingClient::onFindSignal(const OnFindSignalCallback& callback) onFindSignalCallback = callback; } +void StreamingClient::onSubscriptionAck(const OnSubsciptionAckCallback& callback) +{ + onSubscriptionAckCallback = callback; +} + std::string StreamingClient::getHost() { return host; @@ -231,6 +242,27 @@ void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, con { if (method == daq::streaming_protocol::META_METHOD_SIGNAL) onSignal(subscribedSignal, params); + + std::string signalId = subscribedSignal.signalId(); + if (auto it = signalInitializedStatus.find(signalId); it != signalInitializedStatus.end()) + { + if (method == daq::streaming_protocol::META_METHOD_SUBSCRIBE) + { + // skips the first subscribe ack from server + if (std::get<2>(it->second)) + onSubscriptionAckCallback(subscribedSignal.signalId(), true); + else + std::get<2>(it->second) = true; + } + else if (method == daq::streaming_protocol::META_METHOD_UNSUBSCRIBE) + { + // skips the first unsubscribe ack from server + if (std::get<3>(it->second)) + onSubscriptionAckCallback(subscribedSignal.signalId(), false); + else + std::get<3>(it->second) = true; + } + } } void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& protocolHandler, @@ -413,7 +445,7 @@ void StreamingClient::setSignalInitSatisfied(const std::string& signalId) { try { - iterator->second.first.set_value(); + std::get<0>(iterator->second).set_value(); } catch (std::future_error& e) { diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 4c60fba..d21c6e6 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -181,26 +181,10 @@ int StreamingServer::onControlCommand(const std::string& streamId, { if (auto signalIter = clientIter->second.find(signalId); signalIter != clientIter->second.end()) { - // wasn't subscribed by requester client - if (command == "subscribe" && !signalIter->second->isSubscribed()) - { - // wasn't subscribed by any client - if (!isSignalSubscribed(signalId) && onSubscribeCallback) - { - onSubscribeCallback(signalIter->second->getCoreSignal()); - } - signalIter->second->setSubscribed(true); - } - // was subscribed by requester client - if (command == "unsubscribe" && signalIter->second->isSubscribed()) - { - signalIter->second->setSubscribed(false); - // became not subscribed by any client - if (!isSignalSubscribed(signalId) && onUnsubscribeCallback) - { - onUnsubscribeCallback(signalIter->second->getCoreSignal()); - } - } + if (command == "subscribe") + subscribeHandler(signalId, signalIter->second); + else if (command == "unsubscribe") + unsubscribeHandler(signalId, signalIter->second); } else { @@ -277,4 +261,22 @@ bool StreamingServer::isSignalSubscribed(const std::string& signalId) const return result; } +void StreamingServer::subscribeHandler(const std::string& signalId, OutputSignalPtr signal) +{ + // wasn't subscribed by any client + if (!isSignalSubscribed(signalId) && onSubscribeCallback) + onSubscribeCallback(signal->getCoreSignal()); + + signal->setSubscribed(true); +} + +void StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSignalPtr signal) +{ + signal->setSubscribed(false); + + // became not subscribed by any client + if (!isSignalSubscribed(signalId) && onUnsubscribeCallback) + onUnsubscribeCallback(signal->getCoreSignal()); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index deffaea..01dd15a 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -3,6 +3,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -67,6 +68,24 @@ void WebsocketStreamingImpl::prepareStreamingClient() this->onAvailableSignals(signalIds); }; streamingClient->onAvailableStreamingSignals(availableSignalsCallback); + + auto signalSubscriptionAckCallback = [this](const std::string& signalStringId, bool subscribed) + { + auto signalKey = String(signalStringId); + if (auto it = streamingSignalsRefs.find(signalKey); it != streamingSignalsRefs.end()) + { + auto signalRef = it->second; + MirroredSignalConfigPtr signal = signalRef.assigned() ? signalRef.getRef() : nullptr; + if (signal.assigned()) + { + if (subscribed) + signal.template asPtr()->subscribeCompleted(connectionString); + else + signal.template asPtr()->unsubscribeCompleted(connectionString); + } + } + }; + streamingClient->onSubscriptionAck(signalSubscriptionAckCallback); } void WebsocketStreamingImpl::handleEventPacket(const MirroredSignalConfigPtr& signal, const EventPacketPtr& eventPacket) diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index bed498a..ed0a1d9 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -101,6 +101,46 @@ TEST_F(StreamingTest, ParseConnectString) ASSERT_EQ(client->getTarget(), "/path/other"); } +TEST_F(StreamingTest, Subscription) +{ + auto server = std::make_shared(context); + server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testDoubleSignal); + return signals; + }); + server->start(StreamingPort, ControlPort); + + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); + + std::promise subscribeAckPromise; + std::future subscribeAckFuture = subscribeAckPromise.get_future(); + + std::promise unsubscribeAckPromise; + std::future unsubscribeAckFuture = unsubscribeAckPromise.get_future(); + + auto onSubscriptionAck = + [&subscribeAckPromise, &unsubscribeAckPromise](const std::string& signalId, bool subscribed) + { + if (subscribed) + subscribeAckPromise.set_value(signalId); + else + unsubscribeAckPromise.set_value(signalId); + }; + + client.onSubscriptionAck(onSubscriptionAck); + client.connect(); + ASSERT_TRUE(client.isConnected()); + + client.subscribeSignals({testDoubleSignal.getGlobalId()}); + ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + ASSERT_EQ(subscribeAckFuture.get(), testDoubleSignal.getGlobalId()); + + client.unsubscribeSignals({testDoubleSignal.getGlobalId()}); + ASSERT_EQ(unsubscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + ASSERT_EQ(unsubscribeAckFuture.get(), testDoubleSignal.getGlobalId()); +} + TEST_F(StreamingTest, SimpePacket) { std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; @@ -117,6 +157,16 @@ TEST_F(StreamingTest, SimpePacket) std::vector receivedPackets; auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); + std::promise subscribeAckPromise; + std::future subscribeAckFuture = subscribeAckPromise.get_future(); + + auto onSubscriptionAck = + [&subscribeAckPromise](const std::string& signalId, bool subscribed) + { + if (subscribed) + subscribeAckPromise.set_value(signalId); + }; + auto onPacket = [&receivedPackets](const StringPtr& signalId, const PacketPtr& packet) { receivedPackets.push_back(packet); @@ -126,11 +176,12 @@ TEST_F(StreamingTest, SimpePacket) client.onPacket(onPacket); client.onFindSignal(findSignal); + client.onSubscriptionAck(onSubscriptionAck); client.connect(); ASSERT_TRUE(client.isConnected()); client.subscribeSignals({testDoubleSignal.getGlobalId()}); - std::this_thread::sleep_for(std::chrono::milliseconds(250)); + ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); std::string signalId = testDoubleSignal.getGlobalId(); server->sendPacketToSubscribers(signalId, packet); From 66ca4ddd4c999a09c16179be12e2411456cf6b50 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Thu, 11 Jan 2024 08:16:56 +0100 Subject: [PATCH 019/127] [TBBAS-1170] IInstance update: support argument struct in constructor - adding Instance Builder - adding method InstanceFromBuilder - expand instance tests with checking instance builder - update changelog - add description of Instance configuring in doc --- shared/libraries/websocket_streaming/src/streaming_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index d21c6e6..4c88f1d 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -20,7 +20,7 @@ StreamingServer::StreamingServer(const ContextPtr& context) { if (!this->logger.assigned()) throw ArgumentNullException("Logger must not be null"); - loggerComponent = this->logger.addComponent("StreamingServer"); + loggerComponent = this->logger.getOrAddComponent("StreamingServer"); logCallback = [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); }; From f9943f58451166477253834f5f30acd99c9c1d93 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 29 Nov 2023 16:14:21 +0100 Subject: [PATCH 020/127] Add SearchFilter, Visible flag, rework Component attributes Search filters, visible flag, component attributes - Add SearchFilter that allows for more granular component search on tree traversal methods (getItems, getSignals...) - Add visible flag to Component - Default component getters were modified to return only components with visible==true - PropertyChanged event packets were removed - ComponentModified core event was changed to AttributeModified - Attribute lock was added to components preventing changes to locked attributes - Name and Description are no longer properties, but component attributes - Add per-component core event triggers --- .../websocket_streaming/output_signal.h | 3 ++- .../src/async_packet_reader.cpp | 3 ++- .../websocket_streaming/src/output_signal.cpp | 25 ++++++++++++++----- .../src/websocket_client_device_impl.cpp | 14 +++++++---- .../src/websocket_client_signal_impl.cpp | 2 +- .../src/websocket_streaming_server.cpp | 3 ++- .../tests/streaming_test_helpers.h | 2 ++ .../tests/test_websocket_client_device.cpp | 12 +++++---- 8 files changed, 44 insertions(+), 20 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index 7a1d63e..a451b04 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -50,10 +50,11 @@ class OutputSignal uint64_t getTickResolution(); void createSignalStream(); void createStreamedSignal(); + void subscribeToCoreEvent(); void writeEventPacket(const EventPacketPtr& packet); void writeDataPacket(const DataPacketPtr& packet); void writeDescriptorChangedPacket(const EventPacketPtr& packet); - void writePropertyChangedPacket(const EventPacketPtr& packet); + void processCoreEvent(ComponentPtr& component, CoreEventArgsPtr& args); SignalPtr signal; SignalConfigPtr streamedSignal; diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index 77edf50..c4dfb06 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -1,6 +1,7 @@ #include "websocket_streaming/async_packet_reader.h" #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -75,7 +76,7 @@ void AsyncPacketReader::startReadThread() void AsyncPacketReader::createReaders() { signalReaders.clear(); - auto signals = device.getSignalsRecursive(); + auto signals = device.getSignals(search::Recursive(search::Any())); for (const auto& signal : signals) { diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index acb5b1c..2969e50 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -28,6 +28,7 @@ OutputSignal::OutputSignal(const daq::streaming_protocol::StreamWriterPtr& write { createSignalStream(); createStreamedSignal(); + subscribeToCoreEvent(); } void OutputSignal::write(const PacketPtr& packet) @@ -168,14 +169,17 @@ void OutputSignal::createStreamedSignal() streamedSignal.setDescription(signal.getDescription()); } +void OutputSignal::subscribeToCoreEvent() +{ + signal.getOnComponentCoreEvent() += event(this, &OutputSignal::processCoreEvent); +} + void OutputSignal::writeEventPacket(const EventPacketPtr& packet) { const auto eventId = packet.getEventId(); if (eventId == event_packet_id::DATA_DESCRIPTOR_CHANGED) writeDescriptorChangedPacket(packet); - else if (eventId == event_packet_id::PROPERTY_CHANGED) - writePropertyChangedPacket(packet); else { STREAMING_PROTOCOL_LOG_E("Event type {} is not supported by streaming.", eventId); @@ -207,11 +211,14 @@ void OutputSignal::writeDescriptorChangedPacket(const EventPacketPtr& packet) stream->writeSignalMetaInformation(); } -void OutputSignal::writePropertyChangedPacket(const EventPacketPtr& packet) +void OutputSignal::processCoreEvent(ComponentPtr& /*component*/, CoreEventArgsPtr& args) { - const auto params = packet.getParameters(); - const auto name = params.get(event_packet_param::NAME); - const auto value = params.get(event_packet_param::VALUE); + if (args.getEventId() != core_event_ids::AttributeChanged) + return; + + const auto params = args.getParameters(); + const auto name = params.get("AttributeName"); + const auto value = params.get(name); SignalProps sigProps; if (name == "Name") @@ -224,6 +231,12 @@ void OutputSignal::writePropertyChangedPacket(const EventPacketPtr& packet) sigProps.description = value; streamedSignal.setDescription(value); } + else + { + return; + } + + // Streaming LT does not support attribute change forwarding for active, public, and visible SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); stream->writeSignalMetaInformation(); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index f411c0a..4889255 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -86,8 +86,9 @@ void WebsocketClientDeviceImpl::onSignalInit(const StringPtr& signalId, const Su if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) { // sets signal name as it appeared in metadata "name" - auto protectedObject = signalIt->second.asPtr(); - protectedObject.setProtectedPropertyValue("Name", sInfo.signalName); + signalIt->second.asPtr().unlockAllAttributes(); + signalIt->second.setName(sInfo.signalName); + signalIt->second.asPtr().lockAllAttributes(); signalIt->second.asPtr()->assignDescriptor(sInfo.dataDescriptor); updateSignalProperties(signalIt->second, sInfo); @@ -129,11 +130,14 @@ void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector(); + signal.asPtr().unlockAllAttributes(); + if (sInfo.signalProps.name.has_value()) - protectedObject.setProtectedPropertyValue("Name", sInfo.signalProps.name.value()); + signal.setName(sInfo.signalProps.name.value()); if (sInfo.signalProps.description.has_value()) - protectedObject.setProtectedPropertyValue("Description", sInfo.signalProps.description.value()); + signal.setDescription(sInfo.signalProps.description.value()); + + signal.asPtr().lockAllAttributes(); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index 8f59114..262970c 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -11,7 +11,7 @@ static constexpr char delimeter = '#'; WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& streamingId) - : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId), nullptr, ComponentStandardProps::AddReadOnly) + : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId), nullptr) , streamingId(streamingId) { } diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 6823960..9201751 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -6,6 +6,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -46,7 +47,7 @@ void WebsocketStreamingServer::start() if (streamingPort == 0 || controlPort == 0) return; - streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignalsRecursive(); }); + streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignals(search::Recursive(search::Any())); }); streamingServer.onSubscribe([this](const daq::SignalPtr& signal) { packetReader.startReadSignal(signal); } ); streamingServer.onUnsubscribe([this](const daq::SignalPtr& signal) { packetReader.stopReadSignal(signal); } ); streamingServer.start(streamingPort, controlPort); diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index fb1ffc9..8fe87b8 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -88,6 +88,8 @@ namespace streaming_test_helpers signal.setDomainSignal(timeSignal); signal.setName("TestName"); signal.setDescription("TestDescription"); + + signal.asPtr().enableCoreEventTrigger(); return signal; } diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index fc3fbaa..153d41d 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "streaming_test_helpers.h" using namespace daq; @@ -106,14 +107,15 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) std::this_thread::sleep_for(std::chrono::milliseconds(250)); - server->broadcastPacket(signalId, PropertyChangedEventPacket("Name", "NewName")); - server->broadcastPacket(signalId, PropertyChangedEventPacket("Description", "NewDescription")); + testSignal.asPtr().unlockAllAttributes(); + testSignal.setName("NewName"); + testSignal.setDescription("NewDescription"); std::this_thread::sleep_for(std::chrono::milliseconds(250)); ASSERT_EQ(clientDevice.getSignals()[0].getName(), "NewName"); ASSERT_EQ(clientDevice.getSignals()[0].getDescription(), "NewDescription"); - ASSERT_THROW(clientDevice.getSignals()[0].setName("ClientName"), AccessDeniedException); + ASSERT_NO_THROW(clientDevice.getSignals()[0].setName("ClientName")); // Check if the mirrored signal changed ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), @@ -161,10 +163,10 @@ TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) // There should not be any difference if we get signals recursively or not, // since client device doesn't know anything about hierarchy - const size_t expectedSignalCount = serverInstance.getSignalsRecursive().getCount(); + const size_t expectedSignalCount = serverInstance.getSignals(search::Recursive(search::Visible())).getCount(); ListPtr signals; ASSERT_NO_THROW(signals = clientDevice.getSignals()); ASSERT_EQ(signals.getCount(), expectedSignalCount); - ASSERT_NO_THROW(signals = clientDevice.getSignalsRecursive()); + ASSERT_NO_THROW(signals = clientDevice.getSignals(search::Recursive(search::Visible()))); ASSERT_EQ(signals.getCount(), expectedSignalCount); } From 157f0836e1ce82644f47c3fe5c34d25bc95bea02 Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Thu, 18 Jan 2024 11:40:59 +0100 Subject: [PATCH 021/127] Update the minimum required CMake version to avoid deprecation warnings --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- shared/libraries/websocket_streaming/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 60727f6..26fcbe5 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) project(WebsocketStreamingClientModule VERSION 2.0.0 LANGUAGES C CXX) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index 4c2113e..fe97d16 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) project(WebsocketStreamingServerModule VERSION 2.0.0 LANGUAGES CXX) diff --git a/shared/libraries/websocket_streaming/CMakeLists.txt b/shared/libraries/websocket_streaming/CMakeLists.txt index 0feb5a7..166c415 100644 --- a/shared/libraries/websocket_streaming/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME ${SDK_TARGET_NAMESPACE}_websocket_streaming) project(OpenDaqStreaming CXX) From 97b09c4b4b19595e17e91918b040aca75585b3ec Mon Sep 17 00:00:00 2001 From: Dejan Crnila Date: Mon, 22 Jan 2024 15:26:39 +0100 Subject: [PATCH 022/127] Add native configuration protocol library --- shared/libraries/websocket_streaming/tests/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index 63b015b..c36285d 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -9,6 +9,10 @@ add_executable(${TEST_APP} test_signal_generator.cpp ) +if (MSVC) + target_compile_options(${TEST_APP} PRIVATE /bigobj) +endif() + target_link_libraries(${TEST_APP} PRIVATE ${SDK_TARGET_NAMESPACE}::${MODULE_NAME} daq::opendaq From ed11b61dfc16ccfe913e4f620ab2c2b424863ba3 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Fri, 12 Jan 2024 12:34:04 +0100 Subject: [PATCH 023/127] Enable support of shared domain signals in websocket streaming client: * Allow multiple data signals to have same `tableId` * Add client handling of domain signals listed as available * Add `MockStreamingServer` implementation used in tests to bypass the creation of artificial time signals within the streaming-lt library * Expand the WebSocket pseudo device test suite with the relevant tests --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/input_signal.h | 4 + .../signal_descriptor_converter.h | 3 +- .../websocket_streaming/streaming_client.h | 2 +- .../websocket_streaming/src/input_signal.cpp | 11 + .../src/streaming_client.cpp | 124 +++--- .../websocket_streaming/tests/CMakeLists.txt | 2 + .../tests/mock_streaming_server.cpp | 362 ++++++++++++++++++ .../tests/mock_streaming_server.h | 124 ++++++ .../tests/streaming_test_helpers.h | 36 +- .../tests/test_websocket_client_device.cpp | 48 +++ 11 files changed, 671 insertions(+), 49 deletions(-) create mode 100644 shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp create mode 100644 shared/libraries/websocket_streaming/tests/mock_streaming_server.h diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 0195b87..e332a63 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.11 + REQUIRED_VERSION 0.10.12 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.11 + GIT_REF v0.10.12 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index c0372f9..71d9b7b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -42,6 +42,9 @@ class InputSignal void setTableId(std::string id); std::string getTableId() const; + void setIsDomainSignal(bool value); + bool getIsDomainSignal() const; + protected: DataDescriptorPtr currentDataDescriptor; DataDescriptorPtr currentDomainDataDescriptor; @@ -49,6 +52,7 @@ class InputSignal std::string name; std::string description; std::string tableId; + bool isDomainSignal; mutable std::mutex descriptorsSync; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index 50fed95..ab112b7 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -40,12 +40,13 @@ class SignalDescriptorConverter */ static void ToStreamedSignal(const daq::SignalPtr& signal, daq::streaming_protocol::BaseSignalPtr stream, const SignalProps& sigProps); + static void EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra); + private: static daq::DataRulePtr GetRule(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); static void SetTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::BaseSignalPtr signal); static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); - static void EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra); static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor); static nlohmann::json DictToJson(const DictPtr& dict); static DictPtr JsonToDict(const nlohmann::json& json); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index a95c414..02e26d2 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -89,7 +89,7 @@ class StreamingClient void setDomainDescriptor(const std::string& signalId, const InputSignalPtr& inputSignal, const DataDescriptorPtr& domainDescriptor); - std::pair findSignalByTableId(const std::string& tableId); + std::vector> findDataSignalsByTableId(const std::string& tableId); LoggerPtr logger; LoggerComponentPtr loggerComponent; diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 4e529d3..5c2347d 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -11,6 +11,7 @@ using namespace daq; using namespace daq::streaming_protocol; InputSignal::InputSignal() + : isDomainSignal(false) { } @@ -76,4 +77,14 @@ std::string InputSignal::getTableId() const return tableId; } +void InputSignal::setIsDomainSignal(bool value) +{ + isDomainSignal = value; +} + +bool InputSignal::getIsDomainSignal() const +{ + return isDomainSignal; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 5979a23..65679ff 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -248,16 +248,16 @@ void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, con { if (method == daq::streaming_protocol::META_METHOD_SUBSCRIBE) { - // skips the first subscribe ack from server - if (std::get<2>(it->second)) + // skips the first subscribe ACK from server and ignores ACKs for domain signals + if (std::get<2>(it->second) && !subscribedSignal.isTimeSignal()) onSubscriptionAckCallback(subscribedSignal.signalId(), true); else std::get<2>(it->second) = true; } else if (method == daq::streaming_protocol::META_METHOD_UNSUBSCRIBE) { - // skips the first unsubscribe ack from server - if (std::get<3>(it->second)) + // skips the first unsubscribe ACK from server and ignores ACKs for domain signals + if (std::get<3>(it->second) && !subscribedSignal.isTimeSignal()) onSubscriptionAckCallback(subscribedSignal.signalId(), false); else std::get<3>(it->second) = true; @@ -334,19 +334,32 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig if (auto signalIt = signals.find(id); signalIt != signals.end()) { + auto inputSignal = signalIt->second; + + DataDescriptorPtr dataDescriptor = inputSignal->getSignalDescriptor(); + DataDescriptorPtr domainDescriptor = inputSignal->getDomainSignalDescriptor(); + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); - if (!signalIt->second->getSignalDescriptor().assigned()) + if (!inputSignal->getSignalDescriptor().assigned()) onSignalInitCallback(id, sInfo); else onSignalUpdatedCallback(id, sInfo); - signalIt->second->setDataDescriptor(sInfo.dataDescriptor); + inputSignal->setDataDescriptor(sInfo.dataDescriptor); const auto tableId = subscribedSignal.tableId(); - signalIt->second->setTableId(tableId); - if (auto domainDescIt = cachedDomainDescriptors.find(tableId); domainDescIt != cachedDomainDescriptors.end()) + if (inputSignal->getTableId() != tableId) { - setDomainDescriptor(signalIt->first, signalIt->second, domainDescIt->second); - cachedDomainDescriptors.erase(domainDescIt); + inputSignal->setTableId(tableId); + if (auto domainDescIt = cachedDomainDescriptors.find(tableId); domainDescIt != cachedDomainDescriptors.end()) + { + setDomainDescriptor(id, inputSignal, domainDescIt->second); + } + } + + if (dataDescriptor != inputSignal->getSignalDescriptor() || + domainDescriptor != inputSignal->getDomainSignalDescriptor()) + { + publishSignalChanges(id, inputSignal); } } } @@ -356,17 +369,34 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig std::string tableId = subscribedSignal.tableId(); auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); - // check if the input value signal with tableId is known - if (auto [id, inputSignal] = findSignalByTableId(tableId); inputSignal != nullptr) + const auto timeSignalId = subscribedSignal.signalId(); + if (auto signalIt = signals.find(timeSignalId); signalIt != signals.end()) { - // value input signal with tableId is known, set domain descriptor for it - setDomainDescriptor(id, inputSignal, sInfo.dataDescriptor); + // the time signal is published as available by server, + // so do the initialization of its mirrored copy + auto inputSignal = signalIt->second; + if (!inputSignal->getSignalDescriptor().assigned()) + { + onSignalInitCallback(timeSignalId, sInfo); + setSignalInitSatisfied(timeSignalId); + } + else + { + onSignalUpdatedCallback(timeSignalId, sInfo); + } + inputSignal->setDataDescriptor(sInfo.dataDescriptor); + inputSignal->setIsDomainSignal(true); + inputSignal->setTableId(tableId); } - else + + // set domain descriptor for all input data signals with known tableId + for (const auto& [dataSignalId, inputSignal] : findDataSignalsByTableId(tableId)) { - // value signal with tableId is unknown, save domain descriptor - cachedDomainDescriptors.insert_or_assign(tableId, sInfo.dataDescriptor); + setDomainDescriptor(dataSignalId, inputSignal, sInfo.dataDescriptor); } + + // some data signals can have unknown tableId at the moment, save domain descriptor for future + cachedDomainDescriptors.insert_or_assign(tableId, sInfo.dataDescriptor); } void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal) @@ -382,18 +412,17 @@ void StreamingClient::publishSignalChanges(const std::string& signalId, const In onPacketCallback(signalId, eventPacket); } -std::pair StreamingClient::findSignalByTableId(const std::string& tableId) +std::vector> StreamingClient::findDataSignalsByTableId(const std::string& tableId) { - auto signalIt = std::find_if(signals.begin(), - signals.end(), - [&tableId](const std::pair& pair) - { - return tableId == pair.second->getTableId(); - }); - if (signalIt != signals.end()) - return {signalIt->first, signalIt->second}; - else - return {std::string(), nullptr}; + std::vector> result; + for (const auto& [id, inputSignal] : signals) + { + if (tableId == inputSignal->getTableId() && !inputSignal->getIsDomainSignal()) + { + result.push_back({id, inputSignal}); + } + } + return result; } void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params) @@ -410,28 +439,37 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& params.dump()); } - DataDescriptorPtr dataDescriptor, domainDescriptor; - std::string tableId = subscribedSignal.tableId(); - if (auto [_,signal] = findSignalByTableId(tableId); signal != nullptr) + if (subscribedSignal.isTimeSignal()) { - dataDescriptor = signal->getSignalDescriptor(); - domainDescriptor = signal->getDomainSignalDescriptor(); - } + std::unordered_map> descriptors; + std::string tableId = subscribedSignal.tableId(); + for (const auto& [id,signal] : findDataSignalsByTableId(tableId)) + { + std::pair pair(signal->getSignalDescriptor(), + signal->getDomainSignalDescriptor()); + descriptors.insert_or_assign(id, pair); + } - if (subscribedSignal.isTimeSignal()) setTimeSignal(subscribedSignal); - else - setDataSignal(subscribedSignal); - if (auto [id, signal] = findSignalByTableId(tableId); signal != nullptr) - { - if (dataDescriptor != signal->getSignalDescriptor() || - domainDescriptor != signal->getDomainSignalDescriptor()) + for (const auto& [id, signalDescriptors] : descriptors) { - // descriptors changed - generate event packet and send it to signal listeners - publishSignalChanges(id, signal); + if (auto it = signals.find(id); it != signals.end()) + { + auto inputSignal = it->second; + if (signalDescriptors.first != inputSignal->getSignalDescriptor() || + signalDescriptors.second != inputSignal->getDomainSignalDescriptor()) + { + // descriptors changed - generate event packet and send it to signal listeners + publishSignalChanges(id, inputSignal); + } + } } } + else + { + setDataSignal(subscribedSignal); + } } catch (const DaqException& e) { diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index c36285d..ebe647a 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -3,6 +3,8 @@ set(TEST_APP test_${MODULE_NAME}) add_executable(${TEST_APP} streaming_test_helpers.h + mock_streaming_server.h + mock_streaming_server.cpp test_signal_descriptor_converter.cpp test_streaming.cpp test_websocket_client_device.cpp diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp b/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp new file mode 100644 index 0000000..49a544e --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp @@ -0,0 +1,362 @@ +#include + +#include +#include +#include + +#include "mock_streaming_server.h" +#include +#include +#include + +#include +#include + +namespace streaming_test_helpers +{ + +using namespace daq; +using namespace daq::streaming_protocol; +using namespace daq::stream; + +MockStreamingServer::MockStreamingServer(const ContextPtr& context) + : work(ioContext.get_executor()) + , logger(context.getLogger()) +{ + loggerComponent = this->logger.getOrAddComponent("MockStreamingServer"); + logCallback = [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { + this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); + }; +} + +MockStreamingServer::~MockStreamingServer() +{ + stop(); + logger.removeComponent("MockStreamingServer"); +} + +void MockStreamingServer::start(uint16_t port, uint16_t controlPort) +{ + this->port = port; + + ioContext.restart(); + + auto acceptFunc = [this](StreamPtr stream) { this->onAcceptInternal(stream); }; + + this->server = std::make_unique(ioContext, acceptFunc, port); + this->server->start(); + + auto controlCommandCb = [this](const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage) + { + return onControlCommand(streamId, command, signalIds, errorMessage); + }; + this->controlServer = + std::make_unique(ioContext, + controlPort, + controlCommandCb, + logCallback); + this->controlServer->start(); + + this->serverThread = std::thread([this]() { this->ioContext.run(); }); +} + +void MockStreamingServer::stop() +{ + ioContext.stop(); + + if (!serverThread.joinable()) + return; + + this->server->stop(); + serverThread.join(); + this->server.reset(); +} + +void MockStreamingServer::onAccept(const OnAcceptCallback& callback) +{ + onAcceptCallback = callback; +} + +void MockStreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) +{ + auto writer = std::make_shared(stream); + writeProtocolInfo(writer); + writeInit(writer); + + auto signals = List(); + if (onAcceptCallback) + signals = onAcceptCallback(); + + auto outputSignals = std::unordered_map>(); + SizeT signalNumber = 0; + for (const auto& signal : signals) + { + bool isDomainSignal = !signal.getDomainSignal().assigned(); + auto descriptor = signal.getDescriptor(); + + if (!descriptor.assigned() || + !isDomainSignal && descriptor.getSampleType() != daq::SampleType::Float64 || + !isDomainSignal && descriptor.getRule().getType() != daq::DataRuleType::Explicit || + isDomainSignal && descriptor.getSampleType() != daq::SampleType::UInt64 || + isDomainSignal && descriptor.getRule().getType() != daq::DataRuleType::Linear) + { + LOG_E("Unsupported type of signal {}; data signal should be explicit float64, " + "domain signal linear unsigned64", signal.getGlobalId()); + throw GeneralErrorException("Unsupported type of signal"); + } + outputSignals.insert({signal.getGlobalId().toStdString(), {++signalNumber, signal}}); + } + + LOG_I("New client connected. Stream Id: {}", writer->id()); + clients.insert({writer, outputSignals}); + + writeSignalsAvailable(writer, signals); +} + +int MockStreamingServer::onControlCommand(const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage) +{ + if (signalIds.empty()) + { + LOG_W("Signal list is empty, reject command", streamId); + errorMessage = "Signal list is empty"; + return -1; + } + + auto clientIter = std::find_if(std::begin(clients), + std::end(clients), + [&streamId](const auto& pair) + { + return pair.first->id() == streamId; + }); + + if (clientIter == std::end(clients)) + { + LOG_W("Unknown streamId: {}, reject command", streamId); + errorMessage = "Unknown streamId: '" + streamId + "'"; + return -1; + } + + auto writer = clientIter->first; + auto signalMap = clientIter->second; + + if (command == "subscribe") + { + subscribeSignals(signalIds, signalMap, writer); + } + else if (command == "unsubscribe") + { + unsubscribeSignals(signalIds, signalMap, writer); + } + else + { + LOG_W("Unknown control command: {}", command); + errorMessage = "Unknown command: " + command; + return -1; + } + + size_t unknownSignalsCount = 0; + std::string message = "Command '" + command + "' failed for unknown signals:\n"; + for (const auto& signalId : signalIds) + { + if (auto signalMapIter = signalMap.find(signalId); signalMapIter == signalMap.end()) + { + unknownSignalsCount++; + message.append(signalId + "\n"); + } + } + if (unknownSignalsCount > 0) + { + LOG_W("{}", message); + errorMessage = message; + return -1; + } + + return 0; +} + +void MockStreamingServer::subscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, + const SignalMap& signalMap, + const daq::streaming_protocol::StreamWriterPtr& writer) +{ + for (const auto& signalId : signalIds) + { + if (auto signalMapIter = signalMap.find(signalId); signalMapIter != signalMap.end()) + { + auto signalNumber = signalMapIter->second.first; + auto signalPtr = signalMapIter->second.second; + + if (!signalPtr.hasProperty("tableId")) + throw GeneralErrorException("Unknown table id of signal"); + + std::string tableId = signalPtr.getPropertyValue("tableId").asPtr().toStdString(); + + if (!signalPtr.getDomainSignal().assigned()) + { + // considered as domain signal + writeSignalSubscribe(signalId, signalNumber, writer); + writeTimeSignalMeta(tableId, signalNumber, signalPtr, writer); + } + else + { + // considered as data signal + writeSignalSubscribe(signalId, signalNumber, writer); + writeDataSignalMeta(tableId, signalNumber, signalPtr, writer); + + auto domainSignal = signalPtr.getDomainSignal(); + std::string domainSignalId = domainSignal.getGlobalId().toStdString(); + if (auto domainSignalMapIter = signalMap.find(domainSignalId); + domainSignalMapIter == signalMap.end()) + { + throw GeneralErrorException("Domain signal should be registered as available"); + } + } + } + } +} + +void MockStreamingServer::unsubscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, + const SignalMap& signalMap, + const daq::streaming_protocol::StreamWriterPtr& writer) +{ + for (const auto& signalId : signalIds) + { + if (auto signalMapIter = signalMap.find(signalId); signalMapIter != signalMap.end()) + { + auto signalNumber = signalMapIter->second.first; + auto signalPtr = signalMapIter->second.second; + + writeSignalUnsubscribe(signalId, signalNumber, writer); + } + } +} + +void MockStreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json msg; + msg[METHOD] = META_METHOD_APIVERSION; + msg[PARAMS][VERSION] = OPENDAQ_LT_STREAM_VERSION; + writer->writeMetaInformation(0, msg); +} + +void MockStreamingServer::writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const ListPtr& signals) +{ + std::vector signalIds; + + for (const auto& signal : signals) + signalIds.push_back(signal.getGlobalId()); + + nlohmann::json msg; + msg[METHOD] = META_METHOD_AVAILABLE; + msg[PARAMS][META_SIGNALIDS] = signalIds; + writer->writeMetaInformation(0, msg); +} + +void MockStreamingServer::writeInit(const streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json initMeta; + initMeta[METHOD] = META_METHOD_INIT; + initMeta[PARAMS][META_STREAMID] = writer->id(); + + nlohmann::json jsonRpcHttp; + jsonRpcHttp["httpMethod"] = "POST"; + jsonRpcHttp["httpPath"] = "/"; + jsonRpcHttp["httpVersion"] = "1.1"; + jsonRpcHttp["port"] = std::to_string(controlServer->getPort()); + + nlohmann::json commandInterfaces; + commandInterfaces["jsonrpc-http"] = jsonRpcHttp; + + initMeta[PARAMS][COMMANDINTERFACES] = commandInterfaces; + writer->writeMetaInformation(0, initMeta); +} + +void MockStreamingServer::writeSignalSubscribe(const std::string& signalId, + SizeT signalNumber, + const streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json subscribeData; + subscribeData[METHOD] = META_METHOD_SUBSCRIBE; + subscribeData[PARAMS][META_SIGNALID] = signalId; + int result = writer->writeMetaInformation(signalNumber, subscribeData); + if (result < 0) + throw GeneralErrorException("Write Subscribe Meta Information failure"); +} + +void MockStreamingServer::writeDataSignalMeta(const std::string& tableId, + SizeT signalNumber, + const SignalPtr& signal, + const streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json dataSignal; + dataSignal[METHOD] = META_METHOD_SIGNAL; + dataSignal[PARAMS][META_TABLEID] = tableId; + + dataSignal[PARAMS][META_DEFINITION][META_NAME] = signal.getName(); + dataSignal[PARAMS][META_DEFINITION][META_DATATYPE] = DATA_TYPE_REAL64; + dataSignal[PARAMS][META_DEFINITION][META_RULE] = META_RULETYPE_EXPLICIT; + + nlohmann::json interpretationObject; + websocket_streaming::SignalDescriptorConverter::EncodeInterpretationObject( + signal.getDescriptor(), interpretationObject); + dataSignal[PARAMS][META_INTERPRETATION] = interpretationObject; + + int result = writer->writeMetaInformation(signalNumber, dataSignal); + if (result < 0) + throw GeneralErrorException("Write Data Signal Meta Information failure"); +} + +void MockStreamingServer::writeTimeSignalMeta(const std::string& tableId, + SizeT signalNumber, + const SignalPtr& signal, + const streaming_protocol::StreamWriterPtr& writer) +{ + auto descriptor = signal.getDescriptor(); + auto resolution = descriptor.getTickResolution(); + + nlohmann::json timeSignal; + timeSignal[METHOD] = META_METHOD_SIGNAL; + timeSignal[PARAMS][META_TABLEID] = tableId; + timeSignal[PARAMS][META_DEFINITION][META_NAME] = signal.getName(); + timeSignal[PARAMS][META_DEFINITION][META_RULE] = META_RULETYPE_LINEAR; + + timeSignal[PARAMS][META_DEFINITION][META_RULETYPE_LINEAR][META_DELTA] = + (uint64_t)descriptor.getRule().getParameters().get("delta"); + timeSignal[PARAMS][META_DEFINITION][META_DATATYPE] = DATA_TYPE_UINT64; + + timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_UNIT_ID] = daq::streaming_protocol::Unit::UNIT_ID_SECONDS; + timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_DISPLAY_NAME] = "s"; + timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_QUANTITY] = META_TIME; + timeSignal[PARAMS][META_DEFINITION][META_ABSOLUTE_REFERENCE] = UNIX_EPOCH; + timeSignal[PARAMS][META_DEFINITION][META_RESOLUTION][META_NUMERATOR] = 1; + timeSignal[PARAMS][META_DEFINITION][META_RESOLUTION][META_DENOMINATOR] = + resolution.getDenominator() / resolution.getNumerator(); + + nlohmann::json interpretationObject; + websocket_streaming::SignalDescriptorConverter::EncodeInterpretationObject( + descriptor, interpretationObject); + timeSignal[PARAMS][META_INTERPRETATION] = interpretationObject; + + int result = writer->writeMetaInformation(signalNumber, timeSignal); + if (result < 0) + throw GeneralErrorException("Write Time Signal Meta Information failure"); +} + +void MockStreamingServer::writeSignalUnsubscribe(const std::string& signalId, + SizeT signalNumber, + const streaming_protocol::StreamWriterPtr& writer) +{ + nlohmann::json unsubscribe; + unsubscribe[METHOD] = META_METHOD_UNSUBSCRIBE; + int result = writer->writeMetaInformation(signalNumber, unsubscribe); + if (result < 0) + throw GeneralErrorException("Write Unsubscribe Meta Information failure"); +} + +} diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h new file mode 100644 index 0000000..55ca865 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h @@ -0,0 +1,124 @@ +/* + * Copyright 2022-2023 Blueberry d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "websocket_streaming/websocket_streaming.h" +#include "stream/WebsocketServer.hpp" +#include "websocket_streaming/output_signal.h" +#include "streaming_protocol/StreamWriter.h" +#include "streaming_protocol/ControlServer.hpp" +#include "streaming_protocol/Logging.hpp" +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace streaming_test_helpers +{ + +/// This alternative implementation of the Streaming Server is designed for testing purposes. +/// It is intended to bypass the `daq::streaming_protocol::BaseSignal` library interface, +/// preventing the generation of artificial time signals and enabling the sharing of the same +/// domain signal among multiple data signals. +/// +/// Limitations and requirements: +/// - It does not implement the transfer of signal data; only the publication of +/// signal meta-information is supported. +/// - To simplify the implementation, it exclusively handles data signals +/// with explicit rule and a data type of float64, along with domain signals +/// having a linear rule and unsigned64 type. +/// - The domain signal must be published as available. +/// - The `tableId` for the signal is set using the StringProperty named "tableId," +/// and it is expected that all published signals have this property. + +class MockStreamingServer; +using MockStreamingServerPtr = std::shared_ptr; + +class MockStreamingServer +{ +public: + using OnAcceptCallback = std::function()>; + + MockStreamingServer(const daq::ContextPtr& context); + ~MockStreamingServer(); + + void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT, + uint16_t controlPort = daq::streaming_protocol::HTTP_CONTROL_PORT); + void stop(); + void onAccept(const OnAcceptCallback& callback); + +protected: + using SignalMap = std::unordered_map>; + using ClientMap = std::unordered_map; + + void onAcceptInternal(const daq::stream::StreamPtr& stream); + void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); + void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const daq::ListPtr& signals); + void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); + + void writeSignalSubscribe(const std::string& signalId, + daq::SizeT signalNumber, + const daq::streaming_protocol::StreamWriterPtr& writer); + void writeSignalUnsubscribe(const std::string& signalId, + daq::SizeT signalNumber, + const daq::streaming_protocol::StreamWriterPtr& writer); + + void writeDataSignalMeta(const std::string& tableId, + daq::SizeT signalNumber, + const daq::SignalPtr& signal, + const daq::streaming_protocol::StreamWriterPtr& writer); + void writeTimeSignalMeta(const std::string& tableId, + daq::SizeT signalNumber, + const daq::SignalPtr& signal, + const daq::streaming_protocol::StreamWriterPtr& writer); + + int onControlCommand(const std::string& streamId, + const std::string& command, + const daq::streaming_protocol::SignalIds& signalIds, + std::string& errorMessage); + void subscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, + const SignalMap& signalMap, + const daq::streaming_protocol::StreamWriterPtr& writer); + void unsubscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, + const SignalMap& signalMap, + const daq::streaming_protocol::StreamWriterPtr& writer); + + void writeTime(daq::SizeT signalNumber, + uint64_t timeTicks, + const daq::streaming_protocol::StreamWriterPtr& writer); + + uint16_t port; + boost::asio::io_context ioContext; + boost::asio::executor_work_guard work; + daq::stream::WebsocketServerUniquePtr server; + std::unique_ptr controlServer; + std::thread serverThread; + ClientMap clients; + OnAcceptCallback onAcceptCallback; + + daq::LoggerPtr logger; + daq::LoggerComponentPtr loggerComponent; + daq::streaming_protocol::LogCallback logCallback; +}; + +} diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 8fe87b8..006de7b 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -50,7 +50,8 @@ namespace streaming_test_helpers return instance; } - inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx) + inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx, + const daq::StringPtr& tableId = "TestTable") { const size_t nanosecondsInSecond = 1000000000; auto delta = nanosecondsInSecond / 1000; @@ -64,7 +65,9 @@ namespace streaming_test_helpers .setName("Time") .build(); - return SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + signal.addProperty(daq::StringProperty("tableId", tableId)); + return signal; } inline daq::SignalPtr createTestSignal(const daq::ContextPtr& ctx) @@ -112,4 +115,33 @@ namespace streaming_test_helpers auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); return signal; } + + inline daq::SignalPtr createTestSignalWithDomain(const daq::ContextPtr& ctx, + const daq::StringPtr& name, + const daq::SignalPtr& domainSignal, + const daq::StringPtr& tableId = "TestTable") + { + auto meta = daq::Dict(); + meta["color"] = "green"; + meta["used"] = "0"; + + auto descriptor = daq::DataDescriptorBuilder() + .setSampleType(daq::SampleType::Float64) + .setUnit(daq::Unit("V", 1, "voltage", "quantity")) + .setValueRange(daq::Range(0, 10)) + .setRule(daq::ExplicitDataRule()) + .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) + .setName(name) + .setMetadata(meta) + .build(); + + auto timeSignal = createTimeSignal(ctx); + auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + signal.setDomainSignal(domainSignal); + signal.setName(name); + signal.setDescription("TestDescription"); + signal.addProperty(daq::StringProperty("tableId", tableId)); + + return signal; + } } diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 153d41d..8c02ecd 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -6,6 +6,7 @@ #include #include #include "streaming_test_helpers.h" +#include "mock_streaming_server.h" using namespace daq; using namespace std::chrono_literals; @@ -170,3 +171,50 @@ TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) ASSERT_NO_THROW(signals = clientDevice.getSignals(search::Recursive(search::Visible()))); ASSERT_EQ(signals.getCount(), expectedSignalCount); } + +TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) +{ + // Create server signals + auto timeSignal = streaming_test_helpers::createTimeSignal(context); + auto dataSignal1 = streaming_test_helpers::createTestSignalWithDomain(context, "Data1", timeSignal); + auto dataSignal2 = streaming_test_helpers::createTestSignalWithDomain(context, "Data2", timeSignal); + + // Setup and start mock test server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&]() { + auto signals = List(); + signals.pushBack(dataSignal1); + signals.pushBack(timeSignal); + signals.pushBack(dataSignal2); + return signals; + }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 3u); + + // Check the mirrored signals + ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); + ASSERT_TRUE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), + dataSignal1.getDescriptor())); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), + dataSignal1.getDomainSignal().getDescriptor())); + ASSERT_EQ(clientDevice.getSignals()[0].getName(), "Data1"); + + ASSERT_TRUE(clientDevice.getSignals()[1].getDescriptor().assigned()); + ASSERT_TRUE(!clientDevice.getSignals()[1].getDomainSignal().assigned()); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[1].getDescriptor(), + timeSignal.getDescriptor())); + ASSERT_EQ(clientDevice.getSignals()[1].getName(), "Time"); + + ASSERT_TRUE(clientDevice.getSignals()[2].getDescriptor().assigned()); + ASSERT_TRUE(clientDevice.getSignals()[2].getDomainSignal().assigned()); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[2].getDescriptor(), + dataSignal2.getDescriptor())); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[2].getDomainSignal().getDescriptor(), + dataSignal2.getDomainSignal().getDescriptor())); + ASSERT_EQ(clientDevice.getSignals()[2].getName(), "Data2"); +} From 4ee7ff1064226fbb831c2c1fcf6f3ffa423d938d Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 25 Jan 2024 11:59:02 +0100 Subject: [PATCH 024/127] Optimize streaming servers' lookup of connected clients --- .../websocket_streaming/streaming_server.h | 4 +- .../src/streaming_server.cpp | 41 +++++++++++-------- .../tests/mock_streaming_server.cpp | 14 ++----- .../tests/mock_streaming_server.h | 2 +- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 08b0096..669930d 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -54,13 +54,13 @@ class StreamingServer void onAccept(const OnAcceptCallback& callback); void onSubscribe(const OnSubscribeCallback& callback); void onUnsubscribe(const OnUnsubscribeCallback& callback); - void unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, const std::string& signalId, const PacketPtr& packet); + void unicastPacket(const std::string& streamId, const std::string& signalId, const PacketPtr& packet); void broadcastPacket(const std::string& signalId, const PacketPtr &packet); void sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet); protected: using SignalMap = std::unordered_map; - using ClientMap = std::unordered_map; + using ClientMap = std::unordered_map>; void onAcceptInternal(const daq::stream::StreamPtr& stream); void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 4c88f1d..a34ad93 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -87,30 +87,41 @@ void StreamingServer::onUnsubscribe(const OnUnsubscribeCallback& callback) onUnsubscribeCallback = callback; } -void StreamingServer::unicastPacket(const daq::streaming_protocol::StreamWriterPtr& client, +void StreamingServer::unicastPacket(const std::string& streamId, const std::string& signalId, const PacketPtr& packet) { - auto& signals = clients[client]; - if (signals.count(signalId) > 0) - signals[signalId]->write(packet); + if (auto clientIt = clients.find(streamId); clientIt != clients.end()) + { + auto signals = clientIt->second.second; + if (auto signalIt = signals.find(streamId); signalIt != signals.end()) + signalIt->second->write(packet); + } } void StreamingServer::broadcastPacket(const std::string& signalId, const PacketPtr& packet) { for (auto& [_, client] : clients) - if (client.count(signalId) > 0) - client[signalId]->write(packet); + { + auto signals = client.second; + if (auto signalIter = signals.find(signalId); signalIter != signals.end()) + { + signalIter->second->write(packet); + } + } } void StreamingServer::sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet) { for (auto& [_, client] : clients) - if (auto signalIter = client.find(signalId); signalIter != client.end()) + { + auto signals = client.second; + if (auto signalIter = signals.find(signalId); signalIter != signals.end()) { if (signalIter->second->isSubscribed()) signalIter->second->write(packet); } + } } void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) @@ -142,7 +153,7 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) } LOG_I("New client connected. Stream Id: {}", writer->id()); - clients.insert({writer, outputSignals}); + clients.insert({writer->id(), {writer, outputSignals}}); writeSignalsAvailable(writer, signals); } @@ -159,13 +170,7 @@ int StreamingServer::onControlCommand(const std::string& streamId, return -1; } - auto clientIter = std::find_if(std::begin(clients), - std::end(clients), - [&streamId](const auto& pair) - { - return pair.first->id() == streamId; - }); - + auto clientIter = clients.find(streamId); if (clientIter == std::end(clients)) { LOG_W("Unknown streamId: {}, reject command", streamId); @@ -179,7 +184,8 @@ int StreamingServer::onControlCommand(const std::string& streamId, std::string message = "Command '" + command + "' failed for unknown signals:\n"; for (const auto& signalId : signalIds) { - if (auto signalIter = clientIter->second.find(signalId); signalIter != clientIter->second.end()) + auto signals = clientIter->second.second; + if (auto signalIter = signals.find(signalId); signalIter != signals.end()) { if (command == "subscribe") subscribeHandler(signalId, signalIter->second); @@ -253,8 +259,9 @@ void StreamingServer::writeInit(const streaming_protocol::StreamWriterPtr& write bool StreamingServer::isSignalSubscribed(const std::string& signalId) const { bool result = false; - for (const auto& [_, signals] : clients) + for (const auto& [_, client] : clients) { + auto signals = client.second; if (auto iter = signals.find(signalId); iter != signals.end()) result = result || iter->second->isSubscribed(); } diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp b/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp index 49a544e..d362581 100644 --- a/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp @@ -111,7 +111,7 @@ void MockStreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) } LOG_I("New client connected. Stream Id: {}", writer->id()); - clients.insert({writer, outputSignals}); + clients.insert({writer->id(), {writer, outputSignals}}); writeSignalsAvailable(writer, signals); } @@ -128,13 +128,7 @@ int MockStreamingServer::onControlCommand(const std::string& streamId, return -1; } - auto clientIter = std::find_if(std::begin(clients), - std::end(clients), - [&streamId](const auto& pair) - { - return pair.first->id() == streamId; - }); - + auto clientIter = clients.find(streamId); if (clientIter == std::end(clients)) { LOG_W("Unknown streamId: {}, reject command", streamId); @@ -142,8 +136,8 @@ int MockStreamingServer::onControlCommand(const std::string& streamId, return -1; } - auto writer = clientIter->first; - auto signalMap = clientIter->second; + auto writer = clientIter->second.first; + auto signalMap = clientIter->second.second; if (command == "subscribe") { diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h index 55ca865..1d8b133 100644 --- a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h +++ b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h @@ -68,7 +68,7 @@ class MockStreamingServer protected: using SignalMap = std::unordered_map>; - using ClientMap = std::unordered_map; + using ClientMap = std::unordered_map>; void onAcceptInternal(const daq::stream::StreamPtr& stream); void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); From 53db7fca042f0d0c4697dfecff655e3d8ef032fc Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Tue, 30 Jan 2024 15:37:14 +0100 Subject: [PATCH 025/127] Fix openDAQ component (static) libraries linking --- shared/libraries/websocket_streaming/src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index 5baff6d..d3670c2 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -83,6 +83,10 @@ if (WIN32) ) endif() +if (MSVC) + target_compile_options(${LIB_NAME} PRIVATE /bigobj) +endif() + set_target_properties(${LIB_NAME} PROPERTIES PUBLIC_HEADER "${SRC_PublicHeaders}") opendaq_set_output_lib_name(${LIB_NAME} ${LIB_MAJOR_VERSION}) From e29552f2055d3bc3e31159948b6d37217ae5de43 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 24 Jan 2024 13:49:21 +0100 Subject: [PATCH 026/127] Add MSVC /bigobj build flag for websocket streaming module --- modules/websocket_streaming_client_module/src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/websocket_streaming_client_module/src/CMakeLists.txt b/modules/websocket_streaming_client_module/src/CMakeLists.txt index b5260b5..f6f495c 100644 --- a/modules/websocket_streaming_client_module/src/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/src/CMakeLists.txt @@ -29,6 +29,10 @@ add_library(${LIB_NAME} SHARED ${SRC_Include} ) add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) +if (MSVC) + target_compile_options(${LIB_NAME} PRIVATE /bigobj) +endif() + target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq PRIVATE daq::discovery daq::websocket_streaming From d586f83696adab2a2db5cd64b213a336923f9914 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Thu, 25 Jan 2024 14:44:39 +0100 Subject: [PATCH 027/127] Support core events in Native Config Client --- shared/libraries/websocket_streaming/src/output_signal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 2969e50..2a3f765 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -213,7 +213,7 @@ void OutputSignal::writeDescriptorChangedPacket(const EventPacketPtr& packet) void OutputSignal::processCoreEvent(ComponentPtr& /*component*/, CoreEventArgsPtr& args) { - if (args.getEventId() != core_event_ids::AttributeChanged) + if (args.getEventId() != static_cast(CoreEventId::AttributeChanged)) return; const auto params = args.getParameters(); From 58fc9d65824b819f8f3b59a3b4e72031f439f49f Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 24 Jan 2024 16:01:28 +0100 Subject: [PATCH 028/127] Add module implementations for device based on the native config protocol. * Additions: - encoding/decoding of native config packets within the native transport protocol. - support for a new device type in the native client module. - native config protocol support in the native server module. - a minimal set of integration tests. * Fixes: - serialization of signal descriptors. - fix mutex lock issues in mirrored signals implementation upon subscribe/unsubscribe. - assign domain signals for deserialized signals. --- .../websocket_streaming/websocket_client_signal_impl.h | 9 +++++---- .../src/websocket_client_device_impl.cpp | 2 +- .../src/websocket_client_signal_impl.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index b85ac92..017629e 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -23,8 +23,8 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) { - virtual void assignDomainSignal(const DataDescriptorPtr& domainDescriptor) = 0; - virtual void assignDescriptor(const DataDescriptorPtr& descriptor) = 0; + virtual void INTERFACE_FUNC createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) = 0; + virtual void INTERFACE_FUNC assignDescriptor(const DataDescriptorPtr& descriptor) = 0; }; class WebsocketClientSignalImpl final : public MirroredSignalBase @@ -41,8 +41,9 @@ class WebsocketClientSignalImpl final : public MirroredSignalBasesecond.asPtr()->assignDomainSignal(domainDescriptor); + signalIt->second.asPtr()->createAndAssignDomainSignal(domainDescriptor); } void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector& signalIds) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index 262970c..9f7391a 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -75,7 +75,7 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) return True; } -void WebsocketClientSignalImpl::assignDomainSignal(const DataDescriptorPtr& domainDescriptor) +void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) { std::scoped_lock lock(signalMutex); From 7006154ce6570f9f013de723917015cd30859986 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 6 Feb 2024 10:03:52 +0100 Subject: [PATCH 029/127] Transfer available signal info as serialized signal in native streaming --- .../websocket_streaming/src/websocket_client_device_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index c6c1a24..58c3656 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -34,7 +34,7 @@ DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() void WebsocketClientDeviceImpl::activateStreaming() { auto self = this->borrowPtr(); - const auto signals = self.getSignals(); + const auto signals = self.getSignals(search::Any()); websocketStreaming.addSignals(signals); websocketStreaming.setActive(true); From 906d0b6492e9aa9f9bb15d57c98dc083b59784bd Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 6 Feb 2024 10:03:57 +0100 Subject: [PATCH 030/127] Enable multiple device types per single mDNS service --- ...websocket_streaming_client_module_impl.cpp | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index bc18743..c177e26 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -23,14 +23,18 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex daq::VersionInfo(WS_STREAM_CL_MODULE_MAJOR_VERSION, WS_STREAM_CL_MODULE_MINOR_VERSION, WS_STREAM_CL_MODULE_PATCH_VERSION), std::move(context)) , deviceIndex(0) - , discoveryClient([](MdnsDiscoveredDevice discoveredDevice) - { - return fmt::format("daq.ws://{}:{}{}", - discoveredDevice.ipv4Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/")); - }, - {"WS"}) + , discoveryClient( + { + [](MdnsDiscoveredDevice discoveredDevice) + { + return fmt::format("daq.ws://{}:{}{}", + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/")); + } + }, + {"WS"} + ) { discoveryClient.initMdnsClient("_streaming-ws._tcp.local."); } From cece728cc1ab3830ba1102a774d9ed1373226f86 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:32:59 +0100 Subject: [PATCH 031/127] implement logic for signal.setPublic(...) --- .../libraries/websocket_streaming/src/streaming_server.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index a34ad93..0148ca7 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -136,7 +136,10 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) auto outputSignals = std::unordered_map(); for (const auto& signal : signals) - { + { + if (!signal.getPublic()) + continue; + // TODO: We skip domain signals for now. if (!signal.getDomainSignal().assigned()) continue; From 0616a7897c5174c6892171593610052636b93bd0 Mon Sep 17 00:00:00 2001 From: Dejan Crnila Date: Fri, 23 Feb 2024 10:30:23 +0100 Subject: [PATCH 032/127] Add struct sample type --- shared/libraries/websocket_streaming/src/output_signal.cpp | 5 +++-- .../websocket_streaming/src/signal_descriptor_converter.cpp | 1 + .../libraries/websocket_streaming/src/streaming_client.cpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 2a3f765..06769b6 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -59,7 +59,7 @@ DataDescriptorPtr OutputSignal::getValueDescriptor() if (!dataDescriptor.assigned()) throw InvalidParameterException("Signal descriptor not set."); - if (dataDescriptor.isStructDescriptor()) + if (dataDescriptor.getSampleType() == daq::SampleType::Struct) throw InvalidParameterException("Signal cannot be a struct."); return dataDescriptor; @@ -75,7 +75,7 @@ DataDescriptorPtr OutputSignal::getDomainDescriptor() if (!domainDataDescriptor.assigned()) throw InvalidParameterException("Domain signal descriptor not set."); - if (domainDataDescriptor.isStructDescriptor()) + if (domainDataDescriptor.getSampleType() == daq::SampleType::Struct) throw InvalidParameterException("Signal cannot be a struct."); return domainDataDescriptor; @@ -148,6 +148,7 @@ void OutputSignal::createSignalStream() case daq::SampleType::Invalid: case daq::SampleType::String: case daq::SampleType::RangeInt64: + case daq::SampleType::Struct: default: throw InvalidTypeException(); } diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 2340784..77e2545 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -297,6 +297,7 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp return daq::streaming_protocol::SampleType::SAMPLETYPE_U64; break; case daq::SampleType::Binary: + case daq::SampleType::Struct: case daq::SampleType::Invalid: case daq::SampleType::String: case daq::SampleType::RangeInt64: diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 65679ff..4823bdc 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -321,7 +321,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& const auto& signalIter = signals.find(id); if (signalIter != signals.end() && signalIter->second->hasDescriptors() && - !signalIter->second->getSignalDescriptor().isStructDescriptor()) + signalIter->second->getSignalDescriptor().getSampleType() != daq::SampleType::Struct) { auto packet = signalIter->second->createDataPacket(timeStamp, data, size); onPacketCallback(id, packet); From 897b52be8f5d4da113bf22718753b8ae5c63dc2f Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 14 Feb 2024 12:34:52 +0100 Subject: [PATCH 033/127] Refactoring of the streaming framework: * pass the signal IDs instead of signal objects themselves to Streaming private interface functions invoked from internal signal implementation - subscribe / unsubscribe / createDataDescriptorChangedEventPacket * use the inherited component mutex within the base implementation of MirroredSignal. * replace the overriden interface getDomainSignal function with a virtual onGetDomainSignal method for MirroredSignal implementations --- .../websocket_client_signal_impl.h | 6 ++-- .../websocket_streaming_impl.h | 10 +++--- .../src/websocket_client_signal_impl.cpp | 28 ++++++++-------- .../src/websocket_streaming_impl.cpp | 32 ++++++++++++------- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 017629e..7fbe395 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -36,7 +36,6 @@ class WebsocketClientSignalImpl final : public MirroredSignalBase& signalIds); - StringPtr getSignalStreamingId(const MirroredSignalConfigPtr& signal); daq::websocket_streaming::StreamingClientPtr streamingClient; std::vector availableSignalIds; diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index 9f7391a..abf3332 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -41,16 +41,6 @@ ErrCode WebsocketClientSignalImpl::getDescriptor(IDataDescriptor** descriptor) return OPENDAQ_SUCCESS; } -ErrCode WebsocketClientSignalImpl::getDomainSignal(ISignal** signal) -{ - OPENDAQ_PARAM_NOT_NULL(signal); - - std::scoped_lock lock(signalMutex); - - *signal = domainSignalArtificial.addRefAndReturn(); - return OPENDAQ_SUCCESS; -} - Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) { if (eventPacket.assigned() && eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) @@ -67,7 +57,7 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) if (domainSignalArtificial.assigned() && newDomainDescriptor.assigned()) { - domainSignalArtificial.setDescriptor(newDomainDescriptor); + domainSignalArtificial.asPtr()->assignDescriptor(newDomainDescriptor); } } @@ -79,10 +69,11 @@ void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptor { std::scoped_lock lock(signalMutex); - domainSignalArtificial = SignalWithDescriptor(this->context, - domainDescriptor, - this->parent.getRef(), - CreateLocalId(streamingId+"_time_artificial")); + domainSignalArtificial = createWithImplementation( + this->context, + this->parent.getRef(), + CreateLocalId(streamingId+"_time_artificial")); + domainSignalArtificial.asPtr()->assignDescriptor(domainDescriptor); } void WebsocketClientSignalImpl::assignDescriptor(const DataDescriptorPtr& descriptor) @@ -92,4 +83,11 @@ void WebsocketClientSignalImpl::assignDescriptor(const DataDescriptorPtr& descri mirroredDataDescriptor = descriptor; } +SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() +{ + std::scoped_lock lock(signalMutex); + + return domainSignalArtificial; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 01dd15a..8d5705b 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -4,6 +4,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -30,28 +31,35 @@ void WebsocketStreamingImpl::onSetActive(bool active) { } -StringPtr WebsocketStreamingImpl::onAddSignal(const MirroredSignalConfigPtr& signal) +void WebsocketStreamingImpl::onAddSignal(const MirroredSignalConfigPtr& signal) { - return getSignalStreamingId(signal); } void WebsocketStreamingImpl::onRemoveSignal(const MirroredSignalConfigPtr& /*signal*/) { } -void WebsocketStreamingImpl::onSubscribeSignal(const MirroredSignalConfigPtr& signal) +void WebsocketStreamingImpl::onSubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& /*domainSignalRemoteId*/) { - streamingClient->subscribeSignals({getSignalStreamingId(signal).toStdString()}); + auto signalStreamingId = onGetSignalStreamingId(signalRemoteId); + if (auto it = streamingSignalsRefs.find(signalStreamingId); it == streamingSignalsRefs.end()) + throw NotFoundException("Signal with id {} is not added to Websocket streaming", signalRemoteId); + + streamingClient->subscribeSignals({signalStreamingId.toStdString()}); } -void WebsocketStreamingImpl::onUnsubscribeSignal(const MirroredSignalConfigPtr& signal) +void WebsocketStreamingImpl::onUnsubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& /*domainSignalRemoteId*/) { - streamingClient->unsubscribeSignals({getSignalStreamingId(signal).toStdString()}); + auto signalStreamingId = onGetSignalStreamingId(signalRemoteId); + if (auto it = streamingSignalsRefs.find(signalStreamingId); it == streamingSignalsRefs.end()) + throw NotFoundException("Signal with id {} is not added to Websocket streaming", signalRemoteId); + + streamingClient->unsubscribeSignals({signalStreamingId.toStdString()}); } -EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const MirroredSignalConfigPtr& signal) +EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const StringPtr& signalRemoteId) { - StringPtr signalStreamingId = getSignalStreamingId(signal); + StringPtr signalStreamingId = onGetSignalStreamingId(signalRemoteId); return streamingClient->getDataDescriptorChangedEventPacket(signalStreamingId); } @@ -123,21 +131,21 @@ void WebsocketStreamingImpl::onAvailableSignals(const std::vector& availableSignalIds.push_back(String(signalId)); } -StringPtr WebsocketStreamingImpl::getSignalStreamingId(const MirroredSignalConfigPtr& signal) +StringPtr WebsocketStreamingImpl::onGetSignalStreamingId(const StringPtr& signalRemoteId) { const auto it = std::find_if( availableSignalIds.begin(), availableSignalIds.end(), - [&signal](const StringPtr& signalStreamingId) + [&signalRemoteId](const StringPtr& signalStreamingId) { - return signal.template asPtr()->hasMatchingId(signalStreamingId); + return IdsParser::idEndsWith(signalRemoteId.toStdString(), signalStreamingId.toStdString()); } ); if (it != availableSignalIds.end()) return *it; else - throw NotFoundException("Signal with id {} is not available in Websocket streaming", signal.getRemoteId()); + throw NotFoundException("Signal with id {} is not available in Websocket streaming", signalRemoteId); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From db54f85dcbd2f5321cc60d6741085aaee2406a5c Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 15 Feb 2024 11:22:10 +0100 Subject: [PATCH 034/127] Handle signals removal in native modules --- .../websocket_streaming/src/websocket_streaming_impl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 8d5705b..cd555bb 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -4,7 +4,6 @@ #include #include #include -#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 72b4a1178c725381e756afb12ae9f81ebfbee3cf Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Tue, 27 Feb 2024 08:22:08 +0100 Subject: [PATCH 035/127] Change StreamingLT git ref --- external/streaming_protocol/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index e332a63..fd6934f 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -8,6 +8,6 @@ opendaq_dependency( NAME streaming_protocol REQUIRED_VERSION 0.10.12 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.12 + GIT_REF v0.10.12-1 EXPECT_TARGET daq::streaming_protocol ) From b8967fac981c3688eb6c90b4911d0ce64016a007 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:38:21 +0100 Subject: [PATCH 036/127] [TBBAS-1306] Example of using the config provider options in a reference module -add id in module -add enableStandardProviders in context (by default disabled) -add configuring native streaming client module from options -add configuring NumberOfChannels and EnableCANChannel for ref device from options --- .../src/websocket_streaming_client_module_impl.cpp | 3 ++- .../src/websocket_streaming_server_module_impl.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index c177e26..17d9516 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -21,7 +21,8 @@ using namespace daq::websocket_streaming; WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr context) : Module("openDAQ websocket client module", daq::VersionInfo(WS_STREAM_CL_MODULE_MAJOR_VERSION, WS_STREAM_CL_MODULE_MINOR_VERSION, WS_STREAM_CL_MODULE_PATCH_VERSION), - std::move(context)) + std::move(context), + "WebsocketStreamingClient") , deviceIndex(0) , discoveryClient( { diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index 21696f7..ef761a0 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -10,7 +10,8 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE WebsocketStreamingServerModule::WebsocketStreamingServerModule(ContextPtr context) : Module("openDAQ Websocket streaming server module", daq::VersionInfo(WS_STREAM_SRV_MODULE_MAJOR_VERSION, WS_STREAM_SRV_MODULE_MINOR_VERSION, WS_STREAM_SRV_MODULE_PATCH_VERSION), - std::move(context)) + std::move(context), + "WebsockerStreamingServer") { } From d283ecaebec7c52a3cf6c76f7c423a9df53c6bcf Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:26:37 +0100 Subject: [PATCH 037/127] hiding not public signals in streaming lt --- .../libraries/websocket_streaming/src/async_packet_reader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index c4dfb06..7d6520b 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -98,6 +98,9 @@ void AsyncPacketReader::stopReadSignal(const SignalPtr& signal) void AsyncPacketReader::addReader(SignalPtr signalToRead) { + if (!signalToRead.getPublic()) + return; + auto it = std::find_if(signalReaders.begin(), signalReaders.end(), [&signalToRead](const std::pair& element) From 3079fbdb6e05c862c67dcf77be719134b29c3929 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 7 Mar 2024 19:38:57 +0100 Subject: [PATCH 038/127] Streaming-lt: write start domain value only upon signal subscribing * use streaming-lt lib version 0.10.15 --- external/streaming_protocol/CMakeLists.txt | 4 ++-- .../include/websocket_streaming/output_signal.h | 1 + .../websocket_streaming/src/output_signal.cpp | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index fd6934f..62ddd0e 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.12 + REQUIRED_VERSION 0.10.15 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.12-1 + GIT_REF v0.10.15 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index a451b04..bc1913a 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -62,6 +62,7 @@ class OutputSignal SignalStreamPtr stream; size_t sampleSize; bool subscribed; + bool writeStartDomainValue{false}; std::mutex subscribedSync; daq::streaming_protocol::LogCallback logCallback; }; diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 06769b6..d563564 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -190,8 +190,13 @@ void OutputSignal::writeEventPacket(const EventPacketPtr& packet) void OutputSignal::writeDataPacket(const DataPacketPtr& packet) { const auto domainPacket = packet.getDomainPacket(); - if (domainPacket.assigned()) - stream->setTimeStart(domainPacket.getOffset()); + if (writeStartDomainValue) + { + if (domainPacket.assigned()) + stream->setTimeStart(domainPacket.getOffset()); + + writeStartDomainValue = false; + } stream->addData(packet.getRawData(), packet.getSampleCount()); } @@ -263,9 +268,14 @@ void OutputSignal::setSubscribed(bool subscribed) this->subscribed = subscribed; if (subscribed) + { stream->subscribe(); + writeStartDomainValue = true; + } else + { stream->unsubscribe(); + } } } From e59d848357c8e458c5322668e34ac5727d35a13c Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:15:09 +0100 Subject: [PATCH 039/127] [TBBAS-1232]: use filtered signals as available in Streaming Server --- .../libraries/websocket_streaming/src/streaming_server.cpp | 7 +++++-- .../tests/test_websocket_client_device.cpp | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 0148ca7..7ea2a13 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -131,6 +131,7 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) writeInit(writer); auto signals = List(); + auto filteredSignals = List(); if (onAcceptCallback) signals = onAcceptCallback(writer); @@ -140,10 +141,12 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) if (!signal.getPublic()) continue; + filteredSignals.pushBack(signal); + // TODO: We skip domain signals for now. if (!signal.getDomainSignal().assigned()) continue; - + try { const auto outputSignal = std::make_shared(writer, signal, logCallback); @@ -158,7 +161,7 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) LOG_I("New client connected. Stream Id: {}", writer->id()); clients.insert({writer->id(), {writer, outputSignals}}); - writeSignalsAvailable(writer, signals); + writeSignalsAvailable(writer, filteredSignals); } int StreamingServer::onControlCommand(const std::string& streamId, diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 8c02ecd..cae2c7e 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -164,7 +164,10 @@ TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) // There should not be any difference if we get signals recursively or not, // since client device doesn't know anything about hierarchy - const size_t expectedSignalCount = serverInstance.getSignals(search::Recursive(search::Visible())).getCount(); + size_t expectedSignalCount = 0; + for (const auto& signal : serverInstance.getSignals(search::Recursive(search::Visible()))) + expectedSignalCount += signal.getPublic(); + ListPtr signals; ASSERT_NO_THROW(signals = clientDevice.getSignals()); ASSERT_EQ(signals.getCount(), expectedSignalCount); From ce17983eb78218cce489abff51844c453b26f395 Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Thu, 7 Mar 2024 09:32:25 +0100 Subject: [PATCH 040/127] Update FMT to v10.2.1 and SpdLog to v1.13.0 --- shared/libraries/websocket_streaming/src/streaming_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 4823bdc..6210ba5 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -435,7 +435,7 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal.signalId(), subscribedSignal.tableId(), subscribedSignal.memberName(), - subscribedSignal.dataValueType(), + static_cast(subscribedSignal.dataValueType()), params.dump()); } From 71ea366d642e12ee75afd455e990c0eab70773c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE?= Date: Tue, 26 Mar 2024 14:28:55 +0100 Subject: [PATCH 041/127] Update license year from 2023 to 2024 (openDAQ/openDAQ#235) --- .../include/websocket_streaming_client_module/common.h | 2 +- .../include/websocket_streaming_client_module/module_dll.h | 2 +- .../websocket_streaming_client_module_impl.h | 2 +- .../include/websocket_streaming_server_module/common.h | 2 +- .../include/websocket_streaming_server_module/module_dll.h | 2 +- .../websocket_streaming_server_impl.h | 2 +- .../websocket_streaming_server_module_impl.h | 2 +- .../include/websocket_streaming/async_packet_reader.h | 2 +- .../include/websocket_streaming/input_signal.h | 2 +- .../include/websocket_streaming/output_signal.h | 2 +- .../include/websocket_streaming/signal_descriptor_converter.h | 2 +- .../include/websocket_streaming/signal_info.h | 2 +- .../include/websocket_streaming/streaming_client.h | 2 +- .../include/websocket_streaming/streaming_server.h | 2 +- .../websocket_streaming/websocket_client_device_factory.h | 2 +- .../include/websocket_streaming/websocket_client_device_impl.h | 2 +- .../websocket_streaming/websocket_client_signal_factory.h | 2 +- .../include/websocket_streaming/websocket_client_signal_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming.h | 2 +- .../include/websocket_streaming/websocket_streaming_factory.h | 2 +- .../include/websocket_streaming/websocket_streaming_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming_init.h | 2 +- .../include/websocket_streaming/websocket_streaming_server.h | 2 +- .../libraries/websocket_streaming/tests/mock_streaming_server.h | 2 +- .../websocket_streaming/tests/streaming_test_helpers.h | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h index cff630b..474f34c 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h index 42b8a08..91eb53a 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index dff1499..33eff03 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h index 0a7659d..eccef8b 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h index d1b4c6e..4c1560f 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 2ae0cac..9d2f2b8 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h index 8593b32..a417955 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index f25e7f0..0b1a874 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 71d9b7b..f74c418 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index bc1913a..916a664 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index ab112b7..c31df25 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h index be83e91..c6d7b0b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 02e26d2..7bcde00 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 669930d..64fed80 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h index 5d021f3..f5ad3e8 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index c21016d..28f2dc1 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h index d7cdd0d..d179909 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 7fbe395..88fe3d5 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h index f1f2225..d78111a 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h index 5c137a6..2e86ad8 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 68a5db6..22bcf19 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h index 965fdac..dd63503 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index 321f2b3..a5c2de0 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h index 1d8b133..b68a4e3 100644 --- a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h +++ b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 006de7b..2bc1596 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Blueberry d.o.o. + * Copyright 2022-2024 Blueberry d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 0aaeda817ced965b76311445f5b761e8b3a44c97 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Fri, 22 Mar 2024 14:59:55 +0100 Subject: [PATCH 042/127] Ask config for descriptor if mirrored streaming desc is not yet available --- .../websocket_client_signal_impl.h | 12 +--- .../src/websocket_client_device_impl.cpp | 2 +- .../src/websocket_client_signal_impl.cpp | 58 ++++--------------- 3 files changed, 14 insertions(+), 58 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 88fe3d5..30ba4fe 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -24,7 +24,6 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) { virtual void INTERFACE_FUNC createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) = 0; - virtual void INTERFACE_FUNC assignDescriptor(const DataDescriptorPtr& descriptor) = 0; }; class WebsocketClientSignalImpl final : public MirroredSignalBase @@ -34,27 +33,20 @@ class WebsocketClientSignalImpl final : public MirroredSignalBasesecond.setName(sInfo.signalName); signalIt->second.asPtr().lockAllAttributes(); - signalIt->second.asPtr()->assignDescriptor(sInfo.dataDescriptor); + signalIt->second.asPtr()->setMirroredDataDescriptor(sInfo.dataDescriptor); updateSignalProperties(signalIt->second, sInfo); } } diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index abf3332..e34e3e2 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "websocket_streaming/websocket_client_signal_impl.h" BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -31,63 +31,27 @@ StringPtr WebsocketClientSignalImpl::onGetRemoteId() const return streamingId; } -ErrCode WebsocketClientSignalImpl::getDescriptor(IDataDescriptor** descriptor) +Bool WebsocketClientSignalImpl::onTriggerEvent(const EventPacketPtr& eventPacket) { - OPENDAQ_PARAM_NOT_NULL(descriptor); - - std::scoped_lock lock(signalMutex); - - *descriptor = mirroredDataDescriptor.addRefAndReturn(); - return OPENDAQ_SUCCESS; -} - -Bool WebsocketClientSignalImpl::onTriggerEvent(EventPacketPtr eventPacket) -{ - if (eventPacket.assigned() && eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) - { - const auto params = eventPacket.getParameters(); - DataDescriptorPtr newSignalDescriptor = params[event_packet_param::DATA_DESCRIPTOR]; - DataDescriptorPtr newDomainDescriptor = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; - - std::scoped_lock lock(signalMutex); - if (newSignalDescriptor.assigned()) - { - mirroredDataDescriptor = newSignalDescriptor; - } - - if (domainSignalArtificial.assigned() && newDomainDescriptor.assigned()) - { - domainSignalArtificial.asPtr()->assignDescriptor(newDomainDescriptor); - } - } - - // No new duplicated event packets have been created so returns true to forward original packet - return True; + return Self::onTriggerEvent(eventPacket); } void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) { - std::scoped_lock lock(signalMutex); - - domainSignalArtificial = createWithImplementation( - this->context, - this->parent.getRef(), - CreateLocalId(streamingId+"_time_artificial")); - domainSignalArtificial.asPtr()->assignDescriptor(domainDescriptor); + const auto domainSig = createWithImplementation( + this->context, this->parent.getRef(), CreateLocalId(streamingId + "_time_artificial")); + domainSig->setMirroredDataDescriptor(domainDescriptor); + setMirroredDomainSignal(domainSig); } -void WebsocketClientSignalImpl::assignDescriptor(const DataDescriptorPtr& descriptor) +SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() { - std::scoped_lock lock(signalMutex); - - mirroredDataDescriptor = descriptor; + return getMirroredDomainSignal(); } -SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() +DataDescriptorPtr WebsocketClientSignalImpl::onGetDescriptor() { - std::scoped_lock lock(signalMutex); - - return domainSignalArtificial; + return getMirroredDataDescriptor(); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From f82dc60d2648319f0d414dab69a26036d50c90d1 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Mon, 25 Mar 2024 13:24:57 +0100 Subject: [PATCH 043/127] Change interface methods to pure ptrs --- .../src/websocket_client_device_impl.cpp | 2 +- .../src/websocket_client_signal_impl.cpp | 12 ++++++++---- .../src/websocket_streaming_impl.cpp | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 6a39dcc..0f02fbc 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -90,7 +90,7 @@ void WebsocketClientDeviceImpl::onSignalInit(const StringPtr& signalId, const Su signalIt->second.setName(sInfo.signalName); signalIt->second.asPtr().lockAllAttributes(); - signalIt->second.asPtr()->setMirroredDataDescriptor(sInfo.dataDescriptor); + signalIt->second.asPtr().setMirroredDataDescriptor(sInfo.dataDescriptor); updateSignalProperties(signalIt->second, sInfo); } } diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index e34e3e2..e748028 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "websocket_streaming/websocket_client_signal_impl.h" BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -41,17 +41,21 @@ void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptor const auto domainSig = createWithImplementation( this->context, this->parent.getRef(), CreateLocalId(streamingId + "_time_artificial")); domainSig->setMirroredDataDescriptor(domainDescriptor); - setMirroredDomainSignal(domainSig); + checkErrorInfo(setMirroredDomainSignal(domainSig.asPtr())); } SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() { - return getMirroredDomainSignal(); + MirroredSignalConfigPtr sig; + checkErrorInfo(getMirroredDomainSignal(&sig)); + return sig; } DataDescriptorPtr WebsocketClientSignalImpl::onGetDescriptor() { - return getMirroredDataDescriptor(); + DataDescriptorPtr desc; + checkErrorInfo(getMirroredDataDescriptor(&desc)); + return desc; } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index cd555bb..c3ac69c 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -86,9 +86,9 @@ void WebsocketStreamingImpl::prepareStreamingClient() if (signal.assigned()) { if (subscribed) - signal.template asPtr()->subscribeCompleted(connectionString); + signal.template asPtr().subscribeCompleted(connectionString); else - signal.template asPtr()->unsubscribeCompleted(connectionString); + signal.template asPtr().unsubscribeCompleted(connectionString); } } }; @@ -97,7 +97,7 @@ void WebsocketStreamingImpl::prepareStreamingClient() void WebsocketStreamingImpl::handleEventPacket(const MirroredSignalConfigPtr& signal, const EventPacketPtr& eventPacket) { - Bool forwardPacket = signal.template asPtr()->triggerEvent(eventPacket); + Bool forwardPacket = signal.template asPtr().triggerEvent(eventPacket); if (forwardPacket) signal.sendPacket(eventPacket); } From 7a546d4fb36543cce3ae95a8896f208ee0a6f5b9 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 20 Mar 2024 15:40:55 +0100 Subject: [PATCH 044/127] Integrate streaming-lt changes for shared domain signals: * Split server output signals implementation to domain and value signals * Assign real domain signal for websocket pseudo-device data signal if domain signal is publsihed by server * Streaming-lt server packet readers - sync packets of signal with shared domain read single packets one by one for each signal being processed, rather than reading all available packets per signal * Remove mock test server used for shared domain signal test * Add log messages to follow streaming-lt client / server threads joining * Increase timeout for streaming-lt subscription ack test --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/output_signal.h | 162 ++++- .../signal_descriptor_converter.h | 9 +- .../websocket_streaming/streaming_client.h | 13 +- .../websocket_streaming/streaming_server.h | 13 +- .../websocket_client_device_impl.h | 4 +- .../websocket_streaming/websocket_streaming.h | 9 + .../websocket_streaming_init.h | 20 - .../websocket_streaming/src/CMakeLists.txt | 2 - .../src/async_packet_reader.cpp | 21 +- .../websocket_streaming/src/output_signal.cpp | 561 ++++++++++++------ .../src/signal_descriptor_converter.cpp | 114 ++-- .../src/streaming_client.cpp | 46 +- .../src/streaming_server.cpp | 152 ++++- .../src/websocket_client_device_impl.cpp | 38 +- .../src/websocket_streaming_init.cpp | 13 - .../websocket_streaming/tests/CMakeLists.txt | 2 - .../tests/mock_streaming_server.cpp | 356 ----------- .../tests/mock_streaming_server.h | 124 ---- .../tests/streaming_test_helpers.h | 13 +- .../test_signal_descriptor_converter.cpp | 76 +-- .../tests/test_streaming.cpp | 4 +- .../tests/test_websocket_client_device.cpp | 42 +- 23 files changed, 894 insertions(+), 904 deletions(-) delete mode 100644 shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h delete mode 100644 shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp delete mode 100644 shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp delete mode 100644 shared/libraries/websocket_streaming/tests/mock_streaming_server.h diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 62ddd0e..7572360 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.15 + REQUIRED_VERSION 0.10.16 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.15 + GIT_REF v0.10.16 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index 916a664..fc2700c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -17,54 +17,154 @@ #pragma once #include "websocket_streaming/websocket_streaming.h" #include -#include "streaming_protocol/BaseSynchronousSignal.hpp" #include #include #include "streaming_protocol/Logging.hpp" +#include "websocket_streaming/signal_info.h" BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING -class OutputSignal; -using OutputSignalPtr = std::shared_ptr; +class OutputSignalBase; +using OutputSignalBasePtr = std::shared_ptr; -class OutputSignal +class OutputDomainSignalBase; +using OutputDomainSignaBaselPtr = std::shared_ptr; + +class OutputSignalBase { public: - using SignalStreamPtr = std::shared_ptr; + OutputSignalBase(const SignalPtr& signal, + const DataDescriptorPtr& domainDescriptor, + daq::streaming_protocol::BaseSignalPtr stream, + daq::streaming_protocol::LogCallback logCb); + virtual ~OutputSignalBase(); - OutputSignal(const daq::stream::StreamPtr& stream, const SignalPtr& signal, - daq::streaming_protocol::LogCallback logCb); - OutputSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, - daq::streaming_protocol::LogCallback logCb); + virtual void writeDaqPacket(const PacketPtr& packet) = 0; + virtual void setSubscribed(bool subscribed) = 0; + virtual bool isDataSignal() = 0; - virtual void write(const PacketPtr& packet); - virtual void write(const void* data, size_t sampleCount); - SignalPtr getCoreSignal(); - void setSubscribed(bool subscribed); + SignalPtr getDaqSignal(); bool isSubscribed(); + void submitTimeConfigChange(const DataDescriptorPtr& domainDescriptor); + bool isTimeConfigChanged(const DataDescriptorPtr& domainDescriptor); + protected: - DataDescriptorPtr getValueDescriptor(); - DataDescriptorPtr getDomainDescriptor(); - uint64_t getRuleDelta(); - uint64_t getTickResolution(); - void createSignalStream(); - void createStreamedSignal(); + virtual void toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) = 0; + + void submitSignalChanges(); + + static SignalProps getSignalProps(const SignalPtr& signal); + void writeDescriptorChangedEvent(const DataDescriptorPtr& descriptor); + + SignalPtr daqSignal; + SignalConfigPtr streamedDaqSignal; // used as dummy signal for encoding event packets + + daq::streaming_protocol::LogCallback logCallback; + + bool subscribed{false}; + bool doSetStartTime{false}; + std::mutex subscribedSync; + daq::streaming_protocol::BaseSignalPtr stream; + +private: + void processAttributeChangedCoreEvent(ComponentPtr& component, CoreEventArgsPtr& args); void subscribeToCoreEvent(); + void unsubscribeFromCoreEvent(); + void createStreamedSignal(); + + DataDescriptorPtr domainDescriptor; +}; + +class OutputValueSignalBase : public OutputSignalBase +{ +public: + OutputValueSignalBase(daq::streaming_protocol::BaseValueSignalPtr valueStream, + const SignalPtr& signal, + OutputDomainSignaBaselPtr outputDomainSignal, + daq::streaming_protocol::LogCallback logCb); + + void writeDaqPacket(const PacketPtr& packet) override; + void setSubscribed(bool subscribed) override; + bool isDataSignal() override; + +protected: + virtual void writeDataPacket(const DataPacketPtr& packet) = 0; + void toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) override; + + OutputDomainSignaBaselPtr outputDomainSignal; + +private: void writeEventPacket(const EventPacketPtr& packet); - void writeDataPacket(const DataPacketPtr& packet); void writeDescriptorChangedPacket(const EventPacketPtr& packet); - void processCoreEvent(ComponentPtr& component, CoreEventArgsPtr& args); - - SignalPtr signal; - SignalConfigPtr streamedSignal; - daq::streaming_protocol::StreamWriterPtr writer; - SignalStreamPtr stream; - size_t sampleSize; - bool subscribed; - bool writeStartDomainValue{false}; - std::mutex subscribedSync; - daq::streaming_protocol::LogCallback logCallback; + + daq::streaming_protocol::BaseValueSignalPtr valueStream; +}; + +class OutputDomainSignalBase : public OutputSignalBase +{ +public: + friend class OutputValueSignalBase; + + OutputDomainSignalBase(daq::streaming_protocol::BaseDomainSignalPtr domainStream, + const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb); + + void writeDaqPacket(const PacketPtr& packet) override; + void setSubscribed(bool subscribed) override; + bool isDataSignal() override; + + uint64_t calcStartTimeOffset(uint64_t dataPacketTimeStamp); + +private: + void subscribeByDataSignal(); + void unsubscribeByDataSignal(); + + size_t subscribedByDataSignalCount{0}; + daq::streaming_protocol::BaseDomainSignalPtr domainStream; +}; + +class OutputSyncValueSignal : public OutputValueSignalBase +{ +public: + OutputSyncValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + OutputDomainSignaBaselPtr outputDomainSignal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + +protected: + void writeDataPacket(const DataPacketPtr& packet) override; + +private: + static daq::streaming_protocol::BaseSynchronousSignalPtr createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + + daq::streaming_protocol::BaseSynchronousSignalPtr syncStream; +}; + +class OutputLinearDomainSignal : public OutputDomainSignalBase +{ +public: + OutputLinearDomainSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + +protected: + void toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) override; + +private: + static daq::streaming_protocol::LinearTimeSignalPtr createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + + daq::streaming_protocol::LinearTimeSignalPtr linearStream; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index c31df25..197439c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -38,13 +38,18 @@ class SignalDescriptorConverter /** * @throws ConversionFailedException */ - static void ToStreamedSignal(const daq::SignalPtr& signal, daq::streaming_protocol::BaseSignalPtr stream, const SignalProps& sigProps); + static void ToStreamedValueSignal(const daq::SignalPtr& valueSignal, + daq::streaming_protocol::BaseValueSignalPtr valueStream, + const SignalProps& sigProps); + static void ToStreamedLinearSignal(const daq::SignalPtr& domainSignal, + streaming_protocol::LinearTimeSignalPtr linearStream, + const SignalProps& sigProps); static void EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra); private: static daq::DataRulePtr GetRule(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - static void SetTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::BaseSignalPtr signal); + static void SetLinearTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::LinearTimeSignalPtr linearStream); static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 7bcde00..0955bef 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -46,8 +46,8 @@ class StreamingClient using OnPacketCallback = std::function; using OnSignalCallback = std::function; using OnFindSignalCallback = std::function; - using OnDomainDescriptorCallback = - std::function; + using OnDomainSignalInitCallback = + std::function; using OnAvailableSignalsCallback = std::function& signalIds)>; using OnSubsciptionAckCallback = std::function; @@ -60,7 +60,7 @@ class StreamingClient void onPacket(const OnPacketCallback& callack); void onSignalInit(const OnSignalCallback& callback); void onSignalUpdated(const OnSignalCallback& callback); - void onDomainDescriptor(const OnDomainDescriptorCallback& callback); + void onDomainSingalInit(const OnDomainSignalInitCallback& callback); void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); void onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback); void onFindSignal(const OnFindSignalCallback& callback); @@ -86,8 +86,9 @@ class StreamingClient void publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); void setSignalInitSatisfied(const std::string& signalId); - void setDomainDescriptor(const std::string& signalId, + void setDomainIdAndDescriptor(const std::string& dataSignalId, const InputSignalPtr& inputSignal, + const std::string& domainSignalId, const DataDescriptorPtr& domainDescriptor); std::vector> findDataSignalsByTableId(const std::string& tableId); @@ -105,7 +106,7 @@ class StreamingClient std::unordered_map signals; OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; OnSignalCallback onSignalInitCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; - OnDomainDescriptorCallback onDomainDescriptorCallback = [](const StringPtr&, const DataDescriptorPtr&) {}; + OnDomainSignalInitCallback onDomainSignalInitCallback = [](const StringPtr&, const StringPtr&, const DataDescriptorPtr&) {}; OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; OnFindSignalCallback onFindSignalCallback = [](const StringPtr& signalId) { return nullptr; }; @@ -127,7 +128,7 @@ class StreamingClient // 4-th: boolean flag indicating that initial unsubscription completion ack is filtered-out std::unordered_map, std::future, bool, bool>> signalInitializedStatus; - std::unordered_map cachedDomainDescriptors; + std::unordered_map> cachedDomainIdsAndDescriptors; bool useRawTcpConnection; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 64fed80..4235870 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -59,16 +59,19 @@ class StreamingServer void sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet); protected: - using SignalMap = std::unordered_map; + using SignalMap = std::unordered_map; using ClientMap = std::unordered_map>; + void addToOutputSignals(const SignalPtr& signal, + SignalMap& outputSignals, + const streaming_protocol::StreamWriterPtr& writer); void onAcceptInternal(const daq::stream::StreamPtr& stream); void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals); void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); bool isSignalSubscribed(const std::string& signalId) const; - void subscribeHandler(const std::string& signalId, OutputSignalPtr signal); - void unsubscribeHandler(const std::string& signalId, OutputSignalPtr signal); + void subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); + void unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); int onControlCommand(const std::string& streamId, const std::string& command, const daq::streaming_protocol::SignalIds& signalIds, @@ -87,6 +90,10 @@ class StreamingServer LoggerPtr logger; LoggerComponentPtr loggerComponent; daq::streaming_protocol::LogCallback logCallback; + bool serverRunning{false}; + +private: + static DataRuleType getDomainSignalRuleType(const SignalPtr& domainSignal); }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 28f2dc1..2a7db1a 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -37,7 +37,9 @@ class WebsocketClientDeviceImpl : public Device void updateSignalProperties(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); void onSignalInit(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); - void onDomainDescriptor(const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor); + void onDomainSignalInit(const StringPtr& signalId, + const StringPtr& domainSignalId, + const DataDescriptorPtr& domainDescriptor); void createDeviceSignals(const std::vector& signalIds); DeviceInfoConfigPtr deviceInfo; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h index d78111a..616ae82 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -40,8 +40,17 @@ namespace daq::streaming_protocol class BaseSignal; using BaseSignalPtr = std::shared_ptr; + class BaseValueSignal; + using BaseValueSignalPtr = std::shared_ptr; + class BaseSynchronousSignal; using BaseSynchronousSignalPtr = std::shared_ptr; + + class BaseDomainSignal; + using BaseDomainSignalPtr = std::shared_ptr; + + class LinearTimeSignal; + using LinearTimeSignalPtr = std::shared_ptr; } namespace daq::stream diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h deleted file mode 100644 index dd63503..0000000 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_init.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022-2024 Blueberry d.o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include - -void daqInitStreamingLibrary(); diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index d3670c2..0f8860e 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -11,7 +11,6 @@ set(SRC_Cpp signal_descriptor_converter.cpp input_signal.cpp output_signal.cpp async_packet_reader.cpp - websocket_streaming_init.cpp websocket_streaming_server.cpp websocket_client_device_impl.cpp websocket_client_signal_impl.cpp @@ -27,7 +26,6 @@ set(SRC_PublicHeaders input_signal.h output_signal.h async_packet_reader.h - websocket_streaming_init.h websocket_streaming_server.h websocket_client_device_impl.h websocket_client_device_factory.h diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index 7d6520b..1962226 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -59,14 +59,23 @@ void AsyncPacketReader::startReadThread() { { std::scoped_lock lock(readersSync); - for (const auto& [signal, reader] : signalReaders) + bool hasPacketsToRead; + do { - if (reader.getAvailableCount() == 0) - continue; - - const auto& packets = reader.readAll(); - onPacketCallback(signal, packets); + hasPacketsToRead = false; + for (const auto& [signal, reader] : signalReaders) + { + if (reader.getAvailableCount() == 0) + continue; + + const auto& packet = reader.read(); + onPacketCallback(signal, {packet}); + + if (reader.getAvailableCount() > 0) + hasPacketsToRead = true; + } } + while(hasPacketsToRead); } std::this_thread::sleep_for(sleepTime); diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index d563564..ce91c4f 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -1,37 +1,187 @@ #include "websocket_streaming/output_signal.h" -#include "websocket_streaming/signal_descriptor_converter.h" -#include #include #include "streaming_protocol/StreamWriter.h" #include "streaming_protocol/SynchronousSignal.hpp" +#include "streaming_protocol/LinearTimeSignal.hpp" #include #include #include +#include "websocket_streaming/signal_descriptor_converter.h" BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING using namespace daq::streaming_protocol; using namespace daq::stream; -OutputSignal::OutputSignal(const StreamPtr& stream, const SignalPtr& signal, - daq::streaming_protocol::LogCallback logCb) - : OutputSignal(std::make_shared(stream), signal, logCb) -{ -} - -OutputSignal::OutputSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, - daq::streaming_protocol::LogCallback logCb) - : signal(signal) - , writer(writer) - , subscribed(false) +OutputSignalBase::OutputSignalBase(const SignalPtr& signal, + const DataDescriptorPtr& domainDescriptor, + BaseSignalPtr stream, + daq::streaming_protocol::LogCallback logCb) + : daqSignal(signal) , logCallback(logCb) + , subscribed(false) + , stream(stream) + , domainDescriptor(domainDescriptor) { - createSignalStream(); createStreamedSignal(); subscribeToCoreEvent(); } -void OutputSignal::write(const PacketPtr& packet) +OutputSignalBase::~OutputSignalBase() +{ + unsubscribeFromCoreEvent(); +} + +void OutputSignalBase::createStreamedSignal() +{ + const auto context = daqSignal.getContext(); + + streamedDaqSignal = SignalWithDescriptor(context, daqSignal.getDescriptor(), nullptr, daqSignal.getLocalId()); + + streamedDaqSignal.setName(daqSignal.getName()); + streamedDaqSignal.setDescription(daqSignal.getDescription()); +} + +void OutputSignalBase::subscribeToCoreEvent() +{ + daqSignal.getOnComponentCoreEvent() += event(this, &OutputSignalBase::processAttributeChangedCoreEvent); +} + +void OutputSignalBase::unsubscribeFromCoreEvent() +{ + daqSignal.getOnComponentCoreEvent() -= event(this, &OutputSignalBase::processAttributeChangedCoreEvent); +} + +void OutputSignalBase::processAttributeChangedCoreEvent(ComponentPtr& /*component*/, CoreEventArgsPtr& args) +{ + if (args.getEventId() != static_cast(CoreEventId::AttributeChanged)) + return; + + const auto params = args.getParameters(); + const auto name = params.get("AttributeName"); + const auto value = params.get(name); + + SignalProps sigProps; + if (name == "Name") + { + sigProps.name = value; + streamedDaqSignal.setName(value); + } + else if (name == "Description") + { + sigProps.description = value; + streamedDaqSignal.setDescription(value); + } + else + { + return; + } + + // Streaming LT does not support attribute change forwarding for active, public, and visible + toStreamedSignal(daqSignal, sigProps); + submitSignalChanges(); +} + +SignalProps OutputSignalBase::getSignalProps(const SignalPtr& signal) +{ + SignalProps signalProps; + + signalProps.name = signal.getName(); + signalProps.description = signal.getDescription(); + + return signalProps; +} + +SignalPtr OutputSignalBase::getDaqSignal() +{ + return daqSignal; +} + +bool OutputSignalBase::isSubscribed() +{ + std::scoped_lock lock(subscribedSync); + + return subscribed; +} + +void OutputSignalBase::submitSignalChanges() +{ + stream->writeSignalMetaInformation(); +} + +void OutputSignalBase::writeDescriptorChangedEvent(const DataDescriptorPtr& descriptor) +{ + streamedDaqSignal.setDescriptor(descriptor); + + toStreamedSignal(streamedDaqSignal, getSignalProps(streamedDaqSignal)); + submitSignalChanges(); +} + +void OutputSignalBase::submitTimeConfigChange(const DataDescriptorPtr& domainDescriptor) +{ + // significant parameters of domain signal have been changed + // reset values used for timestamp calculations + + this->domainDescriptor = domainDescriptor; + doSetStartTime = true; +} + +bool OutputSignalBase::isTimeConfigChanged(const DataDescriptorPtr& domainDescriptor) +{ + return this->domainDescriptor.getRule() != domainDescriptor.getRule() || + this->domainDescriptor.getTickResolution() != domainDescriptor.getTickResolution(); +} + +OutputValueSignalBase::OutputValueSignalBase(daq::streaming_protocol::BaseValueSignalPtr valueStream, + const SignalPtr& signal, + OutputDomainSignaBaselPtr outputDomainSignal, + daq::streaming_protocol::LogCallback logCb) + : OutputSignalBase(signal, outputDomainSignal->getDaqSignal().getDescriptor(), valueStream, logCb) + , outputDomainSignal(outputDomainSignal) + , valueStream(valueStream) +{ +} + +void OutputValueSignalBase::writeEventPacket(const EventPacketPtr& packet) +{ + const auto eventId = packet.getEventId(); + + if (eventId == event_packet_id::DATA_DESCRIPTOR_CHANGED) + { + writeDescriptorChangedPacket(packet); + } + else + { + STREAMING_PROTOCOL_LOG_E("Event type {} is not supported by streaming.", eventId); + } +} + +void OutputValueSignalBase::writeDescriptorChangedPacket(const EventPacketPtr& packet) +{ + const auto params = packet.getParameters(); + const DataDescriptorPtr valueDescriptor = params.get(event_packet_param::DATA_DESCRIPTOR); + const DataDescriptorPtr domainDescriptor = params.get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); + + if (valueDescriptor.assigned()) + { + this->writeDescriptorChangedEvent(valueDescriptor); + } + if (domainDescriptor.assigned()) + { + if (outputDomainSignal->isTimeConfigChanged(domainDescriptor)) + { + outputDomainSignal->submitTimeConfigChange(domainDescriptor); + } + + if (isTimeConfigChanged(domainDescriptor)) + { + submitTimeConfigChange(domainDescriptor); + } + outputDomainSignal->writeDescriptorChangedEvent(domainDescriptor); + } +} + +void OutputValueSignalBase::writeDaqPacket(const PacketPtr& packet) { const auto type = packet.getType(); @@ -45,102 +195,253 @@ void OutputSignal::write(const PacketPtr& packet) break; default: STREAMING_PROTOCOL_LOG_E("Failed to write a packet of unsupported type."); + break; + } +} + +// bool subscribed; / void setSubscribed(bool); / bool isSubscribed() : +// To prevent client side streaming protocol error the server should not send data before +// the subscribe acknowledgment is sent neither after the unsubscribe acknowledgment is sent. +// The `subscribed` member variable functions as a flag that enables/disables data streaming +// within the OutputSignal. +// Mutex locking ensures that data is sent only when the specified conditions are satisfied. +void OutputValueSignalBase::setSubscribed(bool subscribed) +{ + if (this->subscribed != subscribed) + { + std::scoped_lock lock(subscribedSync); + + this->subscribed = subscribed; + doSetStartTime = true; + if (subscribed) + { + outputDomainSignal->subscribeByDataSignal(); + stream->subscribe(); + } + else + { + stream->unsubscribe(); + outputDomainSignal->unsubscribeByDataSignal(); + } } } -void OutputSignal::write(const void* data, size_t sampleCount) +bool OutputValueSignalBase::isDataSignal() +{ + return true; +} + +void OutputValueSignalBase::toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) +{ + SignalDescriptorConverter::ToStreamedValueSignal(signal, valueStream, sigProps); +} + +OutputDomainSignalBase::OutputDomainSignalBase(daq::streaming_protocol::BaseDomainSignalPtr domainStream, + const SignalPtr& signal, + daq::streaming_protocol::LogCallback logCb) + : OutputSignalBase(signal, signal.getDescriptor(), domainStream, logCb) + , domainStream(domainStream) +{ +} + +void OutputDomainSignalBase::writeDaqPacket(const PacketPtr& packet) { - stream->addData(data, sampleCount); + throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); } -DataDescriptorPtr OutputSignal::getValueDescriptor() +uint64_t OutputDomainSignalBase::calcStartTimeOffset(uint64_t dataPacketTimeStamp) { - auto dataDescriptor = signal.getDescriptor(); - if (!dataDescriptor.assigned()) - throw InvalidParameterException("Signal descriptor not set."); + if (doSetStartTime) + { + STREAMING_PROTOCOL_LOG_I("time signal {}: reset start timestamp: {}", daqSignal.getGlobalId(), dataPacketTimeStamp); + + domainStream->setTimeStart(dataPacketTimeStamp); + doSetStartTime = false; + return 0; + } + else + { + auto signalStartTime = domainStream->getTimeStart(); + if (dataPacketTimeStamp < signalStartTime) + { + STREAMING_PROTOCOL_LOG_E( + "Unable to calc start time index: domain signal start time {}, time stamp from packet {}", + signalStartTime, + dataPacketTimeStamp); + return 0; + } + return (dataPacketTimeStamp - signalStartTime); + } +} - if (dataDescriptor.getSampleType() == daq::SampleType::Struct) - throw InvalidParameterException("Signal cannot be a struct."); +// bool subscribed; / void setSubscribed(bool); / bool isSubscribed(); / (un)subscribeByDataSignal() : +// To prevent client side streaming protocol error the server should not send data before +// the subscribe acknowledgment is sent neither after the unsubscribe acknowledgment is sent. +// The `subscribed` member variable functions as a flag that enables/disables data streaming +// within the OutputSignal. +// Mutex locking ensures that data is sent only when the specified conditions are satisfied. +void OutputDomainSignalBase::subscribeByDataSignal() +{ + std::scoped_lock lock(subscribedSync); - return dataDescriptor; + if (subscribedByDataSignalCount == 0) + { + doSetStartTime = true; + if (!this->subscribed) + stream->subscribe(); + } + + subscribedByDataSignalCount++; } -DataDescriptorPtr OutputSignal::getDomainDescriptor() +void OutputDomainSignalBase::unsubscribeByDataSignal() { - auto domainSignal = signal.getDomainSignal(); - if (!domainSignal.assigned()) - throw InvalidParameterException("Domain signal not set."); + std::scoped_lock lock(subscribedSync); - auto domainDataDescriptor = domainSignal.getDescriptor(); - if (!domainDataDescriptor.assigned()) - throw InvalidParameterException("Domain signal descriptor not set."); + if (subscribedByDataSignalCount == 0) + { + STREAMING_PROTOCOL_LOG_E("Cannot unsubscribe domain signal by data signal - already has 0 subscribers"); + return; + } - if (domainDataDescriptor.getSampleType() == daq::SampleType::Struct) - throw InvalidParameterException("Signal cannot be a struct."); + subscribedByDataSignalCount--; - return domainDataDescriptor; + if (subscribedByDataSignalCount == 0) + { + if (!this->subscribed) + stream->unsubscribe(); + } } -uint64_t OutputSignal::getRuleDelta() +void OutputDomainSignalBase::setSubscribed(bool subscribed) { - auto valueDescriptor = getDomainDescriptor(); + if (this->subscribed != subscribed) + { + std::scoped_lock lock(subscribedSync); - auto dataRule = valueDescriptor.getRule(); - if (dataRule.getType() != DataRuleType::Linear) - throw InvalidParameterException("Invalid data rule."); + this->subscribed = subscribed; + if (subscribed) + { + if (subscribedByDataSignalCount == 0) + stream->subscribe(); + } + else + { + if (subscribedByDataSignalCount == 0) + stream->unsubscribe(); + } + } +} - return dataRule.getParameters().get("delta"); +bool OutputDomainSignalBase::isDataSignal() +{ + return false; } -uint64_t OutputSignal::getTickResolution() +OutputLinearDomainSignal::OutputLinearDomainSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) + : OutputDomainSignalBase(createSignalStream(writer, signal, tableId, logCb), signal, logCb) + , linearStream(std::dynamic_pointer_cast(stream)) +{} + +void OutputLinearDomainSignal::toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) { - auto valueDescriptor = getDomainDescriptor(); - auto resolution = valueDescriptor.getTickResolution(); - return resolution.getDenominator() / resolution.getNumerator(); + SignalDescriptorConverter::ToStreamedLinearSignal(signal, linearStream, sigProps); +} + +LinearTimeSignalPtr OutputLinearDomainSignal::createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) +{ + LinearTimeSignalPtr linearStream; + + const auto signalId = signal.getGlobalId(); + auto descriptor = signal.getDescriptor(); + + // streaming-lt supports only 64bit domain values + daq::SampleType daqSampleType = descriptor.getSampleType(); + if (daqSampleType != daq::SampleType::Int64 && + daqSampleType != daq::SampleType::UInt64) + throw InvalidParameterException("Unsupported domain signal sample type"); + + auto dataRule = descriptor.getRule(); + if (dataRule.getType() != DataRuleType::Linear) + throw InvalidParameterException("Invalid domain signal data rule {}.", (size_t)dataRule.getType()); + + auto unit = descriptor.getUnit(); + if (!unit.assigned() || + /*unit.getId() != streaming_protocol::Unit::UNIT_ID_SECONDS ||*/ + unit.getSymbol() != "s" || + unit.getQuantity() != "time") + { + throw InvalidParameterException( + "Domain signal unit parameters: {}, does not match the predefined values for linear time signal", + unit.assigned() ? unit.toString() : "not assigned"); + } + + // from streaming library side, output rate is defined as nanoseconds between two samples + const auto outputRate = dataRule.getParameters().get("delta"); + const auto resolution = + descriptor.getTickResolution().getDenominator() / descriptor.getTickResolution().getNumerator(); + + auto outputRateInNs = BaseDomainSignal::nanosecondsFromTimeTicks(outputRate, resolution); + linearStream = std::make_shared(signalId, tableId, resolution, outputRateInNs, *writer, logCb); + + SignalDescriptorConverter::ToStreamedLinearSignal(signal, linearStream, getSignalProps(signal)); + + return linearStream; } -void OutputSignal::createSignalStream() +BaseSynchronousSignalPtr OutputSyncValueSignal::createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) { - const auto valueDescriptor = getValueDescriptor(); + BaseSynchronousSignalPtr syncStream; + + const auto valueDescriptor = signal.getDescriptor(); auto sampleType = valueDescriptor.getSampleType(); if (valueDescriptor.getPostScaling().assigned()) sampleType = valueDescriptor.getPostScaling().getInputSampleType(); - const auto id = signal.getGlobalId(); - const auto outputRate = getRuleDelta(); // from streaming library side, output rate is defined as number of tics between two samples - const auto resolution = getTickResolution(); - sampleSize = getSampleSize(sampleType); + + const auto signalId = signal.getGlobalId(); switch (sampleType) { case daq::SampleType::Int8: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::UInt8: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::Int16: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::UInt16: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::Int32: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::UInt32: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::Int64: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::UInt64: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::Float32: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::Float64: - stream = std::make_shared>(id, outputRate, resolution, *writer, logCallback); + syncStream = std::make_shared>(signalId, tableId, *writer, logCb); break; case daq::SampleType::ComplexFloat32: case daq::SampleType::ComplexFloat64: @@ -150,140 +451,54 @@ void OutputSignal::createSignalStream() case daq::SampleType::RangeInt64: case daq::SampleType::Struct: default: - throw InvalidTypeException(); + throw InvalidTypeException("Unsupported data signal sample type"); } - SignalProps sigProps; - sigProps.name = signal.getName(); - sigProps.description = signal.getDescription(); - SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); -} - -void OutputSignal::createStreamedSignal() -{ - const auto context = signal.getContext(); + SignalDescriptorConverter::ToStreamedValueSignal(signal, syncStream, getSignalProps(signal)); - auto domainSignal = Signal(context, nullptr, "domain"); - streamedSignal = Signal(context, nullptr, signal.getLocalId()); - streamedSignal.setDomainSignal(domainSignal); - streamedSignal.setName(signal.getName()); - streamedSignal.setDescription(signal.getDescription()); + return syncStream; } -void OutputSignal::subscribeToCoreEvent() +OutputSyncValueSignal::OutputSyncValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, OutputDomainSignaBaselPtr outputDomainSignal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) + : OutputValueSignalBase(createSignalStream(writer, signal, tableId, logCb), signal, outputDomainSignal, logCb) + , syncStream(std::dynamic_pointer_cast(stream)) { - signal.getOnComponentCoreEvent() += event(this, &OutputSignal::processCoreEvent); } -void OutputSignal::writeEventPacket(const EventPacketPtr& packet) -{ - const auto eventId = packet.getEventId(); - - if (eventId == event_packet_id::DATA_DESCRIPTOR_CHANGED) - writeDescriptorChangedPacket(packet); - else - { - STREAMING_PROTOCOL_LOG_E("Event type {} is not supported by streaming.", eventId); - } -} - -void OutputSignal::writeDataPacket(const DataPacketPtr& packet) +void OutputSyncValueSignal::writeDataPacket(const DataPacketPtr& packet) { const auto domainPacket = packet.getDomainPacket(); - if (writeStartDomainValue) + if (!domainPacket.assigned() || !domainPacket.getDataDescriptor().assigned()) { - if (domainPacket.assigned()) - stream->setTimeStart(domainPacket.getOffset()); - - writeStartDomainValue = false; - } - - stream->addData(packet.getRawData(), packet.getSampleCount()); -} - -void OutputSignal::writeDescriptorChangedPacket(const EventPacketPtr& packet) -{ - const auto params = packet.getParameters(); - const auto valueDescriptor = params.get(event_packet_param::DATA_DESCRIPTOR); - const auto domainDescriptor = params.get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); - - if (valueDescriptor.assigned()) - streamedSignal.setDescriptor(valueDescriptor); - SignalConfigPtr domainSignal = streamedSignal.getDomainSignal(); - if (domainSignal.assigned() && domainDescriptor.assigned()) - domainSignal.setDescriptor(domainDescriptor); - - SignalDescriptorConverter::ToStreamedSignal(streamedSignal, stream, SignalProps{}); - stream->writeSignalMetaInformation(); -} - -void OutputSignal::processCoreEvent(ComponentPtr& /*component*/, CoreEventArgsPtr& args) -{ - if (args.getEventId() != static_cast(CoreEventId::AttributeChanged)) + STREAMING_PROTOCOL_LOG_E("streaming-lt: cannot stream data packet without domain packet / descriptor"); return; - - const auto params = args.getParameters(); - const auto name = params.get("AttributeName"); - const auto value = params.get(name); - - SignalProps sigProps; - if (name == "Name") - { - sigProps.name = value; - streamedSignal.setName(value); - } - else if (name == "Description") - { - sigProps.description = value; - streamedSignal.setDescription(value); } - else + const auto packetDomainDescriptor = domainPacket.getDataDescriptor(); + if (outputDomainSignal->isTimeConfigChanged(packetDomainDescriptor) || + isTimeConfigChanged(packetDomainDescriptor)) { + STREAMING_PROTOCOL_LOG_E("Domain signal config mismatched, skip data packet"); return; } - // Streaming LT does not support attribute change forwarding for active, public, and visible - - SignalDescriptorConverter::ToStreamedSignal(signal, stream, sigProps); - stream->writeSignalMetaInformation(); -} - -SignalPtr OutputSignal::getCoreSignal() -{ - return signal; -} - -// bool subscribed; / void setSubscribed(bool); / bool isSubscribed() : -// To prevent client side streaming protocol error the server should not send data before -// the subscribe acknowledgment is sent neither after the unsubscribe acknowledgment is sent. -// The `subscribed` member variable functions as a flag that enables/disables data streaming -// within the OutputSignal. -// Mutex locking ensures that data is sent only when the specified conditions are satisfied. - -void OutputSignal::setSubscribed(bool subscribed) -{ - if (this->subscribed != subscribed) + if (doSetStartTime) { - std::scoped_lock lock(subscribedSync); + uint64_t timeStamp = domainPacket.getOffset(); + auto timeValueOffset = outputDomainSignal->calcStartTimeOffset(timeStamp); + Int deltaInTicks = packetDomainDescriptor.getRule().getParameters().get("delta"); + uint64_t timeValueIndex = timeValueOffset / deltaInTicks; + syncStream->setValueIndex(timeValueIndex); + submitSignalChanges(); - this->subscribed = subscribed; - if (subscribed) - { - stream->subscribe(); - writeStartDomainValue = true; - } - else - { - stream->unsubscribe(); - } - } -} + STREAMING_PROTOCOL_LOG_I("data signal {}: reset time value index: {}", daqSignal.getGlobalId(), timeValueIndex); -bool OutputSignal::isSubscribed() -{ - std::scoped_lock lock(subscribedSync); + doSetStartTime = false; + } - return subscribed; + syncStream->addData(packet.getRawData(), packet.getSampleCount()); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 77e2545..63ba290 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -9,9 +9,6 @@ #include #include -#include "streaming_protocol/BaseSignal.hpp" -#include "streaming_protocol/BaseSynchronousSignal.hpp" -#include "streaming_protocol/SubscribedSignal.hpp" #include "streaming_protocol/Types.h" #include "websocket_streaming/signal_descriptor_converter.h" @@ -19,6 +16,10 @@ #include #include +#include "streaming_protocol/BaseDomainSignal.hpp" +#include "streaming_protocol/BaseValueSignal.hpp" +#include "streaming_protocol/LinearTimeSignal.hpp" + BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING /** @@ -83,19 +84,17 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre return sInfo; } -void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, - daq::streaming_protocol::BaseSignalPtr stream, - const SignalProps& sigProps) +void SignalDescriptorConverter::ToStreamedValueSignal(const daq::SignalPtr& valueSignal, + daq::streaming_protocol::BaseValueSignalPtr valueStream, + const SignalProps& sigProps) { - auto dataDescriptor = signal.getDescriptor(); + auto dataDescriptor = valueSignal.getDescriptor(); if (!dataDescriptor.assigned()) return; - auto domainDescriptor = signal.getDomainSignal().getDescriptor(); - // *** meta "definition" start *** // set/verify fields which will be lately encoded into signal "definition" object - stream->setMemberName(signal.getName()); + valueStream->setMemberName(valueSignal.getName()); // Data type of stream can not be changed. Complain upon change! daq::SampleType daqSampleType = dataDescriptor.getSampleType(); @@ -103,22 +102,12 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, daqSampleType = dataDescriptor.getPostScaling().getInputSampleType(); daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); - if (requestedSampleType != stream->getSampleType()) + if (requestedSampleType != valueStream->getSampleType()) throw ConversionFailedException(); UnitPtr unit = dataDescriptor.getUnit(); if (unit.assigned()) - stream->setUnit(unit.getId(), unit.getSymbol()); - - // causes an error, so openDAQ utilizes fields of "interpretation" object to encode time rule - // DataRulePtr rule = domainDescriptor.getRule(); - // SetTimeRule(rule, stream); - - if (domainDescriptor.assigned()) - { - auto resolution = domainDescriptor.getTickResolution(); - stream->setTimeTicksPerSecond(resolution.getDenominator() / resolution.getNumerator()); - } + valueStream->setUnit(unit.getId(), unit.getSymbol()); // *** meta "definition" end *** // --- meta "interpretation" start --- @@ -130,16 +119,50 @@ void SignalDescriptorConverter::ToStreamedSignal(const daq::SignalPtr& signal, if (sigProps.description.has_value()) extra["sig_desc"] = sigProps.description.value(); - stream->setDataInterpretationObject(extra); + valueStream->setInterpretationObject(extra); // --- meta "interpretation" end --- +} + +void SignalDescriptorConverter::ToStreamedLinearSignal(const daq::SignalPtr& domainSignal, + streaming_protocol::LinearTimeSignalPtr linearStream, + const SignalProps& sigProps) +{ + auto domainDescriptor = domainSignal.getDescriptor(); + if (!domainDescriptor.assigned()) + return; + + // *** meta "definition" start *** + + // streaming-lt supports only 64bit domain values + daq::SampleType daqSampleType = domainDescriptor.getSampleType(); + daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); + if (requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_S64 && + requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_U64) + throw ConversionFailedException(); + + DataRulePtr rule = domainDescriptor.getRule(); + SetLinearTimeRule(rule, linearStream); + + auto resolution = domainDescriptor.getTickResolution(); + linearStream->setTimeTicksPerSecond(resolution.getDenominator() / resolution.getNumerator()); + // *** meta "definition" end *** + + // --- meta "interpretation" start --- // openDAQ does not encode meta "definition" object directly for domain signal // domain signal "definition" is hardcoded on library level // so openDAQ uses "interpretation" object to transmit metadata which describes domain signal - nlohmann::json domainExtra; + nlohmann::json extra; if (domainDescriptor.assigned()) - EncodeInterpretationObject(domainDescriptor, domainExtra); - stream->setTimeInterpretationObject(domainExtra); + EncodeInterpretationObject(domainDescriptor, extra); + + if (sigProps.name.has_value()) + extra["sig_name"] = sigProps.name.value(); + if (sigProps.description.has_value()) + extra["sig_desc"] = sigProps.description.value(); + + linearStream->setInterpretationObject(extra); + // --- meta "interpretation" end --- } /** @@ -174,45 +197,14 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco /** * @throws ConversionFailedException */ -void SignalDescriptorConverter::SetTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::BaseSignalPtr signal) +void SignalDescriptorConverter::SetLinearTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::LinearTimeSignalPtr linearStream) { - daq::streaming_protocol::RuleType signalTimeRule = signal->getTimeRule(); - if ((rule == nullptr) && (signalTimeRule != daq::streaming_protocol::RULETYPE_EXPLICIT)) + if (!rule.assigned() || rule.getType() != DataRuleType::Linear) { - // no rule is interpreted as explicit rule - - // changing signal time rule is not allowed throw ConversionFailedException(); } - switch (rule.getType()) - { - case DataRuleType::Linear: - { - daq::streaming_protocol::BaseSynchronousSignalPtr baseSyncSignal = - std::dynamic_pointer_cast(signal); - if (!baseSyncSignal) - { - // this is not a synchronous signal. - // changing signal time rule type is not allowed - throw ConversionFailedException(); - } - NumberPtr delta = rule.getParameters().get("delta"); - NumberPtr start = rule.getParameters().get("start"); - baseSyncSignal->setOutputRate(delta); - baseSyncSignal->setTimeStart(start); // this call performs write which causes protocol error - } - break; - case daq::DataRuleType::Explicit: - // changing signal time rule is not allowed - if (signalTimeRule != daq::streaming_protocol::RULETYPE_EXPLICIT) - { - throw ConversionFailedException(); - } - break; - case daq::DataRuleType::Constant: - default: - throw ConversionFailedException(); - } + uint64_t delta = rule.getParameters().get("delta"); + linearStream->setOutputRate(delta); } /** diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 6210ba5..76584d0 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -128,8 +128,23 @@ bool StreamingClient::connect() void StreamingClient::disconnect() { ioContext.stop(); - if (clientThread.joinable()) - clientThread.join(); + if (clientThread.get_id() != std::this_thread::get_id()) + { + if (clientThread.joinable()) + { + clientThread.join(); + LOG_I("Websocket streaming client thread joined"); + } + else + { + LOG_W("Websocket streaming client thread is not joinable"); + } + } + else + { + LOG_C("Websocket streaming client thread cannot join itself"); + } + connected = false; } @@ -148,9 +163,9 @@ void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) onSignalUpdatedCallback = callback; } -void StreamingClient::onDomainDescriptor(const OnDomainDescriptorCallback& callback) +void StreamingClient::onDomainSingalInit(const OnDomainSignalInitCallback& callback) { - onDomainDescriptorCallback = callback; + onDomainSignalInitCallback = callback; } void StreamingClient::onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback) @@ -350,9 +365,11 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig if (inputSignal->getTableId() != tableId) { inputSignal->setTableId(tableId); - if (auto domainDescIt = cachedDomainDescriptors.find(tableId); domainDescIt != cachedDomainDescriptors.end()) + if (auto domainIdAndDescIt = cachedDomainIdsAndDescriptors.find(tableId); domainIdAndDescIt != cachedDomainIdsAndDescriptors.end()) { - setDomainDescriptor(id, inputSignal, domainDescIt->second); + auto domainSignalId = domainIdAndDescIt->second.first; + auto domainSignalDescriptor = domainIdAndDescIt->second.second; + setDomainIdAndDescriptor(id, inputSignal, domainSignalId, domainSignalDescriptor); } } @@ -392,11 +409,11 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig // set domain descriptor for all input data signals with known tableId for (const auto& [dataSignalId, inputSignal] : findDataSignalsByTableId(tableId)) { - setDomainDescriptor(dataSignalId, inputSignal, sInfo.dataDescriptor); + setDomainIdAndDescriptor(dataSignalId, inputSignal, timeSignalId, sInfo.dataDescriptor); } - // some data signals can have unknown tableId at the moment, save domain descriptor for future - cachedDomainDescriptors.insert_or_assign(tableId, sInfo.dataDescriptor); + // some data signals can have unknown tableId at the moment, save domain signal id and descriptor for future + cachedDomainIdsAndDescriptors.insert_or_assign(tableId, std::make_pair(timeSignalId, sInfo.dataDescriptor)); } void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal) @@ -499,15 +516,16 @@ void StreamingClient::setSignalInitSatisfied(const std::string& signalId) } } -void StreamingClient::setDomainDescriptor(const std::string& signalId, - const InputSignalPtr& inputSignal, - const DataDescriptorPtr& domainDescriptor) +void StreamingClient::setDomainIdAndDescriptor(const std::string& dataSignalId, + const InputSignalPtr& inputSignal, + const std::string& domainSignalId, + const DataDescriptorPtr& domainDescriptor) { // Sets the descriptors of pseudo device signal when first connecting if (!inputSignal->getDomainSignalDescriptor().assigned()) { - onDomainDescriptorCallback(signalId, domainDescriptor); - setSignalInitSatisfied(signalId); + onDomainSignalInitCallback(dataSignalId, domainSignalId, domainDescriptor); + setSignalInitSatisfied(dataSignalId); } inputSignal->setDomainDescriptor(domainDescriptor); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 7ea2a13..805646d 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -29,11 +29,13 @@ StreamingServer::StreamingServer(const ContextPtr& context) StreamingServer::~StreamingServer() { stop(); - logger.removeComponent("StreamingServer"); } void StreamingServer::start(uint16_t port, uint16_t controlPort) { + if (serverRunning) + return; + this->port = port; ioContext.restart(); @@ -57,19 +59,47 @@ void StreamingServer::start(uint16_t port, uint16_t controlPort) logCallback); this->controlServer->start(); - this->serverThread = std::thread([this]() { this->ioContext.run(); }); + this->serverThread = std::thread([this]() + { + this->ioContext.run(); + LOG_I("Websocket streaming server thread finished"); + }); + + serverRunning = true; } void StreamingServer::stop() { + if (!serverRunning) + return; + ioContext.stop(); - if (!serverThread.joinable()) - return; + if (this->server) + this->server->stop(); + if (this->controlServer) + this->controlServer->stop(); + + if (serverThread.get_id() != std::this_thread::get_id()) + { + if (!serverThread.joinable()) + { + LOG_W("Websocket streaming server thread is not joinable"); + } + else + { + serverThread.join(); + LOG_I("Websocket streaming server thread joined"); + } + } + else + { + LOG_C("Websocket streaming server thread cannot join itself"); + } + serverRunning = false; - this->server->stop(); - serverThread.join(); this->server.reset(); + this->controlServer.reset(); } void StreamingServer::onAccept(const OnAcceptCallback& callback) @@ -95,7 +125,7 @@ void StreamingServer::unicastPacket(const std::string& streamId, { auto signals = clientIt->second.second; if (auto signalIt = signals.find(streamId); signalIt != signals.end()) - signalIt->second->write(packet); + signalIt->second->writeDaqPacket(packet); } } @@ -106,7 +136,7 @@ void StreamingServer::broadcastPacket(const std::string& signalId, const PacketP auto signals = client.second; if (auto signalIter = signals.find(signalId); signalIter != signals.end()) { - signalIter->second->write(packet); + signalIter->second->writeDaqPacket(packet); } } } @@ -119,7 +149,78 @@ void StreamingServer::sendPacketToSubscribers(const std::string& signalId, const if (auto signalIter = signals.find(signalId); signalIter != signals.end()) { if (signalIter->second->isSubscribed()) - signalIter->second->write(packet); + signalIter->second->writeDaqPacket(packet); + } + } +} + +DataRuleType StreamingServer::getDomainSignalRuleType(const SignalPtr& domainSignal) +{ + auto descriptor = domainSignal.getDescriptor(); + if (!descriptor.assigned() || !descriptor.getRule().assigned()) + { + throw InvalidParameterException("Unknown domain signal rule"); + } + return descriptor.getRule().getType(); +} + +void StreamingServer::addToOutputSignals(const SignalPtr& signal, + SignalMap& outputSignals, + const StreamWriterPtr& writer) +{ + auto createOutputDomainSignal = [&writer, this](const SignalPtr& domainSignal) + -> std::shared_ptr + { + auto tableId = domainSignal.getGlobalId(); + const auto domainRuleType = getDomainSignalRuleType(domainSignal); + + if (domainRuleType == DataRuleType::Linear) + { + return std::make_shared(writer, domainSignal, tableId, logCallback); + } + else + { + throw InvalidParameterException("Unsupported domain signal rule type"); + } + }; + + auto domainSignal = signal.getDomainSignal(); + if (domainSignal.assigned()) + { + auto domainSignalId = domainSignal.getGlobalId(); + + OutputDomainSignaBaselPtr outputDomainSignal; + if (const auto& outputSignalIt = outputSignals.find(domainSignalId); outputSignalIt != outputSignals.end()) + { + outputDomainSignal = std::dynamic_pointer_cast(outputSignalIt->second); + if (!outputDomainSignal) + throw NoInterfaceException("Registered output signal {} is not of domain type", domainSignalId); + } + else + { + outputDomainSignal = createOutputDomainSignal(domainSignal); + outputSignals.insert({domainSignalId, outputDomainSignal}); + } + + auto tableId = domainSignalId; + + const auto domainRuleType = getDomainSignalRuleType(domainSignal); + if (domainRuleType == DataRuleType::Linear) + { + auto outputValueSignal = + std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); + outputSignals.insert({signal.getGlobalId(), outputValueSignal}); + } + else + { + throw InvalidParameterException("Unsupported domain signal rule type"); + } + } + else + { + if (const auto& outputSignalIt = outputSignals.find(signal.getGlobalId()); outputSignalIt == outputSignals.end()) + { + outputSignals.insert({signal.getGlobalId(), createOutputDomainSignal(signal)}); } } } @@ -135,26 +236,21 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) if (onAcceptCallback) signals = onAcceptCallback(writer); - auto outputSignals = std::unordered_map(); + auto outputSignals = std::unordered_map(); for (const auto& signal : signals) - { + { if (!signal.getPublic()) continue; filteredSignals.pushBack(signal); - - // TODO: We skip domain signals for now. - if (!signal.getDomainSignal().assigned()) - continue; try { - const auto outputSignal = std::make_shared(writer, signal, logCallback); - outputSignals.insert({signal.getGlobalId(), outputSignal}); + addToOutputSignals(signal, outputSignals, writer); } - catch (const DaqException&) + catch (const DaqException& e) { - LOG_W("Failed to create an ouput websocket signal."); + LOG_W("Failed to create an output websocket signal: {}", e.what()); } } @@ -274,22 +370,28 @@ bool StreamingServer::isSignalSubscribed(const std::string& signalId) const return result; } -void StreamingServer::subscribeHandler(const std::string& signalId, OutputSignalPtr signal) +void StreamingServer::subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) { // wasn't subscribed by any client - if (!isSignalSubscribed(signalId) && onSubscribeCallback) - onSubscribeCallback(signal->getCoreSignal()); + if (!isSignalSubscribed(signalId)) + { + if (signal->isDataSignal() && onSubscribeCallback) + onSubscribeCallback(signal->getDaqSignal()); + } signal->setSubscribed(true); } -void StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSignalPtr signal) +void StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) { signal->setSubscribed(false); // became not subscribed by any client - if (!isSignalSubscribed(signalId) && onUnsubscribeCallback) - onUnsubscribeCallback(signal->getCoreSignal()); + if (!isSignalSubscribed(signalId)) + { + if (signal->isDataSignal() && onUnsubscribeCallback) + onUnsubscribeCallback(signal->getDaqSignal()); + } } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 0f02fbc..3368006 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -63,11 +63,13 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() }; streamingClient->onSignalUpdated(signalUpdatedCallback); - auto domainDescriptorCallback = [this](const StringPtr& signalId, const DataDescriptorPtr& domainDescriptor) + auto domainSignalInitCallback = [this](const StringPtr& dataSignalId, + const StringPtr& domainSignalId, + const DataDescriptorPtr& domainDescriptor) { - this->onDomainDescriptor(signalId, domainDescriptor); + this->onDomainSignalInit(dataSignalId, domainSignalId, domainDescriptor); }; - streamingClient->onDomainDescriptor(domainDescriptorCallback); + streamingClient->onDomainSingalInit(domainSignalInitCallback); auto availableSignalsCallback = [this](const std::vector& signalIds) { @@ -104,15 +106,31 @@ void WebsocketClientDeviceImpl::onSignalUpdated(const StringPtr& signalId, const updateSignalProperties(signalIt->second, sInfo); } -void WebsocketClientDeviceImpl::onDomainDescriptor(const StringPtr& signalId, - const DataDescriptorPtr& domainDescriptor) +void WebsocketClientDeviceImpl::onDomainSignalInit(const StringPtr& signalId, + const StringPtr& domainSignalId, + const DataDescriptorPtr& domainDescriptor) { if (!domainDescriptor.assigned()) return; - // Sets domain descriptor for data signal - if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) - signalIt->second.asPtr()->createAndAssignDomainSignal(domainDescriptor); + // Sets domain signal for data signal + if (auto dataSignalIt = deviceSignals.find(signalId); dataSignalIt != deviceSignals.end()) + { + auto domainSignalIt = deviceSignals.find(domainSignalId); + if (domainSignalIt == deviceSignals.end()) + { + // domain signal is not found in device because was not published as available by server + dataSignalIt->second.asPtr()->createAndAssignDomainSignal(domainDescriptor); + } + else + { + // domain signal is found in device + auto domainSignal = domainSignalIt->second; + auto dataSignal = dataSignalIt->second; + domainSignal.asPtr().setMirroredDataDescriptor(domainDescriptor); + dataSignal.asPtr().setMirroredDomainSignal(domainSignal); + } + } } void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector& signalIds) @@ -120,8 +138,8 @@ void WebsocketClientDeviceImpl::createDeviceSignals(const std::vectorcontext, this->signals, signalId); this->addSignal(signal); deviceSignals.insert({signalId, signal}); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp deleted file mode 100644 index e3f0ade..0000000 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_init.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace daq::streaming_protocol; - -void daqInitStreamingLibrary() -{ - auto writer = StreamWriter(nullptr); - auto dummySignal = SynchronousSignal("id", 100, 1000, writer, Logging::logCallback()); -} diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index ebe647a..c36285d 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -3,8 +3,6 @@ set(TEST_APP test_${MODULE_NAME}) add_executable(${TEST_APP} streaming_test_helpers.h - mock_streaming_server.h - mock_streaming_server.cpp test_signal_descriptor_converter.cpp test_streaming.cpp test_websocket_client_device.cpp diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp b/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp deleted file mode 100644 index d362581..0000000 --- a/shared/libraries/websocket_streaming/tests/mock_streaming_server.cpp +++ /dev/null @@ -1,356 +0,0 @@ -#include - -#include -#include -#include - -#include "mock_streaming_server.h" -#include -#include -#include - -#include -#include - -namespace streaming_test_helpers -{ - -using namespace daq; -using namespace daq::streaming_protocol; -using namespace daq::stream; - -MockStreamingServer::MockStreamingServer(const ContextPtr& context) - : work(ioContext.get_executor()) - , logger(context.getLogger()) -{ - loggerComponent = this->logger.getOrAddComponent("MockStreamingServer"); - logCallback = [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { - this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); - }; -} - -MockStreamingServer::~MockStreamingServer() -{ - stop(); - logger.removeComponent("MockStreamingServer"); -} - -void MockStreamingServer::start(uint16_t port, uint16_t controlPort) -{ - this->port = port; - - ioContext.restart(); - - auto acceptFunc = [this](StreamPtr stream) { this->onAcceptInternal(stream); }; - - this->server = std::make_unique(ioContext, acceptFunc, port); - this->server->start(); - - auto controlCommandCb = [this](const std::string& streamId, - const std::string& command, - const daq::streaming_protocol::SignalIds& signalIds, - std::string& errorMessage) - { - return onControlCommand(streamId, command, signalIds, errorMessage); - }; - this->controlServer = - std::make_unique(ioContext, - controlPort, - controlCommandCb, - logCallback); - this->controlServer->start(); - - this->serverThread = std::thread([this]() { this->ioContext.run(); }); -} - -void MockStreamingServer::stop() -{ - ioContext.stop(); - - if (!serverThread.joinable()) - return; - - this->server->stop(); - serverThread.join(); - this->server.reset(); -} - -void MockStreamingServer::onAccept(const OnAcceptCallback& callback) -{ - onAcceptCallback = callback; -} - -void MockStreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) -{ - auto writer = std::make_shared(stream); - writeProtocolInfo(writer); - writeInit(writer); - - auto signals = List(); - if (onAcceptCallback) - signals = onAcceptCallback(); - - auto outputSignals = std::unordered_map>(); - SizeT signalNumber = 0; - for (const auto& signal : signals) - { - bool isDomainSignal = !signal.getDomainSignal().assigned(); - auto descriptor = signal.getDescriptor(); - - if (!descriptor.assigned() || - !isDomainSignal && descriptor.getSampleType() != daq::SampleType::Float64 || - !isDomainSignal && descriptor.getRule().getType() != daq::DataRuleType::Explicit || - isDomainSignal && descriptor.getSampleType() != daq::SampleType::UInt64 || - isDomainSignal && descriptor.getRule().getType() != daq::DataRuleType::Linear) - { - LOG_E("Unsupported type of signal {}; data signal should be explicit float64, " - "domain signal linear unsigned64", signal.getGlobalId()); - throw GeneralErrorException("Unsupported type of signal"); - } - outputSignals.insert({signal.getGlobalId().toStdString(), {++signalNumber, signal}}); - } - - LOG_I("New client connected. Stream Id: {}", writer->id()); - clients.insert({writer->id(), {writer, outputSignals}}); - - writeSignalsAvailable(writer, signals); -} - -int MockStreamingServer::onControlCommand(const std::string& streamId, - const std::string& command, - const daq::streaming_protocol::SignalIds& signalIds, - std::string& errorMessage) -{ - if (signalIds.empty()) - { - LOG_W("Signal list is empty, reject command", streamId); - errorMessage = "Signal list is empty"; - return -1; - } - - auto clientIter = clients.find(streamId); - if (clientIter == std::end(clients)) - { - LOG_W("Unknown streamId: {}, reject command", streamId); - errorMessage = "Unknown streamId: '" + streamId + "'"; - return -1; - } - - auto writer = clientIter->second.first; - auto signalMap = clientIter->second.second; - - if (command == "subscribe") - { - subscribeSignals(signalIds, signalMap, writer); - } - else if (command == "unsubscribe") - { - unsubscribeSignals(signalIds, signalMap, writer); - } - else - { - LOG_W("Unknown control command: {}", command); - errorMessage = "Unknown command: " + command; - return -1; - } - - size_t unknownSignalsCount = 0; - std::string message = "Command '" + command + "' failed for unknown signals:\n"; - for (const auto& signalId : signalIds) - { - if (auto signalMapIter = signalMap.find(signalId); signalMapIter == signalMap.end()) - { - unknownSignalsCount++; - message.append(signalId + "\n"); - } - } - if (unknownSignalsCount > 0) - { - LOG_W("{}", message); - errorMessage = message; - return -1; - } - - return 0; -} - -void MockStreamingServer::subscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, - const SignalMap& signalMap, - const daq::streaming_protocol::StreamWriterPtr& writer) -{ - for (const auto& signalId : signalIds) - { - if (auto signalMapIter = signalMap.find(signalId); signalMapIter != signalMap.end()) - { - auto signalNumber = signalMapIter->second.first; - auto signalPtr = signalMapIter->second.second; - - if (!signalPtr.hasProperty("tableId")) - throw GeneralErrorException("Unknown table id of signal"); - - std::string tableId = signalPtr.getPropertyValue("tableId").asPtr().toStdString(); - - if (!signalPtr.getDomainSignal().assigned()) - { - // considered as domain signal - writeSignalSubscribe(signalId, signalNumber, writer); - writeTimeSignalMeta(tableId, signalNumber, signalPtr, writer); - } - else - { - // considered as data signal - writeSignalSubscribe(signalId, signalNumber, writer); - writeDataSignalMeta(tableId, signalNumber, signalPtr, writer); - - auto domainSignal = signalPtr.getDomainSignal(); - std::string domainSignalId = domainSignal.getGlobalId().toStdString(); - if (auto domainSignalMapIter = signalMap.find(domainSignalId); - domainSignalMapIter == signalMap.end()) - { - throw GeneralErrorException("Domain signal should be registered as available"); - } - } - } - } -} - -void MockStreamingServer::unsubscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, - const SignalMap& signalMap, - const daq::streaming_protocol::StreamWriterPtr& writer) -{ - for (const auto& signalId : signalIds) - { - if (auto signalMapIter = signalMap.find(signalId); signalMapIter != signalMap.end()) - { - auto signalNumber = signalMapIter->second.first; - auto signalPtr = signalMapIter->second.second; - - writeSignalUnsubscribe(signalId, signalNumber, writer); - } - } -} - -void MockStreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) -{ - nlohmann::json msg; - msg[METHOD] = META_METHOD_APIVERSION; - msg[PARAMS][VERSION] = OPENDAQ_LT_STREAM_VERSION; - writer->writeMetaInformation(0, msg); -} - -void MockStreamingServer::writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, - const ListPtr& signals) -{ - std::vector signalIds; - - for (const auto& signal : signals) - signalIds.push_back(signal.getGlobalId()); - - nlohmann::json msg; - msg[METHOD] = META_METHOD_AVAILABLE; - msg[PARAMS][META_SIGNALIDS] = signalIds; - writer->writeMetaInformation(0, msg); -} - -void MockStreamingServer::writeInit(const streaming_protocol::StreamWriterPtr& writer) -{ - nlohmann::json initMeta; - initMeta[METHOD] = META_METHOD_INIT; - initMeta[PARAMS][META_STREAMID] = writer->id(); - - nlohmann::json jsonRpcHttp; - jsonRpcHttp["httpMethod"] = "POST"; - jsonRpcHttp["httpPath"] = "/"; - jsonRpcHttp["httpVersion"] = "1.1"; - jsonRpcHttp["port"] = std::to_string(controlServer->getPort()); - - nlohmann::json commandInterfaces; - commandInterfaces["jsonrpc-http"] = jsonRpcHttp; - - initMeta[PARAMS][COMMANDINTERFACES] = commandInterfaces; - writer->writeMetaInformation(0, initMeta); -} - -void MockStreamingServer::writeSignalSubscribe(const std::string& signalId, - SizeT signalNumber, - const streaming_protocol::StreamWriterPtr& writer) -{ - nlohmann::json subscribeData; - subscribeData[METHOD] = META_METHOD_SUBSCRIBE; - subscribeData[PARAMS][META_SIGNALID] = signalId; - int result = writer->writeMetaInformation(signalNumber, subscribeData); - if (result < 0) - throw GeneralErrorException("Write Subscribe Meta Information failure"); -} - -void MockStreamingServer::writeDataSignalMeta(const std::string& tableId, - SizeT signalNumber, - const SignalPtr& signal, - const streaming_protocol::StreamWriterPtr& writer) -{ - nlohmann::json dataSignal; - dataSignal[METHOD] = META_METHOD_SIGNAL; - dataSignal[PARAMS][META_TABLEID] = tableId; - - dataSignal[PARAMS][META_DEFINITION][META_NAME] = signal.getName(); - dataSignal[PARAMS][META_DEFINITION][META_DATATYPE] = DATA_TYPE_REAL64; - dataSignal[PARAMS][META_DEFINITION][META_RULE] = META_RULETYPE_EXPLICIT; - - nlohmann::json interpretationObject; - websocket_streaming::SignalDescriptorConverter::EncodeInterpretationObject( - signal.getDescriptor(), interpretationObject); - dataSignal[PARAMS][META_INTERPRETATION] = interpretationObject; - - int result = writer->writeMetaInformation(signalNumber, dataSignal); - if (result < 0) - throw GeneralErrorException("Write Data Signal Meta Information failure"); -} - -void MockStreamingServer::writeTimeSignalMeta(const std::string& tableId, - SizeT signalNumber, - const SignalPtr& signal, - const streaming_protocol::StreamWriterPtr& writer) -{ - auto descriptor = signal.getDescriptor(); - auto resolution = descriptor.getTickResolution(); - - nlohmann::json timeSignal; - timeSignal[METHOD] = META_METHOD_SIGNAL; - timeSignal[PARAMS][META_TABLEID] = tableId; - timeSignal[PARAMS][META_DEFINITION][META_NAME] = signal.getName(); - timeSignal[PARAMS][META_DEFINITION][META_RULE] = META_RULETYPE_LINEAR; - - timeSignal[PARAMS][META_DEFINITION][META_RULETYPE_LINEAR][META_DELTA] = - (uint64_t)descriptor.getRule().getParameters().get("delta"); - timeSignal[PARAMS][META_DEFINITION][META_DATATYPE] = DATA_TYPE_UINT64; - - timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_UNIT_ID] = daq::streaming_protocol::Unit::UNIT_ID_SECONDS; - timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_DISPLAY_NAME] = "s"; - timeSignal[PARAMS][META_DEFINITION][META_UNIT][META_QUANTITY] = META_TIME; - timeSignal[PARAMS][META_DEFINITION][META_ABSOLUTE_REFERENCE] = UNIX_EPOCH; - timeSignal[PARAMS][META_DEFINITION][META_RESOLUTION][META_NUMERATOR] = 1; - timeSignal[PARAMS][META_DEFINITION][META_RESOLUTION][META_DENOMINATOR] = - resolution.getDenominator() / resolution.getNumerator(); - - nlohmann::json interpretationObject; - websocket_streaming::SignalDescriptorConverter::EncodeInterpretationObject( - descriptor, interpretationObject); - timeSignal[PARAMS][META_INTERPRETATION] = interpretationObject; - - int result = writer->writeMetaInformation(signalNumber, timeSignal); - if (result < 0) - throw GeneralErrorException("Write Time Signal Meta Information failure"); -} - -void MockStreamingServer::writeSignalUnsubscribe(const std::string& signalId, - SizeT signalNumber, - const streaming_protocol::StreamWriterPtr& writer) -{ - nlohmann::json unsubscribe; - unsubscribe[METHOD] = META_METHOD_UNSUBSCRIBE; - int result = writer->writeMetaInformation(signalNumber, unsubscribe); - if (result < 0) - throw GeneralErrorException("Write Unsubscribe Meta Information failure"); -} - -} diff --git a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h b/shared/libraries/websocket_streaming/tests/mock_streaming_server.h deleted file mode 100644 index b68a4e3..0000000 --- a/shared/libraries/websocket_streaming/tests/mock_streaming_server.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022-2024 Blueberry d.o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include "websocket_streaming/websocket_streaming.h" -#include "stream/WebsocketServer.hpp" -#include "websocket_streaming/output_signal.h" -#include "streaming_protocol/StreamWriter.h" -#include "streaming_protocol/ControlServer.hpp" -#include "streaming_protocol/Logging.hpp" -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace streaming_test_helpers -{ - -/// This alternative implementation of the Streaming Server is designed for testing purposes. -/// It is intended to bypass the `daq::streaming_protocol::BaseSignal` library interface, -/// preventing the generation of artificial time signals and enabling the sharing of the same -/// domain signal among multiple data signals. -/// -/// Limitations and requirements: -/// - It does not implement the transfer of signal data; only the publication of -/// signal meta-information is supported. -/// - To simplify the implementation, it exclusively handles data signals -/// with explicit rule and a data type of float64, along with domain signals -/// having a linear rule and unsigned64 type. -/// - The domain signal must be published as available. -/// - The `tableId` for the signal is set using the StringProperty named "tableId," -/// and it is expected that all published signals have this property. - -class MockStreamingServer; -using MockStreamingServerPtr = std::shared_ptr; - -class MockStreamingServer -{ -public: - using OnAcceptCallback = std::function()>; - - MockStreamingServer(const daq::ContextPtr& context); - ~MockStreamingServer(); - - void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT, - uint16_t controlPort = daq::streaming_protocol::HTTP_CONTROL_PORT); - void stop(); - void onAccept(const OnAcceptCallback& callback); - -protected: - using SignalMap = std::unordered_map>; - using ClientMap = std::unordered_map>; - - void onAcceptInternal(const daq::stream::StreamPtr& stream); - void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); - void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, - const daq::ListPtr& signals); - void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); - - void writeSignalSubscribe(const std::string& signalId, - daq::SizeT signalNumber, - const daq::streaming_protocol::StreamWriterPtr& writer); - void writeSignalUnsubscribe(const std::string& signalId, - daq::SizeT signalNumber, - const daq::streaming_protocol::StreamWriterPtr& writer); - - void writeDataSignalMeta(const std::string& tableId, - daq::SizeT signalNumber, - const daq::SignalPtr& signal, - const daq::streaming_protocol::StreamWriterPtr& writer); - void writeTimeSignalMeta(const std::string& tableId, - daq::SizeT signalNumber, - const daq::SignalPtr& signal, - const daq::streaming_protocol::StreamWriterPtr& writer); - - int onControlCommand(const std::string& streamId, - const std::string& command, - const daq::streaming_protocol::SignalIds& signalIds, - std::string& errorMessage); - void subscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, - const SignalMap& signalMap, - const daq::streaming_protocol::StreamWriterPtr& writer); - void unsubscribeSignals(const daq::streaming_protocol::SignalIds& signalIds, - const SignalMap& signalMap, - const daq::streaming_protocol::StreamWriterPtr& writer); - - void writeTime(daq::SizeT signalNumber, - uint64_t timeTicks, - const daq::streaming_protocol::StreamWriterPtr& writer); - - uint16_t port; - boost::asio::io_context ioContext; - boost::asio::executor_work_guard work; - daq::stream::WebsocketServerUniquePtr server; - std::unique_ptr controlServer; - std::thread serverThread; - ClientMap clients; - OnAcceptCallback onAcceptCallback; - - daq::LoggerPtr logger; - daq::LoggerComponentPtr loggerComponent; - daq::streaming_protocol::LogCallback logCallback; -}; - -} diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 2bc1596..c9e46a0 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -50,8 +50,7 @@ namespace streaming_test_helpers return instance; } - inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx, - const daq::StringPtr& tableId = "TestTable") + inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx) { const size_t nanosecondsInSecond = 1000000000; auto delta = nanosecondsInSecond / 1000; @@ -61,12 +60,14 @@ namespace streaming_test_helpers .setRule(daq::LinearDataRule(delta, 100)) .setTickResolution(daq::Ratio(1, nanosecondsInSecond)) .setOrigin(daq::streaming_protocol::UNIX_EPOCH_DATE_UTC_TIME) - .setUnit(daq::Unit("s", daq::streaming_protocol::Unit::UNIT_ID_SECONDS)) + .setUnit(daq::Unit("s", + daq::streaming_protocol::Unit::UNIT_ID_SECONDS, + "seconds", + "time")) .setName("Time") .build(); auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); - signal.addProperty(daq::StringProperty("tableId", tableId)); return signal; } @@ -118,8 +119,7 @@ namespace streaming_test_helpers inline daq::SignalPtr createTestSignalWithDomain(const daq::ContextPtr& ctx, const daq::StringPtr& name, - const daq::SignalPtr& domainSignal, - const daq::StringPtr& tableId = "TestTable") + const daq::SignalPtr& domainSignal) { auto meta = daq::Dict(); meta["color"] = "green"; @@ -140,7 +140,6 @@ namespace streaming_test_helpers signal.setDomainSignal(domainSignal); signal.setName(name); signal.setDescription("TestDescription"); - signal.addProperty(daq::StringProperty("tableId", tableId)); return signal; } diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 6605b71..b70b785 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -7,6 +7,7 @@ #include "streaming_protocol/Defines.h" #include "streaming_protocol/SubscribedSignal.hpp" #include "streaming_protocol/SynchronousSignal.hpp" +#include "streaming_protocol/LinearTimeSignal.hpp" #include "streaming_protocol/iWriter.hpp" #include #include @@ -58,10 +59,10 @@ class DummayWriter : public bsp::iWriter SignalPtr createTimeSignal(const ContextPtr& ctx, uint64_t timeTicksPerSecond = TimeTicksPerSecond) { uint64_t timeTickStart = 10000000; - uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), timeTicksPerSecond); + uint64_t outputRateInTicks = bsp::BaseDomainSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), timeTicksPerSecond); auto descriptor = DataDescriptorBuilder() - .setSampleType(SampleType::Float64) + .setSampleType(SampleType::UInt64) .setRule(LinearDataRule(outputRateInTicks, timeTickStart)) .setTickResolution(Ratio(1, timeTicksPerSecond)) .setName("Time") @@ -137,40 +138,46 @@ SignalPtr createStepSignal(const ContextPtr& ctx) TEST(SignalConverter, synchronousSignal) { auto signal = createSineSignal(NullContext()); - auto domainDescriptor = signal.getDomainSignal().getDescriptor(); auto dataDescriptor = signal.getDescriptor(); - auto start = domainDescriptor.getRule().getParameters().get("start"); auto unit = dataDescriptor.getUnit(); + auto domainSignal = signal.getDomainSignal(); + auto domainDescriptor = domainSignal.getDescriptor(); + DummayWriter dummyWriter; // 1kHz - uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); - auto syncSigna1 = std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); - syncSigna1->setTimeStart(start); - - SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); - ASSERT_EQ(syncSigna1->getTimeDelta(), outputRateInTicks); - ASSERT_EQ(syncSigna1->getTimeStart(), start); - ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); - ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); - ASSERT_EQ(syncSigna1->getMemberName(), signal.getName()); + uint64_t outputRateInTicks = bsp::BaseDomainSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); + auto outputRateInNs = std::chrono::milliseconds(1); + auto syncSignal = std::make_shared>(signal.getGlobalId(), "table1", dummyWriter, bsp::Logging::logCallback()); + auto timeSignal = std::make_shared(domainSignal.getGlobalId(), "table1", TimeTicksPerSecond, outputRateInNs, dummyWriter, bsp::Logging::logCallback()); + + ASSERT_NO_THROW(SignalDescriptorConverter::ToStreamedValueSignal(signal, syncSignal, SignalProps{})); + ASSERT_EQ(syncSignal->getUnitId(), unit.getId()); + ASSERT_EQ(syncSignal->getUnitDisplayName(), unit.getSymbol()); + ASSERT_EQ(syncSignal->getMemberName(), signal.getName()); + ASSERT_EQ(syncSignal->getTableId(), "table1"); + + ASSERT_NO_THROW(SignalDescriptorConverter::ToStreamedLinearSignal(domainSignal, timeSignal, SignalProps{})); + ASSERT_EQ(timeSignal->getTimeDelta(), outputRateInTicks); + ASSERT_EQ(timeSignal->getTableId(), "table1"); + + auto start = domainDescriptor.getRule().getParameters().get("start"); + timeSignal->setTimeStart(start); + ASSERT_EQ(timeSignal->getTimeStart(), start); } TEST(SignalConverter, TickResolution) { - auto signal = createSineSignal(NullContext()); - auto domainDescriptor = signal.getDomainSignal().getDescriptor(); - auto start = domainDescriptor.getRule().getParameters().get("start"); + auto domainSignal = createTimeSignal(NullContext(), TimeTicksPerSecond); + auto domainDescriptor = domainSignal.getDescriptor(); DummayWriter dummyWriter; // 1kHz - uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); - auto syncSigna1 = - std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); - syncSigna1->setTimeStart(start); + auto outputRateInNs = std::chrono::milliseconds(1); + auto timeSignal = std::make_shared(domainSignal.getGlobalId(), "table1", TimeTicksPerSecond, outputRateInNs, dummyWriter, bsp::Logging::logCallback()); - SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); - syncSigna1->writeSignalMetaInformation(); + ASSERT_NO_THROW(SignalDescriptorConverter::ToStreamedLinearSignal(domainSignal, timeSignal, SignalProps{})); + timeSignal->writeSignalMetaInformation(); auto getTickResolution = [](const nlohmann::json& data) -> RatioPtr { using namespace daq::streaming_protocol; @@ -182,10 +189,9 @@ TEST(SignalConverter, TickResolution) ASSERT_EQ(getTickResolution(lastMetaInformation.second), Ratio(1, TimeTicksPerSecond)); auto newDomainSignal = createTimeSignal(NullContext(), 1000); - signal.asPtr(true).setDomainSignal(newDomainSignal); - SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); - syncSigna1->writeSignalMetaInformation(); + ASSERT_NO_THROW(SignalDescriptorConverter::ToStreamedLinearSignal(newDomainSignal, timeSignal, SignalProps{})); + timeSignal->writeSignalMetaInformation(); lastMetaInformation = *(--dummyWriter.metaInformations.end()); ASSERT_EQ(getTickResolution(lastMetaInformation.second), Ratio(1, 1000)); @@ -194,23 +200,17 @@ TEST(SignalConverter, TickResolution) TEST(SignalConverter, synchronousSignalWithPostScaling) { auto signal = createSineSignalWithPostScaling(NullContext()); - auto domainDescriptor = signal.getDomainSignal().getDescriptor(); auto valueDescriptor = signal.getDescriptor(); - auto start = domainDescriptor.getRule().getParameters().get("start"); auto unit = valueDescriptor.getUnit(); DummayWriter dummyWriter; // 1kHz - uint64_t outputRateInTicks = bsp::BaseSignal::timeTicksFromNanoseconds(std::chrono::milliseconds(1), TimeTicksPerSecond); - auto syncSigna1 = std::make_shared>(signal.getGlobalId(), outputRateInTicks, TimeTicksPerSecond, dummyWriter, bsp::Logging::logCallback()); - syncSigna1->setTimeStart(start); - - SignalDescriptorConverter::ToStreamedSignal(signal, syncSigna1, SignalProps{}); - ASSERT_EQ(syncSigna1->getTimeDelta(), outputRateInTicks); - ASSERT_EQ(syncSigna1->getTimeStart(), start); - ASSERT_EQ(syncSigna1->getUnitId(), unit.getId()); - ASSERT_EQ(syncSigna1->getUnitDisplayName(), unit.getSymbol()); - ASSERT_EQ(syncSigna1->getMemberName(), signal.getName()); + auto syncSignal = std::make_shared>(signal.getGlobalId(), "table1", dummyWriter, bsp::Logging::logCallback()); + + ASSERT_NO_THROW(SignalDescriptorConverter::ToStreamedValueSignal(signal, syncSignal, SignalProps{})); + ASSERT_EQ(syncSignal->getUnitId(), unit.getId()); + ASSERT_EQ(syncSignal->getUnitDisplayName(), unit.getSymbol()); + ASSERT_EQ(syncSignal->getMemberName(), signal.getName()); } TEST(SignalConverter, subscribedDataSignal) diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index ed0a1d9..c4fd2b9 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -107,6 +107,7 @@ TEST_F(StreamingTest, Subscription) server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); signals.pushBack(testDoubleSignal); + signals.pushBack(testDoubleSignal.getDomainSignal()); return signals; }); server->start(StreamingPort, ControlPort); @@ -150,6 +151,7 @@ TEST_F(StreamingTest, SimpePacket) server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); signals.pushBack(testDoubleSignal); + signals.pushBack(testDoubleSignal.getDomainSignal()); return signals; }); server->start(StreamingPort, ControlPort); @@ -181,7 +183,7 @@ TEST_F(StreamingTest, SimpePacket) ASSERT_TRUE(client.isConnected()); client.subscribeSignals({testDoubleSignal.getGlobalId()}); - ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::seconds(5)), std::future_status::ready); std::string signalId = testDoubleSignal.getGlobalId(); server->sendPacketToSubscribers(signalId, packet); diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index cae2c7e..b202a98 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -6,7 +6,6 @@ #include #include #include "streaming_test_helpers.h" -#include "mock_streaming_server.h" using namespace daq; using namespace std::chrono_literals; @@ -71,7 +70,7 @@ TEST_F(WebsocketClientDeviceTest, DeviceInfo) ASSERT_EQ(clientDeviceInfo.getConnectionString(), HOST); } -TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) +TEST_F(WebsocketClientDeviceTest, SignalWithDomain) { // Create server signal auto testSignal = streaming_test_helpers::createTestSignal(context); @@ -81,6 +80,7 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); signals.pushBack(testSignal); + signals.pushBack(testSignal.getDomainSignal()); return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); @@ -89,10 +89,13 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); // Check the mirrored signal - ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); ASSERT_TRUE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + ASSERT_FALSE(clientDevice.getSignals()[1].getDomainSignal().assigned()); + ASSERT_EQ(clientDevice.getSignals()[0].getDomainSignal(), clientDevice.getSignals()[1]); + ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), testSignal.getDescriptor())); ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), @@ -125,7 +128,30 @@ TEST_F(WebsocketClientDeviceTest, SingleSignalWithDomain) testSignal.getDomainSignal().getDescriptor())); } -TEST_F(WebsocketClientDeviceTest, SingleSignalWithoutDomain) +TEST_F(WebsocketClientDeviceTest, SingleDomainSignal) +{ + // Create server signal + auto testSignal = streaming_test_helpers::createTimeSignal(context); + + // Setup and start server which will publish created signal + auto server = std::make_shared(context); + server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testSignal); + return signals; + }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // The mirrored signal exists and has descriptor + ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); + ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); + ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); +} + +TEST_F(WebsocketClientDeviceTest, SingleUnsupportedSignal) { // Create server signal auto testSignal = streaming_test_helpers::createTestSignalWithoutDomain(context); @@ -182,9 +208,8 @@ TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) auto dataSignal1 = streaming_test_helpers::createTestSignalWithDomain(context, "Data1", timeSignal); auto dataSignal2 = streaming_test_helpers::createTestSignalWithDomain(context, "Data2", timeSignal); - // Setup and start mock test server which will publish created signals - auto server = std::make_shared(context); - server->onAccept([&]() { + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); signals.pushBack(dataSignal1); signals.pushBack(timeSignal); @@ -220,4 +245,7 @@ TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[2].getDomainSignal().getDescriptor(), dataSignal2.getDomainSignal().getDescriptor())); ASSERT_EQ(clientDevice.getSignals()[2].getName(), "Data2"); + + ASSERT_EQ(clientDevice.getSignals()[2].getDomainSignal(), clientDevice.getSignals()[1]); + ASSERT_EQ(clientDevice.getSignals()[0].getDomainSignal(), clientDevice.getSignals()[1]); } From 117cd218be5eda3a744d807651c7625fde1a4bb7 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 27 Mar 2024 10:29:01 +0100 Subject: [PATCH 045/127] Fix mock signal generator: * use common absolute start time for all generated signals --- .../websocket_streaming/tests/test_signal_generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp index fc15d1b..2c8c9a6 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp @@ -102,7 +102,7 @@ TEST_F(SignalGeneratorTest, StepSignal) auto reader = PacketReader(signal); - auto generator = SignalGenerator(signal); + auto generator = SignalGenerator(signal, std::chrono::system_clock::now()); generator.setFunction(stepFunction10); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); @@ -134,7 +134,7 @@ TEST_F(SignalGeneratorTest, ChangeFunction) generator.setFunction(stepFunction100); }; - auto generator = SignalGenerator(signal); + auto generator = SignalGenerator(signal, std::chrono::system_clock::now()); generator.setFunction(stepFunction10); generator.setUpdateFunction(updateFunction); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); From 6b88b8865b0675af7cc254a1098dbd149fdebb05 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 27 Mar 2024 11:41:44 +0100 Subject: [PATCH 046/127] Reset mirrored descriptor on unsubscribe --- .../src/websocket_client_signal_impl.cpp | 8 ++------ .../websocket_streaming/tests/test_signal_generator.cpp | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index e748028..d9da576 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -46,16 +46,12 @@ void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptor SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() { - MirroredSignalConfigPtr sig; - checkErrorInfo(getMirroredDomainSignal(&sig)); - return sig; + return mirroredDomainSignal.addRefAndReturn(); } DataDescriptorPtr WebsocketClientSignalImpl::onGetDescriptor() { - DataDescriptorPtr desc; - checkErrorInfo(getMirroredDataDescriptor(&desc)); - return desc; + return mirroredDataDescriptor.addRefAndReturn(); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp index 2c8c9a6..f9b65a7 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp @@ -105,7 +105,7 @@ TEST_F(SignalGeneratorTest, StepSignal) auto generator = SignalGenerator(signal, std::chrono::system_clock::now()); generator.setFunction(stepFunction10); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); - generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); auto packets = reader.readAll(); ASSERT_EQ(packets.getCount(), 3u); @@ -138,7 +138,7 @@ TEST_F(SignalGeneratorTest, ChangeFunction) generator.setFunction(stepFunction10); generator.setUpdateFunction(updateFunction); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); - generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); auto packets = reader.readAll(); ASSERT_EQ(packets.getCount(), 3u); From 393973d6ae6dfb10738e35f4742314f74dbf77be Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Thu, 28 Mar 2024 13:40:28 +0100 Subject: [PATCH 047/127] Revert signal generator change --- .../websocket_streaming/tests/test_signal_generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp index f9b65a7..2c8c9a6 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp @@ -105,7 +105,7 @@ TEST_F(SignalGeneratorTest, StepSignal) auto generator = SignalGenerator(signal, std::chrono::system_clock::now()); generator.setFunction(stepFunction10); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); - generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); auto packets = reader.readAll(); ASSERT_EQ(packets.getCount(), 3u); @@ -138,7 +138,7 @@ TEST_F(SignalGeneratorTest, ChangeFunction) generator.setFunction(stepFunction10); generator.setUpdateFunction(updateFunction); generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); - generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); auto packets = reader.readAll(); ASSERT_EQ(packets.getCount(), 3u); From ee06bab5d4608e6d276b1cbe356d78da84c5c233 Mon Sep 17 00:00:00 2001 From: Dejan Crnila Date: Mon, 25 Mar 2024 15:41:53 +0100 Subject: [PATCH 048/127] Rework constant data rule --- .../websocket_streaming/src/signal_descriptor_converter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 63ba290..7f93148 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -174,8 +174,7 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco { case daq::streaming_protocol::RULETYPE_CONSTANT: { - uint64_t start = subscribedSignal.time(); - return ConstantDataRule(start); + return ConstantDataRule(); } break; case daq::streaming_protocol::RULETYPE_EXPLICIT: From 762167010549c1022b64be83c22022a17d750707 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Fri, 29 Mar 2024 09:18:36 +0100 Subject: [PATCH 049/127] Enable adding of unavailable signals to streaming: * move common parts of native and websocket streaming to core base implementation * keep track of signal subscription counter in core streaming implementation * store signal weak reference in streaming under remote or streaming Id depending on availability of signal * log warnings / errors upon adding / subscribing signal that is unavailable in streaming --- .../websocket_streaming_impl.h | 10 +-- .../src/websocket_streaming_impl.cpp | 78 ++----------------- 2 files changed, 11 insertions(+), 77 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 22bcf19..2701c51 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -34,20 +34,16 @@ class WebsocketStreamingImpl : public Streaming const ContextPtr& context); protected: void onSetActive(bool active) override; - StringPtr onGetSignalStreamingId(const StringPtr& signalRemoteId) override; void onAddSignal(const MirroredSignalConfigPtr& signal) override; void onRemoveSignal(const MirroredSignalConfigPtr& signal) override; - void onSubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& domainSignalRemoteId) override; - void onUnsubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& domainSignalRemoteId) override; - EventPacketPtr onCreateDataDescriptorChangedEventPacket(const StringPtr& signalRemoteId) override; + void onSubscribeSignal(const StringPtr& signalStreamingId) override; + void onUnsubscribeSignal(const StringPtr& signalStreamingId) override; + EventPacketPtr onCreateDataDescriptorChangedEventPacket(const StringPtr& signalStreamingId) override; void prepareStreamingClient(); - void handleEventPacket(const MirroredSignalConfigPtr& signal, const EventPacketPtr& eventPacket); - void onPacket(const StringPtr& signalId, const PacketPtr& packet); void onAvailableSignals(const std::vector& signalIds); daq::websocket_streaming::StreamingClientPtr streamingClient; - std::vector availableSignalIds; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index c3ac69c..8a40686 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -38,27 +38,18 @@ void WebsocketStreamingImpl::onRemoveSignal(const MirroredSignalConfigPtr& /*sig { } -void WebsocketStreamingImpl::onSubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& /*domainSignalRemoteId*/) +void WebsocketStreamingImpl::onSubscribeSignal(const StringPtr& signalStreamingId) { - auto signalStreamingId = onGetSignalStreamingId(signalRemoteId); - if (auto it = streamingSignalsRefs.find(signalStreamingId); it == streamingSignalsRefs.end()) - throw NotFoundException("Signal with id {} is not added to Websocket streaming", signalRemoteId); - streamingClient->subscribeSignals({signalStreamingId.toStdString()}); } -void WebsocketStreamingImpl::onUnsubscribeSignal(const StringPtr& signalRemoteId, const StringPtr& /*domainSignalRemoteId*/) +void WebsocketStreamingImpl::onUnsubscribeSignal(const StringPtr& signalStreamingId) { - auto signalStreamingId = onGetSignalStreamingId(signalRemoteId); - if (auto it = streamingSignalsRefs.find(signalStreamingId); it == streamingSignalsRefs.end()) - throw NotFoundException("Signal with id {} is not added to Websocket streaming", signalRemoteId); - streamingClient->unsubscribeSignals({signalStreamingId.toStdString()}); } -EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const StringPtr& signalRemoteId) +EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const StringPtr& signalStreamingId) { - StringPtr signalStreamingId = onGetSignalStreamingId(signalRemoteId); return streamingClient->getDataDescriptorChangedEventPacket(signalStreamingId); } @@ -78,73 +69,20 @@ void WebsocketStreamingImpl::prepareStreamingClient() auto signalSubscriptionAckCallback = [this](const std::string& signalStringId, bool subscribed) { - auto signalKey = String(signalStringId); - if (auto it = streamingSignalsRefs.find(signalKey); it != streamingSignalsRefs.end()) - { - auto signalRef = it->second; - MirroredSignalConfigPtr signal = signalRef.assigned() ? signalRef.getRef() : nullptr; - if (signal.assigned()) - { - if (subscribed) - signal.template asPtr().subscribeCompleted(connectionString); - else - signal.template asPtr().unsubscribeCompleted(connectionString); - } - } + this->triggerSubscribeAck(signalStringId, subscribed); }; streamingClient->onSubscriptionAck(signalSubscriptionAckCallback); } -void WebsocketStreamingImpl::handleEventPacket(const MirroredSignalConfigPtr& signal, const EventPacketPtr& eventPacket) -{ - Bool forwardPacket = signal.template asPtr().triggerEvent(eventPacket); - if (forwardPacket) - signal.sendPacket(eventPacket); -} - -void WebsocketStreamingImpl::onPacket(const StringPtr& signalId, const PacketPtr& packet) -{ - if (!packet.assigned() || !this->isActive) - return; - - if (auto it = streamingSignalsRefs.find(signalId); it != streamingSignalsRefs.end()) - { - auto signalRef = it->second; - MirroredSignalConfigPtr signal = signalRef.assigned() ? signalRef.getRef() : nullptr; - if (signal.assigned() && - signal.getStreamed() && - signal.getActiveStreamingSource() == connectionString) - { - const auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned()) - handleEventPacket(signal, eventPacket); - else - signal.sendPacket(packet); - } - } -} - void WebsocketStreamingImpl::onAvailableSignals(const std::vector& signalIds) { for (const auto& signalId : signalIds) - availableSignalIds.push_back(String(signalId)); -} - -StringPtr WebsocketStreamingImpl::onGetSignalStreamingId(const StringPtr& signalRemoteId) -{ - const auto it = std::find_if( - availableSignalIds.begin(), - availableSignalIds.end(), - [&signalRemoteId](const StringPtr& signalStreamingId) + { + auto signalStringId = String(signalId); { - return IdsParser::idEndsWith(signalRemoteId.toStdString(), signalStreamingId.toStdString()); + addToAvailableSignals(signalStringId); } - ); - - if (it != availableSignalIds.end()) - return *it; - else - throw NotFoundException("Signal with id {} is not available in Websocket streaming", signalRemoteId); + } } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 4e1c1694afe2933a71587ee121fa6099206adf4e Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 3 Apr 2024 13:38:05 +0200 Subject: [PATCH 050/127] Skip domain signal subscribing for websocket streaming-lt --- .../websocket_streaming/src/websocket_streaming_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 8a40686..7ed7ba6 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -18,7 +18,7 @@ WebsocketStreamingImpl::WebsocketStreamingImpl(const StringPtr& connectionString WebsocketStreamingImpl::WebsocketStreamingImpl(StreamingClientPtr streamingClient, const StringPtr& connectionString, const ContextPtr& context) - : Streaming(connectionString, context) + : Streaming(connectionString, context, true) , streamingClient(streamingClient) { prepareStreamingClient(); From d8b037c6cb7b9758fbf62f2a4959c3b5f6453edd Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Thu, 4 Apr 2024 11:55:49 +0200 Subject: [PATCH 051/127] Forward only changed descriptors in streaming LT --- .../websocket_streaming/input_signal.h | 2 +- .../websocket_streaming/streaming_client.h | 2 +- .../websocket_streaming/src/input_signal.cpp | 7 +++++-- .../src/streaming_client.cpp | 20 ++++++++++--------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index f74c418..9c219ed 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -33,7 +33,7 @@ class InputSignal InputSignal(); PacketPtr createDataPacket(uint64_t packetOffset, const uint8_t* data, size_t size) const; - EventPacketPtr createDecriptorChangedPacket() const; + EventPacketPtr createDecriptorChangedPacket(bool valueChanged = true, bool domainChanged = true) const; void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); void setDomainDescriptor(const DataDescriptorPtr& domainDescriptor); bool hasDescriptors() const; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 0955bef..0579b5b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -83,7 +83,7 @@ class StreamingClient void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size); void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - void publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal); + void publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal, bool valueChanged, bool domainChanged); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); void setSignalInitSatisfied(const std::string& signalId); void setDomainIdAndDescriptor(const std::string& dataSignalId, diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 5c2347d..156a566 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -31,10 +31,13 @@ PacketPtr InputSignal::createDataPacket(uint64_t packetOffset, const uint8_t* da return dataPacket; } -EventPacketPtr InputSignal::createDecriptorChangedPacket() const +EventPacketPtr InputSignal::createDecriptorChangedPacket(bool valueChanged, bool domainChanged) const { std::scoped_lock lock(descriptorsSync); - return DataDescriptorChangedEventPacket(currentDataDescriptor, currentDomainDataDescriptor); + const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; + const auto domainDesc = domainChanged ? currentDomainDataDescriptor : nullptr; + + return DataDescriptorChangedEventPacket(valueDesc, domainDesc); } void InputSignal::setDataDescriptor(const DataDescriptorPtr& dataDescriptor) diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 76584d0..0bf579e 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -373,10 +373,11 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig } } - if (dataDescriptor != inputSignal->getSignalDescriptor() || - domainDescriptor != inputSignal->getDomainSignalDescriptor()) + const bool valueChanged = dataDescriptor != inputSignal->getSignalDescriptor(); + const bool domainChanged = domainDescriptor != inputSignal->getDomainSignalDescriptor() ; + if (valueChanged || domainChanged) { - publishSignalChanges(id, inputSignal); + publishSignalChanges(id, inputSignal, valueChanged, domainChanged); } } } @@ -416,7 +417,7 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig cachedDomainIdsAndDescriptors.insert_or_assign(tableId, std::make_pair(timeSignalId, sInfo.dataDescriptor)); } -void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal) +void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal, bool valueChanged, bool domainChanged) { // signal meta information is always received by pairs of META_METHOD_SIGNAL: // one is meta for data signal, another is meta for time signal. @@ -425,7 +426,7 @@ void StreamingClient::publishSignalChanges(const std::string& signalId, const In if (!signal->hasDescriptors()) return; - auto eventPacket = signal->createDecriptorChangedPacket(); + auto eventPacket = signal->createDecriptorChangedPacket(valueChanged, domainChanged); onPacketCallback(signalId, eventPacket); } @@ -474,11 +475,12 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& if (auto it = signals.find(id); it != signals.end()) { auto inputSignal = it->second; - if (signalDescriptors.first != inputSignal->getSignalDescriptor() || - signalDescriptors.second != inputSignal->getDomainSignalDescriptor()) + + const bool valueChanged = signalDescriptors.first != inputSignal->getSignalDescriptor(); + const bool domainChanged = signalDescriptors.second != inputSignal->getDomainSignalDescriptor() ; + if (valueChanged || domainChanged) { - // descriptors changed - generate event packet and send it to signal listeners - publishSignalChanges(id, inputSignal); + publishSignalChanges(id, inputSignal, valueChanged, domainChanged); } } } From 98210be603ddd6c090ab5ec339ede9f7c483f3ee Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:35:09 +0200 Subject: [PATCH 052/127] [TBBAS-1291] Grouping of discovered devices via serial number (openDAQ/openDAQ#196) - Rework device discovery to group devices via manufacturer and serial number - Add server capabilities to device information - Allow connection to devices with "daq://" prefix to automatically select best connection parameters --- .../websocket_streaming_client_module_impl.h | 7 +-- ...websocket_streaming_client_module_impl.cpp | 57 +++++++++---------- ...test_websocket_streaming_client_module.cpp | 35 ++++++------ .../src/websocket_streaming_server_impl.cpp | 4 +- ...test_websocket_streaming_server_module.cpp | 14 ++--- .../src/streaming_client.cpp | 31 +++++----- .../src/websocket_client_device_impl.cpp | 5 +- .../src/websocket_streaming_server.cpp | 24 ++++---- .../tests/test_streaming.cpp | 6 +- 9 files changed, 87 insertions(+), 96 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index 33eff03..fbb062f 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -32,13 +32,12 @@ class WebsocketStreamingClientModule final : public Module const ComponentPtr& parent, const PropertyObjectPtr& config) override; bool onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; - bool onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const StreamingInfoPtr& config) override; - StreamingPtr onCreateStreaming(const StringPtr& connectionString, const StreamingInfoPtr& config) override; + bool onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; + StreamingPtr onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) override; private: - static StringPtr tryCreateWebsocketConnectionString(const StreamingInfoPtr& config); + static StringPtr tryCreateWebsocketConnectionString(const ServerCapabilityPtr& capability); static DeviceTypePtr createWebsocketDeviceType(); - static DeviceTypePtr createTcpsocketDeviceType(); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 17d9516..20633b7 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -5,15 +5,12 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE -static const char* WebsocketDeviceTypeId = "daq.ws"; -static const char* TcpsocketDeviceTypeId = "daq.tcp"; -static const char* WebsocketDevicePrefix = "daq.ws://"; -static const char* TcpsocketDevicePrefix = "daq.tcp://"; -static const char* WebsocketStreamingPrefix = "daq.wss://"; -static const char* WebsocketStreamingID = "daq.wss"; +static const char* WebsocketDeviceTypeId = "opendaq_lt_streaming"; +static const char* WebsocketDevicePrefix = "daq.lt://"; using namespace discovery; using namespace daq::websocket_streaming; @@ -26,18 +23,22 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex , deviceIndex(0) , discoveryClient( { - [](MdnsDiscoveredDevice discoveredDevice) + [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - return fmt::format("daq.ws://{}:{}{}", + auto connectionString = fmt::format("{}{}:{}{}", + WebsocketDevicePrefix, discoveredDevice.ipv4Address, discoveredDevice.servicePort, discoveredDevice.getPropertyOrDefault("path", "/")); + auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming).addConnectionString(connectionString).setConnectionType("Ipv4"); + cap.setPrefix("daq.lt"); + return cap; } }, {"WS"} ) { - discoveryClient.initMdnsClient("_streaming-ws._tcp.local."); + discoveryClient.initMdnsClient("_streaming-lt._tcp.local."); } ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() @@ -55,10 +56,8 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableDevi auto result = Dict(); auto websocketDeviceType = createWebsocketDeviceType(); - auto tcpsocketDeviceType = createTcpsocketDeviceType(); result.set(websocketDeviceType.getId(), websocketDeviceType); - result.set(tcpsocketDeviceType.getId(), tcpsocketDeviceType); return result; } @@ -89,22 +88,20 @@ bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringP { std::string connStr = connectionString; auto found = connStr.find(WebsocketDevicePrefix); - if (found != 0) - found = connStr.find(TcpsocketDevicePrefix); return (found == 0); } -bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const StreamingInfoPtr& config) +bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) { if (connectionString.assigned()) { std::string connStr = connectionString; - auto found = connStr.find(WebsocketStreamingPrefix); + auto found = connStr.find(WebsocketDevicePrefix); return (found == 0); } else if (config.assigned()) { - if (config.getProtocolId() == WebsocketStreamingID) + if (config.getPropertyValue("protocolId") == WebsocketDeviceTypeId) { try { @@ -120,7 +117,7 @@ bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(cons return false; } -StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& connectionString, const StreamingInfoPtr& config) +StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) { StringPtr streamingConnectionString = connectionString; @@ -136,16 +133,21 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& return WebsocketStreaming(streamingConnectionString, context); } -StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(const StreamingInfoPtr &config) +StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(const ServerCapabilityPtr& capability) { - auto address = config.getPrimaryAddress(); - if (address.toStdString().empty()) - throw InvalidParameterException("Device address is not set"); + if (capability == nullptr) + throw InvalidParameterException("Capability is not set"); + + StringPtr connectionString = capability.getPropertyValue("PrimaryConnectionString"); + if (connectionString.getLength() != 0) + return connectionString; - const auto propertyObj = config.asPtr(); - auto port = propertyObj.getPropertyValue("Port").template asPtr(); + StringPtr address = capability.getPropertyValue("address"); + if (!address.assigned() || address.toStdString().empty()) + throw InvalidParameterException("Device address is not set"); - auto connectionString = String(fmt::format("daq.wss://{}:{}", address, port)); + auto port = capability.getPropertyValue("Port").template asPtr(); + connectionString = String(fmt::format("{}{}:{}", WebsocketDevicePrefix, address, port)); return connectionString; } @@ -157,11 +159,4 @@ DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType() "Pseudo device, provides only signals of the remote device as flat list"); } -DeviceTypePtr WebsocketStreamingClientModule::createTcpsocketDeviceType() -{ - return DeviceType(TcpsocketDeviceTypeId, - "Tcpsocket enabled device", - "Pseudo device, provides only signals of the remote device as flat list"); -} - END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 53a34b9..7b4bc55 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include using WebsocketStreamingClientModuleTest = testing::Test; @@ -97,7 +97,7 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringCorrect) { auto module = CreateModule(); - ASSERT_TRUE(module.acceptsConnectionParameters("daq.ws://device8")); + ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://device8")); } TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringNull) @@ -134,7 +134,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionFailed) { auto module = CreateModule(); - ASSERT_THROW(module.createDevice("daq.ws://127.0.0.1", nullptr), NotFoundException); + ASSERT_THROW(module.createDevice("daq.lt://127.0.0.1", nullptr), NotFoundException); } @@ -175,22 +175,25 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorre { auto module = CreateModule(); - ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.wss://device8")); + ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.lt://device8")); } TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) { - auto module = CreateModule(); + auto context = NullContext(); + ModulePtr module; + createModule(&module, context); - StreamingInfoConfigPtr streamingInfoConfig = StreamingInfo("daq.wss"); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); + ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); + serverCapability.setPrefix("daq.lt"); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - streamingInfoConfig.setPrimaryAddress("123.123.123.123"); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); + serverCapability.setPropertyValue("address", "123.123.123.123"); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - streamingInfoConfig.addProperty(IntProperty("Port", 1234)); - ASSERT_TRUE(module.acceptsStreamingConnectionParameters(nullptr, streamingInfoConfig)); + serverCapability.addProperty(IntProperty("Port", 1234)); + ASSERT_TRUE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); } TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) @@ -221,7 +224,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingConnectionStringInvali ASSERT_THROW(module.createStreaming("daqref://devicett3axxr1", nullptr), InvalidParameterException); ASSERT_THROW(module.createStreaming("daq.opcua://devicett3axxr1", nullptr), InvalidParameterException); - ASSERT_THROW(module.createStreaming("daq.ws://devicett3axxr1", nullptr), InvalidParameterException); + ASSERT_THROW(module.createStreaming("daq.lt://devicett3axxr1", nullptr), NotFoundException); } TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) @@ -234,11 +237,9 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); - ASSERT_EQ(deviceTypes.getCount(), 2u); - ASSERT_TRUE(deviceTypes.hasKey("daq.ws")); - ASSERT_EQ(deviceTypes.get("daq.ws").getId(), "daq.ws"); - ASSERT_TRUE(deviceTypes.hasKey("daq.tcp")); - ASSERT_EQ(deviceTypes.get("daq.tcp").getId(), "daq.tcp"); + ASSERT_EQ(deviceTypes.getCount(), 1u); + ASSERT_TRUE(deviceTypes.hasKey("opendaq_lt_streaming")); + ASSERT_EQ(deviceTypes.get("opendaq_lt_streaming").getId(), "opendaq_lt_streaming"); DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 949ec55..aa8092c 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -50,8 +50,8 @@ ServerTypePtr WebsocketStreamingServerImpl::createType() }; return ServerType( - "openDAQ WebsocketTcp Streaming", - "openDAQ WebsocketTcp Streaming server", + "openDAQ LT Streaming", + "openDAQ LT Streaming server", "Publishes device signals as a flat list and streams data over WebsocketTcp protocol", configurationCallback); } diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index 204077b..e48a601 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -55,7 +55,7 @@ static InstancePtr CreateTestInstance() static PropertyObjectPtr CreateServerConfig(const InstancePtr& instance) { - auto config = instance.getAvailableServerTypes().get("openDAQ WebsocketTcp Streaming").createDefaultConfig(); + auto config = instance.getAvailableServerTypes().get("openDAQ LT Streaming").createDefaultConfig(); config.setPropertyValue("WebsocketStreamingPort", 0); config.setPropertyValue("WebsocketControlPort", 0); return config; @@ -108,8 +108,8 @@ TEST_F(WebsocketStreamingServerModuleTest, GetAvailableComponentTypes) DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); ASSERT_EQ(serverTypes.getCount(), 1u); - ASSERT_TRUE(serverTypes.hasKey("openDAQ WebsocketTcp Streaming")); - ASSERT_EQ(serverTypes.get("openDAQ WebsocketTcp Streaming").getId(), "openDAQ WebsocketTcp Streaming"); + ASSERT_TRUE(serverTypes.hasKey("openDAQ LT Streaming")); + ASSERT_EQ(serverTypes.get("openDAQ LT Streaming").getId(), "openDAQ LT Streaming"); } TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) @@ -117,8 +117,8 @@ TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) auto module = CreateModule(); DictPtr serverTypes = module.getAvailableServerTypes(); - ASSERT_TRUE(serverTypes.hasKey("openDAQ WebsocketTcp Streaming")); - auto config = serverTypes.get("openDAQ WebsocketTcp Streaming").createDefaultConfig(); + ASSERT_TRUE(serverTypes.hasKey("openDAQ LT Streaming")); + auto config = serverTypes.get("openDAQ LT Streaming").createDefaultConfig(); ASSERT_TRUE(config.assigned()); ASSERT_TRUE(config.hasProperty("WebsocketStreamingPort")); @@ -134,7 +134,7 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateServer) auto module = CreateModule(device.getContext()); auto config = CreateServerConfig(device); - ASSERT_NO_THROW(module.createServer("openDAQ WebsocketTcp Streaming", device.getRootDevice(), config)); + ASSERT_NO_THROW(module.createServer("openDAQ LT Streaming", device.getRootDevice(), config)); } TEST_F(WebsocketStreamingServerModuleTest, CreateServerFromInstance) @@ -142,5 +142,5 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateServerFromInstance) auto device = CreateTestInstance(); auto config = CreateServerConfig(device); - ASSERT_NO_THROW(device.addServer("openDAQ WebsocketTcp Streaming", config)); + ASSERT_NO_THROW(device.addServer("openDAQ LT Streaming", config)); } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 0bf579e..6d69677 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -230,27 +230,24 @@ void StreamingClient::parseConnectionString(const std::string& url) target = "/"; std::smatch match; - std::string suffix; - auto regexHostname = std::regex("^(.*:\\/\\/)?([^:\\/\\s]+)"); - if (std::regex_search(url, match, regexHostname)) - host = match[2]; - else - return; + // parsing connection string to four groups: prefix, host, port, path + auto regexIpv6Hostname = std::regex(R"(^(.*://)?(?:\[([a-fA-F0-9:]+)\])(?::(\d+))?(/.*)?$)"); + auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + + bool parsed = false; + parsed = std::regex_search(url, match, regexIpv6Hostname); + if (!parsed) + parsed = std::regex_search(url, match, regexIpv4Hostname); - auto regexPort = std::regex("^:(\\d+)"); - suffix = match.suffix().str(); - if (std::regex_search(suffix, match, regexPort)) + if (parsed) { - port = std::stoi(match[1]); - suffix = match.suffix().str(); + host = match[2]; + if (match[3].matched) + port = std::stoi(match[3]); + if (match[4].matched) + target = match[4]; } - - auto regexTarget = std::regex("^\\/?[^\\?]+"); - if (std::regex_search(suffix, match, regexTarget)) - target = match[0]; - else - return; } void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, const std::string& method, const nlohmann::json& params) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 3368006..80f712a 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -23,10 +23,7 @@ WebsocketClientDeviceImpl::WebsocketClientDeviceImpl(const ContextPtr& ctx, DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() { - if (deviceInfo != nullptr) - return deviceInfo; - - deviceInfo = DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); + auto deviceInfo = DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); deviceInfo.freeze(); return deviceInfo; } diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 9201751..1a55ebc 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -3,10 +3,10 @@ #include #include "websocket_streaming/websocket_streaming_server.h" #include -#include #include -#include #include +#include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -60,19 +60,21 @@ void WebsocketStreamingServer::start() }); packetReader.start(); - // The control port is published thru the streaming protocol itself - // so here the streaming port only is added to the StreamingInfo object - StreamingInfoConfigPtr streamingInfo = StreamingInfo("daq.wss"); - streamingInfo.addProperty(IntProperty("Port", streamingPort)); - ErrCode errCode = this->device.asPtr()->addStreamingOption(streamingInfo); - checkErrorInfo(errCode); + const ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ-LT Streaming", ProtocolType::Streaming); + serverCapability.setPrefix("daq.lt"); + serverCapability.addProperty(IntProperty("Port", streamingPort)); + this->device.getInfo().asPtr().addServerCapability(serverCapability); } void WebsocketStreamingServer::stop() { - ErrCode errCode = - this->device.asPtr()->removeStreamingOption(String("daq.wss")); - checkErrorInfo(errCode); + if (this->device.assigned()) + { + const auto info = this->device.getInfo().asPtr(); + if (info.hasServerCapability("opendaq_lt_streaming")) + info.removeServerCapability("opendaq_lt_streaming"); + } + stopInternal(); } diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index c4fd2b9..aebd746 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -85,17 +85,17 @@ TEST_F(StreamingTest, ConnectTwice) TEST_F(StreamingTest, ParseConnectString) { - auto client = std::make_shared(NullContext(), "daq.wss://127.0.0.1"); + auto client = std::make_shared(NullContext(), "daq.lt://127.0.0.1"); ASSERT_EQ(client->getPort(), daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); ASSERT_EQ(client->getHost(), "127.0.0.1"); ASSERT_EQ(client->getTarget(), "/"); - client = std::make_shared(NullContext(), "daq.wss://localhost/path/other"); + client = std::make_shared(NullContext(), "daq.lt://localhost/path/other"); ASSERT_EQ(client->getPort(), daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); ASSERT_EQ(client->getHost(), "localhost"); ASSERT_EQ(client->getTarget(), "/path/other"); - client = std::make_shared(NullContext(), "daq.wss://localhost:3000/path/other"); + client = std::make_shared(NullContext(), "daq.lt://localhost:3000/path/other"); ASSERT_EQ(client->getPort(), 3000u); ASSERT_EQ(client->getHost(), "localhost"); ASSERT_EQ(client->getTarget(), "/path/other"); From a2f87e86a304e80a42a8575af2014dfef86db1e7 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:33:55 +0200 Subject: [PATCH 053/127] [Bug]: websocket mdns discovery backward competitivity (openDAQ/openDAQ#247) - mdns service can handle list of serviceNames - backward competitivity of old lt prefix "daq.ws://" and mdns service name "_streaming-ws._tcp.local." --- ...websocket_streaming_client_module_impl.cpp | 27 ++++++++++--------- ...test_websocket_streaming_client_module.cpp | 3 ++- .../tests/test_streaming.cpp | 5 ++++ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 20633b7..b67dce0 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -11,6 +11,7 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE static const char* WebsocketDeviceTypeId = "opendaq_lt_streaming"; static const char* WebsocketDevicePrefix = "daq.lt://"; +static const char* OldWebsocketDevicePrefix = "daq.ws://"; using namespace discovery; using namespace daq::websocket_streaming; @@ -26,19 +27,21 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex [context = this->context](MdnsDiscoveredDevice discoveredDevice) { auto connectionString = fmt::format("{}{}:{}{}", - WebsocketDevicePrefix, - discoveredDevice.ipv4Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/")); - auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming).addConnectionString(connectionString).setConnectionType("Ipv4"); + WebsocketDevicePrefix, + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/")); + auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); + cap.addConnectionString(connectionString); + cap.setConnectionType("Ipv4"); cap.setPrefix("daq.lt"); return cap; } - }, - {"WS"} + }, + {"LT"} ) { - discoveryClient.initMdnsClient("_streaming-lt._tcp.local."); + discoveryClient.initMdnsClient(List("_streaming-lt._tcp.local.", "_streaming-ws._tcp.local.")); } ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() @@ -87,17 +90,15 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& /*config*/) { std::string connStr = connectionString; - auto found = connStr.find(WebsocketDevicePrefix); - return (found == 0); + auto found = connStr.find(WebsocketDevicePrefix) == 0 || connStr.find(OldWebsocketDevicePrefix) == 0; + return found; } bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) { if (connectionString.assigned()) { - std::string connStr = connectionString; - auto found = connStr.find(WebsocketDevicePrefix); - return (found == 0); + return onAcceptsConnectionParameters(connectionString, config); } else if (config.assigned()) { diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 7b4bc55..3b9c999 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -176,9 +176,10 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorre auto module = CreateModule(); ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.lt://device8")); + // check that old style conenction is also supported + ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.ws://device8")); } - TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) { auto context = NullContext(); diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index aebd746..38ff14a 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -99,6 +99,11 @@ TEST_F(StreamingTest, ParseConnectString) ASSERT_EQ(client->getPort(), 3000u); ASSERT_EQ(client->getHost(), "localhost"); ASSERT_EQ(client->getTarget(), "/path/other"); + + client = std::make_shared(NullContext(), "daq.ws://127.0.0.1"); + ASSERT_EQ(client->getPort(), daq::streaming_protocol::WEBSOCKET_LISTENING_PORT); + ASSERT_EQ(client->getHost(), "127.0.0.1"); + ASSERT_EQ(client->getTarget(), "/"); } TEST_F(StreamingTest, Subscription) From 898c1d968333c45af61163deca704940a4068674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mikoli=C4=8D?= Date: Wed, 10 Apr 2024 11:03:03 +0200 Subject: [PATCH 054/127] Update module and library versions to 3.0.0 --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 26fcbe5..f448418 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 2.0.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION 3.0.0 LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index fe97d16..2bdfb3c 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 2.0.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION 3.0.0 LANGUAGES CXX) add_subdirectory(src) From d427eaf10a75787ad13f7d768ee8fd7fee6570ea Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 11 Apr 2024 21:40:06 +0200 Subject: [PATCH 055/127] Remove the creation of event packets within streaming --- .../include/websocket_streaming/streaming_client.h | 1 - .../websocket_streaming/websocket_streaming_impl.h | 1 - .../websocket_streaming/src/streaming_client.cpp | 8 -------- .../websocket_streaming/src/websocket_streaming_impl.cpp | 5 ----- 4 files changed, 15 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 0579b5b..1f4b1ec 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -70,7 +70,6 @@ class StreamingClient std::string getTarget(); bool isConnected(); void setConnectTimeout(std::chrono::milliseconds timeout); - EventPacketPtr getDataDescriptorChangedEventPacket(const StringPtr& signalStringId); void subscribeSignals(const std::vector& signalIds); void unsubscribeSignals(const std::vector& signalIds); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 2701c51..94bc2c4 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -38,7 +38,6 @@ class WebsocketStreamingImpl : public Streaming void onRemoveSignal(const MirroredSignalConfigPtr& signal) override; void onSubscribeSignal(const StringPtr& signalStreamingId) override; void onUnsubscribeSignal(const StringPtr& signalStreamingId) override; - EventPacketPtr onCreateDataDescriptorChangedEventPacket(const StringPtr& signalStreamingId) override; void prepareStreamingClient(); void onAvailableSignals(const std::vector& signalIds); diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 6d69677..a2bf24c 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -213,14 +213,6 @@ void StreamingClient::setConnectTimeout(std::chrono::milliseconds timeout) this->connectTimeout = timeout; } -EventPacketPtr StreamingClient::getDataDescriptorChangedEventPacket(const StringPtr& signalStringId) -{ - if (auto it = signals.find(signalStringId); it == signals.end()) - return DataDescriptorChangedEventPacket(nullptr, nullptr); - else - return it->second->createDecriptorChangedPacket(); -} - void StreamingClient::parseConnectionString(const std::string& url) { // this is not great but it is convenient until we have a way to pass configuration parameters to a client device diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 7ed7ba6..edf2ce9 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -48,11 +48,6 @@ void WebsocketStreamingImpl::onUnsubscribeSignal(const StringPtr& signalStreamin streamingClient->unsubscribeSignals({signalStreamingId.toStdString()}); } -EventPacketPtr WebsocketStreamingImpl::onCreateDataDescriptorChangedEventPacket(const StringPtr& signalStreamingId) -{ - return streamingClient->getDataDescriptorChangedEventPacket(signalStreamingId); -} - void WebsocketStreamingImpl::prepareStreamingClient() { auto packetCallback = [this](const StringPtr& signalId, const PacketPtr& packet) From 57fdd7c632d2b5003a53ecf3d0cd75e937c83314 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:41:46 +0200 Subject: [PATCH 056/127] rename server capability protocol type rename server capability protocol type from ipv4 to tcp/ip --- .../src/websocket_streaming_client_module_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index b67dce0..d4bfe53 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -33,7 +33,7 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex discoveredDevice.getPropertyOrDefault("path", "/")); auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); cap.addConnectionString(connectionString); - cap.setConnectionType("Ipv4"); + cap.setConnectionType("TCP/IP"); cap.setPrefix("daq.lt"); return cap; } From 76a77b078af3dfdc2761c1591d8cceb83bffa74d Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Fri, 5 Apr 2024 13:52:21 +0200 Subject: [PATCH 057/127] Add support for constant rule signals in the streaming-lt protocol: * On the server side: Add an output signal wrapper for numeric value signals following the constant rule. * On the client side: Create an InputSignal instance for each recognized signal, not limited to those marked as available. Refactor the InputSignal implementation to simplify the support of various signal types/rules. Add support for numeric value input signals following the constant rule. Forward domain packets through the domain signal path. Treat the streaming-lt bitField type values as unsigned integers. Copy bits interpretation into signal descriptor metadata * For pseudo devices: Create mirrored copies of all recognized signals within the device, extending beyond just the available signals. --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/input_signal.h | 138 ++++++- .../websocket_streaming/output_signal.h | 40 +- .../signal_descriptor_converter.h | 1 + .../websocket_streaming/streaming_client.h | 40 +- .../websocket_streaming/streaming_server.h | 2 +- .../websocket_client_device_impl.h | 5 +- .../websocket_client_signal_impl.h | 10 +- .../websocket_streaming/websocket_streaming.h | 3 + .../websocket_streaming_impl.h | 2 + .../websocket_streaming/src/input_signal.cpp | 308 +++++++++++++-- .../websocket_streaming/src/output_signal.cpp | 209 +++++++++++ .../src/signal_descriptor_converter.cpp | 20 +- .../src/streaming_client.cpp | 355 +++++++++++------- .../src/streaming_server.cpp | 34 +- .../src/websocket_client_device_impl.cpp | 45 ++- .../src/websocket_client_signal_impl.cpp | 10 +- .../src/websocket_streaming_impl.cpp | 25 +- .../tests/streaming_test_helpers.h | 58 +-- .../test_signal_descriptor_converter.cpp | 51 +++ .../tests/test_streaming.cpp | 111 ++++-- .../tests/test_websocket_client_device.cpp | 39 +- 22 files changed, 1153 insertions(+), 357 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 7572360..ceb4de8 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.16 + REQUIRED_VERSION 0.10.17 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.16 + GIT_REF v0.10.17 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 9c219ed..e2a7dfc 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -21,40 +21,156 @@ #include #include #include +#include +#include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING +class InputSignalBase; +using InputSignalBasePtr = std::shared_ptr; + class InputSignal; using InputSignalPtr = std::shared_ptr; -class InputSignal +class InputSignalBase { public: - InputSignal(); + InputSignalBase(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + daq::streaming_protocol::LogCallback logCb); + + virtual void processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount); + virtual DataPacketPtr generateDataPacket(uint64_t packetOffset, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) = 0; + virtual bool isDomainSignal() const = 0; + virtual bool isCountable() const = 0; - PacketPtr createDataPacket(uint64_t packetOffset, const uint8_t* data, size_t size) const; EventPacketPtr createDecriptorChangedPacket(bool valueChanged = true, bool domainChanged = true) const; + void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); - void setDomainDescriptor(const DataDescriptorPtr& domainDescriptor); bool hasDescriptors() const; + DataDescriptorPtr getSignalDescriptor() const; - DataDescriptorPtr getDomainSignalDescriptor() const; - void setTableId(std::string id); std::string getTableId() const; + std::string getSignalId() const; - void setIsDomainSignal(bool value); - bool getIsDomainSignal() const; + InputSignalBasePtr getInputDomainSignal() const; protected: + const std::string signalId; + const std::string tableId; DataDescriptorPtr currentDataDescriptor; - DataDescriptorPtr currentDomainDataDescriptor; + const InputSignalBasePtr inputDomainSignal; std::string name; std::string description; - std::string tableId; - bool isDomainSignal; + daq::streaming_protocol::LogCallback logCallback; mutable std::mutex descriptorsSync; }; +class InputDomainSignal : public InputSignalBase +{ +public: + InputDomainSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + streaming_protocol::LogCallback logCb); + + DataPacketPtr generateDataPacket(uint64_t packetOffset, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) override; + bool isDomainSignal() const override; + bool isCountable() const override; + +private: + DataPacketPtr lastDomainPacket; +}; + +class InputExplicitDataSignal : public InputSignalBase +{ +public: + InputExplicitDataSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb); + + DataPacketPtr generateDataPacket(uint64_t packetOffset, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) override; + bool isDomainSignal() const override; + bool isCountable() const override; +}; + +class InputConstantDataSignal : public InputSignalBase +{ +public: + using ConstantValueType = + std::variant; + + InputConstantDataSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb); + + void processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount) override; + DataPacketPtr generateDataPacket(uint64_t packetOffset, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) override; + bool isDomainSignal() const override; + bool isCountable() const override; + +private: + template + static ConstantValueType extractConstantValue(const uint8_t* pValue); + + template + static DataPacketPtr createTypedConstantPacket( + ConstantValueType startValue, + const std::vector>& otherValues, + size_t sampleCount, + const DataPacketPtr& domainPacket, + const DataDescriptorPtr& dataDescriptor); + + std::map cachedConstantValues; + std::optional lastConstantValue; +}; + +inline InputSignalBasePtr InputSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + bool isTimeSignal, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb) +{ + auto dataRuleType = signalInfo.dataDescriptor.getRule().getType(); + + if (isTimeSignal) + { + if (dataRuleType == daq::DataRuleType::Linear) + return std::make_shared(signalId, tabledId, signalInfo, logCb); + else + throw ConversionFailedException("Unsupported input domain signal rule"); + } + else + { + if (dataRuleType == daq::DataRuleType::Explicit) + return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb); + else if (dataRuleType == daq::DataRuleType::Constant) + return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb); + else + throw ConversionFailedException("Unsupported input data signal rule"); + } +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index fc2700c..dd15eed 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -22,6 +22,8 @@ #include "streaming_protocol/Logging.hpp" #include "websocket_streaming/signal_info.h" +#include + BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING class OutputSignalBase; @@ -85,7 +87,7 @@ class OutputValueSignalBase : public OutputSignalBase daq::streaming_protocol::LogCallback logCb); void writeDaqPacket(const PacketPtr& packet) override; - void setSubscribed(bool subscribed) override; + virtual void setSubscribed(bool subscribed) override; bool isDataSignal() override; protected: @@ -105,6 +107,7 @@ class OutputDomainSignalBase : public OutputSignalBase { public: friend class OutputValueSignalBase; + friend class OutputConstValueSignal; OutputDomainSignalBase(daq::streaming_protocol::BaseDomainSignalPtr domainStream, const SignalPtr& signal, @@ -146,6 +149,41 @@ class OutputSyncValueSignal : public OutputValueSignalBase daq::streaming_protocol::BaseSynchronousSignalPtr syncStream; }; +class OutputConstValueSignal : public OutputValueSignalBase +{ +public: + using ConstantValueType = + std::variant; + + OutputConstValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + OutputDomainSignaBaselPtr outputDomainSignal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + + void setSubscribed(bool subscribed) override; + +protected: + void writeDataPacket(const DataPacketPtr& packet) override; + +private: + static daq::streaming_protocol::BaseConstantSignalPtr createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb); + + template + static std::vector> + extractConstValuesFromDataPacket(const DataPacketPtr& packet); + + template + void writeData(const DataPacketPtr& packet, uint64_t firstValueIndex); + + daq::streaming_protocol::BaseConstantSignalPtr constStream; + std::optional lastConstValue; +}; + class OutputLinearDomainSignal : public OutputDomainSignalBase { public: diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index 197439c..e4a555c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -53,6 +53,7 @@ class SignalDescriptorConverter static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor); + static void DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptor); static nlohmann::json DictToJson(const DictPtr& dict); static DictPtr JsonToDict(const nlohmann::json& json); }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 1f4b1ec..5136f55 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -47,7 +47,7 @@ class StreamingClient using OnSignalCallback = std::function; using OnFindSignalCallback = std::function; using OnDomainSignalInitCallback = - std::function; + std::function; using OnAvailableSignalsCallback = std::function& signalIds)>; using OnSubsciptionAckCallback = std::function; @@ -58,20 +58,21 @@ class StreamingClient bool connect(); void disconnect(); void onPacket(const OnPacketCallback& callack); - void onSignalInit(const OnSignalCallback& callback); + void onAvailableSignalInit(const OnSignalCallback& callback); void onSignalUpdated(const OnSignalCallback& callback); void onDomainSingalInit(const OnDomainSignalInitCallback& callback); void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); void onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback); - void onFindSignal(const OnFindSignalCallback& callback); + void onHiddenStreamingSignal(const OnSignalCallback& callback); + void onHiddenDeviceSignal(const OnSignalCallback& callback); void onSubscriptionAck(const OnSubsciptionAckCallback& callback); std::string getHost(); uint16_t getPort(); std::string getTarget(); bool isConnected(); void setConnectTimeout(std::chrono::milliseconds timeout); - void subscribeSignals(const std::vector& signalIds); - void unsubscribeSignals(const std::vector& signalIds); + void subscribeSignal(const std::string& signalId); + void unsubscribeSignal(const std::string& signalId); protected: void parseConnectionString(const std::string& url); @@ -79,17 +80,14 @@ class StreamingClient const std::string& method, const nlohmann::json& params); void onProtocolMeta(daq::streaming_protocol::ProtocolHandler& protocolHandler, const std::string& method, const nlohmann::json& params); - void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size); + void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t valueCount); void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - void publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal, bool valueChanged, bool domainChanged); + void publishSignalChanges(const InputSignalBasePtr& signal, bool valueChanged, bool domainChanged); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); void setSignalInitSatisfied(const std::string& signalId); - void setDomainIdAndDescriptor(const std::string& dataSignalId, - const InputSignalPtr& inputSignal, - const std::string& domainSignalId, - const DataDescriptorPtr& domainDescriptor); - std::vector> findDataSignalsByTableId(const std::string& tableId); + std::vector findDataSignalsByTableId(const std::string& tableId); + InputSignalBasePtr findTimeSignalByTableId(const std::string& tableId); LoggerPtr logger; LoggerComponentPtr loggerComponent; @@ -102,14 +100,16 @@ class StreamingClient boost::asio::io_context ioContext; daq::streaming_protocol::SignalContainer signalContainer; daq::streaming_protocol::ProtocolHanlderPtr protocolHandler; - std::unordered_map signals; + std::unordered_map availableSignals; + std::unordered_map hiddenSignals; OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; - OnSignalCallback onSignalInitCallback = [](const StringPtr&, const SubscribedSignalInfo&) {}; - OnDomainSignalInitCallback onDomainSignalInitCallback = [](const StringPtr&, const StringPtr&, const DataDescriptorPtr&) {}; + OnSignalCallback onAvailableSignalInitCb = [](const StringPtr&, const SubscribedSignalInfo&) {}; + OnDomainSignalInitCallback onDomainSignalInitCallback = [](const StringPtr&, const StringPtr&) {}; OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; - OnFindSignalCallback onFindSignalCallback = [](const StringPtr& signalId) { return nullptr; }; OnSignalCallback onSignalUpdatedCallback = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; + OnSignalCallback onHiddenStreamingSignalCb = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; + OnSignalCallback onHiddenDeviceSignalInitCb = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; OnSubsciptionAckCallback onSubscriptionAckCallback = [](const StringPtr& signalId, bool subscribed) {}; std::thread clientThread; std::mutex clientMutex; @@ -120,14 +120,12 @@ class StreamingClient // is published only for subscribed signals. // as workaround we temporarily subscribe all signals to receive signal meta-info // at initialization stage. - // To manage this the 'signalInitializedStatus' is used, it is map of 4-element tuples, where: + // To manage this the 'availableSigInitStatus' is used, it is map of 4-element tuples, where: // 1-st is std::promise // 2-nd is std::future - // 3-rd: boolean flag indicating that initial subscription completion ack is filtered-out - // 4-th: boolean flag indicating that initial unsubscription completion ack is filtered-out - std::unordered_map, std::future, bool, bool>> signalInitializedStatus; + // 3-rd: boolean flag indicating that initial unsubscription completion ack is filtered-out + std::unordered_map, std::future, bool>> availableSigInitStatus; - std::unordered_map> cachedDomainIdsAndDescriptors; bool useRawTcpConnection; }; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 4235870..7e29bfe 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -93,7 +93,7 @@ class StreamingServer bool serverRunning{false}; private: - static DataRuleType getDomainSignalRuleType(const SignalPtr& domainSignal); + static DataRuleType getSignalRuleType(const SignalPtr& domainSignal); }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 2a7db1a..15345c0 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -37,10 +37,9 @@ class WebsocketClientDeviceImpl : public Device void updateSignalProperties(const SignalPtr& signal, const SubscribedSignalInfo& sInfo); void onSignalInit(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); - void onDomainSignalInit(const StringPtr& signalId, - const StringPtr& domainSignalId, - const DataDescriptorPtr& domainDescriptor); + void onDomainSignalInit(const StringPtr& signalId, const StringPtr& domainSignalId); void createDeviceSignals(const std::vector& signalIds); + void addHiddenSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); DeviceInfoConfigPtr deviceInfo; std::map deviceSignals; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 30ba4fe..eeed687 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -21,12 +21,7 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING -DECLARE_OPENDAQ_INTERFACE(IWebsocketStreamingSignalPrivate, IBaseObject) -{ - virtual void INTERFACE_FUNC createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) = 0; -}; - -class WebsocketClientSignalImpl final : public MirroredSignalBase +class WebsocketClientSignalImpl final : public MirroredSignal { public: explicit WebsocketClientSignalImpl(const ContextPtr& ctx, @@ -36,9 +31,6 @@ class WebsocketClientSignalImpl final : public MirroredSignalBase; + + class BaseConstantSignal; + using BaseConstantSignalPtr = std::shared_ptr; } namespace daq::stream diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 94bc2c4..8b7ce5e 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -41,8 +41,10 @@ class WebsocketStreamingImpl : public Streaming void prepareStreamingClient(); void onAvailableSignals(const std::vector& signalIds); + void onHiddenSignal(const std::string& signalId); daq::websocket_streaming::StreamingClientPtr streamingClient; + std::unordered_set hiddenSignals; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 156a566..1ce9bfa 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -10,84 +10,322 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING using namespace daq; using namespace daq::streaming_protocol; -InputSignal::InputSignal() - : isDomainSignal(false) +InputSignalBase::InputSignalBase(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb) + : signalId(signalId) + , tableId(tabledId) + , currentDataDescriptor(signalInfo.dataDescriptor) + , inputDomainSignal(domainSignal) + , name(signalInfo.signalProps.name.value_or(signalInfo.signalName)) + , description(signalInfo.signalProps.description.value_or("")) + , logCallback(logCb) { } -PacketPtr InputSignal::createDataPacket(uint64_t packetOffset, const uint8_t* data, size_t size) const +void InputSignalBase::processSamples(uint64_t /*packetOffset*/, const uint8_t* /*data*/, size_t /*sampleCount*/) { - std::scoped_lock lock(descriptorsSync); - auto sampleType = currentDataDescriptor.getSampleType(); - if (currentDataDescriptor.getPostScaling().assigned()) - sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); +} - const auto sampleSize = getSampleSize(sampleType); - const auto sampleCount = size / sampleSize; +EventPacketPtr InputSignalBase::createDecriptorChangedPacket(bool valueChanged, bool domainChanged) const +{ + std::scoped_lock lock(descriptorsSync); - auto domainPacket = DataPacket(currentDomainDataDescriptor, sampleCount, (Int) packetOffset); - auto dataPacket = DataPacketWithDomain(domainPacket, currentDataDescriptor, sampleCount); - std::memcpy(dataPacket.getRawData(), data, sampleCount*sampleSize); - return dataPacket; + if (isDomainSignal()) + { + const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; + return DataDescriptorChangedEventPacket(valueDesc, nullptr); + } + else + { + const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; + const auto domainDesc = domainChanged ? inputDomainSignal->getSignalDescriptor() : nullptr; + return DataDescriptorChangedEventPacket(valueDesc, domainDesc); + } } -EventPacketPtr InputSignal::createDecriptorChangedPacket(bool valueChanged, bool domainChanged) const +void InputSignalBase::setDataDescriptor(const DataDescriptorPtr& dataDescriptor) { std::scoped_lock lock(descriptorsSync); - const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; - const auto domainDesc = domainChanged ? currentDomainDataDescriptor : nullptr; - return DataDescriptorChangedEventPacket(valueDesc, domainDesc); + currentDataDescriptor = dataDescriptor; } -void InputSignal::setDataDescriptor(const DataDescriptorPtr& dataDescriptor) +bool InputSignalBase::hasDescriptors() const { std::scoped_lock lock(descriptorsSync); - currentDataDescriptor = dataDescriptor; + + if (isDomainSignal()) + return currentDataDescriptor.assigned(); + else + return currentDataDescriptor.assigned() && + inputDomainSignal && inputDomainSignal->getSignalDescriptor().assigned(); } -void InputSignal::setDomainDescriptor(const DataDescriptorPtr& domainDescriptor) +DataDescriptorPtr InputSignalBase::getSignalDescriptor() const { std::scoped_lock lock(descriptorsSync); - currentDomainDataDescriptor = domainDescriptor; + return currentDataDescriptor; } -bool InputSignal::hasDescriptors() const +std::string InputSignalBase::getTableId() const +{ + return tableId; +} + +std::string InputSignalBase::getSignalId() const +{ + return signalId; +} + +InputSignalBasePtr InputSignalBase::getInputDomainSignal() const +{ + return inputDomainSignal; +} + +InputDomainSignal::InputDomainSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + streaming_protocol::LogCallback logCb) + : InputSignalBase(signalId, tabledId, signalInfo, nullptr, logCb) +{ +} + +DataPacketPtr InputDomainSignal::generateDataPacket(uint64_t packetOffset, + const uint8_t* /*data*/, + size_t sampleCount, + const DataPacketPtr& /*domainPacket*/) { std::scoped_lock lock(descriptorsSync); - return currentDataDescriptor.assigned() && currentDomainDataDescriptor.assigned(); + + if (!lastDomainPacket.assigned() || lastDomainPacket.getOffset().getIntValue() != (Int) packetOffset) + { + lastDomainPacket = DataPacket(currentDataDescriptor, sampleCount, (Int) packetOffset); + } + + return lastDomainPacket; +} + +bool InputDomainSignal::isDomainSignal() const +{ + return true; +} + +bool InputDomainSignal::isCountable() const +{ + return false; +} + +InputExplicitDataSignal::InputExplicitDataSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb) + : InputSignalBase(signalId, tabledId, signalInfo, domainSignal, logCb) +{ } -DataDescriptorPtr InputSignal::getSignalDescriptor() const +DataPacketPtr InputExplicitDataSignal::generateDataPacket(uint64_t /*packetOffset*/, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) { std::scoped_lock lock(descriptorsSync); - return currentDataDescriptor; + + auto sampleType = currentDataDescriptor.getSampleType(); + if (currentDataDescriptor.getPostScaling().assigned()) + sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); + + auto dataPacket = DataPacketWithDomain(domainPacket, currentDataDescriptor, sampleCount); + const auto sampleSize = getSampleSize(sampleType); + std::memcpy(dataPacket.getRawData(), data, sampleCount * sampleSize); + return dataPacket; +} + +bool InputExplicitDataSignal::isDomainSignal() const +{ + return false; +} + +bool InputExplicitDataSignal::isCountable() const +{ + return true; +} + +InputConstantDataSignal::InputConstantDataSignal(const std::string& signalId, + const std::string& tabledId, + const SubscribedSignalInfo& signalInfo, + const InputSignalBasePtr& domainSignal, + streaming_protocol::LogCallback logCb) + : InputSignalBase(signalId, tabledId, signalInfo, domainSignal, logCb) +{ } -DataDescriptorPtr InputSignal::getDomainSignalDescriptor() const +void InputConstantDataSignal::processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount) +{ + auto sampleType = currentDataDescriptor.getSampleType(); + const auto sampleSize = getSampleSize(sampleType); + const auto bufferSize = sampleCount * (sampleSize + sizeof(uint64_t)); + Int delta = inputDomainSignal->getSignalDescriptor().getRule().getParameters().get("delta"); + + for (size_t offset = 0; offset < bufferSize; offset += sizeof(uint64_t) + sampleSize) + { + const uint64_t* pIndex = reinterpret_cast(data + offset); + const uint8_t* pConstantValue = data + offset + sizeof(uint64_t); + NumberPtr timeStamp = (Int)(*pIndex) * delta + timestamp; + + ConstantValueType constantValue; + switch (sampleType) + { + case daq::SampleType::Int8: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::Int16: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::Int32: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::Int64: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::UInt8: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::UInt16: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::UInt32: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::UInt64: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::Float32: + constantValue = extractConstantValue(pConstantValue); + break; + case daq::SampleType::Float64: + constantValue = extractConstantValue(pConstantValue); + break; + default: + return; + } + + cachedConstantValues.insert_or_assign(timeStamp, constantValue); + } +} + +DataPacketPtr InputConstantDataSignal::generateDataPacket(uint64_t /*packetOffset*/, + const uint8_t* /*data*/, + size_t sampleCount, + const DataPacketPtr& domainPacket) { std::scoped_lock lock(descriptorsSync); - return currentDomainDataDescriptor; + + auto sampleType = currentDataDescriptor.getSampleType(); + + Int delta = domainPacket.getDataDescriptor().getRule().getParameters().get("delta"); + Int startOffset = domainPacket.getOffset(); + Int endOffset = startOffset + delta * sampleCount; + + DataPacketPtr dataPacket; + ConstantValueType startValue; + + if (auto it = cachedConstantValues.find(startOffset); it != cachedConstantValues.end()) + startValue = it->second; + else if (lastConstantValue.has_value()) + startValue = lastConstantValue.value(); + else + return nullptr; + + // TODO erase cached values that was already used to generate packets + std::vector> otherValues; + if (!cachedConstantValues.empty()) + { + auto itStart = cachedConstantValues.upper_bound(startOffset); + auto itEnd = cachedConstantValues.lower_bound(endOffset); + + for (auto it = itStart; it != itEnd; ++it) + { + uint32_t pos = (it->first.getIntValue() - startOffset) / delta; + otherValues.emplace_back(pos, it->second); + lastConstantValue = it->second; + } + } + + switch (sampleType) + { + case daq::SampleType::Int8: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::Int16: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::Int32: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::Int64: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::UInt8: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::UInt16: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::UInt32: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::UInt64: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::Float32: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + case daq::SampleType::Float64: + return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); + break; + default: + return nullptr; + } } -void InputSignal::setTableId(std::string id) +bool InputConstantDataSignal::isDomainSignal() const { - tableId = id; + return false; } -std::string InputSignal::getTableId() const +bool InputConstantDataSignal::isCountable() const { - return tableId; + return false; } -void InputSignal::setIsDomainSignal(bool value) +template +InputConstantDataSignal::ConstantValueType InputConstantDataSignal::extractConstantValue(const uint8_t* pValue) { - isDomainSignal = value; + return ConstantValueType(*(reinterpret_cast(pValue))); } -bool InputSignal::getIsDomainSignal() const +template +DataPacketPtr InputConstantDataSignal::createTypedConstantPacket( + ConstantValueType startValue, + const std::vector>& otherValues, + size_t sampleCount, + const DataPacketPtr& domainPacket, + const DataDescriptorPtr& dataDescriptor) { - return isDomainSignal; + const auto startValueTyped = std::get(startValue); + std::vector> otherValuesTyped; + for (const auto& otherValue : otherValues) + { + const auto otherValueTyped = std::get(otherValue.second); + uint32_t pos = otherValue.first; + otherValuesTyped.push_back({pos, otherValueTyped}); + } + + return ConstantDataPacketWithDomain(domainPacket, dataDescriptor, sampleCount, startValueTyped, otherValuesTyped); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index ce91c4f..bf0c5df 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -3,6 +3,7 @@ #include "streaming_protocol/StreamWriter.h" #include "streaming_protocol/SynchronousSignal.hpp" #include "streaming_protocol/LinearTimeSignal.hpp" +#include "streaming_protocol/ConstantSignal.hpp" #include #include #include @@ -501,4 +502,212 @@ void OutputSyncValueSignal::writeDataPacket(const DataPacketPtr& packet) syncStream->addData(packet.getRawData(), packet.getSampleCount()); } +BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( + const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) +{ + BaseConstantSignalPtr constStream; + + const auto valueDescriptor = signal.getDescriptor(); + auto sampleType = valueDescriptor.getSampleType(); + if (valueDescriptor.getPostScaling().assigned()) + sampleType = valueDescriptor.getPostScaling().getInputSampleType(); + + const auto signalId = signal.getGlobalId(); + + switch (sampleType) + { + case daq::SampleType::Int8: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::UInt8: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::Int16: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::UInt16: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::Int32: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::UInt32: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::Int64: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::UInt64: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::Float32: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::Float64: + constStream = std::make_shared>(signalId, tableId, *writer, logCb); + break; + case daq::SampleType::ComplexFloat32: + case daq::SampleType::ComplexFloat64: + case daq::SampleType::Binary: + case daq::SampleType::Invalid: + case daq::SampleType::String: + case daq::SampleType::RangeInt64: + case daq::SampleType::Struct: + default: + throw InvalidTypeException("Unsupported data signal sample type"); + } + + SignalDescriptorConverter::ToStreamedValueSignal(signal, constStream, getSignalProps(signal)); + + return constStream; +} + +OutputConstValueSignal::OutputConstValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, + const SignalPtr& signal, OutputDomainSignaBaselPtr outputDomainSignal, + const std::string& tableId, + daq::streaming_protocol::LogCallback logCb) + : OutputValueSignalBase(createSignalStream(writer, signal, tableId, logCb), signal, outputDomainSignal, logCb) + , constStream(std::dynamic_pointer_cast(stream)) +{ +} + +template +std::vector> +OutputConstValueSignal::extractConstValuesFromDataPacket(const DataPacketPtr& packet) +{ + std::vector> result; + + const auto packetData = reinterpret_cast(packet.getData()); + result.push_back({packetData[0], 0}); + + for (size_t index = 1; index < packet.getSampleCount(); ++index) + { + DataType packetDataValue = packetData[index]; + if (result.back().first != packetDataValue) + result.push_back({packetDataValue, static_cast(index)}); + } + + return result; +} + +template +void OutputConstValueSignal::writeData(const DataPacketPtr& packet, uint64_t firstValueIndex) +{ + if (doSetStartTime) + { + lastConstValue.reset(); + doSetStartTime = false; + } + + const auto values = extractConstValuesFromDataPacket(packet); + const DataType packetFirstValue = values[0].first; + + if (!lastConstValue.has_value() || std::get(lastConstValue.value()) != packetFirstValue || values.size() > 1) + { + size_t startFrom = 0; + if (lastConstValue.has_value() && std::get(lastConstValue.value()) == packetFirstValue) + startFrom = 1; + + auto valuesCount = values.size(); + + std::vector constants; + std::vector indices; + + for (size_t i = startFrom; i < valuesCount; ++i) + { + constants.push_back(static_cast(values[i].first)); + indices.push_back(values[i].second + firstValueIndex); + } + + constStream->addData(constants.data(), indices.data(), valuesCount); + } + + lastConstValue = values.back().first; +} + +void OutputConstValueSignal::writeDataPacket(const DataPacketPtr& packet) +{ + const auto domainPacket = packet.getDomainPacket(); + if (!domainPacket.assigned() || !domainPacket.getDataDescriptor().assigned()) + { + STREAMING_PROTOCOL_LOG_E("streaming-lt: cannot stream data packet without domain packet / descriptor"); + return; + } + const auto packetDomainDescriptor = domainPacket.getDataDescriptor(); + if (outputDomainSignal->isTimeConfigChanged(packetDomainDescriptor) || + isTimeConfigChanged(packetDomainDescriptor)) + { + STREAMING_PROTOCOL_LOG_E("Domain signal config mismatched, skip data packet"); + return; + } + + uint64_t timeStamp = domainPacket.getOffset(); + auto timeValueOffset = outputDomainSignal->calcStartTimeOffset(timeStamp); + Int deltaInTicks = packetDomainDescriptor.getRule().getParameters().get("delta"); + uint64_t firstValueIndex = timeValueOffset / deltaInTicks; + + auto sampleType = packet.getDataDescriptor().getSampleType(); + switch (sampleType) { + case daq::SampleType::Int8: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::Int16: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::Int32: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::Int64: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::UInt8: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::UInt16: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::UInt32: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::UInt64: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::Float32: + writeData(packet, firstValueIndex); + break; + case daq::SampleType::Float64: + writeData(packet, firstValueIndex); + break; + default: + STREAMING_PROTOCOL_LOG_E("Unsupported sample type, skip data packet"); + break; + } +} + +void OutputConstValueSignal::setSubscribed(bool subscribed) +{ + if (this->subscribed != subscribed) + { + std::scoped_lock lock(subscribedSync); + + this->subscribed = subscribed; + doSetStartTime = true; + lastConstValue.reset(); + + if (subscribed) + { + outputDomainSignal->subscribeByDataSignal(); + stream->subscribe(); + } + else + { + stream->unsubscribe(); + outputDomainSignal->unsubscribeByDataSignal(); + } + } +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 7f93148..3579cbc 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -69,6 +69,9 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre dataDescriptor.setValueRange(daq::Range(-15.0, 15.0)); } + auto bitsInterpretation = subscribedSignal.bitsInterpretationObject(); + DecodeBitsInterpretationObject(bitsInterpretation, dataDescriptor); + // --- meta "interpretation" start --- // overwrite/add descriptor fields with ones from optional "interpretation" object auto extra = subscribedSignal.interpretationObject(); @@ -189,7 +192,7 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco } break; default: - throw ConversionFailedException(); + throw ConversionFailedException("Unsupported data rule type"); } } @@ -238,9 +241,11 @@ daq::SampleType SignalDescriptorConverter::Convert(daq::streaming_protocol::Samp case daq::streaming_protocol::SampleType::SAMPLETYPE_REAL64: return daq::SampleType::Float64; case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD32: + return daq::SampleType::UInt32; case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD64: + return daq::SampleType::UInt64; default: - throw ConversionFailedException(); + throw ConversionFailedException("Unsupported input sample type"); } } @@ -293,7 +298,7 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp case daq::SampleType::String: case daq::SampleType::RangeInt64: default: - throw ConversionFailedException(); + throw ConversionFailedException("Unsupported output sample type"); } } @@ -347,6 +352,15 @@ void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorP } } +void SignalDescriptorConverter::DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptor) +{ + if (!bits.empty() && bits.is_array()) + { + auto metadata = dataDescriptor.getMetadata(); + metadata.set("bits", bits.dump()); + } +} + void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor) { // sets descriptor name when corresponding field is present in interpretation object diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index a2bf24c..1218a77 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -60,11 +60,11 @@ bool StreamingClient::connect() auto protocolMetaCallback = [this](ProtocolHandler& protocolHandler, const std::string& method, const nlohmann::json& params) { onProtocolMeta(protocolHandler, method, params); }; - auto messageCallback = [this](const SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t size) - { onMessage(subscribedSignal, timeStamp, data, size); }; + auto messageCallback = [this](const SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t valueCount) + { onMessage(subscribedSignal, timeStamp, data, valueCount); }; signalContainer.setSignalMetaCb(signalMetaCallback); - signalContainer.setDataAsRawCb(messageCallback); + signalContainer.setDataAsValueCb(messageCallback); std::unique_ptr clientStream; if (useRawTcpConnection) @@ -84,17 +84,16 @@ bool StreamingClient::connect() if (connected) { std::vector signalIds; - for (const auto& [signalId,_] : signals) + for (const auto& [signalId,_] : availableSignals) { signalIds.push_back(signalId); std::promise signalInitPromise; std::future signalInitFuture = signalInitPromise.get_future(); - signalInitializedStatus.insert_or_assign( + availableSigInitStatus.insert_or_assign( signalId, std::make_tuple( std::move(signalInitPromise), std::move(signalInitFuture), - false, false ) ); @@ -109,7 +108,7 @@ bool StreamingClient::connect() const auto timeout = std::chrono::seconds(1); auto timeoutExpired = std::chrono::system_clock::now() + timeout; - for (const auto& [id, params] : signalInitializedStatus) + for (const auto& [id, params] : availableSigInitStatus) { auto status = std::get<1>(params).wait_until(timeoutExpired); if (status != std::future_status::ready) @@ -153,9 +152,9 @@ void StreamingClient::onPacket(const OnPacketCallback& callack) onPacketCallback = callack; } -void StreamingClient::onSignalInit(const OnSignalCallback& callback) +void StreamingClient::onAvailableSignalInit(const OnSignalCallback& callback) { - onSignalInitCallback = callback; + onAvailableSignalInitCb = callback; } void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) @@ -178,9 +177,14 @@ void StreamingClient::onAvailableDeviceSignals(const OnAvailableSignalsCallback& onAvailableDeviceSignalsCb = callback; } -void StreamingClient::onFindSignal(const OnFindSignalCallback& callback) +void StreamingClient::onHiddenStreamingSignal(const OnSignalCallback& callback) { - onFindSignalCallback = callback; + onHiddenStreamingSignalCb = callback; +} + +void StreamingClient::onHiddenDeviceSignal(const OnSignalCallback& callback) +{ + onHiddenDeviceSignalInitCb = callback; } void StreamingClient::onSubscriptionAck(const OnSubsciptionAckCallback& callback) @@ -248,23 +252,37 @@ void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, con onSignal(subscribedSignal, params); std::string signalId = subscribedSignal.signalId(); - if (auto it = signalInitializedStatus.find(signalId); it != signalInitializedStatus.end()) + + // triggers ack only for available signals, but not for hidden ones + if (method == daq::streaming_protocol::META_METHOD_SUBSCRIBE) { - if (method == daq::streaming_protocol::META_METHOD_SUBSCRIBE) + if (auto it = availableSignals.find(signalId); it != availableSignals.end()) { - // skips the first subscribe ACK from server and ignores ACKs for domain signals - if (std::get<2>(it->second) && !subscribedSignal.isTimeSignal()) - onSubscriptionAckCallback(subscribedSignal.signalId(), true); - else - std::get<2>(it->second) = true; + auto inputSignal = it->second; + // skips the first subscribe ACK from server as inputSignal is not initialized at the moment + if (inputSignal) + { + // ignores ACKs for domain signals + if (!inputSignal->isDomainSignal()) + onSubscriptionAckCallback(subscribedSignal.signalId(), true); + } } - else if (method == daq::streaming_protocol::META_METHOD_UNSUBSCRIBE) + } + else if (method == daq::streaming_protocol::META_METHOD_UNSUBSCRIBE) + { + if (auto it = availableSigInitStatus.find(signalId); it != availableSigInitStatus.end()) { - // skips the first unsubscribe ACK from server and ignores ACKs for domain signals - if (std::get<3>(it->second) && !subscribedSignal.isTimeSignal()) - onSubscriptionAckCallback(subscribedSignal.signalId(), false); + // skips the first unsubscribe ACK from server + if (std::get<2>(it->second)) + { + // ignores ACKs for domain signals + if (!subscribedSignal.isTimeSignal()) + onSubscriptionAckCallback(subscribedSignal.signalId(), false); + } else - std::get<3>(it->second) = true; + { + std::get<2>(it->second) = true; + } } } } @@ -276,19 +294,18 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p if (method == daq::streaming_protocol::META_METHOD_AVAILABLE) { std::vector signalIds; - auto availableSignals = params.find(META_SIGNALIDS); + auto availableSignalsArray = params.find(META_SIGNALIDS); - if (availableSignals != params.end() && availableSignals->is_array()) + if (availableSignalsArray != params.end() && availableSignalsArray->is_array()) { - for (const auto& arrayItem : *availableSignals) + for (const auto& arrayItem : *availableSignalsArray) { std::string signalId = arrayItem; signalIds.push_back(signalId); - if (auto signalIt = signals.find(signalId); signalIt == signals.end()) + if (auto signalIt = availableSignals.find(signalId); signalIt == availableSignals.end()) { - auto inputSignal = std::make_shared(); - signals.insert({signalId, inputSignal}); + availableSignals.insert({signalId, nullptr}); } else { @@ -306,107 +323,179 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p } } -void StreamingClient::subscribeSignals(const std::vector& signalIds) +void StreamingClient::subscribeSignal(const std::string& signalId) { - protocolHandler->subscribe(signalIds); + if (auto it = availableSignals.find(signalId); it != availableSignals.end()) + protocolHandler->subscribe({signalId}); } -void StreamingClient::unsubscribeSignals(const std::vector& signalIds) +void StreamingClient::unsubscribeSignal(const std::string& signalId) { - protocolHandler->unsubscribe(signalIds); + if (auto it = availableSignals.find(signalId); it != availableSignals.end()) + protocolHandler->unsubscribe({signalId}); } void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, - size_t size) + size_t valueCount) { std::string id = subscribedSignal.signalId(); - const auto& signalIter = signals.find(id); - if (signalIter != signals.end() && - signalIter->second->hasDescriptors() && - signalIter->second->getSignalDescriptor().getSampleType() != daq::SampleType::Struct) - { - auto packet = signalIter->second->createDataPacket(timeStamp, data, size); - onPacketCallback(id, packet); - } -} -void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) -{ - const auto id = subscribedSignal.signalId(); + InputSignalBasePtr inputSignal = nullptr; - if (auto signalIt = signals.find(id); signalIt != signals.end()) - { - auto inputSignal = signalIt->second; + if (auto availableSigIt = availableSignals.find(id); availableSigIt != availableSignals.end()) + inputSignal = availableSigIt->second; + else if (auto hiddenSigIt = hiddenSignals.find(id); hiddenSigIt != hiddenSignals.end()) + inputSignal = hiddenSigIt->second; - DataDescriptorPtr dataDescriptor = inputSignal->getSignalDescriptor(); - DataDescriptorPtr domainDescriptor = inputSignal->getDomainSignalDescriptor(); + if (inputSignal && + inputSignal->hasDescriptors() && + inputSignal->getSignalDescriptor().getSampleType() != daq::SampleType::Struct) + { + if (inputSignal->isCountable()) + { + DataPacketPtr domainPacket; + if (inputSignal->isDomainSignal()) + { + domainPacket = inputSignal->generateDataPacket(timeStamp, data, valueCount, nullptr); + if (domainPacket.assigned()) + onPacketCallback(id, domainPacket); + } + else + { + domainPacket = + inputSignal->getInputDomainSignal()->generateDataPacket(timeStamp, nullptr, valueCount, nullptr); + if (domainPacket.assigned()) + onPacketCallback(inputSignal->getInputDomainSignal()->getSignalId(), domainPacket); + auto packet = inputSignal->generateDataPacket(timeStamp, data, valueCount, domainPacket); + if (packet.assigned()) + onPacketCallback(id, packet); + } - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); - if (!inputSignal->getSignalDescriptor().assigned()) - onSignalInitCallback(id, sInfo); - else - onSignalUpdatedCallback(id, sInfo); - inputSignal->setDataDescriptor(sInfo.dataDescriptor); + auto relatedDataSignals = findDataSignalsByTableId(inputSignal->getTableId()); - const auto tableId = subscribedSignal.tableId(); - if (inputSignal->getTableId() != tableId) - { - inputSignal->setTableId(tableId); - if (auto domainIdAndDescIt = cachedDomainIdsAndDescriptors.find(tableId); domainIdAndDescIt != cachedDomainIdsAndDescriptors.end()) + // trigger packet generation for each related signal which is not countable (is implicit) + for (auto& relatedSignal : relatedDataSignals) { - auto domainSignalId = domainIdAndDescIt->second.first; - auto domainSignalDescriptor = domainIdAndDescIt->second.second; - setDomainIdAndDescriptor(id, inputSignal, domainSignalId, domainSignalDescriptor); + if (!relatedSignal->isCountable()) + { + auto packet = relatedSignal->generateDataPacket(timeStamp, nullptr, valueCount, domainPacket); + if (packet.assigned()) + onPacketCallback(relatedSignal->getSignalId(), packet); + } } } - - const bool valueChanged = dataDescriptor != inputSignal->getSignalDescriptor(); - const bool domainChanged = domainDescriptor != inputSignal->getDomainSignalDescriptor() ; - if (valueChanged || domainChanged) + else { - publishSignalChanges(id, inputSignal, valueChanged, domainChanged); + inputSignal->processSamples(timeStamp, data, valueCount); } } } -void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) { - std::string tableId = subscribedSignal.tableId(); auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + const auto signalId = subscribedSignal.signalId(); + const auto tableId = subscribedSignal.tableId(); + bool available = false; - const auto timeSignalId = subscribedSignal.signalId(); - if (auto signalIt = signals.find(timeSignalId); signalIt != signals.end()) + auto domainInputSignal = findTimeSignalByTableId(tableId); + if (!domainInputSignal) + throw NotFoundException("Unknown domain signal for data signal {}, table {}", signalId, tableId); + + InputSignalBasePtr inputSignal = nullptr; + if (auto availableSigIt = availableSignals.find(signalId); availableSigIt != availableSignals.end()) { - // the time signal is published as available by server, - // so do the initialization of its mirrored copy - auto inputSignal = signalIt->second; - if (!inputSignal->getSignalDescriptor().assigned()) - { - onSignalInitCallback(timeSignalId, sInfo); - setSignalInitSatisfied(timeSignalId); - } - else + inputSignal = availableSigIt->second; + available = true; + } + else if (auto hiddenSigIt = hiddenSignals.find(signalId); hiddenSigIt != hiddenSignals.end()) + { + inputSignal = hiddenSigIt->second; + available = false; + } + + if (!inputSignal && !available) + { + inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback); + hiddenSignals.insert({signalId, inputSignal}); + onHiddenStreamingSignalCb(signalId, sInfo); + onHiddenDeviceSignalInitCb(signalId, sInfo); + onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); + } + else if (available && !inputSignal) + { + inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback); + availableSignals[signalId] = inputSignal; + onAvailableSignalInitCb(signalId, sInfo); + onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); + setSignalInitSatisfied(signalId); + } + else + { + if (sInfo.dataDescriptor != inputSignal->getSignalDescriptor()) { - onSignalUpdatedCallback(timeSignalId, sInfo); + inputSignal->setDataDescriptor(sInfo.dataDescriptor); + publishSignalChanges(inputSignal, true, false); } - inputSignal->setDataDescriptor(sInfo.dataDescriptor); - inputSignal->setIsDomainSignal(true); - inputSignal->setTableId(tableId); + onSignalUpdatedCallback(signalId, sInfo); } +} - // set domain descriptor for all input data signals with known tableId - for (const auto& [dataSignalId, inputSignal] : findDataSignalsByTableId(tableId)) +void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +{ + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + const std::string tableId = subscribedSignal.tableId(); + const std::string timeSignalId = subscribedSignal.signalId(); + bool available = false; + + InputSignalBasePtr inputSignal = nullptr; + if (auto availableSigIt = availableSignals.find(timeSignalId); availableSigIt != availableSignals.end()) + { + inputSignal = availableSigIt->second; + available = true; + } + else if (auto hiddenSigIt = hiddenSignals.find(timeSignalId); hiddenSigIt != hiddenSignals.end()) { - setDomainIdAndDescriptor(dataSignalId, inputSignal, timeSignalId, sInfo.dataDescriptor); + inputSignal = hiddenSigIt->second; + available = false; + } + + if (!inputSignal && !available) + { + // the time signal was not published as available by server, add as hidden + inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback); + hiddenSignals.insert({timeSignalId, inputSignal}); + onHiddenStreamingSignalCb(timeSignalId, sInfo); + onHiddenDeviceSignalInitCb(timeSignalId, sInfo); + } + else if (available && !inputSignal) + { + inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback); + availableSignals[timeSignalId] = inputSignal; + // the time signal is published as available by server, + // so do the initialization of its mirrored copy + onAvailableSignalInitCb(timeSignalId, sInfo); + setSignalInitSatisfied(timeSignalId); } + else + { + if (sInfo.dataDescriptor != inputSignal->getSignalDescriptor()) + { + inputSignal->setDataDescriptor(sInfo.dataDescriptor); - // some data signals can have unknown tableId at the moment, save domain signal id and descriptor for future - cachedDomainIdsAndDescriptors.insert_or_assign(tableId, std::make_pair(timeSignalId, sInfo.dataDescriptor)); + // publish new domain descriptor for all input data signals with known tableId + for (const auto& inputDataSignal : findDataSignalsByTableId(tableId)) + { + publishSignalChanges(inputDataSignal, false, true); + } + } + onSignalUpdatedCallback(timeSignalId, sInfo); + } } -void StreamingClient::publishSignalChanges(const std::string& signalId, const InputSignalPtr& signal, bool valueChanged, bool domainChanged) +void StreamingClient::publishSignalChanges(const InputSignalBasePtr& signal, bool valueChanged, bool domainChanged) { // signal meta information is always received by pairs of META_METHOD_SIGNAL: // one is meta for data signal, another is meta for time signal. @@ -416,22 +505,49 @@ void StreamingClient::publishSignalChanges(const std::string& signalId, const In return; auto eventPacket = signal->createDecriptorChangedPacket(valueChanged, domainChanged); - onPacketCallback(signalId, eventPacket); + onPacketCallback(signal->getSignalId(), eventPacket); } -std::vector> StreamingClient::findDataSignalsByTableId(const std::string& tableId) +std::vector StreamingClient::findDataSignalsByTableId(const std::string& tableId) { - std::vector> result; - for (const auto& [id, inputSignal] : signals) + std::vector result; + for (const auto& [_, inputSignal] : availableSignals) + { + if (inputSignal && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) + { + result.push_back(inputSignal); + } + } + for (const auto& [_, inputSignal] : hiddenSignals) { - if (tableId == inputSignal->getTableId() && !inputSignal->getIsDomainSignal()) + if (inputSignal && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) { - result.push_back({id, inputSignal}); + result.push_back(inputSignal); } } return result; } +InputSignalBasePtr StreamingClient::findTimeSignalByTableId(const std::string& tableId) +{ + std::vector> result; + for (const auto& [_, inputSignal] : availableSignals) + { + if (inputSignal && tableId == inputSignal->getTableId() && inputSignal->isDomainSignal()) + { + return inputSignal; + } + } + for (const auto& [_, inputSignal] : hiddenSignals) + { + if (inputSignal && tableId == inputSignal->getTableId() && inputSignal->isDomainSignal()) + { + return inputSignal; + } + } + return nullptr; +} + void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params) { try @@ -448,31 +564,7 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& if (subscribedSignal.isTimeSignal()) { - std::unordered_map> descriptors; - std::string tableId = subscribedSignal.tableId(); - for (const auto& [id,signal] : findDataSignalsByTableId(tableId)) - { - std::pair pair(signal->getSignalDescriptor(), - signal->getDomainSignalDescriptor()); - descriptors.insert_or_assign(id, pair); - } - setTimeSignal(subscribedSignal); - - for (const auto& [id, signalDescriptors] : descriptors) - { - if (auto it = signals.find(id); it != signals.end()) - { - auto inputSignal = it->second; - - const bool valueChanged = signalDescriptors.first != inputSignal->getSignalDescriptor(); - const bool domainChanged = signalDescriptors.second != inputSignal->getDomainSignalDescriptor() ; - if (valueChanged || domainChanged) - { - publishSignalChanges(id, inputSignal, valueChanged, domainChanged); - } - } - } } else { @@ -487,7 +579,7 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& void StreamingClient::setSignalInitSatisfied(const std::string& signalId) { - if (auto iterator = signalInitializedStatus.find(signalId); iterator != signalInitializedStatus.end()) + if (auto iterator = availableSigInitStatus.find(signalId); iterator != availableSigInitStatus.end()) { try { @@ -507,19 +599,4 @@ void StreamingClient::setSignalInitSatisfied(const std::string& signalId) } } -void StreamingClient::setDomainIdAndDescriptor(const std::string& dataSignalId, - const InputSignalPtr& inputSignal, - const std::string& domainSignalId, - const DataDescriptorPtr& domainDescriptor) -{ - // Sets the descriptors of pseudo device signal when first connecting - if (!inputSignal->getDomainSignalDescriptor().assigned()) - { - onDomainSignalInitCallback(dataSignalId, domainSignalId, domainDescriptor); - setSignalInitSatisfied(dataSignalId); - } - - inputSignal->setDomainDescriptor(domainDescriptor); -} - END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 805646d..090bf98 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -154,12 +154,12 @@ void StreamingServer::sendPacketToSubscribers(const std::string& signalId, const } } -DataRuleType StreamingServer::getDomainSignalRuleType(const SignalPtr& domainSignal) +DataRuleType StreamingServer::getSignalRuleType(const SignalPtr& signal) { - auto descriptor = domainSignal.getDescriptor(); + auto descriptor = signal.getDescriptor(); if (!descriptor.assigned() || !descriptor.getRule().assigned()) { - throw InvalidParameterException("Unknown domain signal rule"); + throw InvalidParameterException("Unknown signal rule"); } return descriptor.getRule().getType(); } @@ -172,9 +172,9 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, -> std::shared_ptr { auto tableId = domainSignal.getGlobalId(); - const auto domainRuleType = getDomainSignalRuleType(domainSignal); + const auto domainSignalRuleType = getSignalRuleType(domainSignal); - if (domainRuleType == DataRuleType::Linear) + if (domainSignalRuleType == DataRuleType::Linear) { return std::make_shared(writer, domainSignal, tableId, logCallback); } @@ -204,12 +204,26 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, auto tableId = domainSignalId; - const auto domainRuleType = getDomainSignalRuleType(domainSignal); - if (domainRuleType == DataRuleType::Linear) + const auto domainSignalRuleType = getSignalRuleType(domainSignal); + if (domainSignalRuleType == DataRuleType::Linear) { - auto outputValueSignal = - std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); - outputSignals.insert({signal.getGlobalId(), outputValueSignal}); + const auto valueSignalRuleType = getSignalRuleType(signal); + if (valueSignalRuleType == DataRuleType::Explicit) + { + auto outputValueSignal = + std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); + outputSignals.insert({signal.getGlobalId(), outputValueSignal}); + } + else if (valueSignalRuleType == DataRuleType::Constant) + { + auto outputValueSignal = + std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); + outputSignals.insert({signal.getGlobalId(), outputValueSignal}); + } + else + { + throw InvalidParameterException("Unsupported value signal rule type"); + } } else { diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 80f712a..9f70c72 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -30,13 +30,10 @@ DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() void WebsocketClientDeviceImpl::activateStreaming() { - auto self = this->borrowPtr(); - const auto signals = self.getSignals(search::Any()); - websocketStreaming.addSignals(signals); websocketStreaming.setActive(true); - - for (const auto& signal : signals) + for (const auto& [_, signal] : deviceSignals) { + websocketStreaming.addSignals({signal}); auto mirroredSignalConfigPtr = signal.template asPtr(); mirroredSignalConfigPtr.setActiveStreamingSource(websocketStreaming.getConnectionString()); } @@ -52,7 +49,7 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() { this->onSignalInit(signalId, sInfo); }; - streamingClient->onSignalInit(signalInitCallback); + streamingClient->onAvailableSignalInit(signalInitCallback); auto signalUpdatedCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { @@ -60,11 +57,9 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() }; streamingClient->onSignalUpdated(signalUpdatedCallback); - auto domainSignalInitCallback = [this](const StringPtr& dataSignalId, - const StringPtr& domainSignalId, - const DataDescriptorPtr& domainDescriptor) + auto domainSignalInitCallback = [this](const StringPtr& dataSignalId,const StringPtr& domainSignalId) { - this->onDomainSignalInit(dataSignalId, domainSignalId, domainDescriptor); + this->onDomainSignalInit(dataSignalId, domainSignalId); }; streamingClient->onDomainSingalInit(domainSignalInitCallback); @@ -74,6 +69,12 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() }; streamingClient->onAvailableDeviceSignals(availableSignalsCallback); + auto hiddenSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) + { + this->addHiddenSignal(signalId, sInfo); + }; + streamingClient->onHiddenDeviceSignal(hiddenSignalCallback); + websocketStreaming = WebsocketStreaming(streamingClient, connectionString, context); } @@ -103,28 +104,17 @@ void WebsocketClientDeviceImpl::onSignalUpdated(const StringPtr& signalId, const updateSignalProperties(signalIt->second, sInfo); } -void WebsocketClientDeviceImpl::onDomainSignalInit(const StringPtr& signalId, - const StringPtr& domainSignalId, - const DataDescriptorPtr& domainDescriptor) +void WebsocketClientDeviceImpl::onDomainSignalInit(const StringPtr& signalId, const StringPtr& domainSignalId) { - if (!domainDescriptor.assigned()) - return; - // Sets domain signal for data signal if (auto dataSignalIt = deviceSignals.find(signalId); dataSignalIt != deviceSignals.end()) { auto domainSignalIt = deviceSignals.find(domainSignalId); - if (domainSignalIt == deviceSignals.end()) - { - // domain signal is not found in device because was not published as available by server - dataSignalIt->second.asPtr()->createAndAssignDomainSignal(domainDescriptor); - } - else + if (domainSignalIt != deviceSignals.end()) { // domain signal is found in device auto domainSignal = domainSignalIt->second; auto dataSignal = dataSignalIt->second; - domainSignal.asPtr().setMirroredDataDescriptor(domainDescriptor); dataSignal.asPtr().setMirroredDomainSignal(domainSignal); } } @@ -143,6 +133,15 @@ void WebsocketClientDeviceImpl::createDeviceSignals(const std::vectorcontext, this->signals, signalId); + this->addSignal(signal); + deviceSignals.insert({signalId, signal}); + onSignalInit(signalId, sInfo); +} + void WebsocketClientDeviceImpl::updateSignalProperties(const SignalPtr& signal, const SubscribedSignalInfo& sInfo) { signal.asPtr().unlockAllAttributes(); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp index d9da576..9fee184 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_signal_impl.cpp @@ -11,7 +11,7 @@ static constexpr char delimeter = '#'; WebsocketClientSignalImpl::WebsocketClientSignalImpl(const ContextPtr& ctx, const ComponentPtr& parent, const StringPtr& streamingId) - : MirroredSignalBase(ctx, parent, CreateLocalId(streamingId), nullptr) + : MirroredSignal(ctx, parent, CreateLocalId(streamingId), nullptr) , streamingId(streamingId) { } @@ -36,14 +36,6 @@ Bool WebsocketClientSignalImpl::onTriggerEvent(const EventPacketPtr& eventPacket return Self::onTriggerEvent(eventPacket); } -void WebsocketClientSignalImpl::createAndAssignDomainSignal(const DataDescriptorPtr& domainDescriptor) -{ - const auto domainSig = createWithImplementation( - this->context, this->parent.getRef(), CreateLocalId(streamingId + "_time_artificial")); - domainSig->setMirroredDataDescriptor(domainDescriptor); - checkErrorInfo(setMirroredDomainSignal(domainSig.asPtr())); -} - SignalPtr WebsocketClientSignalImpl::onGetDomainSignal() { return mirroredDomainSignal.addRefAndReturn(); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index edf2ce9..4cfe8e9 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -40,12 +40,12 @@ void WebsocketStreamingImpl::onRemoveSignal(const MirroredSignalConfigPtr& /*sig void WebsocketStreamingImpl::onSubscribeSignal(const StringPtr& signalStreamingId) { - streamingClient->subscribeSignals({signalStreamingId.toStdString()}); + streamingClient->subscribeSignal(signalStreamingId.toStdString()); } void WebsocketStreamingImpl::onUnsubscribeSignal(const StringPtr& signalStreamingId) { - streamingClient->unsubscribeSignals({signalStreamingId.toStdString()}); + streamingClient->unsubscribeSignal(signalStreamingId.toStdString()); } void WebsocketStreamingImpl::prepareStreamingClient() @@ -62,6 +62,12 @@ void WebsocketStreamingImpl::prepareStreamingClient() }; streamingClient->onAvailableStreamingSignals(availableSignalsCallback); + auto hiddenSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& /*sInfo*/) + { + this->onHiddenSignal(signalId); + }; + streamingClient->onHiddenStreamingSignal(hiddenSignalCallback); + auto signalSubscriptionAckCallback = [this](const std::string& signalStringId, bool subscribed) { this->triggerSubscribeAck(signalStringId, subscribed); @@ -74,9 +80,18 @@ void WebsocketStreamingImpl::onAvailableSignals(const std::vector& for (const auto& signalId : signalIds) { auto signalStringId = String(signalId); - { - addToAvailableSignals(signalStringId); - } + addToAvailableSignals(signalStringId); + } +} + +void WebsocketStreamingImpl::onHiddenSignal(const std::string& signalId) +{ + if (const auto it = hiddenSignals.find(signalId); it == hiddenSignals.end()) + { + hiddenSignals.insert(signalId); + + auto signalStringId = String(signalId); + addToAvailableSignals(signalStringId); } } diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index c9e46a0..37f7ba3 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -50,7 +50,7 @@ namespace streaming_test_helpers return instance; } - inline daq::SignalPtr createTimeSignal(const daq::ContextPtr& ctx) + inline daq::SignalPtr createLinearTimeSignal(const daq::ContextPtr& ctx) { const size_t nanosecondsInSecond = 1000000000; auto delta = nanosecondsInSecond / 1000; @@ -68,10 +68,14 @@ namespace streaming_test_helpers .build(); auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); + + signal.asPtr().enableCoreEventTrigger(); return signal; } - inline daq::SignalPtr createTestSignal(const daq::ContextPtr& ctx) + inline daq::SignalPtr createExplicitValueSignal(const daq::ContextPtr& ctx, + const daq::StringPtr& name, + const daq::SignalPtr& domainSignal) { auto meta = daq::Dict(); meta["color"] = "green"; @@ -83,64 +87,38 @@ namespace streaming_test_helpers .setValueRange(daq::Range(0, 10)) .setRule(daq::ExplicitDataRule()) .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) - .setName("TestSignal") + .setName(name) .setMetadata(meta) .build(); - auto timeSignal = createTimeSignal(ctx); + auto timeSignal = createLinearTimeSignal(ctx); auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); - signal.setDomainSignal(timeSignal); - signal.setName("TestName"); + signal.setDomainSignal(domainSignal); + signal.setName(name); signal.setDescription("TestDescription"); signal.asPtr().enableCoreEventTrigger(); return signal; } - inline daq::SignalPtr createTestSignalWithoutDomain(const daq::ContextPtr& ctx) + inline daq::SignalPtr createConstantValueSignal(const daq::ContextPtr& ctx, + const daq::StringPtr& name, + const daq::SignalPtr& domainSignal) { - auto meta = daq::Dict(); - meta["color"] = "green"; - meta["used"] = "0"; - auto descriptor = daq::DataDescriptorBuilder() - .setSampleType(daq::SampleType::Float64) - .setUnit(daq::Unit("V", 1, "voltage", "quantity")) - .setValueRange(daq::Range(0, 10)) - .setRule(daq::ExplicitDataRule()) - .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) - .setName("TestSignal") - .setMetadata(meta) - .build(); - - auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); - return signal; - } - - inline daq::SignalPtr createTestSignalWithDomain(const daq::ContextPtr& ctx, - const daq::StringPtr& name, - const daq::SignalPtr& domainSignal) - { - auto meta = daq::Dict(); - meta["color"] = "green"; - meta["used"] = "0"; - - auto descriptor = daq::DataDescriptorBuilder() - .setSampleType(daq::SampleType::Float64) - .setUnit(daq::Unit("V", 1, "voltage", "quantity")) + .setSampleType(daq::SampleType::UInt64) .setValueRange(daq::Range(0, 10)) - .setRule(daq::ExplicitDataRule()) - .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) + .setRule(daq::ConstantDataRule()) .setName(name) - .setMetadata(meta) .build(); - auto timeSignal = createTimeSignal(ctx); + auto timeSignal = createLinearTimeSignal(ctx); auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); - signal.setDomainSignal(domainSignal); + signal.setDomainSignal(timeSignal); signal.setName(name); signal.setDescription("TestDescription"); + signal.asPtr().enableCoreEventTrigger(); return signal; } } diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index b70b785..45fd016 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -268,6 +268,57 @@ TEST(SignalConverter, subscribedDataSignal) ASSERT_EQ(daq::DataRuleType::Explicit, rule.getType()); } +TEST(SignalConverter, subscribedBitfieldSignal) +{ + std::string method; + int result; + unsigned int signalNumber = 3; + std::string tableId = "table id"; + std::string signalId = "signal id"; + std::string memberName = "This is the measured value"; + + nlohmann::json bitsInterpretationObject = + R"([{"description": "Data overrun","index": 0,"uuid": "c214c128-2447-4cee-ba39-6227aed2eff4"}])"_json; + + bsp::SubscribedSignal subscribedSignal(signalNumber, bsp::Logging::logCallback()); + + // some meta information is to be processed to have the signal described: + // -subscribe + // -signal + nlohmann::json subscribeParams; + method = bsp::META_METHOD_SUBSCRIBE; + subscribeParams[bsp::META_SIGNALID] = signalId; + result = subscribedSignal.processSignalMetaInformation(method, subscribeParams); + ASSERT_EQ(result, 0); + + nlohmann::json signalParams; + method = bsp::META_METHOD_SIGNAL; + signalParams[bsp::META_TABLEID] = tableId; + signalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; + signalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_BITFIELD; + signalParams[bsp::META_DEFINITION][bsp::DATA_TYPE_BITFIELD]["bits"] = + bitsInterpretationObject; + signalParams[bsp::META_DEFINITION][bsp::DATA_TYPE_BITFIELD][bsp::META_DATATYPE] = + bsp::DATA_TYPE_UINT64; + signalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_CONSTANT; + + result = subscribedSignal.processSignalMetaInformation(method, signalParams); + ASSERT_EQ(result, 0); + ASSERT_FALSE(subscribedSignal.isTimeSignal()); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto dataDescriptor = subscribedSignalInfo.dataDescriptor; + ASSERT_EQ(subscribedSignalInfo.signalName, memberName); + + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::UInt64); + + auto rule = dataDescriptor.getRule(); + ASSERT_TRUE(rule.assigned()); + ASSERT_EQ(daq::DataRuleType::Constant, rule.getType()); + + ASSERT_TRUE(dataDescriptor.getMetadata().hasKey("bits")); + ASSERT_EQ(dataDescriptor.getMetadata().get("bits"), bitsInterpretationObject.dump()); +} + TEST(SignalConverter, subscribedTimeSignal) { std::string method; diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 38ff14a..7a9ec68 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -17,25 +17,33 @@ class StreamingTest : public testing::Test const std::string StreamingTarget = "/"; const uint16_t ControlPort = daq::streaming_protocol::HTTP_CONTROL_PORT; SignalPtr testDoubleSignal; + SignalPtr testConstantSignal; + SignalPtr testDomainSignal; ContextPtr context; + Int delta; + Int packetOffset; + void SetUp() override { context = NullContext(); - testDoubleSignal = streaming_test_helpers::createTestSignal(context); + testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + testDoubleSignal = streaming_test_helpers::createExplicitValueSignal(context, "DoubleSignal", testDomainSignal); + testConstantSignal = streaming_test_helpers::createConstantValueSignal(context, "ConstantSignal", testDomainSignal); + + delta = testDomainSignal.getDescriptor().getRule().getParameters().get("delta"); + packetOffset = testDomainSignal.getDescriptor().getRule().getParameters().get("start"); } void TearDown() override { } - PacketPtr createDataPacket(const std::vector data, Int packetOffset = 0) + DataPacketPtr getNextDomainPacket(size_t sampleCount) { - auto sampleCount = data.size(); - auto dataDescriptor = testDoubleSignal.getDescriptor(); - auto domainDescriptor = testDoubleSignal.getDomainSignal().getDescriptor(); - auto domainPacket = DataPacket(domainDescriptor, sampleCount, packetOffset); - return DataPacketWithDomain(domainPacket, dataDescriptor, sampleCount); + auto packet = DataPacket(testDomainSignal.getDescriptor(), sampleCount, packetOffset); + packetOffset += sampleCount * delta; + return packet; } }; @@ -138,25 +146,26 @@ TEST_F(StreamingTest, Subscription) client.connect(); ASSERT_TRUE(client.isConnected()); - client.subscribeSignals({testDoubleSignal.getGlobalId()}); + client.subscribeSignal(testDoubleSignal.getGlobalId()); ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); ASSERT_EQ(subscribeAckFuture.get(), testDoubleSignal.getGlobalId()); - client.unsubscribeSignals({testDoubleSignal.getGlobalId()}); + client.unsubscribeSignal(testDoubleSignal.getGlobalId()); ASSERT_EQ(unsubscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); ASSERT_EQ(unsubscribeAckFuture.get(), testDoubleSignal.getGlobalId()); } -TEST_F(StreamingTest, SimpePacket) +TEST_F(StreamingTest, SimpePackets) { std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; - auto packet = createDataPacket(data, 100); + auto sampleCount = data.size(); auto server = std::make_shared(context); server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); + signals.pushBack(testDomainSignal); signals.pushBack(testDoubleSignal); - signals.pushBack(testDoubleSignal.getDomainSignal()); + signals.pushBack(testConstantSignal); return signals; }); server->start(StreamingPort, ControlPort); @@ -164,14 +173,20 @@ TEST_F(StreamingTest, SimpePacket) std::vector receivedPackets; auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); - std::promise subscribeAckPromise; - std::future subscribeAckFuture = subscribeAckPromise.get_future(); + std::map> subscribeAckPromises; + subscribeAckPromises.emplace(testConstantSignal.getGlobalId(), std::promise()); + subscribeAckPromises.emplace(testDoubleSignal.getGlobalId(), std::promise()); + + std::map> subscribeAckFutures; + subscribeAckFutures.emplace(testConstantSignal.getGlobalId(), subscribeAckPromises.at(testConstantSignal.getGlobalId()).get_future()); + subscribeAckFutures.emplace(testDoubleSignal.getGlobalId(), subscribeAckPromises.at(testDoubleSignal.getGlobalId()).get_future()); auto onSubscriptionAck = - [&subscribeAckPromise](const std::string& signalId, bool subscribed) + [&subscribeAckPromises](const std::string& signalId, bool subscribed) { + ASSERT_EQ(subscribeAckPromises.count(signalId), 1u); if (subscribed) - subscribeAckPromise.set_value(signalId); + subscribeAckPromises.at(signalId).set_value(); }; auto onPacket = [&receivedPackets](const StringPtr& signalId, const PacketPtr& packet) @@ -179,22 +194,66 @@ TEST_F(StreamingTest, SimpePacket) receivedPackets.push_back(packet); }; - auto findSignal = [&](const StringPtr& signalId) { return testDoubleSignal; }; - client.onPacket(onPacket); - client.onFindSignal(findSignal); client.onSubscriptionAck(onSubscriptionAck); client.connect(); ASSERT_TRUE(client.isConnected()); - client.subscribeSignals({testDoubleSignal.getGlobalId()}); - ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::seconds(5)), std::future_status::ready); + client.subscribeSignal(testConstantSignal.getGlobalId()); + client.subscribeSignal(testDoubleSignal.getGlobalId()); + + ASSERT_EQ(subscribeAckFutures.at(testConstantSignal.getGlobalId()).wait_for(std::chrono::seconds(5)), std::future_status::ready); + ASSERT_EQ(subscribeAckFutures.at(testDoubleSignal.getGlobalId()).wait_for(std::chrono::seconds(5)), std::future_status::ready); + + auto domainPacket1 = getNextDomainPacket(sampleCount); + auto explicitValuePacket1 = DataPacketWithDomain(domainPacket1, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket1.getRawData(), data.data(), explicitValuePacket1.getRawDataSize()); + auto constantValuePacket1 = ConstantDataPacketWithDomain(domainPacket1, + testConstantSignal.getDescriptor(), + sampleCount, + 1, + {{2, 2}, {4, 4}, {5, 5}}); + + server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket1); + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket1); + + auto domainPacket2 = getNextDomainPacket(sampleCount); + auto explicitValuePacket2 = DataPacketWithDomain(domainPacket2, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket2.getRawData(), data.data(), explicitValuePacket2.getRawDataSize()); + + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket2); + + auto domainPacket3 = getNextDomainPacket(sampleCount); + auto explicitValuePacket3 = DataPacketWithDomain(domainPacket3, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket3.getRawData(), data.data(), explicitValuePacket3.getRawDataSize()); + auto constantValuePacket3 = ConstantDataPacketWithDomain(domainPacket3, + testConstantSignal.getDescriptor(), + sampleCount, + 1); + + server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket3); + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket3); - std::string signalId = testDoubleSignal.getGlobalId(); - server->sendPacketToSubscribers(signalId, packet); std::this_thread::sleep_for(std::chrono::milliseconds(250)); - ASSERT_EQ(receivedPackets.size(), 2u); - ASSERT_EQ(receivedPackets[0].asPtr().getEventId(), event_packet_id::DATA_DESCRIPTOR_CHANGED); - ASSERT_TRUE(BaseObjectPtr::Equals(packet, receivedPackets[1])); + // 3 domain and 6 value packets + ASSERT_EQ(receivedPackets.size(), 9u); + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[0])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[1])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket1, receivedPackets[2])); + + // packet automatically generated by client + auto constantValuePacket2 = ConstantDataPacketWithDomain(domainPacket2, + testConstantSignal.getDescriptor(), + sampleCount, + 5); + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[3])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[4])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[5])); + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[6])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[7])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket3, receivedPackets[8])); } diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index b202a98..85c14b0 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -72,15 +72,16 @@ TEST_F(WebsocketClientDeviceTest, DeviceInfo) TEST_F(WebsocketClientDeviceTest, SignalWithDomain) { - // Create server signal - auto testSignal = streaming_test_helpers::createTestSignal(context); + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); // Setup and start server which will publish created signal auto server = std::make_shared(context); - server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); - signals.pushBack(testSignal); - signals.pushBack(testSignal.getDomainSignal()); + signals.pushBack(testValueSignal); + signals.pushBack(testDomainSignal); return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); @@ -97,23 +98,23 @@ TEST_F(WebsocketClientDeviceTest, SignalWithDomain) ASSERT_EQ(clientDevice.getSignals()[0].getDomainSignal(), clientDevice.getSignals()[1]); ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), - testSignal.getDescriptor())); + testValueSignal.getDescriptor())); ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), - testSignal.getDomainSignal().getDescriptor())); + testValueSignal.getDomainSignal().getDescriptor())); ASSERT_EQ(clientDevice.getSignals()[0].getName(), "TestName"); ASSERT_EQ(clientDevice.getSignals()[0].getDescription(), "TestDescription"); // Publish signal changes - auto descriptor = DataDescriptorBuilderCopy(testSignal.getDescriptor()).build(); - std::string signalId = testSignal.getGlobalId(); - server->broadcastPacket(signalId, DataDescriptorChangedEventPacket(descriptor, testSignal.getDomainSignal().getDescriptor())); + auto descriptor = DataDescriptorBuilderCopy(testValueSignal.getDescriptor()).build(); + std::string signalId = testValueSignal.getGlobalId(); + server->broadcastPacket(signalId, DataDescriptorChangedEventPacket(descriptor, testValueSignal.getDomainSignal().getDescriptor())); std::this_thread::sleep_for(std::chrono::milliseconds(250)); - testSignal.asPtr().unlockAllAttributes(); - testSignal.setName("NewName"); - testSignal.setDescription("NewDescription"); + testValueSignal.asPtr().unlockAllAttributes(); + testValueSignal.setName("NewName"); + testValueSignal.setDescription("NewDescription"); std::this_thread::sleep_for(std::chrono::milliseconds(250)); ASSERT_EQ(clientDevice.getSignals()[0].getName(), "NewName"); @@ -125,13 +126,13 @@ TEST_F(WebsocketClientDeviceTest, SignalWithDomain) ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDescriptor(), descriptor)); ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), - testSignal.getDomainSignal().getDescriptor())); + testValueSignal.getDomainSignal().getDescriptor())); } TEST_F(WebsocketClientDeviceTest, SingleDomainSignal) { // Create server signal - auto testSignal = streaming_test_helpers::createTimeSignal(context); + auto testSignal = streaming_test_helpers::createLinearTimeSignal(context); // Setup and start server which will publish created signal auto server = std::make_shared(context); @@ -154,7 +155,7 @@ TEST_F(WebsocketClientDeviceTest, SingleDomainSignal) TEST_F(WebsocketClientDeviceTest, SingleUnsupportedSignal) { // Create server signal - auto testSignal = streaming_test_helpers::createTestSignalWithoutDomain(context); + auto testSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestSignal", nullptr); // Setup and start server which will publish created signal auto server = std::make_shared(context); @@ -204,9 +205,9 @@ TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) { // Create server signals - auto timeSignal = streaming_test_helpers::createTimeSignal(context); - auto dataSignal1 = streaming_test_helpers::createTestSignalWithDomain(context, "Data1", timeSignal); - auto dataSignal2 = streaming_test_helpers::createTestSignalWithDomain(context, "Data2", timeSignal); + auto timeSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto dataSignal1 = streaming_test_helpers::createExplicitValueSignal(context, "Data1", timeSignal); + auto dataSignal2 = streaming_test_helpers::createExplicitValueSignal(context, "Data2", timeSignal); auto server = std::make_shared(context); server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { From b0839835808e0d2807a745de0b48922e452b96bf Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 10 Apr 2024 15:26:54 +0200 Subject: [PATCH 058/127] Refactor Input signal wrappers: * use NumberPtr for domain value calculations * erase old cached values of constant signal --- .../websocket_streaming/input_signal.h | 27 +-- .../websocket_streaming/src/input_signal.cpp | 173 +++++++++++------- .../src/streaming_client.cpp | 11 +- .../tests/test_streaming.cpp | 119 +++++++++++- 4 files changed, 241 insertions(+), 89 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index e2a7dfc..e7f5776 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -42,8 +42,8 @@ class InputSignalBase const InputSignalBasePtr& domainSignal, daq::streaming_protocol::LogCallback logCb); - virtual void processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount); - virtual DataPacketPtr generateDataPacket(uint64_t packetOffset, + virtual void processSamples(const NumberPtr& startDomainValue, const uint8_t* data, size_t sampleCount); + virtual DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, size_t sampleCount, const DataPacketPtr& domainPacket) = 0; @@ -82,7 +82,7 @@ class InputDomainSignal : public InputSignalBase const SubscribedSignalInfo& signalInfo, streaming_protocol::LogCallback logCb); - DataPacketPtr generateDataPacket(uint64_t packetOffset, + DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, size_t sampleCount, const DataPacketPtr& domainPacket) override; @@ -102,7 +102,7 @@ class InputExplicitDataSignal : public InputSignalBase const InputSignalBasePtr& domainSignal, streaming_protocol::LogCallback logCb); - DataPacketPtr generateDataPacket(uint64_t packetOffset, + DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, size_t sampleCount, const DataPacketPtr& domainPacket) override; @@ -113,7 +113,7 @@ class InputExplicitDataSignal : public InputSignalBase class InputConstantDataSignal : public InputSignalBase { public: - using ConstantValueType = + using SignalValueType = std::variant; InputConstantDataSignal(const std::string& signalId, @@ -122,8 +122,8 @@ class InputConstantDataSignal : public InputSignalBase const InputSignalBasePtr& domainSignal, streaming_protocol::LogCallback logCb); - void processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount) override; - DataPacketPtr generateDataPacket(uint64_t packetOffset, + void processSamples(const NumberPtr& absoluteStartDomainValue, const uint8_t* data, size_t sampleCount) override; + DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, size_t sampleCount, const DataPacketPtr& domainPacket) override; @@ -132,18 +132,21 @@ class InputConstantDataSignal : public InputSignalBase private: template - static ConstantValueType extractConstantValue(const uint8_t* pValue); + static SignalValueType extractConstantValue(const uint8_t* pValue); template static DataPacketPtr createTypedConstantPacket( - ConstantValueType startValue, - const std::vector>& otherValues, + SignalValueType startValue, + const std::vector>& otherValues, size_t sampleCount, const DataPacketPtr& domainPacket, const DataDescriptorPtr& dataDescriptor); - std::map cachedConstantValues; - std::optional lastConstantValue; + NumberPtr calcDomainValue(const NumberPtr& startDomainValue, const uint64_t sampleIndex); + NumberPtr getDomainRuleDelta(); + uint32_t calcPosition(const NumberPtr& startDomainValue, const NumberPtr& domainValue); + + std::map cachedSignalValues; }; inline InputSignalBasePtr InputSignal(const std::string& signalId, diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 1ce9bfa..3fa3fa7 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -25,7 +25,7 @@ InputSignalBase::InputSignalBase(const std::string& signalId, { } -void InputSignalBase::processSamples(uint64_t /*packetOffset*/, const uint8_t* /*data*/, size_t /*sampleCount*/) +void InputSignalBase::processSamples(const NumberPtr& /*startDomainValue*/, const uint8_t* /*data*/, size_t /*sampleCount*/) { } @@ -93,16 +93,16 @@ InputDomainSignal::InputDomainSignal(const std::string& signalId, { } -DataPacketPtr InputDomainSignal::generateDataPacket(uint64_t packetOffset, +DataPacketPtr InputDomainSignal::generateDataPacket(const NumberPtr& packetOffset, const uint8_t* /*data*/, size_t sampleCount, const DataPacketPtr& /*domainPacket*/) { std::scoped_lock lock(descriptorsSync); - if (!lastDomainPacket.assigned() || lastDomainPacket.getOffset().getIntValue() != (Int) packetOffset) + if (!lastDomainPacket.assigned() || lastDomainPacket.getOffset() != packetOffset) { - lastDomainPacket = DataPacket(currentDataDescriptor, sampleCount, (Int) packetOffset); + lastDomainPacket = DataPacket(currentDataDescriptor, sampleCount, packetOffset); } return lastDomainPacket; @@ -127,7 +127,7 @@ InputExplicitDataSignal::InputExplicitDataSignal(const std::string& signalId, { } -DataPacketPtr InputExplicitDataSignal::generateDataPacket(uint64_t /*packetOffset*/, +DataPacketPtr InputExplicitDataSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* data, size_t sampleCount, const DataPacketPtr& domainPacket) @@ -163,131 +163,164 @@ InputConstantDataSignal::InputConstantDataSignal(const std::string& signalId, { } -void InputConstantDataSignal::processSamples(uint64_t timestamp, const uint8_t* data, size_t sampleCount) +NumberPtr InputConstantDataSignal::calcDomainValue(const NumberPtr& startDomainValue, const uint64_t sampleIndex) +{ + NumberPtr domainRuleDelta = getDomainRuleDelta(); + + if (startDomainValue.getCoreType() == CoreType::ctFloat) + return startDomainValue.getFloatValue() + sampleIndex * domainRuleDelta.getFloatValue(); + else + return startDomainValue.getIntValue() + sampleIndex * domainRuleDelta.getIntValue(); +} + +void InputConstantDataSignal::processSamples(const NumberPtr& absoluteStartDomainValue, const uint8_t* data, size_t sampleCount) { auto sampleType = currentDataDescriptor.getSampleType(); const auto sampleSize = getSampleSize(sampleType); const auto bufferSize = sampleCount * (sampleSize + sizeof(uint64_t)); - Int delta = inputDomainSignal->getSignalDescriptor().getRule().getParameters().get("delta"); - for (size_t offset = 0; offset < bufferSize; offset += sizeof(uint64_t) + sampleSize) + for (size_t addrOffset = 0; addrOffset < bufferSize; addrOffset += sizeof(uint64_t) + sampleSize) { - const uint64_t* pIndex = reinterpret_cast(data + offset); - const uint8_t* pConstantValue = data + offset + sizeof(uint64_t); - NumberPtr timeStamp = (Int)(*pIndex) * delta + timestamp; + const uint64_t* pIndex = reinterpret_cast(data + addrOffset); + const uint8_t* pSignalValue = data + addrOffset + sizeof(uint64_t); + auto domainValue = calcDomainValue(absoluteStartDomainValue, *pIndex); - ConstantValueType constantValue; + SignalValueType signalValue; switch (sampleType) { case daq::SampleType::Int8: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::Int16: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::Int32: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::Int64: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::UInt8: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::UInt16: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::UInt32: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::UInt64: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::Float32: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; case daq::SampleType::Float64: - constantValue = extractConstantValue(pConstantValue); + signalValue = extractConstantValue(pSignalValue); break; default: return; } - cachedConstantValues.insert_or_assign(timeStamp, constantValue); + cachedSignalValues.insert_or_assign(domainValue, signalValue); } } -DataPacketPtr InputConstantDataSignal::generateDataPacket(uint64_t /*packetOffset*/, +NumberPtr InputConstantDataSignal::getDomainRuleDelta() +{ + return inputDomainSignal->getSignalDescriptor().getRule().getParameters().get("delta"); +} + +uint32_t InputConstantDataSignal::calcPosition(const NumberPtr& startDomainValue, const NumberPtr& domainValue) +{ + NumberPtr domainRuleDelta = getDomainRuleDelta(); + + if (startDomainValue.getCoreType() == CoreType::ctFloat) + return (domainValue.getFloatValue() - startDomainValue.getFloatValue()) / domainRuleDelta.getFloatValue(); + else + return (domainValue.getIntValue() - startDomainValue.getIntValue()) / domainRuleDelta.getIntValue(); +} + +DataPacketPtr InputConstantDataSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* /*data*/, size_t sampleCount, const DataPacketPtr& domainPacket) { + if (sampleCount == 0) + return nullptr; + std::scoped_lock lock(descriptorsSync); - auto sampleType = currentDataDescriptor.getSampleType(); + if (domainPacket.getDataDescriptor() != inputDomainSignal->getSignalDescriptor()) + { + STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: domain descriptor mismatch"); + return nullptr; + } - Int delta = domainPacket.getDataDescriptor().getRule().getParameters().get("delta"); - Int startOffset = domainPacket.getOffset(); - Int endOffset = startOffset + delta * sampleCount; + NumberPtr packetStartDomainValue = domainPacket.getOffset(); + NumberPtr packetEndDomainValue = calcDomainValue(packetStartDomainValue, sampleCount - 1); - DataPacketPtr dataPacket; - ConstantValueType startValue; + // search for cached value to be used as packet start value + // it should have smaller or equal domain value (map key) in comparison with packet domain value + auto itStart = cachedSignalValues.end(); + for (auto it = cachedSignalValues.begin(); it != cachedSignalValues.end(); ++it) + { + if (it->first <= packetStartDomainValue) + itStart = it; + } - if (auto it = cachedConstantValues.find(startOffset); it != cachedConstantValues.end()) - startValue = it->second; - else if (lastConstantValue.has_value()) - startValue = lastConstantValue.value(); - else + // start value is not found + if (itStart == cachedSignalValues.end()) + { + STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: packet start value is unknown"); return nullptr; + } - // TODO erase cached values that was already used to generate packets - std::vector> otherValues; - if (!cachedConstantValues.empty()) - { - auto itStart = cachedConstantValues.upper_bound(startOffset); - auto itEnd = cachedConstantValues.lower_bound(endOffset); + // start value found + SignalValueType packetStartValue = itStart->second; - for (auto it = itStart; it != itEnd; ++it) + // search for other cached values which belong to generated packet domain values range + std::vector> packetOtherValues; + { + auto it = itStart; + ++it; + for (; it != cachedSignalValues.end() && it->first <= packetEndDomainValue; ++it) { - uint32_t pos = (it->first.getIntValue() - startOffset) / delta; - otherValues.emplace_back(pos, it->second); - lastConstantValue = it->second; + auto pos = calcPosition(packetStartDomainValue, it->first); + packetOtherValues.emplace_back(pos, it->second); } } - switch (sampleType) + // erase values related to previously generated packets + if (itStart != cachedSignalValues.begin()) + { + cachedSignalValues.erase(cachedSignalValues.begin(), itStart); + } + + switch (currentDataDescriptor.getSampleType()) { case daq::SampleType::Int8: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::Int16: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::Int32: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::Int64: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::UInt8: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::UInt16: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::UInt32: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::UInt64: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::Float32: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); case daq::SampleType::Float64: - return createTypedConstantPacket(startValue, otherValues, sampleCount, domainPacket, currentDataDescriptor); - break; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); default: + STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: unsupported sample type"); return nullptr; } } @@ -303,15 +336,15 @@ bool InputConstantDataSignal::isCountable() const } template -InputConstantDataSignal::ConstantValueType InputConstantDataSignal::extractConstantValue(const uint8_t* pValue) +InputConstantDataSignal::SignalValueType InputConstantDataSignal::extractConstantValue(const uint8_t* pValue) { - return ConstantValueType(*(reinterpret_cast(pValue))); + return SignalValueType(*(reinterpret_cast(pValue))); } template DataPacketPtr InputConstantDataSignal::createTypedConstantPacket( - ConstantValueType startValue, - const std::vector>& otherValues, + SignalValueType startValue, + const std::vector>& otherValues, size_t sampleCount, const DataPacketPtr& domainPacket, const DataDescriptorPtr& dataDescriptor) diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 1218a77..3b99567 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -342,6 +342,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& { std::string id = subscribedSignal.signalId(); + NumberPtr domainValue = static_cast(timeStamp); InputSignalBasePtr inputSignal = nullptr; if (auto availableSigIt = availableSignals.find(id); availableSigIt != availableSignals.end()) @@ -358,17 +359,17 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& DataPacketPtr domainPacket; if (inputSignal->isDomainSignal()) { - domainPacket = inputSignal->generateDataPacket(timeStamp, data, valueCount, nullptr); + domainPacket = inputSignal->generateDataPacket(domainValue, data, valueCount, nullptr); if (domainPacket.assigned()) onPacketCallback(id, domainPacket); } else { domainPacket = - inputSignal->getInputDomainSignal()->generateDataPacket(timeStamp, nullptr, valueCount, nullptr); + inputSignal->getInputDomainSignal()->generateDataPacket(domainValue, nullptr, valueCount, nullptr); if (domainPacket.assigned()) onPacketCallback(inputSignal->getInputDomainSignal()->getSignalId(), domainPacket); - auto packet = inputSignal->generateDataPacket(timeStamp, data, valueCount, domainPacket); + auto packet = inputSignal->generateDataPacket(domainValue, data, valueCount, domainPacket); if (packet.assigned()) onPacketCallback(id, packet); } @@ -380,7 +381,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& { if (!relatedSignal->isCountable()) { - auto packet = relatedSignal->generateDataPacket(timeStamp, nullptr, valueCount, domainPacket); + auto packet = relatedSignal->generateDataPacket(domainValue, nullptr, valueCount, domainPacket); if (packet.assigned()) onPacketCallback(relatedSignal->getSignalId(), packet); } @@ -388,7 +389,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& } else { - inputSignal->processSamples(timeStamp, data, valueCount); + inputSignal->processSamples(domainValue, data, valueCount); } } } diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 7a9ec68..b60bcf8 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -155,7 +155,8 @@ TEST_F(StreamingTest, Subscription) ASSERT_EQ(unsubscribeAckFuture.get(), testDoubleSignal.getGlobalId()); } -TEST_F(StreamingTest, SimpePackets) +// sends explicit value packet after constant value packet +TEST_F(StreamingTest, PacketsCorrectSequence) { std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; auto sampleCount = data.size(); @@ -212,7 +213,7 @@ TEST_F(StreamingTest, SimpePackets) testConstantSignal.getDescriptor(), sampleCount, 1, - {{2, 2}, {4, 4}, {5, 5}}); + {{2, 2}, {4, 4}, {6, 5}}); server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket1); server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket1); @@ -257,3 +258,117 @@ TEST_F(StreamingTest, SimpePackets) ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[7])); ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket3, receivedPackets[8])); } + +// sends explicit value packet before constant value packet +// this results in client side constant packets that track the last changes of the constant rule, +// but with a delay equivalent to the size of one packet, and potentially missed intermediate changes. +TEST_F(StreamingTest, PacketsIncorrectSequence) +{ + std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; + auto sampleCount = data.size(); + + auto server = std::make_shared(context); + server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { + auto signals = List(); + signals.pushBack(testDomainSignal); + signals.pushBack(testDoubleSignal); + signals.pushBack(testConstantSignal); + return signals; + }); + server->start(StreamingPort, ControlPort); + + std::vector receivedPackets; + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); + + std::map> subscribeAckPromises; + subscribeAckPromises.emplace(testConstantSignal.getGlobalId(), std::promise()); + subscribeAckPromises.emplace(testDoubleSignal.getGlobalId(), std::promise()); + + std::map> subscribeAckFutures; + subscribeAckFutures.emplace(testConstantSignal.getGlobalId(), subscribeAckPromises.at(testConstantSignal.getGlobalId()).get_future()); + subscribeAckFutures.emplace(testDoubleSignal.getGlobalId(), subscribeAckPromises.at(testDoubleSignal.getGlobalId()).get_future()); + + auto onSubscriptionAck = + [&subscribeAckPromises](const std::string& signalId, bool subscribed) + { + ASSERT_EQ(subscribeAckPromises.count(signalId), 1u); + if (subscribed) + subscribeAckPromises.at(signalId).set_value(); + }; + + auto onPacket = [&receivedPackets](const StringPtr& signalId, const PacketPtr& packet) + { + receivedPackets.push_back(packet); + }; + + client.onPacket(onPacket); + client.onSubscriptionAck(onSubscriptionAck); + client.connect(); + ASSERT_TRUE(client.isConnected()); + + client.subscribeSignal(testConstantSignal.getGlobalId()); + client.subscribeSignal(testDoubleSignal.getGlobalId()); + + ASSERT_EQ(subscribeAckFutures.at(testConstantSignal.getGlobalId()).wait_for(std::chrono::seconds(5)), std::future_status::ready); + ASSERT_EQ(subscribeAckFutures.at(testDoubleSignal.getGlobalId()).wait_for(std::chrono::seconds(5)), std::future_status::ready); + + auto domainPacket1 = getNextDomainPacket(sampleCount); + auto explicitValuePacket1 = DataPacketWithDomain(domainPacket1, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket1.getRawData(), data.data(), explicitValuePacket1.getRawDataSize()); + auto constantValuePacket1 = ConstantDataPacketWithDomain(domainPacket1, + testConstantSignal.getDescriptor(), + sampleCount, + 1, + {{2, 2}, {4, 4}, {6, 5}}); + + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket1); + server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket1); + + auto domainPacket2 = getNextDomainPacket(sampleCount); + auto explicitValuePacket2 = DataPacketWithDomain(domainPacket2, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket2.getRawData(), data.data(), explicitValuePacket2.getRawDataSize()); + + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket2); + + auto domainPacket3 = getNextDomainPacket(sampleCount); + auto explicitValuePacket3 = DataPacketWithDomain(domainPacket3, testDoubleSignal.getDescriptor(), sampleCount); + std::memcpy(explicitValuePacket3.getRawData(), data.data(), explicitValuePacket3.getRawDataSize()); + auto constantValuePacket3 = ConstantDataPacketWithDomain(domainPacket3, + testConstantSignal.getDescriptor(), + sampleCount, + 1); + + server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket3); + server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket3); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // packets automatically generated by client + auto constantValuePacket2 = ConstantDataPacketWithDomain(domainPacket2, + testConstantSignal.getDescriptor(), + sampleCount, + 5); + + // packets automatically generated by client + auto clientConstValuePacket3 = ConstantDataPacketWithDomain(domainPacket3, + testConstantSignal.getDescriptor(), + sampleCount, + 5); + + // 3 domain and 6 value packets + ASSERT_EQ(receivedPackets.size(), 8u); + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[0])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[1])); + // no constant packet generated since the signal value is unknown + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[2])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[3])); + // contains only last change of constant + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[4])); + + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[5])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[6])); + // value update is not yet applied, provides old value + ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket3, receivedPackets[7])); +} From 1be50e8b5771f234e8cc4806a5ce4a7530c7c4d5 Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Tue, 23 Apr 2024 09:10:26 +0200 Subject: [PATCH 059/127] Cleanup CMake install --- shared/libraries/websocket_streaming/CMakeLists.txt | 5 ++++- shared/libraries/websocket_streaming/src/CMakeLists.txt | 6 +----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/shared/libraries/websocket_streaming/CMakeLists.txt b/shared/libraries/websocket_streaming/CMakeLists.txt index 166c415..f4d93d3 100644 --- a/shared/libraries/websocket_streaming/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME ${SDK_TARGET_NAMESPACE}_websocket_streaming) -project(OpenDaqStreaming CXX) +project(OpenDaqStreaming + VERSION 4.0.0 + LANGUAGES CXX +) add_subdirectory(src) diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index 0f8860e..bd7290d 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -1,10 +1,6 @@ set(BASE_NAME websocket_streaming) set(LIB_NAME ${SDK_TARGET_NAME}_${BASE_NAME}) -set(LIB_MAJOR_VERSION 0) -set(LIB_MINOR_VERSION 4) -set(LIB_PATCH_VERSION 0) - set(SRC_Cpp signal_descriptor_converter.cpp streaming_client.cpp streaming_server.cpp @@ -87,4 +83,4 @@ endif() set_target_properties(${LIB_NAME} PROPERTIES PUBLIC_HEADER "${SRC_PublicHeaders}") -opendaq_set_output_lib_name(${LIB_NAME} ${LIB_MAJOR_VERSION}) +opendaq_set_output_lib_name(${LIB_NAME} ${PROJECT_VERSION_MAJOR}) From b0f76fe007cdab089e9d65a74c5f8cb078693718 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:41:19 +0200 Subject: [PATCH 060/127] Supporting connection with ipv6 connection string (openDAQ/openDAQ#281) --- .../websocket_streaming_client_module_impl.cpp | 18 +++++++++++++++--- .../test_websocket_streaming_client_module.cpp | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index d4bfe53..c113e11 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -26,13 +26,21 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex { [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - auto connectionString = fmt::format("{}{}:{}{}", + auto connectionStringIpv4 = fmt::format("{}{}:{}{}", WebsocketDevicePrefix, discoveredDevice.ipv4Address, discoveredDevice.servicePort, discoveredDevice.getPropertyOrDefault("path", "/")); + auto connectionStringIpv6 = fmt::format("{}[{}]:{}{}", + WebsocketDevicePrefix, + discoveredDevice.ipv6Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/")); auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); - cap.addConnectionString(connectionString); + cap.addConnectionString(connectionStringIpv4); + cap.addAddress(discoveredDevice.ipv4Address); + cap.addConnectionString(connectionStringIpv6); + cap.addAddress("[" + discoveredDevice.ipv6Address + "]"); cap.setConnectionType("TCP/IP"); cap.setPrefix("daq.lt"); return cap; @@ -143,7 +151,11 @@ StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(con if (connectionString.getLength() != 0) return connectionString; - StringPtr address = capability.getPropertyValue("address"); + StringPtr address; + if (ListPtr addresses = capability.getAddresses(); addresses.getCount() > 0) + { + address = addresses[0]; + } if (!address.assigned() || address.toStdString().empty()) throw InvalidParameterException("Device address is not set"); diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 3b9c999..12c8f33 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -98,6 +98,8 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringCorrect) auto module = CreateModule(); ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://device8")); + ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://[::1]")); + ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]")); } TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringNull) @@ -190,7 +192,7 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) serverCapability.setPrefix("daq.lt"); ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - serverCapability.setPropertyValue("address", "123.123.123.123"); + serverCapability.addAddress("123.123.123.123"); ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); serverCapability.addProperty(IntProperty("Port", 1234)); From fb1dbe75972c78dfdb661ced6e3683b2f281dfb5 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 25 Apr 2024 13:27:51 +0200 Subject: [PATCH 061/127] Move streaming creation from device impl to the module manager: * the opcua client module no longer manages streaming heuristic configuration; this responsibility has shifted to the module manager. * opcua device implementation no longer handles server capabilities or creates streaming objects, as it is done by the module manager * new interfaces added, IMirroredDevice and IMirroredDeviceConfig, to manage streaming sources on per-device basis for configuration-enabled devices. * streaming heuristic config is supported only when connecting via device info "daq://" prefix * fix the device type created for native configuration device --- .../src/websocket_streaming_client_module_impl.cpp | 4 ++-- .../tests/test_websocket_streaming_client_module.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index c113e11..d4b3aee 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -104,7 +104,7 @@ bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringP bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) { - if (connectionString.assigned()) + if (connectionString.assigned() && connectionString != "") { return onAcceptsConnectionParameters(connectionString, config); } @@ -136,7 +136,7 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& if (!onAcceptsStreamingConnectionParameters(streamingConnectionString, config)) throw InvalidParameterException(); - if (!streamingConnectionString.assigned()) + if (!streamingConnectionString.assigned() || streamingConnectionString == "") streamingConnectionString = tryCreateWebsocketConnectionString(config); return WebsocketStreaming(streamingConnectionString, context); diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 12c8f33..169bf5b 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -182,7 +182,7 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorre ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.ws://device8")); } -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) +TEST_F(WebsocketStreamingClientModuleTest, AcceptsServerCapability) { auto context = NullContext(); ModulePtr module; @@ -191,12 +191,15 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConfig) ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); serverCapability.setPrefix("daq.lt"); ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters("", serverCapability)); serverCapability.addAddress("123.123.123.123"); ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); + ASSERT_FALSE(module.acceptsStreamingConnectionParameters("", serverCapability)); serverCapability.addProperty(IntProperty("Port", 1234)); ASSERT_TRUE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); + ASSERT_TRUE(module.acceptsStreamingConnectionParameters("", serverCapability)); } TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) From b6811db04abc73a9449668db48d156fa96886389 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 25 Apr 2024 14:56:00 +0200 Subject: [PATCH 062/127] Clone property object to create default config from type --- .../src/websocket_streaming_server_impl.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index aa8092c..7cc3cce 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -41,19 +41,11 @@ PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig() ServerTypePtr WebsocketStreamingServerImpl::createType() { - auto configurationCallback = [](IBaseObject* input, IBaseObject** output) -> ErrCode - { - PropertyObjectPtr propObjPtr; - ErrCode errCode = wrapHandlerReturn(&WebsocketStreamingServerImpl::createDefaultConfig, propObjPtr); - *output = propObjPtr.detach(); - return errCode; - }; - return ServerType( "openDAQ LT Streaming", "openDAQ LT Streaming server", "Publishes device signals as a flat list and streams data over WebsocketTcp protocol", - configurationCallback); + WebsocketStreamingServerImpl::createDefaultConfig()); } void WebsocketStreamingServerImpl::onStopServer() From 78fead11a05ddd36dcd24499bb955387bec87e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE?= Date: Tue, 7 May 2024 10:11:48 +0200 Subject: [PATCH 063/127] Bump version from 3.0.0 to 3.1.0 --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index f448418..e4b7f5c 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 3.0.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION 3.1.0 LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index 2bdfb3c..b7baad0 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 3.0.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION 3.1.0 LANGUAGES CXX) add_subdirectory(src) From 8b1d83821649802080480ec5fe42fb74d68c6295 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 8 May 2024 16:55:56 +0200 Subject: [PATCH 064/127] Fix - set start time param of linear rule to 0 for streaming-lt signals --- .../websocket_streaming/src/signal_descriptor_converter.cpp | 3 +-- .../tests/test_signal_descriptor_converter.cpp | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 3579cbc..3b26ff9 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -187,8 +187,7 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco break; case daq::streaming_protocol::RULETYPE_LINEAR: { - uint64_t start = subscribedSignal.time(); - return LinearDataRule(subscribedSignal.linearDelta(), start); + return LinearDataRule(subscribedSignal.linearDelta(), 0); } break; default: diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 45fd016..39a7cbf 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -329,7 +329,6 @@ TEST(SignalConverter, subscribedTimeSignal) std::string memberName = "This is the time"; uint64_t ticksPerSecond = 10000000; - uint64_t startTime = 100000; uint64_t linearDelta = 1000; int32_t unitId = bsp::Unit::UNIT_ID_SECONDS; std::string unitDisplayName = "s"; @@ -367,7 +366,6 @@ TEST(SignalConverter, subscribedTimeSignal) timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; result = subscribedSignal.processSignalMetaInformation(method, timeSignalParams); ASSERT_EQ(result, 0); - subscribedSignal.setTime(startTime); ASSERT_TRUE(subscribedSignal.isTimeSignal()); auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); @@ -389,7 +387,7 @@ TEST(SignalConverter, subscribedTimeSignal) uint64_t resultDelta = params.get("delta"); uint64_t resultStart = params.get("start"); ASSERT_EQ(resultDelta, linearDelta); - ASSERT_EQ(resultStart, startTime); + ASSERT_EQ(resultStart, 0); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 45885763c3da0d71da3ce603cee1ac69ce4a7217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mikoli=C4=8D?= Date: Tue, 23 Apr 2024 16:11:50 +0200 Subject: [PATCH 065/127] Add authentication provider to Context object --- .../tests/test_websocket_streaming_server_module.cpp | 4 +++- .../websocket_streaming/tests/streaming_test_helpers.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index e48a601..4a6b6c8 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -10,6 +10,7 @@ #include #include #include +#include class WebsocketStreamingServerModuleTest : public testing::Test { @@ -32,7 +33,8 @@ static InstancePtr CreateTestInstance() { const auto logger = Logger(); const auto moduleManager = ModuleManager("[[none]]"); - const auto context = Context(Scheduler(logger), logger, TypeManager(), moduleManager); + const auto authenticationProvider = AuthenticationProvider(); + const auto context = Context(Scheduler(logger), logger, TypeManager(), moduleManager, authenticationProvider); const ModulePtr deviceModule(MockDeviceModule_Create(context)); moduleManager.addModule(deviceModule); diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 37f7ba3..4bf19f3 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "streaming_protocol/Unit.hpp" namespace streaming_test_helpers @@ -35,7 +36,8 @@ namespace streaming_test_helpers inline daq::InstancePtr createServerInstance() { const auto moduleManager = daq::ModuleManager("[[none]]"); - auto context = Context(nullptr, daq::Logger(), daq::TypeManager(), moduleManager); + const auto authenticationProvider = daq::AuthenticationProvider(); + auto context = Context(nullptr, daq::Logger(), daq::TypeManager(), moduleManager, authenticationProvider); const daq::ModulePtr deviceModule(MockDeviceModule_Create(context)); moduleManager.addModule(deviceModule); From ed029c5b36b4837feb12f4871cb5b2813ecf59de Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 29 Apr 2024 20:18:31 +0200 Subject: [PATCH 066/127] Streaming connection/configuration updates: * Enable support of any streaming protocol type for native-config device * order streaming sources of mirrored device by protocols priority set up by user config * test MIN_HOPS/MIN_CONNECTIONS streaming heuristics with native-config device * use streaming heuristic config for "daq://", "daq.nd://", "daq.opcua://" connection prefixes * add addStreaming method on IDevice interface * remove "FALLBACKS" streaming heuristic * use separate connections for native streaming and native configuration * extend IModule interface to create connection string from ServerCapability instead of creating streaming directly from capability * support config object with connection parameters when creating streaming from module * rework module integration tests for subdevices testing --- .../websocket_streaming_client_module_impl.h | 5 +- ...websocket_streaming_client_module_impl.cpp | 76 +++++++++---------- ...test_websocket_streaming_client_module.cpp | 24 ++++-- .../src/async_packet_reader.cpp | 1 + .../src/websocket_streaming_server.cpp | 1 + 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index fbb062f..b3ea5ce 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -34,10 +34,13 @@ class WebsocketStreamingClientModule final : public Module bool onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; bool onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; StreamingPtr onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) override; + StringPtr onCreateConnectionString(const ServerCapabilityPtr& serverCapability) override; private: - static StringPtr tryCreateWebsocketConnectionString(const ServerCapabilityPtr& capability); static DeviceTypePtr createWebsocketDeviceType(); + static StringPtr createUrlConnectionString(const StringPtr& host, + const IntegerPtr& port, + const StringPtr& path); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index d4b3aee..c977183 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -26,16 +26,16 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex { [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - auto connectionStringIpv4 = fmt::format("{}{}:{}{}", - WebsocketDevicePrefix, - discoveredDevice.ipv4Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/")); - auto connectionStringIpv6 = fmt::format("{}[{}]:{}{}", - WebsocketDevicePrefix, - discoveredDevice.ipv6Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/")); + auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); + auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( + "[" + discoveredDevice.ipv6Address + "]", + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); cap.addConnectionString(connectionStringIpv4); cap.addAddress(discoveredDevice.ipv4Address); @@ -108,61 +108,53 @@ bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(cons { return onAcceptsConnectionParameters(connectionString, config); } - else if (config.assigned()) - { - if (config.getPropertyValue("protocolId") == WebsocketDeviceTypeId) - { - try - { - auto generatedConnectionString = tryCreateWebsocketConnectionString(config); - return true; - } - catch (const std::exception& e) - { - LOG_W("Failed to interpret streaming info config: {}", e.what()) - } - } - } return false; } StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) { - StringPtr streamingConnectionString = connectionString; - - if (!streamingConnectionString.assigned() && !config.assigned()) + if (!connectionString.assigned()) throw ArgumentNullException(); - if (!onAcceptsStreamingConnectionParameters(streamingConnectionString, config)) + if (!onAcceptsStreamingConnectionParameters(connectionString, config)) throw InvalidParameterException(); - if (!streamingConnectionString.assigned() || streamingConnectionString == "") - streamingConnectionString = tryCreateWebsocketConnectionString(config); - - return WebsocketStreaming(streamingConnectionString, context); + return WebsocketStreaming(connectionString, context); } -StringPtr WebsocketStreamingClientModule::tryCreateWebsocketConnectionString(const ServerCapabilityPtr& capability) +StringPtr WebsocketStreamingClientModule::onCreateConnectionString(const ServerCapabilityPtr& serverCapability) { - if (capability == nullptr) - throw InvalidParameterException("Capability is not set"); + if (serverCapability.getProtocolId() != "opendaq_lt_streaming") + return nullptr; - StringPtr connectionString = capability.getPropertyValue("PrimaryConnectionString"); + StringPtr connectionString = serverCapability.getConnectionString(); if (connectionString.getLength() != 0) return connectionString; StringPtr address; - if (ListPtr addresses = capability.getAddresses(); addresses.getCount() > 0) + if (ListPtr addresses = serverCapability.getAddresses(); addresses.getCount() > 0) { address = addresses[0]; } if (!address.assigned() || address.toStdString().empty()) - throw InvalidParameterException("Device address is not set"); + throw InvalidParameterException("Address is not set"); - auto port = capability.getPropertyValue("Port").template asPtr(); - connectionString = String(fmt::format("{}{}:{}", WebsocketDevicePrefix, address, port)); + if (!serverCapability.hasProperty("Port")) + throw InvalidParameterException("Port is not set"); + auto port = serverCapability.getPropertyValue("Port").template asPtr(); - return connectionString; + return WebsocketStreamingClientModule::createUrlConnectionString( + address, + port, + serverCapability.hasProperty("Path") ? serverCapability.getPropertyValue("Path") : "" + ); +} + +StringPtr WebsocketStreamingClientModule::createUrlConnectionString(const StringPtr& host, + const IntegerPtr& port, + const StringPtr& path) +{ + return String(fmt::format("daq.lt://{}:{}{}", host, port, path)); } DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType() diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 169bf5b..40f7008 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -182,26 +182,34 @@ TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorre ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.ws://device8")); } -TEST_F(WebsocketStreamingClientModuleTest, AcceptsServerCapability) +TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) { auto context = NullContext(); ModulePtr module; createModule(&module, context); + StringPtr connectionString; + + ServerCapabilityConfigPtr serverCapabilityIgnored = ServerCapability("test", "test", ProtocolType::Unknown); + ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapabilityIgnored)); + ASSERT_FALSE(connectionString.assigned()); + ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); - serverCapability.setPrefix("daq.lt"); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters("", serverCapability)); + ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); serverCapability.addAddress("123.123.123.123"); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - ASSERT_FALSE(module.acceptsStreamingConnectionParameters("", serverCapability)); + ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); serverCapability.addProperty(IntProperty("Port", 1234)); - ASSERT_TRUE(module.acceptsStreamingConnectionParameters(nullptr, serverCapability)); - ASSERT_TRUE(module.acceptsStreamingConnectionParameters("", serverCapability)); + ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); + ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234"); + + serverCapability.addProperty(StringProperty("Path", "/path")); + ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); + ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234/path"); } + TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) { auto module = CreateModule(); diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index 1962226..d2555d9 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -32,6 +32,7 @@ void AsyncPacketReader::start() void AsyncPacketReader::stop() { + onPacketCallback = [](const SignalPtr& signal, const ListPtr& packets) {}; readThreadStarted = false; if (readThread.joinable()) { diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 1a55ebc..7bcf9db 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -63,6 +63,7 @@ void WebsocketStreamingServer::start() const ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ-LT Streaming", ProtocolType::Streaming); serverCapability.setPrefix("daq.lt"); serverCapability.addProperty(IntProperty("Port", streamingPort)); + serverCapability.setConnectionType("TCP/IP"); this->device.getInfo().asPtr().addServerCapability(serverCapability); } From 88f292c275db96e3f74513842d70908d67d0d01a Mon Sep 17 00:00:00 2001 From: roettger Date: Wed, 15 May 2024 20:02:44 +0000 Subject: [PATCH 067/127] Increase version for streaming protocol. --- external/streaming_protocol/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index ceb4de8..236b490 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 0.10.17 + REQUIRED_VERSION 1.0.1 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v0.10.17 + GIT_REF v1.0.1 EXPECT_TARGET daq::streaming_protocol ) From 0d01e2b2a95f3d067de20eb288fcad275f249c02 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 16 May 2024 16:25:14 +0200 Subject: [PATCH 068/127] Fix streaming-lt compatibility issues --- .../websocket_streaming/output_signal.h | 1 + .../websocket_streaming/src/output_signal.cpp | 28 ++++++++++++++++--- .../tests/test_websocket_client_device.cpp | 15 +++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index dd15eed..d54e044 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -68,6 +68,7 @@ class OutputSignalBase bool doSetStartTime{false}; std::mutex subscribedSync; daq::streaming_protocol::BaseSignalPtr stream; + bool dataWritingAllowed{false}; private: void processAttributeChangedCoreEvent(ComponentPtr& component, CoreEventArgsPtr& args); diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index bf0c5df..401a602 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -107,7 +107,8 @@ bool OutputSignalBase::isSubscribed() void OutputSignalBase::submitSignalChanges() { - stream->writeSignalMetaInformation(); + if (dataWritingAllowed) + stream->writeSignalMetaInformation(); } void OutputSignalBase::writeDescriptorChangedEvent(const DataDescriptorPtr& descriptor) @@ -218,9 +219,11 @@ void OutputValueSignalBase::setSubscribed(bool subscribed) { outputDomainSignal->subscribeByDataSignal(); stream->subscribe(); + dataWritingAllowed = true; } else { + dataWritingAllowed = false; stream->unsubscribe(); outputDomainSignal->unsubscribeByDataSignal(); } @@ -256,7 +259,8 @@ uint64_t OutputDomainSignalBase::calcStartTimeOffset(uint64_t dataPacketTimeStam { STREAMING_PROTOCOL_LOG_I("time signal {}: reset start timestamp: {}", daqSignal.getGlobalId(), dataPacketTimeStamp); - domainStream->setTimeStart(dataPacketTimeStamp); + if (dataWritingAllowed) + domainStream->setTimeStart(dataPacketTimeStamp); doSetStartTime = false; return 0; } @@ -289,7 +293,10 @@ void OutputDomainSignalBase::subscribeByDataSignal() { doSetStartTime = true; if (!this->subscribed) + { stream->subscribe(); + dataWritingAllowed = true; + } } subscribedByDataSignalCount++; @@ -310,7 +317,10 @@ void OutputDomainSignalBase::unsubscribeByDataSignal() if (subscribedByDataSignalCount == 0) { if (!this->subscribed) + { + dataWritingAllowed = false; stream->unsubscribe(); + } } } @@ -324,12 +334,18 @@ void OutputDomainSignalBase::setSubscribed(bool subscribed) if (subscribed) { if (subscribedByDataSignalCount == 0) + { stream->subscribe(); + dataWritingAllowed = true; + } } else { if (subscribedByDataSignalCount == 0) + { + dataWritingAllowed = false; stream->unsubscribe(); + } } } } @@ -499,7 +515,8 @@ void OutputSyncValueSignal::writeDataPacket(const DataPacketPtr& packet) doSetStartTime = false; } - syncStream->addData(packet.getRawData(), packet.getSampleCount()); + if (dataWritingAllowed) + syncStream->addData(packet.getRawData(), packet.getSampleCount()); } BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( @@ -622,7 +639,8 @@ void OutputConstValueSignal::writeData(const DataPacketPtr& packet, uint64_t fir indices.push_back(values[i].second + firstValueIndex); } - constStream->addData(constants.data(), indices.data(), valuesCount); + if (dataWritingAllowed) + constStream->addData(constants.data(), indices.data(), valuesCount); } lastConstValue = values.back().first; @@ -701,9 +719,11 @@ void OutputConstValueSignal::setSubscribed(bool subscribed) { outputDomainSignal->subscribeByDataSignal(); stream->subscribe(); + dataWritingAllowed = true; } else { + dataWritingAllowed = false; stream->unsubscribe(); outputDomainSignal->unsubscribeByDataSignal(); } diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 85c14b0..66b6439 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -6,6 +6,7 @@ #include #include #include "streaming_test_helpers.h" +#include using namespace daq; using namespace std::chrono_literals; @@ -105,10 +106,22 @@ TEST_F(WebsocketClientDeviceTest, SignalWithDomain) ASSERT_EQ(clientDevice.getSignals()[0].getName(), "TestName"); ASSERT_EQ(clientDevice.getSignals()[0].getDescription(), "TestDescription"); + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + auto signal = clientDevice.getSignals()[0].asPtr(); + signal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + }; + + auto reader = PacketReader(clientDevice.getSignals()[0]); + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + // Publish signal changes auto descriptor = DataDescriptorBuilderCopy(testValueSignal.getDescriptor()).build(); std::string signalId = testValueSignal.getGlobalId(); - server->broadcastPacket(signalId, DataDescriptorChangedEventPacket(descriptor, testValueSignal.getDomainSignal().getDescriptor())); + server->sendPacketToSubscribers(signalId, DataDescriptorChangedEventPacket(descriptor, testValueSignal.getDomainSignal().getDescriptor())); std::this_thread::sleep_for(std::chrono::milliseconds(250)); From 46f00f5b4986a8911b34ea48f54e31832e504c9b Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Fri, 10 May 2024 12:39:14 +0200 Subject: [PATCH 069/127] Sync location and name between device and device info --- ...websocket_streaming_client_module_impl.cpp | 37 ++++++++++++------- .../src/websocket_client_device_impl.cpp | 1 + 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index c977183..260a5be 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -26,21 +26,30 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex { [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( - discoveredDevice.ipv4Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/") - ); - auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( - "[" + discoveredDevice.ipv6Address + "]", - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/") - ); auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); - cap.addConnectionString(connectionStringIpv4); - cap.addAddress(discoveredDevice.ipv4Address); - cap.addConnectionString(connectionStringIpv6); - cap.addAddress("[" + discoveredDevice.ipv6Address + "]"); + + if (!discoveredDevice.ipv4Address.empty()) + { + auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); + cap.addConnectionString(connectionStringIpv4); + cap.addAddress(discoveredDevice.ipv4Address); + } + + if(!discoveredDevice.ipv6Address.empty()) + { + auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( + "[" + discoveredDevice.ipv6Address + "]", + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); + cap.addConnectionString(connectionStringIpv6); + cap.addAddress("[" + discoveredDevice.ipv6Address + "]"); + } + cap.setConnectionType("TCP/IP"); cap.setPrefix("daq.lt"); return cap; diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 9f70c72..5fbd351 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -17,6 +17,7 @@ WebsocketClientDeviceImpl::WebsocketClientDeviceImpl(const ContextPtr& ctx, { if (!this->connectionString.assigned()) throw ArgumentNullException("connectionString cannot be null"); + this->name = "WebsocketClientPseudoDevice"; createWebsocketStreaming(); activateStreaming(); } From 2587419c9441876de847f0814eefecf69da10da8 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 23 May 2024 18:10:21 +0200 Subject: [PATCH 070/127] Integrate streaming-LT range and post scaling metadata: * encode/decode signal value range and linear scaling parameters within the LT "definition" metadata * keep compatibility with older client/server versions by also using "interpretation" metadata to transmit range and scaling parameters --- external/streaming_protocol/CMakeLists.txt | 4 +- .../signal_descriptor_converter.h | 5 +- .../src/signal_descriptor_converter.cpp | 133 ++++++++++++++---- .../test_signal_descriptor_converter.cpp | 61 ++++---- 4 files changed, 148 insertions(+), 55 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 236b490..0792880 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.0.1 + REQUIRED_VERSION 1.2.0 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.0.1 + GIT_REF v1.2.0 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index e4a555c..f3efae4 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -52,8 +52,9 @@ class SignalDescriptorConverter static void SetLinearTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::LinearTimeSignalPtr linearStream); static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); - static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor); - static void DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptor); + static daq::RangePtr CreateDefaultRange(daq::SampleType sampleType); + static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptorBuilder); + static void DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptorBuilder); static nlohmann::json DictToJson(const DictPtr& dict); static DictPtr JsonToDict(const nlohmann::json& json); }; diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 3b26ff9..182510c 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -28,22 +28,23 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) { SubscribedSignalInfo sInfo; + auto dataDescriptorBuilder = DataDescriptorBuilder(); // *** meta "definition" start *** // get metainfo received via signal "definition" object and stored in SubscribedSignal struct - auto dataDescriptor = DataDescriptorBuilder().setRule(GetRule(subscribedSignal)); + dataDescriptorBuilder.setRule(GetRule(subscribedSignal)); if (subscribedSignal.isTimeSignal()) { uint64_t numerator = 1; uint64_t denominator = subscribedSignal.timeBaseFrequency(); auto resolution = Ratio(static_cast(numerator), static_cast(denominator)); - dataDescriptor.setTickResolution(resolution); + dataDescriptorBuilder.setTickResolution(resolution); } daq::streaming_protocol::SampleType streamingSampleType = subscribedSignal.dataValueType(); daq::SampleType daqSampleType = Convert(streamingSampleType); - dataDescriptor.setSampleType(daqSampleType); + dataDescriptorBuilder.setSampleType(daqSampleType); sInfo.signalName = subscribedSignal.memberName(); @@ -54,30 +55,51 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre "", subscribedSignal.unitQuantity()); - dataDescriptor.setUnit(unit); + dataDescriptorBuilder.setUnit(unit); } - dataDescriptor.setOrigin(subscribedSignal.timeBaseEpochAsString()); - // *** meta "definition" end *** + dataDescriptorBuilder.setOrigin(subscribedSignal.timeBaseEpochAsString()); - if (!subscribedSignal.isTimeSignal()) + auto streamingRange = subscribedSignal.range(); + if (streamingRange.isUnlimited()) + { + // An unlimited range indicates that it is not set within the "definition" object. + // Since the range is used to configure the renderer, we need to set a default value for fusion + // (non-openDAQ) device signals. + // If the signal is owned by an openDAQ-enabled server, the value range, if present, + // is specified within the "definition" object or alternatively might be set within + // the "interpretation" object, if so that value will override the default one. + if (!subscribedSignal.isTimeSignal()) + dataDescriptorBuilder.setValueRange(CreateDefaultRange(dataDescriptorBuilder.getSampleType())); + } + else { - // "definition" object does not contain signal value range used to configure renderer, - // we need to set the some default value for the fusion (non-openDAQ) device signals - // if the signal is owned by the openDAQ-enabled server - // than the value range from "interpretation" will overwrite the default value - dataDescriptor.setValueRange(daq::Range(-15.0, 15.0)); + dataDescriptorBuilder.setValueRange(Range(streamingRange.low, streamingRange.high)); } + // get linear post scaling from signal definition if it is not default - one-to-one scaling + auto streamingLinearPostScaling = subscribedSignal.postScaling(); + if (!streamingLinearPostScaling.isOneToOne()) + { + auto daqLinearPostScaling = LinearScaling(streamingLinearPostScaling.scale, + streamingLinearPostScaling.offset, + daqSampleType, + ScaledSampleType::Float64); + dataDescriptorBuilder.setPostScaling(daqLinearPostScaling); + // overwrite sample type when linear scaling is present + dataDescriptorBuilder.setSampleType(SampleType::Float64); + } + // *** meta "definition" end *** + auto bitsInterpretation = subscribedSignal.bitsInterpretationObject(); - DecodeBitsInterpretationObject(bitsInterpretation, dataDescriptor); + DecodeBitsInterpretationObject(bitsInterpretation, dataDescriptorBuilder); // --- meta "interpretation" start --- // overwrite/add descriptor fields with ones from optional "interpretation" object auto extra = subscribedSignal.interpretationObject(); - DecodeInterpretationObject(extra, dataDescriptor); + DecodeInterpretationObject(extra, dataDescriptorBuilder); - sInfo.dataDescriptor = dataDescriptor.build(); + sInfo.dataDescriptor = dataDescriptorBuilder.build(); if (extra.count("sig_name") > 0) sInfo.signalProps.name = extra["sig_name"]; if (extra.count("sig_desc") > 0) @@ -111,6 +133,24 @@ void SignalDescriptorConverter::ToStreamedValueSignal(const daq::SignalPtr& valu UnitPtr unit = dataDescriptor.getUnit(); if (unit.assigned()) valueStream->setUnit(unit.getId(), unit.getSymbol()); + + if (dataDescriptor.getValueRange().assigned()) + { + auto daqRange = dataDescriptor.getValueRange(); + daq::streaming_protocol::Range streamingRange; + streamingRange.high = daqRange.getHighValue().getFloatValue(); + streamingRange.low = daqRange.getLowValue().getFloatValue(); + valueStream->setRange(streamingRange); + } + + auto daqPostScaling = dataDescriptor.getPostScaling(); + if (daqPostScaling.assigned() && daqPostScaling.getType() == daq::ScalingType::Linear) + { + daq::streaming_protocol::PostScaling streamingLinearPostScaling; + streamingLinearPostScaling.scale = daqPostScaling.getParameters().get("scale"); + streamingLinearPostScaling.offset = daqPostScaling.getParameters().get("offset"); + valueStream->setPostScaling(streamingLinearPostScaling); + } // *** meta "definition" end *** // --- meta "interpretation" start --- @@ -301,6 +341,47 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp } } +daq::RangePtr SignalDescriptorConverter::CreateDefaultRange(daq::SampleType sampleType) +{ + switch (sampleType) + { + case daq::SampleType::UInt8: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Int8: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::UInt16: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Int16: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::UInt32: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Int32: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::UInt64: + // range integer values are of signed type so the highest value is max of signed type + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Int64: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Float32: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + case daq::SampleType::Float64: + return Range(std::numeric_limits::lowest(), std::numeric_limits::max()); + break; + default: + return nullptr; + break; + } +} + void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorPtr& dataDescriptor, nlohmann::json& extra) { // put descriptor name into interpretation object @@ -351,32 +432,32 @@ void SignalDescriptorConverter::EncodeInterpretationObject(const DataDescriptorP } } -void SignalDescriptorConverter::DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptor) +void SignalDescriptorConverter::DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptorBuilder) { if (!bits.empty() && bits.is_array()) { - auto metadata = dataDescriptor.getMetadata(); + auto metadata = dataDescriptorBuilder.getMetadata(); metadata.set("bits", bits.dump()); } } -void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptor) +void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptorBuilder) { // sets descriptor name when corresponding field is present in interpretation object if (extra.count("desc_name") > 0) - dataDescriptor.setName(extra["desc_name"]); + dataDescriptorBuilder.setName(extra["desc_name"]); if (extra.count("metadata") > 0) { auto meta = JsonToDict(extra["metadata"]); - dataDescriptor.setMetadata(meta); + dataDescriptorBuilder.setMetadata(meta); } if (extra.count("unit") > 0) { auto unitObj = extra["unit"]; auto unit = Unit(unitObj["symbol"], unitObj["id"], unitObj["name"], unitObj["quantity"]); - dataDescriptor.setUnit(unit); + dataDescriptorBuilder.setUnit(unit); } if (extra.count("range") > 0) @@ -385,11 +466,11 @@ void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& auto low = std::stoi(std::string(rangeObj["low"])); auto high = std::stoi(std::string(rangeObj["high"])); auto range = Range(low, high); - dataDescriptor.setValueRange(range); + dataDescriptorBuilder.setValueRange(range); } if (extra.count("origin") > 0) - dataDescriptor.setOrigin(extra["origin"]); + dataDescriptorBuilder.setOrigin(extra["origin"]); if (extra.count("rule") > 0) { @@ -397,7 +478,7 @@ void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& params.freeze(); auto rule = DataRuleBuilder().setType(extra["rule"]["type"]).setParameters(params).build(); - dataDescriptor.setRule(rule); + dataDescriptorBuilder.setRule(rule); } if (extra.count("scaling") > 0) @@ -411,10 +492,10 @@ void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& .setScalingType(extra["scaling"]["scalingType"]) .setParameters(params) .build(); - dataDescriptor.setPostScaling(scaling); + dataDescriptorBuilder.setPostScaling(scaling); // overwrite sample type when scaling is present - dataDescriptor.setSampleType(convertScaledToSampleType(scaling.getOutputSampleType())); + dataDescriptorBuilder.setSampleType(convertScaledToSampleType(scaling.getOutputSampleType())); } } diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 39a7cbf..bb3b173 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -112,29 +112,6 @@ SignalPtr createSineSignalWithPostScaling(const ContextPtr& ctx) return signal; } -SignalPtr createStepSignal(const ContextPtr& ctx) -{ - auto dataDescriptor = DataDescriptorBuilder() - .setSampleType(SampleType::UInt8) - .setName("ByteStepValue") - .setUnit(Unit("V", 1, "voltage", "quantity")) - .setValueRange(Range(0, 10)) - .setRule(ExplicitDataRule()) - .setOrigin("1970-01-01T00:00:00") - .build(); - - auto meta = Dict(); - meta["color"] = "red"; - meta["used"] = "1"; - - auto descriptor = DataDescriptorBuilder().setSampleType(SampleType::Float64).setName("ByteStep").setMetadata(meta).build(); - - auto timeSignal = createTimeSignal(ctx); - auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, "time"); - signal.setDomainSignal(timeSignal); - return signal; -} - TEST(SignalConverter, synchronousSignal) { auto signal = createSineSignal(NullContext()); @@ -242,7 +219,7 @@ TEST(SignalConverter, subscribedDataSignal) method = bsp::META_METHOD_SIGNAL; signalParams[bsp::META_TABLEID] = tableId; signalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; - signalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_REAL64; + signalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_INT32; signalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_EXPLICIT; signalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_UNIT_ID] = unitId; @@ -256,7 +233,7 @@ TEST(SignalConverter, subscribedDataSignal) auto dataDescriptor = subscribedSignalInfo.dataDescriptor; ASSERT_EQ(subscribedSignalInfo.signalName, memberName); - ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Float64); + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Int32); auto unit = dataDescriptor.getUnit(); ASSERT_TRUE(unit.assigned()); @@ -266,6 +243,40 @@ TEST(SignalConverter, subscribedDataSignal) auto rule = dataDescriptor.getRule(); ASSERT_TRUE(rule.assigned()); ASSERT_EQ(daq::DataRuleType::Explicit, rule.getType()); + + // test default post scaling + auto postScaling = dataDescriptor.getPostScaling(); + ASSERT_FALSE(postScaling.assigned()); + + // test default range + auto range = dataDescriptor.getValueRange(); + ASSERT_TRUE(range.assigned()); + ASSERT_EQ(range.getLowValue(), std::numeric_limits::lowest()); + ASSERT_EQ(range.getHighValue(), std::numeric_limits::max()); + + // test custom range and post scaling + signalParams[bsp::META_DEFINITION][bsp::META_RANGE][bsp::META_LOW] = -100; + signalParams[bsp::META_DEFINITION][bsp::META_RANGE][bsp::META_HIGH] = 100; + + signalParams[bsp::META_DEFINITION][bsp::META_POSTSCALING][bsp::META_SCALE] = 2.0; + signalParams[bsp::META_DEFINITION][bsp::META_POSTSCALING][bsp::META_POFFSET] = 3.0; + + result = subscribedSignal.processSignalMetaInformation(method, signalParams); + ASSERT_EQ(result, 0); + + subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + dataDescriptor = subscribedSignalInfo.dataDescriptor; + + range = dataDescriptor.getValueRange(); + ASSERT_TRUE(range.assigned()); + ASSERT_EQ(range.getLowValue(), -100); + ASSERT_EQ(range.getHighValue(), 100); + + postScaling = dataDescriptor.getPostScaling(); + ASSERT_TRUE(postScaling.assigned()); + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Float64); + ASSERT_EQ(postScaling.getParameters().get("scale"), 2.0); + ASSERT_EQ(postScaling.getParameters().get("offset"), 3.0); } TEST(SignalConverter, subscribedBitfieldSignal) From 697bd164ccf9f8fc51b0fad07cad6824ae900851 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Tue, 28 May 2024 14:51:04 +0200 Subject: [PATCH 071/127] [TBBAS-1313] mDNS discovery service in SDK (openDAQ/openDAQ#294) -add ability to make server discovered by mdns -support overriding default server configs from config provider (for native/opcua/streaming lt) -support setting path for opcua server -fix reading port from connection string in opcua client -update quick_start_simulator and device simulator to use discovery -update doc --- .../websocket_streaming_server_impl.h | 7 ++-- .../src/websocket_streaming_server_impl.cpp | 41 ++++++++++++++++--- ...websocket_streaming_server_module_impl.cpp | 4 +- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 9d2f2b8..33e191d 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -28,14 +28,15 @@ class WebsocketStreamingServerImpl : public daq::Server { public: explicit WebsocketStreamingServerImpl(daq::DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context); - static PropertyObjectPtr createDefaultConfig(); - static ServerTypePtr createType(); + static PropertyObjectPtr createDefaultConfig(const ContextPtr& context); + static ServerTypePtr createType(const ContextPtr& context); protected: + PropertyObjectPtr getDiscoveryConfig() override; void onStopServer() override; + static void populateDefaultConfigFromProvider(const ContextPtr& context, const PropertyObjectPtr& config); daq::websocket_streaming::WebsocketStreamingServer websocketStreamingServer; - PropertyObjectPtr config; }; OPENDAQ_DECLARE_CLASS_FACTORY_WITH_INTERFACE( diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 7cc3cce..2d3ca9f 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -3,15 +3,15 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE using namespace daq; WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context) - : Server(nullptr, rootDevice, nullptr, nullptr) + : Server("StreamingLtServer", config, rootDevice, context, nullptr) , websocketStreamingServer(rootDevice, context) - , config(config) { const uint16_t streamingPort = config.getPropertyValue("WebsocketStreamingPort"); const uint16_t controlPort = config.getPropertyValue("WebsocketControlPort"); @@ -21,7 +21,24 @@ WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, websocketStreamingServer.start(); } -PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig() +void WebsocketStreamingServerImpl::populateDefaultConfigFromProvider(const ContextPtr& context, const PropertyObjectPtr& config) +{ + if (!context.assigned()) + return; + if (!config.assigned()) + return; + + auto options = context.getModuleOptions("StreamingLtServer"); + for (const auto& [key, value] : options) + { + if (config.hasProperty(key)) + { + config->setPropertyValue(key, value); + } + } +} + +PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig(const ContextPtr& context) { constexpr Int minPortValue = 0; constexpr Int maxPortValue = 65535; @@ -36,16 +53,30 @@ PropertyObjectPtr WebsocketStreamingServerImpl::createDefaultConfig() IntPropertyBuilder("WebsocketControlPort", 7438).setMinValue(minPortValue).setMaxValue(maxPortValue).build(); defaultConfig.addProperty(websocketControlPortProp); + defaultConfig.addProperty(StringProperty("Path", "/")); + + populateDefaultConfigFromProvider(context, defaultConfig); return defaultConfig; } -ServerTypePtr WebsocketStreamingServerImpl::createType() +PropertyObjectPtr WebsocketStreamingServerImpl::getDiscoveryConfig() +{ + auto discoveryConfig = PropertyObject(); + discoveryConfig.addProperty(StringProperty("ServiceName", "_streaming-lt._tcp.local.")); + discoveryConfig.addProperty(StringProperty("ServiceCap", "LT")); + discoveryConfig.addProperty(StringProperty("Path", config.getPropertyValue("Path"))); + discoveryConfig.addProperty(IntProperty("Port", config.getPropertyValue("WebsocketStreamingPort"))); + return discoveryConfig; +} + + +ServerTypePtr WebsocketStreamingServerImpl::createType(const ContextPtr& context) { return ServerType( "openDAQ LT Streaming", "openDAQ LT Streaming server", "Publishes device signals as a flat list and streams data over WebsocketTcp protocol", - WebsocketStreamingServerImpl::createDefaultConfig()); + WebsocketStreamingServerImpl::createDefaultConfig(context)); } void WebsocketStreamingServerImpl::onStopServer() diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index ef761a0..c3568d0 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -19,7 +19,7 @@ DictPtr WebsocketStreamingServerModule::onGetAvailableServ { auto result = Dict(); - auto serverType = WebsocketStreamingServerImpl::createType(); + auto serverType = WebsocketStreamingServerImpl::createType(context); result.set(serverType.getId(), serverType); return result; @@ -33,7 +33,7 @@ ServerPtr WebsocketStreamingServerModule::onCreateServer(StringPtr serverType, throw InvalidParameterException{"Context parameter cannot be null."}; if (!serverConfig.assigned()) - serverConfig = WebsocketStreamingServerImpl::createDefaultConfig(); + serverConfig = WebsocketStreamingServerImpl::createDefaultConfig(context); ServerPtr server(WebsocketStreamingServer_Create(rootDevice, serverConfig, context)); return server; From 2341ae1ad273a0b36e3265e0d60f75ef9d2d5210 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Fri, 31 May 2024 13:08:34 +0200 Subject: [PATCH 072/127] Add client connection configuration info in the device info (openDAQ/openDAQ#329) --- ...websocket_streaming_client_module_impl.cpp | 37 +++++++++++++++++-- ...test_websocket_streaming_client_module.cpp | 2 +- .../src/websocket_streaming_server.cpp | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 260a5be..53489dc 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -26,7 +26,7 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex { [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - auto cap = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); + auto cap = ServerCapability(WebsocketDeviceTypeId, "openDAQ LT Streaming", ProtocolType::Streaming); if (!discoveredDevice.ipv4Address.empty()) { @@ -101,7 +101,36 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec std::scoped_lock lock(sync); std::string localId = fmt::format("websocket_pseudo_device{}", deviceIndex++); - return WebsocketClientDevice(context, parent, localId, connectionString); + auto device = WebsocketClientDevice(context, parent, localId, connectionString); + + // Set the connection info for the device + auto host = String(""); + auto port = -1; + { + std::smatch match; + auto regexpConnectionString = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + std::string connectionStringStr = connectionString; + if (std::regex_search(connectionStringStr, match, regexpConnectionString)) + { + host = match[2].str(); + port = 7414; + if (match[3].matched) + port = std::stoi(match[3]); + } + } + + // Set the connection info for the device + ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); + connectionInfo.setProtocolId(WebsocketDeviceTypeId); + connectionInfo.setProtocolName("openDAQ LT Streaming"); + connectionInfo.setProtocolType(ProtocolType::Streaming); + connectionInfo.setConnectionType("TCP/IP"); + connectionInfo.addAddress(host); + connectionInfo.setPort(port); + connectionInfo.setPrefix("daq.lt"); + connectionInfo.setConnectionString(connectionString); + + return device; } bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& /*config*/) @@ -148,9 +177,9 @@ StringPtr WebsocketStreamingClientModule::onCreateConnectionString(const ServerC if (!address.assigned() || address.toStdString().empty()) throw InvalidParameterException("Address is not set"); - if (!serverCapability.hasProperty("Port")) + auto port = serverCapability.getPort(); + if (port == -1) throw InvalidParameterException("Port is not set"); - auto port = serverCapability.getPropertyValue("Port").template asPtr(); return WebsocketStreamingClientModule::createUrlConnectionString( address, diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 40f7008..5855bcd 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -200,7 +200,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) serverCapability.addAddress("123.123.123.123"); ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); - serverCapability.addProperty(IntProperty("Port", 1234)); + serverCapability.setPort(1234); ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234"); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 7bcf9db..429b33f 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -62,7 +62,7 @@ void WebsocketStreamingServer::start() const ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ-LT Streaming", ProtocolType::Streaming); serverCapability.setPrefix("daq.lt"); - serverCapability.addProperty(IntProperty("Port", streamingPort)); + serverCapability.setPort(streamingPort); serverCapability.setConnectionType("TCP/IP"); this->device.getInfo().asPtr().addServerCapability(serverCapability); } From 221a9851991c9a5e1acdc935df91c787b84b96d8 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 15 May 2024 12:08:56 +0200 Subject: [PATCH 073/127] opendaq component merge - Merge of all openDAQ targets into a singular target --- shared/libraries/websocket_streaming/src/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/CMakeLists.txt b/shared/libraries/websocket_streaming/src/CMakeLists.txt index bd7290d..495ed9a 100644 --- a/shared/libraries/websocket_streaming/src/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/src/CMakeLists.txt @@ -58,14 +58,8 @@ target_include_directories(${LIB_NAME} PUBLIC $ ) -# links target with daq::streaming_dev (which is a static lib) -# because daq::streaming_dev is also linked into opcuatms_client lib -# and both target & opcuatms_client are lately linked into integration test binary, -# to avoid multiple definition linker errors both target & opcuatms_client should use daq::streaming_dev target_link_libraries(${LIB_NAME} PUBLIC daq::streaming_protocol daq::opendaq - PRIVATE - daq::streaming_dev ) # Fix daq::streaming_protocol` not properly propagating linking requirements on Windows From 8168cdd2493c7e5c28218605ad6ddc9a8c34cdd0 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 30 May 2024 14:43:31 +0200 Subject: [PATCH 074/127] Close all connections on device removal: * Release streaming object on pseudo device removal * Close streaming connections on config-enabled mirrored device removal * Invalidate operations on a removed device * Close config connection on native device removal * Invalidate attributes managing for removed component * Disconnect OpcUa client on device removal * Fix - retrieve the connection string before removing the device in the Python GUI application --- .../websocket_streaming/websocket_client_device_impl.h | 2 ++ .../src/websocket_client_device_impl.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 15345c0..988298c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -31,6 +31,8 @@ class WebsocketClientDeviceImpl : public Device const StringPtr& connectionString); protected: + void removed() override; + DeviceInfoPtr onGetInfo() override; void createWebsocketStreaming(); void activateStreaming(); diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 5fbd351..15a5222 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -22,6 +22,12 @@ WebsocketClientDeviceImpl::WebsocketClientDeviceImpl(const ContextPtr& ctx, activateStreaming(); } +void WebsocketClientDeviceImpl::removed() +{ + websocketStreaming.release(); + Device::removed(); +} + DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() { auto deviceInfo = DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); From a0fb738b04447efb58785350c93365b0457496ad Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Thu, 20 Jun 2024 07:50:42 +0200 Subject: [PATCH 075/127] Other/config object rework (openDAQ/openDAQ#326) - Adds a default config that is used to configure both device (config) connection, streaming, and general settings at once - Add StreamingType - Add new general add-device-config settings --- .../websocket_streaming_client_module_impl.h | 4 + ...websocket_streaming_client_module_impl.cpp | 98 +++++++++++++++++-- .../websocket_streaming_server_impl.h | 1 + .../src/websocket_streaming_server_impl.cpp | 28 +++++- ...websocket_streaming_server_module_impl.cpp | 9 +- 5 files changed, 122 insertions(+), 18 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index b3ea5ce..2103605 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -28,6 +28,7 @@ class WebsocketStreamingClientModule final : public Module ListPtr onGetAvailableDevices() override; DictPtr onGetAvailableDeviceTypes() override; + DictPtr onGetAvailableStreamingTypes() override; DevicePtr onCreateDevice(const StringPtr& connectionString, const ComponentPtr& parent, const PropertyObjectPtr& config) override; @@ -41,6 +42,9 @@ class WebsocketStreamingClientModule final : public Module static StringPtr createUrlConnectionString(const StringPtr& host, const IntegerPtr& port, const StringPtr& path); + static StreamingTypePtr createWebsocketStreamingType(); + static PropertyObjectPtr createDefaultConfig(); + static StringPtr formConnectionString(const StringPtr& connectionString, const PropertyObjectPtr& config); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 53489dc..2b6c5bb 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -82,9 +83,20 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableDevi return result; } +DictPtr WebsocketStreamingClientModule::onGetAvailableStreamingTypes() +{ + auto result = Dict(); + + auto websocketStreamingType = createWebsocketStreamingType(); + + result.set(websocketStreamingType.getId(), websocketStreamingType); + + return result; +} + DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connectionString, - const ComponentPtr& parent, - const PropertyObjectPtr& config) + const ComponentPtr& parent, + const PropertyObjectPtr& config) { if (!connectionString.assigned()) throw ArgumentNullException(); @@ -98,10 +110,13 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec // We don't create any streaming objects here since the // internal streaming object is always created within the device + const StringPtr strPtr = formConnectionString(connectionString, config); + const std::string str = strPtr; + std::scoped_lock lock(sync); std::string localId = fmt::format("websocket_pseudo_device{}", deviceIndex++); - auto device = WebsocketClientDevice(context, parent, localId, connectionString); + auto device = WebsocketClientDevice(context, parent, localId, strPtr); // Set the connection info for the device auto host = String(""); @@ -109,8 +124,7 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec { std::smatch match; auto regexpConnectionString = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); - std::string connectionStringStr = connectionString; - if (std::regex_search(connectionStringStr, match, regexpConnectionString)) + if (std::regex_search(str, match, regexpConnectionString)) { host = match[2].str(); port = 7414; @@ -128,7 +142,7 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec connectionInfo.addAddress(host); connectionInfo.setPort(port); connectionInfo.setPrefix("daq.lt"); - connectionInfo.setConnectionString(connectionString); + connectionInfo.setConnectionString(strPtr); return device; } @@ -157,7 +171,8 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& if (!onAcceptsStreamingConnectionParameters(connectionString, config)) throw InvalidParameterException(); - return WebsocketStreaming(connectionString, context); + const StringPtr str = formConnectionString(connectionString, config); + return WebsocketStreaming(str, context); } StringPtr WebsocketStreamingClientModule::onCreateConnectionString(const ServerCapabilityPtr& serverCapability) @@ -197,9 +212,72 @@ StringPtr WebsocketStreamingClientModule::createUrlConnectionString(const String DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType() { - return DeviceType(WebsocketDeviceTypeId, - "Websocket enabled device", - "Pseudo device, provides only signals of the remote device as flat list"); + return DeviceTypeBuilder() + .setId(WebsocketDeviceTypeId) + .setName("Streaming LT enabled pseudo-device") + .setDescription("Pseudo device, provides only signals of the remote device as flat list") + .setConnectionStringPrefix("daq.lt") + .setDefaultConfig(createDefaultConfig()) + .build(); +} + +StreamingTypePtr WebsocketStreamingClientModule::createWebsocketStreamingType() +{ + return StreamingTypeBuilder() + .setId(WebsocketDeviceTypeId) + .setName("Streaming LT") + .setDescription("openDAQ native streaming protocol client") + .setConnectionStringPrefix("daq.lt") + .setDefaultConfig(createDefaultConfig()) + .build(); +} + +PropertyObjectPtr WebsocketStreamingClientModule::createDefaultConfig() +{ + auto obj = PropertyObject(); + obj.addProperty(IntProperty("Port", 7414)); + return obj; +} + +StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& connectionString, const PropertyObjectPtr& config) +{ + if (!config.assigned() || !config.hasProperty("Port")) + return connectionString; + + int port = config.getPropertyValue("Port"); + if (port == 7414) + return connectionString; + + std::string urlString = connectionString.toStdString(); + + auto regexIpv6Hostname = std::regex(R"(^(.*://)(\[[a-fA-F0-9:]+\])(?::(\d+))?(/.*)?$)"); + auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + std::smatch match; + + std::string host = ""; + std::string target = "/"; + std::string prefix = ""; + std::string path = ""; + + bool parsed = false; + parsed = std::regex_search(urlString, match, regexIpv6Hostname); + if (!parsed) + { + parsed = std::regex_search(urlString, match, regexIpv4Hostname); + } + + if (parsed) + { + prefix = match[1]; + host = match[2]; + + if (match[4].matched) + path = match[4]; + + return prefix + host + ":" + std::to_string(port) + "/" + path; + } + + return connectionString; } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 33e191d..3eeb48e 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -30,6 +30,7 @@ class WebsocketStreamingServerImpl : public daq::Server explicit WebsocketStreamingServerImpl(daq::DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context); static PropertyObjectPtr createDefaultConfig(const ContextPtr& context); static ServerTypePtr createType(const ContextPtr& context); + static PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& config, const ContextPtr& context); protected: PropertyObjectPtr getDiscoveryConfig() override; diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 2d3ca9f..5d9dde0 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -84,11 +84,29 @@ void WebsocketStreamingServerImpl::onStopServer() websocketStreamingServer.stop(); } +PropertyObjectPtr WebsocketStreamingServerImpl::populateDefaultConfig(const PropertyObjectPtr& config, const ContextPtr& context) +{ + const auto defConfig = createDefaultConfig(context); + for (const auto& prop : defConfig.getAllProperties()) + { + const auto name = prop.getName(); + if (config.hasProperty(name)) + defConfig.setPropertyValue(name, config.getPropertyValue(name)); + } + + return defConfig; +} + OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( - INTERNAL_FACTORY, WebsocketStreamingServer, daq::IServer, - daq::DevicePtr, rootDevice, - PropertyObjectPtr, config, - const ContextPtr&, context -) + INTERNAL_FACTORY, + WebsocketStreamingServer, + daq::IServer, + daq::DevicePtr, + rootDevice, + PropertyObjectPtr, + config, + const ContextPtr&, + context + ) END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index c3568d0..b373eb8 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -32,10 +32,13 @@ ServerPtr WebsocketStreamingServerModule::onCreateServer(StringPtr serverType, if (!context.assigned()) throw InvalidParameterException{"Context parameter cannot be null."}; - if (!serverConfig.assigned()) - serverConfig = WebsocketStreamingServerImpl::createDefaultConfig(context); + auto wsConfig = serverConfig; + if (!wsConfig.assigned()) + wsConfig = WebsocketStreamingServerImpl::createDefaultConfig(context); + else + wsConfig = WebsocketStreamingServerImpl::populateDefaultConfig(wsConfig, context); - ServerPtr server(WebsocketStreamingServer_Create(rootDevice, serverConfig, context)); + ServerPtr server(WebsocketStreamingServer_Create(rootDevice, wsConfig, context)); return server; } From 431a73daa468c354a62710141da1acbbabb649c5 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Fri, 21 Jun 2024 11:01:33 +0200 Subject: [PATCH 076/127] Modify discovery capability merge - Take device caps as source of truth - Add port to discovery caps - Fall back to default port if not specified in cap --- .../src/websocket_streaming_client_module_impl.cpp | 8 +++++++- .../tests/test_websocket_streaming_client_module.cpp | 2 +- .../src/websocket_streaming_server.cpp | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 2b6c5bb..f6783e7 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -53,6 +53,8 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex cap.setConnectionType("TCP/IP"); cap.setPrefix("daq.lt"); + if (discoveredDevice.servicePort > 0) + cap.setPort(discoveredDevice.servicePort); return cap; } }, @@ -60,6 +62,7 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex ) { discoveryClient.initMdnsClient(List("_streaming-lt._tcp.local.", "_streaming-ws._tcp.local.")); + loggerComponent = this->context.getLogger().getOrAddComponent("StreamingLTClient"); } ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() @@ -194,7 +197,10 @@ StringPtr WebsocketStreamingClientModule::onCreateConnectionString(const ServerC auto port = serverCapability.getPort(); if (port == -1) - throw InvalidParameterException("Port is not set"); + { + port = 7414; + LOG_W("LT Streaming server capability is missing port. Defaulting to 7414.") + } return WebsocketStreamingClientModule::createUrlConnectionString( address, diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 5855bcd..510d68a 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -198,7 +198,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); serverCapability.addAddress("123.123.123.123"); - ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); + ASSERT_EQ(module.createConnectionString(serverCapability), "daq.lt://123.123.123.123:7414"); serverCapability.setPort(1234); ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 429b33f..dcaec6b 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -71,9 +71,10 @@ void WebsocketStreamingServer::stop() { if (this->device.assigned()) { - const auto info = this->device.getInfo().asPtr(); + const auto info = this->device.getInfo(); + const auto infoInternal = info.asPtr(); if (info.hasServerCapability("opendaq_lt_streaming")) - info.removeServerCapability("opendaq_lt_streaming"); + infoInternal.removeServerCapability("opendaq_lt_streaming"); } stopInternal(); From e3d5f6e77cab856f3bbf73bb0c85cc0f774d2444 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 26 Jun 2024 16:43:47 +0200 Subject: [PATCH 077/127] Fix race condition within LT-streaming on signal unsubscribe: * protect concurrent data / meta-data writes vs signal subscribe / unsubscribe operations by the mutex lock --- .../websocket_streaming/output_signal.h | 1 - .../websocket_streaming/streaming_server.h | 1 - .../websocket_streaming/src/output_signal.cpp | 42 +++++++++---------- .../src/streaming_server.cpp | 13 ------ .../src/websocket_streaming_server.cpp | 2 +- .../tests/test_streaming.cpp | 20 ++++----- .../tests/test_websocket_client_device.cpp | 2 +- 7 files changed, 31 insertions(+), 50 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index d54e044..dd15eed 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -68,7 +68,6 @@ class OutputSignalBase bool doSetStartTime{false}; std::mutex subscribedSync; daq::streaming_protocol::BaseSignalPtr stream; - bool dataWritingAllowed{false}; private: void processAttributeChangedCoreEvent(ComponentPtr& component, CoreEventArgsPtr& args); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 7e29bfe..ec89ace 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -56,7 +56,6 @@ class StreamingServer void onUnsubscribe(const OnUnsubscribeCallback& callback); void unicastPacket(const std::string& streamId, const std::string& signalId, const PacketPtr& packet); void broadcastPacket(const std::string& signalId, const PacketPtr &packet); - void sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet); protected: using SignalMap = std::unordered_map; diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 401a602..d20889b 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -80,7 +80,10 @@ void OutputSignalBase::processAttributeChangedCoreEvent(ComponentPtr& /*componen // Streaming LT does not support attribute change forwarding for active, public, and visible toStreamedSignal(daqSignal, sigProps); - submitSignalChanges(); + + std::scoped_lock lock(subscribedSync); + if(this->subscribed) + submitSignalChanges(); } SignalProps OutputSignalBase::getSignalProps(const SignalPtr& signal) @@ -107,8 +110,7 @@ bool OutputSignalBase::isSubscribed() void OutputSignalBase::submitSignalChanges() { - if (dataWritingAllowed) - stream->writeSignalMetaInformation(); + stream->writeSignalMetaInformation(); } void OutputSignalBase::writeDescriptorChangedEvent(const DataDescriptorPtr& descriptor) @@ -185,6 +187,11 @@ void OutputValueSignalBase::writeDescriptorChangedPacket(const EventPacketPtr& p void OutputValueSignalBase::writeDaqPacket(const PacketPtr& packet) { + std::scoped_lock lock(subscribedSync); + + if (!this->subscribed) + return; + const auto type = packet.getType(); switch (type) @@ -209,21 +216,19 @@ void OutputValueSignalBase::writeDaqPacket(const PacketPtr& packet) // Mutex locking ensures that data is sent only when the specified conditions are satisfied. void OutputValueSignalBase::setSubscribed(bool subscribed) { + std::scoped_lock lock(subscribedSync); + if (this->subscribed != subscribed) { - std::scoped_lock lock(subscribedSync); - this->subscribed = subscribed; doSetStartTime = true; if (subscribed) { outputDomainSignal->subscribeByDataSignal(); stream->subscribe(); - dataWritingAllowed = true; } else { - dataWritingAllowed = false; stream->unsubscribe(); outputDomainSignal->unsubscribeByDataSignal(); } @@ -259,8 +264,7 @@ uint64_t OutputDomainSignalBase::calcStartTimeOffset(uint64_t dataPacketTimeStam { STREAMING_PROTOCOL_LOG_I("time signal {}: reset start timestamp: {}", daqSignal.getGlobalId(), dataPacketTimeStamp); - if (dataWritingAllowed) - domainStream->setTimeStart(dataPacketTimeStamp); + domainStream->setTimeStart(dataPacketTimeStamp); doSetStartTime = false; return 0; } @@ -295,7 +299,6 @@ void OutputDomainSignalBase::subscribeByDataSignal() if (!this->subscribed) { stream->subscribe(); - dataWritingAllowed = true; } } @@ -318,7 +321,6 @@ void OutputDomainSignalBase::unsubscribeByDataSignal() { if (!this->subscribed) { - dataWritingAllowed = false; stream->unsubscribe(); } } @@ -326,24 +328,22 @@ void OutputDomainSignalBase::unsubscribeByDataSignal() void OutputDomainSignalBase::setSubscribed(bool subscribed) { + std::scoped_lock lock(subscribedSync); + if (this->subscribed != subscribed) { - std::scoped_lock lock(subscribedSync); - this->subscribed = subscribed; if (subscribed) { if (subscribedByDataSignalCount == 0) { stream->subscribe(); - dataWritingAllowed = true; } } else { if (subscribedByDataSignalCount == 0) { - dataWritingAllowed = false; stream->unsubscribe(); } } @@ -515,8 +515,7 @@ void OutputSyncValueSignal::writeDataPacket(const DataPacketPtr& packet) doSetStartTime = false; } - if (dataWritingAllowed) - syncStream->addData(packet.getRawData(), packet.getSampleCount()); + syncStream->addData(packet.getRawData(), packet.getSampleCount()); } BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( @@ -639,8 +638,7 @@ void OutputConstValueSignal::writeData(const DataPacketPtr& packet, uint64_t fir indices.push_back(values[i].second + firstValueIndex); } - if (dataWritingAllowed) - constStream->addData(constants.data(), indices.data(), valuesCount); + constStream->addData(constants.data(), indices.data(), valuesCount); } lastConstValue = values.back().first; @@ -707,10 +705,10 @@ void OutputConstValueSignal::writeDataPacket(const DataPacketPtr& packet) void OutputConstValueSignal::setSubscribed(bool subscribed) { + std::scoped_lock lock(subscribedSync); + if (this->subscribed != subscribed) { - std::scoped_lock lock(subscribedSync); - this->subscribed = subscribed; doSetStartTime = true; lastConstValue.reset(); @@ -719,11 +717,9 @@ void OutputConstValueSignal::setSubscribed(bool subscribed) { outputDomainSignal->subscribeByDataSignal(); stream->subscribe(); - dataWritingAllowed = true; } else { - dataWritingAllowed = false; stream->unsubscribe(); outputDomainSignal->unsubscribeByDataSignal(); } diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 090bf98..415dea8 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -141,19 +141,6 @@ void StreamingServer::broadcastPacket(const std::string& signalId, const PacketP } } -void StreamingServer::sendPacketToSubscribers(const std::string& signalId, const PacketPtr& packet) -{ - for (auto& [_, client] : clients) - { - auto signals = client.second; - if (auto signalIter = signals.find(signalId); signalIter != signals.end()) - { - if (signalIter->second->isSubscribed()) - signalIter->second->writeDaqPacket(packet); - } - } -} - DataRuleType StreamingServer::getSignalRuleType(const SignalPtr& signal) { auto descriptor = signal.getDescriptor(); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index dcaec6b..f0d65ed 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -56,7 +56,7 @@ void WebsocketStreamingServer::start() packetReader.onPacket([this](const SignalPtr& signal, const ListPtr& packets) { const auto signalId = signal.getGlobalId(); for (const auto& packet : packets) - streamingServer.sendPacketToSubscribers(signalId, packet); + streamingServer.broadcastPacket(signalId, packet); }); packetReader.start(); diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index b60bcf8..e9286c5 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -215,14 +215,14 @@ TEST_F(StreamingTest, PacketsCorrectSequence) 1, {{2, 2}, {4, 4}, {6, 5}}); - server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket1); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket1); + server->broadcastPacket(testConstantSignal.getGlobalId(), constantValuePacket1); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket1); auto domainPacket2 = getNextDomainPacket(sampleCount); auto explicitValuePacket2 = DataPacketWithDomain(domainPacket2, testDoubleSignal.getDescriptor(), sampleCount); std::memcpy(explicitValuePacket2.getRawData(), data.data(), explicitValuePacket2.getRawDataSize()); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket2); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket2); auto domainPacket3 = getNextDomainPacket(sampleCount); auto explicitValuePacket3 = DataPacketWithDomain(domainPacket3, testDoubleSignal.getDescriptor(), sampleCount); @@ -232,8 +232,8 @@ TEST_F(StreamingTest, PacketsCorrectSequence) sampleCount, 1); - server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket3); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket3); + server->broadcastPacket(testConstantSignal.getGlobalId(), constantValuePacket3); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket3); std::this_thread::sleep_for(std::chrono::milliseconds(250)); @@ -321,14 +321,14 @@ TEST_F(StreamingTest, PacketsIncorrectSequence) 1, {{2, 2}, {4, 4}, {6, 5}}); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket1); - server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket1); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket1); + server->broadcastPacket(testConstantSignal.getGlobalId(), constantValuePacket1); auto domainPacket2 = getNextDomainPacket(sampleCount); auto explicitValuePacket2 = DataPacketWithDomain(domainPacket2, testDoubleSignal.getDescriptor(), sampleCount); std::memcpy(explicitValuePacket2.getRawData(), data.data(), explicitValuePacket2.getRawDataSize()); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket2); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket2); auto domainPacket3 = getNextDomainPacket(sampleCount); auto explicitValuePacket3 = DataPacketWithDomain(domainPacket3, testDoubleSignal.getDescriptor(), sampleCount); @@ -338,8 +338,8 @@ TEST_F(StreamingTest, PacketsIncorrectSequence) sampleCount, 1); - server->sendPacketToSubscribers(testDoubleSignal.getGlobalId(), explicitValuePacket3); - server->sendPacketToSubscribers(testConstantSignal.getGlobalId(), constantValuePacket3); + server->broadcastPacket(testDoubleSignal.getGlobalId(), explicitValuePacket3); + server->broadcastPacket(testConstantSignal.getGlobalId(), constantValuePacket3); std::this_thread::sleep_for(std::chrono::milliseconds(250)); diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 66b6439..29b8c4b 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -121,7 +121,7 @@ TEST_F(WebsocketClientDeviceTest, SignalWithDomain) // Publish signal changes auto descriptor = DataDescriptorBuilderCopy(testValueSignal.getDescriptor()).build(); std::string signalId = testValueSignal.getGlobalId(); - server->sendPacketToSubscribers(signalId, DataDescriptorChangedEventPacket(descriptor, testValueSignal.getDomainSignal().getDescriptor())); + server->broadcastPacket(signalId, DataDescriptorChangedEventPacket(descriptor, testValueSignal.getDomainSignal().getDescriptor())); std::this_thread::sleep_for(std::chrono::milliseconds(250)); From 58aecd3843b905857d4015d744dd0316fee7bb16 Mon Sep 17 00:00:00 2001 From: Dejan Crnila Date: Sun, 30 Jun 2024 11:36:53 +0200 Subject: [PATCH 078/127] Update streaming protocol LT --- external/streaming_protocol/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 0792880..1f8c560 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -8,6 +8,7 @@ opendaq_dependency( NAME streaming_protocol REQUIRED_VERSION 1.2.0 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.2.0 +# GIT_REF v1.2.0 + GIT_REF df10ffc00528b9f3f2a6de84cfda202e8d109087 EXPECT_TARGET daq::streaming_protocol ) From ae854ecf3ece02366d43588f7c573611661561ba Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Tue, 2 Jul 2024 10:28:21 +0200 Subject: [PATCH 079/127] Change streaming protocol req version --- external/streaming_protocol/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 1f8c560..4673381 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,9 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.0 + REQUIRED_VERSION 1.2.1 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git -# GIT_REF v1.2.0 - GIT_REF df10ffc00528b9f3f2a6de84cfda202e8d109087 + GIT_REF v1.2.1 EXPECT_TARGET daq::streaming_protocol ) From 414cd50d3da9a34bfb0d9cee9d0d9f6d3f4f84e3 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Thu, 4 Jul 2024 10:24:12 +0200 Subject: [PATCH 080/127] Remove acceptsConnectionParameters from module --- .../websocket_streaming_client_module_impl.h | 6 +- ...websocket_streaming_client_module_impl.cpp | 34 ++++--- ...test_websocket_streaming_client_module.cpp | 90 +------------------ .../tests/streaming_test_helpers.h | 4 +- 4 files changed, 28 insertions(+), 106 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index 2103605..7d39db8 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -32,13 +32,13 @@ class WebsocketStreamingClientModule final : public Module DevicePtr onCreateDevice(const StringPtr& connectionString, const ComponentPtr& parent, const PropertyObjectPtr& config) override; - bool onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; - bool onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) override; + bool acceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config); + bool acceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config); StreamingPtr onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) override; StringPtr onCreateConnectionString(const ServerCapabilityPtr& serverCapability) override; private: - static DeviceTypePtr createWebsocketDeviceType(); + static DeviceTypePtr createWebsocketDeviceType(bool useOldPrefix); static StringPtr createUrlConnectionString(const StringPtr& host, const IntegerPtr& port, const StringPtr& path); diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index f6783e7..3d9e566 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -10,9 +10,10 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE -static const char* WebsocketDeviceTypeId = "opendaq_lt_streaming"; -static const char* WebsocketDevicePrefix = "daq.lt://"; -static const char* OldWebsocketDevicePrefix = "daq.ws://"; +static std::string WebsocketDeviceTypeId = "opendaq_lt_streaming"; +static std::string OldWebsocketDeviceTypeId = "opendaq_lt_streaming_old"; +static std::string WebsocketDevicePrefix = "daq.lt"; +static std::string OldWebsocketDevicePrefix = "daq.ws"; using namespace discovery; using namespace daq::websocket_streaming; @@ -70,7 +71,7 @@ ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() auto availableDevices = discoveryClient.discoverDevices(); for (auto device : availableDevices) { - device.asPtr().setDeviceType(createWebsocketDeviceType()); + device.asPtr().setDeviceType(createWebsocketDeviceType(false)); } return availableDevices; } @@ -79,9 +80,11 @@ DictPtr WebsocketStreamingClientModule::onGetAvailableDevi { auto result = Dict(); - auto websocketDeviceType = createWebsocketDeviceType(); + const auto websocketDeviceType = createWebsocketDeviceType(false); + const auto oldWebsocketDeviceType = createWebsocketDeviceType(true); result.set(websocketDeviceType.getId(), websocketDeviceType); + result.set(oldWebsocketDeviceType.getId(), oldWebsocketDeviceType); return result; } @@ -104,7 +107,7 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec if (!connectionString.assigned()) throw ArgumentNullException(); - if (!onAcceptsConnectionParameters(connectionString, config)) + if (!acceptsConnectionParameters(connectionString, config)) throw InvalidParameterException(); if (!context.assigned()) @@ -150,18 +153,18 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec return device; } -bool WebsocketStreamingClientModule::onAcceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& /*config*/) +bool WebsocketStreamingClientModule::acceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& /*config*/) { std::string connStr = connectionString; - auto found = connStr.find(WebsocketDevicePrefix) == 0 || connStr.find(OldWebsocketDevicePrefix) == 0; + auto found = connStr.find(WebsocketDevicePrefix + "://") == 0 || connStr.find(OldWebsocketDevicePrefix + "://") == 0; return found; } -bool WebsocketStreamingClientModule::onAcceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) +bool WebsocketStreamingClientModule::acceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) { if (connectionString.assigned() && connectionString != "") { - return onAcceptsConnectionParameters(connectionString, config); + return acceptsConnectionParameters(connectionString, config); } return false; } @@ -171,7 +174,7 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& if (!connectionString.assigned()) throw ArgumentNullException(); - if (!onAcceptsStreamingConnectionParameters(connectionString, config)) + if (!acceptsStreamingConnectionParameters(connectionString, config)) throw InvalidParameterException(); const StringPtr str = formConnectionString(connectionString, config); @@ -216,13 +219,16 @@ StringPtr WebsocketStreamingClientModule::createUrlConnectionString(const String return String(fmt::format("daq.lt://{}:{}{}", host, port, path)); } -DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType() +DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType(bool useOldPrefix) { + const StringPtr prefix = useOldPrefix ? String(OldWebsocketDevicePrefix) : String(WebsocketDevicePrefix); + const StringPtr id = useOldPrefix ? String(OldWebsocketDeviceTypeId) : String(WebsocketDeviceTypeId); + return DeviceTypeBuilder() - .setId(WebsocketDeviceTypeId) + .setId(id) .setName("Streaming LT enabled pseudo-device") .setDescription("Pseudo device, provides only signals of the remote device as flat list") - .setConnectionStringPrefix("daq.lt") + .setConnectionStringPrefix(prefix) .setDefaultConfig(createDefaultConfig()) .build(); } diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 510d68a..5939951 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -60,48 +60,6 @@ TEST_F(WebsocketStreamingClientModuleTest, EnumerateDevices) ASSERT_NO_THROW(deviceInfo = module.getAvailableDevices()); } -TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringNull) -{ - auto module = CreateModule(); - ASSERT_THROW(module.acceptsConnectionParameters(nullptr), ArgumentNullException); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringEmpty) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringInvalid) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("drfrfgt")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringWrongPrefix) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsConnectionParameters("daq.opcua://device8")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsConnectionStringCorrect) -{ - auto module = CreateModule(); - - ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://device8")); - ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://[::1]")); - ASSERT_TRUE(module.acceptsConnectionParameters("daq.lt://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]")); -} - TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionStringNull) { auto module = CreateModule(); @@ -139,49 +97,6 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionFailed) ASSERT_THROW(module.createDevice("daq.lt://127.0.0.1", nullptr), NotFoundException); } - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringNull) -{ - auto module = CreateModule(); - ASSERT_THROW(module.acceptsStreamingConnectionParameters(nullptr), ArgumentNullException); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringEmpty) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringInvalid) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("drfrfgt")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringWrongPrefix) -{ - auto module = CreateModule(); - - bool accepts = true; - ASSERT_NO_THROW(accepts = module.acceptsStreamingConnectionParameters("daq.opcua://device8")); - ASSERT_FALSE(accepts); -} - -TEST_F(WebsocketStreamingClientModuleTest, AcceptsStreamingConnectionStringCorrect) -{ - auto module = CreateModule(); - - ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.lt://device8")); - // check that old style conenction is also supported - ASSERT_TRUE(module.acceptsStreamingConnectionParameters("daq.ws://device8")); -} - TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) { auto context = NullContext(); @@ -209,7 +124,6 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234/path"); } - TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) { auto module = CreateModule(); @@ -251,9 +165,11 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); - ASSERT_EQ(deviceTypes.getCount(), 1u); + ASSERT_EQ(deviceTypes.getCount(), 2u); ASSERT_TRUE(deviceTypes.hasKey("opendaq_lt_streaming")); ASSERT_EQ(deviceTypes.get("opendaq_lt_streaming").getId(), "opendaq_lt_streaming"); + ASSERT_TRUE(deviceTypes.hasKey("opendaq_lt_streaming_old")); + ASSERT_EQ(deviceTypes.get("opendaq_lt_streaming_old").getId(), "opendaq_lt_streaming_old"); DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 4bf19f3..1237b44 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -45,8 +45,8 @@ namespace streaming_test_helpers moduleManager.addModule(fbModule); auto instance = InstanceCustom(context, "localInstance"); - instance.addDevice("daq_client_device"); - instance.addDevice("mock_phys_device"); + instance.addDevice("daqmock://client_device"); + instance.addDevice("daqmock://phys_device"); instance.addFunctionBlock("mock_fb_uid"); return instance; From 85499ca6c9dcab8a909d65dc2a218a8142046e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE?= Date: Wed, 10 Jul 2024 09:05:13 +0200 Subject: [PATCH 081/127] Change company name from `Blueberry d.o.o.` to `openDAQ d.o.o.` (openDAQ/openDAQ#374) --- .../include/websocket_streaming_client_module/common.h | 2 +- .../include/websocket_streaming_client_module/module_dll.h | 2 +- .../websocket_streaming_client_module_impl.h | 2 +- .../include/websocket_streaming_server_module/common.h | 2 +- .../include/websocket_streaming_server_module/module_dll.h | 2 +- .../websocket_streaming_server_impl.h | 2 +- .../websocket_streaming_server_module_impl.h | 2 +- .../include/websocket_streaming/async_packet_reader.h | 2 +- .../include/websocket_streaming/input_signal.h | 2 +- .../include/websocket_streaming/output_signal.h | 2 +- .../include/websocket_streaming/signal_descriptor_converter.h | 2 +- .../include/websocket_streaming/signal_info.h | 2 +- .../include/websocket_streaming/streaming_client.h | 2 +- .../include/websocket_streaming/streaming_server.h | 2 +- .../websocket_streaming/websocket_client_device_factory.h | 2 +- .../include/websocket_streaming/websocket_client_device_impl.h | 2 +- .../websocket_streaming/websocket_client_signal_factory.h | 2 +- .../include/websocket_streaming/websocket_client_signal_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming.h | 2 +- .../include/websocket_streaming/websocket_streaming_factory.h | 2 +- .../include/websocket_streaming/websocket_streaming_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming_server.h | 2 +- .../websocket_streaming/tests/streaming_test_helpers.h | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h index 474f34c..27cfe6b 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h index 91eb53a..909f4cb 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index 7d39db8..b6dd343 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h index eccef8b..090ca45 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h index 4c1560f..51358ee 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 3eeb48e..8114500 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h index a417955..9160589 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index 0b1a874..cd8b689 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index e7f5776..f977532 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index dd15eed..512eae4 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index f3efae4..712c967 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h index c6d7b0b..84c07e2 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 5136f55..8cb1449 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index ec89ace..cd4ffaa 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h index f5ad3e8..1bdf43d 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 988298c..8273087 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h index d179909..feeb7ac 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index eeed687..563c186 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h index 3ef13e0..b1ac0d7 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h index 2e86ad8..db15f3c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 8b7ce5e..5dc0792 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index a5c2de0..972adb7 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 1237b44..4519b2d 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 Blueberry d.o.o. + * Copyright 2022-2024 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From a11a6055ac9a04f622833f2e12464ded664d4910 Mon Sep 17 00:00:00 2001 From: Martin Kraner Date: Tue, 16 Jul 2024 12:39:23 +0200 Subject: [PATCH 082/127] General CMake and testing and discovery fixes (openDAQ/openDAQ#375) * Only set boost target options the target exists * Fix Boost and GTest alias target issues * Fix compiling with Boost 1.85 (add Align to BOOST_INCLUDE_LIBRARIES) * Fix Win32 Visual Studio 2017 compilation * Disable Warnings As Errors for external dependencies fix test warnings * Make ModuleManager ignore invalid paths and just print a warning * Make ModuleManager tests use relative paths instead of fixed/absolute * Fix CTest absolute working directory paths to relative * Set working directory to test runner location for `test_module_manager_internals` * Workaround mDNS discovery no longer providing device IP * Fix finding only IPv6 addresses or multiple ConfigurationAndStreaming capabilities * Fix NativeStreamingDevice protocol adding IPv4 address even if there is none * Add a default argument to changed Context() factory to maintain backwards compatibility * Add fs::proximate() shim for std::experimental::filesystem * Refactor presets so they can be included in vendor ones * Add address type and reachability status --------- Co-authored-by: Jaka Mohorko --- .../websocket_streaming_client_module_impl.h | 2 +- ...websocket_streaming_client_module_impl.cpp | 75 ++++++++++++++----- ...test_websocket_streaming_client_module.cpp | 52 ++++++------- .../tests/CMakeLists.txt | 4 +- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index b6dd343..bde0fbb 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -35,7 +35,7 @@ class WebsocketStreamingClientModule final : public Module bool acceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config); bool acceptsStreamingConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config); StreamingPtr onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) override; - StringPtr onCreateConnectionString(const ServerCapabilityPtr& serverCapability) override; + Bool onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) override; private: static DeviceTypePtr createWebsocketDeviceType(bool useOldPrefix); diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 3d9e566..d9cfbc6 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE @@ -39,6 +40,12 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex ); cap.addConnectionString(connectionStringIpv4); cap.addAddress(discoveredDevice.ipv4Address); + const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv4Address) + .setReachabilityStatus(AddressReachabilityStatus::Unknown) + .setType("IPv4") + .setConnectionString(connectionStringIpv4) + .build(); + cap.addAddressInfo(addressInfo); } if(!discoveredDevice.ipv6Address.empty()) @@ -50,6 +57,13 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex ); cap.addConnectionString(connectionStringIpv6); cap.addAddress("[" + discoveredDevice.ipv6Address + "]"); + + const auto addressInfo = AddressInfoBuilder().setAddress("[" + discoveredDevice.ipv6Address + "]") + .setReachabilityStatus(AddressReachabilityStatus::Unknown) + .setType("IPv6") + .setConnectionString(connectionStringIpv6) + .build(); + cap.addAddressInfo(addressInfo); } cap.setConnectionType("TCP/IP"); @@ -181,42 +195,65 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& return WebsocketStreaming(str, context); } -StringPtr WebsocketStreamingClientModule::onCreateConnectionString(const ServerCapabilityPtr& serverCapability) +Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) { - if (serverCapability.getProtocolId() != "opendaq_lt_streaming") - return nullptr; + if (target.getProtocolId() != "opendaq_lt_streaming") + return false; - StringPtr connectionString = serverCapability.getConnectionString(); - if (connectionString.getLength() != 0) - return connectionString; + if (target.getConnectionString().getLength() != 0) + return true; + + if (source.getConnectionType() != "TCP/IP") + return false; - StringPtr address; - if (ListPtr addresses = serverCapability.getAddresses(); addresses.getCount() > 0) + if (!source.getAddresses().assigned() || !source.getAddresses().getCount()) { - address = addresses[0]; + LOG_W("Source server capability address is not available when filling in missing LT streaming capability information.") + return false; } - if (!address.assigned() || address.toStdString().empty()) - throw InvalidParameterException("Address is not set"); - auto port = serverCapability.getPort(); + const auto addrInfos = source.getAddressInfo(); + if (!addrInfos.assigned() || !addrInfos.getCount()) + { + LOG_W("Source server capability addressInfo is not available when filling in missing LT streaming capability information.") + return false; + } + + auto port = target.getPort(); if (port == -1) { port = 7414; - LOG_W("LT Streaming server capability is missing port. Defaulting to 7414.") + target.setPort(port); + LOG_W("LT server capability is missing port. Defaulting to 7414.") + } + + const auto path = target.hasProperty("Path") ? target.getPropertyValue("Path") : ""; + for (const auto& addrInfo : addrInfos) + { + const auto address = addrInfo.getAddress(); + const auto prefix = WebsocketDevicePrefix; + StringPtr connectionString = createUrlConnectionString(address, port,path); + + const auto targetAddrInfo = AddressInfoBuilder() + .setAddress(addrInfo.getAddress()) + .setReachabilityStatus(addrInfo.getReachabilityStatus()) + .setType(addrInfo.getType()) + .setConnectionString(connectionString) + .build(); + + target.addAddressInfo(targetAddrInfo) + .setConnectionString(connectionString) + .addAddress(address); } - return WebsocketStreamingClientModule::createUrlConnectionString( - address, - port, - serverCapability.hasProperty("Path") ? serverCapability.getPropertyValue("Path") : "" - ); + return true; } StringPtr WebsocketStreamingClientModule::createUrlConnectionString(const StringPtr& host, const IntegerPtr& port, const StringPtr& path) { - return String(fmt::format("daq.lt://{}:{}{}", host, port, path)); + return String(std::string(WebsocketDevicePrefix) + fmt::format("://{}:{}{}", host, port, path)); } DeviceTypePtr WebsocketStreamingClientModule::createWebsocketDeviceType(bool useOldPrefix) diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 5939951..b887201 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -97,32 +97,32 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionFailed) ASSERT_THROW(module.createDevice("daq.lt://127.0.0.1", nullptr), NotFoundException); } -TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) -{ - auto context = NullContext(); - ModulePtr module; - createModule(&module, context); - - StringPtr connectionString; - - ServerCapabilityConfigPtr serverCapabilityIgnored = ServerCapability("test", "test", ProtocolType::Unknown); - ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapabilityIgnored)); - ASSERT_FALSE(connectionString.assigned()); - - ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); - ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); - - serverCapability.addAddress("123.123.123.123"); - ASSERT_EQ(module.createConnectionString(serverCapability), "daq.lt://123.123.123.123:7414"); - - serverCapability.setPort(1234); - ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); - ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234"); - - serverCapability.addProperty(StringProperty("Path", "/path")); - ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); - ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234/path"); -} +//TEST_F(WebsocketStreamingClientModuleTest, CreateConnectionString) +//{ +// auto context = NullContext(); +// ModulePtr module; +// createModule(&module, context); +// +// StringPtr connectionString; +// +// ServerCapabilityConfigPtr serverCapabilityIgnored = ServerCapability("test", "test", ProtocolType::Unknown); +// ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapabilityIgnored)); +// ASSERT_FALSE(connectionString.assigned()); +// +// ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); +// ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); +// +// serverCapability.addAddress("123.123.123.123"); +// ASSERT_EQ(module.createConnectionString(serverCapability), "daq.lt://123.123.123.123:7414"); +// +// serverCapability.setPort(1234); +// ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); +// ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234"); +// +// serverCapability.addProperty(StringProperty("Path", "/path")); +// ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapability)); +// ASSERT_EQ(connectionString, "daq.lt://123.123.123.123:1234/path"); +//} TEST_F(WebsocketStreamingClientModuleTest, CreateStreamingWithNullArguments) { diff --git a/modules/websocket_streaming_server_module/tests/CMakeLists.txt b/modules/websocket_streaming_server_module/tests/CMakeLists.txt index 633b362..204f350 100644 --- a/modules/websocket_streaming_server_module/tests/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/tests/CMakeLists.txt @@ -15,8 +15,8 @@ target_link_libraries(${TEST_APP} PRIVATE daq::test_utils ) add_test(NAME ${TEST_APP} - COMMAND $ - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND $ + WORKING_DIRECTORY bin ) if (MSVC) # Ignoring warning for the Taskflow From dfd8a5c4a161626ceccdcb139e28ef7575c2b6eb Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Fri, 12 Jul 2024 09:12:16 +0200 Subject: [PATCH 083/127] / Handle client disconnection on the LT-streaming server --- .../websocket_streaming/streaming_server.h | 7 +++ .../src/streaming_server.cpp | 51 ++++++++++++++++++- .../tests/test_streaming.cpp | 16 +++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index cd4ffaa..a2b6964 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -61,6 +61,13 @@ class StreamingServer using SignalMap = std::unordered_map; using ClientMap = std::unordered_map>; + void doRead(const std::string& clientId, const stream::StreamPtr& stream); + void onReadDone(const std::string& clientId, + const stream::StreamPtr& stream, + const boost::system::error_code& ec, + std::size_t bytesRead); + void removeClient(const std::string& clientId); + void addToOutputSignals(const SignalPtr& signal, SignalMap& outputSignals, const streaming_protocol::StreamWriterPtr& writer); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 415dea8..bcec5d6 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -226,6 +226,50 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, } } +void StreamingServer::doRead(const std::string& clientId, const daq::stream::StreamPtr& stream) +{ + std::weak_ptr stream_weak = stream; + + // The callback is to be called in the thread that remains active as long as this object exists, + // ensuring that the captured 'this' pointer is always valid. + auto readDoneCallback = [this, stream_weak, clientId](const boost::system::error_code& ec, std::size_t bytesRead) + { + if (auto stream = stream_weak.lock()) + this->onReadDone(clientId, stream, ec, bytesRead); + }; + stream->asyncReadSome(readDoneCallback); +} + +void StreamingServer::onReadDone(const std::string& clientId, + const daq::stream::StreamPtr& stream, + const boost::system::error_code& ec, + std::size_t bytesRead) +{ + if (ec) { + removeClient(clientId); + return; + } + + // any incoming data is ignored + stream->consume(bytesRead); + doRead(clientId, stream); +} + +void StreamingServer::removeClient(const std::string& clientId) +{ + LOG_I("client with id {} disconnected", clientId); + + if (auto iter = clients.find(clientId); iter != clients.end()) + { + auto outputSignals = iter->second.second; + for (const auto& [signalId, outputSignal] : outputSignals) + { + unsubscribeHandler(signalId, outputSignal); + } + clients.erase(iter); + } +} + void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) { auto writer = std::make_shared(stream); @@ -255,10 +299,13 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) } } - LOG_I("New client connected. Stream Id: {}", writer->id()); - clients.insert({writer->id(), {writer, outputSignals}}); + auto clientId = stream->endPointUrl(); + LOG_I("New client connected. Stream Id: {}", clientId); + clients.insert({clientId, {writer, outputSignals}}); writeSignalsAvailable(writer, filteredSignals); + + doRead(clientId, stream); } int StreamingServer::onControlCommand(const std::string& streamId, diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index e9286c5..bb63d4f 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -47,7 +47,7 @@ class StreamingTest : public testing::Test } }; -TEST_F(StreamingTest, Connect) +TEST_F(StreamingTest, ConnectAndDisconnect) { auto server = std::make_shared(context); server->start(StreamingPort, ControlPort); @@ -63,6 +63,20 @@ TEST_F(StreamingTest, Connect) ASSERT_FALSE(client.isConnected()); } +TEST_F(StreamingTest, StopServer) +{ + auto server = std::make_shared(context); + server->start(StreamingPort, ControlPort); + + auto client = StreamingClient(context, "127.0.0.1", StreamingPort, StreamingTarget); + + client.connect(); + ASSERT_TRUE(client.isConnected()); + + server->stop(); + server.reset(); +} + TEST_F(StreamingTest, ConnectTimeout) { auto server = std::make_shared(context); From 483098a857fd21d304e7d42faf28ffa465279f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE?= Date: Tue, 23 Jul 2024 10:13:29 +0200 Subject: [PATCH 084/127] Standardize cases to PascalCase (openDAQ/openDAQ#355) Change the following to PascalCase: * Component IDs * Reference Device * Reference Function Blocks (backwards compatible) * Classifier * FFT * Power * Renderer * Scaling * Statistics * Trigger * AudioDeviceModuleWavWriter * Reference device IO components (AI, CAN, RefCh) * Component names * Default client device * Type IDs * Reference modules * Streaming/config clients (backwards compatible) * Server modules * MiniAudio * Type names * Reference modules * Streaming/config clients * Server modules * Server capability protocol ID (backwards compatible) * Server capability protocol name * Struct Type field names * Other Required integration changes: * Generally none, except for where integration depends upon changed strings listed above (in the description) in some way * If relying on string comparison to hardcoded old IDs of things like FB, device, server types, or protocol IDs, those comparisons will need to be updated to match the new IDs. eg. a check like `if (fbType.getId() == "ref_fb_module_renderer")` will never be true * Old IDs can still be used when adding new objects to a device via `addDevice`/`addFunctionBlock` or similar calls --- .../websocket_streaming_client_module_impl.cpp | 14 +++++++------- .../test_websocket_streaming_client_module.cpp | 12 ++++++------ .../src/websocket_streaming_server_impl.cpp | 2 +- .../websocket_streaming_server_module_impl.cpp | 4 ++-- .../test_websocket_streaming_server_module.cpp | 16 ++++++++-------- .../src/websocket_client_device_impl.cpp | 2 +- .../src/websocket_streaming_server.cpp | 6 +++--- .../tests/streaming_test_helpers.h | 2 +- .../tests/test_signal_descriptor_converter.cpp | 2 +- 9 files changed, 30 insertions(+), 30 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index d9cfbc6..bb0f01a 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -11,8 +11,8 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE -static std::string WebsocketDeviceTypeId = "opendaq_lt_streaming"; -static std::string OldWebsocketDeviceTypeId = "opendaq_lt_streaming_old"; +static std::string WebsocketDeviceTypeId = "OpenDAQLTStreaming"; +static std::string OldWebsocketDeviceTypeId = "OpenDAQLTStreamingOld"; static std::string WebsocketDevicePrefix = "daq.lt"; static std::string OldWebsocketDevicePrefix = "daq.ws"; @@ -20,16 +20,16 @@ using namespace discovery; using namespace daq::websocket_streaming; WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr context) - : Module("openDAQ websocket client module", + : Module("OpenDAQWebsocketClientModule", daq::VersionInfo(WS_STREAM_CL_MODULE_MAJOR_VERSION, WS_STREAM_CL_MODULE_MINOR_VERSION, WS_STREAM_CL_MODULE_PATCH_VERSION), std::move(context), - "WebsocketStreamingClient") + "OpenDAQWebsocketClientModule") , deviceIndex(0) , discoveryClient( { [context = this->context](MdnsDiscoveredDevice discoveredDevice) { - auto cap = ServerCapability(WebsocketDeviceTypeId, "openDAQ LT Streaming", ProtocolType::Streaming); + auto cap = ServerCapability(WebsocketDeviceTypeId, "OpenDAQLTStreaming", ProtocolType::Streaming); if (!discoveredDevice.ipv4Address.empty()) { @@ -156,7 +156,7 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec // Set the connection info for the device ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); connectionInfo.setProtocolId(WebsocketDeviceTypeId); - connectionInfo.setProtocolName("openDAQ LT Streaming"); + connectionInfo.setProtocolName("OpenDAQLTStreaming"); connectionInfo.setProtocolType(ProtocolType::Streaming); connectionInfo.setConnectionType("TCP/IP"); connectionInfo.addAddress(host); @@ -197,7 +197,7 @@ StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) { - if (target.getProtocolId() != "opendaq_lt_streaming") + if (target.getProtocolId() != "OpenDAQLTStreaming") return false; if (target.getConnectionString().getLength() != 0) diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index b887201..483d2be 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -33,7 +33,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateModule) TEST_F(WebsocketStreamingClientModuleTest, ModuleName) { auto module = CreateModule(); - ASSERT_EQ(module.getName(), "openDAQ websocket client module"); + ASSERT_EQ(module.getName(), "OpenDAQWebsocketClientModule"); } TEST_F(WebsocketStreamingClientModuleTest, VersionAvailable) @@ -109,7 +109,7 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateDeviceConnectionFailed) // ASSERT_NO_THROW(connectionString = module.createConnectionString(serverCapabilityIgnored)); // ASSERT_FALSE(connectionString.assigned()); // -// ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ LT Streaming", ProtocolType::Streaming); +// ServerCapabilityConfigPtr serverCapability = ServerCapability("OpenDAQLTStreaming", "OpenDAQLTStreaming", ProtocolType::Streaming); // ASSERT_THROW(module.createConnectionString(serverCapability), InvalidParameterException); // // serverCapability.addAddress("123.123.123.123"); @@ -166,10 +166,10 @@ TEST_F(WebsocketStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); ASSERT_EQ(deviceTypes.getCount(), 2u); - ASSERT_TRUE(deviceTypes.hasKey("opendaq_lt_streaming")); - ASSERT_EQ(deviceTypes.get("opendaq_lt_streaming").getId(), "opendaq_lt_streaming"); - ASSERT_TRUE(deviceTypes.hasKey("opendaq_lt_streaming_old")); - ASSERT_EQ(deviceTypes.get("opendaq_lt_streaming_old").getId(), "opendaq_lt_streaming_old"); + ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreaming")); + ASSERT_EQ(deviceTypes.get("OpenDAQLTStreaming").getId(), "OpenDAQLTStreaming"); + ASSERT_TRUE(deviceTypes.hasKey("OpenDAQLTStreamingOld")); + ASSERT_EQ(deviceTypes.get("OpenDAQLTStreamingOld").getId(), "OpenDAQLTStreamingOld"); DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 5d9dde0..6f86a06 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -73,7 +73,7 @@ PropertyObjectPtr WebsocketStreamingServerImpl::getDiscoveryConfig() ServerTypePtr WebsocketStreamingServerImpl::createType(const ContextPtr& context) { return ServerType( - "openDAQ LT Streaming", + "OpenDAQLTStreaming", "openDAQ LT Streaming server", "Publishes device signals as a flat list and streams data over WebsocketTcp protocol", WebsocketStreamingServerImpl::createDefaultConfig(context)); diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index b373eb8..b75ff3d 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -8,10 +8,10 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE WebsocketStreamingServerModule::WebsocketStreamingServerModule(ContextPtr context) - : Module("openDAQ Websocket streaming server module", + : Module("OpenDAQWebsocketStreamingServerModule", daq::VersionInfo(WS_STREAM_SRV_MODULE_MAJOR_VERSION, WS_STREAM_SRV_MODULE_MINOR_VERSION, WS_STREAM_SRV_MODULE_PATCH_VERSION), std::move(context), - "WebsockerStreamingServer") + "OpenDAQWebsocketStreamingServerModule") { } diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index 4a6b6c8..4ec460f 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -57,7 +57,7 @@ static InstancePtr CreateTestInstance() static PropertyObjectPtr CreateServerConfig(const InstancePtr& instance) { - auto config = instance.getAvailableServerTypes().get("openDAQ LT Streaming").createDefaultConfig(); + auto config = instance.getAvailableServerTypes().get("OpenDAQLTStreaming").createDefaultConfig(); config.setPropertyValue("WebsocketStreamingPort", 0); config.setPropertyValue("WebsocketControlPort", 0); return config; @@ -76,7 +76,7 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateModule) TEST_F(WebsocketStreamingServerModuleTest, ModuleName) { auto module = CreateModule(); - ASSERT_EQ(module.getName(), "openDAQ Websocket streaming server module"); + ASSERT_EQ(module.getName(), "OpenDAQWebsocketStreamingServerModule"); } TEST_F(WebsocketStreamingServerModuleTest, VersionAvailable) @@ -110,8 +110,8 @@ TEST_F(WebsocketStreamingServerModuleTest, GetAvailableComponentTypes) DictPtr serverTypes; ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); ASSERT_EQ(serverTypes.getCount(), 1u); - ASSERT_TRUE(serverTypes.hasKey("openDAQ LT Streaming")); - ASSERT_EQ(serverTypes.get("openDAQ LT Streaming").getId(), "openDAQ LT Streaming"); + ASSERT_TRUE(serverTypes.hasKey("OpenDAQLTStreaming")); + ASSERT_EQ(serverTypes.get("OpenDAQLTStreaming").getId(), "OpenDAQLTStreaming"); } TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) @@ -119,8 +119,8 @@ TEST_F(WebsocketStreamingServerModuleTest, ServerConfig) auto module = CreateModule(); DictPtr serverTypes = module.getAvailableServerTypes(); - ASSERT_TRUE(serverTypes.hasKey("openDAQ LT Streaming")); - auto config = serverTypes.get("openDAQ LT Streaming").createDefaultConfig(); + ASSERT_TRUE(serverTypes.hasKey("OpenDAQLTStreaming")); + auto config = serverTypes.get("OpenDAQLTStreaming").createDefaultConfig(); ASSERT_TRUE(config.assigned()); ASSERT_TRUE(config.hasProperty("WebsocketStreamingPort")); @@ -136,7 +136,7 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateServer) auto module = CreateModule(device.getContext()); auto config = CreateServerConfig(device); - ASSERT_NO_THROW(module.createServer("openDAQ LT Streaming", device.getRootDevice(), config)); + ASSERT_NO_THROW(module.createServer("OpenDAQLTStreaming", device.getRootDevice(), config)); } TEST_F(WebsocketStreamingServerModuleTest, CreateServerFromInstance) @@ -144,5 +144,5 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateServerFromInstance) auto device = CreateTestInstance(); auto config = CreateServerConfig(device); - ASSERT_NO_THROW(device.addServer("openDAQ LT Streaming", config)); + ASSERT_NO_THROW(device.addServer("OpenDAQLTStreaming", config)); } diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 15a5222..bc6d70e 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -92,7 +92,7 @@ void WebsocketClientDeviceImpl::onSignalInit(const StringPtr& signalId, const Su if (auto signalIt = deviceSignals.find(signalId); signalIt != deviceSignals.end()) { - // sets signal name as it appeared in metadata "name" + // sets signal name as it appeared in metadata "Name" signalIt->second.asPtr().unlockAllAttributes(); signalIt->second.setName(sInfo.signalName); signalIt->second.asPtr().lockAllAttributes(); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index f0d65ed..0082494 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -60,7 +60,7 @@ void WebsocketStreamingServer::start() }); packetReader.start(); - const ServerCapabilityConfigPtr serverCapability = ServerCapability("opendaq_lt_streaming", "openDAQ-LT Streaming", ProtocolType::Streaming); + const ServerCapabilityConfigPtr serverCapability = ServerCapability("OpenDAQLTStreaming", "OpenDAQLTStreaming", ProtocolType::Streaming); serverCapability.setPrefix("daq.lt"); serverCapability.setPort(streamingPort); serverCapability.setConnectionType("TCP/IP"); @@ -73,8 +73,8 @@ void WebsocketStreamingServer::stop() { const auto info = this->device.getInfo(); const auto infoInternal = info.asPtr(); - if (info.hasServerCapability("opendaq_lt_streaming")) - infoInternal.removeServerCapability("opendaq_lt_streaming"); + if (info.hasServerCapability("OpenDAQLTStreaming")) + infoInternal.removeServerCapability("OpenDAQLTStreaming"); } stopInternal(); diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 4519b2d..f80a335 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -85,7 +85,7 @@ namespace streaming_test_helpers auto descriptor = daq::DataDescriptorBuilder() .setSampleType(daq::SampleType::Float64) - .setUnit(daq::Unit("V", 1, "voltage", "quantity")) + .setUnit(daq::Unit("V", 1, "voltage", "Quantity")) .setValueRange(daq::Range(0, 10)) .setRule(daq::ExplicitDataRule()) .setPostScaling(daq::LinearScaling(1.0, 0.0, daq::SampleType::Int16, daq::ScaledSampleType::Float64)) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index bb3b173..7ae9501 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -289,7 +289,7 @@ TEST(SignalConverter, subscribedBitfieldSignal) std::string memberName = "This is the measured value"; nlohmann::json bitsInterpretationObject = - R"([{"description": "Data overrun","index": 0,"uuid": "c214c128-2447-4cee-ba39-6227aed2eff4"}])"_json; + R"([{"Description": "Data overrun","index": 0,"uuid": "c214c128-2447-4cee-ba39-6227aed2eff4"}])"_json; bsp::SubscribedSignal subscribedSignal(signalNumber, bsp::Logging::logCallback()); From 984285de5f1eb2340e2de7cb7db7a4c2ea256a36 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 24 Jul 2024 13:42:44 +0200 Subject: [PATCH 085/127] Bump sdk version to 3.2 --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index e4b7f5c..5935a58 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 3.1.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION 3.2.0 LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index b7baad0..cfb4c90 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 3.1.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION 3.2.0 LANGUAGES CXX) add_subdirectory(src) From 7891aa13f179d5dcedbf6adfbcff042d31ad62ea Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Wed, 24 Jul 2024 14:31:49 +0200 Subject: [PATCH 086/127] Bump version to 3.3 --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 5935a58..3f71db8 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 3.2.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION 3.3.0 LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index cfb4c90..b88e0f1 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 3.2.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION 3.3.0 LANGUAGES CXX) add_subdirectory(src) From a7c3743f64065d7d0ccd635503eb052c1215937b Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 1 Jul 2024 06:47:40 +0200 Subject: [PATCH 087/127] Enable signal addition and removal within LT-streaming pseudo device: * Add signals to the device after signal's initialization done streaming client: * Trigger descriptor changed event when signal descriptors are assigned for the first time streaming server: * Capture core events to track available signals * Use a placeholder output signal for unsupported signals --- .../websocket_streaming/output_signal.h | 24 +- .../websocket_streaming/streaming_client.h | 51 +++- .../websocket_streaming/streaming_server.h | 28 +- .../websocket_client_device_impl.h | 9 +- .../websocket_streaming_impl.h | 1 + .../websocket_streaming_server.h | 7 + .../websocket_streaming/src/output_signal.cpp | 34 ++- .../src/streaming_client.cpp | 256 +++++++++++++----- .../src/streaming_server.cpp | 248 +++++++++++------ .../src/websocket_client_device_impl.cpp | 87 +++++- .../src/websocket_streaming_impl.cpp | 19 +- .../src/websocket_streaming_server.cpp | 92 +++++++ .../tests/streaming_test_helpers.h | 1 - .../tests/test_streaming.cpp | 42 +-- .../tests/test_websocket_client_device.cpp | 220 +++++++++++---- 15 files changed, 860 insertions(+), 259 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index 512eae4..aad6ffe 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -30,7 +30,7 @@ class OutputSignalBase; using OutputSignalBasePtr = std::shared_ptr; class OutputDomainSignalBase; -using OutputDomainSignaBaselPtr = std::shared_ptr; +using OutputDomainSignalBasePtr = std::shared_ptr; class OutputSignalBase { @@ -78,12 +78,26 @@ class OutputSignalBase DataDescriptorPtr domainDescriptor; }; +/// Used as a placeholder for openDAQ signals which aren't supported by LT-streaming +class OutputNullSignal : public OutputSignalBase +{ +public: + OutputNullSignal(const SignalPtr& signal, daq::streaming_protocol::LogCallback logCb); + + void writeDaqPacket(const PacketPtr& packet) override; + void setSubscribed(bool subscribed) override; + bool isDataSignal() override; + +protected: + void toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) override; +}; + class OutputValueSignalBase : public OutputSignalBase { public: OutputValueSignalBase(daq::streaming_protocol::BaseValueSignalPtr valueStream, const SignalPtr& signal, - OutputDomainSignaBaselPtr outputDomainSignal, + OutputDomainSignalBasePtr outputDomainSignal, daq::streaming_protocol::LogCallback logCb); void writeDaqPacket(const PacketPtr& packet) override; @@ -94,7 +108,7 @@ class OutputValueSignalBase : public OutputSignalBase virtual void writeDataPacket(const DataPacketPtr& packet) = 0; void toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) override; - OutputDomainSignaBaselPtr outputDomainSignal; + OutputDomainSignalBasePtr outputDomainSignal; private: void writeEventPacket(const EventPacketPtr& packet); @@ -132,7 +146,7 @@ class OutputSyncValueSignal : public OutputValueSignalBase public: OutputSyncValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, - OutputDomainSignaBaselPtr outputDomainSignal, + OutputDomainSignalBasePtr outputDomainSignal, const std::string& tableId, daq::streaming_protocol::LogCallback logCb); @@ -157,7 +171,7 @@ class OutputConstValueSignal : public OutputValueSignalBase OutputConstValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, const SignalPtr& signal, - OutputDomainSignaBaselPtr outputDomainSignal, + OutputDomainSignalBasePtr outputDomainSignal, const std::string& tableId, daq::streaming_protocol::LogCallback logCb); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 8cb1449..c40d20f 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -50,6 +50,7 @@ class StreamingClient std::function; using OnAvailableSignalsCallback = std::function& signalIds)>; using OnSubsciptionAckCallback = std::function; + using OnSignalsInitDoneCallback = std::function; StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection = false); StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target, bool useRawTcpConnection = false); @@ -57,15 +58,21 @@ class StreamingClient bool connect(); void disconnect(); + + void onDeviceAvailableSignalInit(const OnSignalCallback& callback); + void onDeviceSignalUpdated(const OnSignalCallback& callback); + void onDeviceDomainSingalInit(const OnDomainSignalInitCallback& callback); + void onDeviceAvailableSignals(const OnAvailableSignalsCallback& callback); + void onDeviceUnavailableSignals(const OnAvailableSignalsCallback& callback); + void onDeviceHiddenSignal(const OnSignalCallback& callback); + void onDeviceSignalsInitDone(const OnSignalsInitDoneCallback& callback); + + void onStreamingAvailableSignals(const OnAvailableSignalsCallback& callback); + void onStreamingUnavailableSignals(const OnAvailableSignalsCallback& callback); + void onStreamingHiddenSignal(const OnSignalCallback& callback); void onPacket(const OnPacketCallback& callack); - void onAvailableSignalInit(const OnSignalCallback& callback); - void onSignalUpdated(const OnSignalCallback& callback); - void onDomainSingalInit(const OnDomainSignalInitCallback& callback); - void onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback); - void onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback); - void onHiddenStreamingSignal(const OnSignalCallback& callback); - void onHiddenDeviceSignal(const OnSignalCallback& callback); void onSubscriptionAck(const OnSubsciptionAckCallback& callback); + std::string getHost(); uint16_t getPort(); std::string getTarget(); @@ -76,6 +83,8 @@ class StreamingClient protected: void parseConnectionString(const std::string& url); + void stopBackgroundContext(); + void onSignalMeta(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const std::string& method, const nlohmann::json& params); @@ -88,6 +97,9 @@ class StreamingClient void setSignalInitSatisfied(const std::string& signalId); std::vector findDataSignalsByTableId(const std::string& tableId); InputSignalBasePtr findTimeSignalByTableId(const std::string& tableId); + void checkTmpSubscribedSignalsInit(); + void unavailableSignalsHandler(const nlohmann::json::const_iterator& unavailableSignalsArray); + void availableSignalsHandler(const nlohmann::json::const_iterator& availableSignalsArray); LoggerPtr logger; LoggerComponentPtr loggerComponent; @@ -98,20 +110,31 @@ class StreamingClient std::string target; bool connected = false; boost::asio::io_context ioContext; + boost::asio::io_context backgroundContext; + daq::streaming_protocol::SignalContainer signalContainer; daq::streaming_protocol::ProtocolHanlderPtr protocolHandler; std::unordered_map availableSignals; std::unordered_map hiddenSignals; + + // streaming callbacks + OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; + OnAvailableSignalsCallback onUnavailableStreamingSignalsCb = [](const std::vector& signalIds) {}; + OnSignalCallback onHiddenStreamingSignalCb = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; + OnSubsciptionAckCallback onSubscriptionAckCallback = [](const StringPtr& signalId, bool subscribed) {}; OnPacketCallback onPacketCallback = [](const StringPtr&, const PacketPtr&) {}; - OnSignalCallback onAvailableSignalInitCb = [](const StringPtr&, const SubscribedSignalInfo&) {}; + + // device callbacks OnDomainSignalInitCallback onDomainSignalInitCallback = [](const StringPtr&, const StringPtr&) {}; - OnAvailableSignalsCallback onAvailableStreamingSignalsCb = [](const std::vector& signalIds) {}; OnAvailableSignalsCallback onAvailableDeviceSignalsCb = [](const std::vector& signalIds) {}; + OnAvailableSignalsCallback onUnavailableDeviceSignalsCb = [](const std::vector& signalIds) {}; + OnSignalCallback onAvailableSignalInitCb = [](const StringPtr&, const SubscribedSignalInfo&) {}; OnSignalCallback onSignalUpdatedCallback = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; - OnSignalCallback onHiddenStreamingSignalCb = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; OnSignalCallback onHiddenDeviceSignalInitCb = [](const StringPtr& signalId, const SubscribedSignalInfo&) {}; - OnSubsciptionAckCallback onSubscriptionAckCallback = [](const StringPtr& signalId, bool subscribed) {}; - std::thread clientThread; + OnSignalsInitDoneCallback onSignalsInitDone = []() {}; + + std::thread clientIoThread; + std::thread clientBackgroundThread; std::mutex clientMutex; std::condition_variable conditionVariable; std::chrono::milliseconds connectTimeout{1000}; @@ -127,6 +150,10 @@ class StreamingClient std::unordered_map, std::future, bool>> availableSigInitStatus; bool useRawTcpConnection; + + std::unordered_set tmpSubscribedSignalIds; + + void startBackgroundContext(); }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index a2b6964..db8739f 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -51,11 +51,15 @@ class StreamingServer void start(uint16_t port = daq::streaming_protocol::WEBSOCKET_LISTENING_PORT, uint16_t controlPort = daq::streaming_protocol::HTTP_CONTROL_PORT); void stop(); + void onAccept(const OnAcceptCallback& callback); void onSubscribe(const OnSubscribeCallback& callback); void onUnsubscribe(const OnUnsubscribeCallback& callback); - void unicastPacket(const std::string& streamId, const std::string& signalId, const PacketPtr& packet); - void broadcastPacket(const std::string& signalId, const PacketPtr &packet); + + void broadcastPacket(const std::string& signalId, const PacketPtr& packet); + + void addSignals(const ListPtr& signals); + void removeComponentSignals(const StringPtr& componentId); protected: using SignalMap = std::unordered_map; @@ -71,10 +75,28 @@ class StreamingServer void addToOutputSignals(const SignalPtr& signal, SignalMap& outputSignals, const streaming_protocol::StreamWriterPtr& writer); + void publishSignalsToClient(const streaming_protocol::StreamWriterPtr& writer, + const ListPtr& signals, + SignalMap& outputSignals); void onAcceptInternal(const daq::stream::StreamPtr& stream); + OutputDomainSignalBasePtr createOutputDomainSignal(const SignalPtr& daqDomainSignal, + const std::string& tableId, + const streaming_protocol::StreamWriterPtr& writer); + OutputSignalBasePtr createOutputValueSignal(const SignalPtr& daqSignal, + const OutputDomainSignalBasePtr& outputDomainSignal, + const std::string& tableId, + const streaming_protocol::StreamWriterPtr& writer); + void updateOutputValueSignal(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const streaming_protocol::StreamWriterPtr& writer); + void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); - void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals); + void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const std::vector& signalIds); + void writeSignalsUnavailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const std::vector& signalIds); void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); + bool isSignalSubscribed(const std::string& signalId) const; void subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); void unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 8273087..676bce1 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -40,13 +40,16 @@ class WebsocketClientDeviceImpl : public Device void onSignalInit(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onSignalUpdated(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); void onDomainSignalInit(const StringPtr& signalId, const StringPtr& domainSignalId); - void createDeviceSignals(const std::vector& signalIds); - void addHiddenSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void registerAvailableSignals(const std::vector& signalIds); + void removeSignals(const std::vector& signalIds); + void registerHiddenSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo); + void addInitializedSignals(); DeviceInfoConfigPtr deviceInfo; - std::map deviceSignals; + std::unordered_map deviceSignals; StreamingPtr websocketStreaming; StringPtr connectionString; + std::vector orderedSignalIds; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 5dc0792..5bdf799 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -41,6 +41,7 @@ class WebsocketStreamingImpl : public Streaming void prepareStreamingClient(); void onAvailableSignals(const std::vector& signalIds); + void onUnavailableSignals(const std::vector& signalIds); void onHiddenSignal(const std::string& signalId); daq::websocket_streaming::StreamingClientPtr streamingClient; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index 972adb7..cf9e202 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -35,6 +35,12 @@ class WebsocketStreamingServer void stop(); protected: + void addSignalsOfComponent(ComponentPtr& component); + void componentAdded(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); + void componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); + void componentUpdated(ComponentPtr& updatedComponent); + void coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); + DevicePtr device; ContextPtr context; @@ -42,6 +48,7 @@ class WebsocketStreamingServer uint16_t controlPort = 0; daq::websocket_streaming::StreamingServer streamingServer; daq::websocket_streaming::AsyncPacketReader packetReader; + LoggerComponentPtr loggerComponent; private: void stopInternal(); diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index d20889b..28aa76d 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -110,7 +110,8 @@ bool OutputSignalBase::isSubscribed() void OutputSignalBase::submitSignalChanges() { - stream->writeSignalMetaInformation(); + if (stream) + stream->writeSignalMetaInformation(); } void OutputSignalBase::writeDescriptorChangedEvent(const DataDescriptorPtr& descriptor) @@ -138,7 +139,7 @@ bool OutputSignalBase::isTimeConfigChanged(const DataDescriptorPtr& domainDescri OutputValueSignalBase::OutputValueSignalBase(daq::streaming_protocol::BaseValueSignalPtr valueStream, const SignalPtr& signal, - OutputDomainSignaBaselPtr outputDomainSignal, + OutputDomainSignalBasePtr outputDomainSignal, daq::streaming_protocol::LogCallback logCb) : OutputSignalBase(signal, outputDomainSignal->getDaqSignal().getDescriptor(), valueStream, logCb) , outputDomainSignal(outputDomainSignal) @@ -477,7 +478,7 @@ BaseSynchronousSignalPtr OutputSyncValueSignal::createSignalStream( } OutputSyncValueSignal::OutputSyncValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, - const SignalPtr& signal, OutputDomainSignaBaselPtr outputDomainSignal, + const SignalPtr& signal, OutputDomainSignalBasePtr outputDomainSignal, const std::string& tableId, daq::streaming_protocol::LogCallback logCb) : OutputValueSignalBase(createSignalStream(writer, signal, tableId, logCb), signal, outputDomainSignal, logCb) @@ -582,7 +583,7 @@ BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( } OutputConstValueSignal::OutputConstValueSignal(const daq::streaming_protocol::StreamWriterPtr& writer, - const SignalPtr& signal, OutputDomainSignaBaselPtr outputDomainSignal, + const SignalPtr& signal, OutputDomainSignalBasePtr outputDomainSignal, const std::string& tableId, daq::streaming_protocol::LogCallback logCb) : OutputValueSignalBase(createSignalStream(writer, signal, tableId, logCb), signal, outputDomainSignal, logCb) @@ -726,4 +727,29 @@ void OutputConstValueSignal::setSubscribed(bool subscribed) } } +OutputNullSignal::OutputNullSignal(const SignalPtr& signal, daq::streaming_protocol::LogCallback logCb) + : OutputSignalBase(signal, nullptr, nullptr, logCb) +{ +} + +void OutputNullSignal::writeDaqPacket(const PacketPtr& packet) +{ +} + +void OutputNullSignal::setSubscribed(bool subscribed) +{ + std::scoped_lock lock(subscribedSync); + + this->subscribed = subscribed; +} + +bool OutputNullSignal::isDataSignal() +{ + return daqSignal.getDomainSignal().assigned(); +} + +void OutputNullSignal::toStreamedSignal(const SignalPtr& signal, const SignalProps& sigProps) +{ +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 3b99567..3f60451 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -24,6 +24,7 @@ StreamingClient::StreamingClient(const ContextPtr& context, const std::string& c , useRawTcpConnection(useRawTcpConnection) { parseConnectionString(connectionString); + startBackgroundContext(); } StreamingClient::StreamingClient(const ContextPtr& context, const std::string& host, uint16_t port, const std::string& target, bool useRawTcpConnection) @@ -38,11 +39,46 @@ StreamingClient::StreamingClient(const ContextPtr& context, const std::string& h , signalContainer(logCallback) , useRawTcpConnection(useRawTcpConnection) { + startBackgroundContext(); +} + +void daq::websocket_streaming::StreamingClient::startBackgroundContext() +{ + clientBackgroundThread = std::thread( + [this]() + { + using namespace boost::asio; + executor_work_guard workGuard(backgroundContext.get_executor()); + backgroundContext.run(); + } + ); +} + +void StreamingClient::stopBackgroundContext() +{ + backgroundContext.stop(); + if (clientBackgroundThread.get_id() != std::this_thread::get_id()) + { + if (clientBackgroundThread.joinable()) + { + clientBackgroundThread.join(); + LOG_I("Websocket streaming client background thread joined"); + } + else + { + LOG_W("Websocket streaming client background thread is not joinable"); + } + } + else + { + LOG_C("Websocket streaming client background thread cannot join itself"); + } } StreamingClient::~StreamingClient() { disconnect(); + stopBackgroundContext(); } bool StreamingClient::connect() @@ -73,53 +109,17 @@ bool StreamingClient::connect() clientStream = std::make_unique(ioContext, host, std::to_string(port), target); protocolHandler = std::make_shared(ioContext, signalContainer, protocolMetaCallback, logCallback); + std::unique_lock lock(clientMutex); protocolHandler->startWithSyncInit(std::move(clientStream)); ioContext.restart(); - clientThread = std::thread([this]() { ioContext.run(); }); + clientIoThread = std::thread([this]() { ioContext.run(); }); conditionVariable.wait_for(lock, connectTimeout, [this]() { return connected; }); if (connected) - { - std::vector signalIds; - for (const auto& [signalId,_] : availableSignals) - { - signalIds.push_back(signalId); - std::promise signalInitPromise; - std::future signalInitFuture = signalInitPromise.get_future(); - availableSigInitStatus.insert_or_assign( - signalId, - std::make_tuple( - std::move(signalInitPromise), - std::move(signalInitFuture), - false - ) - ); - } - - // signal meta-information (signal description, tableId, related signals, etc.) - // is published only for subscribed signals. - // as workaround we temporarily subscribe all signals to receive signal meta-info - // and initialize signal descriptors - protocolHandler->subscribe(signalIds); - - const auto timeout = std::chrono::seconds(1); - auto timeoutExpired = std::chrono::system_clock::now() + timeout; - - for (const auto& [id, params] : availableSigInitStatus) - { - auto status = std::get<1>(params).wait_until(timeoutExpired); - if (status != std::future_status::ready) - { - LOG_W("signal {} has incomplete descriptors", id); - } - } - - // unsubscribe previously subscribed signals - protocolHandler->unsubscribe(signalIds); - } + checkTmpSubscribedSignalsInit(); return connected; } @@ -127,21 +127,21 @@ bool StreamingClient::connect() void StreamingClient::disconnect() { ioContext.stop(); - if (clientThread.get_id() != std::this_thread::get_id()) + if (clientIoThread.get_id() != std::this_thread::get_id()) { - if (clientThread.joinable()) + if (clientIoThread.joinable()) { - clientThread.join(); - LOG_I("Websocket streaming client thread joined"); + clientIoThread.join(); + LOG_I("Websocket streaming client IO thread joined"); } else { - LOG_W("Websocket streaming client thread is not joinable"); + LOG_W("Websocket streaming client IO thread is not joinable"); } } else { - LOG_C("Websocket streaming client thread cannot join itself"); + LOG_C("Websocket streaming client IO thread cannot join itself"); } connected = false; @@ -152,41 +152,56 @@ void StreamingClient::onPacket(const OnPacketCallback& callack) onPacketCallback = callack; } -void StreamingClient::onAvailableSignalInit(const OnSignalCallback& callback) +void StreamingClient::onDeviceAvailableSignalInit(const OnSignalCallback& callback) { onAvailableSignalInitCb = callback; } -void StreamingClient::onSignalUpdated(const OnSignalCallback& callback) +void StreamingClient::onDeviceSignalUpdated(const OnSignalCallback& callback) { onSignalUpdatedCallback = callback; } -void StreamingClient::onDomainSingalInit(const OnDomainSignalInitCallback& callback) +void StreamingClient::onDeviceDomainSingalInit(const OnDomainSignalInitCallback& callback) { onDomainSignalInitCallback = callback; } -void StreamingClient::onAvailableStreamingSignals(const OnAvailableSignalsCallback& callback) +void StreamingClient::onStreamingAvailableSignals(const OnAvailableSignalsCallback& callback) { onAvailableStreamingSignalsCb = callback; } -void StreamingClient::onAvailableDeviceSignals(const OnAvailableSignalsCallback& callback) +void StreamingClient::onDeviceAvailableSignals(const OnAvailableSignalsCallback& callback) { onAvailableDeviceSignalsCb = callback; } -void StreamingClient::onHiddenStreamingSignal(const OnSignalCallback& callback) +void StreamingClient::onStreamingUnavailableSignals(const OnAvailableSignalsCallback& callback) +{ + onUnavailableStreamingSignalsCb = callback; +} + +void StreamingClient::onDeviceUnavailableSignals(const OnAvailableSignalsCallback& callback) +{ + onUnavailableDeviceSignalsCb = callback; +} + +void StreamingClient::onStreamingHiddenSignal(const OnSignalCallback& callback) { onHiddenStreamingSignalCb = callback; } -void StreamingClient::onHiddenDeviceSignal(const OnSignalCallback& callback) +void StreamingClient::onDeviceHiddenSignal(const OnSignalCallback& callback) { onHiddenDeviceSignalInitCb = callback; } +void StreamingClient::onDeviceSignalsInitDone(const OnSignalsInitDoneCallback& callback) +{ + onSignalsInitDone = callback; +} + void StreamingClient::onSubscriptionAck(const OnSubsciptionAckCallback& callback) { onSubscriptionAckCallback = callback; @@ -293,36 +308,108 @@ void StreamingClient::onProtocolMeta(daq::streaming_protocol::ProtocolHandler& p { if (method == daq::streaming_protocol::META_METHOD_AVAILABLE) { - std::vector signalIds; auto availableSignalsArray = params.find(META_SIGNALIDS); - if (availableSignalsArray != params.end() && availableSignalsArray->is_array()) { - for (const auto& arrayItem : *availableSignalsArray) - { - std::string signalId = arrayItem; - signalIds.push_back(signalId); + availableSignalsHandler(availableSignalsArray); + } + } + else if (method == daq::streaming_protocol::META_METHOD_UNAVAILABLE) + { + auto unavailableSignalsArray = params.find(META_SIGNALIDS); + if (unavailableSignalsArray != params.end() && unavailableSignalsArray->is_array()) + { + unavailableSignalsHandler(unavailableSignalsArray); + } + } +} - if (auto signalIt = availableSignals.find(signalId); signalIt == availableSignals.end()) - { - availableSignals.insert({signalId, nullptr}); - } - else - { - LOG_E("Received duplicate of available signal. ID is {}.", signalId); - } - } +void StreamingClient::availableSignalsHandler(const nlohmann::json::const_iterator& availableSignalsArray) +{ + std::vector signalIds; + for (const auto& arrayItem : *availableSignalsArray) + { + std::string signalId = arrayItem; + signalIds.push_back(signalId); - onAvailableDeviceSignalsCb(signalIds); - onAvailableStreamingSignalsCb(signalIds); + if (auto signalIt = availableSignals.find(signalId); signalIt == availableSignals.end()) + { + availableSignals.insert({signalId, nullptr}); + } + else + { + LOG_E("Received duplicate of available signal. ID is {}.", signalId); } + } + + onAvailableDeviceSignalsCb(signalIds); + onAvailableStreamingSignalsCb(signalIds); - std::unique_lock lock(clientMutex); + std::unique_lock lock(clientMutex); + + for (const auto& signalId : signalIds) + { + tmpSubscribedSignalIds.insert(signalId); + + std::promise signalInitPromise; + std::future signalInitFuture = signalInitPromise.get_future(); + availableSigInitStatus.insert_or_assign( + signalId, + std::make_tuple( + std::move(signalInitPromise), + std::move(signalInitFuture), + false + ) + ); + } + + // signal meta-information (signal description, tableId, related signals, etc.) + // is published only for subscribed signals. + // as workaround we temporarily subscribe all signals to receive signal meta-info + // and initialize signal descriptors + this->protocolHandler->subscribe(signalIds); + + if (connected) + { + // wait for signals initialization done in a separate thread + backgroundContext.dispatch( + [this]() + { + std::unique_lock lock(clientMutex); + checkTmpSubscribedSignalsInit(); + } + ); + } + else + { connected = true; conditionVariable.notify_all(); } } +void StreamingClient::unavailableSignalsHandler(const nlohmann::json::const_iterator& unavailableSignalsArray) +{ + std::vector signalIds; + for (const auto& arrayItem : *unavailableSignalsArray) + { + std::string signalId = arrayItem; + + if (auto signalIt = availableSignals.find(signalId); signalIt != availableSignals.end()) + { + availableSignals.erase(signalIt); + } + else + { + LOG_E("Received unavailable signal which were not available before. ID is {}.", signalId); + } + + signalIds.push_back(signalId); + } + + onUnavailableStreamingSignalsCb(signalIds); + onUnavailableDeviceSignalsCb(signalIds); +} + void StreamingClient::subscribeSignal(const std::string& signalId) { if (auto it = availableSignals.find(signalId); it != availableSignals.end()) @@ -424,6 +511,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig onHiddenStreamingSignalCb(signalId, sInfo); onHiddenDeviceSignalInitCb(signalId, sInfo); onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); + publishSignalChanges(inputSignal, true, true); } else if (available && !inputSignal) { @@ -432,6 +520,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig onAvailableSignalInitCb(signalId, sInfo); onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); setSignalInitSatisfied(signalId); + publishSignalChanges(inputSignal, true, true); } else { @@ -600,4 +689,31 @@ void StreamingClient::setSignalInitSatisfied(const std::string& signalId) } } +void StreamingClient::checkTmpSubscribedSignalsInit() +{ + if (tmpSubscribedSignalIds.empty()) + return; + + const auto timeout = std::chrono::seconds(1); + auto timeoutExpired = std::chrono::system_clock::now() + timeout; + + for (const auto& [id, params] : availableSigInitStatus) + { + if (tmpSubscribedSignalIds.count(id) != 0) + { + auto status = std::get<1>(params).wait_until(timeoutExpired); + if (status != std::future_status::ready) + { + LOG_W("signal {} has incomplete descriptors", id); + } + } + } + + // unsubscribe previously subscribed signals + protocolHandler->unsubscribe(std::vector(tmpSubscribedSignalIds.begin(), tmpSubscribedSignalIds.end())); + tmpSubscribedSignalIds.clear(); + + onSignalsInitDone(); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index bcec5d6..5cf01ce 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -8,6 +8,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -117,26 +118,23 @@ void StreamingServer::onUnsubscribe(const OnUnsubscribeCallback& callback) onUnsubscribeCallback = callback; } -void StreamingServer::unicastPacket(const std::string& streamId, - const std::string& signalId, - const PacketPtr& packet) -{ - if (auto clientIt = clients.find(streamId); clientIt != clients.end()) - { - auto signals = clientIt->second.second; - if (auto signalIt = signals.find(streamId); signalIt != signals.end()) - signalIt->second->writeDaqPacket(packet); - } -} - void StreamingServer::broadcastPacket(const std::string& signalId, const PacketPtr& packet) { for (auto& [_, client] : clients) { - auto signals = client.second; - if (auto signalIter = signals.find(signalId); signalIter != signals.end()) + auto writer = client.first; + auto& outputSignals = client.second; + + if (auto signalIter = outputSignals.find(signalId); signalIter != outputSignals.end()) { - signalIter->second->writeDaqPacket(packet); + auto outputSignal = signalIter->second; + auto eventPacket = packet.asPtrOrNull(); + + if (eventPacket.assigned() && eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) + { + updateOutputValueSignal(outputSignal, outputSignals, writer); + } + outputSignal->writeDaqPacket(packet); } } } @@ -155,62 +153,41 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, SignalMap& outputSignals, const StreamWriterPtr& writer) { - auto createOutputDomainSignal = [&writer, this](const SignalPtr& domainSignal) - -> std::shared_ptr - { - auto tableId = domainSignal.getGlobalId(); - const auto domainSignalRuleType = getSignalRuleType(domainSignal); - - if (domainSignalRuleType == DataRuleType::Linear) - { - return std::make_shared(writer, domainSignal, tableId, logCallback); - } - else - { - throw InvalidParameterException("Unsupported domain signal rule type"); - } - }; - auto domainSignal = signal.getDomainSignal(); if (domainSignal.assigned()) { auto domainSignalId = domainSignal.getGlobalId(); - OutputDomainSignaBaselPtr outputDomainSignal; + OutputDomainSignalBasePtr outputDomainSignal; if (const auto& outputSignalIt = outputSignals.find(domainSignalId); outputSignalIt != outputSignals.end()) { - outputDomainSignal = std::dynamic_pointer_cast(outputSignalIt->second); - if (!outputDomainSignal) - throw NoInterfaceException("Registered output signal {} is not of domain type", domainSignalId); + auto outputSignal = outputSignalIt->second; + + if (std::dynamic_pointer_cast(outputSignal)) + { + outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); + outputSignals[domainSignalId] = outputDomainSignal; + } + else + { + outputDomainSignal = std::dynamic_pointer_cast(outputSignal); + if (!outputDomainSignal) + throw NoInterfaceException("Registered output signal {} is not of domain type", domainSignalId); + } } else { - outputDomainSignal = createOutputDomainSignal(domainSignal); + outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); outputSignals.insert({domainSignalId, outputDomainSignal}); } - auto tableId = domainSignalId; + auto tableId = domainSignalId.toStdString(); const auto domainSignalRuleType = getSignalRuleType(domainSignal); if (domainSignalRuleType == DataRuleType::Linear) { - const auto valueSignalRuleType = getSignalRuleType(signal); - if (valueSignalRuleType == DataRuleType::Explicit) - { - auto outputValueSignal = - std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); - outputSignals.insert({signal.getGlobalId(), outputValueSignal}); - } - else if (valueSignalRuleType == DataRuleType::Constant) - { - auto outputValueSignal = - std::make_shared(writer, signal, outputDomainSignal, tableId, logCallback); - outputSignals.insert({signal.getGlobalId(), outputValueSignal}); - } - else - { - throw InvalidParameterException("Unsupported value signal rule type"); - } + auto outputValueSignal = createOutputValueSignal(signal, outputDomainSignal, tableId, writer); + outputSignals.insert_or_assign(signal.getGlobalId(), outputValueSignal); } else { @@ -221,7 +198,8 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, { if (const auto& outputSignalIt = outputSignals.find(signal.getGlobalId()); outputSignalIt == outputSignals.end()) { - outputSignals.insert({signal.getGlobalId(), createOutputDomainSignal(signal)}); + auto outputDomainSignal = createOutputDomainSignal(signal, signal.getGlobalId(), writer); + outputSignals.insert({signal.getGlobalId(), outputDomainSignal}); } } } @@ -277,34 +255,17 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) writeInit(writer); auto signals = List(); - auto filteredSignals = List(); + if (onAcceptCallback) signals = onAcceptCallback(writer); auto outputSignals = std::unordered_map(); - for (const auto& signal : signals) - { - if (!signal.getPublic()) - continue; - filteredSignals.pushBack(signal); - - try - { - addToOutputSignals(signal, outputSignals, writer); - } - catch (const DaqException& e) - { - LOG_W("Failed to create an output websocket signal: {}", e.what()); - } - } + publishSignalsToClient(writer, signals, outputSignals); auto clientId = stream->endPointUrl(); LOG_I("New client connected. Stream Id: {}", clientId); clients.insert({clientId, {writer, outputSignals}}); - - writeSignalsAvailable(writer, filteredSignals); - doRead(clientId, stream); } @@ -374,19 +335,24 @@ void StreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWri writer->writeMetaInformation(0, msg); } -void StreamingServer::writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals) +void StreamingServer::writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const std::vector& signalIds) { - std::vector signalIds; - - for (const auto& signal : signals) - signalIds.push_back(signal.getGlobalId()); - nlohmann::json msg; msg[METHOD] = META_METHOD_AVAILABLE; msg[PARAMS][META_SIGNALIDS] = signalIds; writer->writeMetaInformation(0, msg); } +void StreamingServer::writeSignalsUnavailable(const daq::streaming_protocol::StreamWriterPtr& writer, + const std::vector& signalIds) +{ + nlohmann::json msg; + msg[METHOD] = META_METHOD_UNAVAILABLE; + msg[PARAMS][META_SIGNALIDS] = signalIds; + writer->writeMetaInformation(0, msg); +} + void StreamingServer::writeInit(const streaming_protocol::StreamWriterPtr& writer) { nlohmann::json initMeta; @@ -442,4 +408,126 @@ void StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSign } } +void StreamingServer::addSignals(const ListPtr& signals) +{ + for (auto& [_, client] : clients) + { + auto writer = client.first; + auto& outputSignals = client.second; + + publishSignalsToClient(writer, signals, outputSignals); + } +} + +void StreamingServer::removeComponentSignals(const StringPtr& componentId) +{ + auto removedComponentId = componentId.toStdString(); + + for (auto& [_, client] : clients) + { + auto writer = client.first; + auto& outputSignals = client.second; + + std::vector signalsToRemove; + + for (const auto& [signalId, outputSignal] : outputSignals) + { + // removed component is a signal, or signal is a descendant of removed component + if (signalId == removedComponentId || IdsParser::isNestedComponentId(removedComponentId, signalId)) + { + signalsToRemove.push_back(signalId); + unsubscribeHandler(signalId, outputSignal); + } + } + for (const auto& signalId : signalsToRemove) + { + outputSignals.erase(signalId); + } + + writeSignalsUnavailable(writer, signalsToRemove); + } +} + +void StreamingServer::updateOutputValueSignal(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const StreamWriterPtr& writer) +{ + auto placeholderSignal = std::dynamic_pointer_cast(outputSignal); + + if (placeholderSignal) + { + auto daqSignal = outputSignal->getDaqSignal(); + auto signalId = daqSignal.getGlobalId().toStdString(); + + LOG_I("Parameters of unsupported signal {} has been changed, check if it is supported now ...", daqSignal.getGlobalId()); + try + { + addToOutputSignals(daqSignal, outputSignals, writer); + outputSignal = outputSignals.at(signalId); + + if (placeholderSignal->isSubscribed()) + outputSignal->setSubscribed(true); + } + catch (const DaqException& e) + { + LOG_W("Failed to create an output LT streaming signal for {}: {}", daqSignal.getGlobalId(), e.what()); + } + } +} + +void StreamingServer::publishSignalsToClient(const StreamWriterPtr& writer, + const ListPtr& signals, + SignalMap& outputSignals) +{ + std::vector filteredSignalsIds; + for (const auto& daqSignal : signals) + { + if (!daqSignal.getPublic()) + continue; + + auto signalId = daqSignal.getGlobalId().toStdString(); + filteredSignalsIds.push_back(signalId); + + try + { + addToOutputSignals(daqSignal, outputSignals, writer); + } + catch (const DaqException& e) + { + LOG_W("Failed to create an output LT streaming signal for {}: {}", signalId, e.what()); + auto placeholderSignal = std::make_shared(daqSignal, logCallback); + outputSignals.insert({signalId, placeholderSignal}); + } + } + + writeSignalsAvailable(writer, filteredSignalsIds); +} + +OutputDomainSignalBasePtr StreamingServer::createOutputDomainSignal(const SignalPtr& daqDomainSignal, + const std::string& tableId, + const StreamWriterPtr& writer) +{ + const auto domainSignalRuleType = getSignalRuleType(daqDomainSignal); + + if (domainSignalRuleType == DataRuleType::Linear) + return std::make_shared(writer, daqDomainSignal, tableId, logCallback); + else + throw InvalidParameterException("Unsupported domain signal rule type"); +} + +OutputSignalBasePtr StreamingServer::createOutputValueSignal(const SignalPtr& daqSignal, + const OutputDomainSignalBasePtr& outputDomainSignal, + const std::string& tableId, + const StreamWriterPtr& writer) +{ + const auto valueSignalRuleType = getSignalRuleType(daqSignal); + + if (valueSignalRuleType == DataRuleType::Explicit) + return std::make_shared(writer, daqSignal, outputDomainSignal, tableId, logCallback); + else if (valueSignalRuleType == DataRuleType::Constant) + return std::make_shared(writer, daqSignal, outputDomainSignal, tableId, logCallback); + else + throw InvalidParameterException("Unsupported value signal rule type"); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index bc6d70e..e7a605d 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -5,6 +5,7 @@ #include "websocket_streaming/websocket_client_signal_factory.h" #include "websocket_streaming/websocket_streaming_factory.h" #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -56,31 +57,43 @@ void WebsocketClientDeviceImpl::createWebsocketStreaming() { this->onSignalInit(signalId, sInfo); }; - streamingClient->onAvailableSignalInit(signalInitCallback); + streamingClient->onDeviceAvailableSignalInit(signalInitCallback); auto signalUpdatedCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { this->onSignalUpdated(signalId, sInfo); }; - streamingClient->onSignalUpdated(signalUpdatedCallback); + streamingClient->onDeviceSignalUpdated(signalUpdatedCallback); auto domainSignalInitCallback = [this](const StringPtr& dataSignalId,const StringPtr& domainSignalId) { this->onDomainSignalInit(dataSignalId, domainSignalId); }; - streamingClient->onDomainSingalInit(domainSignalInitCallback); + streamingClient->onDeviceDomainSingalInit(domainSignalInitCallback); auto availableSignalsCallback = [this](const std::vector& signalIds) { - this->createDeviceSignals(signalIds); + this->registerAvailableSignals(signalIds); }; - streamingClient->onAvailableDeviceSignals(availableSignalsCallback); + streamingClient->onDeviceAvailableSignals(availableSignalsCallback); + + auto unavailableSignalsCallback = [this](const std::vector& signalIds) + { + this->removeSignals(signalIds); + }; + streamingClient->onDeviceUnavailableSignals(unavailableSignalsCallback); auto hiddenSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { - this->addHiddenSignal(signalId, sInfo); + this->registerHiddenSignal(signalId, sInfo); }; - streamingClient->onHiddenDeviceSignal(hiddenSignalCallback); + streamingClient->onDeviceHiddenSignal(hiddenSignalCallback); + + auto signalsInitDoneCallback = [this]() + { + this->addInitializedSignals(); + }; + streamingClient->onDeviceSignalsInitDone(signalsInitDoneCallback); websocketStreaming = WebsocketStreaming(streamingClient, connectionString, context); } @@ -127,7 +140,7 @@ void WebsocketClientDeviceImpl::onDomainSignalInit(const StringPtr& signalId, co } } -void WebsocketClientDeviceImpl::createDeviceSignals(const std::vector& signalIds) +void WebsocketClientDeviceImpl::registerAvailableSignals(const std::vector& signalIds) { // Adds to device only signals published by server explicitly and in the same order these were published for (const auto& signalId : signalIds) @@ -135,18 +148,70 @@ void WebsocketClientDeviceImpl::createDeviceSignals(const std::vectorcontext, this->signals, signalId); - this->addSignal(signal); deviceSignals.insert({signalId, signal}); + orderedSignalIds.push_back(signalId); } } -void WebsocketClientDeviceImpl::addHiddenSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) +void WebsocketClientDeviceImpl::removeSignals(const std::vector& signalIds) +{ + for (const auto& removedSignalId : signalIds) + { + auto signalIter = deviceSignals.find(removedSignalId); + if (signalIter == deviceSignals.end()) + { + LOG_E("Signal with id {} is not found in LT streaming device", removedSignalId); + continue; + } + + auto signalToRemove = signalIter->second; + + // recreate signal -> domainSignal relations in the same way as on server + for (const auto& [_, dataSignal] : deviceSignals) + { + auto domainSignal = dataSignal.getDomainSignal(); + if (domainSignal.assigned() && domainSignal.asPtr().getRemoteId() == removedSignalId) + { + dataSignal.asPtr().setMirroredDomainSignal(nullptr); + } + } + + this->removeSignal(signalToRemove); + deviceSignals.erase(signalIter); + orderedSignalIds.erase(std::remove(orderedSignalIds.begin(), orderedSignalIds.end(), removedSignalId), orderedSignalIds.end()); + } +} + +void WebsocketClientDeviceImpl::registerHiddenSignal(const StringPtr& signalId, const SubscribedSignalInfo& sInfo) { // Adds 'hidden' signal which were not published by server explicitly as available auto signal = WebsocketClientSignal(this->context, this->signals, signalId); - this->addSignal(signal); deviceSignals.insert({signalId, signal}); onSignalInit(signalId, sInfo); + orderedSignalIds.push_back(signalId.toStdString()); +} + +void WebsocketClientDeviceImpl::addInitializedSignals() +{ + for (const auto& signalId : orderedSignalIds) + { + if (auto signalIt = deviceSignals.find(String(signalId)); signalIt != deviceSignals.end()) + { + auto signal = signalIt->second; + + if (!this->signals.findComponent(signal.getLocalId()).assigned()) + { + if (websocketStreaming.assigned()) + { + websocketStreaming.addSignals({signal}); + auto mirroredSignalConfigPtr = signal.template asPtr(); + mirroredSignalConfigPtr.setActiveStreamingSource(websocketStreaming.getConnectionString()); + } + + this->addSignal(signal); + } + } + } } void WebsocketClientDeviceImpl::updateSignalProperties(const SignalPtr& signal, const SubscribedSignalInfo& sInfo) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 4cfe8e9..34f4b8d 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -60,13 +60,19 @@ void WebsocketStreamingImpl::prepareStreamingClient() { this->onAvailableSignals(signalIds); }; - streamingClient->onAvailableStreamingSignals(availableSignalsCallback); + streamingClient->onStreamingAvailableSignals(availableSignalsCallback); + + auto unavailableSignalsCallback = [this](const std::vector& signalIds) + { + this->onUnavailableSignals(signalIds); + }; + streamingClient->onStreamingUnavailableSignals(unavailableSignalsCallback); auto hiddenSignalCallback = [this](const StringPtr& signalId, const SubscribedSignalInfo& /*sInfo*/) { this->onHiddenSignal(signalId); }; - streamingClient->onHiddenStreamingSignal(hiddenSignalCallback); + streamingClient->onStreamingHiddenSignal(hiddenSignalCallback); auto signalSubscriptionAckCallback = [this](const std::string& signalStringId, bool subscribed) { @@ -84,6 +90,15 @@ void WebsocketStreamingImpl::onAvailableSignals(const std::vector& } } +void WebsocketStreamingImpl::onUnavailableSignals(const std::vector& signalIds) +{ + for (const auto& signalId : signalIds) + { + auto signalStringId = String(signalId); + removeFromAvailableSignals(signalStringId); + } +} + void WebsocketStreamingImpl::onHiddenSignal(const std::string& signalId) { if (const auto it = hiddenSignals.find(signalId); it == hiddenSignals.end()) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 0082494..3392295 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -7,11 +7,14 @@ #include #include #include +#include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING WebsocketStreamingServer::~WebsocketStreamingServer() { + this->context.getOnCoreEvent() -= event(this, &WebsocketStreamingServer::coreEventCallback); stopInternal(); } @@ -25,6 +28,7 @@ WebsocketStreamingServer::WebsocketStreamingServer(const DevicePtr& device, cons , context(context) , streamingServer(context) , packetReader(device, context) + , loggerComponent(context.getLogger().getOrAddComponent("WebsocketStreamingServer")) { } @@ -60,6 +64,8 @@ void WebsocketStreamingServer::start() }); packetReader.start(); + this->context.getOnCoreEvent() += event(this, &WebsocketStreamingServer::coreEventCallback); + const ServerCapabilityConfigPtr serverCapability = ServerCapability("OpenDAQLTStreaming", "OpenDAQLTStreaming", ProtocolType::Streaming); serverCapability.setPrefix("daq.lt"); serverCapability.setPort(streamingPort); @@ -86,4 +92,90 @@ void WebsocketStreamingServer::stopInternal() streamingServer.stop(); } +void WebsocketStreamingServer::coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) +{ + switch (static_cast(eventArgs.getEventId())) + { + case CoreEventId::ComponentAdded: + componentAdded(sender, eventArgs); + break; + case CoreEventId::ComponentRemoved: + componentRemoved(sender, eventArgs); + break; + case CoreEventId::ComponentUpdateEnd: + componentUpdated(sender); + break; + default: + break; + } +} + +void WebsocketStreamingServer::componentAdded(ComponentPtr& /*sender*/, CoreEventArgsPtr& eventArgs) +{ + ComponentPtr addedComponent = eventArgs.getParameters().get("Component"); + + auto deviceGlobalId = device.getGlobalId().toStdString(); + auto addedComponentGlobalId = addedComponent.getGlobalId().toStdString(); + if (addedComponentGlobalId.find(deviceGlobalId) != 0) + return; + + LOG_I("Added Component: {};", addedComponentGlobalId); + addSignalsOfComponent(addedComponent); +} + +void WebsocketStreamingServer::addSignalsOfComponent(ComponentPtr& component) +{ + auto signalsToAdd = List(); + + if (component.supportsInterface()) + { + LOG_I("Added Signal: {};", component.getGlobalId()); + signalsToAdd.pushBack(component.asPtr()); + } + else if (component.supportsInterface()) + { + auto nestedComponents = component.asPtr().getItems(search::Recursive(search::Any())); + for (const auto& nestedComponent : nestedComponents) + { + if (nestedComponent.supportsInterface()) + { + LOG_I("Added Signal: {};", nestedComponent.getGlobalId()); + signalsToAdd.pushBack(nestedComponent.asPtr()); + } + } + } + + streamingServer.addSignals(signalsToAdd); +} + +void WebsocketStreamingServer::componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) +{ + StringPtr removedComponentLocalId = eventArgs.getParameters().get("Id"); + + auto deviceGlobalId = device.getGlobalId().toStdString(); + auto removedComponentGlobalId = + sender.getGlobalId().toStdString() + "/" + removedComponentLocalId.toStdString(); + if (removedComponentGlobalId.find(deviceGlobalId) != 0) + return; + + LOG_I("Component: {}; is removed", removedComponentGlobalId); + streamingServer.removeComponentSignals(removedComponentGlobalId); +} + +void WebsocketStreamingServer::componentUpdated(ComponentPtr& updatedComponent) +{ + auto deviceGlobalId = device.getGlobalId().toStdString(); + auto updatedComponentGlobalId = updatedComponent.getGlobalId().toStdString(); + if (updatedComponentGlobalId.find(deviceGlobalId) != 0) + return; + + LOG_I("Component: {}; is updated", updatedComponentGlobalId); + + // remove all registered signal of updated component since those might be modified or removed + streamingServer.removeComponentSignals(updatedComponentGlobalId); + + // add updated versions of signals + addSignalsOfComponent(updatedComponent); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index f80a335..d6acbf6 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -93,7 +93,6 @@ namespace streaming_test_helpers .setMetadata(meta) .build(); - auto timeSignal = createLinearTimeSignal(ctx); auto signal = SignalWithDescriptor(ctx, descriptor, nullptr, descriptor.getName()); signal.setDomainSignal(domainSignal); signal.setName(name); diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index bb63d4f..742c2b0 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -251,12 +251,12 @@ TEST_F(StreamingTest, PacketsCorrectSequence) std::this_thread::sleep_for(std::chrono::milliseconds(250)); - // 3 domain and 6 value packets - ASSERT_EQ(receivedPackets.size(), 9u); + // 2 event packets + data packets: 3 domain and 6 value packets + ASSERT_EQ(receivedPackets.size(), 11u); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[0])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[1])); - ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket1, receivedPackets[2])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[2])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[3])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket1, receivedPackets[4])); // packet automatically generated by client auto constantValuePacket2 = ConstantDataPacketWithDomain(domainPacket2, @@ -264,13 +264,13 @@ TEST_F(StreamingTest, PacketsCorrectSequence) sampleCount, 5); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[3])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[4])); - ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[5])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[5])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[6])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[7])); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[6])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[7])); - ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket3, receivedPackets[8])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[8])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[9])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket3, receivedPackets[10])); } // sends explicit value packet before constant value packet @@ -369,20 +369,20 @@ TEST_F(StreamingTest, PacketsIncorrectSequence) sampleCount, 5); - // 3 domain and 6 value packets - ASSERT_EQ(receivedPackets.size(), 8u); + // 2 event packets + data packets: 3 domain and 6 value packets + ASSERT_EQ(receivedPackets.size(), 10u); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[0])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[1])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[2])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[3])); // no constant packet generated since the signal value is unknown - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[2])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[3])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[4])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[5])); // contains only last change of constant - ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[4])); + ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[6])); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[5])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[6])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[7])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[8])); // value update is not yet applied, provides old value - ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket3, receivedPackets[7])); + ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket3, receivedPackets[9])); } diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 29b8c4b..cfabe22 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -71,24 +71,99 @@ TEST_F(WebsocketClientDeviceTest, DeviceInfo) ASSERT_EQ(clientDeviceInfo.getConnectionString(), HOST); } -TEST_F(WebsocketClientDeviceTest, SignalWithDomain) +class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public testing::WithParamInterface { +public: + bool addSignals(const ListPtr& signals, + const StreamingServerPtr& server, + const ContextPtr& context) + { + SizeT addedSigCount = 0; + std::promise addSigPromise; + std::future addSigFuture = addSigPromise.get_future(); + + auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + { + auto params = args.getParameters(); + if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded) + { + ComponentPtr component = params.get("Component"); + if (component.asPtrOrNull().assigned()) + { + addedSigCount++; + if (addedSigCount == signals.getCount()) + addSigPromise.set_value(); + } + } + }; + + context.getOnCoreEvent() += eventHandler; + + server->addSignals(signals); + + bool result = (addSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); + + context.getOnCoreEvent() -= eventHandler; + return result; + } + + bool removeSignals(const ListPtr& signals, + const StreamingServerPtr& server, + const ContextPtr& context) + { + SizeT removedSigCount = 0; + std::promise rmSigPromise; + std::future rmSigFuture = rmSigPromise.get_future(); + + auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + { + if (static_cast(args.getEventId()) == CoreEventId::ComponentRemoved) + { + removedSigCount++; + if (removedSigCount == signals.getCount()) + rmSigPromise.set_value(); + } + }; + + context.getOnCoreEvent() += eventHandler; + + for (const auto& signal : signals) + server->removeComponentSignals(signal.getGlobalId()); + + bool result = (rmSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); + + context.getOnCoreEvent() -= eventHandler; + return result; + } +}; + +TEST_P(WebsocketClientDeviceTestP, SignalWithDomain) +{ + const bool signalsAddedAfterConnect = GetParam(); + // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); // Setup and start server which will publish created signal auto server = std::make_shared(context); server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { - auto signals = List(); - signals.pushBack(testValueSignal); - signals.pushBack(testDomainSignal); - return signals; + if (signalsAddedAfterConnect) + return List(); + else + return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + if (signalsAddedAfterConnect) + { + ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + } // Check the mirrored signal ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); @@ -140,100 +215,114 @@ TEST_F(WebsocketClientDeviceTest, SignalWithDomain) descriptor)); ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), testValueSignal.getDomainSignal().getDescriptor())); + + ASSERT_TRUE(removeSignals({testDomainSignal}, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); + ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + + ASSERT_TRUE(removeSignals({testValueSignal}, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } -TEST_F(WebsocketClientDeviceTest, SingleDomainSignal) +TEST_P(WebsocketClientDeviceTestP, SingleDomainSignal) { + const bool signalsAddedAfterConnect = GetParam(); + // Create server signal auto testSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto signals = List(testSignal); // Setup and start server which will publish created signal auto server = std::make_shared(context); - server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { - auto signals = List(); - signals.pushBack(testSignal); - return signals; + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { + if (signalsAddedAfterConnect) + return List(); + else + return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + if (signalsAddedAfterConnect) + { + ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + } // The mirrored signal exists and has descriptor ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + + ASSERT_TRUE(removeSignals(signals, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } -TEST_F(WebsocketClientDeviceTest, SingleUnsupportedSignal) +TEST_P(WebsocketClientDeviceTestP, SingleUnsupportedSignal) { + const bool signalsAddedAfterConnect = GetParam(); + // Create server signal auto testSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestSignal", nullptr); + auto signals = List(testSignal); // Setup and start server which will publish created signal auto server = std::make_shared(context); - server->onAccept([&testSignal](const daq::streaming_protocol::StreamWriterPtr& writer) { - auto signals = List(); - signals.pushBack(testSignal); - return signals; + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { + if (signalsAddedAfterConnect) + return List(); + else + return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + if (signalsAddedAfterConnect) + { + ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + } // The mirrored signal exists but does not have descriptor ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); ASSERT_FALSE(clientDevice.getSignals()[0].getDescriptor().assigned()); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); -} - -TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) -{ - // Create server side device - auto serverInstance = streaming_test_helpers::createServerInstance(); - - // Setup and start streaming server - auto server = WebsocketStreamingServer(serverInstance); - server.setStreamingPort(STREAMING_PORT); - server.setControlPort(CONTROL_PORT); - server.start(); - - // Create the client device - auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); - // There should not be any difference if we get signals recursively or not, - // since client device doesn't know anything about hierarchy - size_t expectedSignalCount = 0; - for (const auto& signal : serverInstance.getSignals(search::Recursive(search::Visible()))) - expectedSignalCount += signal.getPublic(); - - ListPtr signals; - ASSERT_NO_THROW(signals = clientDevice.getSignals()); - ASSERT_EQ(signals.getCount(), expectedSignalCount); - ASSERT_NO_THROW(signals = clientDevice.getSignals(search::Recursive(search::Visible()))); - ASSERT_EQ(signals.getCount(), expectedSignalCount); + ASSERT_TRUE(removeSignals(signals, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } -TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) +TEST_P(WebsocketClientDeviceTestP, SignalsWithSharedDomain) { + const bool signalsAddedAfterConnect = GetParam(); + // Create server signals auto timeSignal = streaming_test_helpers::createLinearTimeSignal(context); auto dataSignal1 = streaming_test_helpers::createExplicitValueSignal(context, "Data1", timeSignal); auto dataSignal2 = streaming_test_helpers::createExplicitValueSignal(context, "Data2", timeSignal); + auto signals = List(dataSignal1, timeSignal, dataSignal2); auto server = std::make_shared(context); server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { - auto signals = List(); - signals.pushBack(dataSignal1); - signals.pushBack(timeSignal); - signals.pushBack(dataSignal2); - return signals; + if (signalsAddedAfterConnect) + return List(); + else + return signals; }); server->start(STREAMING_PORT, CONTROL_PORT); // Create the client device auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + if (signalsAddedAfterConnect) + { + ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + } ASSERT_EQ(clientDevice.getSignals().getCount(), 3u); @@ -262,4 +351,41 @@ TEST_F(WebsocketClientDeviceTest, SignalsWithSharedDomain) ASSERT_EQ(clientDevice.getSignals()[2].getDomainSignal(), clientDevice.getSignals()[1]); ASSERT_EQ(clientDevice.getSignals()[0].getDomainSignal(), clientDevice.getSignals()[1]); + + ASSERT_TRUE(removeSignals({timeSignal}, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); + ASSERT_FALSE(clientDevice.getSignals()[1].getDomainSignal().assigned()); + + ASSERT_TRUE(removeSignals({dataSignal1, dataSignal2}, server, clientDevice.getContext())); + ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); +} + +INSTANTIATE_TEST_SUITE_P(SignalsAddedAfterConnect, WebsocketClientDeviceTestP, testing::Values(true, false)); + +TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) +{ + // Create server side device + auto serverInstance = streaming_test_helpers::createServerInstance(); + + // Setup and start streaming server + auto server = WebsocketStreamingServer(serverInstance); + server.setStreamingPort(STREAMING_PORT); + server.setControlPort(CONTROL_PORT); + server.start(); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + + // There should not be any difference if we get signals recursively or not, + // since client device doesn't know anything about hierarchy + size_t expectedSignalCount = 0; + for (const auto& signal : serverInstance.getSignals(search::Recursive(search::Visible()))) + expectedSignalCount += signal.getPublic(); + + ListPtr signals; + ASSERT_NO_THROW(signals = clientDevice.getSignals()); + ASSERT_EQ(signals.getCount(), expectedSignalCount); + ASSERT_NO_THROW(signals = clientDevice.getSignals(search::Recursive(search::Visible()))); + ASSERT_EQ(signals.getCount(), expectedSignalCount); } From ac4160cf951d8c424fd27a036772ad4a9d22b4d0 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Thu, 25 Jul 2024 08:02:01 +0200 Subject: [PATCH 088/127] Apply code review suggestions: * Modify LT-streaming server to only add new signals and remove deleted ones for updated components, instead of resetting all signals. * Make warning logs more descriptive for signals that are not supported by LT-streaming * Implement concurrent access synchronization for client data in the LT-streaming server handler * Reorder the members of the LT pseudo device object --- .../websocket_streaming/async_packet_reader.h | 4 +- .../websocket_streaming/streaming_server.h | 21 +- .../websocket_client_device_impl.h | 5 +- .../websocket_streaming_server.h | 2 +- .../src/async_packet_reader.cpp | 10 +- .../websocket_streaming/src/output_signal.cpp | 8 +- .../src/streaming_server.cpp | 238 ++++++++++++------ .../src/websocket_streaming_server.cpp | 51 ++-- 8 files changed, 215 insertions(+), 124 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index cd8b689..ad5e2aa 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -35,8 +35,8 @@ class AsyncPacketReader void stop(); void onPacket(const OnPacketCallback& callback); void setLoopFrequency(uint32_t freqency); - void startReadSignal(const SignalPtr& signal); - void stopReadSignal(const SignalPtr& signal); + void startReadSignals(const ListPtr& signals); + void stopReadSignals(const ListPtr& signals); protected: void startReadThread(); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index db8739f..937ce7a 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -42,8 +42,8 @@ class StreamingServer { public: using OnAcceptCallback = std::function(const daq::streaming_protocol::StreamWriterPtr& writer)>; - using OnSubscribeCallback = std::function; - using OnUnsubscribeCallback = std::function; + using OnStartSignalsReadCallback = std::function& signal)>; + using OnStopSignalsReadCallback = std::function& signal)>; StreamingServer(const ContextPtr& context); ~StreamingServer(); @@ -53,13 +53,14 @@ class StreamingServer void stop(); void onAccept(const OnAcceptCallback& callback); - void onSubscribe(const OnSubscribeCallback& callback); - void onUnsubscribe(const OnUnsubscribeCallback& callback); + void onStartSignalsRead(const OnStartSignalsReadCallback& callback); + void onStopSignalsRead(const OnStopSignalsReadCallback& callback); void broadcastPacket(const std::string& signalId, const PacketPtr& packet); void addSignals(const ListPtr& signals); void removeComponentSignals(const StringPtr& componentId); + void updateComponentSignals(const DictPtr& signals, const StringPtr& componentId); protected: using SignalMap = std::unordered_map; @@ -98,13 +99,16 @@ class StreamingServer void writeInit(const daq::streaming_protocol::StreamWriterPtr& writer); bool isSignalSubscribed(const std::string& signalId) const; - void subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); - void unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); + bool subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); + bool unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal); int onControlCommand(const std::string& streamId, const std::string& command, const daq::streaming_protocol::SignalIds& signalIds, std::string& errorMessage); + void startReadSignals(const ListPtr& signals); + void stopReadSignals(const ListPtr& signals); + uint16_t port; boost::asio::io_context ioContext; boost::asio::executor_work_guard work; @@ -113,12 +117,13 @@ class StreamingServer std::thread serverThread; ClientMap clients; OnAcceptCallback onAcceptCallback; - OnSubscribeCallback onSubscribeCallback; - OnUnsubscribeCallback onUnsubscribeCallback; + OnStartSignalsReadCallback onStartSignalsReadCallback; + OnStopSignalsReadCallback onStopSignalsReadCallback; LoggerPtr logger; LoggerComponentPtr loggerComponent; daq::streaming_protocol::LogCallback logCallback; bool serverRunning{false}; + std::mutex sync; private: static DataRuleType getSignalRuleType(const SignalPtr& domainSignal); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 676bce1..755d51b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -47,9 +47,10 @@ class WebsocketClientDeviceImpl : public Device DeviceInfoConfigPtr deviceInfo; std::unordered_map deviceSignals; - StreamingPtr websocketStreaming; - StringPtr connectionString; std::vector orderedSignalIds; + StringPtr connectionString; + + StreamingPtr websocketStreaming; }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index cf9e202..af2aa22 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -35,7 +35,6 @@ class WebsocketStreamingServer void stop(); protected: - void addSignalsOfComponent(ComponentPtr& component); void componentAdded(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); void componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); void componentUpdated(ComponentPtr& updatedComponent); @@ -51,6 +50,7 @@ class WebsocketStreamingServer LoggerComponentPtr loggerComponent; private: + static DictPtr getSignalsOfComponent(ComponentPtr& component); void stopInternal(); }; diff --git a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp index d2555d9..c48b839 100644 --- a/shared/libraries/websocket_streaming/src/async_packet_reader.cpp +++ b/shared/libraries/websocket_streaming/src/async_packet_reader.cpp @@ -94,16 +94,18 @@ void AsyncPacketReader::createReaders() } } -void AsyncPacketReader::startReadSignal(const SignalPtr& signal) +void AsyncPacketReader::startReadSignals(const ListPtr& signals) { std::scoped_lock lock(readersSync); - addReader(signal); + for (const auto& signal : signals) + addReader(signal); } -void AsyncPacketReader::stopReadSignal(const SignalPtr& signal) +void AsyncPacketReader::stopReadSignals(const ListPtr& signals) { std::scoped_lock lock(readersSync); - removeReader(signal); + for (const auto& signal : signals) + removeReader(signal); } void AsyncPacketReader::addReader(SignalPtr signalToRead) diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 28aa76d..dbad7e5 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -384,11 +384,11 @@ LinearTimeSignalPtr OutputLinearDomainSignal::createSignalStream( daq::SampleType daqSampleType = descriptor.getSampleType(); if (daqSampleType != daq::SampleType::Int64 && daqSampleType != daq::SampleType::UInt64) - throw InvalidParameterException("Unsupported domain signal sample type"); + throw InvalidParameterException("Unsupported domain signal sample type - only 64bit integer types are supported"); auto dataRule = descriptor.getRule(); if (dataRule.getType() != DataRuleType::Linear) - throw InvalidParameterException("Invalid domain signal data rule {}.", (size_t)dataRule.getType()); + throw InvalidParameterException("Invalid domain signal data rule - linear rule only is supported"); auto unit = descriptor.getUnit(); if (!unit.assigned() || @@ -469,7 +469,7 @@ BaseSynchronousSignalPtr OutputSyncValueSignal::createSignalStream( case daq::SampleType::RangeInt64: case daq::SampleType::Struct: default: - throw InvalidTypeException("Unsupported data signal sample type"); + throw InvalidTypeException("Unsupported data signal sample type - only real numeric types are supported"); } SignalDescriptorConverter::ToStreamedValueSignal(signal, syncStream, getSignalProps(signal)); @@ -574,7 +574,7 @@ BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( case daq::SampleType::RangeInt64: case daq::SampleType::Struct: default: - throw InvalidTypeException("Unsupported data signal sample type"); + throw InvalidTypeException("Unsupported data signal sample type - only real numeric types are supported"); } SignalDescriptorConverter::ToStreamedValueSignal(signal, constStream, getSignalProps(signal)); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 5cf01ce..5246c29 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -108,18 +108,20 @@ void StreamingServer::onAccept(const OnAcceptCallback& callback) onAcceptCallback = callback; } -void StreamingServer::onSubscribe(const OnSubscribeCallback& callback) +void StreamingServer::onStartSignalsRead(const OnStartSignalsReadCallback& callback) { - onSubscribeCallback = callback; + onStartSignalsReadCallback = callback; } -void StreamingServer::onUnsubscribe(const OnUnsubscribeCallback& callback) +void StreamingServer::onStopSignalsRead(const OnStopSignalsReadCallback& callback) { - onUnsubscribeCallback = callback; + onStopSignalsReadCallback = callback; } void StreamingServer::broadcastPacket(const std::string& signalId, const PacketPtr& packet) { + std::scoped_lock lock(sync); + for (auto& [_, client] : clients) { auto writer = client.first; @@ -144,7 +146,7 @@ DataRuleType StreamingServer::getSignalRuleType(const SignalPtr& signal) auto descriptor = signal.getDescriptor(); if (!descriptor.assigned() || !descriptor.getRule().assigned()) { - throw InvalidParameterException("Unknown signal rule"); + throw InvalidParameterException(R"(Signal "{}" has incomplete descriptor - unknown signal rule)", signal.getGlobalId()); } return descriptor.getRule().getType(); } @@ -172,7 +174,7 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, { outputDomainSignal = std::dynamic_pointer_cast(outputSignal); if (!outputDomainSignal) - throw NoInterfaceException("Registered output signal {} is not of domain type", domainSignalId); + throw NoInterfaceException("Previously registered domain output signal {} is not of domain type", domainSignalId); } } else @@ -191,7 +193,7 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, } else { - throw InvalidParameterException("Unsupported domain signal rule type"); + throw InvalidParameterException("Unsupported domain signal rule type - only domain signals with linear rule type are supported in LT-streaming"); } } else @@ -233,19 +235,38 @@ void StreamingServer::onReadDone(const std::string& clientId, doRead(clientId, stream); } +void StreamingServer::startReadSignals(const ListPtr& signals) +{ + if (onStartSignalsReadCallback && signals.getCount() > 0) + onStartSignalsReadCallback(signals); +} + +void StreamingServer::stopReadSignals(const ListPtr& signals) +{ + if (onStopSignalsReadCallback && signals.getCount() > 0) + onStopSignalsReadCallback(signals); +} + void StreamingServer::removeClient(const std::string& clientId) { LOG_I("client with id {} disconnected", clientId); - if (auto iter = clients.find(clientId); iter != clients.end()) + auto signalsToStopRead = List(); { - auto outputSignals = iter->second.second; - for (const auto& [signalId, outputSignal] : outputSignals) + std::scoped_lock lock(sync); + if (auto iter = clients.find(clientId); iter != clients.end()) { - unsubscribeHandler(signalId, outputSignal); + const auto& outputSignals = iter->second.second; + for (const auto& [signalId, outputSignal] : outputSignals) + { + if (unsubscribeHandler(signalId, outputSignal) && outputSignal->isDataSignal()) + signalsToStopRead.pushBack(outputSignal->getDaqSignal()); + } + clients.erase(iter); } - clients.erase(iter); } + + stopReadSignals(signalsToStopRead); } void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) @@ -259,13 +280,15 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) if (onAcceptCallback) signals = onAcceptCallback(writer); - auto outputSignals = std::unordered_map(); - - publishSignalsToClient(writer, signals, outputSignals); - auto clientId = stream->endPointUrl(); LOG_I("New client connected. Stream Id: {}", clientId); - clients.insert({clientId, {writer, outputSignals}}); + { + std::scoped_lock lock(sync); + clients.insert({clientId, {writer, std::unordered_map()}}); + auto& outputSignals = clients.at(clientId).second; + publishSignalsToClient(writer, signals, outputSignals); + } + doRead(clientId, stream); } @@ -280,28 +303,46 @@ int StreamingServer::onControlCommand(const std::string& streamId, errorMessage = "Signal list is empty"; return -1; } - - auto clientIter = clients.find(streamId); - if (clientIter == std::end(clients)) + if (command != "subscribe" && command != "unsubscribe") { - LOG_W("Unknown streamId: {}, reject command", streamId); - errorMessage = "Unknown streamId: '" + streamId + "'"; + LOG_W("Unknown control command: {}", command); + errorMessage = "Unknown command: " + command; return -1; } - if (command == "subscribe" || command == "unsubscribe") + size_t unknownSignalsCount = 0; + std::string message = "Command '" + command + "' failed for unknown signals:\n"; + + auto signalsToStartRead = List(); + auto signalsToStopRead = List(); + { - size_t unknownSignalsCount = 0; - std::string message = "Command '" + command + "' failed for unknown signals:\n"; + std::scoped_lock lock(sync); + + auto clientIter = clients.find(streamId); + if (clientIter == std::end(clients)) + { + LOG_W("Unknown streamId: {}, reject command", streamId); + errorMessage = "Unknown streamId: '" + streamId + "'"; + return -1; + } + for (const auto& signalId : signalIds) { - auto signals = clientIter->second.second; - if (auto signalIter = signals.find(signalId); signalIter != signals.end()) + auto& outputSignals = clientIter->second.second; + if (auto signalIter = outputSignals.find(signalId); signalIter != outputSignals.end()) { + auto outputSignal = signalIter->second; if (command == "subscribe") - subscribeHandler(signalId, signalIter->second); + { + if (subscribeHandler(signalId, outputSignal) && outputSignal->isDataSignal()) + signalsToStartRead.pushBack(outputSignal->getDaqSignal()); + } else if (command == "unsubscribe") - unsubscribeHandler(signalId, signalIter->second); + { + if (unsubscribeHandler(signalId, outputSignal) && outputSignal->isDataSignal()) + signalsToStopRead.pushBack(outputSignal->getDaqSignal()); + } } else { @@ -309,22 +350,24 @@ int StreamingServer::onControlCommand(const std::string& streamId, message.append(signalId + "\n"); } } + } - if (unknownSignalsCount > 0) - { - LOG_W("{}", message); - errorMessage = message; - return -1; - } + if (command == "subscribe") + startReadSignals(signalsToStartRead); + + if (command == "unsubscribe") + stopReadSignals(signalsToStopRead); + + if (unknownSignalsCount > 0) + { + LOG_W("{}", message); + errorMessage = message; + return -1; } else { - LOG_W("Unknown control command: {}", command); - errorMessage = "Unknown command: " + command; - return -1; + return 0; } - return 0; - } void StreamingServer::writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer) @@ -384,32 +427,28 @@ bool StreamingServer::isSignalSubscribed(const std::string& signalId) const return result; } -void StreamingServer::subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) +bool StreamingServer::subscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) { - // wasn't subscribed by any client - if (!isSignalSubscribed(signalId)) - { - if (signal->isDataSignal() && onSubscribeCallback) - onSubscribeCallback(signal->getDaqSignal()); - } - + // returns true if signal wasn't subscribed by any client + bool result = !isSignalSubscribed(signalId); signal->setSubscribed(true); + + return result; } -void StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) +bool StreamingServer::unsubscribeHandler(const std::string& signalId, OutputSignalBasePtr signal) { + if (!signal->isSubscribed()) + return false; signal->setSubscribed(false); - // became not subscribed by any client - if (!isSignalSubscribed(signalId)) - { - if (signal->isDataSignal() && onUnsubscribeCallback) - onUnsubscribeCallback(signal->getDaqSignal()); - } + // returns true if signal became not subscribed by any client + return !isSignalSubscribed(signalId); } void StreamingServer::addSignals(const ListPtr& signals) { + std::scoped_lock lock(sync); for (auto& [_, client] : clients) { auto writer = client.first; @@ -421,31 +460,84 @@ void StreamingServer::addSignals(const ListPtr& signals) void StreamingServer::removeComponentSignals(const StringPtr& componentId) { - auto removedComponentId = componentId.toStdString(); - - for (auto& [_, client] : clients) + auto signalsToStopRead = List(); { - auto writer = client.first; - auto& outputSignals = client.second; - - std::vector signalsToRemove; + std::scoped_lock lock(sync); + auto removedComponentId = componentId.toStdString(); - for (const auto& [signalId, outputSignal] : outputSignals) + for (auto& [_, client] : clients) { - // removed component is a signal, or signal is a descendant of removed component - if (signalId == removedComponentId || IdsParser::isNestedComponentId(removedComponentId, signalId)) + auto writer = client.first; + auto& outputSignals = client.second; + + std::vector signalsToRemove; + + for (const auto& [signalId, outputSignal] : outputSignals) + { + // removed component is a signal, or signal is a descendant of removed component + if (signalId == removedComponentId || IdsParser::isNestedComponentId(removedComponentId, signalId)) + { + signalsToRemove.push_back(signalId); + if (unsubscribeHandler(signalId, outputSignal) && outputSignal->isDataSignal()) + signalsToStopRead.pushBack(outputSignal->getDaqSignal()); + } + } + + if (!signalsToRemove.empty()) { - signalsToRemove.push_back(signalId); - unsubscribeHandler(signalId, outputSignal); + writeSignalsUnavailable(writer, signalsToRemove); + for (const auto& signalId : signalsToRemove) + outputSignals.erase(signalId); } } - for (const auto& signalId : signalsToRemove) + } + + stopReadSignals(signalsToStopRead); +} + +void StreamingServer::updateComponentSignals(const DictPtr& signals, const StringPtr& componentId) +{ + auto signalsToStopRead = List(); + { + std::scoped_lock lock(sync); + auto updatedComponentId = componentId.toStdString(); + + for (auto& [_, client] : clients) { - outputSignals.erase(signalId); - } + auto writer = client.first; + auto& outputSignals = client.second; - writeSignalsUnavailable(writer, signalsToRemove); + auto signalsToAdd = List(); + for (const auto& [signalId, signal] : signals) + { + if (auto iter = outputSignals.find(signalId.toStdString()); iter == outputSignals.end()) + signalsToAdd.pushBack(signal); + } + if (signalsToAdd.getCount() > 0) + publishSignalsToClient(writer, signalsToAdd, outputSignals); + + std::vector signalsToRemove; + for (const auto& [signalId, outputSignal] : outputSignals) + { + // signal is a descendant of updated component + if (IdsParser::isNestedComponentId(updatedComponentId, signalId) && + (!signals.hasKey(signalId) || !signals.get(signalId).getPublic())) + { + signalsToRemove.push_back(signalId); + if (unsubscribeHandler(signalId, outputSignal) && outputSignal->isDataSignal()) + signalsToStopRead.pushBack(outputSignal->getDaqSignal()); + } + } + if (!signalsToRemove.empty()) + { + writeSignalsUnavailable(writer, signalsToRemove); + for (const auto& signalId : signalsToRemove) + outputSignals.erase(signalId); + } + } } + + stopReadSignals(signalsToStopRead); } void StreamingServer::updateOutputValueSignal(OutputSignalBasePtr& outputSignal, @@ -470,7 +562,7 @@ void StreamingServer::updateOutputValueSignal(OutputSignalBasePtr& outputSignal, } catch (const DaqException& e) { - LOG_W("Failed to create an output LT streaming signal for {}: {}", daqSignal.getGlobalId(), e.what()); + LOG_W("Failed to re-create an output LT streaming signal for {}, reason: {}", daqSignal.getGlobalId(), e.what()); } } } @@ -494,7 +586,7 @@ void StreamingServer::publishSignalsToClient(const StreamWriterPtr& writer, } catch (const DaqException& e) { - LOG_W("Failed to create an output LT streaming signal for {}: {}", signalId, e.what()); + LOG_W("Failed to create an output LT streaming signal for {}, reason: {}", signalId, e.what()); auto placeholderSignal = std::make_shared(daqSignal, logCallback); outputSignals.insert({signalId, placeholderSignal}); } diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 3392295..0c11910 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -52,8 +52,8 @@ void WebsocketStreamingServer::start() return; streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignals(search::Recursive(search::Any())); }); - streamingServer.onSubscribe([this](const daq::SignalPtr& signal) { packetReader.startReadSignal(signal); } ); - streamingServer.onUnsubscribe([this](const daq::SignalPtr& signal) { packetReader.stopReadSignal(signal); } ); + streamingServer.onStartSignalsRead([this](const ListPtr& signals) { packetReader.startReadSignals(signals); } ); + streamingServer.onStopSignalsRead([this](const ListPtr& signals) { packetReader.stopReadSignals(signals); } ); streamingServer.start(streamingPort, controlPort); packetReader.setLoopFrequency(50); @@ -110,27 +110,12 @@ void WebsocketStreamingServer::coreEventCallback(ComponentPtr& sender, CoreEvent } } -void WebsocketStreamingServer::componentAdded(ComponentPtr& /*sender*/, CoreEventArgsPtr& eventArgs) +DictPtr WebsocketStreamingServer::getSignalsOfComponent(ComponentPtr& component) { - ComponentPtr addedComponent = eventArgs.getParameters().get("Component"); - - auto deviceGlobalId = device.getGlobalId().toStdString(); - auto addedComponentGlobalId = addedComponent.getGlobalId().toStdString(); - if (addedComponentGlobalId.find(deviceGlobalId) != 0) - return; - - LOG_I("Added Component: {};", addedComponentGlobalId); - addSignalsOfComponent(addedComponent); -} - -void WebsocketStreamingServer::addSignalsOfComponent(ComponentPtr& component) -{ - auto signalsToAdd = List(); - + auto signals = Dict(); if (component.supportsInterface()) { - LOG_I("Added Signal: {};", component.getGlobalId()); - signalsToAdd.pushBack(component.asPtr()); + signals.set(component.getGlobalId(), component.asPtr()); } else if (component.supportsInterface()) { @@ -138,14 +123,23 @@ void WebsocketStreamingServer::addSignalsOfComponent(ComponentPtr& component) for (const auto& nestedComponent : nestedComponents) { if (nestedComponent.supportsInterface()) - { - LOG_I("Added Signal: {};", nestedComponent.getGlobalId()); - signalsToAdd.pushBack(nestedComponent.asPtr()); - } + signals.set(nestedComponent.getGlobalId(), nestedComponent.asPtr()); } } + return signals; +} + +void WebsocketStreamingServer::componentAdded(ComponentPtr& /*sender*/, CoreEventArgsPtr& eventArgs) +{ + ComponentPtr addedComponent = eventArgs.getParameters().get("Component"); + + auto deviceGlobalId = device.getGlobalId().toStdString(); + auto addedComponentGlobalId = addedComponent.getGlobalId().toStdString(); + if (addedComponentGlobalId.find(deviceGlobalId) != 0) + return; - streamingServer.addSignals(signalsToAdd); + LOG_I("Added Component: {};", addedComponentGlobalId); + streamingServer.addSignals(getSignalsOfComponent(addedComponent).getValueList()); } void WebsocketStreamingServer::componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) @@ -171,11 +165,8 @@ void WebsocketStreamingServer::componentUpdated(ComponentPtr& updatedComponent) LOG_I("Component: {}; is updated", updatedComponentGlobalId); - // remove all registered signal of updated component since those might be modified or removed - streamingServer.removeComponentSignals(updatedComponentGlobalId); - - // add updated versions of signals - addSignalsOfComponent(updatedComponent); + // update list of known signals to include added and exclude removed signals + streamingServer.updateComponentSignals(getSignalsOfComponent(updatedComponent), updatedComponentGlobalId); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 9272fbedd07dc56771d71d07fb9cbdf090d2116a Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:50:32 +0200 Subject: [PATCH 089/127] Sync components Implementation (openDAQ/openDAQ#361) - Populating eval expression with %ChildProperty:PropertyNames to get the list of child properties names where the ChildProperty is an Object-type property - Each device now has a sync component, which is visible in default components as `Sync`. Meantime the sync component transfers over OPC UA by node id `Synchronization`. - Sync component is replacing dummy property object in the ref device. --- shared/libraries/websocket_streaming/tests/test_streaming.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 742c2b0..d3c9c2f 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -161,11 +161,11 @@ TEST_F(StreamingTest, Subscription) ASSERT_TRUE(client.isConnected()); client.subscribeSignal(testDoubleSignal.getGlobalId()); - ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + ASSERT_EQ(subscribeAckFuture.wait_for(std::chrono::milliseconds(1000)), std::future_status::ready); ASSERT_EQ(subscribeAckFuture.get(), testDoubleSignal.getGlobalId()); client.unsubscribeSignal(testDoubleSignal.getGlobalId()); - ASSERT_EQ(unsubscribeAckFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + ASSERT_EQ(unsubscribeAckFuture.wait_for(std::chrono::milliseconds(1000)), std::future_status::ready); ASSERT_EQ(unsubscribeAckFuture.get(), testDoubleSignal.getGlobalId()); } From 119ffad268ba7d8cbe5f4d04cddbd57deea8d712 Mon Sep 17 00:00:00 2001 From: Denis Erokhin Date: Tue, 20 Aug 2024 11:44:13 +0200 Subject: [PATCH 090/127] fix connection string regexp with % and path. fix sync component cmake --- .../src/websocket_streaming_client_module_impl.cpp | 7 +++---- .../libraries/websocket_streaming/src/streaming_client.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index bb0f01a..fa519ec 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -299,14 +299,13 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& std::string urlString = connectionString.toStdString(); - auto regexIpv6Hostname = std::regex(R"(^(.*://)(\[[a-fA-F0-9:]+\])(?::(\d+))?(/.*)?$)"); + auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%\d+)?\])(?::(\d+))?(/.*)?$)"); auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); std::smatch match; std::string host = ""; - std::string target = "/"; std::string prefix = ""; - std::string path = ""; + std::string path = "/"; bool parsed = false; parsed = std::regex_search(urlString, match, regexIpv6Hostname); @@ -323,7 +322,7 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& if (match[4].matched) path = match[4]; - return prefix + host + ":" + std::to_string(port) + "/" + path; + return prefix + host + ":" + std::to_string(port) + path; } return connectionString; diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 3f60451..8ab2b64 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -243,7 +243,7 @@ void StreamingClient::parseConnectionString(const std::string& url) std::smatch match; // parsing connection string to four groups: prefix, host, port, path - auto regexIpv6Hostname = std::regex(R"(^(.*://)?(?:\[([a-fA-F0-9:]+)\])(?::(\d+))?(/.*)?$)"); + auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%\d+)?\])(?::(\d+))?(/.*)?$)"); auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); bool parsed = false; From d6df5d16ac573a5e56a3f4494416a715bbeec2a9 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Tue, 20 Aug 2024 11:54:46 +0200 Subject: [PATCH 091/127] Add ipv6 link local interface to connection string when available --- .../src/websocket_streaming_client_module_impl.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index fa519ec..f5b1254 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -51,14 +51,14 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex if(!discoveredDevice.ipv6Address.empty()) { auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( - "[" + discoveredDevice.ipv6Address + "]", + discoveredDevice.ipv6Address, discoveredDevice.servicePort, discoveredDevice.getPropertyOrDefault("path", "/") ); cap.addConnectionString(connectionStringIpv6); - cap.addAddress("[" + discoveredDevice.ipv6Address + "]"); + cap.addAddress(discoveredDevice.ipv6Address); - const auto addressInfo = AddressInfoBuilder().setAddress("[" + discoveredDevice.ipv6Address + "]") + const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv6Address) .setReachabilityStatus(AddressReachabilityStatus::Unknown) .setType("IPv6") .setConnectionString(connectionStringIpv6) From fd0527ba07354ecd87f8a17985380f9b969ce5fe Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Tue, 20 Aug 2024 13:55:52 +0200 Subject: [PATCH 092/127] Fix regex --- .../src/websocket_streaming_client_module_impl.cpp | 2 +- shared/libraries/websocket_streaming/src/streaming_client.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index f5b1254..a51a725 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -299,7 +299,7 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& std::string urlString = connectionString.toStdString(); - auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%\d+)?\])(?::(\d+))?(/.*)?$)"); + auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9]+)?\])(?::(\d+))?(/.*)?$)"); auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); std::smatch match; diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 8ab2b64..4862d99 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -243,7 +243,7 @@ void StreamingClient::parseConnectionString(const std::string& url) std::smatch match; // parsing connection string to four groups: prefix, host, port, path - auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%\d+)?\])(?::(\d+))?(/.*)?$)"); + auto regexIpv6Hostname = std::regex(R"(^(.*://)?(?:\[([a-fA-F0-9:]+(?:\%[a-zA-Z0-9]+)?)\])(?::(\d+))?(/.*)?$)"); auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); bool parsed = false; From 0b343776c7df8df90510594ceec1feb3f91d61ee Mon Sep 17 00:00:00 2001 From: Jaka Mohorko Date: Fri, 23 Aug 2024 09:52:14 +0200 Subject: [PATCH 093/127] Increase version to 3.4 --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 3f71db8..fc769e8 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 3.3.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION 3.4.0 LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index b88e0f1..07981e6 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 3.3.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION 3.4.0 LANGUAGES CXX) add_subdirectory(src) From ad94cb617998360dde2ac5f6f58b8d4f006aa268 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:22:52 +0200 Subject: [PATCH 094/127] [TBBAS-1592] Instance save/load fixes (openDAQ/openDAQ#450) - make connecting signals while loading in correct order - printing connection circle dependency if they were detected --- .../src/websocket_streaming_server_impl.cpp | 2 +- .../src/signal_descriptor_converter.cpp | 8 ++++---- .../tests/test_websocket_client_device.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 6f86a06..0816559 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -10,7 +10,7 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE using namespace daq; WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context) - : Server("StreamingLtServer", config, rootDevice, context, nullptr) + : Server("OpenDAQLTStreaming", config, rootDevice, context) , websocketStreamingServer(rootDevice, context) { const uint16_t streamingPort = config.getPropertyValue("WebsocketStreamingPort"); diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 182510c..f959a8a 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -505,13 +505,13 @@ nlohmann::json SignalDescriptorConverter::DictToJson(const DictPtr().assigned()) + if (value.supportsInterface()) json[key.getCharPtr()] = value.asPtr().toVector(); - else if (value.asPtrOrNull().assigned()) + else if (value.supportsInterface()) json[key.getCharPtr()] = DictToJson(value); - else if (value.asPtrOrNull().assigned()) + else if (value.supportsInterface()) json[key.getCharPtr()] = (Float) value; - else if (value.asPtrOrNull().assigned()) + else if (value.supportsInterface()) json[key.getCharPtr()] = (Int) value; else json[key.getCharPtr()] = value; diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index cfabe22..d237d33 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -88,7 +88,7 @@ class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public test if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded) { ComponentPtr component = params.get("Component"); - if (component.asPtrOrNull().assigned()) + if (component.supportsInterface()) { addedSigCount++; if (addedSigCount == signals.getCount()) From 38dbc0f0490551b75055e3570934a156b48d11f0 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 26 Aug 2024 08:11:14 +0200 Subject: [PATCH 095/127] Enable openDAQ servers to be added to the component tree under the device: * IServer now inherits from IFolder * The base server implementation now inherits from SignalContainer * Servers are added under the root device rather than directly under the instance. * Store the root device pointer as a weak reference within IServer objects * Add the missing default configuration population mechanism for the OpcUa server Limitations: * Servers are not exposed in the component tree through the OpcUa protocol --- .../websocket_streaming_server_impl.h | 6 ++++-- .../websocket_streaming_server_module_impl.h | 2 +- .../src/websocket_streaming_server_impl.cpp | 19 ++++++++----------- ...websocket_streaming_server_module_impl.cpp | 6 +++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 8114500..2a60626 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -24,10 +24,12 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE -class WebsocketStreamingServerImpl : public daq::Server +class WebsocketStreamingServerImpl : public Server { public: - explicit WebsocketStreamingServerImpl(daq::DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context); + explicit WebsocketStreamingServerImpl(const DevicePtr& rootDevice, + const PropertyObjectPtr& config, + const ContextPtr& context); static PropertyObjectPtr createDefaultConfig(const ContextPtr& context); static ServerTypePtr createType(const ContextPtr& context); static PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& config, const ContextPtr& context); diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h index 9160589..c2d609f 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h @@ -26,7 +26,7 @@ class WebsocketStreamingServerModule final : public Module WebsocketStreamingServerModule(ContextPtr context); DictPtr onGetAvailableServerTypes() override; - ServerPtr onCreateServer(StringPtr serverType, PropertyObjectPtr serverConfig, DevicePtr rootDevice) override; + ServerPtr onCreateServer(const StringPtr& serverType, const PropertyObjectPtr& serverConfig, const DevicePtr& rootDevice) override; private: std::mutex sync; diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 0816559..371ce83 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -9,7 +9,9 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE using namespace daq; -WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(DevicePtr rootDevice, PropertyObjectPtr config, const ContextPtr& context) +WebsocketStreamingServerImpl::WebsocketStreamingServerImpl(const DevicePtr& rootDevice, + const PropertyObjectPtr& config, + const ContextPtr& context) : Server("OpenDAQLTStreaming", config, rootDevice, context) , websocketStreamingServer(rootDevice, context) { @@ -98,15 +100,10 @@ PropertyObjectPtr WebsocketStreamingServerImpl::populateDefaultConfig(const Prop } OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( - INTERNAL_FACTORY, - WebsocketStreamingServer, - daq::IServer, - daq::DevicePtr, - rootDevice, - PropertyObjectPtr, - config, - const ContextPtr&, - context - ) + INTERNAL_FACTORY, WebsocketStreamingServer, daq::IServer, + daq::DevicePtr, rootDevice, + PropertyObjectPtr, config, + const ContextPtr&, context +) END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_SERVER_MODULE diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index b75ff3d..80a54d6 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -25,9 +25,9 @@ DictPtr WebsocketStreamingServerModule::onGetAvailableServ return result; } -ServerPtr WebsocketStreamingServerModule::onCreateServer(StringPtr serverType, - PropertyObjectPtr serverConfig, - DevicePtr rootDevice) +ServerPtr WebsocketStreamingServerModule::onCreateServer(const StringPtr& serverType, + const PropertyObjectPtr& serverConfig, + const DevicePtr& rootDevice) { if (!context.assigned()) throw InvalidParameterException{"Context parameter cannot be null."}; From 59511c034ea7121fed7f507fb9ba0ba60f8d87ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Fran=C4=8Di=C4=8D?= Date: Fri, 18 Oct 2024 12:49:25 +0200 Subject: [PATCH 096/127] Inject openDAQ version from a single file (openDAQ/openDAQ#550) * Use `OPENDAQ_PACKAGE_VERSION` instead of hard-coded version throughout `CMakeLists.txt` files * Delete whitespace in `opendaq_verison` * Change `antora.yml` to contain a placeholder * Add Python `openda_version` injector * Add version injection to `build_antora_docs.yml` * Add version injection to `deploy.yml` * Inject version into `quick_start_setting_up_python.adoc` and `quick_start_setting_up_cpp.adoc` * `HEAD_DAQ_VERSION` and `HEAD_DOC_VERSION` variables appear to be unused, so this code is obsolete and we can safely delete the whole CI job * Minor fixes --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index fc769e8..6103361 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingClientModule VERSION 3.4.0 LANGUAGES C CXX) +project(WebsocketStreamingClientModule VERSION ${OPENDAQ_PACKAGE_VERSION} LANGUAGES C CXX) add_subdirectory(src) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index 07981e6..89d28f2 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(WebsocketStreamingServerModule VERSION 3.4.0 LANGUAGES CXX) +project(WebsocketStreamingServerModule VERSION ${OPENDAQ_PACKAGE_VERSION} LANGUAGES CXX) add_subdirectory(src) From 8aa9bd3271cd3160cad30579299499b27bbb0e85 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 8 Oct 2024 16:24:49 +0200 Subject: [PATCH 097/127] Complete the missing changes for event packet triggering on setting a nullptr descriptor: * LT-streaming: - Fix event packet generation on the client side when signal descriptors are unassigned - Add "subscribed" attribute to input signals to track explicit subscriptions - Introduce an input placeholder signal for uninitialized and incomplete signals - Manage signal descriptor transitions between unsupported and supported types * Disallow explicit setting of Null sample type descruptor for signal * Add tests for setting nullptr descriptor within core signal implementation * Fix handling of event packets with null-descriptors in native transport and packet streaming * Correct the creation of initial event packets in reader implementations when retrieving descriptors from input ports * Check for Null sample type in OPC UA converters * Rework streaming integration tests to cover setting nullptr descriptors across different streaming and configuration protocol combinations --- .../websocket_streaming/input_signal.h | 37 +- .../websocket_streaming/output_signal.h | 19 +- .../websocket_streaming/streaming_client.h | 2 +- .../websocket_streaming/streaming_server.h | 16 +- .../websocket_streaming/src/input_signal.cpp | 57 ++- .../websocket_streaming/src/output_signal.cpp | 81 ++-- .../src/signal_descriptor_converter.cpp | 6 +- .../src/streaming_client.cpp | 54 ++- .../src/streaming_server.cpp | 191 ++++++--- .../tests/test_websocket_client_device.cpp | 364 ++++++++++++++++++ 10 files changed, 712 insertions(+), 115 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index f977532..fec922c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -50,10 +50,10 @@ class InputSignalBase virtual bool isDomainSignal() const = 0; virtual bool isCountable() const = 0; - EventPacketPtr createDecriptorChangedPacket(bool valueChanged = true, bool domainChanged = true) const; + virtual EventPacketPtr createDecriptorChangedPacket(bool valueChanged = true, bool domainChanged = true) const; void setDataDescriptor(const DataDescriptorPtr& dataDescriptor); - bool hasDescriptors() const; + virtual bool hasDescriptors() const; DataDescriptorPtr getSignalDescriptor() const; std::string getTableId() const; @@ -61,6 +61,9 @@ class InputSignalBase InputSignalBasePtr getInputDomainSignal() const; + void setSubscribed(bool subscribed); + bool getSubscribed(); + protected: const std::string signalId; const std::string tableId; @@ -72,6 +75,25 @@ class InputSignalBase daq::streaming_protocol::LogCallback logCallback; mutable std::mutex descriptorsSync; + bool subscribed; +}; + +/// Used as a placeholder for uninitialized or incomplete signals which aren't supported by LT-streaming +class InputNullSignal : public InputSignalBase +{ +public: + InputNullSignal(const std::string& signalId, + streaming_protocol::LogCallback logCb); + + EventPacketPtr createDecriptorChangedPacket(bool valueChanged = true, bool domainChanged = true) const override; + bool hasDescriptors() const override; + + DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, + const uint8_t* data, + size_t sampleCount, + const DataPacketPtr& domainPacket) override; + bool isDomainSignal() const override; + bool isCountable() const override; }; class InputDomainSignal : public InputSignalBase @@ -176,4 +198,15 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, } } +inline InputSignalBasePtr InputPlaceHolderSignal(const std::string& signalId, + streaming_protocol::LogCallback logCb) +{ + return std::make_shared(signalId, logCb); +} + +inline bool isPlaceHolderSignal(const InputSignalBasePtr& inputSignal) +{ + return (std::dynamic_pointer_cast(inputSignal)) ? true : false; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index aad6ffe..dc8c768 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -41,7 +41,9 @@ class OutputSignalBase daq::streaming_protocol::LogCallback logCb); virtual ~OutputSignalBase(); - virtual void writeDaqPacket(const PacketPtr& packet) = 0; + virtual void writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) = 0; + virtual void writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) = 0; + virtual void writeDaqDataPacket(const DataPacketPtr& packet) = 0; virtual void setSubscribed(bool subscribed) = 0; virtual bool isDataSignal() = 0; @@ -84,7 +86,9 @@ class OutputNullSignal : public OutputSignalBase public: OutputNullSignal(const SignalPtr& signal, daq::streaming_protocol::LogCallback logCb); - void writeDaqPacket(const PacketPtr& packet) override; + void writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) override; + void writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) override; + void writeDaqDataPacket(const DataPacketPtr& packet) override; void setSubscribed(bool subscribed) override; bool isDataSignal() override; @@ -100,7 +104,9 @@ class OutputValueSignalBase : public OutputSignalBase OutputDomainSignalBasePtr outputDomainSignal, daq::streaming_protocol::LogCallback logCb); - void writeDaqPacket(const PacketPtr& packet) override; + void writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) override; + void writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) override; + void writeDaqDataPacket(const DataPacketPtr& packet) override; virtual void setSubscribed(bool subscribed) override; bool isDataSignal() override; @@ -111,9 +117,6 @@ class OutputValueSignalBase : public OutputSignalBase OutputDomainSignalBasePtr outputDomainSignal; private: - void writeEventPacket(const EventPacketPtr& packet); - void writeDescriptorChangedPacket(const EventPacketPtr& packet); - daq::streaming_protocol::BaseValueSignalPtr valueStream; }; @@ -127,7 +130,9 @@ class OutputDomainSignalBase : public OutputSignalBase const SignalPtr& signal, daq::streaming_protocol::LogCallback logCb); - void writeDaqPacket(const PacketPtr& packet) override; + void writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) override; + void writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) override; + void writeDaqDataPacket(const DataPacketPtr& packet) override; void setSubscribed(bool subscribed) override; bool isDataSignal() override; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index c40d20f..95008a2 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -143,7 +143,7 @@ class StreamingClient // is published only for subscribed signals. // as workaround we temporarily subscribe all signals to receive signal meta-info // at initialization stage. - // To manage this the 'availableSigInitStatus' is used, it is map of 4-element tuples, where: + // To manage this the 'availableSigInitStatus' is used, it is map of 3-element tuples, where: // 1-st is std::promise // 2-nd is std::future // 3-rd: boolean flag indicating that initial unsubscription completion ack is filtered-out diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 937ce7a..bcb52b9 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -76,6 +76,10 @@ class StreamingServer void addToOutputSignals(const SignalPtr& signal, SignalMap& outputSignals, const streaming_protocol::StreamWriterPtr& writer); + OutputDomainSignalBasePtr addUpdateOrFindDomainSignal(const SignalPtr& domainSignal, + SignalMap& outputSignals, + const streaming_protocol::StreamWriterPtr& writer); + void publishSignalsToClient(const streaming_protocol::StreamWriterPtr& writer, const ListPtr& signals, SignalMap& outputSignals); @@ -87,9 +91,15 @@ class StreamingServer const OutputDomainSignalBasePtr& outputDomainSignal, const std::string& tableId, const streaming_protocol::StreamWriterPtr& writer); - void updateOutputValueSignal(OutputSignalBasePtr& outputSignal, - SignalMap& outputSignals, - const streaming_protocol::StreamWriterPtr& writer); + void handleDataDescriptorChanges(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const streaming_protocol::StreamWriterPtr& writer, + const EventPacketPtr& packet); + + void updateOutputPlaceholderSignal(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const streaming_protocol::StreamWriterPtr& writer, + bool subscribed); void writeProtocolInfo(const daq::streaming_protocol::StreamWriterPtr& writer); void writeSignalsAvailable(const daq::streaming_protocol::StreamWriterPtr& writer, diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 3fa3fa7..d14729d 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -22,6 +22,7 @@ InputSignalBase::InputSignalBase(const std::string& signalId, , name(signalInfo.signalProps.name.value_or(signalInfo.signalName)) , description(signalInfo.signalProps.description.value_or("")) , logCallback(logCb) + , subscribed(false) { } @@ -35,14 +36,16 @@ EventPacketPtr InputSignalBase::createDecriptorChangedPacket(bool valueChanged, if (isDomainSignal()) { - const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; - return DataDescriptorChangedEventPacket(valueDesc, nullptr); + const auto valueDescParam = currentDataDescriptor.assigned() ? currentDataDescriptor : NullDataDescriptor(); + return DataDescriptorChangedEventPacket(valueChanged ? valueDescParam : nullptr, nullptr); } else { - const auto valueDesc = valueChanged ? currentDataDescriptor : nullptr; - const auto domainDesc = domainChanged ? inputDomainSignal->getSignalDescriptor() : nullptr; - return DataDescriptorChangedEventPacket(valueDesc, domainDesc); + const auto valueDescParam = currentDataDescriptor.assigned() ? currentDataDescriptor : NullDataDescriptor(); + const auto domainDesc = inputDomainSignal->getSignalDescriptor(); + const auto domainDescParam = domainDesc.assigned() ? domainDesc : NullDataDescriptor(); + return DataDescriptorChangedEventPacket(valueChanged ? valueDescParam : nullptr, + domainChanged ? domainDescParam : nullptr); } } @@ -85,6 +88,16 @@ InputSignalBasePtr InputSignalBase::getInputDomainSignal() const return inputDomainSignal; } +void InputSignalBase::setSubscribed(bool subscribed) +{ + this->subscribed = subscribed; +} + +bool InputSignalBase::getSubscribed() +{ + return subscribed; +} + InputDomainSignal::InputDomainSignal(const std::string& signalId, const std::string& tabledId, const SubscribedSignalInfo& signalInfo, @@ -361,4 +374,38 @@ DataPacketPtr InputConstantDataSignal::createTypedConstantPacket( return ConstantDataPacketWithDomain(domainPacket, dataDescriptor, sampleCount, startValueTyped, otherValuesTyped); } +InputNullSignal::InputNullSignal(const std::string& signalId, streaming_protocol::LogCallback logCb) + : InputSignalBase(signalId, std::string(), SubscribedSignalInfo(), nullptr, logCb) +{ +} + +EventPacketPtr InputNullSignal::createDecriptorChangedPacket(bool valueChanged, bool domainChanged) const +{ + return DataDescriptorChangedEventPacket(valueChanged ? NullDataDescriptor() : nullptr, + domainChanged ? NullDataDescriptor() : nullptr); +} + +bool InputNullSignal::hasDescriptors() const +{ + return true; +} + +DataPacketPtr InputNullSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, + const uint8_t* /*data*/, + size_t /*sampleCount*/, + const DataPacketPtr& /*domainPacket*/) +{ + return nullptr; +} + +bool InputNullSignal::isDomainSignal() const +{ + return false; +} + +bool InputNullSignal::isCountable() const +{ + return false; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index dbad7e5..d31eac3 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -147,30 +147,40 @@ OutputValueSignalBase::OutputValueSignalBase(daq::streaming_protocol::BaseValueS { } -void OutputValueSignalBase::writeEventPacket(const EventPacketPtr& packet) +void OutputValueSignalBase::writeDaqDataPacket(const DataPacketPtr& packet) { - const auto eventId = packet.getEventId(); + std::scoped_lock lock(subscribedSync); + + if (!this->subscribed) + return; - if (eventId == event_packet_id::DATA_DESCRIPTOR_CHANGED) + writeDataPacket(packet); +} + +void OutputValueSignalBase::writeValueDescriptorChanges(const DataDescriptorPtr& valueDescriptor) +{ + std::scoped_lock lock(subscribedSync); + + if (!this->subscribed) + return; + + if (valueDescriptor.assigned()) { - writeDescriptorChangedPacket(packet); + this->writeDescriptorChangedEvent(valueDescriptor); } else { - STREAMING_PROTOCOL_LOG_E("Event type {} is not supported by streaming.", eventId); + throw ConversionFailedException("Unassigned value descriptor"); } } -void OutputValueSignalBase::writeDescriptorChangedPacket(const EventPacketPtr& packet) +void OutputValueSignalBase::writeDomainDescriptorChanges(const DataDescriptorPtr& domainDescriptor) { - const auto params = packet.getParameters(); - const DataDescriptorPtr valueDescriptor = params.get(event_packet_param::DATA_DESCRIPTOR); - const DataDescriptorPtr domainDescriptor = params.get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); + std::scoped_lock lock(subscribedSync); + + if (!this->subscribed) + return; - if (valueDescriptor.assigned()) - { - this->writeDescriptorChangedEvent(valueDescriptor); - } if (domainDescriptor.assigned()) { if (outputDomainSignal->isTimeConfigChanged(domainDescriptor)) @@ -184,28 +194,9 @@ void OutputValueSignalBase::writeDescriptorChangedPacket(const EventPacketPtr& p } outputDomainSignal->writeDescriptorChangedEvent(domainDescriptor); } -} - -void OutputValueSignalBase::writeDaqPacket(const PacketPtr& packet) -{ - std::scoped_lock lock(subscribedSync); - - if (!this->subscribed) - return; - - const auto type = packet.getType(); - - switch (type) + else { - case PacketType::Data: - writeDataPacket(packet); - break; - case PacketType::Event: - writeEventPacket(packet); - break; - default: - STREAMING_PROTOCOL_LOG_E("Failed to write a packet of unsupported type."); - break; + throw ConversionFailedException("Unassigned domain descriptor"); } } @@ -254,7 +245,17 @@ OutputDomainSignalBase::OutputDomainSignalBase(daq::streaming_protocol::BaseDoma { } -void OutputDomainSignalBase::writeDaqPacket(const PacketPtr& packet) +void OutputDomainSignalBase::writeDaqDataPacket(const DataPacketPtr& packet) +{ + throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); +} + +void OutputDomainSignalBase::writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) +{ + throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); +} + +void OutputDomainSignalBase::writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) { throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); } @@ -732,7 +733,15 @@ OutputNullSignal::OutputNullSignal(const SignalPtr& signal, daq::streaming_proto { } -void OutputNullSignal::writeDaqPacket(const PacketPtr& packet) +void OutputNullSignal::writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) +{ +} + +void OutputNullSignal::writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) +{ +} + +void OutputNullSignal::writeDaqDataPacket(const DataPacketPtr& packet) { } diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index f959a8a..bedc9d5 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -128,7 +128,7 @@ void SignalDescriptorConverter::ToStreamedValueSignal(const daq::SignalPtr& valu daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); if (requestedSampleType != valueStream->getSampleType()) - throw ConversionFailedException(); + throw ConversionFailedException("Sample type has been changed"); UnitPtr unit = dataDescriptor.getUnit(); if (unit.assigned()) @@ -181,7 +181,7 @@ void SignalDescriptorConverter::ToStreamedLinearSignal(const daq::SignalPtr& dom daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); if (requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_S64 && requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_U64) - throw ConversionFailedException(); + throw ConversionFailedException("Non-64bit domain sample types are not supported"); DataRulePtr rule = domainDescriptor.getRule(); SetLinearTimeRule(rule, linearStream); @@ -242,7 +242,7 @@ void SignalDescriptorConverter::SetLinearTimeRule(const daq::DataRulePtr& rule, { if (!rule.assigned() || rule.getType() != DataRuleType::Linear) { - throw ConversionFailedException(); + throw ConversionFailedException("Time rule is not supported"); } uint64_t delta = rule.getParameters().get("delta"); linearStream->setOutputRate(delta); diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 4862d99..dfbf6a8 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -274,8 +274,10 @@ void StreamingClient::onSignalMeta(const SubscribedSignal& subscribedSignal, con if (auto it = availableSignals.find(signalId); it != availableSignals.end()) { auto inputSignal = it->second; - // skips the first subscribe ACK from server as inputSignal is not initialized at the moment - if (inputSignal) + // forwards ACK to upper level only if signal is explicitly subscribed + // and doesn't if just temporary subscribed for descriptors initialization + // i.e. skips the first subscribe ACK from server if signal is not explicitly subscribed by streaming + if (inputSignal && inputSignal->getSubscribed()) { // ignores ACKs for domain signals if (!inputSignal->isDomainSignal()) @@ -334,7 +336,7 @@ void StreamingClient::availableSignalsHandler(const nlohmann::json::const_iterat if (auto signalIt = availableSignals.find(signalId); signalIt == availableSignals.end()) { - availableSignals.insert({signalId, nullptr}); + availableSignals.insert({signalId, InputPlaceHolderSignal(signalId, logCallback)}); } else { @@ -413,13 +415,21 @@ void StreamingClient::unavailableSignalsHandler(const nlohmann::json::const_iter void StreamingClient::subscribeSignal(const std::string& signalId) { if (auto it = availableSignals.find(signalId); it != availableSignals.end()) + { + if (auto inputSignal = it->second) + inputSignal->setSubscribed(true); protocolHandler->subscribe({signalId}); + } } void StreamingClient::unsubscribeSignal(const std::string& signalId) { if (auto it = availableSignals.find(signalId); it != availableSignals.end()) + { + if (auto inputSignal = it->second) + inputSignal->setSubscribed(false); protocolHandler->unsubscribe({signalId}); + } } void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, @@ -438,6 +448,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& inputSignal = hiddenSigIt->second; if (inputSignal && + !isPlaceHolderSignal(inputSignal) && inputSignal->hasDescriptors() && inputSignal->getSignalDescriptor().getSampleType() != daq::SampleType::Struct) { @@ -513,9 +524,11 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); publishSignalChanges(inputSignal, true, true); } - else if (available && !inputSignal) + else if (available && isPlaceHolderSignal(inputSignal)) { + bool subscribed = inputSignal->getSubscribed(); inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback); + inputSignal->setSubscribed(subscribed); availableSignals[signalId] = inputSignal; onAvailableSignalInitCb(signalId, sInfo); onDomainSignalInitCallback(signalId, domainInputSignal->getSignalId()); @@ -560,9 +573,11 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig onHiddenStreamingSignalCb(timeSignalId, sInfo); onHiddenDeviceSignalInitCb(timeSignalId, sInfo); } - else if (available && !inputSignal) + else if (available && isPlaceHolderSignal(inputSignal)) { + bool subscribed = inputSignal->getSubscribed(); inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback); + inputSignal->setSubscribed(subscribed); availableSignals[timeSignalId] = inputSignal; // the time signal is published as available by server, // so do the initialization of its mirrored copy @@ -603,14 +618,14 @@ std::vector StreamingClient::findDataSignalsByTableId(const std::vector result; for (const auto& [_, inputSignal] : availableSignals) { - if (inputSignal && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) + if (inputSignal && !isPlaceHolderSignal(inputSignal) && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) { result.push_back(inputSignal); } } for (const auto& [_, inputSignal] : hiddenSignals) { - if (inputSignal && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) + if (inputSignal && !isPlaceHolderSignal(inputSignal) && tableId == inputSignal->getTableId() && !inputSignal->isDomainSignal()) { result.push_back(inputSignal); } @@ -705,12 +720,35 @@ void StreamingClient::checkTmpSubscribedSignalsInit() if (status != std::future_status::ready) { LOG_W("signal {} has incomplete descriptors", id); + + // publish signal descriptor changes as event packet if signal is subscribed by streaming + if (auto availableSigIt = availableSignals.find(id); availableSigIt != availableSignals.end()) + { + auto inputSignal = availableSigIt->second; + publishSignalChanges(inputSignal, true, true); + } } } } // unsubscribe previously subscribed signals - protocolHandler->unsubscribe(std::vector(tmpSubscribedSignalIds.begin(), tmpSubscribedSignalIds.end())); + std::vector signalIdsToUnsubscribe; + signalIdsToUnsubscribe.reserve(tmpSubscribedSignalIds.size()); + std::copy_if( + tmpSubscribedSignalIds.begin(), + tmpSubscribedSignalIds.end(), + std::back_inserter(signalIdsToUnsubscribe), + [this](std::string signalId) + { + if (auto availableSigIt = availableSignals.find(signalId); availableSigIt != availableSignals.end()) + { + if (auto inputSignal = availableSigIt->second; inputSignal && inputSignal->getSubscribed()) + return false; + } + return true; + } + ); + protocolHandler->unsubscribe(signalIdsToUnsubscribe); tmpSubscribedSignalIds.clear(); onSignalsInitDone(); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 5246c29..4a076ba 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -10,6 +10,8 @@ #include #include +#include + BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING using namespace daq::streaming_protocol; @@ -130,13 +132,22 @@ void StreamingServer::broadcastPacket(const std::string& signalId, const PacketP if (auto signalIter = outputSignals.find(signalId); signalIter != outputSignals.end()) { auto outputSignal = signalIter->second; - auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned() && eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) + if (auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) { - updateOutputValueSignal(outputSignal, outputSignals, writer); + if (eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) + { + handleDataDescriptorChanges(outputSignal, outputSignals, writer, eventPacket); + } + else + { + STREAMING_PROTOCOL_LOG_W("Event type {} is not supported by streaming.", eventPacket.getEventId()); + } + } + else if (auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + outputSignal->writeDaqDataPacket(dataPacket); } - outputSignal->writeDaqPacket(packet); } } } @@ -151,40 +162,53 @@ DataRuleType StreamingServer::getSignalRuleType(const SignalPtr& signal) return descriptor.getRule().getType(); } -void StreamingServer::addToOutputSignals(const SignalPtr& signal, - SignalMap& outputSignals, - const StreamWriterPtr& writer) +OutputDomainSignalBasePtr StreamingServer::addUpdateOrFindDomainSignal(const SignalPtr& domainSignal, + SignalMap& outputSignals, + const StreamWriterPtr& writer) { - auto domainSignal = signal.getDomainSignal(); - if (domainSignal.assigned()) + auto domainSignalId = domainSignal.getGlobalId(); + + OutputDomainSignalBasePtr outputDomainSignal; + if (const auto& outputSignalIt = outputSignals.find(domainSignalId); outputSignalIt != outputSignals.end()) { - auto domainSignalId = domainSignal.getGlobalId(); + auto outputSignal = outputSignalIt->second; - OutputDomainSignalBasePtr outputDomainSignal; - if (const auto& outputSignalIt = outputSignals.find(domainSignalId); outputSignalIt != outputSignals.end()) + if (std::dynamic_pointer_cast(outputSignal)) { - auto outputSignal = outputSignalIt->second; - - if (std::dynamic_pointer_cast(outputSignal)) - { - outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); - outputSignals[domainSignalId] = outputDomainSignal; - } - else - { - outputDomainSignal = std::dynamic_pointer_cast(outputSignal); - if (!outputDomainSignal) - throw NoInterfaceException("Previously registered domain output signal {} is not of domain type", domainSignalId); - } + // replace previously added incomplete placeholder signal + outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); + outputSignals[domainSignalId] = outputDomainSignal; } else { - outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); - outputSignals.insert({domainSignalId, outputDomainSignal}); + // find previously added complete output domain signal + outputDomainSignal = std::dynamic_pointer_cast(outputSignal); + if (!outputDomainSignal) + throw NoInterfaceException("Previously registered domain output signal {} is not of domain type", domainSignalId); } + } + else + { + // signal wasn't added before so add it now + outputDomainSignal = createOutputDomainSignal(domainSignal, domainSignal.getGlobalId(), writer); + outputSignals.insert({domainSignalId, outputDomainSignal}); + } - auto tableId = domainSignalId.toStdString(); + return outputDomainSignal; +} + +void StreamingServer::addToOutputSignals(const SignalPtr& signal, + SignalMap& outputSignals, + const StreamWriterPtr& writer) +{ + auto domainSignal = signal.getDomainSignal(); + if (domainSignal.assigned()) + { + // if domain is assigned then signal is considered as value signal + OutputDomainSignalBasePtr outputDomainSignal = addUpdateOrFindDomainSignal(domainSignal, outputSignals, writer); + + auto tableId = domainSignal.getGlobalId().toStdString(); const auto domainSignalRuleType = getSignalRuleType(domainSignal); if (domainSignalRuleType == DataRuleType::Linear) { @@ -198,11 +222,8 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, } else { - if (const auto& outputSignalIt = outputSignals.find(signal.getGlobalId()); outputSignalIt == outputSignals.end()) - { - auto outputDomainSignal = createOutputDomainSignal(signal, signal.getGlobalId(), writer); - outputSignals.insert({signal.getGlobalId(), outputDomainSignal}); - } + // if domain is not assigned then signal is considered as domain signal itself + addUpdateOrFindDomainSignal(signal, outputSignals, writer); } } @@ -540,33 +561,103 @@ void StreamingServer::updateComponentSignals(const DictPtr& si stopReadSignals(signalsToStopRead); } -void StreamingServer::updateOutputValueSignal(OutputSignalBasePtr& outputSignal, - SignalMap& outputSignals, - const StreamWriterPtr& writer) +/// Due to the type-specific "hardcoded" implementations of output signals, +/// when a signal descriptor change is incompatible with the existing output signal (e.g. sample type or rule changed), +/// the signal is replaced with a newly created one. To handle this correctly on both the server and client sides, +/// the old signal is marked as unavailable by server, and the new one is made available under the same signal ID. +/// This ensures any cached signal details across server and client implementations are cleared. +/// As a result, the corresponding signal in the LT pseudo device is also removed and the new one appeared. +void StreamingServer::handleDataDescriptorChanges(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const StreamWriterPtr& writer, + const EventPacketPtr& packet) { - auto placeholderSignal = std::dynamic_pointer_cast(outputSignal); - - if (placeholderSignal) + const auto params = packet.getParameters(); + DataDescriptorPtr valueDescriptorParam = params[event_packet_param::DATA_DESCRIPTOR]; + DataDescriptorPtr domainDescriptorParam = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; + const bool valueDescriptorChanged = valueDescriptorParam.assigned(); + const bool domainDescriptorChanged = domainDescriptorParam.assigned(); + const DataDescriptorPtr newValueDescriptor = valueDescriptorParam != NullDataDescriptor() ? valueDescriptorParam : nullptr; + const DataDescriptorPtr newDomainDescriptor = domainDescriptorParam != NullDataDescriptor() ? domainDescriptorParam : nullptr; + bool subscribed = outputSignal->isSubscribed(); + + if (auto placeholderValueSignal = std::dynamic_pointer_cast(outputSignal)) { - auto daqSignal = outputSignal->getDaqSignal(); - auto signalId = daqSignal.getGlobalId().toStdString(); + if (valueDescriptorChanged && newValueDescriptor.assigned() || + domainDescriptorChanged && newDomainDescriptor.assigned()) + updateOutputPlaceholderSignal(outputSignal, outputSignals, writer, subscribed); + } + else + { + const auto daqValueSignal = outputSignal->getDaqSignal(); + const auto valueSignalId = daqValueSignal.getGlobalId(); - LOG_I("Parameters of unsupported signal {} has been changed, check if it is supported now ...", daqSignal.getGlobalId()); - try + if (valueDescriptorChanged) { - addToOutputSignals(daqSignal, outputSignals, writer); - outputSignal = outputSignals.at(signalId); - - if (placeholderSignal->isSubscribed()) - outputSignal->setSubscribed(true); + try + { + outputSignal->writeValueDescriptorChanges(newValueDescriptor); + } + catch (const DaqException& e) + { + writeSignalsUnavailable(writer, {valueSignalId}); + LOG_W("Failed to change value descriptor for signal {}, reason: {}", valueSignalId, e.what()); + outputSignals.insert_or_assign(valueSignalId, std::make_shared(daqValueSignal, logCallback)); + outputSignal = outputSignals.at(valueSignalId); + if (newValueDescriptor.assigned()) + updateOutputPlaceholderSignal(outputSignal, outputSignals, writer, false); + writeSignalsAvailable(writer, {valueSignalId}); + outputSignal->setSubscribed(subscribed); + } } - catch (const DaqException& e) + + if (domainDescriptorChanged) { - LOG_W("Failed to re-create an output LT streaming signal for {}, reason: {}", daqSignal.getGlobalId(), e.what()); + if (const auto daqDomainSignal = daqValueSignal.getDomainSignal(); daqDomainSignal.assigned()) + { + try + { + outputSignal->writeDomainDescriptorChanges(newDomainDescriptor); + } + catch (const DaqException& e) + { + LOG_W("Failed to change domain descriptor for signal {}, reason: {}", valueSignalId, e.what()); + const auto domainSignalId = daqDomainSignal.getGlobalId(); + writeSignalsUnavailable(writer, {domainSignalId, valueSignalId}); + outputSignals.insert_or_assign(valueSignalId, std::make_shared(daqValueSignal, logCallback)); + outputSignals.insert_or_assign(domainSignalId, std::make_shared(daqDomainSignal, logCallback)); + outputSignal = outputSignals.at(valueSignalId); + if (newDomainDescriptor.assigned()) + updateOutputPlaceholderSignal(outputSignal, outputSignals, writer, false); + writeSignalsAvailable(writer, {valueSignalId, domainSignalId}); + outputSignal->setSubscribed(subscribed); + } + } } } } +void StreamingServer::updateOutputPlaceholderSignal(OutputSignalBasePtr& outputSignal, + SignalMap& outputSignals, + const StreamWriterPtr& writer, + bool subscribed) +{ + auto daqSignal = outputSignal->getDaqSignal(); + auto signalId = daqSignal.getGlobalId().toStdString(); + + LOG_I("Parameters of unsupported signal {} has been changed, check if it is supported now ...", daqSignal.getGlobalId()); + try + { + addToOutputSignals(daqSignal, outputSignals, writer); + outputSignal = outputSignals.at(signalId); + outputSignal->setSubscribed(subscribed); + } + catch (const DaqException& e) + { + LOG_W("Failed to re-create an output LT streaming signal for {}, reason: {}", daqSignal.getGlobalId(), e.what()); + } +} + void StreamingServer::publishSignalsToClient(const StreamWriterPtr& writer, const ListPtr& signals, SignalMap& outputSignals) diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index d237d33..840efcd 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -363,6 +363,370 @@ TEST_P(WebsocketClientDeviceTestP, SignalsWithSharedDomain) INSTANTIATE_TEST_SUITE_P(SignalsAddedAfterConnect, WebsocketClientDeviceTestP, testing::Values(true, false)); +TEST_F(WebsocketClientDeviceTest, ChangeValueDescriptorToSupported) +{ + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); + + // Setup and start server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { return signals; }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + auto valueSignal = clientDevice.getSignals()[0].asPtr(); + + // subscribe value signal + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + valueSignal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + try + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + }; + auto reader = PacketReader(valueSignal); + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + SizeT addedSigCount = 0; + std::promise addSigPromise; + std::future addSigFuture = addSigPromise.get_future(); + auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + { + auto params = args.getParameters(); + if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded && + params.get("Component").supportsInterface() && + ++addedSigCount == 1) + { + addSigPromise.set_value(); + } + }; + clientDevice.getContext().getOnCoreEvent() += eventHandler; + + // remove post scaling to change output sampletype + const auto supportedValueDescriptor = + DataDescriptorBuilderCopy(testValueSignal.getDescriptor()).setPostScaling(nullptr).build(); + testValueSignal.asPtr().setDescriptor(supportedValueDescriptor); + server->broadcastPacket( + testValueSignal.getGlobalId(), + DataDescriptorChangedEventPacket(supportedValueDescriptor, nullptr) + ); + + // wait for 1 new signal added + ASSERT_TRUE(addSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); + clientDevice.getContext().getOnCoreEvent() -= eventHandler; + + // Check if old value signal removed + ASSERT_TRUE(valueSignal.isRemoved()); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + valueSignal = clientDevice.getSignals()[1].asPtr(); + + // wait for subscribe ack + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + // wait for new descriptors assigned + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // Check the updated mirrored signal + ASSERT_TRUE(valueSignal.getDescriptor().assigned()); + ASSERT_EQ(valueSignal.getDomainSignal(), clientDevice.getSignals()[0]); + ASSERT_TRUE(BaseObjectPtr::Equals(valueSignal.getDescriptor(), supportedValueDescriptor)); +} + +class UnsupportedSignalsTestP : public WebsocketClientDeviceTest, public testing::WithParamInterface +{ +public: + void SetUp() override + { + unsupportedDescriptor = GetParam(); + context = NullContext(); + } + + DataDescriptorPtr unsupportedDescriptor; +}; + +TEST_P(UnsupportedSignalsTestP, MakeValueSignalUnsupported) +{ + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); + + // Setup and start server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { return signals; }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + auto valueSignal = clientDevice.getSignals()[0].asPtr(); + + // subscribe value signal + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + valueSignal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + }; + auto reader = PacketReader(valueSignal); + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + SizeT addedSigCount = 0; + std::promise addSigPromise; + std::future addSigFuture = addSigPromise.get_future(); + + auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + { + auto params = args.getParameters(); + if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded && + params.get("Component").supportsInterface() && + ++addedSigCount == 1) + { + addSigPromise.set_value(); + } + }; + clientDevice.getContext().getOnCoreEvent() += eventHandler; + + // make value signal unsupported + testValueSignal.asPtr().setDescriptor(unsupportedDescriptor); + server->broadcastPacket( + testValueSignal.getGlobalId(), + DataDescriptorChangedEventPacket(unsupportedDescriptor.assigned() ? unsupportedDescriptor : NullDataDescriptor(), nullptr) + ); + + // wait for 1 new incomplete signal added + ASSERT_TRUE(addSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); + clientDevice.getContext().getOnCoreEvent() -= eventHandler; + + // Check if old value signal removed + ASSERT_TRUE(valueSignal.isRemoved()); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + valueSignal = clientDevice.getSignals()[1].asPtr(); + auto domainSignal = clientDevice.getSignals()[0].asPtr(); + + ASSERT_TRUE(domainSignal.getDescriptor().assigned()); + // Check if new value signal has nullptr descriptors + ASSERT_FALSE(valueSignal.getDescriptor().assigned()); +} + +TEST_P(UnsupportedSignalsTestP, MakeDomainSignalUnsupported) +{ + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); + + // Setup and start server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { return signals; }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + auto valueSignal = clientDevice.getSignals()[0].asPtr(); + auto domainSignal = clientDevice.getSignals()[1].asPtr(); + + // subscribe value signal + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + valueSignal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + }; + auto reader = PacketReader(valueSignal); + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + SizeT addedSigCount = 0; + std::promise addSigPromise; + std::future addSigFuture = addSigPromise.get_future(); + auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + { + auto params = args.getParameters(); + if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded && + params.get("Component").supportsInterface() && + ++addedSigCount == 2) + { + addSigPromise.set_value(); + } + }; + clientDevice.getContext().getOnCoreEvent() += eventHandler; + + // make domain signal unsupported + testDomainSignal.asPtr().setDescriptor(unsupportedDescriptor); + server->broadcastPacket( + testValueSignal.getGlobalId(), + DataDescriptorChangedEventPacket(nullptr, unsupportedDescriptor.assigned() ? unsupportedDescriptor : NullDataDescriptor()) + ); + + // wait for 2 new incomplete signals added + ASSERT_TRUE(addSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); + clientDevice.getContext().getOnCoreEvent() -= eventHandler; + + // Check if old signals removed + ASSERT_TRUE(valueSignal.isRemoved()); + ASSERT_TRUE(domainSignal.isRemoved()); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + valueSignal = clientDevice.getSignals()[0]; + domainSignal = clientDevice.getSignals()[1]; + + // Check if new signals have nullptr descriptors + ASSERT_FALSE(valueSignal.getDescriptor().assigned()); + ASSERT_FALSE(domainSignal.getDescriptor().assigned()); +} + +TEST_P(UnsupportedSignalsTestP, MakeValueSignalSupported) +{ + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); + + const auto supportedValueDescriptor = DataDescriptorBuilderCopy(testValueSignal.getDescriptor()).build(); + // Set unsupported descriptor + testValueSignal.asPtr().setDescriptor(unsupportedDescriptor); + + // Setup and start server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { return signals; }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + auto valueSignal = clientDevice.getSignals()[0].asPtr(); + auto domainSignal = clientDevice.getSignals()[1].asPtr(); + ASSERT_FALSE(valueSignal.getDescriptor().assigned()); + ASSERT_TRUE(domainSignal.getDescriptor().assigned()); + + // Wait for the signals temporarily subscribed by the client during initialization to be unsubscribed, + // to avoid interfering with the explicit subscription triggered by reader creation. + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // subscribe value signal + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + valueSignal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + try + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + }; + auto reader = PacketReader(valueSignal); + + // make value signal supported, it will trigger subscribe ack + testValueSignal.asPtr().setDescriptor(supportedValueDescriptor); + server->broadcastPacket(testValueSignal.getGlobalId(), DataDescriptorChangedEventPacket(supportedValueDescriptor, nullptr)); + + // wait for subscribe ack + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + // wait for new descriptors assigned + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // Check the updated mirrored signals + ASSERT_TRUE(valueSignal.getDescriptor().assigned()); + ASSERT_TRUE(domainSignal.getDescriptor().assigned()); + ASSERT_FALSE(domainSignal.getDomainSignal().assigned()); + ASSERT_EQ(valueSignal.getDomainSignal(), domainSignal); + ASSERT_TRUE(BaseObjectPtr::Equals(valueSignal.getDescriptor(), supportedValueDescriptor)); + ASSERT_TRUE(BaseObjectPtr::Equals(domainSignal.getDescriptor(), testDomainSignal.getDescriptor())); +} + +TEST_P(UnsupportedSignalsTestP, MakeDomainSignalSupported) +{ + // Create server signals + auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); + auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); + auto signals = List(testValueSignal, testDomainSignal); + + const auto supportedDomainDescriptor = DataDescriptorBuilderCopy(testDomainSignal.getDescriptor()).build(); + // Set unsupported descriptor + testDomainSignal.asPtr().setDescriptor(unsupportedDescriptor); + + // Setup and start server which will publish created signals + auto server = std::make_shared(context); + server->onAccept([&](const daq::streaming_protocol::StreamWriterPtr& writer) { return signals; }); + server->start(STREAMING_PORT, CONTROL_PORT); + + // Create the client device + auto clientDevice = WebsocketClientDevice(NullContext(), nullptr, "device", HOST); + clientDevice.asPtr().enableCoreEventTrigger(); + + ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); + auto valueSignal = clientDevice.getSignals()[0].asPtr(); + auto domainSignal = clientDevice.getSignals()[1].asPtr(); + ASSERT_FALSE(valueSignal.getDescriptor().assigned()); + ASSERT_FALSE(domainSignal.getDescriptor().assigned()); + + // Wait for the signals temporarily subscribed by the client during initialization to be unsubscribed, + // to avoid interfering with the explicit subscription triggered by reader creation. + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // subscribe value signal + std::promise acknowledgementPromise; + std::future acknowledgementFuture = acknowledgementPromise.get_future(); + valueSignal.getOnSubscribeComplete() += + [&acknowledgementPromise](MirroredSignalConfigPtr&, SubscriptionEventArgsPtr& args) + { + try + { + acknowledgementPromise.set_value(args.getStreamingConnectionString()); + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + }; + auto reader = PacketReader(valueSignal); + + // make value signal supported, it will trigger subscribe ack + testDomainSignal.asPtr().setDescriptor(supportedDomainDescriptor); + server->broadcastPacket(testValueSignal.getGlobalId(), DataDescriptorChangedEventPacket(nullptr, supportedDomainDescriptor)); + + // wait for subscribe ack + ASSERT_EQ(acknowledgementFuture.wait_for(std::chrono::milliseconds(500)), std::future_status::ready); + + // wait for new descriptors assigned + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + // Check the updated mirrored signals + ASSERT_TRUE(valueSignal.getDescriptor().assigned()); + ASSERT_TRUE(domainSignal.getDescriptor().assigned()); + ASSERT_FALSE(domainSignal.getDomainSignal().assigned()); + ASSERT_EQ(valueSignal.getDomainSignal(), domainSignal); + ASSERT_TRUE(BaseObjectPtr::Equals(domainSignal.getDescriptor(), supportedDomainDescriptor)); + ASSERT_TRUE(BaseObjectPtr::Equals(valueSignal.getDescriptor(), testValueSignal.getDescriptor())); +} + +INSTANTIATE_TEST_SUITE_P(UnsupportedSignalsTest, + UnsupportedSignalsTestP, + testing::Values(nullptr, DataDescriptorBuilder().setSampleType(daq::SampleType::Invalid).build())); + TEST_F(WebsocketClientDeviceTest, DeviceWithMultipleSignals) { // Create server side device From 1a4deb45ae78ab1f7e92704687600080ce1649cc Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Mon, 21 Oct 2024 17:08:43 +0200 Subject: [PATCH 098/127] Refactor repetitive processing patterns for event packets into separate utility functions --- .../libraries/websocket_streaming/src/input_signal.cpp | 7 ++++--- .../websocket_streaming/src/streaming_server.cpp | 10 +++------- .../tests/test_websocket_client_device.cpp | 5 +++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index d14729d..0ffdc30 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -4,6 +4,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING @@ -36,14 +37,14 @@ EventPacketPtr InputSignalBase::createDecriptorChangedPacket(bool valueChanged, if (isDomainSignal()) { - const auto valueDescParam = currentDataDescriptor.assigned() ? currentDataDescriptor : NullDataDescriptor(); + const auto valueDescParam = descriptorToEventPacketParam(currentDataDescriptor); return DataDescriptorChangedEventPacket(valueChanged ? valueDescParam : nullptr, nullptr); } else { - const auto valueDescParam = currentDataDescriptor.assigned() ? currentDataDescriptor : NullDataDescriptor(); + const auto valueDescParam = descriptorToEventPacketParam(currentDataDescriptor); const auto domainDesc = inputDomainSignal->getSignalDescriptor(); - const auto domainDescParam = domainDesc.assigned() ? domainDesc : NullDataDescriptor(); + const auto domainDescParam = descriptorToEventPacketParam(domainDesc); return DataDescriptorChangedEventPacket(valueChanged ? valueDescParam : nullptr, domainChanged ? domainDescParam : nullptr); } diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 4a076ba..c45da4d 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "websocket_streaming/streaming_server.h" #include #include @@ -572,13 +573,8 @@ void StreamingServer::handleDataDescriptorChanges(OutputSignalBasePtr& outputSig const StreamWriterPtr& writer, const EventPacketPtr& packet) { - const auto params = packet.getParameters(); - DataDescriptorPtr valueDescriptorParam = params[event_packet_param::DATA_DESCRIPTOR]; - DataDescriptorPtr domainDescriptorParam = params[event_packet_param::DOMAIN_DATA_DESCRIPTOR]; - const bool valueDescriptorChanged = valueDescriptorParam.assigned(); - const bool domainDescriptorChanged = domainDescriptorParam.assigned(); - const DataDescriptorPtr newValueDescriptor = valueDescriptorParam != NullDataDescriptor() ? valueDescriptorParam : nullptr; - const DataDescriptorPtr newDomainDescriptor = domainDescriptorParam != NullDataDescriptor() ? domainDescriptorParam : nullptr; + const auto [valueDescriptorChanged, domainDescriptorChanged, newValueDescriptor, newDomainDescriptor] = + parseDataDescriptorEventPacket(packet); bool subscribed = outputSignal->isSubscribed(); if (auto placeholderValueSignal = std::dynamic_pointer_cast(outputSignal)) diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 840efcd..2291a0b 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -7,6 +7,7 @@ #include #include "streaming_test_helpers.h" #include +#include using namespace daq; using namespace std::chrono_literals; @@ -504,7 +505,7 @@ TEST_P(UnsupportedSignalsTestP, MakeValueSignalUnsupported) testValueSignal.asPtr().setDescriptor(unsupportedDescriptor); server->broadcastPacket( testValueSignal.getGlobalId(), - DataDescriptorChangedEventPacket(unsupportedDescriptor.assigned() ? unsupportedDescriptor : NullDataDescriptor(), nullptr) + DataDescriptorChangedEventPacket(descriptorToEventPacketParam(unsupportedDescriptor), nullptr) ); // wait for 1 new incomplete signal added @@ -571,7 +572,7 @@ TEST_P(UnsupportedSignalsTestP, MakeDomainSignalUnsupported) testDomainSignal.asPtr().setDescriptor(unsupportedDescriptor); server->broadcastPacket( testValueSignal.getGlobalId(), - DataDescriptorChangedEventPacket(nullptr, unsupportedDescriptor.assigned() ? unsupportedDescriptor : NullDataDescriptor()) + DataDescriptorChangedEventPacket(nullptr, descriptorToEventPacketParam(unsupportedDescriptor)) ); // wait for 2 new incomplete signals added From 3f2311b23ee0a27c2fe28ae8891a0f47055cd5b9 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Tue, 22 Oct 2024 13:54:58 +0200 Subject: [PATCH 099/127] Skip LT tests which use subscribe ack on MacOs --- .../websocket_streaming/tests/streaming_test_helpers.h | 9 +++++++++ .../websocket_streaming/tests/test_streaming.cpp | 3 +++ .../tests/test_websocket_client_device.cpp | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index d6acbf6..98ca81f 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -31,6 +31,15 @@ #include #include "streaming_protocol/Unit.hpp" +// MAC CI issue +#if !defined(SKIP_TEST_MAC_CI) +# if defined(__clang__) +# define SKIP_TEST_MAC_CI return +# else +# define SKIP_TEST_MAC_CI +# endif +#endif + namespace streaming_test_helpers { inline daq::InstancePtr createServerInstance() diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index d3c9c2f..b6fed7a 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -130,6 +130,7 @@ TEST_F(StreamingTest, ParseConnectString) TEST_F(StreamingTest, Subscription) { + SKIP_TEST_MAC_CI; auto server = std::make_shared(context); server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); @@ -172,6 +173,7 @@ TEST_F(StreamingTest, Subscription) // sends explicit value packet after constant value packet TEST_F(StreamingTest, PacketsCorrectSequence) { + SKIP_TEST_MAC_CI; std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; auto sampleCount = data.size(); @@ -278,6 +280,7 @@ TEST_F(StreamingTest, PacketsCorrectSequence) // but with a delay equivalent to the size of one packet, and potentially missed intermediate changes. TEST_F(StreamingTest, PacketsIncorrectSequence) { + SKIP_TEST_MAC_CI; std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; auto sampleCount = data.size(); diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 2291a0b..7e31d80 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -140,6 +140,7 @@ class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public test TEST_P(WebsocketClientDeviceTestP, SignalWithDomain) { + SKIP_TEST_MAC_CI; const bool signalsAddedAfterConnect = GetParam(); // Create server signals @@ -366,6 +367,7 @@ INSTANTIATE_TEST_SUITE_P(SignalsAddedAfterConnect, WebsocketClientDeviceTestP, t TEST_F(WebsocketClientDeviceTest, ChangeValueDescriptorToSupported) { + SKIP_TEST_MAC_CI; // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); @@ -459,6 +461,7 @@ class UnsupportedSignalsTestP : public WebsocketClientDeviceTest, public testing TEST_P(UnsupportedSignalsTestP, MakeValueSignalUnsupported) { + SKIP_TEST_MAC_CI; // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); @@ -526,6 +529,7 @@ TEST_P(UnsupportedSignalsTestP, MakeValueSignalUnsupported) TEST_P(UnsupportedSignalsTestP, MakeDomainSignalUnsupported) { + SKIP_TEST_MAC_CI; // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); @@ -594,6 +598,7 @@ TEST_P(UnsupportedSignalsTestP, MakeDomainSignalUnsupported) TEST_P(UnsupportedSignalsTestP, MakeValueSignalSupported) { + SKIP_TEST_MAC_CI; // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); @@ -660,6 +665,7 @@ TEST_P(UnsupportedSignalsTestP, MakeValueSignalSupported) TEST_P(UnsupportedSignalsTestP, MakeDomainSignalSupported) { + SKIP_TEST_MAC_CI; // Create server signals auto testDomainSignal = streaming_test_helpers::createLinearTimeSignal(context); auto testValueSignal = streaming_test_helpers::createExplicitValueSignal(context, "TestName", testDomainSignal); From ec6f5cc0b6cd2c136f04c3c629fff2bb59be9224 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:19:26 +0100 Subject: [PATCH 100/127] [TBBAS-1850]: Implement communication protocol version info (openDAQ/openDAQ#556) - Implement protocol version info - Broadcasting protocol version via mDNS - Add transferring the connection string path from server --- .../src/websocket_streaming_client_module_impl.cpp | 1 + .../src/websocket_streaming_server_impl.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index a51a725..5b30b52 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -68,6 +68,7 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex cap.setConnectionType("TCP/IP"); cap.setPrefix("daq.lt"); + cap.setProtocolVersion(discoveredDevice.getPropertyOrDefault("protocolVersion", "")); if (discoveredDevice.servicePort > 0) cap.setPort(discoveredDevice.servicePort); return cap; diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp index 371ce83..0371574 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_impl.cpp @@ -68,6 +68,7 @@ PropertyObjectPtr WebsocketStreamingServerImpl::getDiscoveryConfig() discoveryConfig.addProperty(StringProperty("ServiceCap", "LT")); discoveryConfig.addProperty(StringProperty("Path", config.getPropertyValue("Path"))); discoveryConfig.addProperty(IntProperty("Port", config.getPropertyValue("WebsocketStreamingPort"))); + discoveryConfig.addProperty(StringProperty("ProtocolVersion", "")); return discoveryConfig; } From 41d1c1a8a22edc9dc02ba8527119a81551e52e0d Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 6 Nov 2024 10:51:24 +0100 Subject: [PATCH 101/127] Mark some skipped tests as [SKIPPED] in GTest results: * MacOs skipped tests * Some excluded parametrized integration tests * Tests skipped due to IPv6 unavailability --- .../websocket_streaming/tests/streaming_test_helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 98ca81f..8879e4e 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -34,7 +34,7 @@ // MAC CI issue #if !defined(SKIP_TEST_MAC_CI) # if defined(__clang__) -# define SKIP_TEST_MAC_CI return +# define SKIP_TEST_MAC_CI GTEST_SKIP() << "Skipping test on MacOs" # else # define SKIP_TEST_MAC_CI # endif From 72a5f824a12a260d98b595f3e16d6dddf56e10d0 Mon Sep 17 00:00:00 2001 From: Nikolai Shipilov Date: Wed, 6 Nov 2024 17:01:37 +0100 Subject: [PATCH 102/127] Enable test listeners for packet_streaming and LT libs --- .../websocket_streaming/tests/CMakeLists.txt | 3 ++- .../libraries/websocket_streaming/tests/test_app.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 shared/libraries/websocket_streaming/tests/test_app.cpp diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index c36285d..d072437 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(${TEST_APP} test_streaming.cpp test_websocket_client_device.cpp test_signal_generator.cpp + test_app.cpp ) if (MSVC) @@ -18,7 +19,7 @@ target_link_libraries(${TEST_APP} PRIVATE daq::opendaq daq::opendaq_mocks daq::streaming_protocol - GTest::GTest GTest::Main + ${SDK_TARGET_NAMESPACE}::test_utils ) set_target_properties(${TEST_APP} PROPERTIES DEBUG_POSTFIX _debug) diff --git a/shared/libraries/websocket_streaming/tests/test_app.cpp b/shared/libraries/websocket_streaming/tests/test_app.cpp new file mode 100644 index 0000000..608cd21 --- /dev/null +++ b/shared/libraries/websocket_streaming/tests/test_app.cpp @@ -0,0 +1,12 @@ +#include +#include + +int main(int argc, char** args) +{ + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new BaseTestListener()); + + return RUN_ALL_TESTS(); +} From 6e41f0a52316e16e02345f1bab11ae14e9195b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Fran=C4=8Di=C4=8D?= Date: Tue, 26 Nov 2024 11:17:31 +0100 Subject: [PATCH 103/127] Add Module Info (openDAQ/openDAQ#609) * Delete the fields `id`, `name`, and `versionInfo` from the `IModule` interface and add them to the new `IModuleInfo` interface, which is a new field in the `IModule` interface * `IModuleInfo` field is also added to `IComponentType` interface * Works over Native * Component Type is moved from `coreobjects` to `opendaq` --- .../tests/test_websocket_streaming_client_module.cpp | 6 +++--- .../tests/test_websocket_streaming_server_module.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp index 483d2be..36782c2 100644 --- a/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp +++ b/modules/websocket_streaming_client_module/tests/test_websocket_streaming_client_module.cpp @@ -33,19 +33,19 @@ TEST_F(WebsocketStreamingClientModuleTest, CreateModule) TEST_F(WebsocketStreamingClientModuleTest, ModuleName) { auto module = CreateModule(); - ASSERT_EQ(module.getName(), "OpenDAQWebsocketClientModule"); + ASSERT_EQ(module.getModuleInfo().getName(), "OpenDAQWebsocketClientModule"); } TEST_F(WebsocketStreamingClientModuleTest, VersionAvailable) { auto module = CreateModule(); - ASSERT_TRUE(module.getVersionInfo().assigned()); + ASSERT_TRUE(module.getModuleInfo().getVersionInfo().assigned()); } TEST_F(WebsocketStreamingClientModuleTest, VersionCorrect) { auto module = CreateModule(); - auto version = module.getVersionInfo(); + auto version = module.getModuleInfo().getVersionInfo(); ASSERT_EQ(version.getMajor(), WS_STREAM_CL_MODULE_MAJOR_VERSION); ASSERT_EQ(version.getMinor(), WS_STREAM_CL_MODULE_MINOR_VERSION); diff --git a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp index 4ec460f..65307c0 100644 --- a/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp +++ b/modules/websocket_streaming_server_module/tests/test_websocket_streaming_server_module.cpp @@ -76,19 +76,19 @@ TEST_F(WebsocketStreamingServerModuleTest, CreateModule) TEST_F(WebsocketStreamingServerModuleTest, ModuleName) { auto module = CreateModule(); - ASSERT_EQ(module.getName(), "OpenDAQWebsocketStreamingServerModule"); + ASSERT_EQ(module.getModuleInfo().getName(), "OpenDAQWebsocketStreamingServerModule"); } TEST_F(WebsocketStreamingServerModuleTest, VersionAvailable) { auto module = CreateModule(); - ASSERT_TRUE(module.getVersionInfo().assigned()); + ASSERT_TRUE(module.getModuleInfo().getVersionInfo().assigned()); } TEST_F(WebsocketStreamingServerModuleTest, VersionCorrect) { auto module = CreateModule(); - auto version = module.getVersionInfo(); + auto version = module.getModuleInfo().getVersionInfo(); ASSERT_EQ(version.getMajor(), WS_STREAM_SRV_MODULE_MAJOR_VERSION); ASSERT_EQ(version.getMinor(), WS_STREAM_SRV_MODULE_MINOR_VERSION); From cb656cf17c89c5be413a61c22d3cb9cf8abeb373 Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Fri, 29 Nov 2024 03:58:14 -0800 Subject: [PATCH 104/127] Fix thread sanitizer warnings in streaming read threads (openDAQ/openDAQ#623) --- .../include/websocket_streaming/async_packet_reader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index ad5e2aa..2171e29 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -48,7 +48,7 @@ class AsyncPacketReader ContextPtr context; OnPacketCallback onPacketCallback; std::thread readThread; - bool readThreadStarted = false; + std::atomic readThreadStarted{false}; std::chrono::milliseconds sleepTime; std::vector> signalReaders; From afe57efa221b839dcfdba231ec0fe8d712e390c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Fran=C4=8Di=C4=8D?= Date: Mon, 6 Jan 2025 08:35:08 +0100 Subject: [PATCH 105/127] Bump year in license, etc. to 2025 (openDAQ/openDAQ#656) * Bump year in license, etc. to 2025 * Update additional files --- .../include/websocket_streaming_client_module/common.h | 2 +- .../include/websocket_streaming_client_module/module_dll.h | 2 +- .../websocket_streaming_client_module_impl.h | 2 +- .../include/websocket_streaming_server_module/common.h | 2 +- .../include/websocket_streaming_server_module/module_dll.h | 2 +- .../websocket_streaming_server_impl.h | 2 +- .../websocket_streaming_server_module_impl.h | 2 +- .../include/websocket_streaming/async_packet_reader.h | 2 +- .../include/websocket_streaming/input_signal.h | 2 +- .../include/websocket_streaming/output_signal.h | 2 +- .../include/websocket_streaming/signal_descriptor_converter.h | 2 +- .../include/websocket_streaming/signal_info.h | 2 +- .../include/websocket_streaming/streaming_client.h | 2 +- .../include/websocket_streaming/streaming_server.h | 2 +- .../websocket_streaming/websocket_client_device_factory.h | 2 +- .../include/websocket_streaming/websocket_client_device_impl.h | 2 +- .../websocket_streaming/websocket_client_signal_factory.h | 2 +- .../include/websocket_streaming/websocket_client_signal_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming.h | 2 +- .../include/websocket_streaming/websocket_streaming_factory.h | 2 +- .../include/websocket_streaming/websocket_streaming_impl.h | 2 +- .../include/websocket_streaming/websocket_streaming_server.h | 2 +- .../websocket_streaming/tests/streaming_test_helpers.h | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h index 27cfe6b..53b1e95 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h index 909f4cb..925856d 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index bde0fbb..b0de9df 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h index 090ca45..e075ba1 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h index 51358ee..b129905 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/module_dll.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h index 2a60626..8e11392 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h index c2d609f..2415e17 100644 --- a/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h +++ b/modules/websocket_streaming_server_module/include/websocket_streaming_server_module/websocket_streaming_server_module_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index 2171e29..916afe0 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index fec922c..3b7080b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h index dc8c768..c607633 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/output_signal.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index 712c967..76b2707 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h index 84c07e2..b60884f 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_info.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index 95008a2..ce63d87 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index bcb52b9..63e7484 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h index 1bdf43d..183082c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h index 755d51b..f0fa615 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_device_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h index feeb7ac..83570bc 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h index 563c186..5ed3efe 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_client_signal_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h index b1ac0d7..bbbad8c 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h index db15f3c..6c82b0b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_factory.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h index 5bdf799..c4a3695 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index af2aa22..468dcd6 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h index 8879e4e..484e3a4 100644 --- a/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h +++ b/shared/libraries/websocket_streaming/tests/streaming_test_helpers.h @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 openDAQ d.o.o. + * Copyright 2022-2025 openDAQ d.o.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 90d3b5948ebb9ed2575eab544740c84a75c9a692 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:27:07 +0100 Subject: [PATCH 106/127] Implement configurable device info fields on core (openDAQ/openDAQ#607) --- .../websocket_streaming/src/websocket_client_device_impl.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index e7a605d..29e7b38 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -31,9 +31,7 @@ void WebsocketClientDeviceImpl::removed() DeviceInfoPtr WebsocketClientDeviceImpl::onGetInfo() { - auto deviceInfo = DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); - deviceInfo.freeze(); - return deviceInfo; + return DeviceInfo(connectionString, "WebsocketClientPseudoDevice"); } void WebsocketClientDeviceImpl::activateStreaming() From c9cf166a9f493c71aba27673cf59f6505ba434da Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Thu, 30 Jan 2025 04:33:33 -0800 Subject: [PATCH 107/127] IP modification mechanism (openDAQ/openDAQ#642) * shared libraries: - & extend MDNS discovery server and client to enable transmitting the custom non-discovery packages - Automatically advertise the ip-modification service within mdns discovery server if IP-modification is enabled within instance * core: - enable passing the root device reference to mdns servers * external dependencies: - Add patch for header-only mdns library to support custom non-discovery queries - Rename and extend parameters of mdns query callbacks * Discovery refactoring: - replace dependency on opendaq to dependency on coreobjects lib - replace modules callbacks with direct instantiation of DeviceInfo object using discovered device data - Rename services to servers for discovery - Move duplicating discovery and ip modification code into common library * Simulator: - Enable changing and retrieving IP config on simulator over netplan - Fix getting RefDevice SerialNumber from module options - Update vagrant installation for standalone build-packages ci job - Replace Avahi with internal mDNS server for OPC UA discovery - Remove Avahi from simulator and clean-up it's dependencies - Reduce simulator boot time when network is offline * Add cpp and python examples for IP modification * Fix unused variable warning in reader bindings --- .../websocket_streaming_client_module_impl.h | 1 + ...websocket_streaming_client_module_impl.cpp | 112 +++++++++--------- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h index b0de9df..b46b810 100644 --- a/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h +++ b/modules/websocket_streaming_client_module/include/websocket_streaming_client_module/websocket_streaming_client_module_impl.h @@ -45,6 +45,7 @@ class WebsocketStreamingClientModule final : public Module static StreamingTypePtr createWebsocketStreamingType(); static PropertyObjectPtr createDefaultConfig(); static StringPtr formConnectionString(const StringPtr& connectionString, const PropertyObjectPtr& config); + static DeviceInfoPtr populateDiscoveredDevice(const discovery::MdnsDiscoveredDevice& discoveredDevice); std::mutex sync; size_t deviceIndex; diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 5b30b52..da76858 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -8,6 +8,7 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE @@ -26,54 +27,6 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex "OpenDAQWebsocketClientModule") , deviceIndex(0) , discoveryClient( - { - [context = this->context](MdnsDiscoveredDevice discoveredDevice) - { - auto cap = ServerCapability(WebsocketDeviceTypeId, "OpenDAQLTStreaming", ProtocolType::Streaming); - - if (!discoveredDevice.ipv4Address.empty()) - { - auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( - discoveredDevice.ipv4Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/") - ); - cap.addConnectionString(connectionStringIpv4); - cap.addAddress(discoveredDevice.ipv4Address); - const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv4Address) - .setReachabilityStatus(AddressReachabilityStatus::Unknown) - .setType("IPv4") - .setConnectionString(connectionStringIpv4) - .build(); - cap.addAddressInfo(addressInfo); - } - - if(!discoveredDevice.ipv6Address.empty()) - { - auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( - discoveredDevice.ipv6Address, - discoveredDevice.servicePort, - discoveredDevice.getPropertyOrDefault("path", "/") - ); - cap.addConnectionString(connectionStringIpv6); - cap.addAddress(discoveredDevice.ipv6Address); - - const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv6Address) - .setReachabilityStatus(AddressReachabilityStatus::Unknown) - .setType("IPv6") - .setConnectionString(connectionStringIpv6) - .build(); - cap.addAddressInfo(addressInfo); - } - - cap.setConnectionType("TCP/IP"); - cap.setPrefix("daq.lt"); - cap.setProtocolVersion(discoveredDevice.getPropertyOrDefault("protocolVersion", "")); - if (discoveredDevice.servicePort > 0) - cap.setPort(discoveredDevice.servicePort); - return cap; - } - }, {"LT"} ) { @@ -83,11 +36,9 @@ WebsocketStreamingClientModule::WebsocketStreamingClientModule(ContextPtr contex ListPtr WebsocketStreamingClientModule::onGetAvailableDevices() { - auto availableDevices = discoveryClient.discoverDevices(); - for (auto device : availableDevices) - { - device.asPtr().setDeviceType(createWebsocketDeviceType(false)); - } + auto availableDevices = List(); + for (const auto& device : discoveryClient.discoverMdnsDevices()) + availableDevices.pushBack(populateDiscoveredDevice(device)); return availableDevices; } @@ -329,4 +280,59 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& return connectionString; } +DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const MdnsDiscoveredDevice& discoveredDevice) +{ + PropertyObjectPtr deviceInfo = DeviceInfo(""); + DiscoveryClient::populateDiscoveredInfoProperties(deviceInfo, discoveredDevice); + + auto cap = ServerCapability(WebsocketDeviceTypeId, "OpenDAQLTStreaming", ProtocolType::Streaming); + + if (!discoveredDevice.ipv4Address.empty()) + { + auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( + discoveredDevice.ipv4Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); + cap.addConnectionString(connectionStringIpv4); + cap.addAddress(discoveredDevice.ipv4Address); + const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv4Address) + .setReachabilityStatus(AddressReachabilityStatus::Unknown) + .setType("IPv4") + .setConnectionString(connectionStringIpv4) + .build(); + cap.addAddressInfo(addressInfo); + } + + if(!discoveredDevice.ipv6Address.empty()) + { + auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( + discoveredDevice.ipv6Address, + discoveredDevice.servicePort, + discoveredDevice.getPropertyOrDefault("path", "/") + ); + cap.addConnectionString(connectionStringIpv6); + cap.addAddress(discoveredDevice.ipv6Address); + + const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv6Address) + .setReachabilityStatus(AddressReachabilityStatus::Unknown) + .setType("IPv6") + .setConnectionString(connectionStringIpv6) + .build(); + cap.addAddressInfo(addressInfo); + } + + cap.setConnectionType("TCP/IP"); + cap.setPrefix("daq.lt"); + cap.setProtocolVersion(discoveredDevice.getPropertyOrDefault("protocolVersion", "")); + if (discoveredDevice.servicePort > 0) + cap.setPort(discoveredDevice.servicePort); + + deviceInfo.asPtr().addServerCapability(cap); + deviceInfo.asPtr().setConnectionString(cap.getConnectionString()); + deviceInfo.asPtr().setDeviceType(createWebsocketDeviceType(false)); + + return deviceInfo; +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE From 57d3a3212949cc2817dbaf28ccb3954c724aaf4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20R=C3=BChmer?= <108338550+Daniel-BB@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:11:26 +0100 Subject: [PATCH 108/127] Fixed unknown std::chrono namespace issue introduced in VS2022 17.13.0 (openDAQ/openDAQ#698) --- .../include/websocket_streaming/async_packet_reader.h | 1 + .../include/websocket_streaming/streaming_client.h | 1 + 2 files changed, 2 insertions(+) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h index 916afe0..430b204 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/async_packet_reader.h @@ -15,6 +15,7 @@ */ #pragma once +#include #include #include "websocket_streaming/websocket_streaming.h" #include diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index ce63d87..d757c1b 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include #include "websocket_streaming/websocket_streaming.h" #include "websocket_streaming/input_signal.h" From fcf8819c9fdc048bb5c9a2d2494e9e34fbb6b621 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:42:39 +0100 Subject: [PATCH 109/127] Device info `setName` changed device name + setting owner prevents writing of read-only values (openDAQ/openDAQ#700) --- .../src/websocket_streaming_client_module_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index da76858..b9877c5 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -329,7 +329,7 @@ DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const Mdn cap.setPort(discoveredDevice.servicePort); deviceInfo.asPtr().addServerCapability(cap); - deviceInfo.asPtr().setConnectionString(cap.getConnectionString()); + deviceInfo.asPtr().setProtectedPropertyValue("connectionString", cap.getConnectionString()); deviceInfo.asPtr().setDeviceType(createWebsocketDeviceType(false)); return deviceInfo; From d4cd5a3e46baed9106381eb9ba11808b95ba74f0 Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Sun, 23 Feb 2025 23:52:53 -0800 Subject: [PATCH 110/127] Merge release 3.10 fixes to main (openDAQ/openDAQ#710) Fix LT streaming issues (openDAQ/openDAQ#696) * keep client session open when unknown or incomplete signal's data received * use raw Json value of linear rule delta when creating linear rule parameters * utilize default start value for generating constant rule signal * properly accept control connections via IPv4/v6 --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/input_signal.h | 22 +- .../websocket_streaming/src/input_signal.cpp | 224 ++++++++++++------ .../websocket_streaming/src/output_signal.cpp | 43 +++- .../src/signal_descriptor_converter.cpp | 6 +- .../src/streaming_client.cpp | 14 +- .../test_signal_descriptor_converter.cpp | 91 ++++++- .../tests/test_streaming.cpp | 43 ++-- 8 files changed, 339 insertions(+), 108 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 4673381..04d644b 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.1 + REQUIRED_VERSION 1.2.2 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.2.1 + GIT_REF v1.2.2 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 3b7080b..67fe8f2 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -142,7 +142,8 @@ class InputConstantDataSignal : public InputSignalBase const std::string& tabledId, const SubscribedSignalInfo& signalInfo, const InputSignalBasePtr& domainSignal, - streaming_protocol::LogCallback logCb); + streaming_protocol::LogCallback logCb, + const nlohmann::json& metaInfoStartValue); void processSamples(const NumberPtr& absoluteStartDomainValue, const uint8_t* data, size_t sampleCount) override; DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, @@ -152,9 +153,16 @@ class InputConstantDataSignal : public InputSignalBase bool isDomainSignal() const override; bool isCountable() const override; + void updateStartValue(const nlohmann::json& metaInfoStartValue); + private: + using CachedSignalValues = std::map; + template - static SignalValueType extractConstantValue(const uint8_t* pValue); + static DataType convertToNumeric(const nlohmann::json& jsonNumeric); + + template + static auto callWithSampleType(daq::SampleType sampleType, Func&& func); template static DataPacketPtr createTypedConstantPacket( @@ -167,8 +175,11 @@ class InputConstantDataSignal : public InputSignalBase NumberPtr calcDomainValue(const NumberPtr& startDomainValue, const uint64_t sampleIndex); NumberPtr getDomainRuleDelta(); uint32_t calcPosition(const NumberPtr& startDomainValue, const NumberPtr& domainValue); + CachedSignalValues::iterator insertDefaultValue(const NumberPtr& domainValue); - std::map cachedSignalValues; + CachedSignalValues cachedSignalValues; + std::optional defaultStartValue; + bool suppressDefaultStartValueWarnings; }; inline InputSignalBasePtr InputSignal(const std::string& signalId, @@ -176,7 +187,8 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, const SubscribedSignalInfo& signalInfo, bool isTimeSignal, const InputSignalBasePtr& domainSignal, - streaming_protocol::LogCallback logCb) + streaming_protocol::LogCallback logCb, + const nlohmann::json& constRuleStartValueMeta) { auto dataRuleType = signalInfo.dataDescriptor.getRule().getType(); @@ -192,7 +204,7 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, if (dataRuleType == daq::DataRuleType::Explicit) return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb); else if (dataRuleType == daq::DataRuleType::Constant) - return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb); + return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb, constRuleStartValueMeta); else throw ConversionFailedException("Unsupported input data signal rule"); } diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 0ffdc30..3927d81 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -168,13 +168,46 @@ bool InputExplicitDataSignal::isCountable() const return true; } +template +auto InputConstantDataSignal::callWithSampleType(daq::SampleType sampleType, Func&& func) +{ + switch (sampleType) + { + case daq::SampleType::Int8: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::Int16: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::Int32: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::Int64: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::UInt8: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::UInt16: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::UInt32: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::UInt64: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::Float32: + return func(daq::SampleTypeToType::Type{}); + case daq::SampleType::Float64: + return func(daq::SampleTypeToType::Type{}); + default: + throw std::invalid_argument("Unsupported sample type"); + } +} + InputConstantDataSignal::InputConstantDataSignal(const std::string& signalId, const std::string& tabledId, const SubscribedSignalInfo& signalInfo, const InputSignalBasePtr& domainSignal, - streaming_protocol::LogCallback logCb) + streaming_protocol::LogCallback logCb, + const nlohmann::json& metaInfoStartValue) : InputSignalBase(signalId, tabledId, signalInfo, domainSignal, logCb) + , suppressDefaultStartValueWarnings(false) { + updateStartValue(metaInfoStartValue); } NumberPtr InputConstantDataSignal::calcDomainValue(const NumberPtr& startDomainValue, const uint64_t sampleIndex) @@ -189,6 +222,8 @@ NumberPtr InputConstantDataSignal::calcDomainValue(const NumberPtr& startDomainV void InputConstantDataSignal::processSamples(const NumberPtr& absoluteStartDomainValue, const uint8_t* data, size_t sampleCount) { + std::scoped_lock lock(descriptorsSync); + auto sampleType = currentDataDescriptor.getSampleType(); const auto sampleSize = getSampleSize(sampleType); const auto bufferSize = sampleCount * (sampleSize + sizeof(uint64_t)); @@ -199,44 +234,20 @@ void InputConstantDataSignal::processSamples(const NumberPtr& absoluteStartDomai const uint8_t* pSignalValue = data + addrOffset + sizeof(uint64_t); auto domainValue = calcDomainValue(absoluteStartDomainValue, *pIndex); - SignalValueType signalValue; - switch (sampleType) + try { - case daq::SampleType::Int8: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::Int16: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::Int32: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::Int64: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::UInt8: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::UInt16: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::UInt32: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::UInt64: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::Float32: - signalValue = extractConstantValue(pSignalValue); - break; - case daq::SampleType::Float64: - signalValue = extractConstantValue(pSignalValue); - break; - default: - return; + auto extractConstantValue = [pSignalValue](const auto& typeTag) + { + using DataType = typename std::decay_t; + return SignalValueType(*(reinterpret_cast(pSignalValue))); + }; + SignalValueType signalValue = callWithSampleType(sampleType, extractConstantValue); + cachedSignalValues.insert_or_assign(domainValue, signalValue); + } + catch (...) + { + return; } - - cachedSignalValues.insert_or_assign(domainValue, signalValue); } } @@ -255,6 +266,37 @@ uint32_t InputConstantDataSignal::calcPosition(const NumberPtr& startDomainValue return (domainValue.getIntValue() - startDomainValue.getIntValue()) / domainRuleDelta.getIntValue(); } +InputConstantDataSignal::CachedSignalValues::iterator InputConstantDataSignal::insertDefaultValue(const NumberPtr& domainValue) +{ + if (!suppressDefaultStartValueWarnings) + { + if (defaultStartValue.has_value()) + { + STREAMING_PROTOCOL_LOG_W("Constant rule signal id \"{}\" (table \"{}\"): " + "packet start value isn't yet received, will use default start value from meta-info", + this->signalId, + this->tableId); + } + else + { + STREAMING_PROTOCOL_LOG_W("Constant rule signal id \"{}\" (table \"{}\"): " + "packet start value isn't yet received nor valid one provided in meta-info, will use default start value 0", + this->signalId, + this->tableId); + } + suppressDefaultStartValueWarnings = true; // log warning message just ones + } + + auto createZeroValue = [](const auto& typeTag) + { + using DataType = typename std::decay_t; + return SignalValueType(static_cast(0)); + }; + SignalValueType zeroValue = callWithSampleType(currentDataDescriptor.getSampleType(), createZeroValue); + const auto result = cachedSignalValues.insert_or_assign(domainValue, defaultStartValue.value_or(zeroValue)); + return result.first; +} + DataPacketPtr InputConstantDataSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* /*data*/, size_t sampleCount, @@ -283,11 +325,21 @@ DataPacketPtr InputConstantDataSignal::generateDataPacket(const NumberPtr& /*pac itStart = it; } - // start value is not found + bool removeDefaultValueFromCache = false; + // appropriate start value is not found as it wasn't received as signal data + // temporary insert default value into cache and use it to generate packet if (itStart == cachedSignalValues.end()) { - STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: packet start value is unknown"); - return nullptr; + try + { + itStart = insertDefaultValue(packetStartDomainValue); + removeDefaultValueFromCache = true; + } + catch (const std::exception& e) + { + STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: {}", e.what()); + return nullptr; + } } // start value found @@ -310,32 +362,25 @@ DataPacketPtr InputConstantDataSignal::generateDataPacket(const NumberPtr& /*pac { cachedSignalValues.erase(cachedSignalValues.begin(), itStart); } + // erase temporary inserted default value + if (removeDefaultValueFromCache) + { + cachedSignalValues.erase(itStart); + } - switch (currentDataDescriptor.getSampleType()) + try { - case daq::SampleType::Int8: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::Int16: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::Int32: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::Int64: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::UInt8: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::UInt16: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::UInt32: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::UInt64: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::Float32: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - case daq::SampleType::Float64: - return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); - default: - STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: unsupported sample type"); - return nullptr; + auto createPacket = [&](const auto& typeTag) + { + using DataType = typename std::decay_t; + return createTypedConstantPacket(packetStartValue, packetOtherValues, sampleCount, domainPacket, currentDataDescriptor); + }; + return callWithSampleType(currentDataDescriptor.getSampleType(), createPacket); + } + catch (const std::exception& e) + { + STREAMING_PROTOCOL_LOG_E("Fail to generate constant data packet: {}", e.what()); + return nullptr; } } @@ -349,10 +394,57 @@ bool InputConstantDataSignal::isCountable() const return false; } +void InputConstantDataSignal::updateStartValue(const nlohmann::json& metaInfoStartValue) +{ + std::scoped_lock lock(descriptorsSync); + + try + { + auto getValueFromJson = [&metaInfoStartValue](const auto& typeTag) + { + using DataType = typename std::decay_t; + return SignalValueType(convertToNumeric(metaInfoStartValue)); + }; + defaultStartValue = callWithSampleType(currentDataDescriptor.getSampleType(), getValueFromJson); + suppressDefaultStartValueWarnings = false; + } + catch (const std::exception& e) + { + STREAMING_PROTOCOL_LOG_I("Cannot get default start value from signal meta-info: {}", e.what()); + } +} + template -InputConstantDataSignal::SignalValueType InputConstantDataSignal::extractConstantValue(const uint8_t* pValue) +DataType InputConstantDataSignal::convertToNumeric(const nlohmann::json& jsonNumeric) { - return SignalValueType(*(reinterpret_cast(pValue))); + if (jsonNumeric.is_null()) + throw std::invalid_argument("No value provided"); + + if (!jsonNumeric.is_number()) + throw std::invalid_argument("JSON value is not number"); + + if constexpr (std::is_floating_point::value) + { + double numeric = jsonNumeric.get(); + if (numeric < std::numeric_limits::min() || numeric > std::numeric_limits::max()) + throw std::out_of_range("Value out of range"); + return static_cast(numeric); + } + else if constexpr (std::is_signed::value) + { + int64_t numeric = jsonNumeric.get(); + if (numeric < std::numeric_limits::min() || numeric > std::numeric_limits::max()) + throw std::out_of_range("Value out of range"); + return static_cast(numeric); + } + else if constexpr (std::is_unsigned::value) + { + uint64_t numeric = jsonNumeric.get(); + if (numeric > std::numeric_limits::max()) + throw std::out_of_range("Value out of range"); + return static_cast(numeric); + } + throw std::invalid_argument("Conversion failed - invalid sample type"); } template diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index d31eac3..25d1563 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -535,37 +535,60 @@ BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( const auto signalId = signal.getGlobalId(); + const auto lastValue = signal.getLastValue(); + nlohmann::json defaultStartValue(nullptr); + switch (sampleType) { case daq::SampleType::Int8: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::UInt8: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::Int16: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::UInt16: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::Int32: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::UInt32: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::Int64: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::UInt64: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::Float32: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::Float64: - constStream = std::make_shared>(signalId, tableId, *writer, logCb); + if (lastValue.assigned()) + defaultStartValue = static_cast(lastValue.asPtr()); + constStream = std::make_shared>(signalId, tableId, *writer, defaultStartValue, logCb); break; case daq::SampleType::ComplexFloat32: case daq::SampleType::ComplexFloat64: diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index bedc9d5..2259090 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -227,7 +227,11 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco break; case daq::streaming_protocol::RULETYPE_LINEAR: { - return LinearDataRule(subscribedSignal.linearDelta(), 0); + nlohmann::json linearDeltaJson = subscribedSignal.linearDeltaMeta(); + if (linearDeltaJson.is_number_integer()) + return LinearDataRule(linearDeltaJson.get(), 0); + else + return LinearDataRule(linearDeltaJson.get(), 0); } break; default: diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index dfbf6a8..73e44d4 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -480,7 +480,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& if (!relatedSignal->isCountable()) { auto packet = relatedSignal->generateDataPacket(domainValue, nullptr, valueCount, domainPacket); - if (packet.assigned()) + if (packet.assigned() && relatedSignal->getSubscribed()) onPacketCallback(relatedSignal->getSignalId(), packet); } } @@ -517,7 +517,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig if (!inputSignal && !available) { - inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback); + inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback, subscribedSignal.constRuleStartMeta()); hiddenSignals.insert({signalId, inputSignal}); onHiddenStreamingSignalCb(signalId, sInfo); onHiddenDeviceSignalInitCb(signalId, sInfo); @@ -527,7 +527,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig else if (available && isPlaceHolderSignal(inputSignal)) { bool subscribed = inputSignal->getSubscribed(); - inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback); + inputSignal = InputSignal(signalId, tableId, sInfo, false, domainInputSignal, logCallback, subscribedSignal.constRuleStartMeta()); inputSignal->setSubscribed(subscribed); availableSignals[signalId] = inputSignal; onAvailableSignalInitCb(signalId, sInfo); @@ -568,7 +568,7 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig if (!inputSignal && !available) { // the time signal was not published as available by server, add as hidden - inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback); + inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback, subscribedSignal.constRuleStartMeta()); hiddenSignals.insert({timeSignalId, inputSignal}); onHiddenStreamingSignalCb(timeSignalId, sInfo); onHiddenDeviceSignalInitCb(timeSignalId, sInfo); @@ -576,7 +576,7 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig else if (available && isPlaceHolderSignal(inputSignal)) { bool subscribed = inputSignal->getSubscribed(); - inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback); + inputSignal = InputSignal(timeSignalId, tableId, sInfo, true, nullptr, logCallback, subscribedSignal.constRuleStartMeta()); inputSignal->setSubscribed(subscribed); availableSignals[timeSignalId] = inputSignal; // the time signal is published as available by server, @@ -596,6 +596,10 @@ void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSig publishSignalChanges(inputDataSignal, false, true); } } + if (auto constRuleSignal = std::dynamic_pointer_cast(inputSignal)) + { + constRuleSignal->updateStartValue(subscribedSignal.constRuleStartMeta()); + } onSignalUpdatedCallback(timeSignalId, sInfo); } } diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 7ae9501..7c61f39 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -261,7 +261,10 @@ TEST(SignalConverter, subscribedDataSignal) signalParams[bsp::META_DEFINITION][bsp::META_POSTSCALING][bsp::META_SCALE] = 2.0; signalParams[bsp::META_DEFINITION][bsp::META_POSTSCALING][bsp::META_POFFSET] = 3.0; - result = subscribedSignal.processSignalMetaInformation(method, signalParams); + std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(signalParams); + nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(msgpack); + + result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); @@ -313,7 +316,10 @@ TEST(SignalConverter, subscribedBitfieldSignal) bsp::DATA_TYPE_UINT64; signalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_CONSTANT; - result = subscribedSignal.processSignalMetaInformation(method, signalParams); + std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(signalParams); + nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(msgpack); + + result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); ASSERT_FALSE(subscribedSignal.isTimeSignal()); auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); @@ -375,7 +381,11 @@ TEST(SignalConverter, subscribedTimeSignal) timeSignalParams[bsp::META_DEFINITION][bsp::META_ABSOLUTE_REFERENCE] = bsp::UNIX_EPOCH; timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_NUMERATOR] = 1; timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; - result = subscribedSignal.processSignalMetaInformation(method, timeSignalParams); + + std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(timeSignalParams); + nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(msgpack); + + result = subscribedSignal.processSignalMetaInformation(method, timeSignalParamsToParse); ASSERT_EQ(result, 0); ASSERT_TRUE(subscribedSignal.isTimeSignal()); @@ -401,4 +411,79 @@ TEST(SignalConverter, subscribedTimeSignal) ASSERT_EQ(resultStart, 0); } +TEST(SignalConverter, FloatDeltaOfLinearSignal) +{ + std::string method; + int result; + unsigned int signalNumber = 3; + std::string tableId = "table id"; + std::string signalId = "signal id"; + std::string memberName = "This is the time"; + + uint64_t ticksPerSecond = 10000000; + float linearDelta = 4194304.0f; + int32_t unitId = bsp::Unit::UNIT_ID_SECONDS; + std::string unitDisplayName = "s"; + + bsp::SubscribedSignal subscribedSignal(signalNumber, bsp::Logging::logCallback()); + + // some meta information is to be processed to have the signal described: + // -subscribe + // -signal + // -set the time + nlohmann::json subscribeParams; + method = bsp::META_METHOD_SUBSCRIBE; + + subscribeParams[bsp::META_SIGNALID] = signalId; + result = subscribedSignal.processSignalMetaInformation(method, subscribeParams); + ASSERT_EQ(result, 0); + + nlohmann::json timeSignalParams; + method = bsp::META_METHOD_SIGNAL; + + timeSignalParams[bsp::META_TABLEID] = tableId; + timeSignalParams[bsp::META_DEFINITION][bsp::META_NAME] = memberName; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_LINEAR; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_RULETYPE_LINEAR][bsp::META_DELTA] = linearDelta; + timeSignalParams[bsp::META_DEFINITION][bsp::META_DATATYPE] = bsp::DATA_TYPE_REAL32; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_UNIT_ID] = unitId; + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_DISPLAY_NAME] = unitDisplayName; + timeSignalParams[bsp::META_DEFINITION][bsp::META_UNIT][bsp::META_QUANTITY] = bsp::META_TIME; + + timeSignalParams[bsp::META_DEFINITION][bsp::META_ABSOLUTE_REFERENCE] = bsp::UNIX_EPOCH; + timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_NUMERATOR] = 1; + timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; + + std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(timeSignalParams); + nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(msgpack); + + result = subscribedSignal.processSignalMetaInformation(method, timeSignalParamsToParse); + ASSERT_EQ(result, 0); + ASSERT_TRUE(subscribedSignal.isTimeSignal()); + + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto dataDescriptor = subscribedSignalInfo.dataDescriptor; + ASSERT_EQ(subscribedSignalInfo.signalName, memberName); + + ASSERT_EQ(dataDescriptor.getSampleType(), daq::SampleType::Float32); + + auto unit = dataDescriptor.getUnit(); + ASSERT_TRUE(unit.assigned()); + ASSERT_EQ(unit.getId(), unitId); + ASSERT_EQ(unit.getSymbol(), unitDisplayName); + + auto rule = dataDescriptor.getRule(); + ASSERT_TRUE(rule.assigned()); + ASSERT_EQ(daq::DataRuleType::Linear, rule.getType()); + DictPtr params = rule.getParameters(); + ASSERT_EQ(params.getCount(), 2u); + float resultDelta = params.get("delta"); + uint64_t resultStart = params.get("start"); + ASSERT_EQ(resultDelta, linearDelta); + ASSERT_EQ(resultStart, 0); +} + END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index b6fed7a..6997fdb 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -276,14 +276,22 @@ TEST_F(StreamingTest, PacketsCorrectSequence) } // sends explicit value packet before constant value packet -// this results in client side constant packets that track the last changes of the constant rule, -// but with a delay equivalent to the size of one packet, and potentially missed intermediate changes. +// this results in client side doesn't yet have appropriate data values to generate constant packet +// so it uses the default value instead. TEST_F(StreamingTest, PacketsIncorrectSequence) { SKIP_TEST_MAC_CI; std::vector data = {-1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5}; auto sampleCount = data.size(); + uint64_t defaultStartValue = 0x0; + auto constantDefaultValuePacket = ConstantDataPacketWithDomain(getNextDomainPacket(sampleCount), + testConstantSignal.getDescriptor(), + sampleCount, + defaultStartValue); + // init last value to be used as default start value of signal + testConstantSignal.asPtr().sendPacket(constantDefaultValuePacket); + auto server = std::make_shared(context); server->onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { auto signals = List(); @@ -361,31 +369,34 @@ TEST_F(StreamingTest, PacketsIncorrectSequence) std::this_thread::sleep_for(std::chrono::milliseconds(250)); // packets automatically generated by client - auto constantValuePacket2 = ConstantDataPacketWithDomain(domainPacket2, - testConstantSignal.getDescriptor(), - sampleCount, - 5); - - // packets automatically generated by client + auto clientConstValuePacket1 = ConstantDataPacketWithDomain(domainPacket1, + testConstantSignal.getDescriptor(), + sampleCount, + defaultStartValue); + auto clientConstValuePacket2 = ConstantDataPacketWithDomain(domainPacket2, + testConstantSignal.getDescriptor(), + sampleCount, + 5); auto clientConstValuePacket3 = ConstantDataPacketWithDomain(domainPacket3, testConstantSignal.getDescriptor(), sampleCount, 5); // 2 event packets + data packets: 3 domain and 6 value packets - ASSERT_EQ(receivedPackets.size(), 10u); + ASSERT_EQ(receivedPackets.size(), 11u); ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket1, receivedPackets[2])); ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket1, receivedPackets[3])); - // no constant packet generated since the signal value is unknown + // constant packet with default value generated since the signal data values are unknown + ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket1, receivedPackets[4])); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[4])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[5])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket2, receivedPackets[5])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket2, receivedPackets[6])); // contains only last change of constant - ASSERT_TRUE(BaseObjectPtr::Equals(constantValuePacket2, receivedPackets[6])); + ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket2, receivedPackets[7])); - ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[7])); - ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[8])); + ASSERT_TRUE(BaseObjectPtr::Equals(domainPacket3, receivedPackets[8])); + ASSERT_TRUE(BaseObjectPtr::Equals(explicitValuePacket3, receivedPackets[9])); // value update is not yet applied, provides old value - ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket3, receivedPackets[9])); + ASSERT_TRUE(BaseObjectPtr::Equals(clientConstValuePacket3, receivedPackets[10])); } From 1ac6dd1fe23c73b73b2efbb31ba2aec0fa8072c4 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:22:02 +0100 Subject: [PATCH 111/127] Logging improvement (openDAQ/openDAQ#721) --- ...websocket_streaming_client_module_impl.cpp | 10 ++++---- ...websocket_streaming_server_module_impl.cpp | 2 +- .../websocket_streaming/input_signal.h | 4 ++-- .../websocket_streaming/src/output_signal.cpp | 24 +++++++++---------- .../src/signal_descriptor_converter.cpp | 12 +++++----- .../src/streaming_client.cpp | 2 +- .../src/streaming_server.cpp | 20 +++++++--------- .../src/websocket_client_device_impl.cpp | 2 +- .../src/websocket_streaming_impl.cpp | 2 +- .../src/websocket_streaming_server.cpp | 4 ++-- 10 files changed, 39 insertions(+), 43 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index b9877c5..3902a1a 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -71,13 +71,13 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec const PropertyObjectPtr& config) { if (!connectionString.assigned()) - throw ArgumentNullException(); + DAQ_THROW_EXCEPTION(ArgumentNullException); if (!acceptsConnectionParameters(connectionString, config)) - throw InvalidParameterException(); + DAQ_THROW_EXCEPTION(InvalidParameterException); if (!context.assigned()) - throw InvalidParameterException{"Context is not available."}; + DAQ_THROW_EXCEPTION(InvalidParameterException, "Context is not available."); // We don't create any streaming objects here since the // internal streaming object is always created within the device @@ -138,10 +138,10 @@ bool WebsocketStreamingClientModule::acceptsStreamingConnectionParameters(const StreamingPtr WebsocketStreamingClientModule::onCreateStreaming(const StringPtr& connectionString, const PropertyObjectPtr& config) { if (!connectionString.assigned()) - throw ArgumentNullException(); + DAQ_THROW_EXCEPTION(ArgumentNullException); if (!acceptsStreamingConnectionParameters(connectionString, config)) - throw InvalidParameterException(); + DAQ_THROW_EXCEPTION(InvalidParameterException); const StringPtr str = formConnectionString(connectionString, config); return WebsocketStreaming(str, context); diff --git a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp index 80a54d6..a614496 100644 --- a/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp +++ b/modules/websocket_streaming_server_module/src/websocket_streaming_server_module_impl.cpp @@ -30,7 +30,7 @@ ServerPtr WebsocketStreamingServerModule::onCreateServer(const StringPtr& server const DevicePtr& rootDevice) { if (!context.assigned()) - throw InvalidParameterException{"Context parameter cannot be null."}; + DAQ_THROW_EXCEPTION(InvalidParameterException, "Context parameter cannot be null."); auto wsConfig = serverConfig; if (!wsConfig.assigned()) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 67fe8f2..1f9cc1e 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -197,7 +197,7 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, if (dataRuleType == daq::DataRuleType::Linear) return std::make_shared(signalId, tabledId, signalInfo, logCb); else - throw ConversionFailedException("Unsupported input domain signal rule"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input domain signal rule"); } else { @@ -206,7 +206,7 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, else if (dataRuleType == daq::DataRuleType::Constant) return std::make_shared(signalId, tabledId, signalInfo, domainSignal, logCb, constRuleStartValueMeta); else - throw ConversionFailedException("Unsupported input data signal rule"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input data signal rule"); } } diff --git a/shared/libraries/websocket_streaming/src/output_signal.cpp b/shared/libraries/websocket_streaming/src/output_signal.cpp index 25d1563..33d55a4 100644 --- a/shared/libraries/websocket_streaming/src/output_signal.cpp +++ b/shared/libraries/websocket_streaming/src/output_signal.cpp @@ -170,7 +170,7 @@ void OutputValueSignalBase::writeValueDescriptorChanges(const DataDescriptorPtr& } else { - throw ConversionFailedException("Unassigned value descriptor"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unassigned value descriptor"); } } @@ -196,7 +196,7 @@ void OutputValueSignalBase::writeDomainDescriptorChanges(const DataDescriptorPtr } else { - throw ConversionFailedException("Unassigned domain descriptor"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unassigned domain descriptor"); } } @@ -247,17 +247,17 @@ OutputDomainSignalBase::OutputDomainSignalBase(daq::streaming_protocol::BaseDoma void OutputDomainSignalBase::writeDaqDataPacket(const DataPacketPtr& packet) { - throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); + DAQ_THROW_EXCEPTION(InvalidOperationException, "Streaming-lt: explicit streaming of domain signals is not supported"); } void OutputDomainSignalBase::writeDomainDescriptorChanges(const DataDescriptorPtr& valueDescriptor) { - throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); + DAQ_THROW_EXCEPTION(InvalidOperationException, "Streaming-lt: explicit streaming of domain signals is not supported"); } void OutputDomainSignalBase::writeValueDescriptorChanges(const DataDescriptorPtr& domainDescriptor) { - throw InvalidOperationException("Streaming-lt: explicit streaming of domain signals is not supported"); + DAQ_THROW_EXCEPTION(InvalidOperationException, "Streaming-lt: explicit streaming of domain signals is not supported"); } uint64_t OutputDomainSignalBase::calcStartTimeOffset(uint64_t dataPacketTimeStamp) @@ -385,11 +385,11 @@ LinearTimeSignalPtr OutputLinearDomainSignal::createSignalStream( daq::SampleType daqSampleType = descriptor.getSampleType(); if (daqSampleType != daq::SampleType::Int64 && daqSampleType != daq::SampleType::UInt64) - throw InvalidParameterException("Unsupported domain signal sample type - only 64bit integer types are supported"); + DAQ_THROW_EXCEPTION(InvalidParameterException, "Unsupported domain signal sample type - only 64bit integer types are supported"); auto dataRule = descriptor.getRule(); if (dataRule.getType() != DataRuleType::Linear) - throw InvalidParameterException("Invalid domain signal data rule - linear rule only is supported"); + DAQ_THROW_EXCEPTION(InvalidParameterException, "Invalid domain signal data rule - linear rule only is supported"); auto unit = descriptor.getUnit(); if (!unit.assigned() || @@ -397,9 +397,9 @@ LinearTimeSignalPtr OutputLinearDomainSignal::createSignalStream( unit.getSymbol() != "s" || unit.getQuantity() != "time") { - throw InvalidParameterException( - "Domain signal unit parameters: {}, does not match the predefined values for linear time signal", - unit.assigned() ? unit.toString() : "not assigned"); + DAQ_THROW_EXCEPTION(InvalidParameterException, + "Domain signal unit parameters: {}, does not match the predefined values for linear time signal", + unit.assigned() ? unit.toString() : "not assigned"); } // from streaming library side, output rate is defined as nanoseconds between two samples @@ -470,7 +470,7 @@ BaseSynchronousSignalPtr OutputSyncValueSignal::createSignalStream( case daq::SampleType::RangeInt64: case daq::SampleType::Struct: default: - throw InvalidTypeException("Unsupported data signal sample type - only real numeric types are supported"); + DAQ_THROW_EXCEPTION(InvalidTypeException, "Unsupported data signal sample type - only real numeric types are supported"); } SignalDescriptorConverter::ToStreamedValueSignal(signal, syncStream, getSignalProps(signal)); @@ -598,7 +598,7 @@ BaseConstantSignalPtr OutputConstValueSignal::createSignalStream( case daq::SampleType::RangeInt64: case daq::SampleType::Struct: default: - throw InvalidTypeException("Unsupported data signal sample type - only real numeric types are supported"); + DAQ_THROW_EXCEPTION(InvalidTypeException, "Unsupported data signal sample type - only real numeric types are supported"); } SignalDescriptorConverter::ToStreamedValueSignal(signal, constStream, getSignalProps(signal)); diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 2259090..c381334 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -128,7 +128,7 @@ void SignalDescriptorConverter::ToStreamedValueSignal(const daq::SignalPtr& valu daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); if (requestedSampleType != valueStream->getSampleType()) - throw ConversionFailedException("Sample type has been changed"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Sample type has been changed"); UnitPtr unit = dataDescriptor.getUnit(); if (unit.assigned()) @@ -181,7 +181,7 @@ void SignalDescriptorConverter::ToStreamedLinearSignal(const daq::SignalPtr& dom daq::streaming_protocol::SampleType requestedSampleType = Convert(daqSampleType); if (requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_S64 && requestedSampleType != daq::streaming_protocol::SampleType::SAMPLETYPE_U64) - throw ConversionFailedException("Non-64bit domain sample types are not supported"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Non-64bit domain sample types are not supported"); DataRulePtr rule = domainDescriptor.getRule(); SetLinearTimeRule(rule, linearStream); @@ -235,7 +235,7 @@ daq::DataRulePtr SignalDescriptorConverter::GetRule(const daq::streaming_protoco } break; default: - throw ConversionFailedException("Unsupported data rule type"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported data rule type"); } } @@ -246,7 +246,7 @@ void SignalDescriptorConverter::SetLinearTimeRule(const daq::DataRulePtr& rule, { if (!rule.assigned() || rule.getType() != DataRuleType::Linear) { - throw ConversionFailedException("Time rule is not supported"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Time rule is not supported"); } uint64_t delta = rule.getParameters().get("delta"); linearStream->setOutputRate(delta); @@ -288,7 +288,7 @@ daq::SampleType SignalDescriptorConverter::Convert(daq::streaming_protocol::Samp case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD64: return daq::SampleType::UInt64; default: - throw ConversionFailedException("Unsupported input sample type"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input sample type"); } } @@ -341,7 +341,7 @@ daq::streaming_protocol::SampleType SignalDescriptorConverter::Convert(daq::Samp case daq::SampleType::String: case daq::SampleType::RangeInt64: default: - throw ConversionFailedException("Unsupported output sample type"); + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported output sample type"); } } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 73e44d4..99fbd78 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -501,7 +501,7 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig auto domainInputSignal = findTimeSignalByTableId(tableId); if (!domainInputSignal) - throw NotFoundException("Unknown domain signal for data signal {}, table {}", signalId, tableId); + DAQ_THROW_EXCEPTION(NotFoundException, "Unknown domain signal for data signal {}, table {}", signalId, tableId); InputSignalBasePtr inputSignal = nullptr; if (auto availableSigIt = availableSignals.find(signalId); availableSigIt != availableSignals.end()) diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index c45da4d..84e4960 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -23,7 +23,7 @@ StreamingServer::StreamingServer(const ContextPtr& context) , logger(context.getLogger()) { if (!this->logger.assigned()) - throw ArgumentNullException("Logger must not be null"); + DAQ_THROW_EXCEPTION(ArgumentNullException, "Logger must not be null"); loggerComponent = this->logger.getOrAddComponent("StreamingServer"); logCallback = [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); @@ -156,11 +156,9 @@ void StreamingServer::broadcastPacket(const std::string& signalId, const PacketP DataRuleType StreamingServer::getSignalRuleType(const SignalPtr& signal) { auto descriptor = signal.getDescriptor(); - if (!descriptor.assigned() || !descriptor.getRule().assigned()) - { - throw InvalidParameterException(R"(Signal "{}" has incomplete descriptor - unknown signal rule)", signal.getGlobalId()); - } - return descriptor.getRule().getType(); + if (descriptor.assigned() && descriptor.getRule().assigned()) + return descriptor.getRule().getType(); + DAQ_THROW_EXCEPTION(InvalidParameterException, R"(Signal "{}" has incomplete descriptor - unknown signal rule)", signal.getGlobalId()); } OutputDomainSignalBasePtr StreamingServer::addUpdateOrFindDomainSignal(const SignalPtr& domainSignal, @@ -185,7 +183,7 @@ OutputDomainSignalBasePtr StreamingServer::addUpdateOrFindDomainSignal(const Sig // find previously added complete output domain signal outputDomainSignal = std::dynamic_pointer_cast(outputSignal); if (!outputDomainSignal) - throw NoInterfaceException("Previously registered domain output signal {} is not of domain type", domainSignalId); + DAQ_THROW_EXCEPTION(NoInterfaceException, "Previously registered domain output signal {} is not of domain type", domainSignalId); } } else @@ -218,7 +216,7 @@ void StreamingServer::addToOutputSignals(const SignalPtr& signal, } else { - throw InvalidParameterException("Unsupported domain signal rule type - only domain signals with linear rule type are supported in LT-streaming"); + DAQ_THROW_EXCEPTION(InvalidParameterException, "Unsupported domain signal rule type - only domain signals with linear rule type are supported in LT-streaming"); } } else @@ -690,8 +688,7 @@ OutputDomainSignalBasePtr StreamingServer::createOutputDomainSignal(const Signal if (domainSignalRuleType == DataRuleType::Linear) return std::make_shared(writer, daqDomainSignal, tableId, logCallback); - else - throw InvalidParameterException("Unsupported domain signal rule type"); + DAQ_THROW_EXCEPTION(InvalidParameterException, "Unsupported domain signal rule type"); } OutputSignalBasePtr StreamingServer::createOutputValueSignal(const SignalPtr& daqSignal, @@ -705,8 +702,7 @@ OutputSignalBasePtr StreamingServer::createOutputValueSignal(const SignalPtr& da return std::make_shared(writer, daqSignal, outputDomainSignal, tableId, logCallback); else if (valueSignalRuleType == DataRuleType::Constant) return std::make_shared(writer, daqSignal, outputDomainSignal, tableId, logCallback); - else - throw InvalidParameterException("Unsupported value signal rule type"); + DAQ_THROW_EXCEPTION(InvalidParameterException, "Unsupported value signal rule type"); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp index 29e7b38..fb09862 100644 --- a/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_client_device_impl.cpp @@ -17,7 +17,7 @@ WebsocketClientDeviceImpl::WebsocketClientDeviceImpl(const ContextPtr& ctx, , connectionString(connectionString) { if (!this->connectionString.assigned()) - throw ArgumentNullException("connectionString cannot be null"); + DAQ_THROW_EXCEPTION(ArgumentNullException, "connectionString cannot be null"); this->name = "WebsocketClientPseudoDevice"; createWebsocketStreaming(); activateStreaming(); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp index 34f4b8d..b30fff5 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_impl.cpp @@ -23,7 +23,7 @@ WebsocketStreamingImpl::WebsocketStreamingImpl(StreamingClientPtr streamingClien { prepareStreamingClient(); if (!this->streamingClient->connect()) - throw NotFoundException("Failed to connect to streaming server url: {}", connectionString); + DAQ_THROW_EXCEPTION(NotFoundException, "Failed to connect to streaming server url: {}", connectionString); } void WebsocketStreamingImpl::onSetActive(bool active) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 0c11910..9436d7f 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -45,9 +45,9 @@ void WebsocketStreamingServer::setControlPort(uint16_t port) void WebsocketStreamingServer::start() { if (!device.assigned()) - throw InvalidStateException("Device is not set."); + DAQ_THROW_EXCEPTION(InvalidStateException, "Device is not set."); if (!context.assigned()) - throw InvalidStateException("Context is not set."); + DAQ_THROW_EXCEPTION(InvalidStateException, "Context is not set."); if (streamingPort == 0 || controlPort == 0) return; From 0ed78c408dc906af8ce7db91bf1c71671bfa07e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20R=C3=BChmer?= <108338550+Daniel-BB@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:53:11 +0200 Subject: [PATCH 112/127] Replaced WORKING_DIRECTORY by full path for all tests (openDAQ/openDAQ#756) --- modules/websocket_streaming_client_module/tests/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/tests/CMakeLists.txt | 2 +- shared/libraries/websocket_streaming/tests/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket_streaming_client_module/tests/CMakeLists.txt b/modules/websocket_streaming_client_module/tests/CMakeLists.txt index 89d9447..a251317 100644 --- a/modules/websocket_streaming_client_module/tests/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/tests/CMakeLists.txt @@ -14,7 +14,7 @@ target_link_libraries(${TEST_APP} PRIVATE daq::test_utils add_test(NAME ${TEST_APP} COMMAND $ - WORKING_DIRECTORY bin + WORKING_DIRECTORY $ ) if (OPENDAQ_ENABLE_COVERAGE) diff --git a/modules/websocket_streaming_server_module/tests/CMakeLists.txt b/modules/websocket_streaming_server_module/tests/CMakeLists.txt index 204f350..02df280 100644 --- a/modules/websocket_streaming_server_module/tests/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/tests/CMakeLists.txt @@ -16,7 +16,7 @@ target_link_libraries(${TEST_APP} PRIVATE daq::test_utils add_test(NAME ${TEST_APP} COMMAND $ - WORKING_DIRECTORY bin + WORKING_DIRECTORY $ ) if (MSVC) # Ignoring warning for the Taskflow diff --git a/shared/libraries/websocket_streaming/tests/CMakeLists.txt b/shared/libraries/websocket_streaming/tests/CMakeLists.txt index d072437..3f63dc5 100644 --- a/shared/libraries/websocket_streaming/tests/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/tests/CMakeLists.txt @@ -27,7 +27,7 @@ target_include_directories(${TEST_APP} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/incl add_test(NAME ${TEST_APP} COMMAND $ - WORKING_DIRECTORY bin + WORKING_DIRECTORY $ ) if(OPENDAQ_ENABLE_COVERAGE) From a563f534f8013bd25d5f76bb213b711e0621c397 Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Mon, 7 Apr 2025 00:36:22 -0700 Subject: [PATCH 113/127] Fix IPv6 addresses discovering on windows (openDAQ/openDAQ#751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix parsing IPv6 addresses * modify regex parsing according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 Co-authored-by: Aljaž Frančič * Fix linklocal suffix for discovered IPv6 addresses on Windows * Make connection string parsing regex static * Update changelog * Fix LT pseudo-device IPv6 connection info * add tests for device connection info using IPv6 connection --------- Co-authored-by: Aljaž Frančič --- ...websocket_streaming_client_module_impl.cpp | 21 ++++++++++++------- .../src/streaming_client.cpp | 12 +++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 3902a1a..275d228 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -17,6 +17,9 @@ static std::string OldWebsocketDeviceTypeId = "OpenDAQLTStreamingOld"; static std::string WebsocketDevicePrefix = "daq.lt"; static std::string OldWebsocketDevicePrefix = "daq.ws"; +static const std::regex RegexIpv6Hostname(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?\])(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv4Hostname(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + using namespace discovery; using namespace daq::websocket_streaming; @@ -95,8 +98,15 @@ DevicePtr WebsocketStreamingClientModule::onCreateDevice(const StringPtr& connec auto port = -1; { std::smatch match; - auto regexpConnectionString = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); - if (std::regex_search(str, match, regexpConnectionString)) + + bool parsed = false; + parsed = std::regex_search(str, match, RegexIpv6Hostname); + if (!parsed) + { + parsed = std::regex_search(str, match, RegexIpv4Hostname); + } + + if (parsed) { host = match[2].str(); port = 7414; @@ -250,9 +260,6 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& return connectionString; std::string urlString = connectionString.toStdString(); - - auto regexIpv6Hostname = std::regex(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9]+)?\])(?::(\d+))?(/.*)?$)"); - auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); std::smatch match; std::string host = ""; @@ -260,10 +267,10 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& std::string path = "/"; bool parsed = false; - parsed = std::regex_search(urlString, match, regexIpv6Hostname); + parsed = std::regex_search(urlString, match, RegexIpv6Hostname); if (!parsed) { - parsed = std::regex_search(urlString, match, regexIpv4Hostname); + parsed = std::regex_search(urlString, match, RegexIpv4Hostname); } if (parsed) diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 99fbd78..c411660 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -14,6 +14,10 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING using namespace daq::stream; using namespace daq::streaming_protocol; +// parsing connection string to four groups: prefix, host, port, path +static const std::regex RegexIpv6Hostname(R"(^(.*://)?(?:\[([a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?)\])(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv4Hostname(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection) : logger(context.getLogger()) , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) @@ -242,14 +246,10 @@ void StreamingClient::parseConnectionString(const std::string& url) std::smatch match; - // parsing connection string to four groups: prefix, host, port, path - auto regexIpv6Hostname = std::regex(R"(^(.*://)?(?:\[([a-fA-F0-9:]+(?:\%[a-zA-Z0-9]+)?)\])(?::(\d+))?(/.*)?$)"); - auto regexIpv4Hostname = std::regex(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); - bool parsed = false; - parsed = std::regex_search(url, match, regexIpv6Hostname); + parsed = std::regex_search(url, match, RegexIpv6Hostname); if (!parsed) - parsed = std::regex_search(url, match, regexIpv4Hostname); + parsed = std::regex_search(url, match, RegexIpv4Hostname); if (parsed) { From 370fa6cbb09ad7c756b1c3d315a85e266f9efd22 Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Thu, 10 Apr 2025 00:26:43 -0700 Subject: [PATCH 114/127] Provide list of connected clients via DeviceInfo (openDAQ/openDAQ#730) * Provide host name for native client * Fix redundant dropping of streaming connections when exclusive control client connected * Fix property add and remove core events for DeviceInfo via config protocol * Provide backward compatibility for older native config clients * Switch to libNativeStreaming tag v1.0.18 * Make property removing thread-safe * Add module helper method to populate discovered device info properties --- ...websocket_streaming_client_module_impl.cpp | 10 +---- .../websocket_streaming/streaming_server.h | 6 +++ .../websocket_streaming_server.h | 1 + .../src/streaming_server.cpp | 16 +++++++ .../src/websocket_streaming_server.cpp | 42 ++++++++++++++++--- .../tests/test_streaming.cpp | 37 ++++++++++++++++ 6 files changed, 98 insertions(+), 14 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 275d228..db90c97 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -8,7 +8,6 @@ #include #include #include -#include BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE @@ -289,9 +288,6 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const MdnsDiscoveredDevice& discoveredDevice) { - PropertyObjectPtr deviceInfo = DeviceInfo(""); - DiscoveryClient::populateDiscoveredInfoProperties(deviceInfo, discoveredDevice); - auto cap = ServerCapability(WebsocketDeviceTypeId, "OpenDAQLTStreaming", ProtocolType::Streaming); if (!discoveredDevice.ipv4Address.empty()) @@ -335,11 +331,7 @@ DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const Mdn if (discoveredDevice.servicePort > 0) cap.setPort(discoveredDevice.servicePort); - deviceInfo.asPtr().addServerCapability(cap); - deviceInfo.asPtr().setProtectedPropertyValue("connectionString", cap.getConnectionString()); - deviceInfo.asPtr().setDeviceType(createWebsocketDeviceType(false)); - - return deviceInfo; + return populateDiscoveredDeviceInfo(DiscoveryClient::populateDiscoveredInfoProperties, discoveredDevice, cap, createWebsocketDeviceType(false)); } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING_CLIENT_MODULE diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h index 63e7484..e83ea9e 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_server.h @@ -44,6 +44,8 @@ class StreamingServer using OnAcceptCallback = std::function(const daq::streaming_protocol::StreamWriterPtr& writer)>; using OnStartSignalsReadCallback = std::function& signal)>; using OnStopSignalsReadCallback = std::function& signal)>; + using OnClientConnectedCallback = std::function; + using OnClientDisconnectedCallback = std::function; StreamingServer(const ContextPtr& context); ~StreamingServer(); @@ -55,6 +57,8 @@ class StreamingServer void onAccept(const OnAcceptCallback& callback); void onStartSignalsRead(const OnStartSignalsReadCallback& callback); void onStopSignalsRead(const OnStopSignalsReadCallback& callback); + void onClientConnected(const OnClientConnectedCallback& callback); + void onClientDisconnected(const OnClientDisconnectedCallback& callback); void broadcastPacket(const std::string& signalId, const PacketPtr& packet); @@ -129,6 +133,8 @@ class StreamingServer OnAcceptCallback onAcceptCallback; OnStartSignalsReadCallback onStartSignalsReadCallback; OnStopSignalsReadCallback onStopSignalsReadCallback; + OnClientConnectedCallback clientConnectedHandler; + OnClientDisconnectedCallback clientDisconnectedHandler; LoggerPtr logger; LoggerComponentPtr loggerComponent; daq::streaming_protocol::LogCallback logCallback; diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h index 468dcd6..ae04828 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/websocket_streaming_server.h @@ -48,6 +48,7 @@ class WebsocketStreamingServer daq::websocket_streaming::StreamingServer streamingServer; daq::websocket_streaming::AsyncPacketReader packetReader; LoggerComponentPtr loggerComponent; + std::unordered_map registeredClientIds; private: static DictPtr getSignalsOfComponent(ComponentPtr& component); diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 84e4960..0c94472 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -121,6 +121,16 @@ void StreamingServer::onStopSignalsRead(const OnStopSignalsReadCallback& callbac onStopSignalsReadCallback = callback; } +void StreamingServer::onClientConnected(const OnClientConnectedCallback& callback) +{ + clientConnectedHandler = callback; +} + +void StreamingServer::onClientDisconnected(const OnClientDisconnectedCallback& callback) +{ + clientDisconnectedHandler = callback; +} + void StreamingServer::broadcastPacket(const std::string& signalId, const PacketPtr& packet) { std::scoped_lock lock(sync); @@ -271,6 +281,9 @@ void StreamingServer::removeClient(const std::string& clientId) { LOG_I("client with id {} disconnected", clientId); + if (clientDisconnectedHandler) + clientDisconnectedHandler(clientId); + auto signalsToStopRead = List(); { std::scoped_lock lock(sync); @@ -302,6 +315,9 @@ void StreamingServer::onAcceptInternal(const daq::stream::StreamPtr& stream) auto clientId = stream->endPointUrl(); LOG_I("New client connected. Stream Id: {}", clientId); + + if (clientConnectedHandler) + clientConnectedHandler(clientId, stream->remoteHost()); { std::scoped_lock lock(sync); clients.insert({clientId, {writer, std::unordered_map()}}); diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 9436d7f..994d326 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -54,6 +54,32 @@ void WebsocketStreamingServer::start() streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignals(search::Recursive(search::Any())); }); streamingServer.onStartSignalsRead([this](const ListPtr& signals) { packetReader.startReadSignals(signals); } ); streamingServer.onStopSignalsRead([this](const ListPtr& signals) { packetReader.stopReadSignals(signals); } ); + streamingServer.onClientConnected( + [this](const std::string& clientId, const std::string& address) + { + SizeT clientNumber = 0; + if (device.assigned() && !device.isRemoved()) + { + device.getInfo().asPtr(true).addConnectedClient( + &clientNumber, + ConnectedClientInfo(address, ProtocolType::Streaming, "OpenDAQLTStreaming", "", "")); + } + registeredClientIds.insert({clientId, clientNumber}); + } + ); + streamingServer.onClientDisconnected( + [this](const std::string& clientId) + { + if (auto it = registeredClientIds.find(clientId); it != registeredClientIds.end()) + { + if (device.assigned() && !device.isRemoved() && it->second != 0) + { + device.getInfo().asPtr(true).removeConnectedClient(it->second); + } + registeredClientIds.erase(it); + } + } + ); streamingServer.start(streamingPort, controlPort); packetReader.setLoopFrequency(50); @@ -75,13 +101,19 @@ void WebsocketStreamingServer::start() void WebsocketStreamingServer::stop() { - if (this->device.assigned()) + if (device.assigned() && !device.isRemoved()) { - const auto info = this->device.getInfo(); - const auto infoInternal = info.asPtr(); - if (info.hasServerCapability("OpenDAQLTStreaming")) - infoInternal.removeServerCapability("OpenDAQLTStreaming"); + const auto info = this->device.getInfo(); + const auto infoInternal = info.asPtr(); + if (info.hasServerCapability("OpenDAQLTStreaming")) + infoInternal.removeServerCapability("OpenDAQLTStreaming"); + for (const auto& [_, clientNumber] : registeredClientIds) + { + if (clientNumber != 0) + infoInternal.removeConnectedClient(clientNumber); + } } + registeredClientIds.clear(); stopInternal(); } diff --git a/shared/libraries/websocket_streaming/tests/test_streaming.cpp b/shared/libraries/websocket_streaming/tests/test_streaming.cpp index 6997fdb..a0f7700 100644 --- a/shared/libraries/websocket_streaming/tests/test_streaming.cpp +++ b/shared/libraries/websocket_streaming/tests/test_streaming.cpp @@ -63,6 +63,43 @@ TEST_F(StreamingTest, ConnectAndDisconnect) ASSERT_FALSE(client.isConnected()); } +TEST_F(StreamingTest, ClientConnectDisconnectCallbacks) +{ + auto server = std::make_shared(context); + + std::string clientId; + bool clientConnected{false}; + server->onClientConnected( + [&clientId, &clientConnected](const std::string& id, const std::string& address) + { + ASSERT_NE(address, ""); + clientConnected = true; + clientId = id; + } + ); + std::promise clientDisconnectedPromise; + std::future clientDisconnectedFuture = clientDisconnectedPromise.get_future(); + server->onClientDisconnected( + [&clientId, &clientConnected, &clientDisconnectedPromise](const std::string& id) + { + if (clientConnected && id == clientId) + clientDisconnectedPromise.set_value(true); + } + ); + + server->start(StreamingPort, ControlPort); + + auto client = std::make_shared(context, "127.0.0.1", StreamingPort, StreamingTarget); + + client->connect(); + ASSERT_TRUE(clientConnected); + ASSERT_NE(clientId, ""); + + client.reset(); + ASSERT_EQ(clientDisconnectedFuture.wait_for(std::chrono::milliseconds(1000)), std::future_status::ready); + ASSERT_TRUE(clientDisconnectedFuture.get()); +} + TEST_F(StreamingTest, StopServer) { auto server = std::make_shared(context); From d9611dd75ba271bdcd49b583631d2b237f071344 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Fri, 11 Apr 2025 08:44:47 +0200 Subject: [PATCH 115/127] Merge discovery info and device info based on SN and manufacturer (openDAQ/openDAQ#757) --- ...websocket_streaming_client_module_impl.cpp | 36 +++++++++++-------- .../src/streaming_client.cpp | 4 +-- .../src/websocket_streaming_server.cpp | 9 +++-- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index db90c97..68d32c3 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -16,8 +16,8 @@ static std::string OldWebsocketDeviceTypeId = "OpenDAQLTStreamingOld"; static std::string WebsocketDevicePrefix = "daq.lt"; static std::string OldWebsocketDevicePrefix = "daq.ws"; -static const std::regex RegexIpv6Hostname(R"(^(.*://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?\])(?::(\d+))?(/.*)?$)"); -static const std::regex RegexIpv4Hostname(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv6Hostname(R"(^(.+://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?\])(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv4Hostname(R"(^(.+://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); using namespace discovery; using namespace daq::websocket_streaming; @@ -161,9 +161,6 @@ Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapa if (target.getProtocolId() != "OpenDAQLTStreaming") return false; - if (target.getConnectionString().getLength() != 0) - return true; - if (source.getConnectionType() != "TCP/IP") return false; @@ -189,14 +186,20 @@ Bool WebsocketStreamingClientModule::onCompleteServerCapability(const ServerCapa } const auto path = target.hasProperty("Path") ? target.getPropertyValue("Path") : ""; + const auto targetAddress = target.getAddresses(); for (const auto& addrInfo : addrInfos) { const auto address = addrInfo.getAddress(); - const auto prefix = WebsocketDevicePrefix; - StringPtr connectionString = createUrlConnectionString(address, port,path); - + if (auto it = std::find(targetAddress.begin(), targetAddress.end(), address); it != targetAddress.end()) + continue; + + StringPtr connectionString; + if (source.getPrefix() == target.getPrefix()) + connectionString = addrInfo.getConnectionString(); + else + connectionString = createUrlConnectionString(address, port, path); const auto targetAddrInfo = AddressInfoBuilder() - .setAddress(addrInfo.getAddress()) + .setAddress(address) .setReachabilityStatus(addrInfo.getReachabilityStatus()) .setType(addrInfo.getType()) .setConnectionString(connectionString) @@ -251,12 +254,9 @@ PropertyObjectPtr WebsocketStreamingClientModule::createDefaultConfig() StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& connectionString, const PropertyObjectPtr& config) { - if (!config.assigned() || !config.hasProperty("Port")) - return connectionString; - - int port = config.getPropertyValue("Port"); - if (port == 7414) - return connectionString; + int port = 7414; + if (config.assigned() && config.hasProperty("Port")) + port = config.getPropertyValue("Port"); std::string urlString = connectionString.toStdString(); std::smatch match; @@ -276,7 +276,13 @@ StringPtr WebsocketStreamingClientModule::formConnectionString(const StringPtr& { prefix = match[1]; host = match[2]; + + if (match[3].matched) + port = std::stoi(match[3]); + if (port == 7414) + return connectionString; + if (match[4].matched) path = match[4]; diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index c411660..5b87ff4 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -15,8 +15,8 @@ using namespace daq::stream; using namespace daq::streaming_protocol; // parsing connection string to four groups: prefix, host, port, path -static const std::regex RegexIpv6Hostname(R"(^(.*://)?(?:\[([a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?)\])(?::(\d+))?(/.*)?$)"); -static const std::regex RegexIpv4Hostname(R"(^(.*://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv6Hostname(R"(^(.+://)?(?:\[([a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?)\])(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv4Hostname(R"(^(.+://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection) : logger(context.getLogger()) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index 994d326..d2ccdfc 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -51,6 +51,10 @@ void WebsocketStreamingServer::start() if (streamingPort == 0 || controlPort == 0) return; + auto info = this->device.getInfo(); + if (info.hasServerCapability("OpenDAQLTStreaming")) + DAQ_THROW_EXCEPTION(InvalidStateException, fmt::format("Device \"{}\" already has an OpenDAQLTStreaming server capability.", info.getName())); + streamingServer.onAccept([this](const daq::streaming_protocol::StreamWriterPtr& writer) { return device.getSignals(search::Recursive(search::Any())); }); streamingServer.onStartSignalsRead([this](const ListPtr& signals) { packetReader.startReadSignals(signals); } ); streamingServer.onStopSignalsRead([this](const ListPtr& signals) { packetReader.stopReadSignals(signals); } ); @@ -83,7 +87,8 @@ void WebsocketStreamingServer::start() streamingServer.start(streamingPort, controlPort); packetReader.setLoopFrequency(50); - packetReader.onPacket([this](const SignalPtr& signal, const ListPtr& packets) { + packetReader.onPacket([this](const SignalPtr& signal, const ListPtr& packets) + { const auto signalId = signal.getGlobalId(); for (const auto& packet : packets) streamingServer.broadcastPacket(signalId, packet); @@ -96,7 +101,7 @@ void WebsocketStreamingServer::start() serverCapability.setPrefix("daq.lt"); serverCapability.setPort(streamingPort); serverCapability.setConnectionType("TCP/IP"); - this->device.getInfo().asPtr().addServerCapability(serverCapability); + info.asPtr(true).addServerCapability(serverCapability); } void WebsocketStreamingServer::stop() From 5fc0ffdff7c3cce17ea2b09e8f4220be6f7b1027 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Sat, 26 Apr 2025 07:02:03 +0200 Subject: [PATCH 116/127] Improving device operation mode (openDAQ/openDAQ#771) --- .../tests/test_signal_descriptor_converter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 7c61f39..5ea5417 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -262,7 +262,7 @@ TEST(SignalConverter, subscribedDataSignal) signalParams[bsp::META_DEFINITION][bsp::META_POSTSCALING][bsp::META_POFFSET] = 3.0; std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(signalParams); - nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(msgpack); + nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(std::vector(msgpack.begin(), msgpack.end())); result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); @@ -317,7 +317,7 @@ TEST(SignalConverter, subscribedBitfieldSignal) signalParams[bsp::META_DEFINITION][bsp::META_RULE] = bsp::META_RULETYPE_CONSTANT; std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(signalParams); - nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(msgpack); + nlohmann::json signalParamsToParse = nlohmann::json::from_msgpack(std::vector(msgpack.begin(), msgpack.end())); result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); @@ -383,7 +383,7 @@ TEST(SignalConverter, subscribedTimeSignal) timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(timeSignalParams); - nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(msgpack); + nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(std::vector(msgpack.begin(), msgpack.end())); result = subscribedSignal.processSignalMetaInformation(method, timeSignalParamsToParse); ASSERT_EQ(result, 0); @@ -458,7 +458,7 @@ TEST(SignalConverter, FloatDeltaOfLinearSignal) timeSignalParams[bsp::META_DEFINITION][bsp::META_RESOLUTION][bsp::META_DENOMINATOR] = ticksPerSecond; std::vector < uint8_t > msgpack = nlohmann::json::to_msgpack(timeSignalParams); - nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(msgpack); + nlohmann::json timeSignalParamsToParse = nlohmann::json::from_msgpack(std::vector(msgpack.begin(), msgpack.end())); result = subscribedSignal.processSignalMetaInformation(method, timeSignalParamsToParse); ASSERT_EQ(result, 0); From 60f70753b0ebb4d99c7462d2c63189769e9c7133 Mon Sep 17 00:00:00 2001 From: David Norris Date: Mon, 5 May 2025 11:32:41 +0200 Subject: [PATCH 117/127] high-performance WebSocket streaming server (openDAQ/openDAQ#743) * new WebSocket streaming server module * wip * add docs * wip: CAN signals working except time * wip: disable debug logging * fix tests * cleanup * streaming-protocol-lt: 1.2.4 * add required Boost::asio to cmake * Fixes for building on Windows. * new ws streaming module in changelog * ignore httpparser license headers * Replace cryptopp with Boost for SHA1; cryptopp doesn't support clang * new-ws-streaming fixes for windows 32-bit --- external/streaming_protocol/CMakeLists.txt | 4 +- .../websocket_streaming/input_signal.h | 12 +++ .../signal_descriptor_converter.h | 5 +- .../websocket_streaming/streaming_client.h | 5 +- .../websocket_streaming/src/input_signal.cpp | 30 ++++-- .../src/signal_descriptor_converter.cpp | 93 ++++++++++++++++++- .../src/streaming_client.cpp | 57 ++++++++---- .../test_signal_descriptor_converter.cpp | 10 +- 8 files changed, 182 insertions(+), 34 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 04d644b..2d74bc0 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.2 + REQUIRED_VERSION 1.2.4 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.2.2 + GIT_REF 3b56a7a066b059e7eb1692de5f6704815200a222 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h index 1f9cc1e..2c44004 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/input_signal.h @@ -45,6 +45,7 @@ class InputSignalBase virtual void processSamples(const NumberPtr& startDomainValue, const uint8_t* data, size_t sampleCount); virtual DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) = 0; virtual bool isDomainSignal() const = 0; @@ -64,6 +65,9 @@ class InputSignalBase void setSubscribed(bool subscribed); bool getSubscribed(); + const DataPacketPtr& getLastPacket() const noexcept; + void setLastPacket(const DataPacketPtr& packet); + protected: const std::string signalId; const std::string tableId; @@ -76,6 +80,8 @@ class InputSignalBase daq::streaming_protocol::LogCallback logCallback; mutable std::mutex descriptorsSync; bool subscribed; + + daq::DataPacketPtr lastPacket; }; /// Used as a placeholder for uninitialized or incomplete signals which aren't supported by LT-streaming @@ -90,6 +96,7 @@ class InputNullSignal : public InputSignalBase DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) override; bool isDomainSignal() const override; @@ -106,6 +113,7 @@ class InputDomainSignal : public InputSignalBase DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) override; bool isDomainSignal() const override; @@ -126,6 +134,7 @@ class InputExplicitDataSignal : public InputSignalBase DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) override; bool isDomainSignal() const override; @@ -148,6 +157,7 @@ class InputConstantDataSignal : public InputSignalBase void processSamples(const NumberPtr& absoluteStartDomainValue, const uint8_t* data, size_t sampleCount) override; DataPacketPtr generateDataPacket(const NumberPtr& packetOffset, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) override; bool isDomainSignal() const override; @@ -196,6 +206,8 @@ inline InputSignalBasePtr InputSignal(const std::string& signalId, { if (dataRuleType == daq::DataRuleType::Linear) return std::make_shared(signalId, tabledId, signalInfo, logCb); + if (dataRuleType == daq::DataRuleType::Explicit) + return std::make_shared(signalId, tabledId, signalInfo, nullptr, logCb); else DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input domain signal rule"); } diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index 76b2707..0c30d77 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -34,7 +34,9 @@ class SignalDescriptorConverter * @param subscribedSignal The object holding everything about thee signal on the consumer side * @throws ConversionFailedException */ - static SubscribedSignalInfo ToDataDescriptor(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + static SubscribedSignalInfo ToDataDescriptor( + const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + const daq::ContextPtr& context); /** * @throws ConversionFailedException */ @@ -51,6 +53,7 @@ class SignalDescriptorConverter static daq::DataRulePtr GetRule(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); static void SetLinearTimeRule(const daq::DataRulePtr& rule, daq::streaming_protocol::LinearTimeSignalPtr linearStream); static daq::SampleType Convert(daq::streaming_protocol::SampleType dataType); + static daq::SampleType ConvertSampleTypeString(const std::string& sampleType); static daq::streaming_protocol::SampleType Convert(daq::SampleType sampleType); static daq::RangePtr CreateDefaultRange(daq::SampleType sampleType); static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptorBuilder); diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h index d757c1b..0ab4e31 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/streaming_client.h @@ -91,8 +91,8 @@ class StreamingClient const nlohmann::json& params); void onProtocolMeta(daq::streaming_protocol::ProtocolHandler& protocolHandler, const std::string& method, const nlohmann::json& params); void onMessage(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, uint64_t timeStamp, const uint8_t* data, size_t valueCount); - void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); - void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal); + void setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const ContextPtr& context); + void setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const ContextPtr& context); void publishSignalChanges(const InputSignalBasePtr& signal, bool valueChanged, bool domainChanged); void onSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal, const nlohmann::json& params); void setSignalInitSatisfied(const std::string& signalId); @@ -102,6 +102,7 @@ class StreamingClient void unavailableSignalsHandler(const nlohmann::json::const_iterator& unavailableSignalsArray); void availableSignalsHandler(const nlohmann::json::const_iterator& availableSignalsArray); + ContextPtr context; LoggerPtr logger; LoggerComponentPtr loggerComponent; daq::streaming_protocol::LogCallback logCallback; diff --git a/shared/libraries/websocket_streaming/src/input_signal.cpp b/shared/libraries/websocket_streaming/src/input_signal.cpp index 3927d81..09a6352 100644 --- a/shared/libraries/websocket_streaming/src/input_signal.cpp +++ b/shared/libraries/websocket_streaming/src/input_signal.cpp @@ -99,6 +99,16 @@ bool InputSignalBase::getSubscribed() return subscribed; } +const DataPacketPtr& InputSignalBase::getLastPacket() const noexcept +{ + return lastPacket; +} + +void InputSignalBase::setLastPacket(const DataPacketPtr& packet) +{ + lastPacket = packet; +} + InputDomainSignal::InputDomainSignal(const std::string& signalId, const std::string& tabledId, const SubscribedSignalInfo& signalInfo, @@ -109,6 +119,7 @@ InputDomainSignal::InputDomainSignal(const std::string& signalId, DataPacketPtr InputDomainSignal::generateDataPacket(const NumberPtr& packetOffset, const uint8_t* /*data*/, + size_t dataSize, size_t sampleCount, const DataPacketPtr& /*domainPacket*/) { @@ -143,24 +154,27 @@ InputExplicitDataSignal::InputExplicitDataSignal(const std::string& signalId, DataPacketPtr InputExplicitDataSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* data, + size_t dataSize, size_t sampleCount, const DataPacketPtr& domainPacket) { std::scoped_lock lock(descriptorsSync); - auto sampleType = currentDataDescriptor.getSampleType(); - if (currentDataDescriptor.getPostScaling().assigned()) - sampleType = currentDataDescriptor.getPostScaling().getInputSampleType(); - auto dataPacket = DataPacketWithDomain(domainPacket, currentDataDescriptor, sampleCount); - const auto sampleSize = getSampleSize(sampleType); - std::memcpy(dataPacket.getRawData(), data, sampleCount * sampleSize); + if (dataSize == dataPacket.getRawDataSize()) + std::memcpy(dataPacket.getRawData(), data, dataSize); + else + STREAMING_PROTOCOL_LOG_E("Provided streaming protocol packet data for signal {} has the wrong size: {} instead of {} (sample count: {})", + signalId, + dataSize, + dataPacket.getRawDataSize(), + sampleCount); return dataPacket; } bool InputExplicitDataSignal::isDomainSignal() const { - return false; + return !inputDomainSignal; } bool InputExplicitDataSignal::isCountable() const @@ -299,6 +313,7 @@ InputConstantDataSignal::CachedSignalValues::iterator InputConstantDataSignal::i DataPacketPtr InputConstantDataSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* /*data*/, + size_t /*dataSize*/, size_t sampleCount, const DataPacketPtr& domainPacket) { @@ -485,6 +500,7 @@ bool InputNullSignal::hasDescriptors() const DataPacketPtr InputNullSignal::generateDataPacket(const NumberPtr& /*packetOffset*/, const uint8_t* /*data*/, + size_t /*dataSize*/, size_t /*sampleCount*/, const DataPacketPtr& /*domainPacket*/) { diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index c381334..07cb5b5 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -25,7 +26,9 @@ BEGIN_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING /** * @todo Only scalar values are supported for now. No structs. No dimensions. */ -SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor( + const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + const daq::ContextPtr& context) { SubscribedSignalInfo sInfo; auto dataDescriptorBuilder = DataDescriptorBuilder(); @@ -46,6 +49,63 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor(const daq::stre daq::SampleType daqSampleType = Convert(streamingSampleType); dataDescriptorBuilder.setSampleType(daqSampleType); + if (daqSampleType == daq::SampleType::Struct) + { + const auto& details = subscribedSignal.datatypeDetails(); + auto fields = List(); + + auto fieldNames = List(); + auto fieldTypes = List(); + + for (const auto& field : details) + { + auto fieldBuilder = DataDescriptorBuilder(); + fieldBuilder.setName(field.at("name")); + fieldBuilder.setSampleType(ConvertSampleTypeString(field.value("dataType", ""))); + fieldNames.pushBack(fieldBuilder.getName()); + + if (field.count("dimensions") > 0) + { + auto daqDimensions = List(); + fieldTypes.pushBack(SimpleType(daq::CoreType::ctList)); + + auto dimensions = field["dimensions"]; + for (const auto& dimension : dimensions) + { + if (dimension.at("rule") != "linear") + DAQ_THROW_EXCEPTION(ConversionFailedException, "Struct has field with unsupported dimension"); + + daqDimensions.pushBack( + DimensionBuilder() + .setName(dimension.at("name")) + .setRule(LinearDimensionRule( + static_cast(dimension.at("linear").at("delta")), + static_cast(dimension.at("linear").at("start")), + static_cast(dimension.at("linear").at("size")))) + .build()); + } + + fieldBuilder.setDimensions(daqDimensions); + } + + else + { + fieldTypes.pushBack(SimpleType(daq::CoreType::ctInt)); + } + + fields.pushBack(fieldBuilder.build()); + } + + dataDescriptorBuilder.setStructFields(fields); + + context.getTypeManager().addType( + StructType( + "CAN", // dataDescriptorBuilder.getName(), // XXX TODO + fieldNames, + fieldTypes + )); + } + sInfo.signalName = subscribedSignal.memberName(); if (subscribedSignal.unitId() != daq::streaming_protocol::Unit::UNIT_ID_NONE) @@ -287,11 +347,42 @@ daq::SampleType SignalDescriptorConverter::Convert(daq::streaming_protocol::Samp return daq::SampleType::UInt32; case daq::streaming_protocol::SampleType::SAMPLETYPE_BITFIELD64: return daq::SampleType::UInt64; + case daq::streaming_protocol::SampleType::SAMPLETYPE_STRUCT: + return daq::SampleType::Struct; default: DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input sample type"); } } +/** + * @throws ConversionFailedException + */ +daq::SampleType SignalDescriptorConverter::ConvertSampleTypeString(const std::string& sampleType) +{ + if (sampleType == "uint8") + return daq::SampleType::UInt8; + if (sampleType == "uint16") + return daq::SampleType::UInt16; + if (sampleType == "uint32") + return daq::SampleType::UInt32; + if (sampleType == "uint64") + return daq::SampleType::UInt64; + if (sampleType == "int8") + return daq::SampleType::Int8; + if (sampleType == "int16") + return daq::SampleType::Int16; + if (sampleType == "int32") + return daq::SampleType::Int32; + if (sampleType == "int64") + return daq::SampleType::Int64; + if (sampleType == "real32") + return daq::SampleType::Float32; + if (sampleType == "real64") + return daq::SampleType::Float64; + + DAQ_THROW_EXCEPTION(ConversionFailedException, "Unsupported input sample type"); +} + /** * @throws ConversionFailedException */ diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index 5b87ff4..c2bc172 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -19,7 +19,8 @@ static const std::regex RegexIpv6Hostname(R"(^(.+://)?(?:\[([a-fA-F0-9:]+(?:\%[a static const std::regex RegexIpv4Hostname(R"(^(.+://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); StreamingClient::StreamingClient(const ContextPtr& context, const std::string& connectionString, bool useRawTcpConnection) - : logger(context.getLogger()) + : context(context) + , logger(context.getLogger()) , loggerComponent( logger.assigned() ? logger.getOrAddComponent("StreamingClient") : throw ArgumentNullException("Logger must not be null") ) , logCallback( [this](spdlog::source_loc location, spdlog::level::level_enum level, const char* msg) { this->loggerComponent.logMessage(SourceLocation{location.filename, location.line, location.funcname}, msg, static_cast(level)); @@ -438,6 +439,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& size_t valueCount) { std::string id = subscribedSignal.signalId(); + std::size_t dataSize = subscribedSignal.dataValueSize() * valueCount; NumberPtr domainValue = static_cast(timeStamp); InputSignalBasePtr inputSignal = nullptr; @@ -449,25 +451,44 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& if (inputSignal && !isPlaceHolderSignal(inputSignal) && - inputSignal->hasDescriptors() && - inputSignal->getSignalDescriptor().getSampleType() != daq::SampleType::Struct) + inputSignal->hasDescriptors()) { if (inputSignal->isCountable()) { DataPacketPtr domainPacket; if (inputSignal->isDomainSignal()) { - domainPacket = inputSignal->generateDataPacket(domainValue, data, valueCount, nullptr); + domainPacket = inputSignal->generateDataPacket(domainValue, data, dataSize, valueCount, nullptr); + inputSignal->setLastPacket(domainPacket); if (domainPacket.assigned()) onPacketCallback(id, domainPacket); } else { - domainPacket = - inputSignal->getInputDomainSignal()->generateDataPacket(domainValue, nullptr, valueCount, nullptr); - if (domainPacket.assigned()) - onPacketCallback(inputSignal->getInputDomainSignal()->getSignalId(), domainPacket); - auto packet = inputSignal->generateDataPacket(domainValue, data, valueCount, domainPacket); + // If the domain signal is linear-rule, we artificially generate a packet here + // using the timestamp reported by streaming-protocol-lt. If the domain signal + // is explicit-rule, we must (by requirement) already have received and cached + // the domain packet above, and we use that instead. + DataRuleType domainRuleType = DataRuleType::Other; + auto domainSignal = inputSignal->getInputDomainSignal(); + if (domainSignal) + if (auto domainDescriptor = domainSignal->getSignalDescriptor(); domainDescriptor.assigned()) + if (auto domainRule = domainDescriptor.getRule(); domainRule.assigned()) + domainRuleType = domainRule.getType(); + if (domainRuleType == DataRuleType::Explicit) + { + domainPacket = inputSignal->getInputDomainSignal()->getLastPacket(); + } + else + { + domainPacket = + inputSignal->getInputDomainSignal()->generateDataPacket(domainValue, data, dataSize, valueCount, nullptr); + if (domainPacket.assigned()) + onPacketCallback(inputSignal->getInputDomainSignal()->getSignalId(), domainPacket); + } + + auto packet = inputSignal->generateDataPacket(domainValue, data, dataSize, valueCount, domainPacket); + inputSignal->setLastPacket(packet); if (packet.assigned()) onPacketCallback(id, packet); } @@ -479,7 +500,7 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& { if (!relatedSignal->isCountable()) { - auto packet = relatedSignal->generateDataPacket(domainValue, nullptr, valueCount, domainPacket); + auto packet = relatedSignal->generateDataPacket(domainValue, nullptr, 0, valueCount, domainPacket); if (packet.assigned() && relatedSignal->getSubscribed()) onPacketCallback(relatedSignal->getSignalId(), packet); } @@ -492,9 +513,11 @@ void StreamingClient::onMessage(const daq::streaming_protocol::SubscribedSignal& } } -void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +void StreamingClient::setDataSignal( + const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + const daq::ContextPtr& context) { - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, context); const auto signalId = subscribedSignal.signalId(); const auto tableId = subscribedSignal.tableId(); bool available = false; @@ -546,9 +569,11 @@ void StreamingClient::setDataSignal(const daq::streaming_protocol::SubscribedSig } } -void StreamingClient::setTimeSignal(const daq::streaming_protocol::SubscribedSignal& subscribedSignal) +void StreamingClient::setTimeSignal( + const daq::streaming_protocol::SubscribedSignal& subscribedSignal, + const daq::ContextPtr& context) { - auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto sInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, context); const std::string tableId = subscribedSignal.tableId(); const std::string timeSignalId = subscribedSignal.signalId(); bool available = false; @@ -673,11 +698,11 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& if (subscribedSignal.isTimeSignal()) { - setTimeSignal(subscribedSignal); + setTimeSignal(subscribedSignal, context); } else { - setDataSignal(subscribedSignal); + setDataSignal(subscribedSignal, context); } } catch (const DaqException& e) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp index 5ea5417..cd303ce 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_descriptor_converter.cpp @@ -229,7 +229,7 @@ TEST(SignalConverter, subscribedDataSignal) ASSERT_EQ(result, 0); ASSERT_FALSE(subscribedSignal.isTimeSignal()); - auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, NullContext()); auto dataDescriptor = subscribedSignalInfo.dataDescriptor; ASSERT_EQ(subscribedSignalInfo.signalName, memberName); @@ -267,7 +267,7 @@ TEST(SignalConverter, subscribedDataSignal) result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); - subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, NullContext()); dataDescriptor = subscribedSignalInfo.dataDescriptor; range = dataDescriptor.getValueRange(); @@ -322,7 +322,7 @@ TEST(SignalConverter, subscribedBitfieldSignal) result = subscribedSignal.processSignalMetaInformation(method, signalParamsToParse); ASSERT_EQ(result, 0); ASSERT_FALSE(subscribedSignal.isTimeSignal()); - auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, NullContext()); auto dataDescriptor = subscribedSignalInfo.dataDescriptor; ASSERT_EQ(subscribedSignalInfo.signalName, memberName); @@ -389,7 +389,7 @@ TEST(SignalConverter, subscribedTimeSignal) ASSERT_EQ(result, 0); ASSERT_TRUE(subscribedSignal.isTimeSignal()); - auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, NullContext()); auto dataDescriptor = subscribedSignalInfo.dataDescriptor; ASSERT_EQ(subscribedSignalInfo.signalName, memberName); @@ -464,7 +464,7 @@ TEST(SignalConverter, FloatDeltaOfLinearSignal) ASSERT_EQ(result, 0); ASSERT_TRUE(subscribedSignal.isTimeSignal()); - auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal); + auto subscribedSignalInfo = SignalDescriptorConverter::ToDataDescriptor(subscribedSignal, NullContext()); auto dataDescriptor = subscribedSignalInfo.dataDescriptor; ASSERT_EQ(subscribedSignalInfo.signalName, memberName); From abb00fbe013e17bc906ad7c695b67c844b8caed0 Mon Sep 17 00:00:00 2001 From: NikolaiShipilov <127689162+NikolaiShipilov@users.noreply.github.com> Date: Wed, 14 May 2025 00:12:14 -0700 Subject: [PATCH 118/127] Use LT streaming client lib release version 1.4.0 (openDAQ/openDAQ#800) --- external/streaming_protocol/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 2d74bc0..53ecafb 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.4 + REQUIRED_VERSION 1.4.0 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF 3b56a7a066b059e7eb1692de5f6704815200a222 + GIT_REF v1.4.0 EXPECT_TARGET daq::streaming_protocol ) From a750945ae529016bccf0cf590145defd0be27007 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:02:39 +0200 Subject: [PATCH 119/127] Increase cmake min version to 3.10 (openDAQ/openDAQ#817) Co-authored-by: Jaka Mohorko --- modules/websocket_streaming_client_module/CMakeLists.txt | 2 +- modules/websocket_streaming_server_module/CMakeLists.txt | 2 +- shared/libraries/websocket_streaming/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 6103361..9866e8d 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) project(WebsocketStreamingClientModule VERSION ${OPENDAQ_PACKAGE_VERSION} LANGUAGES C CXX) diff --git a/modules/websocket_streaming_server_module/CMakeLists.txt b/modules/websocket_streaming_server_module/CMakeLists.txt index 89d28f2..0b5b926 100644 --- a/modules/websocket_streaming_server_module/CMakeLists.txt +++ b/modules/websocket_streaming_server_module/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) project(WebsocketStreamingServerModule VERSION ${OPENDAQ_PACKAGE_VERSION} LANGUAGES CXX) diff --git a/shared/libraries/websocket_streaming/CMakeLists.txt b/shared/libraries/websocket_streaming/CMakeLists.txt index f4d93d3..a89b800 100644 --- a/shared/libraries/websocket_streaming/CMakeLists.txt +++ b/shared/libraries/websocket_streaming/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME ${SDK_TARGET_NAMESPACE}_websocket_streaming) project(OpenDaqStreaming VERSION 4.0.0 From f240482198fc0c96e23349002d279bd85e993e94 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:31:00 +0200 Subject: [PATCH 120/127] Revert faulty LT changes (openDAQ/openDAQ#832) --- external/streaming_protocol/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index 53ecafb..f6ed6c0 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.4.0 + REQUIRED_VERSION 1.2.5 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.4.0 + GIT_REF v1.2.5 EXPECT_TARGET daq::streaming_protocol ) From 063eb5418a5957c27cecdc7fb411066f6d2f9c2e Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:32:21 +0200 Subject: [PATCH 121/127] Bump LT version to 1.2.6 (openDAQ/openDAQ#840) --- external/streaming_protocol/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index f6ed6c0..a752cc5 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.5 + REQUIRED_VERSION 1.2.6 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.2.5 + GIT_REF v1.2.6 EXPECT_TARGET daq::streaming_protocol ) From bc4d5ae1376e965daba25ae77ed685629cfd0de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Zako=C5=A1ek?= Date: Wed, 16 Jul 2025 08:31:00 +0200 Subject: [PATCH 122/127] Circular buffer implementation (openDAQ/openDAQ#726) Added circular buffer functionality when creating packets, buffer can only be used on packets of Exact data type. Default length of a buffer is 2 seconds. --- .../tests/test_signal_generator.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp index 2c8c9a6..c0f6194 100644 --- a/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp +++ b/shared/libraries/websocket_streaming/tests/test_signal_generator.cpp @@ -151,3 +151,24 @@ TEST_F(SignalGeneratorTest, ChangeFunction) ASSERT_EQ(packet2.getSampleCount(), packetSize); ASSERT_TRUE(compareSamples(expectedSamples2.data(), packet2.getData(), packetSize)); } + +TEST_F(SignalGeneratorTest, SignalGeneratorCountCheck) +{ + const size_t packetSize= 100; + auto expectedSamples1 = calculateExpectedSamples(0, packetSize, stepFunction10); + + auto reader = PacketReader(signal); + + auto generator = SignalGenerator(signal, std::chrono::system_clock::now()); + generator.setFunction(stepFunction10); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 2)); + generator.generateSamplesTo(std::chrono::milliseconds(packetSize * 3)); + + auto packets = reader.readAll(); + ASSERT_EQ(packets.getCount(), 4u); + auto packet1 = packets[1].asPtr(); + ASSERT_EQ(packet1.getSampleCount(), packetSize); + auto packet2 = packets[2].asPtr(); + ASSERT_EQ(packet2.getSampleCount(), packetSize); +} From cb367f2217f834b6457bd9da5f47f7fe51b22e53 Mon Sep 17 00:00:00 2001 From: Denis Erokhin <149682271+denise-opendaq@users.noreply.github.com> Date: Sat, 2 Aug 2025 08:26:24 +0200 Subject: [PATCH 123/127] Improving error logging and implementing an error guard (openDAQ/openDAQ#849) --- modules/websocket_streaming_client_module/tests/test_app.cpp | 2 +- shared/libraries/websocket_streaming/src/streaming_client.cpp | 1 + shared/libraries/websocket_streaming/src/streaming_server.cpp | 4 ++++ .../websocket_streaming/src/websocket_streaming_server.cpp | 1 - 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/tests/test_app.cpp b/modules/websocket_streaming_client_module/tests/test_app.cpp index f7351c2..d92f41f 100644 --- a/modules/websocket_streaming_client_module/tests/test_app.cpp +++ b/modules/websocket_streaming_client_module/tests/test_app.cpp @@ -17,5 +17,5 @@ int main(int argc, char** args) auto res = RUN_ALL_TESTS(); - return res; + return res; } diff --git a/shared/libraries/websocket_streaming/src/streaming_client.cpp b/shared/libraries/websocket_streaming/src/streaming_client.cpp index c2bc172..d8573c9 100644 --- a/shared/libraries/websocket_streaming/src/streaming_client.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_client.cpp @@ -686,6 +686,7 @@ void StreamingClient::onSignal(const daq::streaming_protocol::SubscribedSignal& { try { + auto errorGuard = DAQ_ERROR_GUARD(); { LOG_I("Signal #{}; signalId {}; tableId {}; name {}; value type {}; Json parameters: \n\n{}\n", subscribedSignal.signalNumber(), diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index 0c94472..dc955f4 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -606,6 +606,7 @@ void StreamingServer::handleDataDescriptorChanges(OutputSignalBasePtr& outputSig { try { + auto errorGuard = DAQ_ERROR_GUARD(); outputSignal->writeValueDescriptorChanges(newValueDescriptor); } catch (const DaqException& e) @@ -627,6 +628,7 @@ void StreamingServer::handleDataDescriptorChanges(OutputSignalBasePtr& outputSig { try { + auto errorGuard = DAQ_ERROR_GUARD(); outputSignal->writeDomainDescriptorChanges(newDomainDescriptor); } catch (const DaqException& e) @@ -658,6 +660,7 @@ void StreamingServer::updateOutputPlaceholderSignal(OutputSignalBasePtr& outputS LOG_I("Parameters of unsupported signal {} has been changed, check if it is supported now ...", daqSignal.getGlobalId()); try { + auto errorGuard = DAQ_ERROR_GUARD(); addToOutputSignals(daqSignal, outputSignals, writer); outputSignal = outputSignals.at(signalId); outputSignal->setSubscribed(subscribed); @@ -683,6 +686,7 @@ void StreamingServer::publishSignalsToClient(const StreamWriterPtr& writer, try { + auto errorGuard = DAQ_ERROR_GUARD(); addToOutputSignals(daqSignal, outputSignals, writer); } catch (const DaqException& e) diff --git a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp index d2ccdfc..91f7fa5 100644 --- a/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/websocket_streaming_server.cpp @@ -1,6 +1,5 @@ #include #include -#include #include "websocket_streaming/websocket_streaming_server.h" #include #include From 1f964f3a520c885db1fd0fbff74721bbdcadb2bb Mon Sep 17 00:00:00 2001 From: Yaroslav Popov Date: Mon, 1 Sep 2025 13:35:37 +0700 Subject: [PATCH 124/127] [TBBAS-2555] Upgrade nlohmann-json 3.10.2 To 3.11.3 (openDAQ/openDAQ#891) * json lib update Co-authored-by: David Norris * streaming-lt version bump --------- Co-authored-by: David Norris --- external/streaming_protocol/CMakeLists.txt | 4 +- .../signal_descriptor_converter.h | 2 +- .../src/signal_descriptor_converter.cpp | 77 +++++++++++-------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/external/streaming_protocol/CMakeLists.txt b/external/streaming_protocol/CMakeLists.txt index a752cc5..be75a99 100644 --- a/external/streaming_protocol/CMakeLists.txt +++ b/external/streaming_protocol/CMakeLists.txt @@ -6,8 +6,8 @@ endif() opendaq_dependency( NAME streaming_protocol - REQUIRED_VERSION 1.2.6 + REQUIRED_VERSION 1.2.7 GIT_REPOSITORY https://github.com/openDAQ/streaming-protocol-lt.git - GIT_REF v1.2.6 + GIT_REF v1.2.7 EXPECT_TARGET daq::streaming_protocol ) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h index 0c30d77..6ceb232 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/signal_descriptor_converter.h @@ -59,6 +59,6 @@ class SignalDescriptorConverter static void DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptorBuilder); static void DecodeBitsInterpretationObject(const nlohmann::json& bits, DataDescriptorBuilderPtr& dataDescriptorBuilder); static nlohmann::json DictToJson(const DictPtr& dict); - static DictPtr JsonToDict(const nlohmann::json& json); + static ObjectPtr JsonToObject(const nlohmann::json& json); }; END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING diff --git a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp index 07cb5b5..696bafc 100644 --- a/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp +++ b/shared/libraries/websocket_streaming/src/signal_descriptor_converter.cpp @@ -60,7 +60,7 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor( for (const auto& field : details) { auto fieldBuilder = DataDescriptorBuilder(); - fieldBuilder.setName(field.at("name")); + fieldBuilder.setName(String(field.at("name"))); fieldBuilder.setSampleType(ConvertSampleTypeString(field.value("dataType", ""))); fieldNames.pushBack(fieldBuilder.getName()); @@ -77,7 +77,7 @@ SubscribedSignalInfo SignalDescriptorConverter::ToDataDescriptor( daqDimensions.pushBack( DimensionBuilder() - .setName(dimension.at("name")) + .setName(String(dimension.at("name"))) .setRule(LinearDimensionRule( static_cast(dimension.at("linear").at("delta")), static_cast(dimension.at("linear").at("start")), @@ -539,23 +539,23 @@ void SignalDescriptorConverter::DecodeBitsInterpretationObject(const nlohmann::j void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& extra, DataDescriptorBuilderPtr& dataDescriptorBuilder) { // sets descriptor name when corresponding field is present in interpretation object - if (extra.count("desc_name") > 0) - dataDescriptorBuilder.setName(extra["desc_name"]); + if (extra.contains("desc_name")) + dataDescriptorBuilder.setName(String(extra["desc_name"])); - if (extra.count("metadata") > 0) + if (extra.contains("metadata")) { - auto meta = JsonToDict(extra["metadata"]); + auto meta = JsonToObject(extra["metadata"]); dataDescriptorBuilder.setMetadata(meta); } - if (extra.count("unit") > 0) + if (extra.contains("unit")) { auto unitObj = extra["unit"]; - auto unit = Unit(unitObj["symbol"], unitObj["id"], unitObj["name"], unitObj["quantity"]); + auto unit = Unit(String(unitObj["symbol"]), Integer(unitObj["id"]), String(unitObj["name"]), String(unitObj["quantity"])); dataDescriptorBuilder.setUnit(unit); } - if (extra.count("range") > 0) + if (extra.contains("range")) { auto rangeObj = extra["range"]; auto low = std::stoi(std::string(rangeObj["low"])); @@ -564,21 +564,21 @@ void SignalDescriptorConverter::DecodeInterpretationObject(const nlohmann::json& dataDescriptorBuilder.setValueRange(range); } - if (extra.count("origin") > 0) - dataDescriptorBuilder.setOrigin(extra["origin"]); + if (extra.contains("origin")) + dataDescriptorBuilder.setOrigin(String(extra["origin"])); - if (extra.count("rule") > 0) + if (extra.contains("rule") && extra["rule"].contains("parameters") && extra["rule"]["parameters"].is_object()) { - auto params = JsonToDict(extra["rule"]["parameters"]); + auto params = JsonToObject(extra["rule"]["parameters"]); params.freeze(); auto rule = DataRuleBuilder().setType(extra["rule"]["type"]).setParameters(params).build(); dataDescriptorBuilder.setRule(rule); } - if (extra.count("scaling") > 0) + if (extra.contains("scaling") && extra["scaling"].contains("parameters") && extra["scaling"]["parameters"].is_object()) { - auto params = JsonToDict(extra["scaling"]["parameters"]); + auto params = JsonToObject(extra["scaling"]["parameters"]); params.freeze(); auto scaling = ScalingBuilder() @@ -615,29 +615,38 @@ nlohmann::json SignalDescriptorConverter::DictToJson(const DictPtr SignalDescriptorConverter::JsonToDict(const nlohmann::json& json) +ObjectPtr SignalDescriptorConverter::JsonToObject(const nlohmann::json& json) { - auto dict = Dict(); - auto items = json.items(); - - for (const auto& entry : items) + if (json.is_object()) { - if (entry.value().is_array()) - { - auto vect = entry.value().get>(); - dict[entry.key()] = ListPtr::FromVector(vect); - } - else if (entry.value().is_object()) - dict[entry.key()] = JsonToDict(entry.value()); - else if (entry.value().is_number_float()) - dict[entry.key()] = entry.value().get(); - else if (entry.value().is_number_integer()) - dict[entry.key()] = entry.value().get(); - else - dict[entry.key()] = entry.value(); + auto dict = Dict(); + for (const auto& entry : json.items()) + if (auto obj = JsonToObject(entry.value()); obj.assigned()) + dict[entry.key()] = obj; + return dict; + } + else if (json.is_array()) + { + auto list = List(); + for (const auto& entry : json) + if (auto obj = JsonToObject(entry); obj.assigned()) + list.pushBack(obj); + return list; } + else if (json.is_number_float()) + return json.get(); + + else if (json.is_number_integer()) + return json.get(); + + else if (json.is_string()) + return StringPtr(json.get()); - return dict; + else if (json.is_boolean()) + return Boolean(json.get()); + + else + return nullptr; } END_NAMESPACE_OPENDAQ_WEBSOCKET_STREAMING From 5b8affe2dabb3072f6d90d963c4bd10fcc2cf82a Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:59:31 +0200 Subject: [PATCH 125/127] Other/merge 3.30 rc (openDAQ/openDAQ#928) --- .../websocket_streaming_client_module/CMakeLists.txt | 12 ++++++++++++ .../websocket_streaming/src/streaming_server.cpp | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/websocket_streaming_client_module/CMakeLists.txt b/modules/websocket_streaming_client_module/CMakeLists.txt index 9866e8d..be07a8f 100644 --- a/modules/websocket_streaming_client_module/CMakeLists.txt +++ b/modules/websocket_streaming_client_module/CMakeLists.txt @@ -2,6 +2,18 @@ cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) project(WebsocketStreamingClientModule VERSION ${OPENDAQ_PACKAGE_VERSION} LANGUAGES C CXX) +if (MSVC) + # loss of data / precision, unsigned <--> signed + # + # 'argument' : conversion from 'type1' to 'type2', possible loss of data + # https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4244 + add_compile_options(/wd4244) + + # 'var' : conversion from 'size_t' to 'type', possible loss of data + # https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4267 + add_compile_options(/wd4267) +endif() + add_subdirectory(src) if (OPENDAQ_ENABLE_TESTS) diff --git a/shared/libraries/websocket_streaming/src/streaming_server.cpp b/shared/libraries/websocket_streaming/src/streaming_server.cpp index dc955f4..4aa815c 100644 --- a/shared/libraries/websocket_streaming/src/streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/streaming_server.cpp @@ -593,8 +593,8 @@ void StreamingServer::handleDataDescriptorChanges(OutputSignalBasePtr& outputSig if (auto placeholderValueSignal = std::dynamic_pointer_cast(outputSignal)) { - if (valueDescriptorChanged && newValueDescriptor.assigned() || - domainDescriptorChanged && newDomainDescriptor.assigned()) + if ((valueDescriptorChanged && newValueDescriptor.assigned()) || + (domainDescriptorChanged && newDomainDescriptor.assigned())) updateOutputPlaceholderSignal(outputSignal, outputSignals, writer, subscribed); } else From 23dde3710de3b5f0845a9ba7ef91f94bddce5348 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:47:14 +0200 Subject: [PATCH 126/127] Other/merge rc (openDAQ/openDAQ#947) --- .../websocket_streaming_client_module_impl.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp index 68d32c3..26d2bbb 100644 --- a/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp +++ b/modules/websocket_streaming_client_module/src/websocket_streaming_client_module_impl.cpp @@ -296,16 +296,16 @@ DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const Mdn { auto cap = ServerCapability(WebsocketDeviceTypeId, "OpenDAQLTStreaming", ProtocolType::Streaming); - if (!discoveredDevice.ipv4Address.empty()) + for (const auto& ipAddress : discoveredDevice.ipv4Addresses) { auto connectionStringIpv4 = WebsocketStreamingClientModule::createUrlConnectionString( - discoveredDevice.ipv4Address, + ipAddress, discoveredDevice.servicePort, discoveredDevice.getPropertyOrDefault("path", "/") ); cap.addConnectionString(connectionStringIpv4); - cap.addAddress(discoveredDevice.ipv4Address); - const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv4Address) + cap.addAddress(ipAddress); + const auto addressInfo = AddressInfoBuilder().setAddress(ipAddress) .setReachabilityStatus(AddressReachabilityStatus::Unknown) .setType("IPv4") .setConnectionString(connectionStringIpv4) @@ -313,17 +313,17 @@ DeviceInfoPtr WebsocketStreamingClientModule::populateDiscoveredDevice(const Mdn cap.addAddressInfo(addressInfo); } - if(!discoveredDevice.ipv6Address.empty()) + for (const auto& ipAddress : discoveredDevice.ipv6Addresses) { auto connectionStringIpv6 = WebsocketStreamingClientModule::createUrlConnectionString( - discoveredDevice.ipv6Address, + ipAddress, discoveredDevice.servicePort, discoveredDevice.getPropertyOrDefault("path", "/") ); cap.addConnectionString(connectionStringIpv6); - cap.addAddress(discoveredDevice.ipv6Address); + cap.addAddress(ipAddress); - const auto addressInfo = AddressInfoBuilder().setAddress(discoveredDevice.ipv6Address) + const auto addressInfo = AddressInfoBuilder().setAddress(ipAddress) .setReachabilityStatus(AddressReachabilityStatus::Unknown) .setType("IPv6") .setConnectionString(connectionStringIpv6) From 638f616b59c1578ac8bf6cf52aba6a23bd091986 Mon Sep 17 00:00:00 2001 From: Jaka Mohorko <96818661+JakaMohorkoDS@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:06:16 +0100 Subject: [PATCH 127/127] Merge 3.30 release to main (openDAQ/openDAQ#970) --- .../tests/test_websocket_client_device.cpp | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp index 7e31d80..6d00ead 100644 --- a/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp +++ b/shared/libraries/websocket_streaming/tests/test_websocket_client_device.cpp @@ -77,13 +77,13 @@ class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public test public: bool addSignals(const ListPtr& signals, const StreamingServerPtr& server, - const ContextPtr& context) + const DevicePtr& device) { SizeT addedSigCount = 0; std::promise addSigPromise; std::future addSigFuture = addSigPromise.get_future(); - auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + auto eventHandler = [&](ComponentPtr comp, CoreEventArgsPtr args) { auto params = args.getParameters(); if (static_cast(args.getEventId()) == CoreEventId::ComponentAdded) @@ -98,25 +98,25 @@ class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public test } }; - context.getOnCoreEvent() += eventHandler; + device.getItem("Sig").getOnComponentCoreEvent() += eventHandler; server->addSignals(signals); bool result = (addSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); - context.getOnCoreEvent() -= eventHandler; + device.getItem("Sig").getOnComponentCoreEvent() -= eventHandler; return result; } bool removeSignals(const ListPtr& signals, const StreamingServerPtr& server, - const ContextPtr& context) + const DevicePtr& device) { SizeT removedSigCount = 0; std::promise rmSigPromise; std::future rmSigFuture = rmSigPromise.get_future(); - auto eventHandler = [&](const ComponentPtr& comp, const CoreEventArgsPtr& args) + auto eventHandler = [&](ComponentPtr comp, CoreEventArgsPtr args) { if (static_cast(args.getEventId()) == CoreEventId::ComponentRemoved) { @@ -126,21 +126,20 @@ class WebsocketClientDeviceTestP : public WebsocketClientDeviceTest, public test } }; - context.getOnCoreEvent() += eventHandler; + device.getItem("Sig").getOnComponentCoreEvent() += eventHandler; for (const auto& signal : signals) server->removeComponentSignals(signal.getGlobalId()); bool result = (rmSigFuture.wait_for(std::chrono::seconds(5)) == std::future_status::ready); - context.getOnCoreEvent() -= eventHandler; + device.getItem("Sig").getOnComponentCoreEvent() -= eventHandler; return result; } }; TEST_P(WebsocketClientDeviceTestP, SignalWithDomain) { - SKIP_TEST_MAC_CI; const bool signalsAddedAfterConnect = GetParam(); // Create server signals @@ -164,7 +163,7 @@ TEST_P(WebsocketClientDeviceTestP, SignalWithDomain) if (signalsAddedAfterConnect) { - ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(addSignals(signals, server, clientDevice)); } // Check the mirrored signal @@ -218,11 +217,11 @@ TEST_P(WebsocketClientDeviceTestP, SignalWithDomain) ASSERT_TRUE(BaseObjectPtr::Equals(clientDevice.getSignals()[0].getDomainSignal().getDescriptor(), testValueSignal.getDomainSignal().getDescriptor())); - ASSERT_TRUE(removeSignals({testDomainSignal}, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals({testDomainSignal}, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 1u); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); - ASSERT_TRUE(removeSignals({testValueSignal}, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals({testValueSignal}, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } @@ -250,7 +249,7 @@ TEST_P(WebsocketClientDeviceTestP, SingleDomainSignal) if (signalsAddedAfterConnect) { - ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(addSignals(signals, server, clientDevice)); } // The mirrored signal exists and has descriptor @@ -258,7 +257,7 @@ TEST_P(WebsocketClientDeviceTestP, SingleDomainSignal) ASSERT_TRUE(clientDevice.getSignals()[0].getDescriptor().assigned()); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); - ASSERT_TRUE(removeSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals(signals, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } @@ -286,7 +285,7 @@ TEST_P(WebsocketClientDeviceTestP, SingleUnsupportedSignal) if (signalsAddedAfterConnect) { - ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(addSignals(signals, server, clientDevice)); } // The mirrored signal exists but does not have descriptor @@ -294,7 +293,7 @@ TEST_P(WebsocketClientDeviceTestP, SingleUnsupportedSignal) ASSERT_FALSE(clientDevice.getSignals()[0].getDescriptor().assigned()); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); - ASSERT_TRUE(removeSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals(signals, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); } @@ -323,7 +322,7 @@ TEST_P(WebsocketClientDeviceTestP, SignalsWithSharedDomain) if (signalsAddedAfterConnect) { - ASSERT_TRUE(addSignals(signals, server, clientDevice.getContext())); + ASSERT_TRUE(addSignals(signals, server, clientDevice)); } ASSERT_EQ(clientDevice.getSignals().getCount(), 3u); @@ -354,12 +353,12 @@ TEST_P(WebsocketClientDeviceTestP, SignalsWithSharedDomain) ASSERT_EQ(clientDevice.getSignals()[2].getDomainSignal(), clientDevice.getSignals()[1]); ASSERT_EQ(clientDevice.getSignals()[0].getDomainSignal(), clientDevice.getSignals()[1]); - ASSERT_TRUE(removeSignals({timeSignal}, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals({timeSignal}, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 2u); ASSERT_FALSE(clientDevice.getSignals()[0].getDomainSignal().assigned()); ASSERT_FALSE(clientDevice.getSignals()[1].getDomainSignal().assigned()); - ASSERT_TRUE(removeSignals({dataSignal1, dataSignal2}, server, clientDevice.getContext())); + ASSERT_TRUE(removeSignals({dataSignal1, dataSignal2}, server, clientDevice)); ASSERT_EQ(clientDevice.getSignals().getCount(), 0u); }