Skip to content

feat: add gPTP driver for IEEE 802.1AS / PTP time synchronization - #392

Open
vtz wants to merge 5 commits into
jumpstarter-dev:mainfrom
vtz:feat/gptp-driver
Open

vtz wants to merge 5 commits into
jumpstarter-dev:mainfrom
vtz:feat/gptp-driver

Conversation

@vtz

@vtz vtz commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds jumpstarter-driver-gptp wrapping linuxptp (ptp4l/phc2sys) to provide IEEE 802.1AS / PTP precision time synchronization for automotive Ethernet testing
  • Includes Gptp driver (real hardware), MockGptp driver (testing), GptpClient with wait_for_sync helper and Click CLI
  • Pydantic models for PTP status, offsets, sync events, port stats, and parent info with validated state transitions
  • Comprehensive test suite: unit tests, end-to-end mocked tests via serve(), and stateful tests enforcing PTP state machine transitions
  • Documentation integrated into the jumpstarter.dev docs site with full API reference, configuration examples, and troubleshooting guide

Details

Driver Architecture

  • Gptp driver: Manages ptp4l and phc2sys subprocess lifecycle, parses real-time log output for sync status, offset measurements, port state changes, and clock quality metrics
  • MockGptp driver: Pluggable backend for testing without PTP hardware — supports a StatefulPtp4l backend that enforces valid PTP state machine transitions
  • GptpClient: Async client with wait_for_sync(threshold_ns, timeout) convenience method and full Click CLI (j gptp status, j gptp start, j gptp offset, etc.)

Models (common.py)

  • GptpStatus, GptpOffset, GptpSyncEvent, GptpPortStats, GptpParentInfo
  • PortState and ServoState enums with VALID_PORT_TRANSITIONS map

Test Coverage

  • Unit: ptp4l log parsing, config generation, HW timestamping detection, Pydantic model validation
  • E2E Mocked: Full driver↔client lifecycle over gRPC via serve(), error paths, CLI invocation
  • Stateful: StatefulPtp4l mock enforcing PTP state transitions, illegal transition rejection, operation ordering

Test plan

  • make pkg-test-jumpstarter-driver-gptp passes all tests
  • make lint passes with no new errors
  • Manual: verify docs render correctly on jumpstarter.dev after merge

Made with Cursor

@netlify

netlify Bot commented Mar 28, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for jumpstarter-docs failed. Why did it fail? →

Name Link
🔨 Latest commit ee84f1f
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69dc9bcc055bb500081cef64

@coderabbitai

coderabbitai Bot commented Mar 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new jumpstarter-driver-gptp package with Pydantic models, an async Gptp driver that manages ptp4l/phc2sys, MockGptp and a stateful test backend, a DriverClient CLI, extensive unit→integration tests, package metadata, examples, and Sphinx docs/index entries.

Changes

gPTP driver package

Layer / File(s) Summary
Models & enums
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/common.py
Adds PortState/ServoState, VALID_PORT_TRANSITIONS, and Pydantic models: GptpStatus, GptpOffset, GptpSyncEvent, GptpPortStats, GptpParentInfo.
Parsing & helpers
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py (parsing/config helpers)
Adds parse_ptp4l_log_line, _generate_ptp4l_config, and _validate_extra_args to parse logs and prepare safe ptp4l invocation.
Gptp core driver
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
Implements async Gptp driver: hw timestamp probing, spawn/cleanup of ptp4l/phc2sys, background reader updating state, guarded query methods, and async read() event stream.
Mock driver
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
Adds MockGptp and MockGptpBackend providing simulated lifecycle, offsets, role forcing, and synthetic event streaming for tests.
Client & CLI
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
Adds GptpClient (DriverClient subclass) with lifecycle methods, status/getters, wait_for_sync, monitor() streaming, and Click CLI subcommands.
Stateful test backend & fixtures
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py
Adds StatefulPtp4l mock enforcing port-state transitions, runtime errors, simulation methods, and pytest fixtures (stateful_ptp4l, stateful_client).
Tests
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py
Comprehensive tests: unit parsing/config, async hw detection, pydantic validation, Mock/E2E lifecycle, stateful state-machine tests, and env-gated Linux/hardware integration tests.
Docs & README
python/packages/jumpstarter-driver-gptp/README.md, python/docs/source/reference/package-apis/drivers/gptp.md, python/docs/source/reference/package-apis/drivers/index.md
Adds package README with examples, CLI/docs, hardware/troubleshooting notes, and Sphinx toctree/index entries referencing the package README.
Examples & packaging
python/packages/jumpstarter-driver-gptp/examples/exporter.yaml, python/packages/jumpstarter-driver-gptp/.gitignore, python/packages/jumpstarter-driver-gptp/pyproject.toml, python/pyproject.toml
Adds example ExporterConfig YAML, VCS ignore rules, package pyproject with entry point jumpstarter.drivers: Gptp, and workspace source mapping.

Sequence Diagram

