Skip to content

Add DirectWriteAbsTime time sync procedure for non-conformant outstations#403

Merged
jadamcrain merged 2 commits intomainfrom
issue/374
Oct 3, 2025
Merged

Add DirectWriteAbsTime time sync procedure for non-conformant outstations#403
jadamcrain merged 2 commits intomainfrom
issue/374

Conversation

@jadamcrain
Copy link
Member

Summary

  • Adds DirectWriteAbsTime variant to TimeSyncProcedure enum
  • Allows time synchronization via direct write of Group 50 Var 1 without delay measurement
  • Workaround for outstations that do not support the DELAY_MEASURE function code

Implementation Details

  • New state machine state that gets fresh system time and transitions to WriteAbsoluteTime
  • Writes g50v1 directly without any delay measurement or latency compensation
  • Includes warning documentation about non-conformance and lack of network latency compensation
  • Updated FFI bindings for both auto_time_sync and time_sync_mode enums

Testing

  • Added 2 unit tests for the new procedure
  • All existing tests pass (245 tests)
  • No clippy warnings

Breaking Changes

  • Marked TimeSyncProcedure as #[non_exhaustive] for future compatibility

Closes #374

…utstations

Add support for time synchronization via direct write of Group 50 Var 1
without delay measurement. This is a workaround for outstations that do
not support the DELAY_MEASURE function code.

This method does not account for network latency and should only be used
when standard time synchronization procedures are not supported.

Closes #374
@gemini-code-assist
Copy link

Summary of Changes

Hello @jadamcrain, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new, non-standard time synchronization procedure, DirectWriteAbsTime, to address compatibility issues with DNP3 outstations that do not support the DELAY_MEASURE function code. This workaround allows masters to directly write absolute time (Group 50 Var 1) without network latency compensation, providing a functional but potentially less accurate time sync option for specific scenarios. The change includes updates to the time synchronization state machine, FFI bindings, and comprehensive unit tests.

Highlights

  • New Time Synchronization Procedure: Introduced a new DirectWriteAbsTime variant to the TimeSyncProcedure enum, offering an alternative time synchronization method.
  • Workaround for Non-Conformant Outstations: This new procedure allows time synchronization by directly writing Group 50 Var 1, specifically designed for outstations that do not support the standard DELAY_MEASURE function code.
  • No Network Latency Compensation: The DirectWriteAbsTime method explicitly bypasses delay measurement, meaning it does not account for network latency and may result in less accurate time synchronization.
  • State Machine Integration: A new state (DirectWriteAbsTime) has been added to the time synchronization state machine, which retrieves the current system time and then transitions to writing the absolute time.
  • FFI Bindings Updated: The Foreign Function Interface (FFI) bindings for auto_time_sync and time_sync_mode enums have been updated to include the new DirectWriteAbsTime variant.
  • Enum Marked Non-Exhaustive: The TimeSyncProcedure enum is now marked #[non_exhaustive] to allow for future additions without breaking existing code.
  • Unit Tests Added: Two new unit tests were added to thoroughly verify the functionality and behavior of the DirectWriteAbsTime procedure, including scenarios where system time is unavailable.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a DirectWriteAbsTime time synchronization procedure, providing a workaround for outstations that don't support the standard delay measurement. The implementation is sound, adding a new state to the TimeSyncTask state machine and updating FFI bindings and schema definitions accordingly. The new functionality is also covered by unit tests. My feedback focuses on improving code clarity and robustness by using the unreachable! macro for code paths that are logically impossible to reach in the state machine, which is a common Rust idiom.

State::WriteAbsoluteTime(_) => FunctionCode::Write,
State::RecordCurrentTime(_) => FunctionCode::RecordCurrentTime,
State::WriteLastRecordedTime(_) => FunctionCode::Write,
State::DirectWriteAbsTime => FunctionCode::Write,

Choose a reason for hiding this comment

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

medium

The DirectWriteAbsTime state is transient and should always transition to WriteAbsoluteTime within the start() method. Therefore, this match arm should be unreachable. Using the unreachable! macro here makes this assumption explicit and will cause a panic during development if this logic is ever violated. This is preferable to silently proceeding with potentially incorrect behavior and can also provide hints to the compiler for optimization.

            State::DirectWriteAbsTime => unreachable!("DirectWriteAbsTime should transition to WriteAbsoluteTime in start()"),

State::WriteAbsoluteTime(x) => writer.write_count_of_one(Group50Var1 { time: x }),
State::RecordCurrentTime(_) => Ok(()),
State::WriteLastRecordedTime(x) => writer.write_count_of_one(Group50Var3 { time: x }),
State::DirectWriteAbsTime => Ok(()), // Should never be reached as it transitions in start()

Choose a reason for hiding this comment

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

medium

As the comment indicates, this code path should never be reached. It's more idiomatic and safer to enforce this with the unreachable! macro. This makes the code more robust by panicking if the assumption is violated, which helps catch bugs early during development or testing.

            State::DirectWriteAbsTime => unreachable!("DirectWriteAbsTime should transition to WriteAbsoluteTime in start()"),

Comment on lines 150 to 153
State::DirectWriteAbsTime => {
// Should never be reached as it transitions to WriteAbsoluteTime in start()
self.handle_write_absolute_time(association, response)
}

Choose a reason for hiding this comment

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

medium

Similar to the other match arms for DirectWriteAbsTime, this one is expected to be unreachable. Using unreachable! is more idiomatic and safer than implementing a behavior for a state that should not be possible at this point in the execution flow.

            State::DirectWriteAbsTime => unreachable!("DirectWriteAbsTime should transition to WriteAbsoluteTime in start()"),

…e state

Eliminates the DirectWriteAbsTime state and all 'unreachable' code paths
by using WriteAbsoluteTime(Option<Timestamp>) instead. This makes the
invariant more local: timestamp must be populated before write() is called,
which is enforced by start().

- WriteAbsoluteTime(None) signals timestamp should be fetched in start()
- WriteAbsoluteTime(Some(timestamp)) signals timestamp is ready
- write() uses expect() with clear error message if invariant is violated
@jadamcrain jadamcrain merged commit 7539523 into main Oct 3, 2025
59 of 60 checks passed
@jadamcrain jadamcrain deleted the issue/374 branch October 3, 2025 15:29
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.

Allow master to synchronize time by just writing Group 50 Var 1

1 participant

Comments