Time synchronization
An outstation timestamps its events with its own clock, and many outstations rely on the master to set it. The master
synchronizes the outstation's time automatically when the outstation asks for it, if you configure automatic time
synchronization in the AssociationConfig. You can also synchronize it on demand with
synchronizeTime on the MasterChannel (synchronize_time on the association in Rust), which reports whether it
succeeded.
Either way, the time comes from your AssociationHandler.
Choosing a procedure
| Procedure | Use it for |
|---|---|
| LAN | Links with low, steady latency, such as Ethernet |
| Non-LAN | Serial or other links whose latency is high or varies. The master measures the delay first and corrects for it |
| Direct write | Only outstations that support neither of the others. It ignores latency, so the time can be off by the delay |
AssociationHandler
AssociationHandler supplies the time the master sends. Its only method, getCurrentTime (get_current_time in Rust),
returns the current UTC time.
Typically you return the system time, synchronized by NTP. You can use another clock, for example in a data concentrator that receives its time from an upstream DNP3 master.
Return an invalid timestamp from getCurrentTime (None in Rust) when you don't have a trustworthy time. The sync then
fails with "system time not available" instead of sending a wrong time.
The examples return the system time:
- Rust
- C
- C++
- Java
- C#
#[derive(Copy, Clone)]
struct ExampleAssociationHandler;
impl AssociationHandler for ExampleAssociationHandler {}
In Rust, get_current_time returns the system time by default, so the example doesn't implement it.
dnp3_utc_timestamp_t get_system_time(void *context) { return dnp3_utc_timestamp_valid(now_ms()); }
dnp3_association_handler_t get_association_handler(void)
{
return (dnp3_association_handler_t){
.get_current_time = &get_system_time,
.on_destroy = NULL,
.ctx = NULL,
};
}
class AssociationHandler : public dnp3::AssociationHandler {
dnp3::UtcTimestamp get_current_time() override
{
const auto time_since_epoch = std::chrono::system_clock::now().time_since_epoch();
return dnp3::UtcTimestamp::valid(std::chrono::duration_cast<std::chrono::milliseconds>(time_since_epoch).count());
}
};
std::unique_ptr<dnp3::AssociationHandler> create_association_handler()
{
return std::make_unique<AssociationHandler>();
}
class ExampleAssociationHandler implements AssociationHandler {
@Override
public UtcTimestamp getCurrentTime() {
return UtcTimestamp.valid(ulong(System.currentTimeMillis()));
}
}
class ExampleAssociationHandler : IAssociationHandler
{
// the system time in milliseconds since the epoch, used to synchronize the outstation's clock
public UtcTimestamp GetCurrentTime()
{
return UtcTimestamp.Valid((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
}
}