sequenceDiagram
    participant User
    participant Client as GptpClient
    participant Driver as Gptp
    participant ptp4l as ptp4l
    participant System as SystemClock

    User->>Client: start()
    Client->>Driver: start()
    Driver->>ptp4l: spawn subprocess (write config, -H/-S)
    loop read logs
        ptp4l->>Driver: stdout log line
        Driver->>Driver: parse -> update state / emit event
        Driver-->>Client: GptpSyncEvent (stream)
    end
    User->>Client: wait_for_sync(...)
    loop poll
        Client->>Driver: is_synchronized()
        Driver-->>Client: bool
    end
    User->>Client: stop()
    Client->>Driver: stop()
    Driver->>ptp4l: terminate subprocesses / cleanup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

backport release-0.7

Suggested reviewers

  • raballew
  • mangelajo

Poem

🐰 I hopped through ptp4l logs at dawn,
spawned tiny processes on the lawn.
Mocks and readers hum in sync,
docs and tests in tidy link.
A rabbit cheers — time kept fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding a gPTP driver for IEEE 802.1AS/PTP time synchronization. It accurately reflects the primary focus of the changeset.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, providing detailed summaries of driver architecture, models, test coverage, and implementation details for the gPTP driver being added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-gptp/README.md (1)

214-219: Normalize command examples to satisfy markdownlint MD014.

For command-only examples without output, remove $ prompts (or add output lines) to silence MD014 warnings.

🛠️ Proposed fix
-$ j gptp start              # Start PTP synchronization
-$ j gptp stop               # Stop PTP synchronization
-$ j gptp status             # Show sync status
-$ j gptp offset             # Show current clock offset
-$ j gptp monitor -n 20      # Monitor 20 sync events
-$ j gptp set-priority 0     # Force grandmaster role
+j gptp start              # Start PTP synchronization
+j gptp stop               # Stop PTP synchronization
+j gptp status             # Show sync status
+j gptp offset             # Show current clock offset
+j gptp monitor -n 20      # Monitor 20 sync events
+j gptp set-priority 0     # Force grandmaster role
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/README.md` around lines 214 - 219,
The README command examples use shell prompt characters which triggers
markdownlint MD014; update the example block so command-only lines do not
include the "$" prompt (or alternatively add expected output lines), e.g.,
replace entries like "$ j gptp start" with "j gptp start" for each command
(start, stop, status, offset, monitor -n 20, set-priority 0) to normalize the
code block for MD014 compliance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`:
- Around line 113-129: wait_for_sync currently swallows all exceptions and
doesn't enforce an offset convergence threshold; change its signature to accept
an optional max_offset (seconds) and in the loop require both
self.is_synchronized() and that the current offset (e.g. obtained via
self.get_offset() or self.offset_reading method) is <= max_offset before
returning True; replace the blanket "except Exception" with catching only
expected transient errors (or let unexpected exceptions propagate) so
driver/transport failures surface instead of being silently retried, and ensure
the function returns False on timeout if convergence criteria aren't met.

