From cfcf18d7e5d0d3fd670c36a5cc6208b992c0ec83 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Wed, 19 Aug 2026 14:39:56 +0100 Subject: [PATCH 1/3] migrate time detailed design from features docs --- docs/features/index.rst | 1 - docs/features/time/index.rst | 826 ------------------ .../_assets/architecture_layers.puml | 13 + .../_assets/class_overview.puml | 13 + .../high_res_steady_time/ht1_polling.puml | 13 + .../_assets/steady_clock/st1_polling.puml | 13 + .../_assets/system_clock/sc1_polling.puml | 13 + .../_assets/testing/t1_scoped_override.puml | 13 + .../_assets/testing/t2_test_factory.puml | 13 + .../_assets/vehicle_time/vt1_polling.puml | 13 + .../vehicle_time/vt2_availability.puml | 13 + .../vehicle_time/vt3_subscription.puml | 13 + .../vehicle_time/vt4_status_subscription.puml | 13 + .../_assets/vehicle_time/vt5_diagnostics.puml | 13 + .../docs/detailed_design/detailed_design.rst | 96 -- score/time/docs/detailed_design/index.rst | 806 ++++++++++++++++- 16 files changed, 952 insertions(+), 933 deletions(-) delete mode 100644 docs/features/time/index.rst rename {docs/features/time => score/time/docs/detailed_design}/_assets/architecture_layers.puml (79%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/class_overview.puml (87%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/high_res_steady_time/ht1_polling.puml (76%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/steady_clock/st1_polling.puml (73%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/system_clock/sc1_polling.puml (73%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/testing/t1_scoped_override.puml (79%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/testing/t2_test_factory.puml (75%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/vehicle_time/vt1_polling.puml (78%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/vehicle_time/vt2_availability.puml (78%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/vehicle_time/vt3_subscription.puml (84%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/vehicle_time/vt4_status_subscription.puml (77%) rename {docs/features/time => score/time/docs/detailed_design}/_assets/vehicle_time/vt5_diagnostics.puml (66%) delete mode 100644 score/time/docs/detailed_design/detailed_design.rst diff --git a/docs/features/index.rst b/docs/features/index.rst index db95ddb0..53249533 100644 --- a/docs/features/index.rst +++ b/docs/features/index.rst @@ -19,7 +19,6 @@ Features :maxdepth: 2 :caption: Features: - time/index time_daemon/index time_slave/index test_cases diff --git a/docs/features/time/index.rst b/docs/features/time/index.rst deleted file mode 100644 index 653b8a15..00000000 --- a/docs/features/time/index.rst +++ /dev/null @@ -1,826 +0,0 @@ -score::time — Unified Clock Interface -====================================== - -.. contents:: Table of Contents - :depth: 3 - :local: - -Overview --------- - -``score::time`` provides a **unified, clock-domain-agnostic API** for reading time -snapshots, checking clock readiness, and subscribing to clock synchronization events — -all through a single template wrapper ``Clock``. - -The design separates two concerns: - -1. **What kind of time** — expressed as a *tag struct* (``VehicleTime``, - ``HighResSteadyTime``, ``std::chrono::steady_clock``, - ``std::chrono::system_clock``). -2. **How to access it** — always via ``Clock::GetInstance()``; clock-domain - selection is a compile-time decision, enforced by the type system. - -Clock domains -~~~~~~~~~~~~~ - -.. list-table:: - :header-rows: 1 - :widths: 20 30 50 - - * - Clock alias - - Tag - - Status concept - * - ``VehicleClock`` - - ``VehicleTime`` - - ``VehicleTimeStatus`` - * - ``HighResSteadyClock`` - - ``HighResSteadyTime`` - - ``NoStatus`` - * - ``SteadyClock`` - - ``std::chrono::steady_clock`` - - ``NoStatus`` - * - ``SystemClock`` - - ``std::chrono::system_clock`` - - ``NoStatus`` - -VehicleTime -^^^^^^^^^^^ - -``VehicleTime`` is a PTP-synchronized timebase driven by the network Grand Master clock. -Each ``Now()`` call returns a ``ClockSnapshot`` that bundles the timepoint with a -``VehicleTimeStatus`` — a set of quality flags (``kSynchronized``, ``kTimeOut``, -``kTimeLeapFuture``, ``kTimeLeapPast``) and a rate-deviation measurement. The flags let -callers decide whether the time value is reliable enough for their use case without -making a separate status call. - -Because ``VehicleTime`` depends on an IPC channel to the -:doc:`TimeDaemon <../time_daemon/index>`, it requires an explicit ``Init()`` call before -``Now()`` returns synchronized data. Readiness can be probed non-blocking via -``IsAvailable()`` or waited for with ``WaitUntilAvailable()``. Callers can also -subscribe to synchronization events (status changes, sync messages, peer-delay -measurements) via ``Clock::Subscribe()``. - -HighResSteadyTime -^^^^^^^^^^^^^^^^^ - -``HighResSteadyTime`` is a monotonic, nanosecond-resolution clock optimized for -low-overhead timing. On QNX the backend reads the hardware cycle counter directly via -``ClockCycles()`` — no kernel call, no scheduler interaction. On Linux it delegates to -``std::chrono::high_resolution_clock``. It carries ``NoStatus`` and is always ready: no -``Init()`` is needed and ``IsAvailable()`` / ``WaitUntilAvailable()`` are not available -(calling them is a compile error). Use it for tight timing loops and deadline checks -where call overhead matters. - -SteadyClock -^^^^^^^^^^^ - -``SteadyClock`` wraps ``std::chrono::steady_clock`` (POSIX ``CLOCK_MONOTONIC``). It is -monotonic and never goes backward, making it the standard choice for measuring elapsed -time and computing timeouts. It carries ``NoStatus`` and requires no initialization. - -SystemClock -^^^^^^^^^^^ - -``SystemClock`` wraps ``std::chrono::system_clock`` (POSIX ``CLOCK_REALTIME``). It -represents wall-clock (UTC-based) time and may be adjusted or jump forward or backward. -It carries ``NoStatus`` and requires no initialization. Use it when a calendar -timestamp is needed — not for measuring elapsed time or computing timeouts. - -Architecture ------------- - -.. raw:: html - -
- -.. uml:: _assets/architecture_layers.puml - :alt: Architecture layer diagram - -.. raw:: html - -
- -The library has three layers: - -- **Public headers** under ``score/time//`` — tag structs and callback types that - clients include directly. -- **Framework layer** under ``score/time/clock/`` — the ``Clock`` wrapper, traits, - subscription hooks, and the test utilities (``clock_test_utils`` Bazel target). This - layer has no backend dependency. - The ``clock_test_utils`` target (``scoped_clock_override.h``, ``clock_test_factory.h``) - is ``testonly`` and must not appear in production deps. -- **Internal** under ``score/time//details/`` — pure-virtual backend interfaces and - production implementations. *Clients must never include anything from a* ``details/`` - *subfolder.* - -Class overview -~~~~~~~~~~~~~~ - -.. raw:: html - -
- -.. uml:: _assets/class_overview.puml - :alt: Class overview - :width: 100% - :align: center - -.. raw:: html - -
- -Core types -~~~~~~~~~~ - -``Clock`` -^^^^^^^^^^^^^^ - -The sole user-facing handle for a clock domain. A cheaply copyable value type — all -copies share the same backend instance via a ``shared_ptr``. Its API surface is -intentionally uniform across all domains: ``Now()`` for reading, ``Subscribe`` / -``Unsubscribe`` for events, ``Init`` / ``IsAvailable`` / ``WaitUntilAvailable`` for -readiness — with opt-in capabilities gated at compile time by the hook templates below. - -``ClockTraits`` -^^^^^^^^^^^^^^^^^^^^ - -The domain registration point. The primary template is intentionally incomplete; each -clock domain provides a full explicit specialisation that binds together the backend -type, duration, timepoint, snapshot, and the ``CallNow`` factory function. No existing -file is modified when a new domain is added. - -``ClockSnapshot`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The immutable return value of every ``Now()`` call. Bundles the timepoint and its -quality metadata into a single atomic read — no separate status call is ever needed. -``TimepointT`` is ``std::chrono::time_point``, making different clock -domains' timepoints incompatible types so cross-domain arithmetic is a compile error. -``StatusT`` is the domain's chosen metadata type (see below). - -``StatusT``, ``ClockStatus``, and ``NoStatus`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All domain-specific metadata lives in ``StatusT`` — ``ClockSnapshot`` itself is never -extended. Two building blocks are provided: ``NoStatus`` (zero-size placeholder for -always-ready clocks with no quality concept) and ``ClockStatus`` (generic -bitmask over a scoped flag enum). A domain may use either alone or compose them inside -a richer struct alongside continuous fields — as ``VehicleTimeStatus`` does with -``ClockStatus`` and ``double rate_deviation``. - -Capability hooks -^^^^^^^^^^^^^^^^ - -Three SFINAE hook templates gate the optional capabilities of ``Clock``. Each -primary template is intentionally undefined — using an ungated capability on a domain -that has not opted in is a **compile error**, not a runtime failure: -``InitializationHook`` unlocks ``Init()``, ``AvailabilityHook`` unlocks -``IsAvailable()`` and ``WaitUntilAvailable()``, and ``SubscriptionHook`` -unlocks ``Subscribe`` / ``Unsubscribe`` for a specific event type. - -Use Cases ---------- - -VehicleTime -~~~~~~~~~~~ - -VT1 — Time polling with status check -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Obtain a snapshot and inspect the synchronization quality before using the time value. -``Now()`` returns a single immutable ``ClockSnapshot`` — the timepoint and its -``VehicleTimeStatus`` are always fetched together, with no separate status call needed. - -.. raw:: html - -
- -.. uml:: _assets/vehicle_time/vt1_polling.puml - :alt: VT1 — Time polling with status check - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - - void MyComponent::CheckTime() - { - auto clock = score::time::VehicleClock::GetInstance(); - auto snapshot = clock.Now(); - - if (snapshot.Status().IsReliable()) { - auto tp = snapshot.TimePoint(); - // use tp ... - } else if (snapshot.Status().IsFlagActive( - score::time::VehicleTime::StatusFlag::kTimeOut)) { - HandleTimeout(); - } - } - -.. note:: - - ``Init()`` must be called once during application startup before ``Now()`` is expected - to return synchronized data (see VT2). Without it, ``Now()`` returns a snapshot with - no flags set (``IsConsistent()`` returns ``false``). - -**Status flags:** - -+---------------------------+--------------------------------------------------------------+ -| Flag | Meaning | -+===========================+==============================================================+ -| ``kSynchronized`` | Synchronized at least once to the PTP Grand Master | -+---------------------------+--------------------------------------------------------------+ -| ``kTimeOut`` | No sync message received within the configured time window | -+---------------------------+--------------------------------------------------------------+ -| ``kTimeLeapFuture`` | A large forward adjustment was applied | -+---------------------------+--------------------------------------------------------------+ -| ``kTimeLeapPast`` | A large backward adjustment was applied | -+---------------------------+--------------------------------------------------------------+ - -``VehicleTimeStatus::IsReliable()`` returns ``true`` only when ``kSynchronized`` is set -**and** none of ``{kTimeOut, kTimeLeapFuture, kTimeLeapPast}`` is set. -``VehicleTimeStatus::HasBeenSynchronized()`` returns ``true`` whenever ``kSynchronized`` -has been set at least once during this lifecycle, regardless of current fault flags. -``VehicleTimeStatus::IsConsistent()`` checks that the flag combination is internally -valid (at least one flag set, and not both leap flags simultaneously). - -These three methods belong to ``VehicleTimeStatus`` and encode VehicleTime-domain -semantics. ``ClockStatus`` itself exposes only generic bit-manipulation -(``IsFlagActive``, ``IsAnyOfFlagsActive``, ``AddFlag``) and the domain-specific -``PrintTo()`` specialization. - -VT2 — Initialization and readiness check -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``VehicleTime`` requires an explicit ``Init()`` call to open the IPC channel to the -:doc:`TimeDaemon <../time_daemon/index>` before any time data becomes available. Until ``Init()`` returns ``true``, -``Now()`` returns a snapshot with no flags set (``IsConsistent()`` returns ``false``) and ``IsAvailable()`` returns -``false``. - -After a successful ``Init()``, ``IsAvailable()`` returns ``true`` immediately. The -non-blocking ``IsAvailable()`` probe and the blocking ``WaitUntilAvailable()`` are useful -when ``Init()`` is retried on a background thread. - -.. raw:: html - -
- -.. uml:: _assets/vehicle_time/vt2_availability.puml - :alt: VT2 — Initialization and readiness check - -.. raw:: html - -
- -**Simple startup (same thread):** - -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - - bool MyService::Startup() - { - auto clock = score::time::VehicleClock::GetInstance(); - if (!clock.Init()) { - LOG_ERROR("VehicleTime: failed to open IPC channel"); - return false; - } - auto snapshot = clock.Now(); - // ... - return true; - } - -**Blocking wait when Init is retried from a background thread:** - -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - #include - #include - - void MyService::WaitForClock(const score::cpp::stop_token& stop) - { - auto clock = score::time::VehicleClock::GetInstance(); - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{30}; - if (!clock.WaitUntilAvailable(stop, deadline)) { - LOG_ERROR("VehicleTime did not become available within 30 s"); - return; - } - auto snapshot = clock.Now(); - // ... - } - -.. note:: - - ``Init()``, ``IsAvailable()``, and ``WaitUntilAvailable()`` are **only available on - clock domains that require explicit initialisation** (currently ``VehicleTime``). - Calling them on ``HighResSteadyTime``, ``SteadyClock``, or ``SystemClock`` is a **compile - error** — those clocks are always ready. - -VT3 — Async PTP protocol data subscription -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``VehicleTime`` exposes two PTP protocol data callbacks, intended primarily for -diagnostics and PTP data sanity checks: - -- ``TimeSlaveSyncData`` — fired on each PTP Sync/Follow_Up message pair; - carries the offset, rate correction, and raw timestamps computed by the TimeSlave. -- ``PDelayMeasurementData`` — fired when a peer-delay measurement cycle - completes; carries the measured peer delay and associated timestamps. - -.. raw:: html - -
- -.. uml:: _assets/vehicle_time/vt3_subscription.puml - :alt: VT3 — Async PTP protocol data subscription - -.. raw:: html - -
- -.. warning:: - - Both PTP data callbacks (``TimeSlaveSyncData`` and ``PDelayMeasurementData``) are - **not yet delivered**. Calling ``Subscribe<...>()`` compiles and runs without error, - but the registered callbacks will never be invoked. Delivery will be wired from a - dedicated background thread in a future change. - -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - #include "score/time/ptp/src/time_slave_sync_data.h" - #include "score/time/ptp/src/pdelay_measurement_data.h" - - void MyDiagHandler::RegisterCallbacks() - { - auto clock = score::time::VehicleClock::GetInstance(); - - clock.Subscribe>( - [this](const auto& data) { OnTimeSyncData(data); }); - - clock.Subscribe>( - [this](const auto& data) { OnPDelayData(data); }); - } - - void MyDiagHandler::Shutdown() - { - auto clock = score::time::VehicleClock::GetInstance(); - clock.Unsubscribe>(); - clock.Unsubscribe>(); - } - -.. warning:: - - Callbacks are invoked on the **backend thread** — the callback implementation must be - thread-safe. - -VT4 — Synchronization status subscription -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Subscribe to ``VehicleTimeStatus`` changes to react when the clock synchronization state -changes — for example, when the timebase becomes synchronized and is ready to use, when a -timeout occurs, or when a large time leap is applied. This is the primary mechanism for -application components to know that ``VehicleTime`` is reliable and may be safely read. - -Unlike the PTP protocol data callbacks in VT3, ``VehicleTimeStatus`` carries no protocol -internals. It delivers the same status value already available via ``Now().Status()``, -but pushed proactively on every change rather than polled per call. - -The callback fires unconditionally on the first PTP status update received after -registration, and subsequently only when the flag set changes. Rate deviation is -excluded from the comparison. - -.. raw:: html - -
- -.. uml:: _assets/vehicle_time/vt4_status_subscription.puml - :alt: VT4 — Synchronization status subscription - -.. raw:: html - -
- -.. warning:: - - The ``VehicleTimeStatus`` callback is **not yet delivered**. Calling - ``Subscribe()`` compiles and runs without error, but the registered - callback will never be invoked. Delivery will be wired from a dedicated background - thread in a future change. - -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - - void MyService::WatchClockReadiness() - { - auto clock = score::time::VehicleClock::GetInstance(); - - clock.Subscribe( - [this](const score::time::VehicleTimeStatus& status) { - if (status.IsReliable()) { - OnClockReady(); - } else if (status.HasBeenSynchronized()) { - OnClockDegraded(); - } else { - OnClockUnavailable(); - } - }); - } - - void MyService::Shutdown() - { - auto clock = score::time::VehicleClock::GetInstance(); - clock.Unsubscribe(); - } - -.. warning:: - - Callbacks are invoked on the **backend thread** — the callback implementation must be - thread-safe. - -VT5 — Status flag inspection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When mapping ``VehicleTime`` status to diagnostic outputs such as DTC bitmasks, use -``IsFlagActive(flag)`` with the ``VehicleTime::StatusFlag`` enum to access individual -bits. For the higher-level reliability predicates (``IsReliable()``, -``HasBeenSynchronized()``), see the status flag table and method descriptions in VT1. - -.. raw:: html - -
- -.. uml:: _assets/vehicle_time/vt5_diagnostics.puml - :alt: VT5 — Status flag inspection - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - #include - - using SvtFlag = score::time::VehicleTime::StatusFlag; - - static const std::map kDiagBitMap = { - {SvtFlag::kSynchronized, 0x01U}, - {SvtFlag::kTimeOut, 0x02U}, - {SvtFlag::kTimeLeapFuture, 0x04U}, - {SvtFlag::kTimeLeapPast, 0x08U}, - {SvtFlag::kUnknown, 0x80U}, - }; - - uint8_t BuildDiagByte(const score::time::VehicleTimeStatus& status) - { - uint8_t result{0U}; - for (const auto& entry : kDiagBitMap) { - if (status.IsFlagActive(entry.first)) { - result |= entry.second; - } - } - return result; - } - -HighResSteadyTime -~~~~~~~~~~~~~~~~~ - -HT1 — Time polling -^^^^^^^^^^^^^^^^^^^ - -Measure a short code-path latency or compute a tight deadline where call overhead matters. -``HighResSteadyClock`` avoids a kernel call on QNX by reading the hardware cycle counter -directly — the same ``Now()`` snapshot pattern used for all clock domains, with no status -check required. - -.. raw:: html - -
- -.. uml:: _assets/high_res_steady_time/ht1_polling.puml - :alt: HT1 — HighResSteadyTime time polling - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/high_res_steady_time/src/high_res_steady_clock.h" - #include - - void MyValidator::CheckDeadline() - { - auto hirs = score::time::HighResSteadyClock::GetInstance(); - const auto deadline = hirs.Now().TimePoint() + std::chrono::seconds{3}; - - // ... do work ... - - if (hirs.Now().TimePoint() > deadline) { - HandleDeadlineExceeded(); - } - } - -SteadyClock -~~~~~~~~~~~ - -ST1 — Time polling -^^^^^^^^^^^^^^^^^^^ - -Measure elapsed time between two points, or derive a deadline, using a clock that is -guaranteed never to go backward regardless of external time adjustments. - -.. raw:: html - -
- -.. uml:: _assets/steady_clock/st1_polling.puml - :alt: ST1 — SteadyClock time polling - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/steady_time/src/steady_clock.h" - - void MyComponent::MeasureElapsed() - { - auto clock = score::time::SteadyClock::GetInstance(); - const auto start = clock.Now().TimePoint(); - - // ... do work ... - - const auto elapsed = clock.Now().TimePoint() - start; - } - -SystemClock -~~~~~~~~~~~ - -SC1 — Time polling -^^^^^^^^^^^^^^^^^^^ - -Record a wall-clock timestamp for logging or audit trails where the absolute calendar -time matters. Do not use ``SystemClock`` for elapsed time or timeouts — the timepoint -may jump. - -.. raw:: html - -
- -.. uml:: _assets/system_clock/sc1_polling.puml - :alt: SC1 — SystemClock time polling - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/system_time/src/system_clock.h" - #include - - void MyLogger::LogEvent() - { - auto clock = score::time::SystemClock::GetInstance(); - const auto wall_time = clock.Now().TimePoint(); - const auto t = std::chrono::system_clock::to_time_t(wall_time); - LOG_INFO("Event at: {}", std::ctime(&t)); - } - -Testing (all domains) -~~~~~~~~~~~~~~~~~~~~~ - -Both test utilities work with any clock domain. Choose based on how the SUT obtains the -clock: - -+------------------------------+-------------------------------------------------------+ -| Utility | When to use | -+==============================+=======================================================+ -| ``ScopedClockOverride`` | SUT calls ``Clock::GetInstance()`` internally. | -| | Scope-bound RAII guard — automatically restored on | -| | destruction. | -+------------------------------+-------------------------------------------------------+ -| ``ClockTestFactory`` | SUT accepts ``Clock`` as a constructor argument. | -| | No global state is touched — safe for parallel tests. | -+------------------------------+-------------------------------------------------------+ - -T1 — ScopedClockOverride (scope-bound override) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. raw:: html - -
- -.. uml:: _assets/testing/t1_scoped_override.puml - :alt: T1 — ScopedClockOverride - -.. raw:: html - -
- -.. warning:: - - ``ScopedClockOverride`` modifies a **process-wide singleton**. Any ``cc_test`` target that - uses it must declare ``tags = ["exclusive", "unit"]`` in its Bazel BUILD file. Without - ``"exclusive"``, Bazel may run multiple tests in the same process shard in parallel, causing - one test's mock to corrupt another test's clock state and producing flaky failures. - - .. code-block:: python - - cc_test( - name = "my_service_test", - srcs = ["my_service_test.cpp"], - tags = ["exclusive", "unit"], - deps = [...], - ) - - If the SUT receives the clock via constructor injection instead, use - ``ClockTestFactory`` (T2) — it does **not** touch the global singleton and - requires no special tag. - -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - #include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h" - - TEST(MyServiceTest, ReportsReliableTime) - { - auto mock = std::make_shared(); - EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */)); - - const score::time::test_utils::ScopedClockOverride guard{mock}; - - MyService svc; - svc.DoSomething(); // calls VehicleClock::GetInstance() internally - } - -T2 — ClockTestFactory (constructor injection) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. raw:: html - -
- -.. uml:: _assets/testing/t2_test_factory.puml - :alt: T2 — ClockTestFactory - -.. raw:: html - -
- -.. code-block:: cpp - - #include "score/time/vehicle_time/src/vehicle_clock.h" - #include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h" - - TEST(MyServiceTest, ReportsReliableTime) - { - auto mock = std::make_shared(); - EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */)); - - const auto clock = - score::time::test_utils::ClockTestFactory::Make(mock); - - MyService svc{clock}; - svc.DoSomething(); - } - -Bazel dependencies ------------------- - -Choose the target that matches your use case: - -.. list-table:: - :header-rows: 1 - :widths: 55 45 - - * - Target - - When to use - * - ``//score/time/vehicle_time:vehicle_time`` - - Production binary — includes real PTP backend - * - ``//score/time/vehicle_time:vehicle_time_mock`` - - Unit test — ``VehicleClockBackendMock`` + scope-bound override or constructor injection - * - ``//score/time/clock:clock_test_utils`` - - Test utilities — ``ScopedClockOverride`` and ``ClockTestFactory`` (``testonly``; - must not appear in production deps). Tests using ``ScopedClockOverride`` must - also add ``tags = ["exclusive", "unit"]`` to their ``cc_test`` target; tests - using ``ClockTestFactory`` (constructor injection) do not need this tag. - * - ``//score/time/vehicle_time:interface`` - - Header-only, no backend — interface/type usage only; required when subscribing - to ``VehicleTimeStatus`` (provides the type definition) - * - ``//score/time/high_res_steady_time:high_res_steady_time`` - - Production binary — HIRS steady clock - * - ``//score/time/high_res_steady_time:high_res_steady_time_mock`` - - Unit test — ``HighResSteadyClockBackendMock`` + scope-bound override or constructor injection - * - ``//score/time/high_res_steady_time:interface`` - - Header-only - * - ``//score/time/steady_time:steady_time`` - - ``std::chrono::steady_clock`` wrapper - * - ``//score/time/system_time:system_time`` - - ``std::chrono::system_clock`` wrapper - * - ``//score/time/ptp:ptp_types`` - - PTP notification data types (``TimeSlaveSyncData``, ``PDelayMeasurementData``) - -Design decisions ----------------- - -Single entry point — no factory classes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Classical time APIs expose a factory or manager object that clients instantiate and -configure before reading time (e.g. ``TimeBaseManager tm; tm.GetCurrentTime(kVehicleBase)``). -``score::time`` removes that level of indirection: ``Clock::GetInstance()`` is the -sole entry point, and the production backend is chosen at **link time** by the Bazel -alias target. - -Compile-time domain selection over runtime integer selector -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``score::time`` addresses the same problem domain as time-base management modules found -in automotive middleware stacks: reading a time snapshot, inspecting synchronization -quality flags, waiting for clock availability, and subscribing to synchronization events. - -The key design upgrade over typical C-style automotive APIs is replacing the **runtime -integer time-base selector** with a **compile-time ``Tag`` template parameter**. This -gives full type-safety and zero runtime dispatch for time-domain selection: a component -that depends on ``Clock`` simply cannot accidentally read -``VehicleTime`` at runtime — the compiler enforces the distinction. All other -structural concepts (composite snapshot result, quality status flags, layered backend -hiding) follow the same principles as established automotive time synchronization -practice, expressed in modern C++. - -Opacity of ``details/`` -~~~~~~~~~~~~~~~~~~~~~~~ - -Virtual dispatch exists solely to enable GMock test doubles. The vtable is hidden inside -``details/`` — public headers never declare a virtual function. ``Clock`` is a plain -value type. The ``*_mock.h`` headers are the only public headers permitted to include -``details/`` internals. - -``ClockSnapshot`` — immutable composite result -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Classical time APIs return a raw timestamp and require a separate call to retrieve the -synchronization status, or expose a struct with public mutable data members and -raw-integer constructors. ``ClockSnapshot`` is a simple -immutable two-field struct: - -.. code-block:: cpp - - auto snap = VehicleClock::GetInstance().Now(); - snap.TimePoint(); // std::chrono::time_point - snap.Status(); // VehicleTimeStatus — returned by value - -Generic code works for all clock domains: - -.. code-block:: cpp - - template - auto Age(score::time::Clock& clk, - typename score::time::Clock::time_point ref) - { - return clk.Now().TimePoint() - ref; - } - -``Subscribe`` — uniform subscription API -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Classical event-callback APIs require a separate named setter and unsetter for each event -type (e.g. ``SetSyncDataCallback``, ``UnsetSyncDataCallback``, ``SetPDelayCallback``, -``UnsetPDelayCallback``). ``Clock`` exposes a single pair ``Subscribe()`` / -``Unsubscribe()`` templated on the event type. -The ``SubscriptionHook`` specialisation bridges to the named virtual methods -on the backend interface — which must remain non-template (C++ forbids virtual templates). - -Extending with a new clock domain ----------------------------------- - -Adding a new time domain (e.g. ``SdatTime``) requires only new files — no existing file -is modified: - -1. Create ``score/time/sdat_time/sdat_time.h`` — tag struct with ``Duration`` and - ``Timepoint``, and a domain-specific ``SdatTimeStatus`` struct containing whatever - metadata the backend needs to expose (flags via ``ClockStatus``, continuous - fields, or both). -2. Create ``score/time/sdat_time/details/sdat_time_iface.h`` — pure-virtual backend interface. -3. Create ``score/time/sdat_time/details/sdat_prod_impl.cpp`` — production backend. -4. Add ``ClockTraits`` specialisation in ``score/time/sdat_time/sdat_clock.h``. -5. Create ``score/time/sdat_time/sdat_clock_mock.h`` — GMock test double. -6. Add ``sdat_time``, ``sdat_time_mock``, ``interface`` aliases in ``score/time/sdat_time/BUILD``. -7. *(If the new domain requires explicit initialisation)* Add a full specialisation of - ``InitializationHook`` in ``sdat_clock.h`` supplying - ``static bool CallInit(Backend&) noexcept``. This makes ``Clock::Init()`` - available at compile time without touching any existing files. -8. *(If the new domain requires readiness checking)* Add a full specialisation of - ``AvailabilityHook`` in ``sdat_clock.h`` supplying - ``static bool CallIsAvailable(const Backend&)`` and - ``static bool CallWaitUntilAvailable(const Backend&, stop_token, time_point)``. - This makes ``IsAvailable()`` and ``WaitUntilAvailable()`` available at compile time. diff --git a/docs/features/time/_assets/architecture_layers.puml b/score/time/docs/detailed_design/_assets/architecture_layers.puml similarity index 79% rename from docs/features/time/_assets/architecture_layers.puml rename to score/time/docs/detailed_design/_assets/architecture_layers.puml index 71c5dd4d..b7a038fb 100644 --- a/docs/features/time/_assets/architecture_layers.puml +++ b/score/time/docs/detailed_design/_assets/architecture_layers.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml architecture_layers title score::time — Architecture Layers diff --git a/docs/features/time/_assets/class_overview.puml b/score/time/docs/detailed_design/_assets/class_overview.puml similarity index 87% rename from docs/features/time/_assets/class_overview.puml rename to score/time/docs/detailed_design/_assets/class_overview.puml index 5d88ec18..586b6693 100644 --- a/docs/features/time/_assets/class_overview.puml +++ b/score/time/docs/detailed_design/_assets/class_overview.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml class_overview title score::time — Class Overview diff --git a/docs/features/time/_assets/high_res_steady_time/ht1_polling.puml b/score/time/docs/detailed_design/_assets/high_res_steady_time/ht1_polling.puml similarity index 76% rename from docs/features/time/_assets/high_res_steady_time/ht1_polling.puml rename to score/time/docs/detailed_design/_assets/high_res_steady_time/ht1_polling.puml index 19154bb6..162f438c 100644 --- a/docs/features/time/_assets/high_res_steady_time/ht1_polling.puml +++ b/score/time/docs/detailed_design/_assets/high_res_steady_time/ht1_polling.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml ht1_polling title HT1 — HighResSteadyTime: Time Polling diff --git a/docs/features/time/_assets/steady_clock/st1_polling.puml b/score/time/docs/detailed_design/_assets/steady_clock/st1_polling.puml similarity index 73% rename from docs/features/time/_assets/steady_clock/st1_polling.puml rename to score/time/docs/detailed_design/_assets/steady_clock/st1_polling.puml index 913479fd..2dcf0380 100644 --- a/docs/features/time/_assets/steady_clock/st1_polling.puml +++ b/score/time/docs/detailed_design/_assets/steady_clock/st1_polling.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml st1_polling title ST1 — SteadyClock: Time Polling diff --git a/docs/features/time/_assets/system_clock/sc1_polling.puml b/score/time/docs/detailed_design/_assets/system_clock/sc1_polling.puml similarity index 73% rename from docs/features/time/_assets/system_clock/sc1_polling.puml rename to score/time/docs/detailed_design/_assets/system_clock/sc1_polling.puml index f79ce5a4..b42a811c 100644 --- a/docs/features/time/_assets/system_clock/sc1_polling.puml +++ b/score/time/docs/detailed_design/_assets/system_clock/sc1_polling.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml sc1_polling title SC1 — SystemClock: Time Polling diff --git a/docs/features/time/_assets/testing/t1_scoped_override.puml b/score/time/docs/detailed_design/_assets/testing/t1_scoped_override.puml similarity index 79% rename from docs/features/time/_assets/testing/t1_scoped_override.puml rename to score/time/docs/detailed_design/_assets/testing/t1_scoped_override.puml index c4eee024..c35bf73a 100644 --- a/docs/features/time/_assets/testing/t1_scoped_override.puml +++ b/score/time/docs/detailed_design/_assets/testing/t1_scoped_override.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml t1_scoped_override title T1 — Testing: ScopedClockOverride (Scope-Bound Override) diff --git a/docs/features/time/_assets/testing/t2_test_factory.puml b/score/time/docs/detailed_design/_assets/testing/t2_test_factory.puml similarity index 75% rename from docs/features/time/_assets/testing/t2_test_factory.puml rename to score/time/docs/detailed_design/_assets/testing/t2_test_factory.puml index a4b85ac9..c6747c1a 100644 --- a/docs/features/time/_assets/testing/t2_test_factory.puml +++ b/score/time/docs/detailed_design/_assets/testing/t2_test_factory.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml t2_test_factory title T2 — Testing: ClockTestFactory (Constructor Injection) diff --git a/docs/features/time/_assets/vehicle_time/vt1_polling.puml b/score/time/docs/detailed_design/_assets/vehicle_time/vt1_polling.puml similarity index 78% rename from docs/features/time/_assets/vehicle_time/vt1_polling.puml rename to score/time/docs/detailed_design/_assets/vehicle_time/vt1_polling.puml index 5fe8d05d..9093d7b7 100644 --- a/docs/features/time/_assets/vehicle_time/vt1_polling.puml +++ b/score/time/docs/detailed_design/_assets/vehicle_time/vt1_polling.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml vt1_polling title VT1 — VehicleTime: Time Polling with Status Check diff --git a/docs/features/time/_assets/vehicle_time/vt2_availability.puml b/score/time/docs/detailed_design/_assets/vehicle_time/vt2_availability.puml similarity index 78% rename from docs/features/time/_assets/vehicle_time/vt2_availability.puml rename to score/time/docs/detailed_design/_assets/vehicle_time/vt2_availability.puml index 9bbea636..3b21bbb8 100644 --- a/docs/features/time/_assets/vehicle_time/vt2_availability.puml +++ b/score/time/docs/detailed_design/_assets/vehicle_time/vt2_availability.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml vt2_availability title VT2 — VehicleTime: Initialization and Readiness Check diff --git a/docs/features/time/_assets/vehicle_time/vt3_subscription.puml b/score/time/docs/detailed_design/_assets/vehicle_time/vt3_subscription.puml similarity index 84% rename from docs/features/time/_assets/vehicle_time/vt3_subscription.puml rename to score/time/docs/detailed_design/_assets/vehicle_time/vt3_subscription.puml index 5eaf5cf5..1baf80cf 100644 --- a/docs/features/time/_assets/vehicle_time/vt3_subscription.puml +++ b/score/time/docs/detailed_design/_assets/vehicle_time/vt3_subscription.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml vt3_subscription title VT3 — VehicleTime: Async PTP Protocol Data Subscription diff --git a/docs/features/time/_assets/vehicle_time/vt4_status_subscription.puml b/score/time/docs/detailed_design/_assets/vehicle_time/vt4_status_subscription.puml similarity index 77% rename from docs/features/time/_assets/vehicle_time/vt4_status_subscription.puml rename to score/time/docs/detailed_design/_assets/vehicle_time/vt4_status_subscription.puml index 71e32047..0cfe90c0 100644 --- a/docs/features/time/_assets/vehicle_time/vt4_status_subscription.puml +++ b/score/time/docs/detailed_design/_assets/vehicle_time/vt4_status_subscription.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml vt4_status_subscription title VT4 — VehicleTime: Synchronization Status Subscription diff --git a/docs/features/time/_assets/vehicle_time/vt5_diagnostics.puml b/score/time/docs/detailed_design/_assets/vehicle_time/vt5_diagnostics.puml similarity index 66% rename from docs/features/time/_assets/vehicle_time/vt5_diagnostics.puml rename to score/time/docs/detailed_design/_assets/vehicle_time/vt5_diagnostics.puml index 52901206..35f72b1c 100644 --- a/docs/features/time/_assets/vehicle_time/vt5_diagnostics.puml +++ b/score/time/docs/detailed_design/_assets/vehicle_time/vt5_diagnostics.puml @@ -1,3 +1,16 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* + @startuml vt5_diagnostics title VT5 — VehicleTime: Status Flag Inspection diff --git a/score/time/docs/detailed_design/detailed_design.rst b/score/time/docs/detailed_design/detailed_design.rst deleted file mode 100644 index 43469beb..00000000 --- a/score/time/docs/detailed_design/detailed_design.rst +++ /dev/null @@ -1,96 +0,0 @@ -.. - # ******************************************************************************* - # Copyright (c) 2026 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - -.. _time_detailed_design: - -Time Detailed Design -==================== - -.. document:: Time Detailed Design - :id: doc__time_detailed_design - :status: draft - :version: 1 - :safety: ASIL_B - :security: NO - :realizes: wp__sw_implementation - :tags: time - -.. note:: - Work in progress: structure, titles, and needs IDs only. Content and req/comp/feat traceability links to follow in later PRs. - -.. attention:: - The above directive must be updated according to your Component. - - - Adjust ``status`` to be ``valid`` - - Adjust ``safety`` and ``tags`` according to your needs - -Detailed Design for Time -========================= - -Description ------------ - -| Design Decisions - For the documentation of the decision the :need:`gd_temp__change_decision_record` can be used. -| Design Constraints - -Example: - - - component is split into two units unit1 and unit2 based on single responsibility principle. - - unit2 is injected to unit1 one via dependency injection for testability. - -Rationale Behind Decomposition into Units -****************************************** -| mandatory: a motivation for the decomposition into one or more units. - -.. note:: Reason for split into multiple units could be- - - Based on design principles like SOLID,DRY etc - - Based on design pattern's etc. - -Static Diagrams for Unit Interactions -------------------------------------- - -A static view provides an overview of the units and their relationships using -UML 2.0 notations (e.g. class diagrams, component diagrams). Use ``.. uml::`` -or ``.. image::`` directives to include the diagram. - -.. uml:: dd_example_ex_sta.puml - -Dynamic Diagrams for Unit Interactions (optional) --------------------------------------------------- - -A dynamic view illustrates how the units within a component interact over their -interfaces to fulfill a specific use case or functionality. It is optional when the -component's behaviour is straightforward and can be understood from the static view -and interface documentation alone. - -Use standard UML behavioural diagrams (sequence diagrams, state machine diagrams) -with ``.. uml::`` or ``.. image::`` directives. - -.. uml:: dd_example_ex_dyn.puml - -Units within the Component --------------------------- - -The relationship between a unit and its parent component is established implicitly -through the file path. Each component has its own directory, and units residing -within that directory belong to it. The unit's attributes and behaviour are documented -in the source code itself. A separate static diagram per unit is not required. - -Interface documentation of a software unit is part of the source code (e.g. public -API headers, trait definitions, or documented function signatures). - -Example: - -- unit1: implements the main logic (see source code for details) -- unit2: injected into unit1 via dependency injection for testability diff --git a/score/time/docs/detailed_design/index.rst b/score/time/docs/detailed_design/index.rst index e6e47f11..9a7a9b45 100644 --- a/score/time/docs/detailed_design/index.rst +++ b/score/time/docs/detailed_design/index.rst @@ -12,28 +12,814 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -.. _time_detailed_design_index: +.. _time_detailed_design: Detailed Design ############### +.. document:: score::time Detailed Design + :id: doc__time_detailed_design + :status: draft + :version: 1 + :safety: ASIL_B + :security: NO + :realizes: wp__detailed_design[version==1] + :tags: time, detailed_design + +Description +----------- + +``score::time`` provides a **unified, clock-domain-agnostic API** for reading time +snapshots, checking clock readiness, and subscribing to clock synchronization events — +all through a single template wrapper ``Clock``. + +The design separates two concerns: + +1. **What kind of time** — expressed as a *tag struct* (``VehicleTime``, + ``HighResSteadyTime``, ``std::chrono::steady_clock``, + ``std::chrono::system_clock``). +2. **How to access it** — always via ``Clock::GetInstance()``; clock-domain + selection is a compile-time decision, enforced by the type system. + +Clock Domains +~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 20 30 50 + + * - Clock alias + - Tag + - Status concept + * - ``VehicleClock`` + - ``VehicleTime`` + - ``VehicleTimeStatus`` + * - ``HighResSteadyClock`` + - ``HighResSteadyTime`` + - ``NoStatus`` + * - ``SteadyClock`` + - ``std::chrono::steady_clock`` + - ``NoStatus`` + * - ``SystemClock`` + - ``std::chrono::system_clock`` + - ``NoStatus`` + +**VehicleTime** is a PTP-synchronized timebase driven by the network Grand Master clock. +Each ``Now()`` call returns a ``ClockSnapshot`` that bundles the timepoint with a +``VehicleTimeStatus`` — a set of quality flags (``kSynchronized``, ``kTimeOut``, +``kTimeLeapFuture``, ``kTimeLeapPast``) and a rate-deviation measurement. + +**HighResSteadyTime** is a monotonic, nanosecond-resolution clock optimized for +low-overhead timing. On QNX the backend reads the hardware cycle counter directly via +``ClockCycles()`` — no kernel call, no scheduler interaction. On Linux it delegates to +``std::chrono::high_resolution_clock``. + +**SteadyClock** wraps ``std::chrono::steady_clock`` (POSIX ``CLOCK_MONOTONIC``). +Monotonic and never goes backward, making it the standard choice for measuring elapsed +time and computing timeouts. + +**SystemClock** wraps ``std::chrono::system_clock`` (POSIX ``CLOCK_REALTIME``). +Represents wall-clock (UTC-based) time and may be adjusted or jump forward or backward. +Use when a calendar timestamp is needed — not for measuring elapsed time or computing timeouts. + +Architecture +------------ + +.. raw:: html + +
+ +.. uml:: _assets/architecture_layers.puml + :alt: Architecture layer diagram + +.. raw:: html + +
+ +The library has three layers: + +- **Public headers** under ``score/time//`` — tag structs and callback types that + clients include directly. +- **Framework layer** under ``score/time/clock/`` — the ``Clock`` wrapper, traits, + subscription hooks, and the test utilities (``clock_test_utils`` Bazel target). This + layer has no backend dependency. + The ``clock_test_utils`` target (``scoped_clock_override.h``, ``clock_test_factory.h``) + is ``testonly`` and must not appear in production deps. +- **Internal** under ``score/time//details/`` — pure-virtual backend interfaces and + production implementations. *Clients must never include anything from a* ``details/`` + *subfolder.* + +Static View +~~~~~~~~~~~ + +.. raw:: html + +
+ +.. uml:: _assets/class_overview.puml + :alt: Class overview + :width: 100% + :align: center + +.. raw:: html + +
+ +Core Types +~~~~~~~~~~ + +``Clock`` +^^^^^^^^^^^^^^ + +The sole user-facing handle for a clock domain. A cheaply copyable value type — all +copies share the same backend instance via a ``shared_ptr``. Its API surface is +intentionally uniform across all domains: ``Now()`` for reading, ``Subscribe`` / +``Unsubscribe`` for events, ``Init`` / ``IsAvailable`` / ``WaitUntilAvailable`` for +readiness — with opt-in capabilities gated at compile time by the hook templates. + +``ClockTraits`` +^^^^^^^^^^^^^^^^^^^^ + +The domain registration point. The primary template is intentionally incomplete; each +clock domain provides a full explicit specialisation that binds together the backend +type, duration, timepoint, snapshot, and the ``CallNow`` factory function. No existing +file is modified when a new domain is added. + +``ClockSnapshot`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The immutable return value of every ``Now()`` call. Bundles the timepoint and its +quality metadata into a single atomic read — no separate status call is ever needed. +``TimepointT`` is ``std::chrono::time_point``, making different clock +domains' timepoints incompatible types so cross-domain arithmetic is a compile error. +``StatusT`` is the domain's chosen metadata type. + +``StatusT``, ``ClockStatus``, and ``NoStatus`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All domain-specific metadata lives in ``StatusT`` — ``ClockSnapshot`` itself is never +extended. Two building blocks are provided: ``NoStatus`` (zero-size placeholder for +always-ready clocks with no quality concept) and ``ClockStatus`` (generic +bitmask over a scoped flag enum). A domain may use either alone or compose them inside +a richer struct alongside continuous fields — as ``VehicleTimeStatus`` does with +``ClockStatus`` and ``double rate_deviation``. + +Capability Hooks +^^^^^^^^^^^^^^^^ + +Three SFINAE hook templates gate the optional capabilities of ``Clock``. Each +primary template is intentionally undefined — using an ungated capability on a domain +that has not opted in is a **compile error**, not a runtime failure: +``InitializationHook`` unlocks ``Init()``, ``AvailabilityHook`` unlocks +``IsAvailable()`` and ``WaitUntilAvailable()``, and ``SubscriptionHook`` +unlocks ``Subscribe`` / ``Unsubscribe`` for a specific event type. + +Design Decisions +---------------- + +Single Entry Point — No Factory Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classical time APIs expose a factory or manager object that clients instantiate and +configure before reading time (e.g. ``TimeBaseManager tm; tm.GetCurrentTime(kVehicleBase)``). +``score::time`` removes that level of indirection: ``Clock::GetInstance()`` is the +sole entry point, and the production backend is chosen at **link time** by the Bazel +alias target. + +Compile-Time Domain Selection Over Runtime Integer Selector +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``score::time`` addresses the same problem domain as time-base management modules found +in automotive middleware stacks: reading a time snapshot, inspecting synchronization +quality flags, waiting for clock availability, and subscribing to synchronization events. + +The key design upgrade over typical C-style automotive APIs is replacing the **runtime +integer time-base selector** with a **compile-time ``Tag`` template parameter**. This +gives full type-safety and zero runtime dispatch for time-domain selection: a component +that depends on ``Clock`` simply cannot accidentally read +``VehicleTime`` at runtime — the compiler enforces the distinction. All other +structural concepts (composite snapshot result, quality status flags, layered backend +hiding) follow the same principles as established automotive time synchronization +practice, expressed in modern C++. + +Opacity of ``details/`` +~~~~~~~~~~~~~~~~~~~~~~~ + +Virtual dispatch exists solely to enable GMock test doubles. The vtable is hidden inside +``details/`` — public headers never declare a virtual function. ``Clock`` is a plain +value type. The ``*_mock.h`` headers are the only public headers permitted to include +``details/`` internals. + +``ClockSnapshot`` — Immutable Composite Result +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classical time APIs return a raw timestamp and require a separate call to retrieve the +synchronization status, or expose a struct with public mutable data members and +raw-integer constructors. ``ClockSnapshot`` is a simple +immutable two-field struct: + +.. code-block:: cpp + + auto snap = VehicleClock::GetInstance().Now(); + snap.TimePoint(); // std::chrono::time_point + snap.Status(); // VehicleTimeStatus — returned by value + +Generic code works for all clock domains: + +.. code-block:: cpp + + template + auto Age(score::time::Clock& clk, + typename score::time::Clock::time_point ref) + { + return clk.Now().TimePoint() - ref; + } + +``Subscribe`` — Uniform Subscription API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classical event-callback APIs require a separate named setter and unsetter for each event +type (e.g. ``SetSyncDataCallback``, ``UnsetSyncDataCallback``, ``SetPDelayCallback``, +``UnsetPDelayCallback``). ``Clock`` exposes a single pair ``Subscribe()`` / +``Unsubscribe()`` templated on the event type. +The ``SubscriptionHook`` specialisation bridges to the named virtual methods +on the backend interface — which must remain non-template (C++ forbids virtual templates). + +Extending with a New Clock Domain +---------------------------------- + +Adding a new time domain (e.g. ``SdatTime``) requires only new files — no existing file +is modified: + +1. Create ``score/time/sdat_time/sdat_time.h`` — tag struct with ``Duration`` and + ``Timepoint``, and a domain-specific ``SdatTimeStatus`` struct containing whatever + metadata the backend needs to expose (flags via ``ClockStatus``, continuous + fields, or both). +2. Create ``score/time/sdat_time/details/sdat_time_iface.h`` — pure-virtual backend interface. +3. Create ``score/time/sdat_time/details/sdat_prod_impl.cpp`` — production backend. +4. Add ``ClockTraits`` specialisation in ``score/time/sdat_time/sdat_clock.h``. +5. Create ``score/time/sdat_time/sdat_clock_mock.h`` — GMock test double. +6. Add ``sdat_time``, ``sdat_time_mock``, ``interface`` aliases in ``score/time/sdat_time/BUILD``. +7. *(If the new domain requires explicit initialisation)* Add a full specialisation of + ``InitializationHook`` in ``sdat_clock.h`` supplying + ``static bool CallInit(Backend&) noexcept``. This makes ``Clock::Init()`` + available at compile time without touching any existing files. +8. *(If the new domain requires readiness checking)* Add a full specialisation of + ``AvailabilityHook`` in ``sdat_clock.h`` supplying + ``static bool CallIsAvailable(const Backend&)`` and + ``static bool CallWaitUntilAvailable(const Backend&, stop_token, time_point)``. + This makes ``IsAvailable()`` and ``WaitUntilAvailable()`` available at compile time. + +Use Cases +--------- + +VehicleTime +~~~~~~~~~~~ + +VT1 — Time Polling with Status Check +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Obtain a snapshot and inspect the synchronization quality before using the time value. +``Now()`` returns a single immutable ``ClockSnapshot`` — the timepoint and its +``VehicleTimeStatus`` are always fetched together, with no separate status call needed. + +.. raw:: html + +
+ +.. uml:: _assets/vehicle_time/vt1_polling.puml + :alt: VT1 — Time polling with status check + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + + void MyComponent::CheckTime() + { + auto clock = score::time::VehicleClock::GetInstance(); + auto snapshot = clock.Now(); + + if (snapshot.Status().IsReliable()) { + auto tp = snapshot.TimePoint(); + // use tp ... + } else if (snapshot.Status().IsFlagActive( + score::time::VehicleTime::StatusFlag::kTimeOut)) { + HandleTimeout(); + } + } + .. note:: - Work in progress: page structure only, content to follow in later PRs. -.. attention:: + ``Init()`` must be called once during application startup before ``Now()`` is expected + to return synchronized data (see VT2). Without it, ``Now()`` returns a snapshot with + no flags set (``IsConsistent()`` returns ``false``). + +**Status flags:** + ++---------------------------+--------------------------------------------------------------+ +| Flag | Meaning | ++===========================+==============================================================+ +| ``kSynchronized`` | Synchronized at least once to the PTP Grand Master | ++---------------------------+--------------------------------------------------------------+ +| ``kTimeOut`` | No sync message received within the configured time window | ++---------------------------+--------------------------------------------------------------+ +| ``kTimeLeapFuture`` | A large forward adjustment was applied | ++---------------------------+--------------------------------------------------------------+ +| ``kTimeLeapPast`` | A large backward adjustment was applied | ++---------------------------+--------------------------------------------------------------+ + +``VehicleTimeStatus::IsReliable()`` returns ``true`` only when ``kSynchronized`` is set +**and** none of ``{kTimeOut, kTimeLeapFuture, kTimeLeapPast}`` is set. +``VehicleTimeStatus::HasBeenSynchronized()`` returns ``true`` whenever ``kSynchronized`` +has been set at least once during this lifecycle, regardless of current fault flags. +``VehicleTimeStatus::IsConsistent()`` checks that the flag combination is internally +valid (at least one flag set, and not both leap flags simultaneously). + +These three methods belong to ``VehicleTimeStatus`` and encode VehicleTime-domain +semantics. ``ClockStatus`` itself exposes only generic bit-manipulation +(``IsFlagActive``, ``IsAnyOfFlagsActive``, ``AddFlag``) and the domain-specific +``PrintTo()`` specialization. + +VT2 — Initialization and Readiness Check +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``VehicleTime`` requires an explicit ``Init()`` call to open the IPC channel to the +:ref:`time_daemon_detailed_design` before any time data becomes available. Until ``Init()`` returns ``true``, +``Now()`` returns a snapshot with no flags set (``IsConsistent()`` returns ``false``) and ``IsAvailable()`` returns +``false``. + +After a successful ``Init()``, ``IsAvailable()`` returns ``true`` immediately. The +non-blocking ``IsAvailable()`` probe and the blocking ``WaitUntilAvailable()`` are useful +when ``Init()`` is retried on a background thread. + +.. raw:: html + +
+ +.. uml:: _assets/vehicle_time/vt2_availability.puml + :alt: VT2 — Initialization and readiness check + +.. raw:: html + +
+ +**Simple startup (same thread):** + +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + + bool MyService::Startup() + { + auto clock = score::time::VehicleClock::GetInstance(); + if (!clock.Init()) { + LOG_ERROR("VehicleTime: failed to open IPC channel"); + return false; + } + auto snapshot = clock.Now(); + // ... + return true; + } + +**Blocking wait when Init is retried from a background thread:** + +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + #include + #include + + void MyService::WaitForClock(const score::cpp::stop_token& stop) + { + auto clock = score::time::VehicleClock::GetInstance(); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{30}; + if (!clock.WaitUntilAvailable(stop, deadline)) { + LOG_ERROR("VehicleTime did not become available within 30 s"); + return; + } + auto snapshot = clock.Now(); + // ... + } + +.. note:: + + ``Init()``, ``IsAvailable()``, and ``WaitUntilAvailable()`` are **only available on + clock domains that require explicit initialisation** (currently ``VehicleTime``). + Calling them on ``HighResSteadyTime``, ``SteadyClock``, or ``SystemClock`` is a **compile + error** — those clocks are always ready. + +VT3 — Async PTP Protocol Data Subscription +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``VehicleTime`` exposes two PTP protocol data callbacks, intended primarily for +diagnostics and PTP data sanity checks: + +- ``TimeSlaveSyncData`` — fired on each PTP Sync/Follow_Up message pair; + carries the offset, rate correction, and raw timestamps computed by the TimeSlave. +- ``PDelayMeasurementData`` — fired when a peer-delay measurement cycle + completes; carries the measured peer delay and associated timestamps. + +.. raw:: html + +
+ +.. uml:: _assets/vehicle_time/vt3_subscription.puml + :alt: VT3 — Async PTP protocol data subscription + +.. raw:: html + +
+ +.. warning:: + + Both PTP data callbacks (``TimeSlaveSyncData`` and ``PDelayMeasurementData``) are + **not yet delivered**. Calling ``Subscribe<...>()`` compiles and runs without error, + but the registered callbacks will never be invoked. Delivery will be wired from a + dedicated background thread in a future change. + +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + #include "score/time/ptp/src/time_slave_sync_data.h" + #include "score/time/ptp/src/pdelay_measurement_data.h" + + void MyDiagHandler::RegisterCallbacks() + { + auto clock = score::time::VehicleClock::GetInstance(); + + clock.Subscribe>( + [this](const auto& data) { OnTimeSyncData(data); }); + + clock.Subscribe>( + [this](const auto& data) { OnPDelayData(data); }); + } + + void MyDiagHandler::Shutdown() + { + auto clock = score::time::VehicleClock::GetInstance(); + clock.Unsubscribe>(); + clock.Unsubscribe>(); + } + +.. warning:: + + Callbacks are invoked on the **backend thread** — the callback implementation must be + thread-safe. + +VT4 — Synchronization Status Subscription +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Subscribe to ``VehicleTimeStatus`` changes to react when the clock synchronization state +changes — for example, when the timebase becomes synchronized and is ready to use, when a +timeout occurs, or when a large time leap is applied. This is the primary mechanism for +application components to know that ``VehicleTime`` is reliable and may be safely read. + +Unlike the PTP protocol data callbacks in VT3, ``VehicleTimeStatus`` carries no protocol +internals. It delivers the same status value already available via ``Now().Status()``, +but pushed proactively on every change rather than polled per call. + +The callback fires unconditionally on the first PTP status update received after +registration, and subsequently only when the flag set changes. Rate deviation is +excluded from the comparison. + +.. raw:: html + +
+ +.. uml:: _assets/vehicle_time/vt4_status_subscription.puml + :alt: VT4 — Synchronization status subscription + +.. raw:: html + +
+ +.. warning:: + + The ``VehicleTimeStatus`` callback is **not yet delivered**. Calling + ``Subscribe()`` compiles and runs without error, but the registered + callback will never be invoked. Delivery will be wired from a dedicated background + thread in a future change. + +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + + void MyService::WatchClockReadiness() + { + auto clock = score::time::VehicleClock::GetInstance(); + + clock.Subscribe( + [this](const score::time::VehicleTimeStatus& status) { + if (status.IsReliable()) { + OnClockReady(); + } else if (status.HasBeenSynchronized()) { + OnClockDegraded(); + } else { + OnClockUnavailable(); + } + }); + } + + void MyService::Shutdown() + { + auto clock = score::time::VehicleClock::GetInstance(); + clock.Unsubscribe(); + } + +.. warning:: + + Callbacks are invoked on the **backend thread** — the callback implementation must be + thread-safe. + +VT5 — Status Flag Inspection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When mapping ``VehicleTime`` status to diagnostic outputs such as DTC bitmasks, use +``IsFlagActive(flag)`` with the ``VehicleTime::StatusFlag`` enum to access individual +bits. For the higher-level reliability predicates (``IsReliable()``, +``HasBeenSynchronized()``), see the status flag table and method descriptions in VT1. + +.. raw:: html + +
+ +.. uml:: _assets/vehicle_time/vt5_diagnostics.puml + :alt: VT5 — Status flag inspection + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + #include + + using SvtFlag = score::time::VehicleTime::StatusFlag; + + static const std::map kDiagBitMap = { + {SvtFlag::kSynchronized, 0x01U}, + {SvtFlag::kTimeOut, 0x02U}, + {SvtFlag::kTimeLeapFuture, 0x04U}, + {SvtFlag::kTimeLeapPast, 0x08U}, + {SvtFlag::kUnknown, 0x80U}, + }; + + uint8_t BuildDiagByte(const score::time::VehicleTimeStatus& status) + { + uint8_t result{0U}; + for (const auto& entry : kDiagBitMap) { + if (status.IsFlagActive(entry.first)) { + result |= entry.second; + } + } + return result; + } + +HighResSteadyTime +~~~~~~~~~~~~~~~~~ + +HT1 — Time Polling +^^^^^^^^^^^^^^^^^^^ + +Measure a short code-path latency or compute a tight deadline where call overhead matters. +``HighResSteadyClock`` avoids a kernel call on QNX by reading the hardware cycle counter +directly — the same ``Now()`` snapshot pattern used for all clock domains, with no status +check required. + +.. raw:: html + +
+ +.. uml:: _assets/high_res_steady_time/ht1_polling.puml + :alt: HT1 — HighResSteadyTime time polling + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/high_res_steady_time/src/high_res_steady_clock.h" + #include + + void MyValidator::CheckDeadline() + { + auto hirs = score::time::HighResSteadyClock::GetInstance(); + const auto deadline = hirs.Now().TimePoint() + std::chrono::seconds{3}; + + // ... do work ... + + if (hirs.Now().TimePoint() > deadline) { + HandleDeadlineExceeded(); + } + } + +SteadyClock +~~~~~~~~~~~ + +ST1 — Time Polling +^^^^^^^^^^^^^^^^^^^ + +Measure elapsed time between two points, or derive a deadline, using a clock that is +guaranteed never to go backward regardless of external time adjustments. + +.. raw:: html + +
+ +.. uml:: _assets/steady_clock/st1_polling.puml + :alt: ST1 — SteadyClock time polling + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/steady_time/src/steady_clock.h" + + void MyComponent::MeasureElapsed() + { + auto clock = score::time::SteadyClock::GetInstance(); + const auto start = clock.Now().TimePoint(); + + // ... do work ... + + const auto elapsed = clock.Now().TimePoint() - start; + } + +SystemClock +~~~~~~~~~~~ + +SC1 — Time Polling +^^^^^^^^^^^^^^^^^^^ + +Record a wall-clock timestamp for logging or audit trails where the absolute calendar +time matters. Do not use ``SystemClock`` for elapsed time or timeouts — the timepoint +may jump. + +.. raw:: html + +
+ +.. uml:: _assets/system_clock/sc1_polling.puml + :alt: SC1 — SystemClock time polling + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/system_time/src/system_clock.h" + #include + + void MyLogger::LogEvent() + { + auto clock = score::time::SystemClock::GetInstance(); + const auto wall_time = clock.Now().TimePoint(); + const auto t = std::chrono::system_clock::to_time_t(wall_time); + LOG_INFO("Event at: {}", std::ctime(&t)); + } + +Testing (All Domains) +~~~~~~~~~~~~~~~~~~~~~ + +Both test utilities work with any clock domain. Choose based on how the SUT obtains the +clock: + ++------------------------------+-------------------------------------------------------+ +| Utility | When to use | ++==============================+=======================================================+ +| ``ScopedClockOverride`` | SUT calls ``Clock::GetInstance()`` internally. | +| | Scope-bound RAII guard — automatically restored on | +| | destruction. | ++------------------------------+-------------------------------------------------------+ +| ``ClockTestFactory`` | SUT accepts ``Clock`` as a constructor argument. | +| | No global state is touched — safe for parallel tests. | ++------------------------------+-------------------------------------------------------+ + +T1 — ScopedClockOverride (Scope-Bound Override) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. raw:: html + +
+ +.. uml:: _assets/testing/t1_scoped_override.puml + :alt: T1 — ScopedClockOverride + +.. raw:: html + +
+ +.. warning:: + + ``ScopedClockOverride`` modifies a **process-wide singleton**. Any ``cc_test`` target that + uses it must declare ``tags = ["exclusive", "unit"]`` in its Bazel BUILD file. Without + ``"exclusive"``, Bazel may run multiple tests in the same process shard in parallel, causing + one test's mock to corrupt another test's clock state and producing flaky failures. + + .. code-block:: python + + cc_test( + name = "my_service_test", + srcs = ["my_service_test.cpp"], + tags = ["exclusive", "unit"], + deps = [...], + ) + + If the SUT receives the clock via constructor injection instead, use + ``ClockTestFactory`` (T2) — it does **not** touch the global singleton and + requires no special tag. + +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + #include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h" + + TEST(MyServiceTest, ReportsReliableTime) + { + auto mock = std::make_shared(); + EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */)); + + const score::time::test_utils::ScopedClockOverride guard{mock}; + + MyService svc; + svc.DoSomething(); // calls VehicleClock::GetInstance() internally + } + +T2 — ClockTestFactory (Constructor Injection) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. raw:: html + +
+ +.. uml:: _assets/testing/t2_test_factory.puml + :alt: T2 — ClockTestFactory + +.. raw:: html + +
+ +.. code-block:: cpp + + #include "score/time/vehicle_time/src/vehicle_clock.h" + #include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h" + + TEST(MyServiceTest, ReportsReliableTime) + { + auto mock = std::make_shared(); + EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */)); - The detailed design document is optional and should be created if the design of the component is complex and cannot be easily understood from the architecture documentation and interface documentation alone. - But the inspection checklist for the implementation is mandatory. + const auto clock = + score::time::test_utils::ClockTestFactory::Make(mock); + MyService svc{clock}; + svc.DoSomething(); + } -Detail design example ---------------------- +Bazel Dependencies +------------------ -An example of documenting detailed design can be found in: +Choose the target that matches your use case: - .. toctree:: +.. list-table:: + :header-rows: 1 + :widths: 55 45 - detailed_design_example + * - Target + - When to use + * - ``//score/time/vehicle_time:vehicle_time`` + - Production binary — includes real PTP backend + * - ``//score/time/vehicle_time:vehicle_time_mock`` + - Unit test — ``VehicleClockBackendMock`` + scope-bound override or constructor injection + * - ``//score/time/clock:clock_test_utils`` + - Test utilities — ``ScopedClockOverride`` and ``ClockTestFactory`` (``testonly``; + must not appear in production deps). Tests using ``ScopedClockOverride`` must + also add ``tags = ["exclusive", "unit"]`` to their ``cc_test`` target; tests + using ``ClockTestFactory`` (constructor injection) do not need this tag. + * - ``//score/time/vehicle_time:interface`` + - Header-only, no backend — interface/type usage only; required when subscribing + to ``VehicleTimeStatus`` (provides the type definition) + * - ``//score/time/high_res_steady_time:high_res_steady_time`` + - Production binary — HIRS steady clock + * - ``//score/time/high_res_steady_time:high_res_steady_time_mock`` + - Unit test — ``HighResSteadyClockBackendMock`` + scope-bound override or constructor injection + * - ``//score/time/high_res_steady_time:interface`` + - Header-only + * - ``//score/time/steady_time:steady_time`` + - ``std::chrono::steady_clock`` wrapper + * - ``//score/time/system_time:system_time`` + - ``std::chrono::system_clock`` wrapper + * - ``//score/time/ptp:ptp_types`` + - PTP notification data types (``TimeSlaveSyncData``, ``PDelayMeasurementData``) Inspection Checklist -------------------- From 827ba252e95ce5da3837295177efdaff986cb51d Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Wed, 19 Aug 2026 16:40:47 +0100 Subject: [PATCH 2/3] Merge branch 'main' of https://github.com/etas-contrib/score_time into populate-architecture-docs-time --- BUILD | 23 ++++++++++ MODULE.bazel | 1 + MODULE.bazel.lock | 3 ++ docs/index.rst | 1 + docs/module/index.rst | 45 +++++++++++++++++++ score/time/BUILD | 7 +++ score/time/docs/index.rst | 2 +- score/time/docs/requirements/requirements.rst | 24 +++++----- score/time_daemon/BUILD | 7 +++ .../docs/requirements/requirements.rst | 24 +++++----- score/time_slave/BUILD | 7 +++ .../docs/detailed_design/detailed_design.rst | 4 +- .../time_slave/docs/detailed_design/index.rst | 2 +- score/ts_client/BUILD | 20 +++++++++ 14 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 docs/module/index.rst create mode 100644 score/ts_client/BUILD diff --git a/BUILD b/BUILD index 74d09995..b5f6ba4c 100644 --- a/BUILD +++ b/BUILD @@ -21,8 +21,31 @@ setup_starpls( ) docs( + bundles = [ + { + "bundle": "//score/time_slave:docs_bundle", + "mount_at": "time_slave", + "attach_to": "module/index", + }, + { + "bundle": "//score/time_daemon:docs_bundle", + "mount_at": "time_daemon", + "attach_to": "module/index", + }, + { + "bundle": "//score/ts_client:docs_bundle", + "mount_at": "ts_client", + "attach_to": "module/index", + }, + { + "bundle": "//score/time:docs_bundle", + "mount_at": "time", + "attach_to": "module/index", + }, + ], external_needs = [ "@score_process//:needs_json_file", + "@score_platform//:needs_json_file", ], project = "S-CORE Time", project_url = "https://eclipse-score.github.io/time", diff --git a/MODULE.bazel b/MODULE.bazel index db4d9edc..5c2c0f23 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -77,6 +77,7 @@ git_override( remote = "https://github.com/eclipse-score/score_cpp_policies.git", ) +bazel_dep(name = "score_platform", version = "0.6.4", dev_dependency = True) bazel_dep(name = "score_process", version = "2.0.3", dev_dependency = True) bazel_dep(name = "score_tooling", version = "1.2.0", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 9f375b4d..7b7d9cc2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1042,6 +1042,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.5.0/MODULE.bazel": "4cfe52fe8b8dbeaf7e87500036391da278f72f1c2b41b689ffdd4337196dd8fe", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.6.0/MODULE.bazel": "d5fbfed7b9bd65f10830e2290045dea639a8cfcaf9f9f0f7a1b12888c14e7d2b", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/6.0.0/MODULE.bazel": "ab2af2d8fab73e4512d2e2bd399a64d10c5c5463388322f7025637b13ec7585c", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/7.0.1/MODULE.bazel": "8ca16bc1143f4834e1ad061ddebb3b57dac966d46f890a07829f055dbb3a3d15", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/7.1.0/MODULE.bazel": "7d89729cc6a1cb7a13b9cfbf4bd84ace437451f5eb0873202586ac7cb288eff5", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/7.1.0/source.json": "2aebac074ccaef8aad11822d04a98e97d9aded5d851812075969618da8cf1996", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_format_checker/0.1.1/MODULE.bazel": "1acc254faa90e9f97b79ac69af25b6c21c561f8d6079914f6352b9b20d26bd37", @@ -1051,6 +1052,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_logging/0.2.1/source.json": "4742b24f2c7f3e504effd08edfa02df86e71745b345e601c7500008c7fb28c82", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.0/MODULE.bazel": "cc9eae86e76f2a930510ed6e50ec991bb5661687e24881685b39c322087adf6f", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.1/MODULE.bazel": "eb086ba99f9319371fbbd0a9252dfd27b0817039b88bd4d691602974b1ada005", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.6.4/MODULE.bazel": "dac9bdb1dd0934bd54549587d0d6d2db93c8298fa190fccd417fff4e0f4a7519", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.6.4/source.json": "a77419136e32cd79a51557dbbf67a73203d67b1b92102a3970720959f31b00fd", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.4/MODULE.bazel": "f74302cb90a7c4878db302276afae82966878099861dcfca3ef43256131dab52", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.5/MODULE.bazel": "ed17c232ebd65e9d50fd5c1832f90f95ffe95b2a1113d63a176295a2af64d111", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.0/MODULE.bazel": "97dd927309f87ecb73629725683028a5dbb37a49b1159c771292e6993569055b", diff --git a/docs/index.rst b/docs/index.rst index d7d50f3d..bf37b218 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,6 +43,7 @@ For a detailed concept and architectural design, please refer to the :doc:`time_ :caption: Contents: features/index + module/index Project Layout -------------- diff --git a/docs/module/index.rst b/docs/module/index.rst new file mode 100644 index 00000000..e2e437bc --- /dev/null +++ b/docs/module/index.rst @@ -0,0 +1,45 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Module +====== + +The S-CORE ``time`` module provides a unified API for accessing system, steady, high-resolution steady, and PTP-synchronized vehicle time. The module contains four components: a client library for application-facing access, Time Slave for PTP clock synchronization, ``ts_client`` for shared-memory IPC between Time Slave and Time Daemon, and Time Daemon for synchronization quality validation before serving Vehicle Time. + +.. code-block:: rst + + .. mod:: Time + :id: mod__time + :includes: comp__component_name_template + +Module View +----------- + +.. code-block:: rst + + .. mod_view_sta:: Time Module Static View + :id: mod_view_sta__time__time + :includes: comp__component_name_template + + .. needarch:: + :scale: 50 + :align: center + + {{ draw_module(need(), needs) }} + +Module Documents +---------------- + +.. toctree:: + :maxdepth: 1 diff --git a/score/time/BUILD b/score/time/BUILD index 1048941a..7f707a4b 100644 --- a/score/time/BUILD +++ b/score/time/BUILD @@ -13,6 +13,13 @@ load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") load("@score_baselibs//third_party/itf:py_unittest_qnx_test.bzl", "py_unittest_qnx_test") +load("@score_docs_as_code//:docs.bzl", "docs_bundle") + +docs_bundle( + name = "docs_bundle", + source_dir = "docs", + visibility = ["//visibility:public"], +) py_unittest_qnx_test( name = "qnx_unit_test_cases", diff --git a/score/time/docs/index.rst b/score/time/docs/index.rst index 31184c6f..cc4456f1 100644 --- a/score/time/docs/index.rst +++ b/score/time/docs/index.rst @@ -18,7 +18,7 @@ Time #### .. document:: Time - :id: doc__time + :id: doc__time_time :status: draft :version: 1 :safety: ASIL_B diff --git a/score/time/docs/requirements/requirements.rst b/score/time/docs/requirements/requirements.rst index 307d3863..6589d6db 100644 --- a/score/time/docs/requirements/requirements.rst +++ b/score/time/docs/requirements/requirements.rst @@ -39,19 +39,21 @@ Component Time Requirements Functional Requirements ----------------------- -.. comp_req:: Some Title - :id: comp_req__time__some_title - :reqtype: Functional - :security: NO - :safety: ASIL_B - :derived_from: feat_req__time__example_req - :status: invalid - :version: 1 - :satisfied_by: comp__time +.. code-block:: rst + + .. comp_req:: Some Title + :id: comp_req__time__some_title + :reqtype: Functional + :security: NO + :safety: ASIL_B + :derived_from: feat_req__time__example_req + :status: invalid + :version: 1 + :satisfied_by: comp__time - The Component shall do xyz to another component to bring it to this condition at this time + The Component shall do xyz to another component to bring it to this condition at this time - Note: (optional, not to be verified) + Note: (optional, not to be verified) .. attention:: The above directive must be updated according to your component requirements. diff --git a/score/time_daemon/BUILD b/score/time_daemon/BUILD index a1a29bac..c6430a3d 100644 --- a/score/time_daemon/BUILD +++ b/score/time_daemon/BUILD @@ -12,6 +12,13 @@ # ******************************************************************************* load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_docs_as_code//:docs.bzl", "docs_bundle") + +docs_bundle( + name = "docs_bundle", + source_dir = "docs", + visibility = ["//visibility:public"], +) cc_unit_test_suites_for_host_and_qnx( name = "unit_test_suite", diff --git a/score/time_daemon/docs/requirements/requirements.rst b/score/time_daemon/docs/requirements/requirements.rst index dfadfd33..f94781be 100644 --- a/score/time_daemon/docs/requirements/requirements.rst +++ b/score/time_daemon/docs/requirements/requirements.rst @@ -39,19 +39,21 @@ Component Time Daemon Requirements Functional Requirements ----------------------- -.. comp_req:: Some Title - :id: comp_req__time_daemon__some_title - :reqtype: Process - :security: YES - :safety: ASIL_B - :derived_from: feat_req__example_feature__example_req - :status: invalid - :version: 1 - :satisfied_by: comp__time_daemon +.. code-block:: rst + + .. comp_req:: Some Title + :id: comp_req__time_daemon__some_title + :reqtype: Process + :security: YES + :safety: ASIL_B + :derived_from: feat_req__example_feature__example_req + :status: invalid + :version: 1 + :satisfied_by: comp__time_daemon - The Component shall do xyz to another component to bring it to this condition at this time + The Component shall do xyz to another component to bring it to this condition at this time - Note: (optional, not to be verified) + Note: (optional, not to be verified) .. attention:: The above directive must be updated according to your component requirements. diff --git a/score/time_slave/BUILD b/score/time_slave/BUILD index 4383af58..59c276e7 100644 --- a/score/time_slave/BUILD +++ b/score/time_slave/BUILD @@ -12,6 +12,13 @@ # ******************************************************************************* load("@score_baselibs//:bazel/unit_tests.bzl", "cc_unit_test_suites_for_host_and_qnx") +load("@score_docs_as_code//:docs.bzl", "docs_bundle") + +docs_bundle( + name = "docs_bundle", + source_dir = "docs", + visibility = ["//visibility:public"], +) cc_unit_test_suites_for_host_and_qnx( name = "unit_test_suite", diff --git a/score/time_slave/docs/detailed_design/detailed_design.rst b/score/time_slave/docs/detailed_design/detailed_design.rst index cbed4127..0c8cfb1f 100644 --- a/score/time_slave/docs/detailed_design/detailed_design.rst +++ b/score/time_slave/docs/detailed_design/detailed_design.rst @@ -64,7 +64,7 @@ A static view provides an overview of the units and their relationships using UML 2.0 notations (e.g. class diagrams, component diagrams). Use ``.. uml::`` or ``.. image::`` directives to include the diagram. -.. uml:: dd_example_ex_sta.puml +.. .. uml:: dd_example_ex_sta.puml Dynamic Diagrams for Unit Interactions (optional) -------------------------------------------------- @@ -77,7 +77,7 @@ and interface documentation alone. Use standard UML behavioural diagrams (sequence diagrams, state machine diagrams) with ``.. uml::`` or ``.. image::`` directives. -.. uml:: dd_example_ex_dyn.puml +.. .. uml:: dd_example_ex_dyn.puml Units within the Component -------------------------- diff --git a/score/time_slave/docs/detailed_design/index.rst b/score/time_slave/docs/detailed_design/index.rst index fc9872af..71f2534d 100644 --- a/score/time_slave/docs/detailed_design/index.rst +++ b/score/time_slave/docs/detailed_design/index.rst @@ -33,7 +33,7 @@ An example of documenting detailed design can be found in: .. toctree:: - detailed_design_example + detailed_design Inspection Checklist -------------------- diff --git a/score/ts_client/BUILD b/score/ts_client/BUILD new file mode 100644 index 00000000..44cf6513 --- /dev/null +++ b/score/ts_client/BUILD @@ -0,0 +1,20 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@score_docs_as_code//:docs.bzl", "docs_bundle") + +docs_bundle( + name = "docs_bundle", + source_dir = "docs", + visibility = ["//visibility:public"], +) From 175d03e10fccdad7ad75091b93e629b4c2ebee13 Mon Sep 17 00:00:00 2001 From: Ryan Steel Date: Wed, 19 Aug 2026 16:44:19 +0100 Subject: [PATCH 3/3] fix build --- docs/features/time_daemon/index.rst | 6 +++--- score/time/docs/detailed_design/index.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/features/time_daemon/index.rst b/docs/features/time_daemon/index.rst index 9af39f65..5eb3a53e 100644 --- a/docs/features/time_daemon/index.rst +++ b/docs/features/time_daemon/index.rst @@ -549,7 +549,7 @@ When the ``Verification Machine`` receives new PTP data, it processes it through IPC Machine SW component ^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``IPC Machine`` component shall get the `verified-ptp-data <#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 ``IPC Machine`` component shall get the `verified-ptp-data <#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. @@ -655,7 +655,7 @@ applications access vehicle time. Its ``td_impl`` backend connects to the ``IPC to receive the time data published by the ``TimeDaemon``. For the full API description, test patterns, and Bazel dependencies, see -:doc:`score::time — Unified Clock Interface <../time/index>`. +:doc:`score::time — Unified Clock Interface <../../time/index>`. Component requirements '''''''''''''''''''''' @@ -686,7 +686,7 @@ The Class Diagram is presented below. Receive data '''''''''''' -In case of receiving data, the ``Application`` shall just call ``VehicleClock::GetInstance().Now()`` (see :doc:`score::time <../time/index>`) and it shall return the latest published Vehicle Time, which is already adjusted with local clock. +In case of receiving data, the ``Application`` shall just call ``VehicleClock::GetInstance().Now()`` (see :doc:`score::time <../../time/index>`) and it shall return the latest published Vehicle Time, which is already adjusted with local clock. To do so, in the ``VehicleClock`` ``td_impl`` backend there is a thread, who polls for new data the ``IPCMachine::receiver`` and put the data to the process-internal shared buffer (memory), from where it is being read on ``VehicleClock::GetInstance().Now()`` call. diff --git a/score/time/docs/detailed_design/index.rst b/score/time/docs/detailed_design/index.rst index 9a7a9b45..57c16bb7 100644 --- a/score/time/docs/detailed_design/index.rst +++ b/score/time/docs/detailed_design/index.rst @@ -23,7 +23,7 @@ Detailed Design :version: 1 :safety: ASIL_B :security: NO - :realizes: wp__detailed_design[version==1] + :realizes: wp__sw_implementation :tags: time, detailed_design Description