diff --git a/CMakePresets.json b/CMakePresets.json index 4152fa138..5d4c9d58e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -64,6 +64,11 @@ }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, + { + "name": "x64-debug-docs", + "inherits": "x64-debug", + "cacheVariables": { "SILKIT_BUILD_DOCS": "ON" } + }, { "name": "x64-release", "displayName": "x64 Release", diff --git a/Demos/CMakeLists.txt b/Demos/CMakeLists.txt index 86f910101..7b7184256 100755 --- a/Demos/CMakeLists.txt +++ b/Demos/CMakeLists.txt @@ -82,8 +82,6 @@ endif() set(make_silkit_demo_caller_dir ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "") -set(demo_list "") - add_custom_target(Demos COMMENT "Build all available Demos") set_property(TARGET Demos PROPERTY FOLDER "Demos") @@ -97,10 +95,17 @@ else() set(_DEMO_OUTPUT_DIR "${CMAKE_BINARY_DIR}/$") endif() +set(SILKIT_DEMO_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +####################################################################################################################### +# Demo creation macros +####################################################################################################################### + macro(make_silkit_demo executableName demoSourceFile) + add_executable(${executableName} ${demoSourceFile} - $<$: "${CMAKE_CURRENT_SOURCE_DIR}/../demo.manifest" > + $<$: "${CMAKE_CURRENT_SOURCE_DIR}/../../demo.manifest" > ) remove_definitions(-DEXPORT_SilKitAPI) @@ -124,29 +129,38 @@ macro(make_silkit_demo executableName demoSourceFile) SilKit::SilKit Threads::Threads ) - if(MSVC) target_compile_options(${executableName} PRIVATE /wd4996) endif() add_dependencies(Demos ${executableName}) - set(demo_list "${demo_list} ${executableName}" PARENT_SCOPE) + +endmacro() + +macro(make_silkit_communication_demo executableName demoSourceFile) + + make_silkit_demo(${executableName} ${demoSourceFile}) + target_include_directories(${executableName} PRIVATE ${SILKIT_DEMO_DIR}/communication/include) + endmacro() ####################################################################################################################### # Add the actual demo projects ####################################################################################################################### -# C++ Demos -add_subdirectory(Can) -add_subdirectory(Ethernet) -add_subdirectory(Flexray) -add_subdirectory(Lin) -add_subdirectory(PubSub) -add_subdirectory(Rpc) -add_subdirectory(Benchmark) -add_subdirectory(NetworkSimulator) - -message(STATUS "Demos available: ${demo_list}") + +add_subdirectory(communication/Can) +add_subdirectory(communication/Ethernet) +add_subdirectory(communication/Flexray) +add_subdirectory(communication/Lin) +add_subdirectory(communication/PubSub) +add_subdirectory(communication/Rpc) + +add_subdirectory(tools/Benchmark) + +add_subdirectory(api/SimpleCan) +add_subdirectory(api/NetworkSimulator) +add_subdirectory(api/Orchestration) + ####################################################################################################################### # VisualStudio specific setup ####################################################################################################################### diff --git a/Demos/Can/CanDemo.cpp b/Demos/Can/CanDemo.cpp deleted file mode 100644 index 5cef427fd..000000000 --- a/Demos/Can/CanDemo.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/logging/ILogger.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" -#include "silkit/services/can/all.hpp" -#include "silkit/services/can/string_utils.hpp" - - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Can; -using namespace SilKit::Services::Logging; - -using namespace std::chrono_literals; - -namespace std { -namespace chrono { -std::ostream& operator<<(std::ostream& out, nanoseconds timestamp) -{ - auto seconds = std::chrono::duration_cast>>(timestamp); - out << seconds.count() << "s"; - return out; -} -} // namespace chrono -} // namespace std - -void FrameTransmitHandler(const CanFrameTransmitEvent& ack, ILogger* logger) -{ - std::stringstream buffer; - buffer << ">> " << ack.status << " for CAN frame with timestamp=" << ack.timestamp - << " and userContext=" << ack.userContext; - logger->Info(buffer.str()); -} - -void FrameHandler(const CanFrameEvent& frameEvent, ILogger* logger) -{ - std::string payload(frameEvent.frame.dataField.begin(), frameEvent.frame.dataField.end()); - std::stringstream buffer; - buffer << ">> CAN frame: canId=" << frameEvent.frame.canId << " timestamp=" << frameEvent.timestamp << " \"" - << payload << "\""; - logger->Info(buffer.str()); -} - -void SendFrame(ICanController* controller, ILogger* logger) -{ - CanFrame canFrame{}; - canFrame.canId = 3; - canFrame.flags |= static_cast(CanFrameFlag::Fdf) // FD Format Indicator - | static_cast(CanFrameFlag::Brs); // Bit Rate Switch (for FD Format only) - - static int msgId = 0; - const auto currentMessageId = msgId++; - - std::stringstream payloadBuilder; - payloadBuilder << "CAN " << (currentMessageId % 100); - auto payloadStr = payloadBuilder.str(); - - std::vector payloadBytes; - payloadBytes.resize(payloadStr.size()); - std::copy(payloadStr.begin(), payloadStr.end(), payloadBytes.begin()); - - canFrame.dataField = payloadBytes; - canFrame.dlc = static_cast(canFrame.dataField.size()); - - void* const userContext = reinterpret_cast(static_cast(currentMessageId)); - - controller->SendFrame(std::move(canFrame), userContext); - std::stringstream buffer; - buffer << "<< CAN frame sent with userContext=" << userContext; - logger->Info(buffer.str()); -} - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -int main(int argc, char** argv) -{ - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"CanWriter\" or \"CanReader\" as ." << std::endl; - return -1; - } - - if (argc > 5) - { - std::cerr << "Too many arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"CanWriter\" or \"CanReader\" as ." << std::endl; - return -1; - } - - std::string participantName(argv[2]); - - if (participantName != "CanWriter" && participantName != "CanReader") - { - std::cout << "Wrong participant name provided. Use either \"CanWriter\" or \"CanReader\"." << std::endl; - return -1; - } - - try - { - std::string participantConfigurationFilename(argv[1]); - - std::string registryUri = "silkit://localhost:8500"; - - bool runSync = true; - - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg; - } - } - - auto participantConfiguration = - SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - auto sleepTimePerTick = 1000ms; - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - - auto* logger = participant->GetLogger(); - auto* canController = participant->CreateCanController("CAN1", "CAN1"); - - canController->AddFrameTransmitHandler([logger](ICanController* /*ctrl*/, const CanFrameTransmitEvent& ack) { - FrameTransmitHandler(ack, logger); - }); - canController->AddFrameHandler( - [logger](ICanController* /*ctrl*/, const CanFrameEvent& frameEvent) { FrameHandler(frameEvent, logger); }); - - auto operationMode = (runSync ? OperationMode::Coordinated : OperationMode::Autonomous); - - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - - // Observe state changes - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - lifecycleService->SetAbortHandler( - [](auto lastState) { std::cout << "Abort handler called while in state " << lastState << std::endl; }); - - if (runSync) - { - lifecycleService->SetCommunicationReadyHandler([canController, &participantName]() { - std::cout << "Communication ready for " << participantName << std::endl; - canController->SetBaudRate(10'000, 1'000'000, 2'000'000); - canController->Start(); - }); - - auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - - if (participantName == "CanWriter") - { - timeSyncService->SetSimulationStepHandler( - [canController, logger, sleepTimePerTick](std::chrono::nanoseconds now, - std::chrono::nanoseconds duration) { - std::cout << "now=" << now << ", duration=" << duration << std::endl; - SendFrame(canController, logger); - std::this_thread::sleep_for(sleepTimePerTick); - }, - 5ms); - } - else - { - timeSyncService->SetSimulationStepHandler( - [sleepTimePerTick](std::chrono::nanoseconds now, std::chrono::nanoseconds duration) { - std::cout << "now=" << now << ", duration=" << duration << std::endl; - std::this_thread::sleep_for(sleepTimePerTick); - }, 5ms); - } - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::atomic isStopRequested = {false}; - std::thread workerThread; - - std::promise promiseObj; - std::future futureObj = promiseObj.get_future(); - lifecycleService->SetCommunicationReadyHandler([&]() { - std::cout << "Communication ready for " << participantName << std::endl; - canController->SetBaudRate(10'000, 1'000'000, 2'000'000); - - workerThread = std::thread{[&]() { - futureObj.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - if (participantName == "CanWriter") - { - SendFrame(canController, logger); - } - std::this_thread::sleep_for(sleepTimePerTick); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - canController->Start(); - }); - - lifecycleService->SetStartingHandler([&]() { promiseObj.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - catch (const SilKit::ConfigurationError& error) - { - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; - } - - return 0; -} diff --git a/Demos/Can/DemoCan.silkit.yaml b/Demos/Can/DemoCan.silkit.yaml deleted file mode 100644 index ae291a319..000000000 --- a/Demos/Can/DemoCan.silkit.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Sample configuration for CAN -Logging: - Sinks: - - Level: Info - Type: Stdout diff --git a/Demos/Can/NetworkSimulatorConfig.yaml b/Demos/Can/NetworkSimulatorConfig.yaml deleted file mode 100644 index 64e7871ec..000000000 --- a/Demos/Can/NetworkSimulatorConfig.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Network simulator configuration for a detailed simulation of the CAN Demo -SchemaVersion: '1' -SimulatedNetworks: - - Name: CAN1 - Type: CAN diff --git a/Demos/Ethernet/CMakeLists.txt b/Demos/Ethernet/CMakeLists.txt deleted file mode 100644 index 1bd9796ce..000000000 --- a/Demos/Ethernet/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2022 Vector Informatik GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -make_silkit_demo(SilKitDemoEthernet EthernetDemo.cpp) - -target_sources(SilKitDemoEthernet - PRIVATE DemoEthernet.silkit.yaml - PRIVATE NetworkSimulatorConfig.yaml -) diff --git a/Demos/Ethernet/DemoEthernet.silkit.yaml b/Demos/Ethernet/DemoEthernet.silkit.yaml deleted file mode 100644 index 4c73ab642..000000000 --- a/Demos/Ethernet/DemoEthernet.silkit.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Sample configuration for Ethernet -Logging: - Sinks: - - Level: Info - Type: Stdout diff --git a/Demos/Ethernet/EthernetDemo.cpp b/Demos/Ethernet/EthernetDemo.cpp deleted file mode 100644 index 0743dfa0f..000000000 --- a/Demos/Ethernet/EthernetDemo.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/SilKitVersion.hpp" - -#include "silkit/services/all.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" - - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Ethernet; - -using namespace std::chrono_literals; - -// Field in a frame that can indicate the protocol, payload size, or the start of a VLAN tag -using EtherType = uint16_t; -using EthernetMac = std::array; - -std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) -{ - auto seconds = std::chrono::duration_cast>>(timestamp); - out << seconds.count() << "s"; - return out; -} - -std::vector CreateFrame(const EthernetMac& destinationAddress, const EthernetMac& sourceAddress, - const std::vector& payload) -{ - const uint16_t etherType = 0x0000; // no protocol - - std::vector raw; - - std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(raw)); - std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(raw)); - auto etherTypeBytes = reinterpret_cast(ðerType); - raw.push_back(etherTypeBytes[1]); // We assume our platform to be little-endian - raw.push_back(etherTypeBytes[0]); - std::copy(payload.begin(), payload.end(), std::back_inserter(raw)); - - return raw; -} - -std::string GetPayloadStringFromFrame(const EthernetFrame& frame) -{ - const size_t FrameHeaderSize = 2 * sizeof(EthernetMac) + sizeof(EtherType); - - std::vector payload; - payload.insert(payload.end(), frame.raw.begin() + FrameHeaderSize, frame.raw.end()); - std::string payloadString(payload.begin(), payload.end()); - return payloadString; -} - -void FrameTransmitHandler(IEthernetController* /*controller*/, const EthernetFrameTransmitEvent& frameTransmitEvent) -{ - if (frameTransmitEvent.status == EthernetTransmitStatus::Transmitted) - { - std::cout << ">> ACK for Ethernet frame with userContext=" << frameTransmitEvent.userContext << std::endl; - } - else - { - std::cout << ">> NACK for Ethernet frame with userContext=" << frameTransmitEvent.userContext; - switch (frameTransmitEvent.status) - { - case EthernetTransmitStatus::Transmitted: - break; - case EthernetTransmitStatus::InvalidFrameFormat: - std::cout << ": InvalidFrameFormat"; - break; - case EthernetTransmitStatus::ControllerInactive: - std::cout << ": ControllerInactive"; - break; - case EthernetTransmitStatus::LinkDown: - std::cout << ": LinkDown"; - break; - case EthernetTransmitStatus::Dropped: - std::cout << ": Dropped"; - break; - } - - std::cout << std::endl; - } -} - -void FrameHandler(IEthernetController* /*controller*/, const EthernetFrameEvent& frameEvent) -{ - auto frame = frameEvent.frame; - auto payload = GetPayloadStringFromFrame(frame); - std::cout << ">> Ethernet frame: \"" << payload << "\"" << std::endl; -} - -void SendFrame(IEthernetController* controller, const EthernetMac& from, const EthernetMac& to) -{ - static int frameId = 0; - std::stringstream stream; - stream - << "Hello from Ethernet writer! (frameId =" << frameId++ - << ")" - "----------------------------------------------------"; // ensure that the payload is long enough to constitute a valid Ethernet frame - - auto payloadString = stream.str(); - std::vector payload(payloadString.size() + 1); - memcpy(payload.data(), payloadString.c_str(), payloadString.size() + 1); - - const auto userContext = reinterpret_cast(static_cast(frameId)); - - auto frame = CreateFrame(to, from, payload); - controller->SendFrame(EthernetFrame{frame}, userContext); - std::cout << "<< ETH Frame sent with userContext=" << userContext << std::endl; -} - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -int main(int argc, char** argv) -{ - EthernetMac WriterMacAddr = {0xF6, 0x04, 0x68, 0x71, 0xAA, 0xC1}; - EthernetMac BroadcastMacAddr = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"EthernetWriter\" or \"EthernetReader\" as ." << std::endl; - return -1; - } - - if (argc > 5) - { - std::cerr << "Too many arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"EthernetWriter\" or \"EthernetReader\" as ." << std::endl; - return -1; - } - - std::string participantName(argv[2]); - if (participantName != "EthernetWriter" && participantName != "EthernetReader") - { - std::cout << "Wrong participant name provided. Use either \"EthernetWriter\" or \"EthernetReader\"." - << std::endl; - return 1; - } - std::cout << "SIL Kit Version: " << SilKit::Version::String() << std::endl; - try - { - std::string participantConfigurationFilename(argv[1]); - std::string registryUri{"silkit://localhost:8500"}; - - bool runSync = true; - - // skip argv[0] and collect all arguments - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg.c_str(); - } - } - - using SilKit::CreateParticipant; - using SilKit::Config::ParticipantConfigurationFromFile; - - auto participantConfiguration = ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = CreateParticipant(participantConfiguration, participantName, registryUri); - auto* ethernetController = participant->CreateEthernetController("Eth1", "Eth1"); - - ethernetController->AddFrameHandler(&FrameHandler); - ethernetController->AddFrameTransmitHandler(&FrameTransmitHandler); - - auto operationMode = (runSync ? OperationMode::Coordinated : OperationMode::Autonomous); - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - - // Observe state changes - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - lifecycleService->SetAbortHandler( - [](auto lastState) { std::cout << "Abort handler called while in state " << lastState << std::endl; }); - - if (runSync) - { - auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - - // Set a CommunicationReady Handler - lifecycleService->SetCommunicationReadyHandler([&participantName, ethernetController]() { - std::cout << "Communication ready handler called for " << participantName << std::endl; - ethernetController->Activate(); - }); - - if (participantName == "EthernetWriter") - { - timeSyncService->SetSimulationStepHandler( - [ethernetController, WriterMacAddr, destinationAddress = BroadcastMacAddr]( - std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - std::cout << "now=" << std::chrono::duration_cast(now).count() << "ms" - << std::endl; - SendFrame(ethernetController, WriterMacAddr, destinationAddress); - std::this_thread::sleep_for(300ms); - }, - 1ms); - } - else - { - timeSyncService->SetSimulationStepHandler( - [](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - std::cout << "now=" << std::chrono::duration_cast(now).count() << "ms" - << std::endl; - std::this_thread::sleep_for(300ms); - }, 1ms); - } - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::promise startHandlerPromise; - auto startHandlerFuture = startHandlerPromise.get_future(); - std::atomic isStopRequested = {false}; - std::thread workerThread; - - lifecycleService->SetCommunicationReadyHandler([&]() { - std::cout << "Communication ready handler called for " << participantName << std::endl; - workerThread = std::thread{[&]() { - startHandlerFuture.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - if (participantName == "EthernetWriter") - { - SendFrame(ethernetController, WriterMacAddr, BroadcastMacAddr); - } - std::this_thread::sleep_for(1s); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - ethernetController->Activate(); - }); - - lifecycleService->SetStartingHandler([&]() { startHandlerPromise.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - catch (const SilKit::ConfigurationError& error) - { - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; - } - - return 0; -} diff --git a/Demos/Flexray/CMakeLists.txt b/Demos/Flexray/CMakeLists.txt deleted file mode 100644 index 4fdab4fac..000000000 --- a/Demos/Flexray/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2022 Vector Informatik GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -make_silkit_demo(SilKitDemoFlexray FlexrayDemo.cpp) - -target_sources(SilKitDemoFlexray - PRIVATE DemoFlexray.silkit.yaml - PRIVATE NetworkSimulatorConfig.yaml -) diff --git a/Demos/Flexray/DemoFlexray.silkit.yaml b/Demos/Flexray/DemoFlexray.silkit.yaml deleted file mode 100644 index 6fbf42eba..000000000 --- a/Demos/Flexray/DemoFlexray.silkit.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Sample configuration for FlexRay -Logging: - Sinks: - - Level: Info - Type: Stdout diff --git a/Demos/Flexray/FlexrayDemo.cpp b/Demos/Flexray/FlexrayDemo.cpp deleted file mode 100644 index c34767661..000000000 --- a/Demos/Flexray/FlexrayDemo.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include -#include -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" -#include "silkit/services/flexray/all.hpp" -#include "silkit/services/flexray/string_utils.hpp" - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Flexray; - -using namespace std::chrono_literals; -using namespace std::placeholders; - -std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) -{ - auto seconds = std::chrono::duration_cast>>(timestamp); - out << seconds.count() << "s"; - return out; -} - -template -void ReceiveMessage(IFlexrayController* /*controller*/, const T& t) -{ - std::cout << ">> " << t << "\n"; -} - -struct FlexrayNode -{ - FlexrayNode(IFlexrayController* controller, FlexrayControllerConfig config) - : controller{controller} - , controllerConfig{std::move(config)} - { - oldPocStatus.state = FlexrayPocState::DefaultConfig; - } - - void SetStartupDelay(std::chrono::nanoseconds delay) - { - _startupDelay = delay; - } - - void Init() - { - if (_configureCalled) - return; - - controller->Configure(controllerConfig); - _configureCalled = true; - } - - void doAction(std::chrono::nanoseconds now) - { - if (now < _startupDelay) - return; - switch (oldPocStatus.state) - { - case FlexrayPocState::DefaultConfig: - Init(); - case FlexrayPocState::Ready: - return pocReady(now); - case FlexrayPocState::NormalActive: - if (now == 100ms + std::chrono::duration_cast(_startupDelay)) - { - return ReconfigureTxBuffers(); - } - else - { - return txBufferUpdate(now); - } - case FlexrayPocState::Config: - case FlexrayPocState::Startup: - case FlexrayPocState::Wakeup: - case FlexrayPocState::NormalPassive: - case FlexrayPocState::Halt: - return; - } - } - - void pocReady(std::chrono::nanoseconds /*now*/) - { - switch (busState) - { - case MasterState::PerformWakeup: - controller->Wakeup(); - return; - case MasterState::WaitForWakeup: - return; - case MasterState::WakeupDone: - controller->AllowColdstart(); - controller->Run(); - return; - default: - return; - } - } - - void txBufferUpdate(std::chrono::nanoseconds /*now*/) - { - if (controllerConfig.bufferConfigs.empty()) - return; - - static auto msgNumber = -1; - msgNumber++; - - auto bufferIdx = msgNumber % controllerConfig.bufferConfigs.size(); - - // prepare a friendly message as payload - std::stringstream payloadStream; - payloadStream << "FlexrayFrameEvent#" << std::setw(4) << msgNumber << "; bufferId=" << bufferIdx; - auto payloadString = payloadStream.str(); - - std::vector payloadBytes; - payloadBytes.resize(payloadString.size()); - - std::copy(payloadString.begin(), payloadString.end(), payloadBytes.begin()); - - FlexrayTxBufferUpdate update; - update.payload = payloadBytes; - update.payloadDataValid = true; - update.txBufferIndex = static_cast(bufferIdx); - - controller->UpdateTxBuffer(update); - } - - // Reconfigure buffers: Swap Channels A and B - void ReconfigureTxBuffers() - { - std::cout << "Reconfiguring TxBuffers. Swapping FlexrayChannel::A and FlexrayChannel::B\n"; - for (uint16_t idx = 0; idx < controllerConfig.bufferConfigs.size(); idx++) - { - auto&& bufferConfig = controllerConfig.bufferConfigs[idx]; - switch (bufferConfig.channels) - { - case FlexrayChannel::A: - bufferConfig.channels = FlexrayChannel::B; - controller->ReconfigureTxBuffer(idx, bufferConfig); - break; - case FlexrayChannel::B: - bufferConfig.channels = FlexrayChannel::A; - controller->ReconfigureTxBuffer(idx, bufferConfig); - break; - default: - break; - } - } - } - - void PocStatusHandler(IFlexrayController* /*controller*/, const FlexrayPocStatusEvent& pocStatus) - { - std::cout << ">> POC=" << pocStatus.state << ", Freeze=" << pocStatus.freeze - << ", Wakeup=" << pocStatus.wakeupStatus << ", Slot=" << pocStatus.slotMode - << " @t=" << pocStatus.timestamp << std::endl; - - if (oldPocStatus.state == FlexrayPocState::Wakeup && pocStatus.state == FlexrayPocState::Ready) - { - std::cout << " Wakeup finished..." << std::endl; - busState = MasterState::WakeupDone; - } - - oldPocStatus = pocStatus; - } - - void WakeupHandler(IFlexrayController* frController, const FlexrayWakeupEvent& flexrayWakeupEvent) - { - std::cout << ">> WAKEUP! (" << flexrayWakeupEvent.pattern << ")" << std::endl; - frController->AllowColdstart(); - frController->Run(); - } - - - IFlexrayController* controller = nullptr; - - FlexrayControllerConfig controllerConfig; - FlexrayPocStatusEvent oldPocStatus{}; - bool _configureCalled = false; - std::chrono::nanoseconds _startupDelay = 0ns; - - enum class MasterState - { - Ignore, - PerformWakeup, - WaitForWakeup, - WakeupDone - }; - MasterState busState = MasterState::Ignore; -}; - - -auto MakeNodeParams(const std::string& participantName) -> FlexrayNodeParameters -{ - FlexrayNodeParameters nodeParams; - nodeParams.pAllowHaltDueToClock = 1; - nodeParams.pAllowPassiveToActive = 0; - nodeParams.pChannels = FlexrayChannel::AB; - nodeParams.pClusterDriftDamping = 2; - nodeParams.pdAcceptedStartupRange = 212; - nodeParams.pdListenTimeout = 400162; - nodeParams.pKeySlotOnlyEnabled = 0; - nodeParams.pKeySlotUsedForStartup = 1; - nodeParams.pKeySlotUsedForSync = 0; - nodeParams.pLatestTx = 249; - nodeParams.pMacroInitialOffsetA = 3; - nodeParams.pMacroInitialOffsetB = 3; - nodeParams.pMicroInitialOffsetA = 6; - nodeParams.pMicroInitialOffsetB = 6; - nodeParams.pMicroPerCycle = 200000; - nodeParams.pOffsetCorrectionOut = 127; - nodeParams.pOffsetCorrectionStart = 3632; - nodeParams.pRateCorrectionOut = 81; - nodeParams.pWakeupChannel = FlexrayChannel::A; - nodeParams.pWakeupPattern = 33; - nodeParams.pdMicrotick = FlexrayClockPeriod::T25NS; - nodeParams.pSamplesPerMicrotick = 2; - - if (participantName == "Node0") - { - nodeParams.pKeySlotId = 40; - } - else if (participantName == "Node1") - { - nodeParams.pKeySlotId = 60; - } - else - { - throw std::runtime_error("Invalid participant name."); - } - - return nodeParams; -} - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -int main(int argc, char** argv) -{ - FlexrayClusterParameters clusterParams; - clusterParams.gColdstartAttempts = 8; - clusterParams.gCycleCountMax = 63; - clusterParams.gdActionPointOffset = 2; - clusterParams.gdDynamicSlotIdlePhase = 1; - clusterParams.gdMiniSlot = 5; - clusterParams.gdMiniSlotActionPointOffset = 2; - clusterParams.gdStaticSlot = 31; - clusterParams.gdSymbolWindow = 0; - clusterParams.gdSymbolWindowActionPointOffset = 1; - clusterParams.gdTSSTransmitter = 9; - clusterParams.gdWakeupTxActive = 60; - clusterParams.gdWakeupTxIdle = 180; - clusterParams.gListenNoise = 2; - clusterParams.gMacroPerCycle = 3636; - clusterParams.gMaxWithoutClockCorrectionFatal = 2; - clusterParams.gMaxWithoutClockCorrectionPassive = 2; - clusterParams.gNumberOfMiniSlots = 291; - clusterParams.gNumberOfStaticSlots = 70; - clusterParams.gPayloadLengthStatic = 13; - clusterParams.gSyncFrameIDCountMax = 15; - - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri]" << std::endl - << "Use \"Node0\" or \"Node1\" as ." << std::endl; - return -1; - } - - try - { - std::string participantConfigurationFilename(argv[1]); - std::string participantName(argv[2]); - - auto registryUri = "silkit://localhost:8500"; - if (argc >= 4) - { - registryUri = argv[3]; - } - - auto participantConfiguration = - SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - auto* controller = participant->CreateFlexrayController("FlexRay1", "PowerTrain1"); - auto* lifecycleService = participant->CreateLifecycleService({OperationMode::Coordinated}); - auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - - std::vector bufferConfigs; - - if (participantName == "Node0") - { - // initialize bufferConfig to send some FrMessages - FlexrayTxBufferConfig cfg; - cfg.channels = FlexrayChannel::AB; - cfg.slotId = 40; - cfg.offset = 0; - cfg.repetition = 1; - cfg.hasPayloadPreambleIndicator = false; - cfg.headerCrc = 5; - cfg.transmissionMode = FlexrayTransmissionMode::SingleShot; - bufferConfigs.push_back(cfg); - - cfg.channels = FlexrayChannel::A; - cfg.slotId = 41; - bufferConfigs.push_back(cfg); - - cfg.channels = FlexrayChannel::B; - cfg.slotId = 42; - bufferConfigs.push_back(cfg); - } - else if (participantName == "Node1") - { - // initialize bufferConfig to send some FrMessages - FlexrayTxBufferConfig cfg; - cfg.channels = FlexrayChannel::AB; - cfg.slotId = 60; - cfg.offset = 0; - cfg.repetition = 1; - cfg.hasPayloadPreambleIndicator = false; - cfg.headerCrc = 5; - cfg.transmissionMode = FlexrayTransmissionMode::SingleShot; - bufferConfigs.push_back(cfg); - - cfg.channels = FlexrayChannel::A; - cfg.slotId = 61; - bufferConfigs.push_back(cfg); - - cfg.channels = FlexrayChannel::B; - cfg.slotId = 62; - bufferConfigs.push_back(cfg); - } - - FlexrayControllerConfig config; - config.bufferConfigs = bufferConfigs; - - config.clusterParams = clusterParams; - - config.nodeParams = MakeNodeParams(participantName); - - FlexrayNode frNode(controller, std::move(config)); - if (participantName == "Node0") - { - frNode.busState = FlexrayNode::MasterState::PerformWakeup; - } - else if (participantName == "Node1") - { - frNode.busState = FlexrayNode::MasterState::PerformWakeup; - frNode.SetStartupDelay(0ms); - } - else - { - std::cout << "Wrong participant name provided. Use either \"Node0\" or \"Node1\"." << std::endl; - return 1; - } - - controller->AddPocStatusHandler( - [&frNode](IFlexrayController* frController, const FlexrayPocStatusEvent& pocStatusEvent) { - frNode.PocStatusHandler(frController, pocStatusEvent); - }); - controller->AddFrameHandler(&ReceiveMessage); - controller->AddFrameTransmitHandler(&ReceiveMessage); - controller->AddWakeupHandler( - [&frNode](IFlexrayController* frController, const FlexrayWakeupEvent& wakeupEvent) { - frNode.WakeupHandler(frController, wakeupEvent); - }); - controller->AddSymbolHandler(&ReceiveMessage); - controller->AddSymbolTransmitHandler(&ReceiveMessage); - controller->AddCycleStartHandler(&ReceiveMessage); - - timeSyncService->SetSimulationStepHandler( - [&frNode](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - frNode.doAction(now); - std::this_thread::sleep_for(500ms); - }, 1ms); - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - catch (const SilKit::ConfigurationError& error) - { - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; - } - - return 0; -} diff --git a/Demos/Flexray/NetworkSimulatorConfig.yaml b/Demos/Flexray/NetworkSimulatorConfig.yaml deleted file mode 100644 index adbc7a593..000000000 --- a/Demos/Flexray/NetworkSimulatorConfig.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Network simulator configuration for a detailed simulation of the FlexRay Demo -SchemaVersion: '1' -SimulatedNetworks: - - Name: PowerTrain1 - Type: FlexRay diff --git a/Demos/Lin/CMakeLists.txt b/Demos/Lin/CMakeLists.txt deleted file mode 100644 index 63dc7dbbe..000000000 --- a/Demos/Lin/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2022 Vector Informatik GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -make_silkit_demo(SilKitDemoLin LinDemo.cpp) -target_sources(SilKitDemoLin - PRIVATE DemoLin.silkit.yaml - PRIVATE NetworkSimulatorConfig.yaml -) - -make_silkit_demo(SilKitDemoLinDynamic LinDemoDynamic.cpp) -target_sources(SilKitDemoLinDynamic - PRIVATE DemoLin.silkit.yaml - PRIVATE NetworkSimulatorConfig.yaml -) - diff --git a/Demos/Lin/DemoLin.silkit.yaml b/Demos/Lin/DemoLin.silkit.yaml deleted file mode 100644 index ffd73f56e..000000000 --- a/Demos/Lin/DemoLin.silkit.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Sample configuration for LIN -Logging: - Sinks: - - Level: Info - Type: Stdout diff --git a/Demos/Lin/LinDemo.cpp b/Demos/Lin/LinDemo.cpp deleted file mode 100644 index c632ac137..000000000 --- a/Demos/Lin/LinDemo.cpp +++ /dev/null @@ -1,642 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/string_utils.hpp" -#include "silkit/services/lin/all.hpp" -#include "silkit/services/lin/string_utils.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Lin; - -using namespace std::chrono_literals; -using namespace std::placeholders; - -std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) -{ - auto seconds = std::chrono::duration_cast>(timestamp); - out << seconds.count() << "ms"; - return out; -} - -class Timer -{ -public: - void Set(std::chrono::nanoseconds timeOut, std::function action) noexcept - { - _isActive = true; - _timeOut = timeOut; - _action = std::move(action); - } - void Clear() noexcept - { - _isActive = false; - _timeOut = std::chrono::nanoseconds::max(); - _action = std::function{}; - } - auto ExecuteAction(std::chrono::nanoseconds now) -> bool - { - if (!_isActive || (now < _timeOut)) - { - return false; - } - - auto action = std::move(_action); - Clear(); - action(now); - - return true; - } - -private: - bool _isActive = false; - std::chrono::nanoseconds _timeOut = std::chrono::nanoseconds::max(); - std::function _action; -}; - -class Schedule -{ -public: - Schedule() = default; - Schedule( - std::initializer_list>> tasks) - { - for (auto&& task : tasks) - { - _schedule.emplace_back(task.first, task.second); - } - Reset(); - } - - void Reset() - { - _nextTask = _schedule.begin(); - ScheduleNextTask(); - } - - void ExecuteTask(std::chrono::nanoseconds now) - { - _now = now; - if (_timer.ExecuteAction(now)) - { - ScheduleNextTask(); - } - } - -private: - void ScheduleNextTask() - { - auto currentTask = _nextTask++; - if (_nextTask == _schedule.end()) - { - _nextTask = _schedule.begin(); - } - - _timer.Set(_now + currentTask->delay, currentTask->action); - } - - struct Task - { - Task(std::chrono::nanoseconds delay, std::function action) - : delay{delay} - , action{action} - { - } - - std::chrono::nanoseconds delay; - std::function action; - }; - - Timer _timer; - std::vector _schedule; - std::vector::iterator _nextTask; - std::chrono::nanoseconds _now = 0ns; -}; - -class LinMaster -{ -public: - LinMaster(ILinController* controller) - : controller{controller} - { - schedule = {{10ms, [this](std::chrono::nanoseconds now) { SendFrame_16(now); }}, - {20ms, [this](std::chrono::nanoseconds now) { SendFrame_17(now); }}, - {10ms, [this](std::chrono::nanoseconds now) { SendFrame_18(now); }}, - {10ms, [this](std::chrono::nanoseconds now) { SendFrame_19(now); }}, - {10ms, [this](std::chrono::nanoseconds now) { SendFrame_34(now); }}, - {10ms, [this](std::chrono::nanoseconds /*now*/) { GoToSleep(); }}}; - } - - void DoAction(std::chrono::nanoseconds now) - { - schedule.ExecuteTask(now); - } - - void SendFrame_16(std::chrono::nanoseconds /*now*/) - { - LinFrame frame; - frame.id = 16; - frame.checksumModel = LinChecksumModel::Classic; - frame.dataLength = 6; - frame.data = std::array{1, 6, 1, 6, 1, 6, 1, 6}; - - SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); - } - - void SendFrame_17(std::chrono::nanoseconds /*now*/) - { - LinFrame frame; - frame.id = 17; - frame.checksumModel = LinChecksumModel::Classic; - frame.dataLength = 6; - frame.data = std::array{1, 7, 1, 7, 1, 7, 1, 7}; - - SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); - } - - void SendFrame_18(std::chrono::nanoseconds /*now*/) - { - LinFrame frame; - frame.id = 18; - frame.checksumModel = LinChecksumModel::Enhanced; - frame.dataLength = 8; - frame.data = std::array{0}; - - SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); - } - - void SendFrame_19(std::chrono::nanoseconds /*now*/) - { - LinFrame frame; - frame.id = 19; - frame.checksumModel = LinChecksumModel::Classic; - frame.dataLength = 8; - frame.data = std::array{0}; - - SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); - } - - void SendFrame_34(std::chrono::nanoseconds /*now*/) - { - LinFrame frame; - frame.id = 34; - frame.checksumModel = LinChecksumModel::Enhanced; - frame.dataLength = 6; - - SendFrameIfOperational(frame, LinFrameResponseType::SlaveResponse); - } - - void GoToSleep() - { - std::cout << "<< Sending Go-To-Sleep Command and entering sleep state" << std::endl; - controller->GoToSleep(); - } - - - void FrameStatusHandler(ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) - { - switch (frameStatusEvent.status) - { - case LinFrameStatus::LIN_RX_OK: - break; // good case, no need to warn - case LinFrameStatus::LIN_TX_OK: - break; // good case, no need to warn - default: - std::cout << "WARNING: LIN transmission failed!" << std::endl; - } - - std::cout << ">> " << frameStatusEvent.frame << " status=" << frameStatusEvent.status << std::endl; - } - - void WakeupHandler(ILinController* linController, const LinWakeupEvent& wakeupEvent) - { - if (linController->Status() != LinControllerStatus::Sleep) - { - std::cout << "WARNING: Received Wakeup pulse while LinControllerStatus is " << linController->Status() - << "." << std::endl; - } - - std::cout << ">> Wakeup pulse received; direction=" << wakeupEvent.direction << std::endl; - linController->WakeupInternal(); - } - -private: - void SendFrameIfOperational(const LinFrame& linFrame, LinFrameResponseType responseType) - { - const auto linId{static_cast(linFrame.id)}; - - if (controller->Status() != LinControllerStatus::Operational) - { - std::cout << "!! LIN Frame with ID=" << linId << " not sent, since the controller is not operational" - << std::endl; - return; - } - - controller->SendFrame(linFrame, responseType); - - if (responseType == LinFrameResponseType::SlaveResponse) - { - std::cout << "<< LIN Frame Header sent for ID=" << linId << std::endl; - } - else - { - std::cout << "<< LIN Frame sent with ID=" << linId << std::endl; - } - } - -private: - ILinController* controller{nullptr}; - Schedule schedule; -}; - - -class LinSlave -{ -public: - LinSlave() {} - - void DoAction(std::chrono::nanoseconds now_) - { - now = now_; - timer.ExecuteAction(now); - } - - void UpdateTxBuffer_LinId34(ILinController* linController) - { - LinFrame frame34; - frame34.id = 34; - frame34.checksumModel = LinChecksumModel::Enhanced; - frame34.dataLength = 6; - frame34.data = {static_cast(rand() % 10), 0, 0, 0, 0, 0, 0, 0}; - linController->UpdateTxBuffer(frame34); - } - - void FrameStatusHandler(ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) - { - // On a TX acknowledge for ID 34, update the TxBuffer for the next transmission - if (frameStatusEvent.frame.id == 34) - { - UpdateTxBuffer_LinId34(linController); - } - - std::cout << ">> " << frameStatusEvent.frame << " status=" << frameStatusEvent.status - << " timestamp=" << frameStatusEvent.timestamp << std::endl; - } - - void GoToSleepHandler(ILinController* linController, const LinGoToSleepEvent& /*goToSleepEvent*/) - { - std::cout << "LIN Slave received go-to-sleep command; entering sleep mode." << std::endl; - // wakeup in 15 ms - timer.Set(now + 15ms, [linController](std::chrono::nanoseconds tnow) { - std::cout << "<< Wakeup pulse @" << tnow << std::endl; - linController->Wakeup(); - }); - linController->GoToSleepInternal(); - } - - void WakeupHandler(ILinController* linController, const LinWakeupEvent& wakeupEvent) - { - std::cout << "LIN Slave received wakeup pulse; direction=" << wakeupEvent.direction - << "; Entering normal operation mode." << std::endl; - - // No need to set the controller status if we sent the wakeup - if (wakeupEvent.direction == SilKit::Services::TransmitDirection::RX) - { - linController->WakeupInternal(); - } - } - -private: - Timer timer; - std::chrono::nanoseconds now{0ns}; -}; - -void InitLinMaster(ILinController* linController, std::string participantName) -{ - std::cout << "Initializing " << participantName << std::endl; - - LinControllerConfig config; - config.controllerMode = LinControllerMode::Master; - config.baudRate = 20'000; - linController->Init(config); -} - -void InitLinSlave(ILinController* linController, std::string participantName) -{ - std::cout << "Initializing " << participantName << std::endl; - - // Configure LIN Controller to receive a LinFrameResponse for LIN ID 16 - LinFrameResponse response_16; - response_16.frame.id = 16; - response_16.frame.checksumModel = LinChecksumModel::Classic; - response_16.frame.dataLength = 6; - response_16.responseMode = LinFrameResponseMode::Rx; - - // Configure LIN Controller to receive a LinFrameResponse for LIN ID 17 - // - This LinFrameResponseMode::Unused causes the controller to ignore - // this message and not trigger a callback. This is also the default. - LinFrameResponse response_17; - response_17.frame.id = 17; - response_17.frame.checksumModel = LinChecksumModel::Classic; - response_17.frame.dataLength = 6; - response_17.responseMode = LinFrameResponseMode::Unused; - - // Configure LIN Controller to receive LIN ID 18 - // - LinChecksumModel does not match with master --> Receive with LIN_RX_ERROR - LinFrameResponse response_18; - response_18.frame.id = 18; - response_18.frame.checksumModel = LinChecksumModel::Classic; - response_18.frame.dataLength = 8; - response_18.responseMode = LinFrameResponseMode::Rx; - - // Configure LIN Controller to receive LIN ID 19 - // - dataLength does not match with master --> Receive with LIN_RX_ERROR - LinFrameResponse response_19; - response_19.frame.id = 19; - response_19.frame.checksumModel = LinChecksumModel::Enhanced; - response_19.frame.dataLength = 1; - response_19.responseMode = LinFrameResponseMode::Rx; - - // Configure LIN Controller to send a LinFrameResponse for LIN ID 34 - LinFrameResponse response_34; - response_34.frame.id = 34; - response_34.frame.checksumModel = LinChecksumModel::Enhanced; - response_34.frame.dataLength = 6; - response_34.frame.data = std::array{3, 4, 3, 4, 3, 4, 3, 4}; - response_34.responseMode = LinFrameResponseMode::TxUnconditional; - - LinControllerConfig config; - config.controllerMode = LinControllerMode::Slave; - config.baudRate = 20'000; - config.frameResponses.push_back(response_16); - config.frameResponses.push_back(response_17); - config.frameResponses.push_back(response_18); - config.frameResponses.push_back(response_19); - config.frameResponses.push_back(response_34); - - linController->Init(config); -} - - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -int main(int argc, char** argv) -try -{ - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"LinMaster\" or \"LinSlave\" as ." << std::endl; - return -1; - } - - std::string participantConfigurationFilename(argv[1]); - std::string participantName(argv[2]); - - std::string registryUri = "silkit://localhost:8500"; - - bool runSync = true; - - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg; - } - } - - auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - - auto operationMode = (runSync ? OperationMode::Coordinated : OperationMode::Autonomous); - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - auto* timeSyncService = (runSync ? lifecycleService->CreateTimeSyncService() : nullptr); - auto* linController = participant->CreateLinController("LIN1", "LIN1"); - - // Observe state changes - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - lifecycleService->SetAbortHandler( - [](auto lastState) { std::cout << "Abort handler called while in state " << lastState << std::endl; }); - - LinMaster master{linController}; - LinSlave slave; - - if (participantName == "LinMaster") - { - linController->AddFrameStatusHandler( - [&master](ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) { - master.FrameStatusHandler(linController, frameStatusEvent); - }); - linController->AddWakeupHandler([&master](ILinController* linController, const LinWakeupEvent& wakeupEvent) { - master.WakeupHandler(linController, wakeupEvent); - }); - - if (runSync) - { - lifecycleService->SetCommunicationReadyHandler( - [&participantName, linController]() { InitLinMaster(linController, participantName); }); - timeSyncService->SetSimulationStepHandler( - [&master](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - - master.DoAction(now); - - std::this_thread::sleep_for(100ms); - }, 1ms); - - auto lifecycleFuture = lifecycleService->StartLifecycle(); - auto finalState = lifecycleFuture.get(); - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::atomic isStopRequested = {false}; - std::thread workerThread; - auto now = 0ms; - - std::promise startHandlerPromise; - auto startHandlerFuture = startHandlerPromise.get_future(); - - lifecycleService->SetCommunicationReadyHandler([&]() { - InitLinMaster(linController, participantName); - workerThread = std::thread{[&]() { - startHandlerFuture.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - master.DoAction(now); - now += 1ms; - std::this_thread::sleep_for(100ms); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->SetStartingHandler([&]() { startHandlerPromise.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - else if (participantName == "LinSlave") - { - linController->AddFrameStatusHandler( - [&slave](ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) { - slave.FrameStatusHandler(linController, frameStatusEvent); - }); - linController->AddGoToSleepHandler( - [&slave](ILinController* linController, const LinGoToSleepEvent& goToSleepEvent) { - slave.GoToSleepHandler(linController, goToSleepEvent); - }); - linController->AddWakeupHandler([&slave](ILinController* linController, const LinWakeupEvent& wakeupEvent) { - slave.WakeupHandler(linController, wakeupEvent); - }); - - if (runSync) - { - lifecycleService->SetCommunicationReadyHandler( - [&participantName, linController]() { InitLinSlave(linController, participantName); }); - timeSyncService->SetSimulationStepHandler( - [&slave](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - std::cout << "now=" << std::chrono::duration_cast(now).count() << "ms" - << std::endl; - slave.DoAction(now); - - std::this_thread::sleep_for(100ms); - }, 1ms); - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::atomic isStopRequested = {false}; - std::thread workerThread; - auto now = 0ms; - std::promise startHandlerPromise; - auto startHandlerFuture = startHandlerPromise.get_future(); - - lifecycleService->SetCommunicationReadyHandler([&]() { - InitLinSlave(linController, participantName); - workerThread = std::thread{[&]() { - startHandlerFuture.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - slave.DoAction(now); - now += 1ms; - std::this_thread::sleep_for(100ms); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->SetStartingHandler([&]() { startHandlerPromise.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - else - { - std::cout << "Wrong participant name provided. Use either \"LinMaster\" or \"LinSlave\"." << std::endl; - return 1; - } - - - return 0; -} -catch (const SilKit::ConfigurationError& error) -{ - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; -} -catch (const std::exception& error) -{ - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; -} diff --git a/Demos/Lin/LinDemoDynamic.cpp b/Demos/Lin/LinDemoDynamic.cpp deleted file mode 100644 index 083dfc675..000000000 --- a/Demos/Lin/LinDemoDynamic.cpp +++ /dev/null @@ -1,617 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/string_utils.hpp" -#include "silkit/services/lin/all.hpp" -#include "silkit/services/lin/string_utils.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" -#include "silkit/experimental/services/lin/LinControllerExtensions.hpp" - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Lin; - -using namespace std::chrono_literals; -using namespace std::placeholders; - -std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) -{ - auto seconds = std::chrono::duration_cast>(timestamp); - out << seconds.count() << "ms"; - return out; -} - -class Timer -{ -public: - void Set(std::chrono::nanoseconds timeOut, std::function action) noexcept - { - _isActive = true; - _timeOut = timeOut; - _action = std::move(action); - } - void Clear() noexcept - { - _isActive = false; - _timeOut = std::chrono::nanoseconds::max(); - _action = std::function{}; - } - void ExecuteAction(std::chrono::nanoseconds now) - { - if (!_isActive || (now < _timeOut)) - return; - - auto action = std::move(_action); - Clear(); - action(now); - } - -private: - bool _isActive = false; - std::chrono::nanoseconds _timeOut = std::chrono::nanoseconds::max(); - std::function _action; -}; - -class Schedule -{ -public: - Schedule() = default; - Schedule( - std::initializer_list>> tasks) - { - for (auto&& task : tasks) - { - _schedule.emplace_back(task.first, task.second); - } - Reset(); - } - - void Reset() - { - _nextTask = _schedule.begin(); - ScheduleNextTask(); - } - - void ScheduleNextTask() - { - auto currentTask = _nextTask++; - if (_nextTask == _schedule.end()) - { - _nextTask = _schedule.begin(); - } - - _timer.Set(_now + currentTask->delay, currentTask->action); - } - - void ExecuteTask(std::chrono::nanoseconds now) - { - _now = now; - _timer.ExecuteAction(now); - } - -private: - struct Task - { - Task(std::chrono::nanoseconds delay, std::function action) - : delay{delay} - , action{action} - { - } - - std::chrono::nanoseconds delay; - std::function action; - }; - - Timer _timer; - std::vector _schedule; - std::vector::iterator _nextTask; - std::chrono::nanoseconds _now = 0ns; -}; - -class LinMaster -{ -public: - LinMaster(ILinController* controller) - : controller{controller} - { - schedule = { - {5ms, [this](std::chrono::nanoseconds now) { SendFrameHeader(now, 16); }}, - {5ms, [this](std::chrono::nanoseconds now) { SendFrameHeader(now, 17); }}, - {5ms, [this](std::chrono::nanoseconds now) { SendFrameHeader(now, 18); }}, - {5ms, [this](std::chrono::nanoseconds now) { SendFrameHeader(now, 19); }}, - {5ms, [this](std::chrono::nanoseconds now) { SendFrameHeader(now, 34); }}, - {5ms, [this](std::chrono::nanoseconds now) { GoToSleep(now); }}, - }; - - LinFrame f16{}; - f16.id = 16; - f16.checksumModel = LinChecksumModel::Classic; - f16.dataLength = 6; - f16.data = std::array{1, 6, 1, 6, 1, 6, 1, 6}; - _masterResponses[f16.id] = f16; - - LinFrame f17{}; - f17.id = 17; - f17.checksumModel = LinChecksumModel::Classic; - f17.dataLength = 6; - f17.data = std::array{1, 7, 1, 7, 1, 7, 1, 7}; - _masterResponses[f17.id] = f17; - - LinFrame f18{}; - f18.id = 18; - f18.checksumModel = LinChecksumModel::Enhanced; - f18.dataLength = 8; - f18.data = std::array{0}; - _masterResponses[f18.id] = f18; - - LinFrame f19{}; - f19.id = 19; - f19.checksumModel = LinChecksumModel::Classic; - f19.dataLength = 8; - f19.data = std::array{0}; - _masterResponses[f19.id] = f19; - } - - void DoAction(std::chrono::nanoseconds now) - { - if (controller->Status() != LinControllerStatus::Operational) - return; - - schedule.ExecuteTask(now); - } - - void SendFrameHeader(std::chrono::nanoseconds now, LinId linId) - { - controller->SendFrameHeader(linId); - std::cout << "<< LIN Frame sent with ID=" << static_cast(linId) << " @" << now.count() << "ns" - << std::endl; - } - - void GoToSleep(std::chrono::nanoseconds now) - { - std::cout << "<< Sending Go-To-Sleep Command and entering sleep state @" << now.count() << "ns" << std::endl; - controller->GoToSleep(); - } - - void FrameStatusHandler(ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) - { - switch (frameStatusEvent.status) - { - case LinFrameStatus::LIN_RX_OK: - break; // good case, no need to warn - case LinFrameStatus::LIN_TX_OK: - break; // good case, no need to warn - default: - std::cout << "WARNING: LIN transmission failed!" << std::endl; - } - - std::cout << ">> " << frameStatusEvent.frame << " status=" << frameStatusEvent.status << std::endl; - schedule.ScheduleNextTask(); - } - - void WakeupHandler(ILinController* linController, const LinWakeupEvent& wakeupEvent) - { - if (linController->Status() != LinControllerStatus::Sleep) - { - std::cout << "WARNING: Received Wakeup pulse while LinControllerStatus is " << linController->Status() - << "." << std::endl; - } - - std::cout << ">> Wakeup pulse received; direction=" << wakeupEvent.direction << std::endl; - linController->WakeupInternal(); - schedule.ScheduleNextTask(); - } - - void OnFrameHeader(ILinController* linController, - const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& header) - { - std::cout << ">> Received Frame Header: id=" << (int)header.id << "@" << header.timestamp << std::endl; - - const auto it = _masterResponses.find(header.id); - if (it != _masterResponses.end()) - { - const auto& frame = it->second; - SilKit::Experimental::Services::Lin::SendDynamicResponse(linController, frame); - std::cout << "<< Sending dynamic response: id=" << static_cast(header.id) << std::endl; - } - else - { - std::cout << "!! Not sending dynamic response: id=" << static_cast(header.id) << std::endl; - } - } - -private: - ILinController* controller{nullptr}; - Schedule schedule; - std::unordered_map _masterResponses; -}; - -class LinSlave -{ -public: - LinSlave() - { - UpdateDynamicResponseTo34(); - } - - void DoAction(std::chrono::nanoseconds now_) - { - now = now_; - timer.ExecuteAction(now); - } - - void UpdateDynamicResponseTo34() - { - LinFrame f34{}; - f34.id = 34; - f34.checksumModel = LinChecksumModel::Enhanced; - f34.dataLength = 6; - f34.data = {static_cast(rand() % 10), 0, 0, 0, 0, 0, 0, 0}; - _slaveResponses[f34.id] = f34; - } - - void FrameStatusHandler(ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) - { - // On a TX acknowledge for ID 34, update the TxBuffer for the next transmission - if (frameStatusEvent.frame.id == 34) - { - UpdateDynamicResponseTo34(); - } - - std::cout << ">> " << frameStatusEvent.frame << " status=" << frameStatusEvent.status - << " timestamp=" << frameStatusEvent.timestamp << std::endl; - } - - void GoToSleepHandler(ILinController* linController, const LinGoToSleepEvent& /*goToSleepEvent*/) - { - std::cout << "LIN Slave received go-to-sleep command; entering sleep mode." << std::endl; - // wakeup in 10 ms - timer.Set(now + 10ms, [linController](std::chrono::nanoseconds tnow) { - std::cout << "<< Wakeup pulse @" << tnow << std::endl; - linController->Wakeup(); - }); - linController->GoToSleepInternal(); - } - - void WakeupHandler(ILinController* linController, const LinWakeupEvent& wakeupEvent) - { - std::cout << "LIN Slave received wakeup pulse; direction=" << wakeupEvent.direction - << "; Entering normal operation mode." << std::endl; - - // No need to set the controller status if we sent the wakeup - if (wakeupEvent.direction == SilKit::Services::TransmitDirection::RX) - { - linController->WakeupInternal(); - } - } - - void OnFrameHeader(ILinController* linController, - const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& header) - { - std::cout << "<< Received Frame Header: id=" << (int)header.id << "@" << header.timestamp << std::endl; - - const auto it = _slaveResponses.find(header.id); - if (it != _slaveResponses.end()) - { - const auto& frame = it->second; - SilKit::Experimental::Services::Lin::SendDynamicResponse(linController, frame); - std::cout << ">> Sending dynamic response: id=" << static_cast(header.id) << std::endl; - } - else - { - std::cout << "!! Not sending dynamic response: id=" << static_cast(header.id) << std::endl; - } - } - -private: - Timer timer; - std::chrono::nanoseconds now{0ns}; - std::unordered_map _slaveResponses; -}; - -void InitLinMaster(ILinController* linController, std::string participantName) -{ - std::cout << "Initializing " << participantName << std::endl; - - SilKit::Experimental::Services::Lin::LinControllerDynamicConfig config; - config.controllerMode = LinControllerMode::Master; - config.baudRate = 20'000; - - SilKit::Experimental::Services::Lin::InitDynamic(linController, config); -} - -void InitLinSlave(ILinController* linController, std::string participantName) -{ - std::cout << "Initializing " << participantName << std::endl; - - SilKit::Experimental::Services::Lin::LinControllerDynamicConfig config{}; - config.controllerMode = LinControllerMode::Slave; - config.baudRate = 20'000; - - SilKit::Experimental::Services::Lin::InitDynamic(linController, config); -} - - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -int main(int argc, char** argv) -try -{ - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"LinMaster\" or \"LinSlave\" as ." << std::endl; - return -1; - } - - std::string participantConfigurationFilename(argv[1]); - std::string participantName(argv[2]); - - std::string registryUri = "silkit://localhost:8500"; - - bool runSync = true; - - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg; - } - } - - auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - - ILifecycleService* lifecycleService{nullptr}; - ITimeSyncService* timeSyncService{nullptr}; - - if (runSync) - { - lifecycleService = participant->CreateLifecycleService({OperationMode::Coordinated}); - timeSyncService = lifecycleService->CreateTimeSyncService(); - } - else - { - lifecycleService = participant->CreateLifecycleService({OperationMode::Autonomous}); - } - - auto* linController = participant->CreateLinController("LIN1", "LIN1"); - - // Set a Stop and Shutdown Handler - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - - LinMaster master{linController}; - LinSlave slave; - - if (participantName == "LinMaster") - { - if (runSync) - { - lifecycleService->SetCommunicationReadyHandler( - [&participantName, linController]() { InitLinMaster(linController, participantName); }); - } - - linController->AddFrameStatusHandler( - [&master](ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) { - master.FrameStatusHandler(linController, frameStatusEvent); - }); - linController->AddWakeupHandler([&master](ILinController* linController, const LinWakeupEvent& wakeupEvent) { - master.WakeupHandler(linController, wakeupEvent); - }); - - SilKit::Experimental::Services::Lin::AddFrameHeaderHandler( - linController, [&master](ILinController* linController, - const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& event) { - master.OnFrameHeader(linController, event); - }); - - if (runSync) - { - timeSyncService->SetSimulationStepHandler( - [&master](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - - master.DoAction(now); - }, 1ms); - - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - InitLinMaster(linController, participantName); - - std::atomic isStopRequested = {false}; - std::thread workerThread; - auto now = 0ms; - - lifecycleService->SetStartingHandler([&] { - workerThread = std::thread{[&]() { - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - master.DoAction(now); - now += 1ms; - std::this_thread::sleep_for(200ms); - } - - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->StartLifecycle(); - - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - else if (participantName == "LinSlave") - { - if (runSync) - { - lifecycleService->SetCommunicationReadyHandler( - [&participantName, linController]() { InitLinSlave(linController, participantName); }); - } - - linController->AddFrameStatusHandler( - [&slave](ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) { - slave.FrameStatusHandler(linController, frameStatusEvent); - }); - linController->AddGoToSleepHandler( - [&slave](ILinController* linController, const LinGoToSleepEvent& goToSleepEvent) { - slave.GoToSleepHandler(linController, goToSleepEvent); - }); - linController->AddWakeupHandler([&slave](ILinController* linController, const LinWakeupEvent& wakeupEvent) { - slave.WakeupHandler(linController, wakeupEvent); - }); - - SilKit::Experimental::Services::Lin::AddFrameHeaderHandler( - linController, [&slave](ILinController* linController, - const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& event) { - slave.OnFrameHeader(linController, event); - }); - - if (runSync) - { - timeSyncService->SetSimulationStepHandler( - [&slave](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - std::cout << "now=" << std::chrono::duration_cast(now).count() << "ms" - << std::endl; - slave.DoAction(now); - - std::this_thread::sleep_for(100ms); - }, 1ms); - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - InitLinSlave(linController, participantName); - - std::atomic isStopRequested = {false}; - std::thread workerThread; - auto now = 0ms; - - lifecycleService->SetStartingHandler([&] { - workerThread = std::thread{[&]() { - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - slave.DoAction(now); - now += 1ms; - std::this_thread::sleep_for(200ms); - } - - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->StartLifecycle(); - - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - else - { - std::cout << "Wrong participant name provided. Use either \"LinMaster\" or \"LinSlave\"." << std::endl; - return 1; - } - - - return 0; -} -catch (const SilKit::ConfigurationError& error) -{ - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; -} -catch (const std::exception& error) -{ - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; -} diff --git a/Demos/Lin/NetworkSimulatorConfig.yaml b/Demos/Lin/NetworkSimulatorConfig.yaml deleted file mode 100644 index 8cec65fa4..000000000 --- a/Demos/Lin/NetworkSimulatorConfig.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Network simulator configuration for a detailed simulation of the LIN Demo -SchemaVersion: '1' -SimulatedNetworks: - - Name: LIN1 - Type: LIN diff --git a/Demos/PubSub/CMakeLists.txt b/Demos/PubSub/CMakeLists.txt deleted file mode 100644 index 4b7d03d6b..000000000 --- a/Demos/PubSub/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2022 Vector Informatik GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -make_silkit_demo(SilKitDemoPubSub PubSubDemo.cpp) -target_sources(SilKitDemoPubSub - PRIVATE DemoPubSub.silkit.yaml -) diff --git a/Demos/PubSub/DemoPubSub.silkit.yaml b/Demos/PubSub/DemoPubSub.silkit.yaml deleted file mode 100644 index 0d9632668..000000000 --- a/Demos/PubSub/DemoPubSub.silkit.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Description: Sample Configuration for Data Publish/Subscribe -Logging: - Sinks: - - Level: Info - Type: Stdout diff --git a/Demos/PubSub/PubSubDemo.cpp b/Demos/PubSub/PubSubDemo.cpp deleted file mode 100644 index 527426b49..000000000 --- a/Demos/PubSub/PubSubDemo.cpp +++ /dev/null @@ -1,318 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/all.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" -#include "silkit/services/pubsub/PubSubSpec.hpp" -#include "silkit/util/serdes/Serialization.hpp" - - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::PubSub; - -using namespace std::chrono_literals; - -/************************************************************************************************** - * Main Function - **************************************************************************************************/ - -/* -Publisher -Pub: "Gps" -Pub: "Temperature" - -Subscriber -Sub: "Gps" -Sub: "Temperature" -*/ - -struct GpsData -{ - double latitude; - double longitude; - std::string signalQuality; -}; - -std::vector Serialize(const GpsData& gpsData) -{ - // Serialize data - SilKit::Util::SerDes::Serializer serializer; - serializer.BeginStruct(); - serializer.Serialize(gpsData.latitude); - serializer.Serialize(gpsData.longitude); - serializer.Serialize(gpsData.signalQuality); - serializer.EndStruct(); - - return serializer.ReleaseBuffer(); -} - -GpsData Deserialize(const std::vector& data) -{ - GpsData gpsData; - - // Deserialize event data - SilKit::Util::SerDes::Deserializer deserializer(data); - deserializer.BeginStruct(); - gpsData.latitude = deserializer.Deserialize(); - gpsData.longitude = deserializer.Deserialize(); - gpsData.signalQuality = deserializer.Deserialize(); - deserializer.EndStruct(); - - return gpsData; -} - -void PublishData(IDataPublisher* gpsPublisher, IDataPublisher* temperaturePublisher) -{ - // GPS - GpsData gpsData; - gpsData.latitude = 48.8235 + static_cast((rand() % 150)) / 100000; - gpsData.longitude = 9.0965 + static_cast((rand() % 150)) / 100000; - gpsData.signalQuality = "Strong"; - - // Serialize data - auto gpsSerialized = Serialize(gpsData); - - // Publish serialized data - gpsPublisher->Publish(gpsSerialized); - std::cout << ">> Published GPS data: lat=" << gpsData.latitude << ", lon=" << gpsData.longitude - << ", signalQuality=" << gpsData.signalQuality << std::endl; - - // Temperature - double temperature = 25.0 + static_cast(rand() % 10) / 10.0; - - // Serialize data - SilKit::Util::SerDes::Serializer temperatureSerializer; - temperatureSerializer.Serialize(temperature); - - // Publish serialized data - temperaturePublisher->Publish(temperatureSerializer.ReleaseBuffer()); - std::cout << ">> Published temperature data: temperature=" << temperature << std::endl; -} - -void ReceiveGpsData(IDataSubscriber* /*subscriber*/, const DataMessageEvent& dataMessageEvent) -{ - auto eventData = SilKit::Util::ToStdVector(dataMessageEvent.data); - auto gpsData = Deserialize(eventData); - - // Print results - std::cout << "<< Received GPS data: lat=" << gpsData.latitude << ", lon=" << gpsData.longitude - << ", signalQuality=" << gpsData.signalQuality << std::endl; -} - -void ReceiveTemperatureData(IDataSubscriber* /*subscriber*/, const DataMessageEvent& dataMessageEvent) -{ - auto eventData = SilKit::Util::ToStdVector(dataMessageEvent.data); - - // Deserialize event data - SilKit::Util::SerDes::Deserializer deserializer(eventData); - double temperature = deserializer.Deserialize(); - - // Print results - std::cout << "<< Received temperature data: temperature=" << temperature << std::endl; -} - -int main(int argc, char** argv) -{ - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri] [--async]" << std::endl - << "Use \"Publisher\" or \"Subscriber\" as ." << std::endl; - return -1; - } - - std::string participantName(argv[2]); - - if (participantName != "Publisher" && participantName != "Subscriber") - { - std::cout << "Wrong participant name provided. Use either \"Publisher\" or \"Subscriber\"." << std::endl; - return 1; - } - - std::string mediaType{SilKit::Util::SerDes::MediaTypeData()}; - PubSubSpec dataSpecPubGps{"Gps", mediaType}; - PubSubSpec dataSpecPubTemperature{"Temperature", mediaType}; - - try - { - std::string participantConfigurationFilename(argv[1]); - - std::string registryUri = "silkit://localhost:8500"; - - bool runSync = true; - - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto&& arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg; - } - } - - auto participantConfiguration = - SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - auto operationMode = (runSync ? OperationMode::Coordinated : OperationMode::Autonomous); - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - - // Observe state changes - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - lifecycleService->SetAbortHandler( - [](auto lastState) { std::cout << "Abort handler called while in state " << lastState << std::endl; }); - - auto isPublisher = (participantName == "Publisher"); - - // Create a data publisher for GPS data - auto* gpsPublisher = - (isPublisher ? participant->CreateDataPublisher("GpsPublisher", dataSpecPubGps, 0) : nullptr); - // Create a data publisher for temperature data - auto* temperaturePublisher = - (isPublisher ? participant->CreateDataPublisher("TemperaturePublisher", dataSpecPubTemperature, 0) - : nullptr); - - if (!isPublisher) - { - participant->CreateDataSubscriber("GpsSubscriber", dataSpecPubGps, &ReceiveGpsData); - participant->CreateDataSubscriber("TemperatureSubscriber", dataSpecPubTemperature, &ReceiveTemperatureData); - } - - if (runSync) - { - auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - - lifecycleService->SetCommunicationReadyHandler([&participantName]() { - std::cout << "Communication ready handler called for " << participantName << std::endl; - }); - - if (isPublisher) - { - timeSyncService->SetSimulationStepHandler( - [gpsPublisher, temperaturePublisher](std::chrono::nanoseconds now, - std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - - if (gpsPublisher && temperaturePublisher) - { - PublishData(gpsPublisher, temperaturePublisher); - } - std::this_thread::sleep_for(1s); - }, - 1s); - } - else - { - timeSyncService->SetSimulationStepHandler( - [](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - std::this_thread::sleep_for(1s); - }, 1s); - } - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to stop the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::atomic isStopRequested = {false}; - std::thread workerThread; - std::promise startHandlerPromise; - auto startHandlerFuture = startHandlerPromise.get_future(); - lifecycleService->SetCommunicationReadyHandler([&]() { - std::cout << "Communication ready handler called for " << participantName << std::endl; - workerThread = std::thread{[&]() { - startHandlerFuture.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - if (gpsPublisher && temperaturePublisher) - { - PublishData(gpsPublisher, temperaturePublisher); - } - std::this_thread::sleep_for(1s); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->SetStartingHandler([&]() { startHandlerPromise.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to leave the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - catch (const SilKit::ConfigurationError& error) - { - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; - } - - return 0; -} diff --git a/Demos/README.md b/Demos/README.md deleted file mode 100644 index bd44234c7..000000000 --- a/Demos/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# SIL Kit Demos - -This directory contains sample projects that demonstrate how the SIL Kit -API can be used: - -* **CAN, LIN, FlexRay, Ethernet, Pub/Sub, RPC**: - Write or read participants that are able to connect to SIL Kit and use buses of - all supported protocols including CAN, LIN, FlexRay, Ethernet, Pub/Sub and the RPC service. - -The build system is based on cmake. -Supported target platforms and build tools: -* Ubuntu 18.04 (GCC 7 or later) -* Visual Studio 2017 - -## Build Instructions - -For the binary distribution, the cmake build from the 'SilKit-Demos' -directory should work on all supported platforms in a similar way: - -> cmake -B build - -> cmake --build build - -You might want to add the appropriate '-T toolset' and '-A architecture' options on MSVC builds. -The demos will be placed alongside the binaries, if the binary distribution is unpacked next -to the SilKit-Source package. -For example, if the directory layout looks as follows, the demo executables will -be placed in `SilKit-X.Y.Z-$Platform/bin`: - -> SilKit-X.Y.Z-Source/Demos - -> SilKit-X.Y.Z-$Platform/ - - -You can also specify the location of the unpacked binary package using the `SILKIT_DIR` variable: - -> cmake -B build -D SILKIT_DIR=path/to/SilKit-X.Y.Z-PlatformEtc - - -To build the demos as developer from within the SIL Kit source tree and place them alongside -the binaries, build the 'Demos' CMake target from the SIL Kit 'build' directory: - -> cmake --build . --target Demos - -On CMake version >=3.12 the '--parallel' flag can be used to speed up -compilation. - -* **Note:** - By default, the demo build system assumes that it resides next to a binary - release of the SIL Kit in a directory 'SilKit' which contains a properly - exported build of the SIL Kit. You can override this by providing your own - SilKit::SilKit target in cmake. - -* **Windows: Visual Studio 2017 and later** - It is possible to use Visual Studio directly, without project files or - solutions, thanks to the built-in CMake support. - Open this directory in Explorer, right click and select 'open in Visual - Studio'. This should start CMake implicitly from Visual Studio. - Please note that using cmake from command line requires specifying a - '--config' flag when using a Visual Studio generator, e.g.: - - > cmake --build . --config Debug - diff --git a/Demos/Rpc/CMakeLists.txt b/Demos/Rpc/CMakeLists.txt deleted file mode 100644 index 93d483ac5..000000000 --- a/Demos/Rpc/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2022 Vector Informatik GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -make_silkit_demo(SilKitDemoRpc RpcDemo.cpp) -target_sources(SilKitDemoRpc - PRIVATE DemoRpc.silkit.yaml -) diff --git a/Demos/Rpc/RpcDemo.cpp b/Demos/Rpc/RpcDemo.cpp deleted file mode 100644 index b14b61eac..000000000 --- a/Demos/Rpc/RpcDemo.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/* Copyright (c) 2022 Vector Informatik GmbH - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -#include -#include -#include -#include -#include -#include - -#include "silkit/SilKit.hpp" -#include "silkit/services/all.hpp" -#include "silkit/services/orchestration/all.hpp" -#include "silkit/services/orchestration/string_utils.hpp" -#include "silkit/util/serdes/Serialization.hpp" - - -using namespace SilKit::Services::Orchestration; -using namespace SilKit::Services::Rpc; - -using namespace std::chrono_literals; - -// An incrementing call counter, that is used to identify the calls of the different clients -uint16_t callCounter = 0; - -static std::ostream& operator<<(std::ostream& os, const SilKit::Util::Span& v) -{ - os << "[ "; - for (auto i : v) - os << static_cast(i) << " "; - os << "]"; - return os; -} - -static std::ostream& operator<<(std::ostream& os, const std::vector& v) -{ - return os << SilKit::Util::ToSpan(v); -} - -void Call(IRpcClient* client) -{ - std::vector argumentData{static_cast(rand() % 10), static_cast(rand() % 10), - static_cast(rand() % 10)}; - - // Add an incrementing callCounter as userContext, to reidentify the corresponding call on reception of a - // call result. - const auto userContext = reinterpret_cast(uintptr_t(callCounter++)); - - // Serialize call argument data - SilKit::Util::SerDes::Serializer serializer; - serializer.Serialize(argumentData); - - client->Call(serializer.ReleaseBuffer(), userContext); - std::cout << ">> Calling with argumentData=" << argumentData << " and userContext=" << userContext << std::endl; -} - -void CallReturn(IRpcClient* /*client*/, RpcCallResultEvent event) -{ - // Deserialize call result data - auto resultDataVector = SilKit::Util::ToStdVector(event.resultData); - std::vector resultData{}; - if (!resultDataVector.empty()) - { - SilKit::Util::SerDes::Deserializer deserializer(resultDataVector); - resultData = deserializer.Deserialize>(); - } - - switch (event.callStatus) - { - case RpcCallStatus::Success: - std::cout << ">> Call " << event.userContext << " returned with resultData=" << resultData << std::endl; - break; - case RpcCallStatus::ServerNotReachable: - std::cout << "Warning: Call " << event.userContext << " failed with RpcCallStatus::ServerNotReachable" - << std::endl; - break; - case RpcCallStatus::UndefinedError: - std::cout << "Warning: Call " << event.userContext << " failed with RpcCallStatus::UndefinedError" << std::endl; - break; - case RpcCallStatus::InternalServerError: - std::cout << "Warning: Call " << event.userContext << " failed with RpcCallStatus::InternalServerError" - << std::endl; - break; - case RpcCallStatus::Timeout: - std::cout << "Warning: Call " << event.userContext << " failed with RpcCallStatus::Timeout" << std::endl; - break; - } -} - -// A function offered by a RpcServer to add 100 to each enty of an array of numbers -void RemoteFunc_Add100(IRpcServer* server, RpcCallEvent event) -{ - // Deserialize call argument data - auto argumentDataVector = SilKit::Util::ToStdVector(event.argumentData); - SilKit::Util::SerDes::Deserializer deserializer(argumentDataVector); - const std::vector argumentData = deserializer.Deserialize>(); - - // Copy argument data for calculation - std::vector resultData{argumentData}; - - // Perform calculation (increment each argument value by 100) - for (auto& v : resultData) - { - v += 100; - } - - std::cout << ">> Received call with argumentData=" << argumentData << ", returning resultData=" << resultData - << std::endl; - - // Serialize result data - SilKit::Util::SerDes::Serializer serializer; - serializer.Serialize(resultData); - - // Submit call result to client - server->SubmitResult(event.callHandle, serializer.ReleaseBuffer()); -} - -// A function offered by a RpcServer to sort an array of numbers -void RemoteFunc_Sort(IRpcServer* server, RpcCallEvent event) -{ - // Deserialize call argument data - auto argumentDataVector = SilKit::Util::ToStdVector(event.argumentData); - SilKit::Util::SerDes::Deserializer deserializer(argumentDataVector); - std::vector argumentData = deserializer.Deserialize>(); - - // Copy argument data for calculation - std::vector resultData(argumentData); - - // Perform calculation (sort argument values) - std::sort(resultData.begin(), resultData.end()); - std::cout << ">> Received call with argumentData=" << argumentData << ", returning resultData=" << resultData - << std::endl; - - // Serialize result data - SilKit::Util::SerDes::Serializer serializer; - serializer.Serialize(resultData); - - // Submit call result to client - server->SubmitResult(event.callHandle, serializer.ReleaseBuffer()); -} - -int main(int argc, char** argv) -{ - if (argc < 3) - { - std::cerr << "Missing arguments! Start demo with: " << argv[0] - << " [RegistryUri]" << std::endl - << "Use \"Server\" or \"Client\" as ." << std::endl; - return -1; - } - - std::string participantName(argv[2]); - if (participantName != "Client" && participantName != "Server") - { - std::cout << "Wrong participant name provided. Use either \"Client\" or \"Server\"." << std::endl; - return 1; - } - - std::string mediaType{SilKit::Util::SerDes::MediaTypeRpc()}; - RpcSpec rpcSpecAdd100{"Add100", mediaType}; - RpcSpec rpcSpecSort{"Sort", mediaType}; - - try - { - std::string participantConfigurationFilename(argv[1]); - std::string registryUri = "silkit://localhost:8500"; - - bool runSync = true; - - std::vector args; - std::copy((argv + 3), (argv + argc), std::back_inserter(args)); - - for (auto arg : args) - { - if (arg == "--async") - { - runSync = false; - } - else - { - registryUri = arg; - } - } - - auto participantConfiguration = - SilKit::Config::ParticipantConfigurationFromFile(participantConfigurationFilename); - - std::cout << "Creating participant '" << participantName << "' with registry " << registryUri << std::endl; - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - - auto operationMode = (runSync ? OperationMode::Coordinated : OperationMode::Autonomous); - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - - // Observe state changes - lifecycleService->SetStopHandler([]() { std::cout << "Stop handler called" << std::endl; }); - lifecycleService->SetShutdownHandler([]() { std::cout << "Shutdown handler called" << std::endl; }); - lifecycleService->SetAbortHandler( - [](auto lastState) { std::cout << "Abort handler called while in state " << lastState << std::endl; }); - - auto isClient = participantName == "Client"; - if (!isClient) - { - // Create RpcServer to respond to calls for "Add100" - participant->CreateRpcServer("ServerAdd100", rpcSpecAdd100, &RemoteFunc_Add100); - - // Create RpcServer to respond to calls for "Sort" - participant->CreateRpcServer("ServerSort", rpcSpecSort, &RemoteFunc_Sort); - } - - // Create RpcClient to call "Add100" - auto* clientAdd100 = - (isClient ? participant->CreateRpcClient("ClientAdd100", rpcSpecAdd100, &CallReturn) : nullptr); - // Create RpcClient to call "Sort" - auto* clientSort = (isClient ? participant->CreateRpcClient("ClientSort", rpcSpecSort, &CallReturn) : nullptr); - - if (runSync) - { - auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - - if (isClient) - { - timeSyncService->SetSimulationStepHandler( - [clientAdd100, clientSort](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - - // Call both remote procedures in each simulation step - if (clientAdd100) - Call(clientAdd100); - if (clientSort) - Call(clientSort); - }, 1s); - } - else - { - timeSyncService->SetSimulationStepHandler( - [](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { - auto nowMs = std::chrono::duration_cast(now); - std::cout << "now=" << nowMs.count() << "ms" << std::endl; - std::this_thread::sleep_for(1s); - }, 1s); - } - - auto finalStateFuture = lifecycleService->StartLifecycle(); - auto finalState = finalStateFuture.get(); - - std::cout << "Simulation stopped. Final State: " << finalState << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - } - else - { - std::atomic isStopRequested = {false}; - std::thread workerThread; - std::promise startHandlerPromise; - auto startHandlerFuture = startHandlerPromise.get_future(); - lifecycleService->SetCommunicationReadyHandler([&]() { - workerThread = std::thread{[&]() { - startHandlerFuture.get(); - while (lifecycleService->State() == ParticipantState::ReadyToRun - || lifecycleService->State() == ParticipantState::Running) - { - if (clientAdd100) - Call(clientAdd100); - if (clientSort) - Call(clientSort); - std::this_thread::sleep_for(1s); - } - if (!isStopRequested) - { - std::cout << "Press enter to end the process..." << std::endl; - } - }}; - }); - - lifecycleService->SetStartingHandler([&]() { startHandlerPromise.set_value(); }); - - lifecycleService->StartLifecycle(); - std::cout << "Press enter to stop the simulation..." << std::endl; - std::cin.ignore(); - - isStopRequested = true; - if (lifecycleService->State() == ParticipantState::Running - || lifecycleService->State() == ParticipantState::Paused) - { - std::cout << "User requested to stop in state " << lifecycleService->State() << std::endl; - lifecycleService->Stop("User requested to stop"); - } - - if (workerThread.joinable()) - { - workerThread.join(); - } - std::cout << "The participant has shut down and left the simulation" << std::endl; - } - } - catch (const SilKit::ConfigurationError& error) - { - std::cerr << "Invalid configuration: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -2; - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - std::cout << "Press enter to end the process..." << std::endl; - std::cin.ignore(); - return -3; - } - - return 0; -} diff --git a/Demos/NetworkSimulator/CMakeLists.txt b/Demos/api/NetworkSimulator/CMakeLists.txt similarity index 100% rename from Demos/NetworkSimulator/CMakeLists.txt rename to Demos/api/NetworkSimulator/CMakeLists.txt diff --git a/Demos/NetworkSimulator/DemoNetSim.silkit.yaml b/Demos/api/NetworkSimulator/DemoNetSim.silkit.yaml similarity index 100% rename from Demos/NetworkSimulator/DemoNetSim.silkit.yaml rename to Demos/api/NetworkSimulator/DemoNetSim.silkit.yaml diff --git a/Demos/NetworkSimulator/NetSimDemo.cpp b/Demos/api/NetworkSimulator/NetSimDemo.cpp similarity index 100% rename from Demos/NetworkSimulator/NetSimDemo.cpp rename to Demos/api/NetworkSimulator/NetSimDemo.cpp diff --git a/Demos/NetworkSimulator/src/Can/MySimulatedCanController.cpp b/Demos/api/NetworkSimulator/src/Can/MySimulatedCanController.cpp similarity index 100% rename from Demos/NetworkSimulator/src/Can/MySimulatedCanController.cpp rename to Demos/api/NetworkSimulator/src/Can/MySimulatedCanController.cpp diff --git a/Demos/NetworkSimulator/src/Can/MySimulatedCanController.hpp b/Demos/api/NetworkSimulator/src/Can/MySimulatedCanController.hpp similarity index 100% rename from Demos/NetworkSimulator/src/Can/MySimulatedCanController.hpp rename to Demos/api/NetworkSimulator/src/Can/MySimulatedCanController.hpp diff --git a/Demos/NetworkSimulator/src/MySimulatedNetwork.cpp b/Demos/api/NetworkSimulator/src/MySimulatedNetwork.cpp similarity index 100% rename from Demos/NetworkSimulator/src/MySimulatedNetwork.cpp rename to Demos/api/NetworkSimulator/src/MySimulatedNetwork.cpp diff --git a/Demos/NetworkSimulator/src/MySimulatedNetwork.hpp b/Demos/api/NetworkSimulator/src/MySimulatedNetwork.hpp similarity index 100% rename from Demos/NetworkSimulator/src/MySimulatedNetwork.hpp rename to Demos/api/NetworkSimulator/src/MySimulatedNetwork.hpp diff --git a/Demos/NetworkSimulator/src/Scheduler.cpp b/Demos/api/NetworkSimulator/src/Scheduler.cpp similarity index 100% rename from Demos/NetworkSimulator/src/Scheduler.cpp rename to Demos/api/NetworkSimulator/src/Scheduler.cpp diff --git a/Demos/NetworkSimulator/src/Scheduler.hpp b/Demos/api/NetworkSimulator/src/Scheduler.hpp similarity index 100% rename from Demos/NetworkSimulator/src/Scheduler.hpp rename to Demos/api/NetworkSimulator/src/Scheduler.hpp diff --git a/Demos/api/Orchestration/Autonomous.cpp b/Demos/api/Orchestration/Autonomous.cpp new file mode 100644 index 000000000..98eef7ec1 --- /dev/null +++ b/Demos/api/Orchestration/Autonomous.cpp @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "silkit/SilKit.hpp" + +using namespace std::chrono_literals; + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + // Create an autonomous lifecycle + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Autonomous}); + + + // Future / promise to control entrance of the main loop in the worker thread + std::promise startWorkPromise; + auto startWorkFuture = startWorkPromise.get_future(); + + // The worker thread is 'unleashed' in the starting handler... + lifecycleService->SetStartingHandler([&startWorkPromise]() { + startWorkPromise.set_value(); + }); + + // Start the worker thread and wait for the go from the starting handler. + auto workerThread = std::thread([&startWorkFuture, lifecycleService, logger]() { + startWorkFuture.get(); + + for (int i = 0; i < 10; ++i) + { + std::this_thread::sleep_for(1s); + logger->Info("Simulation stop in " + std::to_string(10-i)); + }; + + logger->Info("Stopping just me."); + lifecycleService->Stop("Stopping just me"); + }); + + // Start and wait until lifecycleService->Stop is called. + logger->Info("Start the participant lifecycle."); + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + + // Clean up the worker thread + if (workerThread.joinable()) + { + workerThread.join(); + } + + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/api/Orchestration/CMakeLists.txt b/Demos/api/Orchestration/CMakeLists.txt new file mode 100644 index 000000000..8930fe76f --- /dev/null +++ b/Demos/api/Orchestration/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_demo(SilKitDemoAutonomous Autonomous.cpp) +make_silkit_demo(SilKitDemoCoordinated Coordinated.cpp) +make_silkit_demo(SilKitDemoSimStep SimStep.cpp) +make_silkit_demo(SilKitDemoSimStepAsync SimStepAsync.cpp) + diff --git a/Demos/api/Orchestration/Coordinated.cpp b/Demos/api/Orchestration/Coordinated.cpp new file mode 100644 index 000000000..bb4e62d76 --- /dev/null +++ b/Demos/api/Orchestration/Coordinated.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "silkit/SilKit.hpp" + +using namespace std::chrono_literals; + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + // Create a coordinated lifecycle + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + + // Future / promise to control entrance of the main loop in the worker thread + std::promise startWorkPromise; + std::future startWorkFuture; + startWorkFuture = startWorkPromise.get_future(); + + // The worker thread is 'unleashed' in the starting handler... + lifecycleService->SetStartingHandler([&startWorkPromise]() { startWorkPromise.set_value(); }); + + // Start the worker thread and wait for the go from the starting handler. + std::atomic workerThreadDone{false}; + auto workerThread = std::thread([&startWorkFuture, &workerThreadDone, logger]() { + startWorkFuture.get(); + while (!workerThreadDone) + { + std::this_thread::sleep_for(1s); + logger->Info("Simulation running. Stop via CTRL-C in the sil-kit-system-controller."); + }; + }); + + // Start and wait until the sil-kit-system-controller is stopped. + logger->Info( + "Start the participant lifecycle and wait for the sil-kit-system-controller to start the simulation."); + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + + // Clean up the worker thread. + workerThreadDone = true; + + if (workerThread.joinable()) + { + workerThread.join(); + } + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/api/Orchestration/SimStep.cpp b/Demos/api/Orchestration/SimStep.cpp new file mode 100644 index 000000000..a7520300a --- /dev/null +++ b/Demos/api/Orchestration/SimStep.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include + +#include "silkit/SilKit.hpp" + +using namespace std::chrono_literals; + +std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) +{ + out << std::chrono::duration_cast(timestamp).count() << "ms"; + return out; +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + + auto* timeSyncService = lifecycleService->CreateTimeSyncService(); + + const auto stepSize = 1ms; + timeSyncService->SetSimulationStepHandler( + [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { + // The invocation of this handler marks the beginning of a simulation step. + + std::stringstream ss; + ss << "--------- Simulation step T=" << now << " ---------"; + logger->Info(ss.str()); + + // All messages sent here are guaranteed to arrive at other participants before their next simulation step is called. + // So here, we can rely on having received all messages from the past (< now). + // Note that this guarantee only holds for messages sent within a simulation step, + // not for messages send outside of this handler (e.g. directly in a reception handler). + + // Returning from the handler marks the end of a simulation step. + }, stepSize); + + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/api/Orchestration/SimStepAsync.cpp b/Demos/api/Orchestration/SimStepAsync.cpp new file mode 100644 index 000000000..1c051a28e --- /dev/null +++ b/Demos/api/Orchestration/SimStepAsync.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "silkit/SilKit.hpp" + +using namespace std::chrono_literals; + +std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) +{ + out << std::chrono::duration_cast(timestamp).count() << "ms"; + return out; +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + + auto* timeSyncService = lifecycleService->CreateTimeSyncService(); + + // 1. The simulation step gets called by the SIL Kit Time Synchronization algorithm. + // 2. The simulation step handler unlocks a step in the asyncThread with a condition variable. + // 3. The asyncThread performs some asynchronous operation. + // 4. The asyncThread completes the simulation step. + + std::mutex mx; + bool doStep = false; + std::condition_variable cv; + std::atomic asyncThreadDone{false}; + using MxType = decltype(mx); + + const auto stepSize = 1ms; + timeSyncService->SetSimulationStepHandlerAsync( + [&](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { + // The invocation of this handler marks the beginning of a simulation step. + + std::stringstream ss; + ss << "--------- Simulation step T=" << now << " ---------"; + logger->Info(ss.str()); + + std::unique_lock lock(mx); + doStep = true; + cv.notify_one(); + + // Returning from the handler is NOT the end of a simulation step. + // With the SetSimulationStepHandlerAsync, completing the step is done explicitly by calling CompleteSimulationStep(). + }, stepSize); + + auto asyncThread = std::thread([&]() { + while (!asyncThreadDone) + { + std::unique_lock lock(mx); + cv.wait(lock, [doStep] { return doStep; }); + doStep = false; + + logger->Info("Asynchronous operation in the simulation step:"); + logger->Info(" Sending a message to another participant..."); + std::this_thread::sleep_for(0.5s); + logger->Info(" Awaiting the reply of another participant..."); + std::this_thread::sleep_for(0.5s); + logger->Info("Done."); + + // This call completes the simulation step. + timeSyncService->CompleteSimulationStep(); + } + }); + + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + + asyncThreadDone = true; + if (asyncThread.joinable()) + { + asyncThread.join(); + } + + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/api/SimpleCan/CMakeLists.txt b/Demos/api/SimpleCan/CMakeLists.txt new file mode 100644 index 000000000..209040d1a --- /dev/null +++ b/Demos/api/SimpleCan/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_demo(SilKitDemoSimpleCan SimpleCan.cpp) + diff --git a/Demos/api/SimpleCan/SimpleCan.cpp b/Demos/api/SimpleCan/SimpleCan.cpp new file mode 100644 index 000000000..c61504390 --- /dev/null +++ b/Demos/api/SimpleCan/SimpleCan.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include + +#include "silkit/SilKit.hpp" + +using namespace SilKit::Services::Can; +using namespace std::chrono_literals; + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + auto* timeSyncService = lifecycleService->CreateTimeSyncService(); + auto* logger = participant->GetLogger(); + + // CAN controller + auto* canController = participant->CreateCanController("CanController1", "CAN1"); + + canController->AddFrameTransmitHandler([logger](ICanController* /*ctrl*/, const CanFrameTransmitEvent& /*ack*/) { + logger->Info("Receive CAN frame transmit acknowledge"); + }); + canController->AddFrameHandler([logger](ICanController* /*ctrl*/, const CanFrameEvent& canFrameEvent) { + std::string payload(canFrameEvent.frame.dataField.begin(), canFrameEvent.frame.dataField.end()); + logger->Info("Receive CAN frame: data='" + payload + "'"); + }); + + // Initialize CAN controller + lifecycleService->SetCommunicationReadyHandler([canController]() { + canController->SetBaudRate(10'000, 1'000'000, 2'000'000); + canController->Start(); + }); + + // Simulation steps + const auto stepSize = 1ms; + timeSyncService->SetSimulationStepHandler( + [participantName, canController, logger](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { + // Send CAN Frame every 10 seconds + if (now.count() % std::chrono::nanoseconds(10s).count() == 0) + { + logger->Info("--------- T = " + std::to_string(now.count() / 1000000000) + "s ---------"); + + // Create CAN frame with dynamic content + const std::string payloadStr = "Data from " + participantName + ": " + std::to_string(std::rand()); + std::vector payloadBytes(payloadStr.begin(), payloadStr.end()); + CanFrame canFrame{}; + canFrame.canId = 1; + canFrame.dataField = SilKit::Util::ToSpan(payloadBytes); + canFrame.dlc = static_cast(canFrame.dataField.size()); + + // Send frame (move ownership of canFrame to avoid additional copy) + logger->Info("Sending CAN frame: data='" + payloadStr + "'"); + canController->SendFrame(canFrame); + } + }, stepSize); + + // Start and wait + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/communication/Can/CMakeLists.txt b/Demos/communication/Can/CMakeLists.txt new file mode 100644 index 000000000..c45b97502 --- /dev/null +++ b/Demos/communication/Can/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_communication_demo(SilKitDemoCanWriter CanWriterDemo.cpp) +make_silkit_communication_demo(SilKitDemoCanReader CanReaderDemo.cpp) + diff --git a/Demos/communication/Can/CanDemoCommon.hpp b/Demos/communication/Can/CanDemoCommon.hpp new file mode 100644 index 000000000..87cde09a9 --- /dev/null +++ b/Demos/communication/Can/CanDemoCommon.hpp @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/services/can/all.hpp" +#include "silkit/services/can/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" + +using namespace SilKit::Services::Can; + +// This is the common behavior used in CanReaderDemo and CanWriterDemo +namespace CanDemoCommon { + +void FrameTransmitHandler(const CanFrameTransmitEvent& canFrameAck, ILogger* logger) +{ + std::stringstream ss; + ss << "Receive CAN frame transmit acknowledge: canId=" << canFrameAck.canId << ", status='" << canFrameAck.status << "'"; + logger->Info(ss.str()); +} + +void FrameHandler(const CanFrameEvent& canFrameEvent, ILogger* logger, bool printHex) +{ + std::string frameTypeHint = ""; + if ((canFrameEvent.frame.flags & static_cast(CanFrameFlag::Fdf)) != 0) + { + frameTypeHint = "FD "; + } + + std::stringstream ss; + ss << "Receive CAN " << frameTypeHint << "frame: canId=" << canFrameEvent.frame.canId << ", data="; + if (printHex) + { + ss << "[" << Util::AsHexString(canFrameEvent.frame.dataField).WithSeparator(" ") << "]"; + } + else + { + ss << "'" << std::string(canFrameEvent.frame.dataField.begin(), canFrameEvent.frame.dataField.end()) << "'"; + } + logger->Info(ss.str()); +} + +} // namespace CanDemoBehavior diff --git a/Demos/communication/Can/CanReaderDemo.cpp b/Demos/communication/Can/CanReaderDemo.cpp new file mode 100644 index 000000000..4eb4880d4 --- /dev/null +++ b/Demos/communication/Can/CanReaderDemo.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "CanDemoCommon.hpp" + +class CanReader: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ICanController* _canController{nullptr}; + std::string _networkName = "CAN1"; + bool _printHex{false}; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the CAN network to use.", "Defaults to '" + _networkName + "'."}); + + GetCommandLineParser()->Add( + "hex", "H", "-H, --hex", + std::vector{"Print the CAN payloads in hexadecimal format.", + "Otherwise, the payloads are interpreted as strings."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + _printHex = GetCommandLineParser()->Get("hex").Value(); + } + + void CreateControllers() override + { + _canController = GetParticipant()->CreateCanController("CanController1", _networkName); + + _canController->AddFrameTransmitHandler([this](ICanController* /*ctrl*/, const CanFrameTransmitEvent& ack) { + CanDemoCommon::FrameTransmitHandler(ack, GetLogger()); + }); + _canController->AddFrameHandler([this](ICanController* /*ctrl*/, const CanFrameEvent& frameEvent) { + CanDemoCommon::FrameHandler(frameEvent, GetLogger(), _printHex); + }); + } + + void InitControllers() override + { + _canController->SetBaudRate(10'000, 1'000'000, 2'000'000); + _canController->Start(); + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + // NOP + } + + void DoWorkAsync() override + { + // NOP + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "CanReader"; + args.duration = 5ms; + CanReader app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Can: Log received frames"); + + return app.Run(); +} + diff --git a/Demos/communication/Can/CanWriterDemo.cpp b/Demos/communication/Can/CanWriterDemo.cpp new file mode 100644 index 000000000..e9231801a --- /dev/null +++ b/Demos/communication/Can/CanWriterDemo.cpp @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "CanDemoCommon.hpp" + +class CanWriter : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ICanController* _canController{nullptr}; + std::string _networkName = "CAN1"; + bool _printHex{false}; + int _frameId = 0; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the CAN network to use.", "Defaults to '" + _networkName + "'."}); + + GetCommandLineParser()->Add("hex", "H", "-H, --hex", + std::vector{"Print the CAN payload as hex."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + _printHex = GetCommandLineParser()->Get("hex").Value(); + } + + void CreateControllers() override + { + _canController = GetParticipant()->CreateCanController("CanController1", _networkName); + + _canController->AddFrameTransmitHandler([this](ICanController* /*ctrl*/, const CanFrameTransmitEvent& ack) { + CanDemoCommon::FrameTransmitHandler(ack, GetLogger()); + }); + _canController->AddFrameHandler([this](ICanController* /*ctrl*/, const CanFrameEvent& frameEvent) { + CanDemoCommon::FrameHandler(frameEvent, GetLogger(), _printHex); + }); + } + + void InitControllers() override + { + _canController->SetBaudRate(10'000, 1'000'000, 2'000'000); + _canController->Start(); + } + + void SendFrame() + { + _frameId++; + + // Build a CAN FD frame + CanFrame canFrame{}; + canFrame.canId = 3; + canFrame.flags = static_cast(CanFrameFlag::Fdf) // FD Format Indicator + | static_cast(CanFrameFlag::Brs); // Bit Rate Switch (for FD Format only) + + // Build a payload with the frame Id + std::stringstream payloadBuilder; + payloadBuilder << "CAN " << _frameId % 10000; + auto payloadStr = payloadBuilder.str(); + std::vector payloadBytes(payloadStr.begin(), payloadStr.end()); + canFrame.dataField = payloadBytes; + canFrame.dlc = static_cast(canFrame.dataField.size()); + + // Log + std::stringstream ss; + ss << "Sending CAN FD frame, canId=" << canFrame.canId << ", data="; + if (_printHex) + { + ss << "[" << Util::AsHexString(canFrame.dataField).WithSeparator(" ") << "]"; + } + else + { + ss << "'" << std::string(canFrame.dataField.begin(), canFrame.dataField.end()) << "'"; + } + GetLogger()->Info(ss.str()); + + // Send + _canController->SendFrame(canFrame); + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + SendFrame(); + } + + void DoWorkAsync() override + { + SendFrame(); + } + +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "CanWriter"; + args.duration = 5ms; + CanWriter app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Can: Send CanFd frames"); + + return app.Run(); +} diff --git a/Demos/communication/Ethernet/CMakeLists.txt b/Demos/communication/Ethernet/CMakeLists.txt new file mode 100644 index 000000000..9952df136 --- /dev/null +++ b/Demos/communication/Ethernet/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_communication_demo(SilKitDemoEthernetWriter EthernetWriterDemo.cpp) +make_silkit_communication_demo(SilKitDemoEthernetReader EthernetReaderDemo.cpp) + diff --git a/Demos/communication/Ethernet/EthernetDemoCommon.hpp b/Demos/communication/Ethernet/EthernetDemoCommon.hpp new file mode 100644 index 000000000..c6f8add00 --- /dev/null +++ b/Demos/communication/Ethernet/EthernetDemoCommon.hpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/services/ethernet/all.hpp" +#include "silkit/services/ethernet/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" + +using namespace SilKit::Services::Ethernet; + +// This is the common behavior used in EthernetReaderDemo and EthernetWriterDemo +namespace EthernetDemoCommon { + +using EtherType = uint16_t; +using EthernetMac = std::array; + +void FrameTransmitHandler(const EthernetFrameTransmitEvent& frameTransmitEvent, ILogger* logger) +{ + std::stringstream ss; + if (frameTransmitEvent.status == EthernetTransmitStatus::Transmitted) + { + ss << "Received ACK for Ethernet frame with userContext=" << frameTransmitEvent.userContext; + } + else + { + ss << "Received NACK for Ethernet frame with userContext=" << frameTransmitEvent.userContext; + switch (frameTransmitEvent.status) + { + case EthernetTransmitStatus::Transmitted: + break; + case EthernetTransmitStatus::InvalidFrameFormat: + ss << ": InvalidFrameFormat"; + break; + case EthernetTransmitStatus::ControllerInactive: + ss << ": ControllerInactive"; + break; + case EthernetTransmitStatus::LinkDown: + ss << ": LinkDown"; + break; + case EthernetTransmitStatus::Dropped: + ss << ": Dropped"; + break; + } + } + logger->Info(ss.str()); +} + +auto PrintPayload(const std::vector& payload, bool printHex) +{ + std::stringstream ss; + if (printHex) + { + ss << "[" << Util::AsHexString(payload).WithSeparator(" ") << "]"; + } + else + { + ss << "'" << std::string(payload.begin(), payload.end()) << "'"; + } + return ss.str(); +} + +void FrameHandler(const EthernetFrameEvent& ethernetFrameEvent, ILogger* logger, bool printHex) +{ + const size_t FrameHeaderSize = 2 * sizeof(EthernetMac) + sizeof(EtherType); + std::vector payloadWithoutHeader; + payloadWithoutHeader.insert(payloadWithoutHeader.end(), ethernetFrameEvent.frame.raw.begin() + FrameHeaderSize, + ethernetFrameEvent.frame.raw.end()); + + std::stringstream ss; + ss << "Receive Ethernet frame, data=" << PrintPayload(payloadWithoutHeader, printHex); + logger->Info(ss.str()); +} + +} // namespace EthernetDemoBehavior diff --git a/Demos/communication/Ethernet/EthernetReaderDemo.cpp b/Demos/communication/Ethernet/EthernetReaderDemo.cpp new file mode 100644 index 000000000..deede6738 --- /dev/null +++ b/Demos/communication/Ethernet/EthernetReaderDemo.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "EthernetDemoCommon.hpp" + +class EthernetReader: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + IEthernetController* _ethernetController{nullptr}; + std::string _networkName = "Eth1"; + bool _printHex{false}; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the Ethernet network to use.", "Defaults to '" + _networkName + "'."}); + + GetCommandLineParser()->Add( + "hex", "H", "-H, --hex", + std::vector{"Print the CAN payloads in hexadecimal format.", + "Otherwise, the payloads are interpreted as strings."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + _printHex = GetCommandLineParser()->Get("hex").Value(); + } + + void CreateControllers() override + { + _ethernetController = GetParticipant()->CreateEthernetController("EthernetController1", _networkName); + + _ethernetController->AddFrameTransmitHandler([this](IEthernetController* /*ctrl*/, const EthernetFrameTransmitEvent& ack) { + EthernetDemoCommon::FrameTransmitHandler(ack, GetLogger()); + }); + _ethernetController->AddFrameHandler([this](IEthernetController* /*ctrl*/, const EthernetFrameEvent& frameEvent) { + EthernetDemoCommon::FrameHandler(frameEvent, GetLogger(), _printHex); + }); + } + + void InitControllers() override + { + _ethernetController->Activate(); + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + // NOP + } + + void DoWorkAsync() override + { + // NOP + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "EthernetReader"; + EthernetReader app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Ethernet: Log received frames"); + + return app.Run(); +} + diff --git a/Demos/communication/Ethernet/EthernetWriterDemo.cpp b/Demos/communication/Ethernet/EthernetWriterDemo.cpp new file mode 100644 index 000000000..809d73ddc --- /dev/null +++ b/Demos/communication/Ethernet/EthernetWriterDemo.cpp @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "EthernetDemoCommon.hpp" + +class EthernetWriter : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + IEthernetController* _ethernetController{nullptr}; + std::string _networkName = "Eth1"; + bool _printHex{false}; + int _frameId = 0; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the Ethernet network to use.", "Defaults to '" + _networkName + "'."}); + + GetCommandLineParser()->Add( + "hex", "H", "-H, --hex", + std::vector{"Print the CAN payloads in hexadecimal format.", + "Otherwise, the payloads are interpreted as strings."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + _printHex = GetCommandLineParser()->Get("hex").Value(); + } + + void CreateControllers() override + { + _ethernetController = GetParticipant()->CreateEthernetController("EthernetController1", _networkName); + + _ethernetController->AddFrameTransmitHandler( + [this](IEthernetController* /*ctrl*/, const EthernetFrameTransmitEvent& ack) { + EthernetDemoCommon::FrameTransmitHandler(ack, GetLogger()); + }); + _ethernetController->AddFrameHandler( + [this](IEthernetController* /*ctrl*/, const EthernetFrameEvent& frameEvent) { + EthernetDemoCommon::FrameHandler(frameEvent, GetLogger(), _printHex); + }); + } + + void InitControllers() override + { + _ethernetController->Activate(); + } + + std::vector CreateFrame(const EthernetDemoCommon::EthernetMac& destinationAddress, + const EthernetDemoCommon::EthernetMac& sourceAddress, + const std::vector& payload) + { + const uint16_t etherType = 0x0000; // no protocol + std::vector raw; + std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(raw)); + std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(raw)); + auto etherTypeBytes = reinterpret_cast(ðerType); + raw.push_back(etherTypeBytes[1]); // We assume our platform to be little-endian + raw.push_back(etherTypeBytes[0]); + std::copy(payload.begin(), payload.end(), std::back_inserter(raw)); + return raw; + } + + void SendFrame() + { + EthernetDemoCommon::EthernetMac WriterMacAddr = {0xF6, 0x04, 0x68, 0x71, 0xAA, 0xC1}; + EthernetDemoCommon::EthernetMac BroadcastMacAddr = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + _frameId++; + + std::stringstream stream; + // Ensure that the payload is long enough to constitute a valid Ethernet frame + stream << "Hello from Ethernet writer! (frameId=" << _frameId + << ")----------------------------------------------------"; + auto payloadString = stream.str(); + std::vector payload(payloadString.begin(), payloadString.end()); + auto frame = CreateFrame(BroadcastMacAddr, WriterMacAddr, payload); + const auto userContext = reinterpret_cast(static_cast(_frameId)); + + std::stringstream ss; + ss << "Sending Ethernet frame, data=" << EthernetDemoCommon::PrintPayload(payload, _printHex); + GetLogger()->Info(ss.str()); + + _ethernetController->SendFrame(EthernetFrame{frame}, userContext); + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + SendFrame(); + } + + void DoWorkAsync() override + { + SendFrame(); + } + +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "EthernetWriter"; + EthernetWriter app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Ethernet: Broadcast test frames"); + + return app.Run(); +} diff --git a/Demos/Can/CMakeLists.txt b/Demos/communication/Flexray/CMakeLists.txt similarity index 87% rename from Demos/Can/CMakeLists.txt rename to Demos/communication/Flexray/CMakeLists.txt index 4d421494b..4fd7be687 100644 --- a/Demos/Can/CMakeLists.txt +++ b/Demos/communication/Flexray/CMakeLists.txt @@ -19,9 +19,5 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -make_silkit_demo(SilKitDemoCan CanDemo.cpp) - -target_sources(SilKitDemoCan - PRIVATE DemoCan.silkit.yaml - PRIVATE NetworkSimulatorConfig.yaml -) +make_silkit_communication_demo(SilKitDemoFlexrayNode0 FlexrayNode0Demo.cpp) +make_silkit_communication_demo(SilKitDemoFlexrayNode1 FlexrayNode1Demo.cpp) diff --git a/Demos/communication/Flexray/FlexrayDemoCommon.hpp b/Demos/communication/Flexray/FlexrayDemoCommon.hpp new file mode 100644 index 000000000..5e29e49e5 --- /dev/null +++ b/Demos/communication/Flexray/FlexrayDemoCommon.hpp @@ -0,0 +1,276 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + + +#include "silkit/services/flexray/all.hpp" +#include "silkit/services/flexray/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" + +using namespace SilKit::Services::Flexray; +using namespace std::chrono_literals; + +// This is the common behavior used in FlexrayNode0 and FlexrayNode1 +namespace FlexrayDemoCommon { + +class FlexrayNode +{ +public: + FlexrayNode(IFlexrayController* controller, FlexrayControllerConfig config, ILogger* logger) + : _flexrayController{controller} + , _controllerConfig{std::move(config)} + , _logger{logger} + { + _lastPocStatus.state = FlexrayPocState::DefaultConfig; + _busState = FlexrayNode::MasterState::PerformWakeup; + + _flexrayController->AddFrameHandler([this](IFlexrayController* /*ctrl*/, FlexrayFrameEvent ev) { + std::stringstream ss; + ss << "Received " << ev; + _logger->Info(ss.str()); + }); + + _flexrayController->AddFrameTransmitHandler( + [this](IFlexrayController* /*ctrl*/, const FlexrayFrameTransmitEvent& ev) { + std::stringstream ss; + ss << "Received " << ev; + _logger->Info(ss.str()); + }); + + _flexrayController->AddSymbolHandler([this](IFlexrayController* /*ctrl*/, FlexraySymbolEvent ev) { + std::stringstream ss; + ss << "Received " << ev; + _logger->Info(ss.str()); + }); + + _flexrayController->AddSymbolTransmitHandler( + [this](IFlexrayController* /*ctrl*/, const FlexraySymbolTransmitEvent& ev) { + std::stringstream ss; + ss << "Received " << ev; + _logger->Info(ss.str()); + }); + + _flexrayController->AddCycleStartHandler( + [this](IFlexrayController* /*ctrl*/, const FlexrayCycleStartEvent& ev) { + std::stringstream ss; + ss << "Received " << ev; + _logger->Info(ss.str()); + }); + + _flexrayController->AddWakeupHandler( + [this](IFlexrayController* /*ctrl*/, const FlexrayWakeupEvent& wakeupEvent) { + std::stringstream ss; + ss << "Received WAKEUP! (" << wakeupEvent.pattern << ")"; + _logger->Info(ss.str()); + + _flexrayController->AllowColdstart(); + _flexrayController->Run(); + }); + + _flexrayController->AddPocStatusHandler( + [this](IFlexrayController* /*ctrl*/, const FlexrayPocStatusEvent& pocStatusEvent) { + { + std::stringstream ss; + ss << "Received POC=" << pocStatusEvent.state << ", Freeze=" << pocStatusEvent.freeze + << ", Wakeup=" << pocStatusEvent.wakeupStatus << ", Slot=" << pocStatusEvent.slotMode + << " @t=" << pocStatusEvent.timestamp; + _logger->Info(ss.str()); + } + + if (_lastPocStatus.state == FlexrayPocState::Wakeup && pocStatusEvent.state == FlexrayPocState::Ready) + { + std::stringstream ss; + ss << " Wakeup finished..."; + _logger->Info(ss.str()); + _busState = MasterState::WakeupDone; + } + + _lastPocStatus = pocStatusEvent; + }); + } + +private: + IFlexrayController* _flexrayController{nullptr}; + FlexrayControllerConfig _controllerConfig; + FlexrayPocStatusEvent _lastPocStatus{}; + int _msgId = 0; + bool _configured{false}; + ILogger* _logger; + + enum class MasterState + { + Ignore, + PerformWakeup, + WaitForWakeup, + WakeupDone + }; + MasterState _busState = MasterState::Ignore; + +private: + void PocReady() + { + switch (_busState) + { + case MasterState::PerformWakeup: + _flexrayController->Wakeup(); + break; + case MasterState::WaitForWakeup: + break; + case MasterState::WakeupDone: + _flexrayController->AllowColdstart(); + _flexrayController->Run(); + break; + default: + break; + } + } + + void TxBufferUpdate() + { + if (_controllerConfig.bufferConfigs.empty()) + return; + + _msgId++; + + auto bufferIdx = _msgId % _controllerConfig.bufferConfigs.size(); + + // prepare a friendly message as payload + std::stringstream payloadStream; + payloadStream << "FlexrayFrameEvent#" << std::setw(4) << _msgId << "; bufferId=" << bufferIdx; + auto payloadString = payloadStream.str(); + + std::vector payloadBytes; + payloadBytes.resize(payloadString.size()); + + std::copy(payloadString.begin(), payloadString.end(), payloadBytes.begin()); + + FlexrayTxBufferUpdate update; + update.payload = payloadBytes; + update.payloadDataValid = true; + update.txBufferIndex = static_cast(bufferIdx); + + _flexrayController->UpdateTxBuffer(update); + } + + void SwapChannels() + { + std::stringstream ss; + ss << "Reconfiguring TxBuffers: Swapping FlexrayChannel::A and FlexrayChannel::B"; + _logger->Info(ss.str()); + + for (uint16_t idx = 0; idx < _controllerConfig.bufferConfigs.size(); idx++) + { + auto&& bufferConfig = _controllerConfig.bufferConfigs[idx]; + switch (bufferConfig.channels) + { + case FlexrayChannel::A: + bufferConfig.channels = FlexrayChannel::B; + _flexrayController->ReconfigureTxBuffer(idx, bufferConfig); + break; + case FlexrayChannel::B: + bufferConfig.channels = FlexrayChannel::A; + _flexrayController->ReconfigureTxBuffer(idx, bufferConfig); + break; + default: + break; + } + } + } + + void Init() + { + if (_configured) + return; + _flexrayController->Configure(_controllerConfig); + _configured = true; + } + +public: + void DoAction(std::chrono::nanoseconds now) + { + switch (_lastPocStatus.state) + { + case FlexrayPocState::DefaultConfig: + Init(); + break; + case FlexrayPocState::Ready: + PocReady(); + break; + case FlexrayPocState::NormalActive: + if (now == 100ms) + { + SwapChannels(); + } + else + { + TxBufferUpdate(); + } + break; + case FlexrayPocState::Config: + case FlexrayPocState::Startup: + case FlexrayPocState::Wakeup: + case FlexrayPocState::NormalPassive: + case FlexrayPocState::Halt: + default: + break; + } + } +}; + +auto MakeControllerConfig() -> FlexrayControllerConfig +{ + FlexrayClusterParameters clusterParams; + clusterParams.gColdstartAttempts = 8; + clusterParams.gCycleCountMax = 63; + clusterParams.gdActionPointOffset = 2; + clusterParams.gdDynamicSlotIdlePhase = 1; + clusterParams.gdMiniSlot = 5; + clusterParams.gdMiniSlotActionPointOffset = 2; + clusterParams.gdStaticSlot = 31; + clusterParams.gdSymbolWindow = 0; + clusterParams.gdSymbolWindowActionPointOffset = 1; + clusterParams.gdTSSTransmitter = 9; + clusterParams.gdWakeupTxActive = 60; + clusterParams.gdWakeupTxIdle = 180; + clusterParams.gListenNoise = 2; + clusterParams.gMacroPerCycle = 3636; + clusterParams.gMaxWithoutClockCorrectionFatal = 2; + clusterParams.gMaxWithoutClockCorrectionPassive = 2; + clusterParams.gNumberOfMiniSlots = 291; + clusterParams.gNumberOfStaticSlots = 70; + clusterParams.gPayloadLengthStatic = 13; + clusterParams.gSyncFrameIDCountMax = 15; + + FlexrayNodeParameters nodeParams; + nodeParams.pAllowHaltDueToClock = 1; + nodeParams.pAllowPassiveToActive = 0; + nodeParams.pChannels = FlexrayChannel::AB; + nodeParams.pClusterDriftDamping = 2; + nodeParams.pdAcceptedStartupRange = 212; + nodeParams.pdListenTimeout = 400162; + nodeParams.pKeySlotOnlyEnabled = 0; + nodeParams.pKeySlotUsedForStartup = 1; + nodeParams.pKeySlotUsedForSync = 0; + nodeParams.pLatestTx = 249; + nodeParams.pMacroInitialOffsetA = 3; + nodeParams.pMacroInitialOffsetB = 3; + nodeParams.pMicroInitialOffsetA = 6; + nodeParams.pMicroInitialOffsetB = 6; + nodeParams.pMicroPerCycle = 200000; + nodeParams.pOffsetCorrectionOut = 127; + nodeParams.pOffsetCorrectionStart = 3632; + nodeParams.pRateCorrectionOut = 81; + nodeParams.pWakeupChannel = FlexrayChannel::A; + nodeParams.pWakeupPattern = 33; + nodeParams.pdMicrotick = FlexrayClockPeriod::T25NS; + nodeParams.pSamplesPerMicrotick = 2; + + FlexrayControllerConfig config; + config.clusterParams = clusterParams; + config.nodeParams = nodeParams; + + return config; +} + + +} // namespace FlexrayDemoCommon \ No newline at end of file diff --git a/Demos/communication/Flexray/FlexrayNode0Demo.cpp b/Demos/communication/Flexray/FlexrayNode0Demo.cpp new file mode 100644 index 000000000..48f460e61 --- /dev/null +++ b/Demos/communication/Flexray/FlexrayNode0Demo.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "FlexrayDemoCommon.hpp" + +using namespace SilKit::Services::Flexray; + +class FlexrayNode0 : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + std::unique_ptr _flexrayNode; + std::string _networkName = "PowerTrain1"; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the Flexray network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + auto flexrayController = GetParticipant()->CreateFlexrayController("FlexrayController1", _networkName); + + FlexrayControllerConfig config = FlexrayDemoCommon::MakeControllerConfig(); + + // The specific buffer configs for this node + std::vector bufferConfigs; + FlexrayTxBufferConfig baseBufferCfg; + baseBufferCfg.offset = 0; + baseBufferCfg.repetition = 1; + baseBufferCfg.hasPayloadPreambleIndicator = false; + baseBufferCfg.headerCrc = 5; + baseBufferCfg.transmissionMode = FlexrayTransmissionMode::SingleShot; + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::AB; + cfg.slotId = 40; + bufferConfigs.push_back(cfg); + } + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::A; + cfg.slotId = 41; + bufferConfigs.push_back(cfg); + } + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::B; + cfg.slotId = 42; + bufferConfigs.push_back(cfg); + } + config.bufferConfigs = bufferConfigs; + + // The specific keyslotID for this node + config.nodeParams.pKeySlotId = 40; + + _flexrayNode = std::make_unique(flexrayController, std::move(config), GetLogger()); + } + + void InitControllers() override + { + // Configuration is done via POC state + } + + void DoWorkAsync() override + { + // No async mode for flexray + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _flexrayNode->DoAction(now); + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "Node0"; + FlexrayNode0 app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Flexray: Node0 of a two-node Flexray system", {ApplicationBase::DefaultArg::Async}); + + return app.Run(); +} + + diff --git a/Demos/communication/Flexray/FlexrayNode1Demo.cpp b/Demos/communication/Flexray/FlexrayNode1Demo.cpp new file mode 100644 index 000000000..755ce48ab --- /dev/null +++ b/Demos/communication/Flexray/FlexrayNode1Demo.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "FlexrayDemoCommon.hpp" + +using namespace SilKit::Services::Flexray; + +class FlexrayNode0 : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + std::unique_ptr _flexrayNode; + std::string _networkName = "PowerTrain1"; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the Flexray network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + auto flexrayController = GetParticipant()->CreateFlexrayController("FlexrayController1", _networkName); + + FlexrayControllerConfig config = FlexrayDemoCommon::MakeControllerConfig(); + + // The specific buffer configs for this node + std::vector bufferConfigs; + FlexrayTxBufferConfig baseBufferCfg; + baseBufferCfg.offset = 0; + baseBufferCfg.repetition = 1; + baseBufferCfg.hasPayloadPreambleIndicator = false; + baseBufferCfg.headerCrc = 5; + baseBufferCfg.transmissionMode = FlexrayTransmissionMode::SingleShot; + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::AB; + cfg.slotId = 60; + bufferConfigs.push_back(cfg); + } + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::A; + cfg.slotId = 61; + bufferConfigs.push_back(cfg); + } + { + FlexrayTxBufferConfig cfg = baseBufferCfg; + cfg.channels = FlexrayChannel::B; + cfg.slotId = 62; + bufferConfigs.push_back(cfg); + } + config.bufferConfigs = bufferConfigs; + + // The specific keyslotID for this node + config.nodeParams.pKeySlotId = 60; + + _flexrayNode = std::make_unique(flexrayController, std::move(config), GetLogger()); + } + + void InitControllers() override + { + // Configuration is done via POC state + } + + void DoWorkAsync() override + { + // No async mode for flexray + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _flexrayNode->DoAction(now); + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "Node1"; + FlexrayNode0 app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Flexray: Node1 of a two-node Flexray system", {ApplicationBase::DefaultArg::Async}); + + return app.Run(); +} + + diff --git a/Demos/communication/Lin/CMakeLists.txt b/Demos/communication/Lin/CMakeLists.txt new file mode 100644 index 000000000..c490f0aa7 --- /dev/null +++ b/Demos/communication/Lin/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_communication_demo(SilKitDemoLinSlave LinSlaveDemo.cpp) +make_silkit_communication_demo(SilKitDemoLinMaster LinMasterDemo.cpp) + +make_silkit_communication_demo(SilKitDemoLinSlaveDynamic LinSlaveDynamicDemo.cpp) +make_silkit_communication_demo(SilKitDemoLinMasterDynamic LinMasterDynamicDemo.cpp) \ No newline at end of file diff --git a/Demos/communication/Lin/LinDemoCommon.hpp b/Demos/communication/Lin/LinDemoCommon.hpp new file mode 100644 index 000000000..936e15efa --- /dev/null +++ b/Demos/communication/Lin/LinDemoCommon.hpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/services/string_utils.hpp" +#include "silkit/services/lin/all.hpp" +#include "silkit/services/lin/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" + +using namespace SilKit::Services::Lin; + +// This is the common behavior used in LinSlaveDemo and LinMasterDemo +namespace LinDemoCommon { + +class Timer +{ +public: + void Set(std::chrono::nanoseconds timeOut, std::function action) noexcept + { + auto lock = Lock(); + + _isActive = true; + _timeOut = timeOut; + _action = std::move(action); + } + + void Clear() noexcept + { + auto lock = Lock(); + + _isActive = false; + _timeOut = std::chrono::nanoseconds::max(); + _action = std::function{}; + } + + auto ExecuteIfDue(std::chrono::nanoseconds now) -> bool + { + std::function action; + + { + auto lock = Lock(); + + if (!_isActive || (now < _timeOut)) + { + return false; + } + + action = std::move(_action); + + // Clear + _isActive = false; + _timeOut = std::chrono::nanoseconds::max(); + _action = std::function{}; + } + + action(now); + + return true; + } + +private: + bool _isActive = false; + std::chrono::nanoseconds _timeOut = std::chrono::nanoseconds::max(); + std::function _action; + + std::mutex _mutex; + auto Lock() -> std::unique_lock + { + return std::unique_lock{_mutex}; + } +}; + +class Schedule +{ +public: + Schedule( + std::initializer_list>> tasks, + bool autoScheduleNext = true) + : _autoScheduleNext{autoScheduleNext} + { + for (auto&& task : tasks) + { + _schedule.emplace_back(task.first, task.second); + } + Reset(); + } + + void Reset() + { + _nextTask = _schedule.begin(); + ScheduleNextTask(); + } + + void ExecuteTask(std::chrono::nanoseconds now) + { + _now = now; + if (_timer.ExecuteIfDue(now) && _autoScheduleNext) + { + ScheduleNextTask(); + } + } + + void ScheduleNextTask() + { + auto currentTask = _nextTask++; + if (_nextTask == _schedule.end()) + { + _nextTask = _schedule.begin(); + } + + _timer.Set(_now + currentTask->delay, currentTask->action); + } + +private: + + struct Task + { + Task(std::chrono::nanoseconds delay, std::function action) + : delay{delay} + , action{action} + { + } + + std::chrono::nanoseconds delay; + std::function action; + }; + + Timer _timer; + std::vector _schedule; + std::vector::iterator _nextTask; + std::chrono::nanoseconds _now = 0ns; + bool _autoScheduleNext; +}; + +} // namespace LinDemoCommon diff --git a/Demos/communication/Lin/LinMasterDemo.cpp b/Demos/communication/Lin/LinMasterDemo.cpp new file mode 100644 index 000000000..e054a4ef5 --- /dev/null +++ b/Demos/communication/Lin/LinMasterDemo.cpp @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "LinDemoCommon.hpp" + +class LinMaster : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ILinController* _linController{nullptr}; + std::unique_ptr _schedule; + std::chrono::nanoseconds _now{0ns}; + std::string _networkName = "LIN1"; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the LIN network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + _linController = GetParticipant()->CreateLinController("LinController1", _networkName); + + _linController->AddFrameStatusHandler( + [this](ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) { + switch (frameStatusEvent.status) + { + case LinFrameStatus::LIN_RX_OK: + break; // good case, no need to warn + case LinFrameStatus::LIN_TX_OK: + break; // good case, no need to warn + default: + std::stringstream ss; + ss << "LIN transmission failed!"; + GetLogger()->Warn(ss.str()); + } + + std::stringstream ss; + ss << "Received " << frameStatusEvent.frame << ", status=" << frameStatusEvent.status; + GetLogger()->Info(ss.str()); + }); + + + _linController->AddWakeupHandler([this](ILinController* /*linController*/, const LinWakeupEvent& wakeupEvent) { + if (_linController->Status() != LinControllerStatus::Sleep) + { + std::stringstream ss; + ss << "Received Wakeup pulse while LinControllerStatus is " << _linController->Status() << "."; + GetLogger()->Warn(ss.str()); + } + + std::stringstream ss; + ss << "Received Wakeup pulse, direction=" << wakeupEvent.direction; + GetLogger()->Info(ss.str()); + + _linController->WakeupInternal(); + }); + + _schedule = std::make_unique( + std::initializer_list>>{ + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrame_16(); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrame_17(); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrame_18(); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrame_19(); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrame_34(); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { GoToSleep(); }}}); + } + + void InitControllers() override + { + LinControllerConfig config; + config.controllerMode = LinControllerMode::Master; + config.baudRate = 20'000; + _linController->Init(config); + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _now = now; + _schedule->ExecuteTask(now); + } + + void DoWorkAsync() override + { + _schedule->ExecuteTask(_now); + _now += 1ms; + } + + // LinMaster schedule + + void SendFrameIfOperational(const LinFrame& linFrame, LinFrameResponseType responseType) + { + const auto linId{static_cast(linFrame.id)}; + + if (_linController->Status() != LinControllerStatus::Operational) + { + std::stringstream ss; + ss << "LIN Frame with ID=" << linId << " not sent, since the controller is not operational"; + GetLogger()->Warn(ss.str()); + return; + } + + _linController->SendFrame(linFrame, responseType); + + std::stringstream ss; + if (responseType == LinFrameResponseType::SlaveResponse) + { + ss << "LIN frame Header sent for ID=" << linId; + } + else + { + ss << "LIN frame sent with ID=" << linId; + } + GetLogger()->Info(ss.str()); + } + + void SendFrame_16() + { + LinFrame frame; + frame.id = 16; + frame.checksumModel = LinChecksumModel::Classic; + frame.dataLength = 6; + frame.data = std::array{1, 6, 1, 6, 1, 6, 1, 6}; + + SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); + } + + void SendFrame_17() + { + LinFrame frame; + frame.id = 17; + frame.checksumModel = LinChecksumModel::Classic; + frame.dataLength = 6; + frame.data = std::array{1, 7, 1, 7, 1, 7, 1, 7}; + + SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); + } + + void SendFrame_18() + { + LinFrame frame; + frame.id = 18; + frame.checksumModel = LinChecksumModel::Enhanced; + frame.dataLength = 8; + frame.data = std::array{0}; + + SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); + } + + void SendFrame_19() + { + LinFrame frame; + frame.id = 19; + frame.checksumModel = LinChecksumModel::Classic; + frame.dataLength = 8; + frame.data = std::array{0}; + + SendFrameIfOperational(frame, LinFrameResponseType::MasterResponse); + } + + void SendFrame_34() + { + LinFrame frame; + frame.id = 34; + frame.checksumModel = LinChecksumModel::Enhanced; + frame.dataLength = 8; + + SendFrameIfOperational(frame, LinFrameResponseType::SlaveResponse); + } + + void GoToSleep() + { + std::stringstream ss; + ss << "Sending Go-To-Sleep command and entering sleep state"; + GetLogger()->Info(ss.str()); + + _linController->GoToSleep(); + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "LinMaster"; + LinMaster app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Lin: Master Node"); + + return app.Run(); +} diff --git a/Demos/communication/Lin/LinMasterDynamicDemo.cpp b/Demos/communication/Lin/LinMasterDynamicDemo.cpp new file mode 100644 index 000000000..599957f99 --- /dev/null +++ b/Demos/communication/Lin/LinMasterDynamicDemo.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "LinDemoCommon.hpp" +#include "silkit/experimental/services/lin/LinControllerExtensions.hpp" + +class LinMaster : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ILinController* _linController{nullptr}; + std::unique_ptr _schedule; + std::chrono::nanoseconds _now{0ns}; + std::string _networkName = "LIN1"; + std::unordered_map _masterResponses; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the LIN network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + _linController = GetParticipant()->CreateLinController("LinController1", _networkName); + + _linController->AddFrameStatusHandler( + [this](ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) { + + switch (frameStatusEvent.status) + { + case LinFrameStatus::LIN_RX_OK: + break; // good case, no need to warn + case LinFrameStatus::LIN_TX_OK: + break; // good case, no need to warn + default: + std::stringstream ss; + ss << "LIN transmission failed!"; + GetLogger()->Warn(ss.str()); + } + + std::stringstream ss; + ss << "Received FrameStatus for " << frameStatusEvent.frame << ", status=" << frameStatusEvent.status; + GetLogger()->Info(ss.str()); + + _schedule->ScheduleNextTask(); + }); + + _linController->AddWakeupHandler([this](ILinController* /*linController*/, const LinWakeupEvent& wakeupEvent) { + if (_linController->Status() != LinControllerStatus::Sleep) + { + std::stringstream ss; + ss << "Received Wakeup pulse while LinControllerStatus is " << _linController->Status() << "."; + GetLogger()->Warn(ss.str()); + } + + std::stringstream ss; + ss << "Received Wakeup pulse, direction=" << wakeupEvent.direction; + GetLogger()->Info(ss.str()); + + _linController->WakeupInternal(); + + _schedule->ScheduleNextTask(); + + }); + + SilKit::Experimental::Services::Lin::AddFrameHeaderHandler( + _linController, [this](ILinController* linController, + const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& headerEvent) { + + { + std::stringstream ss; + ss << "Received frame header: id=" << (int)headerEvent.id; + GetLogger()->Info(ss.str()); + } + + const auto it = _masterResponses.find(headerEvent.id); + if (it != _masterResponses.end()) + { + std::stringstream ss; + ss << "Sending dynamic response: id=" << static_cast(headerEvent.id); + GetLogger()->Info(ss.str()); + + const auto& frame = it->second; + SilKit::Experimental::Services::Lin::SendDynamicResponse(linController, frame); + } + else + { + std::stringstream ss; + ss << "!! Not sending dynamic response: id=" << static_cast(headerEvent.id); + GetLogger()->Info(ss.str()); + } + + }); + + LinFrame f16{}; + f16.id = 16; + f16.checksumModel = LinChecksumModel::Classic; + f16.dataLength = 6; + f16.data = std::array{1, 6, 1, 6, 1, 6, 1, 6}; + _masterResponses[f16.id] = f16; + + LinFrame f17{}; + f17.id = 17; + f17.checksumModel = LinChecksumModel::Classic; + f17.dataLength = 6; + f17.data = std::array{1, 7, 1, 7, 1, 7, 1, 7}; + _masterResponses[f17.id] = f17; + + LinFrame f18{}; + f18.id = 18; + f18.checksumModel = LinChecksumModel::Enhanced; + f18.dataLength = 8; + f18.data = std::array{0}; + _masterResponses[f18.id] = f18; + + LinFrame f19{}; + f19.id = 19; + f19.checksumModel = LinChecksumModel::Classic; + f19.dataLength = 8; + f19.data = std::array{0}; + _masterResponses[f19.id] = f19; + + _schedule = std::make_unique( + std::initializer_list>>{ + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrameHeader(16); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrameHeader(17); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrameHeader(18); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrameHeader(19); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { SendFrameHeader(34); }}, + {5ms, [this](std::chrono::nanoseconds /*now*/) { GoToSleep(); }} + }, false); + + } + + void InitControllers() override + { + SilKit::Experimental::Services::Lin::LinControllerDynamicConfig config; + config.controllerMode = LinControllerMode::Master; + config.baudRate = 20'000; + + SilKit::Experimental::Services::Lin::InitDynamic(_linController, config); + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _now = now; + DoWork(); + } + + void DoWorkAsync() override + { + DoWork(); + _now += 1ms; + } + + void DoWork() + { + if (_linController->Status() != LinControllerStatus::Operational) + return; + _schedule->ExecuteTask(_now); + } + + // LinMaster schedule + + void SendFrameHeader(LinId linId) + { + _linController->SendFrameHeader(linId); + std::stringstream ss; + ss << "Sending LIN frame header, ID=" << static_cast(linId) << " @" << _now; + GetLogger()->Info(ss.str()); + } + + void GoToSleep() + { + std::stringstream ss; + ss << "Sending Go-To-Sleep command and entering sleep state"; + GetLogger()->Info(ss.str()); + + _linController->GoToSleep(); + } + +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "LinMaster"; + LinMaster app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Lin: Master Node"); + + return app.Run(); +} diff --git a/Demos/communication/Lin/LinSlaveDemo.cpp b/Demos/communication/Lin/LinSlaveDemo.cpp new file mode 100644 index 000000000..e91a84516 --- /dev/null +++ b/Demos/communication/Lin/LinSlaveDemo.cpp @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "LinDemoCommon.hpp" +#include "silkit/experimental/services/lin/LinControllerExtensions.hpp" + +class LinSlave: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ILinController* _linController{nullptr}; + LinDemoCommon::Timer _timer; + std::chrono::nanoseconds _now{0ns}; + std::string _networkName = "LIN1"; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the LIN network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + _linController = GetParticipant()->CreateLinController("LinController1", _networkName); + + _linController->AddFrameStatusHandler( + [this](ILinController* linController, const LinFrameStatusEvent& frameStatusEvent) { + + // On a TX acknowledge for ID 34, update the TxBuffer for the next transmission + if (frameStatusEvent.frame.id == 34) + { + LinFrame frame34; + frame34.id = 34; + frame34.checksumModel = LinChecksumModel::Enhanced; + frame34.dataLength = 8; + frame34.data = {static_cast(rand() % 10), 0, 0, 0, 0, 0, 0, 0}; + _linController->UpdateTxBuffer(frame34); + } + + std::stringstream ss; + ss << "Received " << frameStatusEvent.frame << " status=" << frameStatusEvent.status; + + GetLogger()->Info(ss.str()); + + }); + + _linController->AddGoToSleepHandler( + [this](ILinController* /*linController*/, const LinGoToSleepEvent& /*goToSleepEvent*/) { + + std::stringstream ss; + ss << "Received go-to-sleep command; entering sleep mode."; + GetLogger()->Info(ss.str()); + + // Wakeup in 15 ms + _timer.Set(_now + 15ms, [this](std::chrono::nanoseconds now) { + std::stringstream ss; + ss << "Sending Wakeup pulse @" << now; + GetLogger()->Info(ss.str()); + + _linController->Wakeup(); + }); + _linController->GoToSleepInternal(); + }); + + _linController->AddWakeupHandler([this](ILinController* /*linController*/, const LinWakeupEvent& wakeupEvent) { + + std::stringstream ss; + ss << "Received Wakeup pulse, direction=" << wakeupEvent.direction + << "; Entering normal operation mode."; + GetLogger()->Info(ss.str()); + + // No need to set the controller status if we sent the wakeup + if (wakeupEvent.direction == SilKit::Services::TransmitDirection::RX) + { + _linController->WakeupInternal(); + } + + }); + } + + void InitControllers() override + { + // Configure LIN Controller to receive a LinFrameResponse for LIN ID 16 + LinFrameResponse response_16; + response_16.frame.id = 16; + response_16.frame.checksumModel = LinChecksumModel::Classic; + response_16.frame.dataLength = 6; + response_16.responseMode = LinFrameResponseMode::Rx; + + // Configure LIN Controller to receive a LinFrameResponse for LIN ID 17 + // - This LinFrameResponseMode::Unused causes the controller to ignore + // this message and not trigger a callback. This is also the default. + LinFrameResponse response_17; + response_17.frame.id = 17; + response_17.frame.checksumModel = LinChecksumModel::Classic; + response_17.frame.dataLength = 6; + response_17.responseMode = LinFrameResponseMode::Unused; + + // Configure LIN Controller to receive LIN ID 18 + // - LinChecksumModel does not match with master --> Receive with LIN_RX_ERROR + LinFrameResponse response_18; + response_18.frame.id = 18; + response_18.frame.checksumModel = LinChecksumModel::Classic; + response_18.frame.dataLength = 8; + response_18.responseMode = LinFrameResponseMode::Rx; + + // Configure LIN Controller to receive LIN ID 19 + // - dataLength does not match with master --> Receive with LIN_RX_ERROR + LinFrameResponse response_19; + response_19.frame.id = 19; + response_19.frame.checksumModel = LinChecksumModel::Enhanced; + response_19.frame.dataLength = 1; + response_19.responseMode = LinFrameResponseMode::Rx; + + // Configure LIN Controller to send a LinFrameResponse for LIN ID 34 + LinFrameResponse response_34; + response_34.frame.id = 34; + response_34.frame.checksumModel = LinChecksumModel::Enhanced; + response_34.frame.dataLength = 8; + response_34.frame.data = std::array{3, 4, 3, 4, 3, 4, 3, 4}; + response_34.responseMode = LinFrameResponseMode::TxUnconditional; + + LinControllerConfig config; + config.controllerMode = LinControllerMode::Slave; + config.baudRate = 20'000; + config.frameResponses.push_back(response_16); + config.frameResponses.push_back(response_17); + config.frameResponses.push_back(response_18); + config.frameResponses.push_back(response_19); + config.frameResponses.push_back(response_34); + + _linController->Init(config); + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _now = now; + _timer.ExecuteIfDue(now); + } + + void DoWorkAsync() override + { + _timer.ExecuteIfDue(_now); + _now += 1ms; + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "LinSlave"; + LinSlave app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Lin: Slave Node"); + + return app.Run(); +} + diff --git a/Demos/communication/Lin/LinSlaveDynamicDemo.cpp b/Demos/communication/Lin/LinSlaveDynamicDemo.cpp new file mode 100644 index 000000000..ee66eafe1 --- /dev/null +++ b/Demos/communication/Lin/LinSlaveDynamicDemo.cpp @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "LinDemoCommon.hpp" +#include "silkit/experimental/services/lin/LinControllerExtensions.hpp" + +class LinSlave: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + ILinController* _linController{nullptr}; + LinDemoCommon::Timer _timer; + std::chrono::nanoseconds _now{0ns}; + std::string _networkName = "LIN1"; + std::unordered_map _slaveResponses; + + void AddCommandLineArgs() override + { + GetCommandLineParser()->Add( + "network", "N", _networkName, "-N, --network ", + std::vector{"Name of the LIN network to use.", "Defaults to '" + _networkName + "'."}); + } + + void EvaluateCommandLineArgs() override + { + _networkName = GetCommandLineParser()->Get("network").Value(); + } + + void CreateControllers() override + { + _linController = GetParticipant()->CreateLinController("LinController1", _networkName); + UpdateDynamicResponseTo34(); + + _linController->AddFrameStatusHandler( + [this](ILinController* /*linController*/, const LinFrameStatusEvent& frameStatusEvent) { + + // On a TX acknowledge for ID 34, update the TxBuffer for the next transmission + if (frameStatusEvent.frame.id == 34) + { + UpdateDynamicResponseTo34(); + } + + std::stringstream ss; + ss << "Received " << frameStatusEvent.frame << " status=" << frameStatusEvent.status + << " timestamp=" << frameStatusEvent.timestamp; + + GetLogger()->Info(ss.str()); + + }); + + _linController->AddGoToSleepHandler( + [this](ILinController* /*linController*/, const LinGoToSleepEvent& /*goToSleepEvent*/) { + + std::stringstream ss; + ss << "Received go-to-sleep command; entering sleep mode."; + GetLogger()->Info(ss.str()); + + // Wakeup in 10 ms + _timer.Set(_now + 10ms, [this](std::chrono::nanoseconds now) { + std::stringstream ss; + ss << "Sending Wakeup pulse @" << now; + GetLogger()->Info(ss.str()); + + _linController->Wakeup(); + }); + _linController->GoToSleepInternal(); + }); + + _linController->AddWakeupHandler([this](ILinController* /*linController*/, const LinWakeupEvent& wakeupEvent) { + + std::stringstream ss; + ss << "LIN Slave received wakeup pulse; direction=" << wakeupEvent.direction + << "; Entering normal operation mode."; + GetLogger()->Info(ss.str()); + + // No need to set the controller status if we sent the wakeup + if (wakeupEvent.direction == SilKit::Services::TransmitDirection::RX) + { + _linController->WakeupInternal(); + } + + }); + + SilKit::Experimental::Services::Lin::AddFrameHeaderHandler( + _linController, [this](ILinController* /*linController*/, + const SilKit::Experimental::Services::Lin::LinFrameHeaderEvent& headerEvent) { + + { + std::stringstream ss; + ss << "Received frame header: id=" << (int)headerEvent.id << " @" << headerEvent.timestamp; + GetLogger()->Info(ss.str()); + } + + const auto it = _slaveResponses.find(headerEvent.id); + if (it != _slaveResponses.end()) + { + std::stringstream ss; + ss << "Sending dynamic response: id=" << static_cast(headerEvent.id); + GetLogger()->Info(ss.str()); + + const auto& frame = it->second; + SilKit::Experimental::Services::Lin::SendDynamicResponse(_linController, frame); + } + else + { + std::stringstream ss; + ss << "!! Not sending dynamic response: id=" << static_cast(headerEvent.id); + GetLogger()->Info(ss.str()); + } + + }); + } + + void InitControllers() override + { + SilKit::Experimental::Services::Lin::LinControllerDynamicConfig config{}; + config.controllerMode = LinControllerMode::Slave; + config.baudRate = 20'000; + + SilKit::Experimental::Services::Lin::InitDynamic(_linController, config); + } + + void DoWorkSync(std::chrono::nanoseconds now) override + { + _now = now; + _timer.ExecuteIfDue(now); + } + + void DoWorkAsync() override + { + _timer.ExecuteIfDue(_now); + _now += 1ms; + } + + void UpdateDynamicResponseTo34() + { + LinFrame f34{}; + f34.id = 34; + f34.checksumModel = LinChecksumModel::Enhanced; + f34.dataLength = 6; + f34.data = {static_cast(rand() % 10), 0, 0, 0, 0, 0, 0, 0}; + _slaveResponses[f34.id] = f34; + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "LinSlave"; + LinSlave app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Lin: Slave Node"); + + return app.Run(); +} + diff --git a/Demos/communication/PubSub/CMakeLists.txt b/Demos/communication/PubSub/CMakeLists.txt new file mode 100644 index 000000000..a5894426f --- /dev/null +++ b/Demos/communication/PubSub/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_communication_demo(SilKitDemoPublisher PublisherDemo.cpp) +make_silkit_communication_demo(SilKitDemoSubscriber SubscriberDemo.cpp) diff --git a/Demos/communication/PubSub/PubSubDemoCommon.hpp b/Demos/communication/PubSub/PubSubDemoCommon.hpp new file mode 100644 index 000000000..081eca5ea --- /dev/null +++ b/Demos/communication/PubSub/PubSubDemoCommon.hpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/services/pubsub/all.hpp" +#include "silkit/services/pubsub/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" +#include "silkit/util/serdes/Serialization.hpp" + +using namespace SilKit::Services::PubSub; + +// This are the common data structures used in PublisherDemo and SubscriberDemo +namespace PubSubDemoCommon { + +const std::string mediaType{SilKit::Util::SerDes::MediaTypeData()}; +PubSubSpec dataSpecGps{"Gps", mediaType}; +PubSubSpec dataSpecTemperature{"Temperature", mediaType}; + +// ---------------------------------------------------------------- +// Data structure, serialization and deserialization for GPS Data +// ---------------------------------------------------------------- + +struct GpsData +{ + double latitude; + double longitude; + std::string signalQuality; +}; +std::vector SerializeGPSData(const PubSubDemoCommon::GpsData& gpsData) +{ + SilKit::Util::SerDes::Serializer serializer; + serializer.BeginStruct(); + serializer.Serialize(gpsData.latitude); + serializer.Serialize(gpsData.longitude); + serializer.Serialize(gpsData.signalQuality); + serializer.EndStruct(); + + return serializer.ReleaseBuffer(); +} + +PubSubDemoCommon::GpsData DeserializeGPSData(const std::vector& eventData) +{ + PubSubDemoCommon::GpsData gpsData; + + SilKit::Util::SerDes::Deserializer deserializer(eventData); + deserializer.BeginStruct(); + gpsData.latitude = deserializer.Deserialize(); + gpsData.longitude = deserializer.Deserialize(); + gpsData.signalQuality = deserializer.Deserialize(); + deserializer.EndStruct(); + + return gpsData; +} + +// ----------------------------------------------------------------- +// Serialization and deserialization for a double (temperature) +// ----------------------------------------------------------------- + +std::vector SerializeTemperature(double temperature) +{ + SilKit::Util::SerDes::Serializer temperatureSerializer; + temperatureSerializer.Serialize(temperature); + + return temperatureSerializer.ReleaseBuffer(); +} + +double DeserializeTemperature(const std::vector& eventData) +{ + double temperature; + + SilKit::Util::SerDes::Deserializer deserializer(eventData); + temperature = deserializer.Deserialize(); + + return temperature; +} + +} // namespace PubSubDemoCommon diff --git a/Demos/communication/PubSub/PublisherDemo.cpp b/Demos/communication/PubSub/PublisherDemo.cpp new file mode 100644 index 000000000..fda6029a8 --- /dev/null +++ b/Demos/communication/PubSub/PublisherDemo.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "PubSubDemoCommon.hpp" + +class Publisher: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + + IDataPublisher* _gpsPublisher; + IDataPublisher* _temperaturePublisher; + + void AddCommandLineArgs() override + { + + } + + void EvaluateCommandLineArgs() override + { + + } + + void CreateControllers() override + { + _gpsPublisher = GetParticipant()->CreateDataPublisher("GpsPublisher", PubSubDemoCommon::dataSpecGps, 0); + _temperaturePublisher = + GetParticipant()->CreateDataPublisher("TemperaturePublisher", PubSubDemoCommon::dataSpecTemperature, 0); + } + + void InitControllers() override + { + + } + + void PublishGPSData() + { + PubSubDemoCommon::GpsData gpsData; + gpsData.latitude = 48.8235 + static_cast((rand() % 150)) / 100000; + gpsData.longitude = 9.0965 + static_cast((rand() % 150)) / 100000; + gpsData.signalQuality = "Strong"; + auto gpsSerialized = PubSubDemoCommon::SerializeGPSData(gpsData); + + std::stringstream ss; + ss << "Publishing GPS data: lat=" << gpsData.latitude << ", lon=" << gpsData.longitude + << ", signalQuality=" << gpsData.signalQuality; + GetLogger()->Info(ss.str()); + + _gpsPublisher->Publish(gpsSerialized); + } + + void PublishTemperatureData() + { + double temperature = 25.0 + static_cast(rand() % 10) / 10.0; + auto temperatureSerialized = PubSubDemoCommon::SerializeTemperature(temperature); + + std::stringstream ss; + ss << "Publishing temperature data: temperature=" << temperature; + GetLogger()->Info(ss.str()); + + _temperaturePublisher->Publish(temperatureSerialized); + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + PublishGPSData(); + PublishTemperatureData(); + } + + void DoWorkAsync() override + { + PublishGPSData(); + PublishTemperatureData(); + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "Publisher"; + Publisher app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Publisher: Publish GPS and Temperature data"); + + return app.Run(); +} + diff --git a/Demos/communication/PubSub/SubscriberDemo.cpp b/Demos/communication/PubSub/SubscriberDemo.cpp new file mode 100644 index 000000000..835662a4c --- /dev/null +++ b/Demos/communication/PubSub/SubscriberDemo.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "PubSubDemoCommon.hpp" + +class Subscriber: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + + IDataSubscriber* _gpsSubscriber; + IDataSubscriber* _temperatureSubscriber; + + void AddCommandLineArgs() override + { + + } + + void EvaluateCommandLineArgs() override + { + + } + + void CreateControllers() override + { + _gpsSubscriber = GetParticipant()->CreateDataSubscriber( + "GpsSubscriber", PubSubDemoCommon::dataSpecGps, + [this](IDataSubscriber* /*subscriber*/, const DataMessageEvent& dataMessageEvent) { + auto gpsData = PubSubDemoCommon::DeserializeGPSData(SilKit::Util::ToStdVector(dataMessageEvent.data)); + + std::stringstream ss; + ss << "Received GPS data: lat=" << gpsData.latitude << ", lon=" << gpsData.longitude + << ", signalQuality=" << gpsData.signalQuality; + GetLogger()->Info(ss.str()); + }); + + _temperatureSubscriber = GetParticipant()->CreateDataSubscriber( + "TemperatureSubscriber", PubSubDemoCommon::dataSpecTemperature, + [this](IDataSubscriber* /*subscriber*/, const DataMessageEvent& dataMessageEvent) { + double temperature = + PubSubDemoCommon::DeserializeTemperature(SilKit::Util::ToStdVector(dataMessageEvent.data)); + + std::stringstream ss; + ss << "Received temperature data: temperature=" << temperature; + GetLogger()->Info(ss.str()); + }); + } + + void InitControllers() override + { + + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + + } + + void DoWorkAsync() override + { + + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "Subscriber"; + Subscriber app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - Subscriber: Receive GPS and Temperature data"); + + return app.Run(); +} + diff --git a/Demos/communication/Rpc/CMakeLists.txt b/Demos/communication/Rpc/CMakeLists.txt new file mode 100644 index 000000000..27511b753 --- /dev/null +++ b/Demos/communication/Rpc/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +make_silkit_communication_demo(SilKitDemoRpcServer RpcServerDemo.cpp) +make_silkit_communication_demo(SilKitDemoRpcClient RpcClientDemo.cpp) diff --git a/Demos/communication/Rpc/RpcClientDemo.cpp b/Demos/communication/Rpc/RpcClientDemo.cpp new file mode 100644 index 000000000..3c051f1b3 --- /dev/null +++ b/Demos/communication/Rpc/RpcClientDemo.cpp @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "RpcDemoCommon.hpp" + +class RpcClient : public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + IRpcClient* _rpcClientSignalStrength; + IRpcClient* _rpcClientSort; + uint16_t _callCounter = 0; + + void AddCommandLineArgs() override {} + + void EvaluateCommandLineArgs() override {} + + void CreateControllers() override + { + _rpcClientSignalStrength = GetParticipant()->CreateRpcClient( + "ClientSignalStrength", RpcDemoCommon::rpcSpecSignalStrength, + [this](IRpcClient* /*client*/, RpcCallResultEvent event) { CallReturnGetSignalStrength(event); }); + + _rpcClientSort = GetParticipant()->CreateRpcClient( + "ClientSort", RpcDemoCommon::rpcSpecSort, + [this](IRpcClient* /*client*/, RpcCallResultEvent event) { CallReturnSort(event); }); + } + + void InitControllers() override {} + + void CallSignalStrength() + { + // Add an incrementing callCounter as userContext to identify the corresponding call on reception of a call result. + const auto userContext = reinterpret_cast(uintptr_t(_callCounter++)); + + RpcDemoCommon::TunerData randomTunerData; + randomTunerData.tunerBand = RpcDemoCommon::TunerBand::FM; + std::string band = "FM"; + randomTunerData.frequency = 87.0 + static_cast(rand() % 100) / 100.0; + auto tunerDataSerialized = RpcDemoCommon::SerializeTunerData(randomTunerData); + + std::stringstream ss; + ss << "Calling 'SignalStrength' with arguments: band=" << band << ", frequency=" << randomTunerData.frequency + << " (userContext=" << userContext << ")"; + GetLogger()->Info(ss.str()); + + _rpcClientSignalStrength->Call(tunerDataSerialized, userContext); + } + + void CallReturnGetSignalStrength(RpcCallResultEvent callResult) + { + if (RpcDemoCommon::EvaluateCallStatus(callResult, GetLogger())) + { + auto signalStrength = + RpcDemoCommon::DeserializeSignalStrength(SilKit::Util::ToStdVector(callResult.resultData)); + + std::stringstream ss; + ss << "Call 'GetSignalStrength' returned: signalStrength=" << signalStrength + << " (userContext=" << callResult.userContext << ")"; + GetLogger()->Info(ss.str()); + } + } + + + void CallSort() + { + // Add an incrementing callCounter as userContext to identify the corresponding call on reception of a call result. + const auto userContext = reinterpret_cast(uintptr_t(_callCounter++)); + + std::vector randomNumberList{static_cast(rand() % 10), static_cast(rand() % 10), + static_cast(rand() % 10)}; + auto numberListSerialized = RpcDemoCommon::SerializeSortData(randomNumberList); + + std::stringstream ss; + ss << "Calling 'Sort' with arguments: randomNumberList=" << randomNumberList << " (userContext=" << userContext + << ")"; + GetLogger()->Info(ss.str()); + + _rpcClientSort->Call(numberListSerialized, userContext); + } + + void CallReturnSort(RpcCallResultEvent callResult) + { + if (RpcDemoCommon::EvaluateCallStatus(callResult, GetLogger())) + { + auto sortedNumbers = RpcDemoCommon::DeserializeSortData(SilKit::Util::ToStdVector(callResult.resultData)); + + std::stringstream ss; + ss << "Call 'Sort' returned: sortedNumbers=" << sortedNumbers << " (userContext=" << callResult.userContext + << ")"; + GetLogger()->Info(ss.str()); + } + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + CallSignalStrength(); + CallSort(); + } + + void DoWorkAsync() override + { + CallSignalStrength(); + CallSort(); + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "RpcClient"; + RpcClient app{args}; + app.SetupCommandLineArgs( + argc, argv, "SIL Kit Demo - RPC Client: Call remote procedures with arguments and evaluate the return values"); + + return app.Run(); +} diff --git a/Demos/communication/Rpc/RpcDemoCommon.hpp b/Demos/communication/Rpc/RpcDemoCommon.hpp new file mode 100644 index 000000000..2de9dc522 --- /dev/null +++ b/Demos/communication/Rpc/RpcDemoCommon.hpp @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/services/rpc/all.hpp" +#include "silkit/services/rpc/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" +#include "silkit/util/serdes/Serialization.hpp" + +using namespace SilKit::Services::Rpc; + +static std::ostream& operator<<(std::ostream& os, const SilKit::Util::Span& v) +{ + os << "[ "; + for (auto i : v) + os << static_cast(i) << " "; + os << "]"; + return os; +} + +static std::ostream& operator<<(std::ostream& os, const std::vector& v) +{ + return os << SilKit::Util::ToSpan(v); +} + +// This are the common data structures used in RpcServerDemo and RpcClientDemo +namespace RpcDemoCommon { + +std::string mediaType{SilKit::Util::SerDes::MediaTypeRpc()}; +RpcSpec rpcSpecSignalStrength{"GetSignalStrength", mediaType}; +RpcSpec rpcSpecSort{"Sort", mediaType}; + +// ---------------------------------------------------------------- +// Data structure, serialization and deserialization for Tuner Data +// ---------------------------------------------------------------- + +enum class TunerBand : uint8_t +{ + AM = 0, + FM = 1 +}; + +struct TunerData +{ + double frequency; + TunerBand tunerBand; +}; + +std::vector SerializeTunerData(const TunerData& tunerData) +{ + SilKit::Util::SerDes::Serializer serializer; + serializer.BeginStruct(); + serializer.Serialize(tunerData.frequency); + serializer.Serialize(static_cast(tunerData.tunerBand), 8); + serializer.EndStruct(); + + return serializer.ReleaseBuffer(); +} + +TunerData DeserializeTunerData(const std::vector& eventData) +{ + TunerData tunerData; + + SilKit::Util::SerDes::Deserializer deserializer(eventData); + deserializer.BeginStruct(); + tunerData.frequency = deserializer.Deserialize(); + tunerData.tunerBand = static_cast(deserializer.Deserialize(8)); + deserializer.EndStruct(); + + return tunerData; +} + +// ---------------------------------------------------------------- +// Serialization and deserialization for a double (signal strength) +// ---------------------------------------------------------------- + +std::vector SerializeSignalStrength(double signalStrength) +{ + SilKit::Util::SerDes::Serializer signalStrengthSerializer; + signalStrengthSerializer.Serialize(signalStrength); + + return signalStrengthSerializer.ReleaseBuffer(); +} + +double DeserializeSignalStrength(const std::vector& eventData) +{ + double signalStrength; + + SilKit::Util::SerDes::Deserializer deserializer(eventData); + signalStrength = deserializer.Deserialize(); + + return signalStrength; +} + +// ---------------------------------------------------------------- +// Serialization and deserialization for a std::vector +// ---------------------------------------------------------------- + +std::vector SerializeSortData(const std::vector& numberList) +{ + SilKit::Util::SerDes::Serializer serializer; + serializer.Serialize(numberList); + + return serializer.ReleaseBuffer(); +} + +std::vector DeserializeSortData(const std::vector& eventData) +{ + SilKit::Util::SerDes::Deserializer deserializer(eventData); + std::vector numberList = deserializer.Deserialize>(); + + return numberList; +} + +bool EvaluateCallStatus(RpcCallResultEvent callResult, ILogger* logger) +{ + std::stringstream ss; + bool isSuccessful = false; + + switch (callResult.callStatus) + { + case RpcCallStatus::Success: + { + isSuccessful = true; + break; + } + case RpcCallStatus::ServerNotReachable: + { + ss << "Warning: Call " << callResult.userContext << " failed with RpcCallStatus::ServerNotReachable"; + break; + } + case RpcCallStatus::UndefinedError: + { + ss << "Warning: Call " << callResult.userContext << " failed with RpcCallStatus::UndefinedError"; + break; + } + case RpcCallStatus::InternalServerError: + { + ss << "Warning: Call " << callResult.userContext << " failed with RpcCallStatus::InternalServerError"; + break; + } + case RpcCallStatus::Timeout: + { + ss << "Warning: Call " << callResult.userContext << " failed with RpcCallStatus::Timeout"; + break; + } + default: + break; + } + + if (!isSuccessful) + { + logger->Info(ss.str()); + } + + return isSuccessful; +} + +} // namespace PubSubDemoCommon diff --git a/Demos/communication/Rpc/RpcServerDemo.cpp b/Demos/communication/Rpc/RpcServerDemo.cpp new file mode 100644 index 000000000..ed4b0fbf5 --- /dev/null +++ b/Demos/communication/Rpc/RpcServerDemo.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "ApplicationBase.hpp" +#include "RpcDemoCommon.hpp" +#include + +class RpcServer: public ApplicationBase +{ +public: + // Inherit constructors + using ApplicationBase::ApplicationBase; + +private: + + IRpcServer* _rpcServerSignalStrength; + IRpcServer* _rpcServerSort; + + + void AddCommandLineArgs() override + { + + } + + void EvaluateCommandLineArgs() override + { + + } + + void CreateControllers() override + { + _rpcServerSignalStrength = GetParticipant()->CreateRpcServer("ServerSignalStrength", RpcDemoCommon::rpcSpecSignalStrength, + [this](IRpcServer* server, RpcCallEvent event) { + + auto tunerData = RpcDemoCommon::DeserializeTunerData(SilKit::Util::ToStdVector(event.argumentData)); + + // Calculation + auto calcDummySignalStrength = [](double frequency, double peakFreq) { + return exp(-(50.0 + static_cast(rand() % 10)) * pow(frequency - peakFreq, 2)); + }; + + double signalStrength = 0; + std::string band = ""; + + switch (tunerData.tunerBand) + { + case RpcDemoCommon::TunerBand::AM: + band = "AM"; + signalStrength = calcDummySignalStrength(tunerData.frequency, 0.558); + break; + case RpcDemoCommon::TunerBand::FM: + band = "FM"; + signalStrength = calcDummySignalStrength(tunerData.frequency, 87.6); + break; + default: + break; + } + + std::stringstream ss; + ss << "Receive call to 'GetSignalStrength' with arguments: band=" << band + << ", frequency=" << tunerData.frequency << "; returning signalStrength=" << signalStrength; + GetLogger()->Info(ss.str()); + + // Serialize result data + SilKit::Util::SerDes::Serializer serializer; + serializer.Serialize(signalStrength); + + // Submit call result to client + server->SubmitResult(event.callHandle, serializer.ReleaseBuffer()); + }); + + _rpcServerSort = GetParticipant()->CreateRpcServer("ServerSort", RpcDemoCommon::rpcSpecSort, + [this](IRpcServer* server, RpcCallEvent event) { + // Deserialize incoming data + auto argumentData = RpcDemoCommon::DeserializeSortData(SilKit::Util::ToStdVector(event.argumentData)); + + // Calculation + std::vector resultData(argumentData); + std::sort(resultData.begin(), resultData.end()); + + std::stringstream ss; + ss << "Receive call to 'Sort' with argumentData=" << argumentData << "; returning resultData=" << resultData; + GetLogger()->Info(ss.str()); + + // Serialize result data + SilKit::Util::SerDes::Serializer serializer; + serializer.Serialize(resultData); + + // Submit call result to client + server->SubmitResult(event.callHandle, serializer.ReleaseBuffer()); + }); + } + + void InitControllers() override + { + + } + + void DoWorkSync(std::chrono::nanoseconds /*now*/) override + { + + } + + void DoWorkAsync() override + { + + } +}; + +int main(int argc, char** argv) +{ + Arguments args; + args.participantName = "RpcServer"; + RpcServer app{args}; + app.SetupCommandLineArgs(argc, argv, "SIL Kit Demo - RPC Server: Provide remotely accessible procedures with arguments and return values"); + + return app.Run(); +} + diff --git a/Demos/communication/include/ApplicationBase.hpp b/Demos/communication/include/ApplicationBase.hpp new file mode 100644 index 000000000..545f04db1 --- /dev/null +++ b/Demos/communication/include/ApplicationBase.hpp @@ -0,0 +1,729 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "silkit/SilKit.hpp" +#include "silkit/services/logging/ILogger.hpp" +#include "silkit/services/orchestration/all.hpp" +#include "silkit/services/orchestration/string_utils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "SignalHandler.hpp" +#include "CommandlineParser.hpp" + +using namespace SilKit; +using namespace SilKit::Services::Orchestration; +using namespace SilKit::Services::Can; +using namespace SilKit::Services::Logging; +using namespace SilKit::Util; + +using namespace std::chrono_literals; + +// The application arguments. +// Values might be overridden by the command line arguments. +struct Arguments +{ + std::string participantName = "Participant1"; + std::string registryUri = "silkit://localhost:8500"; + bool runAutonomous{false}; + bool runAsync{false}; + std::chrono::nanoseconds duration = 1ms; + bool asFastAsPossible{false}; +}; +std::shared_ptr _participantConfiguration{nullptr}; + +std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) +{ + out << std::chrono::duration_cast(timestamp).count() << "ms"; + return out; +} + +class ApplicationBase +{ +public: + ApplicationBase(Arguments args = Arguments{}) + { + _arguments = args; + _commandLineParser = std::make_shared(); + } + +protected: + // Must be implemented by the actual application + + // Add application specific command line arguments. Called during SetupCommandLineArgs. + virtual void AddCommandLineArgs() = 0; + // Evaluate application specific command line arguments. Called during SetupCommandLineArgs. + virtual void EvaluateCommandLineArgs() = 0; + + // Controller creates SIL Kit controllers / services here. + virtual void CreateControllers() = 0; + + // Boot up SIL Kit controllers here. + // Called in the CommunicationReadyHandler to ensure that messages emerging from the + // controller creation reach other involved participants. + virtual void InitControllers() = 0; + + // When running with the '--async' flag, this is called continuously in a separate worker thread. + // This thread is managed by the ApplicationBase. + // The function will trigger as soon as the participant state is 'Running'. + virtual void DoWorkAsync() = 0; + + // When running with time synchronization (no '--async' flag), this is called in the SimulationStepHandler. + virtual void DoWorkSync(std::chrono::nanoseconds now) = 0; + +public: + // Default command line argument identifiers (to allow exclusion) + enum struct DefaultArg + { + Name, + Uri, + Log, + Config, + Async, + Autonomous, + Duration, + AsFastAsPossible + }; + +private: + // Command line parser + std::shared_ptr _commandLineParser; + + // Names of the default command line arguments. + // Note that the 'short name' (e.g. -n) and description are defined at runtime. + const std::unordered_map defaultArgName = {{DefaultArg::Name, "name"}, + {DefaultArg::Uri, "registry-uri"}, + {DefaultArg::Log, "log"}, + {DefaultArg::Config, "config"}, + {DefaultArg::Async, "async"}, + {DefaultArg::Autonomous, "autonomous"}, + {DefaultArg::Duration, "sim-step-duration"}, + {DefaultArg::AsFastAsPossible, "fast"}}; + Arguments _arguments; + + // SIL Kit API + std::unique_ptr _participant; + ILifecycleService* _lifecycleService{nullptr}; + ITimeSyncService* _timeSyncService{nullptr}; + ISystemMonitor* _systemMonitor{nullptr}; + + // For sync: wait for sil-kit-system-controller start/abort or manual user abort + enum struct SystemControllerResult + { + Unknown, + CoordinatedStart, + SystemControllerAbort, + UserAbort + }; + std::atomic _hasSystemControllerResult{false}; + std::promise _waitForSystemControllerPromise; + std::future _waitForSystemControllerFuture; + std::atomic _systemControllerResult{SystemControllerResult::Unknown}; + + // For async: Couple worker thread to SIL Kit lifecycle + enum struct UnleashWorkerThreadResult + { + Unknown, + ParticipantStarting, + UserAbort + }; + std::promise _startWorkPromise; + std::future _startWorkFuture; + + std::promise _stopWorkPromise; + std::future _stopWorkFuture; + std::atomic _gracefulStop{false}; + + std::thread _workerThread; + + // Wait for the SIL Kit lifecycle to end + std::future _participantStateFuture; + ParticipantState _finalParticipantState{ParticipantState::Invalid}; + +private: + void AddDefaultArgs(std::unordered_set excludedCommandLineArgs = {}) + { + _commandLineParser->Add("help", "h", "-h, --help", + std::vector{"Get this help."}); + + Arguments defaultArgs{}; + + if (!excludedCommandLineArgs.count(DefaultArg::Name)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Name), "n", defaultArgs.participantName, + "-n, --" + defaultArgName.at(DefaultArg::Name) + " ", + std::vector{"The participant name used to take part in the simulation.", + "Defaults to '" + _arguments.participantName + "'."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Uri)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Uri), "u", defaultArgs.registryUri, + "-u, --" + defaultArgName.at(DefaultArg::Uri) + " ", + std::vector{"The registry URI to connect to.", + "Defaults to '" + _arguments.registryUri + "'."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Log)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Log), "l", "info", + "-l, --" + defaultArgName.at(DefaultArg::Log) + " ", + std::vector{ + "Log to stdout with level:", + "'trace', 'debug', 'warn', 'info', 'error', 'critical' or 'off'.", + "Defaults to 'info'.", + "Cannot be used together with '--" + defaultArgName.at(DefaultArg::Config) + "'."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Config)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Config), "c", "", + "-c, --" + defaultArgName.at(DefaultArg::Config) + " ", + std::vector{"Path to the Participant configuration YAML or JSON file.", + "Cannot be used together with '--" + defaultArgName.at(DefaultArg::Log) + "'.", + "Will always run as fast as possible."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Async)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Async), "a", "-a, --" + defaultArgName.at(DefaultArg::Async), + std::vector{ + "Run without time synchronization mode.", + "Cannot be used together with '--" + defaultArgName.at(DefaultArg::Duration) + "'."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Autonomous)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Autonomous), "A", "-A, --" + defaultArgName.at(DefaultArg::Autonomous), + std::vector{"Start the simulation autonomously.", + "Without this flag, a coordinated start is performed", + "which requires the SIL Kit System Controller." + }); + } + + + if (!excludedCommandLineArgs.count(DefaultArg::Duration)) + { + auto defaultValue = std::to_string(defaultArgs.duration.count() / 1000000); + _commandLineParser->Add( + defaultArgName.at(DefaultArg::Duration), "d", defaultValue, + "-d, --" + defaultArgName.at(DefaultArg::Duration) + " ", + std::vector{"The duration of a simulation step in microseconds.", + "Defaults to " + defaultValue + + "us.", + "Cannot be used together with '--" + defaultArgName.at(DefaultArg::Async) + "'."}); + } + + if (!excludedCommandLineArgs.count(DefaultArg::AsFastAsPossible)) + { + _commandLineParser->Add( + defaultArgName.at(DefaultArg::AsFastAsPossible), "f", + "-f, --" + defaultArgName.at(DefaultArg::AsFastAsPossible), + std::vector{ + "Run the simulation as fast as possible.", + "By default, the execution is slowed down to two work cycles per second.", + "Cannot be used together with '--" + defaultArgName.at(DefaultArg::Config) + "'."}); + } + } + + auto ToLowerCase(std::string s) -> std::string + { + std::for_each(s.begin(), s.end(), [](char& c) { c = static_cast(std::tolower(c)); }); + return s; + } + auto IsValidLogLevel(const std::string& levelStr) -> bool + { + auto logLevel = ToLowerCase(levelStr); + return logLevel == "trace" || logLevel == "debug" || logLevel == "warn" || logLevel == "info" + || logLevel == "error" || logLevel == "critical" || logLevel == "off"; + } + + void ParseArguments(int argc, char** argv) + { + try + { + _commandLineParser->ParseArguments(argc, argv); + } + catch (const std::exception& e) + { + std::cerr << "Error parsing command line arguments: " << e.what() << std::endl; + _commandLineParser->PrintUsageInfo(std::cerr); + exit(-1); + } + } + + void EvaluateDefaultArgs(std::unordered_set excludedCommandLineArgs) + { + if (_commandLineParser->Get("help").Value()) + { + _commandLineParser->PrintUsageInfo(std::cout); + exit(0); + } + + if (!excludedCommandLineArgs.count(DefaultArg::Name)) + { + if (_commandLineParser->Get(defaultArgName.at(DefaultArg::Name)).HasValue()) + { + _arguments.participantName = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Name)).Value(); + } + } + + if (!excludedCommandLineArgs.count(DefaultArg::Uri)) + { + if (_commandLineParser->Get(defaultArgName.at(DefaultArg::Uri)).HasValue()) + { + _arguments.registryUri = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Uri)).Value(); + } + } + + bool hasAsyncFlag = false; + if (!excludedCommandLineArgs.count(DefaultArg::Async)) + { + _arguments.runAsync = hasAsyncFlag = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Async)).Value(); + } + + + bool hasAutonomousFlag = false; + if (!excludedCommandLineArgs.count(DefaultArg::Autonomous)) + { + _arguments.runAutonomous = hasAutonomousFlag = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Autonomous)).Value(); + } + + bool hasDurationOption = false; + if (!excludedCommandLineArgs.count(DefaultArg::Duration)) + { + hasDurationOption = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Duration)).HasValue(); + if (hasDurationOption) + { + int durationUs = std::stoi( + _commandLineParser->Get(defaultArgName.at(DefaultArg::Duration)) + .Value()); + _arguments.duration = std::chrono::microseconds(durationUs); + } + } + + if (hasAsyncFlag && hasDurationOption) + { + std::cerr << "Error: Options '--" << defaultArgName.at(DefaultArg::Async) << "' and '--" + << defaultArgName.at(DefaultArg::Duration) << "' cannot be used simultaneously" << std::endl; + _commandLineParser->PrintUsageInfo(std::cerr); + exit(-1); + } + + bool hasAsFastAsPossibleFlag = false; + if (!excludedCommandLineArgs.count(DefaultArg::AsFastAsPossible)) + { + _arguments.asFastAsPossible = hasAsFastAsPossibleFlag = + _commandLineParser->Get(defaultArgName.at(DefaultArg::AsFastAsPossible)) + .Value(); + } + + bool hasLogOption = false; + if (!excludedCommandLineArgs.count(DefaultArg::Log)) + { + hasLogOption = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Log)).HasValue(); + } + + bool hasCfgOption = false; + if (!excludedCommandLineArgs.count(DefaultArg::Config)) + { + hasCfgOption = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Config)).HasValue(); + } + + if (hasLogOption && hasCfgOption) + { + std::cerr << "Error: Options '--" << defaultArgName.at(DefaultArg::Log) << "' and '--" + << defaultArgName.at(DefaultArg::Config) << "' cannot be used simultaneously" << std::endl; + _commandLineParser->PrintUsageInfo(std::cerr); + exit(-1); + } + + if (hasAsFastAsPossibleFlag && hasCfgOption) + { + std::cerr << "Error: Options '--" << defaultArgName.at(DefaultArg::AsFastAsPossible) << "' and '--" + << defaultArgName.at(DefaultArg::Config) << "' cannot be used simultaneously" << std::endl; + _commandLineParser->PrintUsageInfo(std::cerr); + exit(-1); + } + + if (hasCfgOption) + { + // --config always runs as fast as possible + _arguments.asFastAsPossible = true; + + const auto configFileName = + _commandLineParser->Get(defaultArgName.at(DefaultArg::Config)).Value(); + try + { + _participantConfiguration = SilKit::Config::ParticipantConfigurationFromFile(configFileName); + } + catch (const SilKit::ConfigurationError& error) + { + std::cerr << "Failed to load configuration '" << configFileName << "':" << std::endl + << error.what() << std::endl; + exit(-1); + } + } + else + { + std::string configLogLevel{"Off"}; + if (!excludedCommandLineArgs.count(DefaultArg::Log)) + { + const auto logLevel{ + _commandLineParser->Get(defaultArgName.at(DefaultArg::Log)).Value()}; + if (!IsValidLogLevel(logLevel)) + { + std::cerr << "Error: Argument of the '--" << defaultArgName.at(DefaultArg::Log) + << "' option must be one of 'trace', 'debug', 'warn', 'info', " + "'error', 'critical', or 'off'" + << std::endl; + exit(-1); + } + configLogLevel = logLevel; + } + + configLogLevel[0] = static_cast(std::toupper(configLogLevel[0])); + + std::ostringstream ss; + ss << "{"; + ss << R"("Logging":{"Sinks":[{"Type":"Stdout","Level":")" << configLogLevel << R"("}]})"; + + if (!_arguments.runAsync && !_arguments.asFastAsPossible) + { + // For async: sleep 0.5s per cycle + // For sync: set the animation factor to 0.5/duration(s) here, resulting in two simulation step per second + double animationFactor = + (_arguments.duration.count() > 0) ? 0.5 / (1e-9 * _arguments.duration.count()) : 1.0; + ss << R"(,"Experimental":{"TimeSynchronization":{"AnimationFactor":)" << animationFactor << R"(}})"; + } + ss << "}"; + + const auto configString = ss.str(); + try + { + _participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + } + catch (const SilKit::ConfigurationError& error) + { + std::cerr << "Failed to set configuration from string '" << configString << "', " << error.what() + << std::endl; + exit(-1); + } + } + } + + void SetupSignalHandler() + { + RegisterSignalHandler([&](auto signalValue) { + { + std::ostringstream ss; + ss << "Signal " << signalValue << " received, attempting to stop simulation..."; + _participant->GetLogger()->Info(ss.str()); + + Stop(); + } + }); + } + + void WorkerThread() + { + auto result = _startWorkFuture.get(); + if (result == UnleashWorkerThreadResult::UserAbort) + { + return; + } + + while (true) + { + DoWorkAsync(); + + auto wait = _arguments.asFastAsPossible ? 0ms : 500ms; + auto futureStatus = _stopWorkFuture.wait_for(wait); + if (futureStatus == std::future_status::ready) + { + break; + } + } + } + + void SetupParticipant() + { + _participant = + SilKit::CreateParticipant(_participantConfiguration, _arguments.participantName, _arguments.registryUri); + _systemMonitor = _participant->CreateSystemMonitor(); + } + + void SetupLifecycle() + { + auto operationMode = (_arguments.runAutonomous ? OperationMode::Autonomous : OperationMode::Coordinated); + _lifecycleService = _participant->CreateLifecycleService({operationMode}); + + // Handle simulation abort by sil-kit-system-controller + _waitForSystemControllerFuture = _waitForSystemControllerPromise.get_future(); + _lifecycleService->SetAbortHandler([this](ParticipantState /*lastState*/) { + if (!_hasSystemControllerResult) + { + _hasSystemControllerResult = true; + _waitForSystemControllerPromise.set_value(SystemControllerResult::SystemControllerAbort); + } + }); + + // Called during startup + _lifecycleService->SetCommunicationReadyHandler([this]() { + if (!_arguments.runAutonomous) + { + // Handle valid simulation start by sil-kit-system-controller + if (!_hasSystemControllerResult) + { + _hasSystemControllerResult = true; + _waitForSystemControllerPromise.set_value(SystemControllerResult::CoordinatedStart); + } + } + // App should initialize it's controllers here + InitControllers(); + }); + } + + void SetupAsync() + { + // No time sync: Work by the app is done in the worker thread + + // Future / promise to control entrance / exit of the main loop in the worker thread + _startWorkFuture = _startWorkPromise.get_future(); + _stopWorkFuture = _stopWorkPromise.get_future(); + + // The worker thread of participants without time synchronization is 'unleashed' in the starting handler... + _lifecycleService->SetStartingHandler( + [this]() { _startWorkPromise.set_value(UnleashWorkerThreadResult::ParticipantStarting); }); + + // ... and stopped in the stop handler. + _lifecycleService->SetStopHandler([this]() { + _gracefulStop = true; + _stopWorkPromise.set_value(); + }); + + _lifecycleService->SetShutdownHandler([this]() { + if (!_gracefulStop) + { + // If the simulation was aborted, the stop handler has been skipped and the promise must be set here. + _stopWorkPromise.set_value(); + } + }); + + // Start the worker thread + _workerThread = std::thread{&ApplicationBase::WorkerThread, this}; + } + + void SetupSync() + { + // With time sync: Work by the app is done in the SimulationStepHandler + _timeSyncService = _lifecycleService->CreateTimeSyncService(); + _timeSyncService->SetSimulationStepHandler( + [this](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { + std::stringstream ss; + ss << "--------- Simulation step T=" << now << " ---------"; + _participant->GetLogger()->Info(ss.str()); + + DoWorkSync(now); + }, _arguments.duration); + } + + void Launch() + { + _participantStateFuture = _lifecycleService->StartLifecycle(); + } + + void Stop() + { + auto state = _lifecycleService->State(); + if (state == ParticipantState::Running || state == ParticipantState::Paused) + { + _lifecycleService->Stop("User requested to stop"); + } + else + { + if (!_arguments.runAutonomous && !_hasSystemControllerResult) + { + _waitForSystemControllerPromise.set_value(SystemControllerResult::UserAbort); + } + } + } + + void WaitUntilDone() + { + if (!_arguments.runAutonomous) + { + _participant->GetLogger()->Info("Waiting for the system controller to start the simulation"); + // Allow the application to exit by itself in case the user aborted while waiting for the sil-kit-system-controller + _systemControllerResult = _waitForSystemControllerFuture.get(); + if (_systemControllerResult == SystemControllerResult::UserAbort) + { + // Premature user abort, don't wait for _participantStateFuture + _participant->GetLogger()->Info("Terminated while waiting for coordinated start"); + + if (_arguments.runAsync) + { + _startWorkPromise.set_value(UnleashWorkerThreadResult::UserAbort); + + if (_workerThread.joinable()) + { + _workerThread.join(); + } + } + return; + } + else if (_systemControllerResult == SystemControllerResult::SystemControllerAbort) + { + _participant->GetLogger()->Info("System Controller aborted the simulation"); + // No return here as a System Controller abort leads to ParticipantState::Shutdown + // and the _participantStateFuture.get() will return a result. + } + } + + _finalParticipantState = _participantStateFuture.get(); + + if (_arguments.runAsync) + { + if (_workerThread.joinable()) + { + _workerThread.join(); + } + } + } + + // Only use inside class +protected: + IParticipant* GetParticipant() const + { + return _participant.get(); + } + + ILifecycleService* GetLifecycleService() const + { + return _lifecycleService; + } + + ITimeSyncService* GetTimeSyncService() const + { + return _timeSyncService; + } + + ISystemMonitor* GetSystemMonitor() const + { + return _systemMonitor; + } + + ILogger* GetLogger() const + { + return _participant->GetLogger(); + } + + // Also accessible in app / main +public: + const Arguments& GetArguments() const + { + return _arguments; + } + + const auto& GetCommandLineParser() + { + return _commandLineParser; + } + + // Setup of default and application command line arguments + void SetupCommandLineArgs(int argc, char** argv, const std::string& appDescription, + std::unordered_set excludedCommandLineArgs = {}) + { + _commandLineParser->SetDescription(appDescription); + AddDefaultArgs(excludedCommandLineArgs); + try + { + AddCommandLineArgs(); + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + exit(-1); + } + ParseArguments(argc, argv); + EvaluateDefaultArgs(excludedCommandLineArgs); + try + { + EvaluateCommandLineArgs(); + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + _commandLineParser->PrintUsageInfo(std::cerr); + exit(-1); + } + } + + // Run the SIL Kit workflow + int Run() + { + try + { + // Create participant + SetupParticipant(); + + // Stop() on Ctrl-C + SetupSignalHandler(); + + // app: Controller creation + CreateControllers(); + + // React on valid start / simulation abort + // app: Controller initialization in CommunicationReadyHandler + SetupLifecycle(); + + if (_arguments.runAsync) + { + // Create thread and couple to starting handler + // app: DoWorkAsync + SetupAsync(); + } + else + { + // Create timeSyncService and simulationStepHandler + // app: DoWorkSync + SetupSync(); + } + + // Start lifecycle + Launch(); + + // Wait for lifecycle to end + // async: Join worker thread + WaitUntilDone(); + + return 0; + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -1; + } + } +}; + diff --git a/Demos/communication/include/CommandlineParser.hpp b/Demos/communication/include/CommandlineParser.hpp new file mode 100644 index 000000000..4bafc770f --- /dev/null +++ b/Demos/communication/include/CommandlineParser.hpp @@ -0,0 +1,471 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SilKit { +namespace Util { + +//! \brief Parse commandline arguments +class CommandlineParser +{ +public: + CommandlineParser() = default; + + //! \brief Set the application description + void SetDescription(const std::string& description) + { + _appDescription = description; + } + + //! \brief Declare a new argument + template + auto& Add(Args&&... args) + { + auto argument = std::make_unique(std::forward(args)...); + if (NameExists(argument->Name())) + { + throw std::runtime_error("Cannot add argument '" + argument->Name() + "'. Name already exists."); + } + auto shortNameCollision = ShortNameExists(argument->ShortName()); + if (shortNameCollision.first) + { + throw std::runtime_error("Cannot add argument '" + argument->Name() + "'. Short name '" + + argument->ShortName() + "' already exists for argument '" + + shortNameCollision.second + "'"); + } + + if (argument->Usage().size() > _longestUsage) + { + _longestUsage = argument->Usage().size(); + } + _arguments.push_back(std::move(argument)); + return *this; + } + + /*! \brief Retrieve a command line argument by its name + * \throw SilKit::std::runtime_error when argument does not exist or is of a different kind + */ + template + auto Get(std::string name) -> TArgument& + { + auto* argument = GetByName(name); + if (!argument) + { + throw std::runtime_error("Unknown argument '" + name + "'"); + } + return *argument; + } + + /*! \brief Output usage info for previously declared parameters to the given stream + */ + void PrintUsageInfo(std::ostream& out) + { + out << std::endl; + out << _appDescription << std::endl; + out << std::endl; + + + out << "Arguments:" << std::endl; + for (auto& argument : _arguments) + { + if (argument->IsHidden()) + continue; + + if (argument->DescriptionLines().size() > 0) + { + out << std::setw(_longestUsage + 1) << std::left << argument->Usage() << "| " + << argument->DescriptionLines()[0] << std::endl; + } + bool skipFirst = true; + for (auto& l : argument->DescriptionLines()) + { + if (skipFirst) + { + skipFirst = false; + continue; + } + std::cout << std::setw(_longestUsage + 3) << " " << l << std::endl; + } + } + out << std::endl; + } + + /*! \brief Parse arguments based on argc/argv parameters from a main function + * \throw SilKit::std::runtime_error when a parsing error occurs + */ + void ParseArguments(int argc, char** argv) + { + auto positionalArgumentIt = std::find_if(_arguments.begin(), _arguments.end(), [](const auto& el) { + return el->Kind() == ArgumentKind::Positional || el->Kind() == ArgumentKind::PositionalList; + }); + for (auto i = 1; i < argc; ++i) + { + std::string argument{argv[i]}; + + auto arg{argument}; + auto isShortForm{false}; + if (arg.length() >= 3 && arg.substr(0, 2) == "--") + { + arg.erase(0, 2); + } + else if (arg.length() >= 2 && arg.substr(0, 1) == "-") + { + arg.erase(0, 1); + isShortForm = true; + } + else if (positionalArgumentIt != _arguments.end()) + { + if ((*positionalArgumentIt)->Kind() == ArgumentKind::Positional) + { + auto* positionalArgument = static_cast(positionalArgumentIt->get()); + positionalArgument->_value = std::move(arg); + positionalArgumentIt = std::find_if(++positionalArgumentIt, _arguments.end(), [](const auto& el) { + return el->Kind() == ArgumentKind::Positional || el->Kind() == ArgumentKind::PositionalList; + }); + } + else + { + auto* positionalArgument = static_cast(positionalArgumentIt->get()); + positionalArgument->_values.push_back(std::move(arg)); + } + + continue; + } + else + { + throw std::runtime_error("Bad argument '" + argument + "'"); + } + + auto splitPos = std::find(arg.begin(), arg.end(), '='); + if (splitPos != arg.end()) + { + std::string name = {arg.begin(), splitPos}; + std::string value = {splitPos + 1, arg.end()}; + + auto* option = isShortForm ? GetByShortName