In
`@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py`:
- Around line 577-592: The test's veth_pair fixture moves veth-s into namespace
"ns-ptp-slave" but the test then constructs Gptp(interface=slave_iface) and
calls serve() in the current namespace so client.start() spawns ptp4l for veth-s
in the wrong namespace; fix by either creating/serving the slave driver inside
the slave netns or keep veth-s in the current namespace. Concretely, update the
test around veth_pair / Gptp(interface=slave_iface) / serve() so that the object
passed to serve() is created and served from inside ns-ptp-slave (use the same
netns context as veth_pair) or change veth_pair to not move veth-s out of the
test process namespace so ptp4l -i veth-s runs where the interface exists.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py`:
- Around line 410-436: The read() async generator currently uses a fixed for _
in range(100) loop which forcibly ends the stream after ~100 samples; change
that to an indefinite loop (e.g., while True) so the generator yields until the
session is stopped/cancelled, preserving the existing logic that checks
self._port_state, prev_state, and yields GptpSyncEvent with
port_state/servo_state/offset/path_delay/freq/timestamp, and rely on task
cancellation to terminate; apply the same replacement of the fixed-range loop
with an endless loop in the corresponding mock/alternate stream implementation
(the other method using the same range-based pattern) so both real and mock
drivers stream until stopped.
- Around line 172-175: The code currently treats a non-None _ptp4l_proc as proof
ptp4l is alive; change this so an EOF/exit actually invalidates the session: in
_read_ptp4l_output detect EOF/termination and set self._ptp4l_proc = None (and
any state flags like self._synchronized = False or clear last_state buffer) so
the session is marked dead, and ensure any cleanup/notification happens there;
update _require_started to check that _ptp4l_proc is running (e.g.,
self._ptp4l_proc.poll() is None) rather than just non-None so status(),
is_synchronized(), and read() will raise once the process has exited; ensure
status/is_synchronized/read rely on that running check or the invalidated state.
- Around line 360-382: get_clock_identity() and get_parent_info() currently
return placeholders; instead populate and return real values from linuxptp state
(or raise until available). Add state fields (e.g. self._clock_identity: str and
self._parent_info: GptpParentInfo) and update them when parsing ptp4l output
(the same parsing routine that updates _last_offset_ns, _port_state, and
_stats); then have get_clock_identity() return self._clock_identity (or raise
RuntimeError if not yet known) and have get_parent_info() return
self._parent_info (or raise RuntimeError if not yet known). Ensure
_require_started() remains called and mirror MockGptp behavior for value
semantics so callers can distinguish "not implemented/unset" from valid empty
results.
- Around line 384-397: set_priority1 currently only stores _priority1 and never
applies it to ptp4l because _generate_ptp4l_config hardcodes priority1=0; update
the real driver so set_priority1 updates the running ptp4l config: have
_generate_ptp4l_config read self._priority1 (instead of hardcoding 0) and ensure
set_priority1 triggers writing the new config and reloading/restarting ptp4l
(call the existing ptp4l restart/reload routine or implement one) so the
production behavior matches the mock behavior; touch symbols: set_priority1,
_priority1, _generate_ptp4l_config, and the ptp4l start/reload method.
- Around line 160-168: The blocking call in _supports_hw_timestamping() uses
subprocess.run() and is invoked from start() inside async code; make it
non-blocking by either converting _supports_hw_timestamping to async and using
asyncio.create_subprocess_exec() with a timeout (and read stdout/stderr) or keep
it sync but call it via await asyncio.to_thread(self._supports_hw_timestamping)
from start(); ensure you preserve the stdout parsing
("hardware-transmit"/"hardware-receive"), add timeout and exception handling so
failures return False, and align behavior with how ptp4l/phc2sys are started and
managed.
- Around line 216-220: The start() method must ensure cleanup on any failure:
wrap the entire startup sequence (from creation of self._config_file through
spawning subprocesses for ptp4l and phc2sys) in a try/finally so that the
finally block calls the instance cleanup (e.g., invoking stop() or explicit
teardown of self._config_file and any started processes) to remove the temp
config and terminate any partially-started subprocesses; additionally, after
spawning phc2sys (same place where ptp4l has an immediate-exit check), add the
same immediate-exit check used for ptp4l (short sleep then poll() and
raise/cleanup if it exited) so a dead phc2sys is detected and triggers the
cleanup path.

In `@python/packages/jumpstarter-driver-gptp/README.md`:
- Around line 126-131: The fenced state-machine block starting with
"INITIALIZING → LISTENING → SLAVE (synchronized to master)" lacks a language tag
and triggers markdownlint MD040; update the opening fence from ``` to ```text
(or another appropriate language) so the block becomes a labeled code fence
(e.g., ```text) and the linter warning is resolved.

---

Nitpick comments:
In `@python/packages/jumpstarter-driver-gptp/README.md`:
- Around line 214-219: The README command examples use shell prompt characters
which triggers markdownlint MD014; update the example block so command-only
lines do not include the "$" prompt (or alternatively add expected output
lines), e.g., replace entries like "$ j gptp start" with "j gptp start" for each
command (start, stop, status, offset, monitor -n 20, set-priority 0) to
normalize the code block for MD014 compliance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 459f2d69-f987-4b81-93a2-4500f1d9b823

📥 Commits

Reviewing files that changed from the base of the PR and between 03fc412 and e7aa29b.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • python/docs/source/reference/package-apis/drivers/gptp.md
  • python/docs/source/reference/package-apis/drivers/index.md
  • python/packages/jumpstarter-driver-gptp/.gitignore
  • python/packages/jumpstarter-driver-gptp/README.md
  • python/packages/jumpstarter-driver-gptp/examples/exporter.yaml
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/__init__.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/common.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py
  • python/packages/jumpstarter-driver-gptp/pyproject.toml
  • python/pyproject.toml

Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/README.md Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/pyproject.toml Outdated
Comment thread python/packages/jumpstarter-driver-gptp/pyproject.toml
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py Outdated
@vtz
vtz force-pushed the feat/gptp-driver branch from e7aa29b to f2d7fa5 Compare April 9, 2026 03:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py (1)

148-152: Redundant condition check in set_priority1.

The inner check if self._port_state != "MASTER" on line 150 is always true when the outer condition on line 149 is satisfied, since the outer condition already restricts _port_state to ("SLAVE", "LISTENING", "PASSIVE").

🔧 Suggested simplification
     def set_priority1(self, value: int):
         self.require_started()
         self._priority1 = value
         if value < 128 and self._port_state in ("SLAVE", "LISTENING", "PASSIVE"):
