diff --git a/score/time_daemon/docs/detailed_design/index.rst b/score/time_daemon/docs/detailed_design/index.rst index 21292f0d..3c8d6762 100644 --- a/score/time_daemon/docs/detailed_design/index.rst +++ b/score/time_daemon/docs/detailed_design/index.rst @@ -126,688 +126,377 @@ The data and control flow between units is presented in the following diagram: -On this view you could see several "workers" scopes: +On this view you can see several execution scopes: -1. PTP retrieving scope -2. PTPTimeInfo handling scope -3. PTPTimeInfo receiving on Application side scope +1. **PTP retrieving scope** — retrieves latest PTP data and publishes to ``raw_ptp_data`` topic +2. **PTPTimeInfo handling scope** — validates time data and publishes to ``verified_ptp_data`` topic +3. **PTPTimeInfo receiving scope** — propagates qualified time to client applications -Each control flow is implemented with the dedicated thread or process and is independent form another ones. +Each control flow is implemented with a dedicated thread or process and is independent from the others. -Control flows -^^^^^^^^^^^^^ - -PTP retrieving scope -'''''''''''''''''''' - -This control flow is responsible for the: - -1. retrieve the latest information from the ptp stack and -2. provide it to the ``PTPTimeInfo handling`` control flow - -PTPTimeInfo handling scope -''''''''''''''''''''''''''' +Data Types or Events +^^^^^^^^^^^^^^^^^^^^^ -This control flow is responsible for the: +Main data exchanged between units via MessageBroker topics: -1. Validate the time information, provided by the ``PTP retrieving`` workflow and -2. publish it to the ``Applications`` via some IPC +- ``PtpTimeInfo`` — internal time snapshot struct containing sync data, peer delay, status flags, and timestamps -PTPTimeInfo receiving on Application side scope -'''''''''''''''''''''''''''''''''''''''''''''''' - -This control flow is responsible for the: - -1. Propagate the time information from the ``PTPTimeInfo handling`` to the business logic of the applications. - -Data types or events -^^^^^^^^^^^^^^^^^^^^ - -There are also several data types, which components are communicating to each other: +Topic names: .. _raw_ptp_data: -Raw ptp data -'''''''''''' +.. rubric:: raw_ptp_data -``raw_ptp_data`` is the data, which is provided by ``PTPMachine`` component and is just the raw data from ptp stack. is handled in the "PTP retrieving scope" +Raw PTP snapshot from ``PtpMachine``; published to ``ControlFlowDivider`` for thread separation. .. _input_ptp_data: -Input ptp data -'''''''''''''' +.. rubric:: input_ptp_data -``input_ptp_data`` is the same data as :ref:`raw_ptp_data` but which is handled already in "PTPTimeInfo handling scope" +Same data as :ref:`raw_ptp_data` but republished at consistent rate by ``ControlFlowDivider``; consumed by ``VerificationMachine``. .. _verified_ptp_data: -Verified ptp data -''''''''''''''''' +.. rubric:: verified_ptp_data -``verified_ptp_data`` is the :ref:`input_ptp_data` which was verified according to the business logic and updated accordingly. This data should be published to the Applications. +Validated and qualified time snapshot from ``VerificationMachine``; includes sync status, timeout flags, time jump detection; published to ``PublisherImpl``. -Units within Time Daemon -------------------------- +Units within the Component +--------------------------- The following units comprise TimeDaemon's internal implementation: -Application Unit -~~~~~~~~~~~~~~~~ +- ``TimeDaemon`` — main entry point orchestrating lifecycle and initialization +- ``MessageBroker`` — publish-subscribe hub for decoupled inter-unit communication +- ``ControlFlowDivider`` — separates execution threads to prevent blocking +- ``PtpMachine`` — retrieves raw time data from PTP stack +- ``ShmPtpEngine`` — reads gPTP data from TimeSlave shared memory channel +- ``VerificationMachine`` — validates and qualifies time data +- ``PublisherImpl`` — publishes qualified time snapshots via shared memory +- ``ReceiverImpl`` — receives time data from shared memory -The ``Application`` component is the main entry point for the ``TimeDaemon``. It is responsible for orchestrating the overall lifecycle and initialization of all daemon components. +Per-Unit Diagrams +~~~~~~~~~~~~~~~~~ -The ``TimebaseHandler`` component is an timebase-specific logic implementation. There might be several handlers available in the ``Application`` per amount of timebases supported. This separation allows for different timebase implementations while maintaining a consistent application structure. +Application +^^^^^^^^^^^ -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The ``Application`` component is the main entry point for TimeDaemon, orchestrating lifecycle and initialization of all daemon components. -The ``Application`` has the following requirements: +The ``TimebaseHandler`` component implements timebase-specific logic. Multiple handlers may exist per supported timebase count, allowing different timebase implementations while maintaining consistent application structure. -- The ``Application`` shall implement the ``Initialize()`` method to create and initialize all daemon components -- The ``Application`` shall implement the ``Run()`` method to start all components and wait for termination -- The ``Application`` shall connect components to the ``MessageBroker`` by setting up all required subscriptions during initialization stage -- The ``Application`` shall support extension for different timebases. +During initialization, the ``Application`` uses a factory pattern to create components in specific order: -Class view -^^^^^^^^^^ +- Create ``MessageBroker`` first (other components depend on it) +- Create ProactiveMachines (``PtpMachine``, ``ControlFlowDivider``) that drive system behavior, then initialize and wire subscriptions +- Create ReactiveMachines (``VerificationMachine``, ``IPCMachine``) that respond to events, then initialize and wire subscriptions -The Class Diagram is presented below: +During execution, ProactiveMachines start in correct order. On termination, they stop in reverse order. + +Class diagram: .. raw:: html
.. uml:: _assets/app/app_class.puml - :alt: Class Diagram + :alt: Application class diagram .. raw:: html
-Initialization flow -^^^^^^^^^^^^^^^^^^^ - -During initialization, the ``Application`` uses the ``MachineFactory`` to create, configure and subscribe all components in a specific order: - -- Create the ``MessageBroker`` first, as other components depend on it -- Create ProactiveMachines (``PtpMachine``, ``ControlFlowDivider``) that drive system behavior - - - Initialize each component - - Set up MessageBroker subscriptions to component notifications - - Set up component subscriptions to MessageBroker topics - -- Create ReactiveMachines (``VerificationMachine``, ``IPCMachine``) that respond to events - - - Initialize each component - - Set up MessageBroker subscriptions to component notifications - - Set up component subscriptions to MessageBroker topics - -The initialization workflow is represented in the following sequence diagram: +Initialization sequence showing component creation order and MessageBroker subscription wiring: .. raw:: html
.. uml:: _assets/app/app_init_seq.puml - :alt: Initialization workflow + :alt: Application initialization sequence .. raw:: html
-Execution and shutdown flow -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -During execution, the ``Application``: - -- Starts all ``ProactiveMachines`` in the correct order -- Monitors the stop token for termination requests -- When termination is requested, stops all ``ProactiveMachines`` in reverse order - -The execution and shutdown workflow is represented in the following sequence diagram: +Runtime workflow showing ProactiveMachine startup and shutdown coordination: .. raw:: html
.. uml:: _assets/app/app_workflow_seq.puml - :alt: Execution workflow + :alt: Application workflow sequence .. raw:: html
+MessageBroker +^^^^^^^^^^^^^ - -Message Broker Unit -~~~~~~~~~~~~~~~~~~~ - -The ``Message Broker`` component is the central communication hub that implements the Publish-Subscribe pattern within the ``TimeDaemon``. It enables decoupled communication between components by managing topics and distributing messages to interested subscribers. - -The component maintains a registry of topics and their subscribers, delivering messages to all registered subscribers when a component publishes to a topic. This decoupling allows components to evolve independently without direct dependencies on each other. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Message Broker`` has the following requirements: - -- The ``Message Broker`` shall maintain a registry of topics and their subscribers -- The ``Message Broker`` shall allow components to subscribe to topics of interest -- The ``Message Broker`` shall distribute messages to all subscribers when a topic is published to - -Class view -^^^^^^^^^^ - -The Class Diagram is presented below: +Class diagram: .. raw:: html
.. uml:: _assets/msg_broker/msg_broker_class.puml - :alt: Class Diagram + :alt: MessageBroker class diagram .. raw:: html
-Initialization flow -^^^^^^^^^^^^^^^^^^^ - -During initialization, all machine objects, see ``BaseMachine``, the ``Application`` component needs to subscribe machines to ``Message Broker`` to the topics of interest. - -The initialization workflow is represented in the following sequence diagram: +Initialization workflow: .. raw:: html
.. uml:: _assets/msg_broker/msg_broker_init_seq.puml - :alt: Initialization workflow + :alt: MessageBroker initialization .. raw:: html
-Message flow -^^^^^^^^^^^^ - -The message flow through the ``Message Broker`` is represented in the following sequence diagram: +Message flow through MessageBroker showing topic distribution to multiple subscribers: .. raw:: html
.. uml:: _assets/msg_broker/msg_broker_workflow_seq.puml - :alt: Message DiagramFlow + :alt: MessageBroker message distribution .. raw:: html
-Concurrency aspects -^^^^^^^^^^^^^^^^^^^ - -The ``Message Broker`` doesn't provide any synchronization between the publish-callback invoking processes. -Moreover, the callback invoke will happened in the scope of the thread, where the ``publish`` method is called. -To separate the control flows, the ``ControlFlowDivider`` shall be used. - -Scalability -^^^^^^^^^^^ - -The ``Message Broker`` can be extended to support configuration-driven subscriptions, where topic relationships are defined in configuration files rather than hardcoded. +ControlFlowDivider +^^^^^^^^^^^^^^^^^^ - - -ControlFlowDivider Unit -~~~~~~~~~~~~~~~~~~~~~~~ - -The ``ControlFlowDivider`` component is responsible for separating control (execution) flows within the ``TimeDaemon`` and providing the execution control flow for the data processing. It contains dedicated threads where data is published to the ``Message Broker``, ensuring that blocking operations in one component do not affect the execution of other components and data missing is not affecting the data analysis in processing pipeline. - -This component acts as a crucial intermediary that maintains the responsiveness of the system by decoupling the execution contexts of different operations, particularly between the PTP data retrieval and the time data processing pipelines. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``ControlFlowDivider`` has the following requirements: - -- The ``ControlFlowDivider`` shall provide separate execution threads for different control flows -- The ``ControlFlowDivider`` shall isolate components from execution time variations in other components -- The ``ControlFlowDivider`` shall maintain consistent data publishing rates to the subscribers -- The ``ControlFlowDivider`` shall push the last received data to the subscribers if there is no new data for some time with the predefined rate, to avoid data missing in the processing pipeline -- The ``ControlFlowDivider`` shall enable periodic processing of the pipeline through consistent event generation -- The ``ControlFlowDivider`` shall buffer incoming data from fast producers - -Class view -^^^^^^^^^^ - -The Class Diagram is presented below: +Class diagram: .. raw:: html
.. uml:: _assets/ctrlflow/ctrlflow_class.puml - :alt: Class Diagram + :alt: ControlFlowDivider class diagram .. raw:: html
-Initialization flow -^^^^^^^^^^^^^^^^^^^ - -During initialization, the ``ControlFlowDivider`` performs the following steps: - -- Initialize internal data structures (queue, mutex, condition variable) -- Create a worker thread to process data independently -- Start the worker thread which enters a waiting state - -The initialization workflow is represented in the following sequence diagram: +Initialization workflow: .. raw:: html
.. uml:: _assets/ctrlflow/ctrlflow_init_seq.puml - :alt: Initialization workflow + :alt: ControlFlowDivider initialization .. raw:: html
-Message flow -^^^^^^^^^^^^ - -When the ``ControlFlowDivider`` receives new data from the ``PTP Machine`` via the ``Message Broker``, it processes it through the following workflow: - -1. The ``Message Broker`` executes the onNewData callback and provides the new data -2. The data is placed in a thread-safe queue and exists from the callback -3. The worker thread wakes up, retrieves the data from the queue and -4. The worker thread publishes the retrieved data to the :ref:`input_ptp_data` topic -5. if there was no data for some timeout, the worker shall published the empty data to the :ref:`input_ptp_data` topic. - -This separation of control flows ensures that slow or blocking operations in the PTP stack communication do not affect the responsiveness of time data processing in the ``TimeDaemon``. - -The execution workflow is represented in the following sequence diagram: +Thread separation and periodic republishing workflow: .. raw:: html
.. uml:: _assets/ctrlflow/ctrlflow_workflow_seq.puml - :alt: Execution workflow + :alt: ControlFlowDivider workflow .. raw:: html
- - -PTP Machine Unit -~~~~~~~~~~~~~~~~ - -The ``PTP Machine`` component shall retrieve all needed information from the ptp stack (ex ``ptpd``) and provide it to the ``Message Broker`` for routing. -All communication with the ptp stack ight use ``devctl`` calls, which take some time, thus these calls shall be done in the dedicated thread. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``PTP Machine`` has the following requirements: - -- The ``PTP Machine`` shall retrieve the latest time information from the PTP stack (e.g., ``ptpd``) -- The ``PTP Machine`` shall publish retrieved time information to the ``Message Broker`` using the defined topic -- The ``PTP Machine`` shall format data according to the ``PtpTimeInfo`` structure required by downstream components -- The ``PTP Machine`` shall retrieve time information at a consistent rate to maintain time synchronization -- The ``PTP Machine`` shall maintain consistent publishing rates for time data even when experiencing delays in PTP stack communication. -- The ``PTP Machine`` shall support exchangeability with different PTP stack implementations - -Class view +PTPMachine ^^^^^^^^^^ -The Class Diagram is presented below. +Class diagram: .. raw:: html
.. uml:: _assets/ptp_machine/ptp_machine_class.puml - :alt: Class Diagram + :alt: PTPMachine class diagram .. raw:: html
-As long as it wraps the particular communication with the ptp stack, the implementations should be easily exchangeable with another one in case of stack change. - -Component initialization -^^^^^^^^^^^^^^^^^^^^^^^^ - -During initialization the ``PTP Machine`` shall initialize the ptp stack to be able to communicate with it. - -The initialization workflow is described below. +Initialization workflow: .. raw:: html
.. uml:: _assets/ptp_machine/ptp_machine_init_seq.puml - :alt: Initialization workflow + :alt: PTPMachine initialization .. raw:: html
-Publish new data -^^^^^^^^^^^^^^^^ - -After ``PTP Machine`` collects new data from the ptp stack, the component shall publish it to the ``Message Broker`` as :ref:`raw_ptp_data`. - -The publish workflow is described below. +PTP data retrieval cycle from shared memory: .. raw:: html
.. uml:: _assets/ptp_machine/ptp_machine_get_new_data_seq.puml - :alt: Publish workflow + :alt: PTPMachine data retrieval .. raw:: html
-ShmPTPEngine Unit -~~~~~~~~~~~~~~~~~ - -The ``ShmPTPEngine`` component (in ``score::td::details``) is a ``PTP Machine`` implementation that reads ``GptpIpcData`` from the shared memory channel written by TimeSlave and converts it into the ``PtpTimeInfo`` structure expected by the TimeDaemon pipeline. - -It is instantiated as ``GPTPShmMachine`` — a type alias for ``PTPMachine`` — which connects ``ShmPTPEngine`` to the TimeDaemon's internal ``MessageBroker``. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``ShmPTPEngine`` has the following requirements: - -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Init(ipc_name)`` during ``Initialize()`` to open the shared memory channel -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Receive()`` in ``ReadPTPSnapshot()`` to fetch the latest ``GptpIpcData`` -- The ``ShmPTPEngine`` shall map all fields of ``GptpIpcData`` to the corresponding fields of ``PtpTimeInfo`` (status flags, Sync/FollowUp data, peer-delay data, time references) -- The ``ShmPTPEngine`` shall call ``GptpIpcReceiver::Close()`` during ``Deinitialize()`` -- The ``ShmPTPEngine`` shall be instantiatable with a configurable IPC channel name (default: ``/gptp_ptp_info``) - -Class View -^^^^^^^^^^ +ShmPTPEngine +^^^^^^^^^^^^ -The Class Diagram is presented below: +Class diagram: .. raw:: html
.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_class.puml - :alt: Class Diagram + :alt: ShmPTPEngine class diagram .. raw:: html
-Initialization -^^^^^^^^^^^^^^ - -During initialization the ``ShmPTPEngine`` shall open the shared memory channel to be able to read from it. - -The initialization workflow is represented in the following sequence diagram: +Initialization workflow: .. raw:: html
.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_init_seq.puml - :alt: Initialization workflow + :alt: ShmPTPEngine initialization .. raw:: html
-Read PTP Snapshot -^^^^^^^^^^^^^^^^^ - -After ``ShmPTPEngine`` reads the latest ``GptpIpcData`` from shared memory, it maps it to ``PtpTimeInfo`` and publishes via the ``MessageBroker``. - -The periodic read and publish workflow is described below: +Shared memory read from TimeSlave gPTP publisher: .. raw:: html
.. uml:: _assets/shm_ptp_engine/shm_ptp_engine_read_seq.puml - :alt: Periodic read and publish workflow + :alt: ShmPTPEngine read sequence .. raw:: html
-Data Mapping -^^^^^^^^^^^^ - -``ShmPTPEngine::ReadPTPSnapshot()`` performs a field-by-field mapping from ``GptpIpcData`` to ``PtpTimeInfo``: - -.. list-table:: GptpIpcData → PtpTimeInfo Mapping - :header-rows: 1 - :widths: 50 50 - - * - ``GptpIpcData`` field - - ``PtpTimeInfo`` field - * - ``ptp_assumed_time`` - - ``ptp_assumed_time`` - * - ``local_time`` - - ``local_time`` (wrapped in ``ReferenceClock::time_point``) - * - ``rate_deviation`` - - ``rate_deviation`` - * - ``status.is_synchronized`` - - ``status.is_synchronized`` - * - ``status.is_timeout`` - - ``status.is_timeout`` - * - ``status.is_time_jump_future`` - - ``status.is_time_jump_future`` - * - ``status.is_time_jump_past`` - - ``status.is_time_jump_past`` - * - ``status.is_correct`` - - ``status.is_correct`` - * - ``sync_fup_data.*`` (9 fields) - - ``sync_fup_data.*`` (direct copy) - * - ``pdelay_data.*`` (12 fields) - - ``pdelay_data.*`` (direct copy) - -Factory -^^^^^^^ - -``CreateGPTPShmMachine(name, ipc_name)`` is a convenience factory function in ``score::td`` that creates a configured ``GPTPShmMachine`` (``shared_ptr``) backed by ``ShmPTPEngine``: - -.. code-block:: cpp - - auto machine = CreateGPTPShmMachine("shm", "/gptp_ptp_info"); - - - -Verification Machine Unit -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``Verification Machine`` component is responsible for validating and qualifying the time information received from the ``PTP Machine``. It applies various validation rules to ensure the time data meets quality requirements before distribution to applications. - -The component implements a pipeline pattern where each stage performs a specific validation and adds appropriate qualifiers to the time data. This modular design allows for easy extension with additional validation steps. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Verification Machine`` has the following requirements: - -- The ``Verification Machine`` shall validate and qualify time information received from the PTP Machine -- The ``Verification Machine`` shall validate if the time base is synchronized state -- The ``Verification Machine`` shall validate if the time base is in timeout state -- The ``Verification Machine`` shall validate timestamp for time jumps based on local clock -- The ``Verification Machine`` shall subscribe to the :ref:`input_ptp_data` topic via the ``Message Broker`` -- The ``Verification Machine`` shall publish verified time data to the ``Message Broker`` using the :ref:`verified_ptp_data` topic -- The ``Verification Machine`` shall support extensibility to add new validation stages in the pipeline - -Class view -^^^^^^^^^^ +VerificationMachine +^^^^^^^^^^^^^^^^^^^ -The Class Diagram is presented below. +Class diagram: .. raw:: html
.. uml:: _assets/ver_machine/ver_class.puml - :alt: Class Diagram + :alt: VerificationMachine class diagram .. raw:: html
-Component initialization -^^^^^^^^^^^^^^^^^^^^^^^^ - -During initialization, the ``Verification Machine`` performs the following steps: - -1. Set up the validation pipeline by creating and connecting validation stages - -The component shall be subscribed by the ``Application`` to the :ref:`input_ptp_data` topic of the ``MessageBroker`` - -The initialization workflow is represented in the following sequence diagram: +Initialization workflow: .. raw:: html
.. uml:: _assets/ver_machine/ver_init_seq.puml - :alt: Initialization workflow + :alt: VerificationMachine initialization .. raw:: html
-Data verification workflow -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When the ``Verification Machine`` receives new PTP data, it processes it through the validation pipeline: +Time data validation stages (sync check, timeout, jump detection): .. raw:: html
.. uml:: _assets/ver_machine/ver_verification_seq.puml - :alt: Validation pipeline + :alt: VerificationMachine pipeline .. raw:: html
+IPC Machine +^^^^^^^^^^^ - -IPC Machine Unit -~~~~~~~~~~~~~~~~ - -The ``IPC Machine`` component shall get the :ref:`verified_ptp_data` from the ``Verification Machine`` and provide it to the ``VehicleClock`` backend (see :doc:`score::time — Unified Clock Interface <../../time/index>`) through a custom shared memory channel. - -The component provides two sub components: publisher and receiver to be deployed on the TimeDaemon and Application sides accordingly. - -Implementation Requirements -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``IPC Machine`` has the following requirements: - -- The ``IPC Machine`` shall provide verified time data to the ``VehicleClock`` backend component through a custom shared memory channel -- The ``IPC Machine`` shall create and initialize the IPC -- The ``IPC Machine`` shall support multiple client applications accessing the same time data -- The ``IPC Machine`` shall subscribe to the :ref:`verified_ptp_data` topic via the ``Message Broker`` - -Class view -^^^^^^^^^^ - -The Class Diagram is presented below. +Class diagram: .. raw:: html
.. uml:: _assets/ipc/ipc_class.puml - :alt: Class Diagram + :alt: IPC class diagram .. raw:: html
-Component initialization -^^^^^^^^^^^^^^^^^^^^^^^^ - -Initialization is divided to two parts: - -1. Initialization on the TimeDaemon side -2. Initialization on the Application side - -Important thing, the shared memory IPC publisher shall be created and offered by the ``TimeDaemon`` before the Application side subscriber can connect. The Application shall retry until the service is found. - -The main workflow is described below. +Initialization workflow: .. raw:: html
.. uml:: _assets/ipc/ipc_init_seq.puml - :alt: Main workflow + :alt: IPC initialization .. raw:: html
-The component shall be subscribed during initialization by the ``Application`` on the :ref:`verified_ptp_data` updates from the ``Message Broker`` - -Publish new data -^^^^^^^^^^^^^^^^ - -When ``IPC Machine`` receives the new :ref:`verified_ptp_data` from Message Broker, it shall serialize data and write it to shared memory. - -As long as there are different use cases by using it, like: - -1. Get current Vehicle time -2. Get data for diagnostics - -All ``PtpTimeInfo`` data (or almost all) shall be published to the subscribed applications. - -The publish workflow is described below. +Qualified time snapshot publishing to client shared memory: .. raw:: html
.. uml:: _assets/ipc/ipc_publish_seq.puml - :alt: Publish workflow + :alt: IPC publish sequence .. raw:: html
-Receive data -^^^^^^^^^^^^ - -From Application side the receiver shall read from shared memory via the IPC receiver component and provide the data to the caller. - -The receive workflow is described below. +Client-side receive from TimeDaemon shared memory channel: .. raw:: html
.. uml:: _assets/ipc/ipc_receive_seq.puml - :alt: Receive workflow + :alt: IPC receive sequence .. raw:: html
- - Logging configuration ~~~~~~~~~~~~~~~~~~~~~ diff --git a/score/time_daemon/src/application/time_daemon.h b/score/time_daemon/src/application/time_daemon.h index 91108263..5e3ec474 100644 --- a/score/time_daemon/src/application/time_daemon.h +++ b/score/time_daemon/src/application/time_daemon.h @@ -22,6 +22,16 @@ namespace score namespace td { +/// @brief Main entry point for the TimeDaemon process. Orchestrates the lifecycle +/// of all daemon components. +/// +/// Retrieves Vehicle Time from the PTP slave daemon, verifies and validates +/// timepoints, and distributes time to clients via shared memory. Uses +/// MachineFactory to create components and wire pub/sub relationships via +/// MessageBroker. +/// +/// @see TimebaseHandler +/// @see score::mw::lifecycle::Application class TimeDaemon final : public score::mw::lifecycle::Application { public: @@ -33,7 +43,26 @@ class TimeDaemon final : public score::mw::lifecycle::Application TimeDaemon& operator=(TimeDaemon&&) & noexcept = delete; TimeDaemon& operator=(const TimeDaemon&) & noexcept = delete; + /// @brief Creates the MessageBroker and all machine components, and sets up subscriptions. + /// + /// Creates components in dependency order: + /// 1. MessageBroker + /// 2. ProactiveMachines: PtpMachine, ControlFlowDivider + /// 3. ReactiveMachines: VerificationMachine, IPCMachine + /// + /// Wires all pub/sub relationships via MessageBroker topic configuration. + /// + /// @param context Application context from the middleware. + /// @return 0 on success, non-zero on failure. std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext& context) override; + + /// @brief Starts the ProactiveMachines and monitors the stop token. + /// + /// Execution loop monitors stop_token. On termination request, stops all + /// ProactiveMachines in reverse order of startup for clean shutdown. + /// + /// @param token Stop token for graceful shutdown. + /// @return 0 on success, non-zero on failure. std::int32_t Run(const score::cpp::stop_token& token) override; private: diff --git a/score/time_daemon/src/control_flow_divider/core/control_flow_divider.h b/score/time_daemon/src/control_flow_divider/core/control_flow_divider.h index b6e1a9e3..efaef775 100644 --- a/score/time_daemon/src/control_flow_divider/core/control_flow_divider.h +++ b/score/time_daemon/src/control_flow_divider/core/control_flow_divider.h @@ -32,29 +32,28 @@ namespace score namespace td { -/** - * @brief Component responsible for decoupling data flow and timing within VehicleTimeDaemon. - * - * The ControlFlowDivider acts as a buffer between data producers and consumers, ensuring - * that timing-sensitive operations are not affected by blocking operations in downstream - * components. It maintains consistent data publishing rates by using a dedicated thread - * and internal buffering. When new data arrives, it processes all available data immediately. - * If no new data arrives within the specified timeout, it republishes the last known data - * to maintain consistent output timing. - * - * @tparam DataType The type of data being processed (e.g., PtpTimeInfo) - * @tparam BufferSize The size of the internal circular buffer for storing incoming data - */ +/// @brief Component responsible for decoupling data flow and timing within VehicleTimeDaemon. +/// +/// The ControlFlowDivider acts as a buffer between data producers and consumers, ensuring +/// that timing-sensitive operations are not affected by blocking operations in downstream +/// components. It maintains consistent data publishing rates by using a dedicated thread +/// and internal buffering. When new data arrives, it processes all available data immediately. +/// If no new data arrives within the specified timeout, it republishes the last known data +/// to maintain consistent output timing. +/// +/// Thread safety: @c OnMessage() called from MessageBroker thread; @c OnEvent() / @c OnTimeout() +/// called from worker thread. Synchronized via @c data_buffer_mutex_. +/// +/// @tparam DataType The type of data being processed (e.g. PtpTimeInfo). +/// @tparam BufferSize The size of the internal circular buffer for incoming data. template class ControlFlowDivider final : public EventDrivenMachine, public Consumer, public Producer { public: - /** - * @brief Constructs a ControlFlowDivider with specified timeout. - * - * @param name The name of this control flow divider instance - * @param timeout Maximum time to wait for new data before republishing the last known data - */ + /// @brief Constructs a ControlFlowDivider with the specified timeout. + /// + /// @param name The name of this control flow divider instance. + /// @param timeout Maximum time to wait for new data before republishing the last known data. explicit ControlFlowDivider(const std::string& name, std::chrono::milliseconds timeout); ~ControlFlowDivider() override = default; @@ -64,50 +63,48 @@ class ControlFlowDivider final : public EventDrivenMachine, public Consumer callback) override; - /** - * @brief Receives new data and queues it for processing. - * - * This method receives incoming data, stores it in the internal buffer, - * and triggers the event-driven processing mechanism to handle the data. - * The operation is thread-safe and non-blocking. - * - * @param data The data to be queued for processing - */ + /// @brief Receives new data and queues it for asynchronous processing. + /// + /// This method receives incoming data, stores it in the internal buffer, + /// and triggers the event-driven processing mechanism to handle the data. + /// The operation is thread-safe and non-blocking. + /// + /// @param data The data to be queued for processing. void OnMessage(DataType data) override; protected: + /// @brief Drains the circular buffer and publishes each queued item in FIFO order. + /// + /// Called by the EventDrivenMachine worker thread when @c OnMessage() triggers + /// an event. Acquires @c data_buffer_mutex_ and publishes all pending items. void OnEvent() noexcept override; + /// @brief Republishes the last known data when no new data has arrived within the timeout. + /// + /// Called by the EventDrivenMachine worker thread on timeout expiry. Publishes + /// the cached @c last_data_ to preserve a consistent output cadence even when + /// the upstream data flow stalls. void OnTimeout() noexcept override; private: - /** - * @brief Publishes data to the Message Broker. - * - * @param data The data to be published - */ + /// @brief Publishes data to the MessageBroker via the registered callback. + /// + /// @param data The data to be published. void Publish(const DataType& data) override; std::mutex data_buffer_mutex_; score::cpp::circular_buffer data_buffer_; - /** @brief Callback function for publishing data */ + /// @brief Callback function for publishing data std::function publish_callback_; DataType last_data_; diff --git a/score/time_daemon/src/ipc/core/publisher_impl.h b/score/time_daemon/src/ipc/core/publisher_impl.h index 40ed78ed..e76efe73 100644 --- a/score/time_daemon/src/ipc/core/publisher_impl.h +++ b/score/time_daemon/src/ipc/core/publisher_impl.h @@ -25,13 +25,33 @@ namespace score namespace td { +/// @brief IPC Machine publisher component that distributes verified time data +/// to client applications via shared memory. /// -/// \brief Publisher class to publish data through ipc to Client +/// Receives verified @c PtpTimeInfo from the MessageBroker (via the +/// @c verified_ptp_data topic), converts it to the target IPC format (e.g. +/// @c svt::TimeBaseSnapshot), and writes it to a custom shared memory channel +/// for consumption by VehicleClock backend clients. /// +/// Runs in reactive mode: @c OnMessage() is called by MessageBroker when new +/// @c verified_ptp_data arrives. @c SharedMemoryHandler::Send() is thread-safe +/// for a single writer (only one thread may call @c OnMessage() concurrently); +/// multiple readers on the application side can access the segment concurrently. +/// +/// @tparam DataType Internal data type (e.g. PtpTimeInfo from MessageBroker). +/// @tparam IpcDataType Shared memory IPC data type (e.g. svt::TimeBaseSnapshot). +/// +/// @see SvtPublisher Type alias for PublisherImpl +/// @see SharedMemoryHandler Shared memory management implementation +/// @see ConvertToIpcData Data conversion function template class PublisherImpl : public ReactiveMachine, public Consumer { public: + /// @brief Constructs the IPC publisher with a shared memory path. + /// + /// @param name Publisher instance name (used for logging). + /// @param shared_memory_path POSIX shared memory name (e.g. /td_svt_ipc). PublisherImpl(const std::string& name, const std::string& shared_memory_path) noexcept : ReactiveMachine(name), Consumer(), shm_handler_{shared_memory_path} { @@ -43,7 +63,21 @@ class PublisherImpl : public ReactiveMachine, public Consumer PublisherImpl& operator=(PublisherImpl&&) = delete; ~PublisherImpl() override = default; + /// @brief Initialises the shared memory IPC channel. + /// + /// Creates the backing shared memory segment for downstream readers via + /// @c SharedMemoryHandler::Init(). Must be called once before @c OnMessage(). + /// + /// @return true if the shared memory segment was created successfully. bool Init() override; + + /// @brief Receives verified time data from the MessageBroker and publishes to shared memory. + /// + /// Converts @c DataType to @c IpcDataType via @c ConvertToIpcData(), then calls + /// @c SharedMemoryHandler::Send() which performs a single-writer atomic write + /// into the shared memory segment. + /// + /// @param data PtpTimeInfo from the @c verified_ptp_data topic. void OnMessage(DataType data) override; private: diff --git a/score/time_daemon/src/ipc/core/receiver_impl.h b/score/time_daemon/src/ipc/core/receiver_impl.h index 1cc905fc..94709a2d 100644 --- a/score/time_daemon/src/ipc/core/receiver_impl.h +++ b/score/time_daemon/src/ipc/core/receiver_impl.h @@ -22,13 +22,28 @@ namespace score namespace td { +/// @brief IPC Machine receiver component for reading verified time data from +/// the shared memory channel written by @c PublisherImpl. /// -/// \brief Receiver impl class to receive data from ipc +/// Opens an existing shared memory channel and reads the latest IPC-formatted +/// time data. Used by the VehicleClock backend on the application +/// side to retrieve time information from TimeDaemon. /// +/// Thread-safe for multiple concurrent readers. +/// @c SharedMemoryHandler::Receive() returns the latest snapshot atomically. +/// The call is non-blocking and returns @c std::nullopt if no data is available. +/// +/// @tparam IpcDataType Shared memory IPC data type (e.g. @c svt::TimeBaseSnapshot). +/// +/// @see SvtReceiver Type alias for ReceiverImpl +/// @see PublisherImpl TimeDaemon-side publisher component template class ReceiverImpl : public Receiver { public: + /// @brief Constructs the IPC receiver with a shared memory path. + /// + /// @param shared_memory_path POSIX shared memory name matching the publisher (e.g. /td_svt_ipc). ReceiverImpl(const std::string& shared_memory_path) noexcept : Receiver(), shm_handler_{shared_memory_path} { @@ -40,8 +55,19 @@ class ReceiverImpl : public Receiver ReceiverImpl& operator=(ReceiverImpl&&) = delete; ~ReceiverImpl() override = default; + /// @brief Opens the existing shared memory IPC channel created by the publisher. + /// + /// Connects to the already-created segment by name; does not create a new segment. + /// Non-blocking; returns false immediately if segment not found. + /// + /// @return true if the shared memory segment was opened successfully. bool Init() noexcept override; + /// @brief Reads the latest time data from shared memory. + /// + /// Non-blocking. Returns @c std::nullopt if no new or valid sample is available. + /// + /// @return Latest IPC data if available, @c std::nullopt otherwise. std::optional Receive() noexcept override; private: diff --git a/score/time_daemon/src/msg_broker/msg_broker.h b/score/time_daemon/src/msg_broker/msg_broker.h index 6aac807d..f74e4db4 100644 --- a/score/time_daemon/src/msg_broker/msg_broker.h +++ b/score/time_daemon/src/msg_broker/msg_broker.h @@ -28,17 +28,40 @@ namespace score namespace td { +/// @brief Central publish-subscribe communication hub within the TimeDaemon. /// -/// \brief Class to store each subscriber for dedicated topic. Idea is simple, any component can use subscribe with -/// dedicated callback -/// for explicit "topic". Then callback will be invoked when some component will call OnNewData() for dedicated -/// topic. +/// Manages topics and distributes messages to interested subscribers, enabling +/// decoupled communication: components evolve independently without direct +/// dependencies on each other. /// +/// MessageBroker does not provide synchronization between publish and callback +/// invocation. Callbacks run synchronously in caller's thread; no queuing. +/// To separate control flows, use @c ControlFlowDivider. +/// +/// @tparam T Message data type for this broker instance (e.g. PtpTimeInfo). +/// +/// @see ControlFlowDivider For control flow separation between threads. template class MessageBroker : public std::enable_shared_from_this> { public: + /// @brief Registers a consumer component to receive messages on the specified topic. + /// + /// Creates a subscription wrapping the consumer's @c OnMessage() callback and + /// adds it to the topic registry. Uses a weak_ptr to avoid prolonging the + /// consumer's lifetime. + /// + /// @param topic Topic name to subscribe to. + /// @param subscriber_weak Weak pointer to the consumer component. void AddSubscriber(const Topic& topic, std::weak_ptr> subscriber_weak); + + /// @brief Registers a producer component to publish messages on the specified topic. + /// + /// Sets the producer's publish callback to invoke @c OnNewData() on this broker. + /// Uses a weak_ptr to avoid prolonging the producer's lifetime. + /// + /// @param topic Topic name to publish on. + /// @param producer_weak Weak pointer to the producer component. void AddProducer(const Topic& topic, std::weak_ptr> producer_weak); private: diff --git a/score/time_daemon/src/ptp_machine/core/ptp_machine.h b/score/time_daemon/src/ptp_machine/core/ptp_machine.h index 5a3dcd28..5b7855aa 100644 --- a/score/time_daemon/src/ptp_machine/core/ptp_machine.h +++ b/score/time_daemon/src/ptp_machine/core/ptp_machine.h @@ -27,25 +27,34 @@ namespace score namespace td { -/** - * @brief Manages time synchronization by interfacing with the PTP engine. - * - * The PTPMachine class abstracts the interaction with the Precision Time Protocol (PTP) engine, - * periodically retrieving time information and publishing it to interested subscribers. - * It handles initialization, deinitialization, and error recovery for the PTP stack, - * ensuring reliable and up-to-date time data delivery within the system. - */ +/// @brief Retrieves raw PTP time data from the PTP stack and publishes it to +/// the MessageBroker. +/// +/// Wraps platform-specific PTP stack communication (e.g. ptpd via devctl or +/// gPTP shared memory), making stack implementations exchangeable. Runs in a +/// dedicated thread via @c PeriodicMachine to maintain a consistent publishing +/// rate even when PTP stack communication experiences delays. +/// +/// Data flow per period: +/// 1. @c PeriodicTask() called at the configured update interval. +/// 2. @c ReadPTPSnapshot() retrieves raw data from the PTP stack via @c engine_impl_. +/// 3. Data published to MessageBroker as the @c raw_ptp_data topic. +/// 4. @c ControlFlowDivider receives it and separates control flow for the pipeline. +/// +/// @tparam PTPEngine Platform-specific PTP stack interface (e.g. ShmPTPEngine). +/// +/// @see ControlFlowDivider For control flow separation. +/// @see PtpTimeInfo For the data structure definition. +/// @see PeriodicMachine For the threading model. template class PTPMachine final : public PeriodicMachine, public Producer { public: - /** - * @brief Constructs a PTPMachine with the specified name, update interval, and PTP engine arguments. - * - * @param name The name of the PTPMachine instance. - * @param updateInterval The interval at which time data is retrieved and published. - * @param args Arguments forwarded to the PTPEngine constructor. - */ + /// @brief Constructs a PTPMachine with the specified name, update interval, and PTP engine arguments. + /// + /// @param name The name of the PTPMachine instance. + /// @param updateInterval The interval at which time data is retrieved and published. + /// @param args Arguments forwarded to the PTPEngine constructor. template explicit PTPMachine(const std::string& name, std::chrono::milliseconds updateInterval, PTPEngineArgs&&... args) : PeriodicMachine(name, updateInterval), @@ -65,51 +74,40 @@ class PTPMachine final : public PeriodicMachine, public Producer PTPMachine(PTPMachine&&) = delete; PTPMachine& operator=(PTPMachine&&) = delete; - /** - * @brief Initializes the PTP stack and prepares the machine for operation. - * - * Attempts to establish communication with the underlying PTP engine. - * This method should be called before starting periodic time synchronization tasks. - * - * @return true if initialization was successful, false otherwise - */ + /// @brief Initialises the PTP stack and prepares the machine for operation. + /// + /// Attempts to establish communication with the underlying PTP engine. + /// Must be called before starting periodic time synchronisation tasks. + /// + /// @return true if initialisation was successful, false otherwise. bool Init() override; - /** - * @brief Sets the callback function to be invoked when publishing data. - * - * @param callback Function to be called when data is published - */ + /// @brief Sets the callback function to be invoked when publishing data. + /// + /// @param callback Function to be called when data is published. void SetPublishCallback(std::function callback) override; protected: - /** - * @brief Periodically retrieves and publishes the latest PTP time data. - * - * Invoked at each update interval, this method obtains the current time information - * from the PTP engine and publishes it to subscribers. Handles error recovery if the - * PTP stack is not initialized or data retrieval fails. - */ + /// @brief Periodically retrieves and publishes the latest PTP time data. + /// + /// Invoked at each update interval by the PeriodicMachine worker thread. + /// Obtains the current time information from the PTP engine and publishes + /// it to subscribers. On read failure or uninitialised engine, calls + /// @c Deinit() and retries on the next interval. void PeriodicTask() noexcept override; private: - /** - * @brief Deinitializes the PTP stack and releases associated resources. - * - * Cleans up the connection to the underlying PTP engine and resets the internal state. - * - * @return true if deinitialization was successful, false otherwise - */ + /// @brief Deinitialises the PTP stack and releases associated resources. + /// + /// Cleans up the connection to the underlying PTP engine and resets the internal state. void Deinit(); - /** - * @brief Publishes time information data to registered subscribers. - * - * @param data The time information to publish - */ + /// @brief Publishes time information data to the registered subscribers. + /// + /// @param data The time information to publish. void Publish(const PtpTimeInfo& data) override; - /** @brief Callback function invoked when publishing data */ + /// @brief Callback invoked when publishing data. std::function publish_callback_; std::unique_ptr engine_impl_; diff --git a/score/time_daemon/src/ptp_machine/shm/details/shm_ptp_engine.h b/score/time_daemon/src/ptp_machine/shm/details/shm_ptp_engine.h index 4fdf9c3b..12a97599 100644 --- a/score/time_daemon/src/ptp_machine/shm/details/shm_ptp_engine.h +++ b/score/time_daemon/src/ptp_machine/shm/details/shm_ptp_engine.h @@ -25,13 +25,17 @@ namespace td namespace details { -/** - * @brief PTP engine that reads time data from the shared-memory IPC channel - * written by TimeSlave via GptpIpcPublisher. - * - * Converts the ts_client/src-internal GptpIpcData to the TimeDaemon PtpTimeInfo - * data model. - */ +/// @brief PTP engine implementation that reads @c GptpIpcData from the shared +/// memory channel written by TimeSlave via @c GptpIpcPublisher. +/// +/// Converts the @c GptpIpcData (ts_client-internal type) to the @c PtpTimeInfo +/// structure expected by the TimeDaemon pipeline. Instantiated as +/// @c GPTPShmMachine (type alias for @c PTPMachine), +/// connecting this engine to the TimeDaemon MessageBroker. +/// +/// @see CreateGPTPShmMachine Factory function. +/// @see GptpIpcReceiver In ts_client, for the shared memory protocol. +/// @see PTPMachine Template wrapper. class ShmPTPEngine final { public: @@ -43,10 +47,44 @@ class ShmPTPEngine final ShmPTPEngine(ShmPTPEngine&&) = delete; ShmPTPEngine& operator=(ShmPTPEngine&&) = delete; + /// @brief Opens the shared memory IPC channel written by TimeSlave. + /// + /// Calls @c GptpIpcReceiver::Init(ipc_name_) to map the shared memory + /// segment. Must be called once before @c ReadPTPSnapshot(). + /// + /// @return @c true if the channel was opened successfully. bool Initialize(); + /// @brief Closes the shared memory IPC channel. + /// + /// Calls @c GptpIpcReceiver::Close() to unmap the shared memory region. + /// + /// @return @c true (always succeeds). bool Deinitialize(); + /// @brief Reads the latest gPTP snapshot from shared memory and converts it + /// to @c PtpTimeInfo. + /// + /// Calls @c GptpIpcReceiver::Receive() then performs a field-by-field + /// mapping from @c GptpIpcData to @c PtpTimeInfo: + /// + /// | @c GptpIpcData field | @c PtpTimeInfo field | + /// |---------------------------------|------------------------------------------------| + /// | @c ptp_assumed_time | @c ptp_assumed_time | + /// | @c local_time | @c local_time (wrapped in ReferenceClock::time_point) | + /// | @c rate_deviation | @c rate_deviation | + /// | @c status.is_synchronized | @c status.is_synchronized | + /// | @c status.is_timeout | @c status.is_timeout | + /// | @c status.is_time_jump_future | @c status.is_time_jump_future | + /// | @c status.is_time_jump_past | @c status.is_time_jump_past | + /// | @c status.is_correct | @c status.is_correct | + /// | @c sync_fup_data.* (9 fields) | @c sync_fup_data.* (direct copy) | + /// | @c pdelay_data.* (12 fields) | @c pdelay_data.* (direct copy) | + /// + /// All @c GptpIpcData fields mapped 1:1. Unmapped @c PtpTimeInfo fields zero-initialized. + /// + /// @param info Output parameter filled with the converted snapshot. + /// @return @c true if a valid snapshot was read; @c false otherwise. bool ReadPTPSnapshot(PtpTimeInfo& info); private: diff --git a/score/time_daemon/src/verification_machine/core/verification_machine.h b/score/time_daemon/src/verification_machine/core/verification_machine.h index 345e7899..14f59e81 100644 --- a/score/time_daemon/src/verification_machine/core/verification_machine.h +++ b/score/time_daemon/src/verification_machine/core/verification_machine.h @@ -29,28 +29,33 @@ namespace score namespace td { -/** - * @brief Machine component responsible for validating and qualifying time information. - * - * The VerificationMachine implements a pipeline pattern where each stage performs - * specific validation and adds appropriate qualifiers to the provided data. - */ +/// @brief Validates and qualifies time information received from the PTP Machine. +/// +/// Implements a configurable pipeline pattern where each stage performs a +/// specific validation and adds appropriate qualifiers to the provided data. +/// The pipeline is assembled at construction time from factory functions. +/// +/// Typical validation stages: +/// 1. Sync state validation +/// 2. Timeout detection +/// 3. Time jump detection (forward / backward) +/// +/// @tparam DataType The data type being validated (e.g. PtpTimeInfo). template class VerificationMachine final : public ReactiveMachine, public Consumer, public Producer { public: - /** - * @brief Constructor for VerificationMachine with validator factories. - * - * Each factory creates one validation stage. Stages are processed in the order - * they are provided (first factory creates first stage in pipeline). - * - * @tparam Factories Types of factory functions - * @param name The name of this verification machine instance - * @param factories Factory functions to create each validator. Each factory - * must return a unique_ptr to a VerificationStage. - * Factories can be lambdas, std::bind expressions, or function pointers. - */ + /// @brief Constructs a VerificationMachine from a set of validator factories. + /// + /// Each factory creates one validation stage. Stages are processed in the order + /// they are provided (first factory creates first stage in pipeline). + /// + /// @tparam Factories Types of factory functions. + /// @param name The name of this verification machine instance. + /// @param factories Factory functions to create each validator. Each factory + /// must return @c unique_ptr>; + /// nullptr causes assertion. Factories can be lambdas, @c std::bind + /// expressions, or function pointers. template explicit VerificationMachine(const std::string& name, Factories&&... factories) : ReactiveMachine(name), Consumer(), Producer(), pipeline_(), publish_callback_() @@ -68,33 +73,23 @@ class VerificationMachine final : public ReactiveMachine, public Consumer callback) override; - /** - * @brief Process the received time information. - * - * This method is responsible for processing the time information data. - * - * @param data The time information data to be processed - */ + /// @brief Receives time information, runs it through the validation pipeline, and publishes the result. + /// + /// @param data The time information data to be processed. void OnMessage(DataType data) override; - /** - * @brief Initialize machine - * - * As there is no explicit Init actions, it will be stubbed and return true. - * - * @param bool Init result - */ + /// @brief Initialize the machine. Stubbed — returns true immediately as no + /// explicit initialization actions are required. + + /// @return true bool Init() override; private: @@ -103,30 +98,23 @@ class VerificationMachine final : public ReactiveMachine, public Consumer; using StageFactory = std::function; - /** - * @brief Publishes the time information data using the registered callback. - * - * This method invokes the previously set publish callback to distribute - * the data to interested consumers. - * - * @param data The data to be published - */ + /// @brief Publishes the time information data using the registered callback. + /// + /// @param data The data to be published. void Publish(const DataType& data) override; - /** - * @brief Sets up the validation pipeline by creating and connecting stages. - */ + /// @brief Sets up the validation pipeline by creating and connecting stages. + /// + /// Stages are connected in factory order: output of stage N becomes input of stage N+1. void SetupPipeline(const std::vector& factories); - /** - * @brief Processes time information through the validation pipeline. - * - * @param data The time information to validate - * @return The validated and qualified time information - */ + /// @brief Runs data through all validation pipeline stages sequentially. + /// + /// @param data The time information to validate. + /// @return The validated and qualified time information. auto ProcessMessage(DataType data) -> DataType; - /** @brief First stage in the validation pipeline */ + /// @brief First stage in the validation pipeline. std::unique_ptr pipeline_; std::function publish_callback_;