-            if self._port_state != "MASTER":
-                self._transition_to("MASTER")
+            self._transition_to("MASTER")
         self._call_log.append(f"set_priority1({value})")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py`
around lines 148 - 152, In set_priority1, remove the redundant inner check "if
self._port_state != 'MASTER'" because the outer condition already guarantees
_port_state is one of "SLAVE", "LISTENING", "PASSIVE"; instead directly call
self._transition_to("MASTER") when value < 128 and self._port_state in
("SLAVE","LISTENING","PASSIVE"), preserving assignment to self._priority1 and
the self._call_log.append("set_priority1({value})") behavior and keep references
to _priority1, _port_state, _transition_to, and _call_log intact.
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py (1)

155-184: Fragile test setup bypassing __post_init__.

Using Gptp.__new__(Gptp) to directly instantiate without running __post_init__ (lines 162, 172, 182) works for now but is fragile—if _supports_hw_timestamping later depends on other initialized attributes, these tests will fail with confusing errors. Consider creating a minimal valid instance instead.

🔧 Alternative approach using minimal valid instance
     async def test_detect_hw_timestamping(self):
         mock_proc = AsyncMock()
         mock_proc.communicate.return_value = (
             b"Capabilities:\n  hardware-transmit\n  hardware-receive\n  hardware-raw-clock\n",
             b"",
         )
         with patch("jumpstarter_driver_gptp.driver.asyncio.create_subprocess_exec", return_value=mock_proc):
-            driver = Gptp.__new__(Gptp)
-            driver.interface = "eth0"
+            driver = Gptp(interface="eth0")
             assert await driver._supports_hw_timestamping() is True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py`
around lines 155 - 184, The tests currently bypass Gptp.__post_init__ by using
Gptp.__new__(Gptp) before calling _supports_hw_timestamping (in tests
test_detect_hw_timestamping, test_detect_sw_only_timestamping,
test_detect_timestamping_ethtool_missing), which is fragile; instead create a
minimal valid instance that runs initialization: either construct via the normal
constructor (e.g., Gptp(...) with the minimal required args such as
interface="eth0") or call the real initializer on the instance
(driver.__init__(...) or driver.__post_init__()) after __new__ so that required
attributes are set; if the real constructor has heavy side effects, add a
lightweight test-only factory or patch side-effecting calls during construction
to allow creating a properly initialized Gptp before calling
_supports_hw_timestamping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py`:
- Around line 148-152: In set_priority1, remove the redundant inner check "if
self._port_state != 'MASTER'" because the outer condition already guarantees
_port_state is one of "SLAVE", "LISTENING", "PASSIVE"; instead directly call
self._transition_to("MASTER") when value < 128 and self._port_state in
("SLAVE","LISTENING","PASSIVE"), preserving assignment to self._priority1 and
the self._call_log.append("set_priority1({value})") behavior and keep references
to _priority1, _port_state, _transition_to, and _call_log intact.

In
`@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py`:
- Around line 155-184: The tests currently bypass Gptp.__post_init__ by using
Gptp.__new__(Gptp) before calling _supports_hw_timestamping (in tests
test_detect_hw_timestamping, test_detect_sw_only_timestamping,
test_detect_timestamping_ethtool_missing), which is fragile; instead create a
minimal valid instance that runs initialization: either construct via the normal
constructor (e.g., Gptp(...) with the minimal required args such as
interface="eth0") or call the real initializer on the instance
(driver.__init__(...) or driver.__post_init__()) after __new__ so that required
attributes are set; if the real constructor has heavy side effects, add a
lightweight test-only factory or patch side-effecting calls during construction
to allow creating a properly initialized Gptp before calling
_supports_hw_timestamping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21219289-579f-46a5-a78a-fa5b6337062c

📥 Commits

Reviewing files that changed from the base of the PR and between e7aa29b and f2d7fa5.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • python/docs/source/reference/package-apis/drivers/gptp.md
  • python/docs/source/reference/package-apis/drivers/index.md
  • python/packages/jumpstarter-driver-gptp/.gitignore
  • python/packages/jumpstarter-driver-gptp/README.md
  • python/packages/jumpstarter-driver-gptp/examples/exporter.yaml
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/__init__.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/common.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py
  • python/packages/jumpstarter-driver-gptp/pyproject.toml
  • python/pyproject.toml
✅ Files skipped from review due to trivial changes (7)
  • python/packages/jumpstarter-driver-gptp/.gitignore
  • python/docs/source/reference/package-apis/drivers/gptp.md
  • python/pyproject.toml
  • python/packages/jumpstarter-driver-gptp/examples/exporter.yaml
  • python/packages/jumpstarter-driver-gptp/pyproject.toml
  • python/docs/source/reference/package-apis/drivers/index.md
  • python/packages/jumpstarter-driver-gptp/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/common.py

@raballew

raballew commented Apr 9, 2026

Copy link
Copy Markdown
Member

@vtz please update the comments in case you are ready for another review.

@vtz
vtz force-pushed the feat/gptp-driver branch from cc5f9cc to f9bbfc9 Compare April 11, 2026 17:30
@vtz

vtz commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@raballew All 14 review comments have been addressed and replied to individually. Here's a summary:

Security (3 items):

  • Interface name validation with regex + length check
  • ptp4l_extra_args denylist for dangerous flags (-f, --config, -i, etc.)
  • Secure temp file creation with mkstemp + fchmod 0o600, cleanup in try/except

Async correctness (2 items):

  • _supports_hw_timestamping() converted to async using asyncio.create_subprocess_exec
  • Replaced deprecated asyncio.get_event_loop().create_task with asyncio.create_task + done callback

Process lifecycle (3 items):

  • Corrected teardown order: ptp4l → reader task → phc2sys → config cleanup
  • Added await proc.wait() after every kill() to prevent zombies
  • start() wrapped in try/except calling _cleanup() on failure

phc2sys (2 items):

  • Scoped to managed interface: -s <interface> -c CLOCK_REALTIME -w -m (replaced -a)
  • stdout/stderr → DEVNULL to prevent pipe-buffer deadlock

API & streaming (2 items):

  • Stubs (get_clock_identity, get_parent_info, set_priority1) raise NotImplementedError
  • read() uses while True instead of range(100)

Client & config (2 items):

  • wait_for_sync() narrowed to except RuntimeError
  • Removed MockGptp from entry points; added pytest-asyncio to dev deps

Ready for re-review when you have time.

@vtz
vtz force-pushed the feat/gptp-driver branch 2 times, most recently from f9bbfc9 to ee84f1f Compare April 13, 2026 07:31
Comment thread python/packages/jumpstarter-driver-gptp/README.md Outdated

@mangelajo mangelajo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some comments about the readme.

Comment thread python/packages/jumpstarter-driver-gptp/README.md Outdated
Comment thread python/packages/jumpstarter-driver-gptp/README.md
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py Outdated
Comment thread python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py Outdated
@mangelajo

Copy link
Copy Markdown
Member

@ambient-code can you handle comments and rebase?

@ambient-code

ambient-code Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Addressed the review comments in commit d3bf524:

  • README examples: Updated to use env() helper with client.gptp attribute access instead of serve() (the MockGptp test example still uses serve() appropriately)
  • threshold_ns parameter: Added to wait_for_sync() -- when provided, additionally checks that offset is below the threshold before returning True
  • port-stats CLI command: Added (parent-info skipped since get_parent_info() raises NotImplementedError on the real driver)
  • _require_started local variable: Captures self._ptp4l_proc into a local proc variable to avoid fragile access patterns
  • FileNotFoundError handling: ptp4l spawn now catches FileNotFoundError specifically and re-raises as RuntimeError("ptp4l not found -- install linuxptp")
  • IEEE standards question: Left for @vtz to answer

Rebase note: Could not complete the rebase on main due to a GitHub App token limitation -- the rebase brings in .github/workflows/python-tests.yaml changes from main, and the token lacks the workflows permission needed to push workflow file modifications to the fork. Additionally, GitHub's API-based rebase/merge also fails due to a conflict in python/docs/source/reference/package-apis/drivers/index.md (SOME/IP driver was added to main since this PR branched). The conflict resolution is straightforward (both entries should be kept). @vtz or a maintainer with appropriate permissions will need to perform the rebase manually.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py (1)

12-28: Remove unnecessary pass statements after exception docstrings.

The pass statements in the exception class bodies are redundant when a docstring is already present. The docstring serves as the body.

♻️ Suggested cleanup
 class PtpNotStartedError(RuntimeError):
     """Raised when an operation requires ptp4l to be running."""
-
-    pass


 class PtpAlreadyRunningError(RuntimeError):
     """Raised when attempting to start ptp4l while it is already running."""
-
-    pass


 class PtpStateError(RuntimeError):
     """Raised when an invalid PTP state transition is attempted."""
-
-    pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py`
around lines 12 - 28, The three custom exceptions PtpNotStartedError,
PtpAlreadyRunningError, and PtpStateError include redundant `pass` statements
after their docstrings; remove the `pass` lines from each class body so the
docstring is the sole content (i.e., leave the class definitions with only the
triple-quoted docstring for PtpNotStartedError, PtpAlreadyRunningError, and
PtpStateError).
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py (1)

234-249: Add parent-info CLI command.

The port-stats command was added per past review, but parent-info is still missing. The client already has get_parent_info() method at lines 94-104.

♻️ Add parent-info command
         `@base.command`(name="set-priority")
         `@click.argument`("priority", type=int)
         def set_priority(priority):
             """Set clock priority1 for BMCA."""
             self.set_priority1(priority)
             click.echo(f"Priority1 set to {priority}")

+        `@base.command`(name="parent-info")
+        def parent_info():
+            """Show parent/grandmaster clock information."""
+            p = self.get_parent_info()
+            click.echo(f"Grandmaster identity:  {p.grandmaster_identity}")
+            click.echo(f"Grandmaster priority1: {p.grandmaster_priority1}")
+            click.echo(f"Parent port identity:  {p.parent_port_identity}")
+
         return base
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`
around lines 234 - 249, Add a new CLI subcommand using
`@base.command`(name="parent-info") that calls self.get_parent_info(), formats the
returned object's fields, and prints them via click.echo similar to the existing
port_stats and set_priority commands; locate the CLI block where
`@base.command`(name="port-stats") and `@base.command`(name="set-priority") are
defined and add a def parent_info(): docstring "Show PTP parent info.", call
parent = self.get_parent_info(), then echo the relevant attributes from parent
(e.g., identity, steps_removed, port_number or other fields returned by
get_parent_info()) so the CLI surfaces the parent information.
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py (1)

166-166: Consider validating domain range (0-127).

Per IEEE 1588/802.1AS, the PTP domain number should be in range 0-127. Currently there's no range validation.

♻️ Add domain validation
     def __post_init__(self):
         if hasattr(super(), "__post_init__"):
             super().__post_init__()

         if not _INTERFACE_RE.match(self.interface):
             raise ValueError(
                 f"Invalid interface name: {self.interface!r}. "
                 "Must match [a-zA-Z0-9][a-zA-Z0-9._-]{0,14}"
             )
+        if not 0 <= self.domain <= 127:
+            raise ValueError(
+                f"Invalid domain: {self.domain}. Must be in range 0-127"
+            )
         if self.profile not in _VALID_PROFILES:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py` at
line 166, The domain attribute (domain: int = 0) needs range validation to
ensure values are within 0–127; add a check where domain is set (preferably in
the class __init__ or a domain property setter in
jumpstarter_driver_gptp.driver) that raises ValueError for out-of-range values
and include a clear error message; if domain can come from external
config/parsing, validate immediately after parsing/assignment (e.g., in the
constructor or set_domain method) to prevent invalid PTP domain numbers from
propagating.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`:
- Around line 221-232: The CLI monitor command is formatting Optional floats
(GptpSyncEvent.offset_ns and path_delay_ns) with "{:.0f}" which raises when
values are None; within the monitor() function (the `@base.command` monitor and
the loop over self.monitor()), guard those fields by computing safe strings
(e.g., offset_str = f"{event.offset_ns:.0f}ns" if event.offset_ns is not None
else "N/A" and similarly for path_delay_ns) and then use those precomputed
strings in the click.echo output so formatting never gets applied to None.

---

Nitpick comments:
In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`:
- Around line 234-249: Add a new CLI subcommand using
`@base.command`(name="parent-info") that calls self.get_parent_info(), formats the
returned object's fields, and prints them via click.echo similar to the existing
port_stats and set_priority commands; locate the CLI block where
`@base.command`(name="port-stats") and `@base.command`(name="set-priority") are
defined and add a def parent_info(): docstring "Show PTP parent info.", call
parent = self.get_parent_info(), then echo the relevant attributes from parent
(e.g., identity, steps_removed, port_number or other fields returned by
get_parent_info()) so the CLI surfaces the parent information.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py`:
- Around line 12-28: The three custom exceptions PtpNotStartedError,
PtpAlreadyRunningError, and PtpStateError include redundant `pass` statements
after their docstrings; remove the `pass` lines from each class body so the
docstring is the sole content (i.e., leave the class definitions with only the
triple-quoted docstring for PtpNotStartedError, PtpAlreadyRunningError, and
PtpStateError).

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py`:
- Line 166: The domain attribute (domain: int = 0) needs range validation to
ensure values are within 0–127; add a check where domain is set (preferably in
the class __init__ or a domain property setter in
jumpstarter_driver_gptp.driver) that raises ValueError for out-of-range values
and include a clear error message; if domain can come from external
config/parsing, validate immediately after parsing/assignment (e.g., in the
constructor or set_domain method) to prevent invalid PTP domain numbers from
propagating.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3eb8ba5e-267f-486e-9766-f0cc37ce698f

📥 Commits

Reviewing files that changed from the base of the PR and between f9bbfc9 and d3bf524.

📒 Files selected for processing (5)
  • python/packages/jumpstarter-driver-gptp/README.md
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py
✅ Files skipped from review due to trivial changes (2)
  • python/packages/jumpstarter-driver-gptp/README.md
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py

Comment on lines +221 to +232
@base.command()
@click.option("--count", "-n", default=10, help="Number of events to show")
def monitor(count):
"""Monitor PTP sync events."""
for i, event in enumerate(self.monitor()):
click.echo(
f"[{event.event_type}] state={event.port_state} "
f"offset={event.offset_ns:.0f}ns "
f"delay={event.path_delay_ns:.0f}ns"
)
if i + 1 >= count:
break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle None values in monitor output formatting.

GptpSyncEvent.offset_ns and path_delay_ns are Optional[float]. Formatting None with :.0f raises TypeError.

🛡️ Proposed fix
         `@base.command`()
         `@click.option`("--count", "-n", default=10, help="Number of events to show")
         def monitor(count):
             """Monitor PTP sync events."""
             for i, event in enumerate(self.monitor()):
+                offset_str = f"{event.offset_ns:.0f}ns" if event.offset_ns is not None else "N/A"
+                delay_str = f"{event.path_delay_ns:.0f}ns" if event.path_delay_ns is not None else "N/A"
                 click.echo(
                     f"[{event.event_type}] state={event.port_state} "
-                    f"offset={event.offset_ns:.0f}ns "
-                    f"delay={event.path_delay_ns:.0f}ns"
+                    f"offset={offset_str} "
+                    f"delay={delay_str}"
                 )
                 if i + 1 >= count:
                     break
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@base.command()
@click.option("--count", "-n", default=10, help="Number of events to show")
def monitor(count):
"""Monitor PTP sync events."""
for i, event in enumerate(self.monitor()):
click.echo(
f"[{event.event_type}] state={event.port_state} "
f"offset={event.offset_ns:.0f}ns "
f"delay={event.path_delay_ns:.0f}ns"
)
if i + 1 >= count:
break
`@base.command`()
`@click.option`("--count", "-n", default=10, help="Number of events to show")
def monitor(count):
"""Monitor PTP sync events."""
for i, event in enumerate(self.monitor()):
offset_str = f"{event.offset_ns:.0f}ns" if event.offset_ns is not None else "N/A"
delay_str = f"{event.path_delay_ns:.0f}ns" if event.path_delay_ns is not None else "N/A"
click.echo(
f"[{event.event_type}] state={event.port_state} "
f"offset={offset_str} "
f"delay={delay_str}"
)
if i + 1 >= count:
break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`
around lines 221 - 232, The CLI monitor command is formatting Optional floats
(GptpSyncEvent.offset_ns and path_delay_ns) with "{:.0f}" which raises when
values are None; within the monitor() function (the `@base.command` monitor and
the loop over self.monitor()), guard those fields by computing safe strings
(e.g., offset_str = f"{event.offset_ns:.0f}ns" if event.offset_ns is not None
else "N/A" and similarly for path_delay_ns) and then use those precomputed
strings in the click.echo output so formatting never gets applied to None.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py (1)

221-232: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Monitor command will crash on None offset/delay values.

The CLI monitor command formats event.offset_ns and event.path_delay_ns with :.0f, but both fields are Optional[float] in GptpSyncEvent. When either value is None, this raises TypeError: unsupported format string passed to NoneType.__format__.

This issue was flagged in a previous review but remains unaddressed.

🛡️ Proposed fix to handle None values safely
         `@base.command`()
         `@click.option`("--count", "-n", default=10, help="Number of events to show")
         def monitor(count):
             """Monitor PTP sync events."""
             for i, event in enumerate(self.monitor()):
+                offset_str = f"{event.offset_ns:.0f}ns" if event.offset_ns is not None else "N/A"
+                delay_str = f"{event.path_delay_ns:.0f}ns" if event.path_delay_ns is not None else "N/A"
                 click.echo(
                     f"[{event.event_type}] state={event.port_state} "
-                    f"offset={event.offset_ns:.0f}ns "
-                    f"delay={event.path_delay_ns:.0f}ns"
+                    f"offset={offset_str} "
+                    f"delay={delay_str}"
                 )
                 if i + 1 >= count:
                     break
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`
around lines 221 - 232, The monitor CLI currently formats Optional floats
event.offset_ns and event.path_delay_ns with "{:.0f}" which raises if they are
None; update the monitor() command (the enumerate(self.monitor()) loop and
click.echo call) to detect None for event.offset_ns and event.path_delay_ns and
substitute a safe string (e.g., "N/A" or "unknown") or a default numeric value
before formatting, ensuring the f-string only applies "{:.0f}" when the value is
not None and otherwise emits the fallback text.
🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-gptp/README.md (1)

103-103: 💤 Low value

Minor formatting inconsistency in the interface description.

The interface parameter description has an extra space before the closing backtick in enp3s0 )which should beenp3s0) for consistency.

✏️ Proposed fix
-| interface          | Network interface for PTP (e.g. `eth0`, `enp3s0`)    | str        | yes      |          |                     |
+| interface          | Network interface for PTP (e.g. `eth0`, `enp3s0`)   | str        | yes      |          |                     |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/packages/jumpstarter-driver-gptp/README.md` at line 103, Fix the minor
formatting in the README table row for the "interface" parameter: remove the
extra space before the closing backtick so the example reads `enp3s0`) (i.e.,
change "enp3s0` )" to "enp3s0`)") in the table cell describing Network interface
for PTP.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py`:
- Around line 221-232: The monitor CLI currently formats Optional floats
event.offset_ns and event.path_delay_ns with "{:.0f}" which raises if they are
None; update the monitor() command (the enumerate(self.monitor()) loop and
click.echo call) to detect None for event.offset_ns and event.path_delay_ns and
substitute a safe string (e.g., "N/A" or "unknown") or a default numeric value
before formatting, ensuring the f-string only applies "{:.0f}" when the value is
not None and otherwise emits the fallback text.

---

Nitpick comments:
In `@python/packages/jumpstarter-driver-gptp/README.md`:
- Line 103: Fix the minor formatting in the README table row for the "interface"
parameter: remove the extra space before the closing backtick so the example
reads `enp3s0`) (i.e., change "enp3s0` )" to "enp3s0`)") in the table cell
describing Network interface for PTP.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d206e055-fb6a-4c7f-9eb6-8bb1da5c26b5

📥 Commits

Reviewing files that changed from the base of the PR and between d3bf524 and 659a173.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • python/packages/jumpstarter-driver-gptp/README.md
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/client.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/common.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/conftest.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver.py
  • python/packages/jumpstarter-driver-gptp/jumpstarter_driver_gptp/driver_test.py

vtz and others added 5 commits May 29, 2026 20:44
Add jumpstarter-driver-gptp wrapping linuxptp (ptp4l/phc2sys) to
provide precision time synchronization for automotive Ethernet testing.

Includes:
- Gptp driver with ptp4l/phc2sys process management and log parsing
- MockGptp driver for testing without PTP hardware
- GptpClient with wait_for_sync helper and Click CLI
- Pydantic models for status, offsets, sync events, port stats
- Comprehensive test suite (unit, e2e mocked, stateful)
- Documentation integrated into jumpstarter.dev docs site

Made-with: Cursor
Security:
- Validate interface name with regex (reject injection attacks)
- Add denylist for ptp4l_extra_args (-f, --config, -i, --uds_address, etc.)
- Set temp config file permissions to 0o600 via os.fchmod

Async correctness:
- Convert _supports_hw_timestamping to async with asyncio.create_subprocess_exec
  and 10s timeout (was blocking subprocess.run in async context)
- Replace deprecated asyncio.get_event_loop().create_task with asyncio.create_task
- Add done_callback to reader task for unhandled exception logging

Process lifecycle:
- Wrap start() in try/except with _cleanup() on any failure path
- Add phc2sys immediate-exit check (matching ptp4l pattern)
- Fix teardown order: terminate ptp4l first, then cancel reader, then phc2sys
- Add await proc.wait() after kill() to prevent zombie processes
- Invalidate session on ptp4l EOF (_ptp4l_proc = None, reset state)
- Check returncode in _require_started() to detect exited processes

phc2sys:
- Replace -a (all PHCs) with -s <interface> -c CLOCK_REALTIME -w (scoped)
- Use stdout=DEVNULL instead of PIPE (prevent pipe buffer deadlock)
- Add start_new_session=True to both ptp4l and phc2sys

API changes:
- get_clock_identity, get_parent_info, set_priority1 now raise
  NotImplementedError on real driver (require UDS integration)
- MockGptp still implements these for testing
- Remove MockGptp from entry points (not a production driver)
- read() streams indefinitely (while True) instead of range(100)

Client:
- Narrow wait_for_sync except to RuntimeError only

Tests:
- Update HW timestamping tests for async _supports_hw_timestamping
- Add interface name validation tests (injection, too-long, valid names)
- Add extra_args denylist tests
- Add config generation priority1 test
- Fix veth_pair fixture: keep both interfaces in root namespace
- Use pytest-asyncio with asyncio_mode=auto (project convention)

Documentation:
- Add comprehensive docstrings to all public APIs (~80% coverage)
- Add text language tag to state-machine code fence (MD040)
- Remove $ prompts from CLI examples (MD014)
- Document NotImplementedError for stub methods in README
- Fix source_archive URL in pyproject.toml

Made-with: Cursor
- Remove redundant inner condition in StatefulPtp4l.set_priority1
- Use Gptp(interface="eth0") instead of Gptp.__new__ in HW timestamping tests

Made-with: Cursor
- Update README examples to use env() helper instead of serve() for
  non-test usage (mangelajo feedback)
- Add threshold_ns parameter to wait_for_sync for offset-aware sync
  checking (raballew feedback)
- Add port-stats CLI command (raballew feedback)
- Capture self._ptp4l_proc into local variable in _require_started to
  avoid fragile access patterns (raballew feedback)
- Catch FileNotFoundError specifically on ptp4l startup and re-raise
  as RuntimeError with actionable message (raballew feedback)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Specify IEEE 802.1AS-2020 and IEEE 1588-2019 as target standards
- Add domain validation (0-127 per IEEE 1588-2019 §7.1)
- Add priority1 validation (0-255 per IEEE 1588-2019 §7.6.2.2)
- Add standard references to README config table and comparison table
- Fix lint: B904 raise-from, C901 noqa for Click CLI method
- Add tests for domain/priority boundary validation
@vtz
vtz force-pushed the feat/gptp-driver branch from 659a173 to acbb31d Compare May 30, 2026 00:45
@vtz

vtz commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

@mangelajo All three comments have been addressed — README examples now use env() with client.gptp attribute access instead of serve() (only the MockGptp unit test example still uses serve(), which is appropriate for tests).

All review comments from @raballew and @kirkbrauer have also been addressed and replied to individually. The branch has been rebased onto latest main. Ready for re-review.

@raballew

raballew commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

@vtz could you rebase this branch? those changes lgtm but I am running a local test now to see if it actually works - lets see what @kirkbrauer and @mangelajo say.

@raballew raballew left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example uses drivers: but the schema field is export: (see ExporterConfigV1Alpha1 in jumpstarter/config/exporter.py). Pydantic silently ignores unknown fields, so the exporter starts with an empty composite — no drivers are loaded and j gptp never appears in the shell. All other driver examples in the repo use export:. Please change drivers: to export: here, and also add the required metadata.name field which the schema mandates.

apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
endpoint: grpc://localhost:8082
drivers:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drivers: should be export: — that is the actual field name in ExporterConfigV1Alpha1. With drivers:, Pydantic silently drops the block and the exporter starts with no child drivers, so j gptp never appears in the shell. Also missing a required metadata: block (e.g. metadata:\n name: gptp-example).

@raballew

Copy link
Copy Markdown
Member

I was able to test the mock setup but have not support for gptp on my hardware @mangelajo @vtz do could you validate that it actually works for you?

@raballew raballew added this to the 0.10.0 milestone Sep 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants