mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-03 03:01:44 +00:00
[ntcore] Add method to get server time offset (#4847)
Also exposes this as an event signaled when the offset is updated due to a ping response from the server.
This commit is contained in:
@@ -35,6 +35,9 @@ class IListenerStorage {
|
||||
virtual void Notify(unsigned int flags, unsigned int level,
|
||||
std::string_view filename, unsigned int line,
|
||||
std::string_view message) = 0;
|
||||
virtual void NotifyTimeSync(std::span<const NT_Listener> handles,
|
||||
unsigned int flags, int64_t serverTimeOffset,
|
||||
int64_t rtt2, bool valid) = 0;
|
||||
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
const ConnectionInfo* info) {
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
|
||||
@@ -110,6 +110,9 @@ void InstanceImpl::StartServer(std::string_view persistFilename,
|
||||
networkMode &= ~NT_NET_MODE_STARTING;
|
||||
});
|
||||
networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_STARTING;
|
||||
listenerStorage.NotifyTimeSync({}, NT_EVENT_TIMESYNC, 0, 0, true);
|
||||
m_serverTimeOffset = 0;
|
||||
m_rtt2 = 0;
|
||||
}
|
||||
|
||||
void InstanceImpl::StopServer() {
|
||||
@@ -121,6 +124,9 @@ void InstanceImpl::StopServer() {
|
||||
}
|
||||
server = std::move(m_networkServer);
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
listenerStorage.NotifyTimeSync({}, NT_EVENT_TIMESYNC, 0, 0, false);
|
||||
m_serverTimeOffset.reset();
|
||||
m_rtt2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +149,19 @@ void InstanceImpl::StartClient4(std::string_view identity) {
|
||||
return;
|
||||
}
|
||||
m_networkClient = std::make_shared<NetworkClient>(
|
||||
m_inst, identity, localStorage, connectionList, logger);
|
||||
m_inst, identity, localStorage, connectionList, logger,
|
||||
[this](int64_t serverTimeOffset, int64_t rtt2, bool valid) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
listenerStorage.NotifyTimeSync({}, NT_EVENT_TIMESYNC, serverTimeOffset,
|
||||
rtt2, valid);
|
||||
if (valid) {
|
||||
m_serverTimeOffset = serverTimeOffset;
|
||||
m_rtt2 = rtt2;
|
||||
} else {
|
||||
m_serverTimeOffset.reset();
|
||||
m_rtt2 = 0;
|
||||
}
|
||||
});
|
||||
if (!m_servers.empty()) {
|
||||
m_networkClient->SetServers(m_servers);
|
||||
}
|
||||
@@ -151,12 +169,22 @@ void InstanceImpl::StartClient4(std::string_view identity) {
|
||||
}
|
||||
|
||||
void InstanceImpl::StopClient() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if ((networkMode & (NT_NET_MODE_CLIENT3 | NT_NET_MODE_CLIENT4)) == 0) {
|
||||
return;
|
||||
std::shared_ptr<INetworkClient> client;
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if ((networkMode & (NT_NET_MODE_CLIENT3 | NT_NET_MODE_CLIENT4)) == 0) {
|
||||
return;
|
||||
}
|
||||
client = std::move(m_networkClient);
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
}
|
||||
client.reset();
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
listenerStorage.NotifyTimeSync({}, NT_EVENT_TIMESYNC, 0, 0, false);
|
||||
m_serverTimeOffset.reset();
|
||||
m_rtt2 = 0;
|
||||
}
|
||||
m_networkClient.reset();
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
}
|
||||
|
||||
void InstanceImpl::SetServers(
|
||||
@@ -178,12 +206,33 @@ std::shared_ptr<INetworkClient> InstanceImpl::GetClient() {
|
||||
return m_networkClient;
|
||||
}
|
||||
|
||||
std::optional<int64_t> InstanceImpl::GetServerTimeOffset() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_serverTimeOffset;
|
||||
}
|
||||
|
||||
void InstanceImpl::AddTimeSyncListener(NT_Listener listener,
|
||||
unsigned int eventMask) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
eventMask &= (NT_EVENT_TIMESYNC | NT_EVENT_IMMEDIATE);
|
||||
listenerStorage.Activate(listener, eventMask);
|
||||
if ((eventMask & (NT_EVENT_TIMESYNC | NT_EVENT_IMMEDIATE)) ==
|
||||
(NT_EVENT_TIMESYNC | NT_EVENT_IMMEDIATE) &&
|
||||
m_serverTimeOffset) {
|
||||
listenerStorage.NotifyTimeSync({&listener, 1},
|
||||
NT_EVENT_TIMESYNC | NT_EVENT_IMMEDIATE,
|
||||
*m_serverTimeOffset, m_rtt2, true);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceImpl::Reset() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_networkServer.reset();
|
||||
m_networkClient.reset();
|
||||
m_servers.clear();
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
m_serverTimeOffset.reset();
|
||||
m_rtt2 = 0;
|
||||
|
||||
listenerStorage.Reset();
|
||||
// connectionList should have been cleared by destroying networkClient/server
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -56,6 +57,9 @@ class InstanceImpl {
|
||||
std::shared_ptr<NetworkServer> GetServer();
|
||||
std::shared_ptr<INetworkClient> GetClient();
|
||||
|
||||
std::optional<int64_t> GetServerTimeOffset();
|
||||
void AddTimeSyncListener(NT_Listener listener, unsigned int eventMask);
|
||||
|
||||
void Reset();
|
||||
|
||||
ListenerStorage listenerStorage;
|
||||
@@ -77,6 +81,8 @@ class InstanceImpl {
|
||||
std::shared_ptr<NetworkServer> m_networkServer;
|
||||
std::shared_ptr<INetworkClient> m_networkClient;
|
||||
std::vector<std::pair<std::string, unsigned int>> m_servers;
|
||||
std::optional<int64_t> m_serverTimeOffset;
|
||||
int64_t m_rtt2 = 0;
|
||||
int m_inst;
|
||||
};
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ void ListenerStorage::Activate(NT_Listener listenerHandle, unsigned int mask,
|
||||
(deltaMask & 0x1ff0000) != 0) {
|
||||
m_logListeners.Add(listener);
|
||||
}
|
||||
if ((deltaMask & NT_EVENT_TIMESYNC) != 0) {
|
||||
m_timeSyncListeners.Add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +240,42 @@ void ListenerStorage::Notify(unsigned int flags, unsigned int level,
|
||||
}
|
||||
}
|
||||
|
||||
void ListenerStorage::NotifyTimeSync(std::span<const NT_Listener> handles,
|
||||
unsigned int flags,
|
||||
int64_t serverTimeOffset, int64_t rtt2,
|
||||
bool valid) {
|
||||
if (flags == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto doSignal = [&](ListenerData& listener) {
|
||||
if ((flags & listener.eventMask) != 0) {
|
||||
for (auto&& [finishEvent, mask] : listener.sources) {
|
||||
if ((flags & mask) != 0) {
|
||||
listener.poller->queue.emplace_back(listener.handle, flags,
|
||||
serverTimeOffset, rtt2, valid);
|
||||
// finishEvent is never set (see InstanceImpl)
|
||||
}
|
||||
}
|
||||
listener.handle.Set();
|
||||
listener.poller->handle.Set();
|
||||
}
|
||||
};
|
||||
|
||||
if (!handles.empty()) {
|
||||
for (auto handle : handles) {
|
||||
if (auto listener = m_listeners.Get(handle)) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto&& listener : m_timeSyncListeners) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NT_Listener ListenerStorage::AddListener(ListenerCallback callback) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (!m_thread) {
|
||||
|
||||
@@ -41,6 +41,8 @@ class ListenerStorage final : public IListenerStorage {
|
||||
NT_Topic topic, NT_Handle subentry, const Value& value) final;
|
||||
void Notify(unsigned int flags, unsigned int level, std::string_view filename,
|
||||
unsigned int line, std::string_view message) final;
|
||||
void NotifyTimeSync(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
int64_t serverTimeOffset, int64_t rtt2, bool valid) final;
|
||||
|
||||
// user-facing functions
|
||||
NT_Listener AddListener(ListenerCallback callback);
|
||||
@@ -105,6 +107,7 @@ class ListenerStorage final : public IListenerStorage {
|
||||
VectorSet<ListenerData*> m_topicListeners;
|
||||
VectorSet<ListenerData*> m_valueListeners;
|
||||
VectorSet<ListenerData*> m_logListeners;
|
||||
VectorSet<ListenerData*> m_timeSyncListeners;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_thread;
|
||||
|
||||
@@ -107,8 +107,11 @@ class NCImpl3 : public NCImpl {
|
||||
|
||||
class NCImpl4 : public NCImpl {
|
||||
public:
|
||||
NCImpl4(int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger);
|
||||
NCImpl4(
|
||||
int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated);
|
||||
~NCImpl4() override;
|
||||
|
||||
void HandleLocal();
|
||||
@@ -116,6 +119,8 @@ class NCImpl4 : public NCImpl {
|
||||
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
m_timeSyncUpdated;
|
||||
std::shared_ptr<net::WebSocketConnection> m_wire;
|
||||
std::unique_ptr<net::ClientImpl> m_clientImpl;
|
||||
};
|
||||
@@ -325,10 +330,13 @@ void NCImpl3::Disconnect(std::string_view reason) {
|
||||
NCImpl::Disconnect(reason);
|
||||
}
|
||||
|
||||
NCImpl4::NCImpl4(int inst, std::string_view id,
|
||||
net::ILocalStorage& localStorage, IConnectionList& connList,
|
||||
wpi::Logger& logger)
|
||||
: NCImpl{inst, id, localStorage, connList, logger} {
|
||||
NCImpl4::NCImpl4(
|
||||
int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated)
|
||||
: NCImpl{inst, id, localStorage, connList, logger},
|
||||
m_timeSyncUpdated{std::move(timeSyncUpdated)} {
|
||||
m_loopRunner.ExecAsync([this](uv::Loop& loop) {
|
||||
m_parallelConnect = wpi::ParallelTcpConnector::Create(
|
||||
loop, kReconnectRate, m_logger,
|
||||
@@ -421,7 +429,7 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(ws);
|
||||
m_clientImpl = std::make_unique<net::ClientImpl>(
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger,
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger, m_timeSyncUpdated,
|
||||
[this](uint32_t repeatMs) {
|
||||
DEBUG4("Setting periodic timer to {}", repeatMs);
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
@@ -453,20 +461,26 @@ void NCImpl4::Disconnect(std::string_view reason) {
|
||||
m_clientImpl.reset();
|
||||
m_wire.reset();
|
||||
NCImpl::Disconnect(reason);
|
||||
m_timeSyncUpdated(0, 0, false);
|
||||
}
|
||||
|
||||
class NetworkClient::Impl final : public NCImpl4 {
|
||||
public:
|
||||
Impl(int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger)
|
||||
: NCImpl4{inst, id, localStorage, connList, logger} {}
|
||||
IConnectionList& connList, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated)
|
||||
: NCImpl4{inst, id, localStorage,
|
||||
connList, logger, std::move(timeSyncUpdated)} {}
|
||||
};
|
||||
|
||||
NetworkClient::NetworkClient(int inst, std::string_view id,
|
||||
net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger)
|
||||
: m_impl{std::make_unique<Impl>(inst, id, localStorage, connList, logger)} {
|
||||
}
|
||||
NetworkClient::NetworkClient(
|
||||
int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated)
|
||||
: m_impl{std::make_unique<Impl>(inst, id, localStorage, connList, logger,
|
||||
std::move(timeSyncUpdated))} {}
|
||||
|
||||
NetworkClient::~NetworkClient() {
|
||||
m_impl->m_localStorage.ClearNetwork();
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -27,8 +29,11 @@ class IConnectionList;
|
||||
|
||||
class NetworkClient final : public INetworkClient {
|
||||
public:
|
||||
NetworkClient(int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger);
|
||||
NetworkClient(
|
||||
int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
IConnectionList& connList, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated);
|
||||
~NetworkClient() final;
|
||||
|
||||
void SetServers(
|
||||
|
||||
@@ -39,7 +39,9 @@ static JClass eventCls;
|
||||
static JClass floatCls;
|
||||
static JClass logMessageCls;
|
||||
static JClass longCls;
|
||||
static JClass optionalLongCls;
|
||||
static JClass pubSubOptionsCls;
|
||||
static JClass timeSyncEventDataCls;
|
||||
static JClass topicInfoCls;
|
||||
static JClass valueCls;
|
||||
static JClass valueEventDataCls;
|
||||
@@ -55,7 +57,9 @@ static const JClassInit classes[] = {
|
||||
{"java/lang/Float", &floatCls},
|
||||
{"edu/wpi/first/networktables/LogMessage", &logMessageCls},
|
||||
{"java/lang/Long", &longCls},
|
||||
{"java/util/OptionalLong", &optionalLongCls},
|
||||
{"edu/wpi/first/networktables/PubSubOptions", &pubSubOptionsCls},
|
||||
{"edu/wpi/first/networktables/TimeSyncEventData", &timeSyncEventDataCls},
|
||||
{"edu/wpi/first/networktables/TopicInfo", &topicInfoCls},
|
||||
{"edu/wpi/first/networktables/NetworkTableValue", &valueCls},
|
||||
{"edu/wpi/first/networktables/ValueEventData", &valueEventDataCls}};
|
||||
@@ -164,6 +168,25 @@ static nt::PubSubOptions FromJavaPubSubOptions(JNIEnv* env, jobject joptions) {
|
||||
// Conversions from C++ to Java objects
|
||||
//
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, std::optional<int64_t> value) {
|
||||
static jmethodID emptyMethod = nullptr;
|
||||
static jmethodID ofMethod = nullptr;
|
||||
if (!emptyMethod) {
|
||||
emptyMethod = env->GetStaticMethodID(optionalLongCls, "empty",
|
||||
"()Ljava/util/OptionalLong;");
|
||||
}
|
||||
if (!ofMethod) {
|
||||
ofMethod = env->GetStaticMethodID(optionalLongCls, "of",
|
||||
"(J)Ljava/util/OptionalLong;");
|
||||
}
|
||||
if (value) {
|
||||
return env->CallStaticObjectMethod(optionalLongCls, ofMethod,
|
||||
static_cast<jlong>(*value));
|
||||
} else {
|
||||
return env->CallStaticObjectMethod(optionalLongCls, emptyMethod);
|
||||
}
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const nt::Value& value) {
|
||||
static jmethodID booleanConstructor = nullptr;
|
||||
static jmethodID doubleConstructor = nullptr;
|
||||
@@ -275,6 +298,15 @@ static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
static_cast<jint>(data.subentry), value.obj());
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const nt::TimeSyncEventData& data) {
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(timeSyncEventDataCls, "<init>", "(JJZ)V");
|
||||
return env->NewObject(timeSyncEventDataCls, constructor,
|
||||
static_cast<jlong>(data.serverTimeOffset),
|
||||
static_cast<jlong>(data.rtt2),
|
||||
static_cast<jboolean>(data.valid));
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst, const nt::Event& event) {
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(eventCls, "<init>",
|
||||
@@ -282,11 +314,13 @@ static jobject MakeJObject(JNIEnv* env, jobject inst, const nt::Event& event) {
|
||||
"Ledu/wpi/first/networktables/ConnectionInfo;"
|
||||
"Ledu/wpi/first/networktables/TopicInfo;"
|
||||
"Ledu/wpi/first/networktables/ValueEventData;"
|
||||
"Ledu/wpi/first/networktables/LogMessage;)V");
|
||||
"Ledu/wpi/first/networktables/LogMessage;"
|
||||
"Ledu/wpi/first/networktables/TimeSyncEventData;)V");
|
||||
JLocal<jobject> connInfo{env, nullptr};
|
||||
JLocal<jobject> topicInfo{env, nullptr};
|
||||
JLocal<jobject> valueData{env, nullptr};
|
||||
JLocal<jobject> logMessage{env, nullptr};
|
||||
JLocal<jobject> timeSyncData{env, nullptr};
|
||||
if (auto v = event.GetConnectionInfo()) {
|
||||
connInfo = JLocal<jobject>{env, MakeJObject(env, *v)};
|
||||
} else if (auto v = event.GetTopicInfo()) {
|
||||
@@ -295,11 +329,13 @@ static jobject MakeJObject(JNIEnv* env, jobject inst, const nt::Event& event) {
|
||||
valueData = JLocal<jobject>{env, MakeJObject(env, inst, *v)};
|
||||
} else if (auto v = event.GetLogMessage()) {
|
||||
logMessage = JLocal<jobject>{env, MakeJObject(env, *v)};
|
||||
} else if (auto v = event.GetTimeSyncEventData()) {
|
||||
timeSyncData = JLocal<jobject>{env, MakeJObject(env, *v)};
|
||||
}
|
||||
return env->NewObject(eventCls, constructor, inst,
|
||||
static_cast<jint>(event.listener),
|
||||
static_cast<jint>(event.flags), connInfo.obj(),
|
||||
topicInfo.obj(), valueData.obj(), logMessage.obj());
|
||||
return env->NewObject(
|
||||
eventCls, constructor, inst, static_cast<jint>(event.listener),
|
||||
static_cast<jint>(event.flags), connInfo.obj(), topicInfo.obj(),
|
||||
valueData.obj(), logMessage.obj(), timeSyncData.obj());
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, std::span<const nt::Value> arr) {
|
||||
@@ -1344,6 +1380,18 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_isConnected
|
||||
return nt::IsConnected(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: getServerTimeOffset
|
||||
* Signature: (I)Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_getServerTimeOffset
|
||||
(JNIEnv* env, jclass, jint inst)
|
||||
{
|
||||
return MakeJObject(env, nt::GetServerTimeOffset(inst));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: now
|
||||
|
||||
@@ -49,6 +49,8 @@ struct PublisherData {
|
||||
class CImpl : public ServerMessageHandler {
|
||||
public:
|
||||
CImpl(uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
@@ -76,6 +78,8 @@ class CImpl : public ServerMessageHandler {
|
||||
WireConnection& m_wire;
|
||||
wpi::Logger& m_logger;
|
||||
LocalInterface* m_local{nullptr};
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
m_timeSyncUpdated;
|
||||
std::function<void(uint32_t repeatMs)> m_setPeriodic;
|
||||
|
||||
// indexed by publisher index
|
||||
@@ -102,12 +106,15 @@ class CImpl : public ServerMessageHandler {
|
||||
|
||||
} // namespace
|
||||
|
||||
CImpl::CImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
|
||||
wpi::Logger& logger,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic)
|
||||
CImpl::CImpl(
|
||||
uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic)
|
||||
: m_inst{inst},
|
||||
m_wire{wire},
|
||||
m_logger{logger},
|
||||
m_timeSyncUpdated{std::move(timeSyncUpdated)},
|
||||
m_setPeriodic{std::move(setPeriodic)},
|
||||
m_nextPingTimeMs{curTimeMs + kPingIntervalMs} {
|
||||
// immediately send RTT ping
|
||||
@@ -151,6 +158,7 @@ void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
m_serverTimeOffsetUs = value.server_time() + rtt2 - now;
|
||||
DEBUG3("Time offset: {}", m_serverTimeOffsetUs);
|
||||
m_haveTimeOffset = true;
|
||||
m_timeSyncUpdated(m_serverTimeOffsetUs, m_rtt2Us, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -424,14 +432,24 @@ void CImpl::ServerPropertiesUpdate(std::string_view name,
|
||||
class ClientImpl::Impl final : public CImpl {
|
||||
public:
|
||||
Impl(uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic)
|
||||
: CImpl{curTimeMs, inst, wire, logger, std::move(setPeriodic)} {}
|
||||
: CImpl{curTimeMs,
|
||||
inst,
|
||||
wire,
|
||||
logger,
|
||||
std::move(timeSyncUpdated),
|
||||
std::move(setPeriodic)} {}
|
||||
};
|
||||
|
||||
ClientImpl::ClientImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
|
||||
wpi::Logger& logger,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic)
|
||||
ClientImpl::ClientImpl(
|
||||
uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic)
|
||||
: m_impl{std::make_unique<Impl>(curTimeMs, inst, wire, logger,
|
||||
std::move(timeSyncUpdated),
|
||||
std::move(setPeriodic))} {}
|
||||
|
||||
ClientImpl::~ClientImpl() = default;
|
||||
|
||||
@@ -32,9 +32,11 @@ class WireConnection;
|
||||
|
||||
class ClientImpl {
|
||||
public:
|
||||
ClientImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
|
||||
wpi::Logger& logger,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
ClientImpl(
|
||||
uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
~ClientImpl();
|
||||
|
||||
void ProcessIncomingText(std::string_view data);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "Value_internal.h"
|
||||
#include "ntcore.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
@@ -52,6 +53,12 @@ static void ConvertToC(const LogMessage& in, NT_LogMessage* out) {
|
||||
ConvertToC(in.message, &out->message);
|
||||
}
|
||||
|
||||
static void ConvertToC(const TimeSyncEventData& in, NT_TimeSyncEventData* out) {
|
||||
out->serverTimeOffset = in.serverTimeOffset;
|
||||
out->rtt2 = in.rtt2;
|
||||
out->valid = in.valid;
|
||||
}
|
||||
|
||||
static void ConvertToC(const Event& in, NT_Event* out) {
|
||||
out->listener = in.listener;
|
||||
out->flags = in.flags;
|
||||
@@ -71,6 +78,10 @@ static void ConvertToC(const Event& in, NT_Event* out) {
|
||||
if (auto v = in.GetLogMessage()) {
|
||||
return ConvertToC(*v, &out->data.logMessage);
|
||||
}
|
||||
} else if ((in.flags & NT_EVENT_TIMESYNC) != 0) {
|
||||
if (auto v = in.GetTimeSyncEventData()) {
|
||||
return ConvertToC(*v, &out->data.timeSyncData);
|
||||
}
|
||||
}
|
||||
out->flags = NT_EVENT_NONE; // sanity to make sure we don't dispose
|
||||
}
|
||||
@@ -551,15 +562,25 @@ struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count) {
|
||||
return ConvertToC<NT_ConnectionInfo>(conn_v, count);
|
||||
}
|
||||
|
||||
int64_t NT_GetServerTimeOffset(NT_Inst inst, NT_Bool* valid) {
|
||||
if (auto v = nt::GetServerTimeOffset(inst)) {
|
||||
*valid = true;
|
||||
return *v;
|
||||
} else {
|
||||
*valid = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility Functions
|
||||
*/
|
||||
|
||||
uint64_t NT_Now(void) {
|
||||
int64_t NT_Now(void) {
|
||||
return nt::Now();
|
||||
}
|
||||
|
||||
void NT_SetNow(uint64_t timestamp) {
|
||||
void NT_SetNow(int64_t timestamp) {
|
||||
nt::SetNow(timestamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -422,6 +422,9 @@ static void DoAddListener(InstanceImpl& ii, NT_Listener listener,
|
||||
if ((mask & NT_EVENT_LOGMESSAGE) != 0) {
|
||||
ii.logger_impl.AddListener(listener, NT_LOG_INFO, UINT_MAX);
|
||||
}
|
||||
if ((mask & NT_EVENT_TIMESYNC) != 0) {
|
||||
ii.AddTimeSyncListener(listener, mask);
|
||||
}
|
||||
} else if ((mask & (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL)) != 0) {
|
||||
ii.localStorage.AddListener(listener, handle, mask);
|
||||
}
|
||||
@@ -735,6 +738,14 @@ bool IsConnected(NT_Inst inst) {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int64_t> GetServerTimeOffset(NT_Inst inst) {
|
||||
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
|
||||
return ii->GetServerTimeOffset();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
NT_Listener AddLogger(NT_Inst inst, unsigned int minLevel,
|
||||
unsigned int maxLevel, ListenerCallback func) {
|
||||
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -391,6 +392,20 @@ class NetworkTableInstance final {
|
||||
NT_Listener AddConnectionListener(bool immediate_notify,
|
||||
ListenerCallback callback) const;
|
||||
|
||||
/**
|
||||
* Add a time synchronization listener. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use
|
||||
* synchronization or atomics when accessing any shared state from the
|
||||
* callback function.
|
||||
*
|
||||
* @param immediate_notify notify listener of current time synchronization
|
||||
* value
|
||||
* @param callback listener to add
|
||||
* @return Listener handle
|
||||
*/
|
||||
NT_Listener AddTimeSyncListener(bool immediate_notify,
|
||||
ListenerCallback callback) const;
|
||||
|
||||
/**
|
||||
* Add a listener for changes on a particular topic. The callback
|
||||
* function is called asynchronously on a separate thread, so it's important
|
||||
@@ -614,6 +629,19 @@ class NetworkTableInstance final {
|
||||
*/
|
||||
bool IsConnected() const;
|
||||
|
||||
/**
|
||||
* Get the time offset between server time and local time. Add this value to
|
||||
* local time to get the estimated equivalent server time. In server mode,
|
||||
* this always returns 0. In client mode, this returns the time offset only if
|
||||
* the client and server are connected and have exchanged synchronization
|
||||
* messages. Note the time offset may change over time as it is periodically
|
||||
* updated; to receive updates as events, add a listener to the "time sync"
|
||||
* event.
|
||||
*
|
||||
* @return Time offset in microseconds (optional)
|
||||
*/
|
||||
std::optional<int64_t> GetServerTimeOffset() const;
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,6 +100,13 @@ inline NT_Listener NetworkTableInstance::AddConnectionListener(
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
inline NT_Listener NetworkTableInstance::AddTimeSyncListener(
|
||||
bool immediate_notify, ListenerCallback callback) const {
|
||||
return ::nt::AddListener(
|
||||
m_handle, NT_EVENT_TIMESYNC | (immediate_notify ? NT_EVENT_IMMEDIATE : 0),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
inline NT_Listener NetworkTableInstance::AddListener(
|
||||
std::span<const std::string_view> prefixes, int eventMask,
|
||||
ListenerCallback listener) {
|
||||
@@ -181,6 +188,11 @@ inline bool NetworkTableInstance::IsConnected() const {
|
||||
return ::nt::IsConnected(m_handle);
|
||||
}
|
||||
|
||||
inline std::optional<int64_t> NetworkTableInstance::GetServerTimeOffset()
|
||||
const {
|
||||
return ::nt::GetServerTimeOffset(m_handle);
|
||||
}
|
||||
|
||||
inline NT_DataLogger NetworkTableInstance::StartEntryDataLog(
|
||||
wpi::log::DataLog& log, std::string_view prefix,
|
||||
std::string_view logPrefix) {
|
||||
|
||||
@@ -106,6 +106,19 @@ class NetworkTableListener final {
|
||||
NetworkTableInstance inst, bool immediate_notify,
|
||||
ListenerCallback listener);
|
||||
|
||||
/**
|
||||
* Create a time synchronization listener.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param immediate_notify notify listener of current time synchronization
|
||||
* value
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
static NetworkTableListener CreateTimeSyncListener(NetworkTableInstance inst,
|
||||
bool immediate_notify,
|
||||
ListenerCallback listener);
|
||||
|
||||
/**
|
||||
* Create a listener for log messages. By default, log messages are sent to
|
||||
* stderr; this function sends log messages with the specified levels to the
|
||||
@@ -251,6 +264,18 @@ class NetworkTableListenerPoller final {
|
||||
*/
|
||||
NT_Listener AddConnectionListener(bool immediate_notify);
|
||||
|
||||
/**
|
||||
* Add a time synchronization listener. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use
|
||||
* synchronization or atomics when accessing any shared state from the
|
||||
* callback function.
|
||||
*
|
||||
* @param immediate_notify notify listener of current time synchronization
|
||||
* value
|
||||
* @return Listener handle
|
||||
*/
|
||||
NT_Listener AddTimeSyncListener(bool immediate_notify);
|
||||
|
||||
/**
|
||||
* Add logger callback function. By default, log messages are sent to stderr;
|
||||
* this function sends log messages with the specified levels to the provided
|
||||
|
||||
@@ -58,6 +58,15 @@ inline NetworkTableListener NetworkTableListener::CreateConnectionListener(
|
||||
std::move(listener))};
|
||||
}
|
||||
|
||||
inline NetworkTableListener NetworkTableListener::CreateTimeSyncListener(
|
||||
NetworkTableInstance inst, bool immediate_notify,
|
||||
ListenerCallback listener) {
|
||||
return NetworkTableListener{::nt::AddListener(
|
||||
inst.GetHandle(),
|
||||
NT_EVENT_TIMESYNC | (immediate_notify ? NT_EVENT_IMMEDIATE : 0),
|
||||
std::move(listener))};
|
||||
}
|
||||
|
||||
inline NetworkTableListener NetworkTableListener::CreateLogger(
|
||||
NetworkTableInstance inst, unsigned int minLevel, unsigned int maxLevel,
|
||||
ListenerCallback listener) {
|
||||
@@ -152,6 +161,13 @@ inline NT_Listener NetworkTableListenerPoller::AddConnectionListener(
|
||||
NT_EVENT_CONNECTION | (immediate_notify ? NT_EVENT_IMMEDIATE : 0));
|
||||
}
|
||||
|
||||
inline NT_Listener NetworkTableListenerPoller::AddTimeSyncListener(
|
||||
bool immediate_notify) {
|
||||
return ::nt::AddPolledListener(
|
||||
m_handle, ::nt::GetInstanceFromHandle(m_handle),
|
||||
NT_EVENT_TIMESYNC | (immediate_notify ? NT_EVENT_IMMEDIATE : 0));
|
||||
}
|
||||
|
||||
inline NT_Listener NetworkTableListenerPoller::AddLogger(
|
||||
unsigned int minLevel, unsigned int maxLevel) {
|
||||
return ::nt::AddPolledLogger(m_handle, minLevel, maxLevel);
|
||||
|
||||
@@ -115,6 +115,8 @@ enum NT_EventFlags {
|
||||
NT_EVENT_VALUE_ALL = NT_EVENT_VALUE_REMOTE | NT_EVENT_VALUE_LOCAL,
|
||||
/** Log message. */
|
||||
NT_EVENT_LOGMESSAGE = 0x100,
|
||||
/** Time synchronized with server. */
|
||||
NT_EVENT_TIMESYNC = 0x200,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -247,6 +249,24 @@ struct NT_LogMessage {
|
||||
char* message;
|
||||
};
|
||||
|
||||
/** NetworkTables time sync event data. */
|
||||
struct NT_TimeSyncEventData {
|
||||
/**
|
||||
* Offset between local time and server time, in microseconds. Add this value
|
||||
* to local time to get the estimated equivalent server time.
|
||||
*/
|
||||
int64_t serverTimeOffset;
|
||||
|
||||
/** Measured round trip time divided by 2, in microseconds. */
|
||||
int64_t rtt2;
|
||||
|
||||
/**
|
||||
* If serverTimeOffset and RTT are valid. An event with this set to false is
|
||||
* sent when the client disconnects.
|
||||
*/
|
||||
NT_Bool valid;
|
||||
};
|
||||
|
||||
/** NetworkTables event */
|
||||
struct NT_Event {
|
||||
/** Listener that triggered this event. */
|
||||
@@ -259,6 +279,7 @@ struct NT_Event {
|
||||
* - NT_EVENT_PUBLISH, NT_EVENT_UNPUBLISH, or NT_EVENT_PROPERTIES: topicInfo
|
||||
* - NT_EVENT_VALUE_REMOTE, NT_NOTIFY_VALUE_LOCAL: valueData
|
||||
* - NT_EVENT_LOGMESSAGE: logMessage
|
||||
* - NT_EVENT_TIMESYNC: timeSyncData
|
||||
*/
|
||||
unsigned int flags;
|
||||
|
||||
@@ -268,6 +289,7 @@ struct NT_Event {
|
||||
struct NT_TopicInfo topicInfo;
|
||||
struct NT_ValueEventData valueData;
|
||||
struct NT_LogMessage logMessage;
|
||||
struct NT_TimeSyncEventData timeSyncData;
|
||||
} data;
|
||||
};
|
||||
|
||||
@@ -1190,6 +1212,22 @@ struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count);
|
||||
*/
|
||||
NT_Bool NT_IsConnected(NT_Inst inst);
|
||||
|
||||
/**
|
||||
* Get the time offset between server time and local time. Add this value to
|
||||
* local time to get the estimated equivalent server time. In server mode, this
|
||||
* always returns a valid value of 0. In client mode, this returns the time
|
||||
* offset only if the client and server are connected and have exchanged
|
||||
* synchronization messages. Note the time offset may change over time as it is
|
||||
* periodically updated; to receive updates as events, add a listener to the
|
||||
* "time sync" event.
|
||||
*
|
||||
* @param inst instance handle
|
||||
* @param valid set to true if the return value is valid, false otherwise
|
||||
* (output)
|
||||
* @return Time offset in microseconds (if valid is set to true)
|
||||
*/
|
||||
int64_t NT_GetServerTimeOffset(NT_Inst inst, NT_Bool* valid);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@@ -1286,7 +1324,7 @@ void NT_DisposeEvent(struct NT_Event* event);
|
||||
*
|
||||
* @return Timestamp
|
||||
*/
|
||||
uint64_t NT_Now(void);
|
||||
int64_t NT_Now(void);
|
||||
|
||||
/**
|
||||
* Sets the current timestamp used for timestamping values that do not
|
||||
@@ -1297,7 +1335,7 @@ uint64_t NT_Now(void);
|
||||
*
|
||||
* @param timestamp timestamp (1 us increments)
|
||||
*/
|
||||
void NT_SetNow(uint64_t timestamp);
|
||||
void NT_SetNow(int64_t timestamp);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -80,6 +81,8 @@ struct EventFlags {
|
||||
static constexpr unsigned int kValueAll = kValueRemote | kValueLocal;
|
||||
/** Log message. */
|
||||
static constexpr unsigned int kLogMessage = NT_EVENT_LOGMESSAGE;
|
||||
/** Time synchronized with server. */
|
||||
static constexpr unsigned int kTimeSync = NT_EVENT_TIMESYNC;
|
||||
};
|
||||
|
||||
/** NetworkTables Topic Information */
|
||||
@@ -186,6 +189,29 @@ class LogMessage {
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/** NetworkTables time sync event data. */
|
||||
class TimeSyncEventData {
|
||||
public:
|
||||
TimeSyncEventData() = default;
|
||||
TimeSyncEventData(int64_t serverTimeOffset, int64_t rtt2, bool valid)
|
||||
: serverTimeOffset{serverTimeOffset}, rtt2{rtt2}, valid{valid} {}
|
||||
|
||||
/**
|
||||
* Offset between local time and server time, in microseconds. Add this value
|
||||
* to local time to get the estimated equivalent server time.
|
||||
*/
|
||||
int64_t serverTimeOffset;
|
||||
|
||||
/** Measured round trip time divided by 2, in microseconds. */
|
||||
int64_t rtt2;
|
||||
|
||||
/**
|
||||
* If serverTimeOffset and RTT are valid. An event with this set to false is
|
||||
* sent when the client disconnects.
|
||||
*/
|
||||
bool valid;
|
||||
};
|
||||
|
||||
/** NetworkTables event */
|
||||
class Event {
|
||||
public:
|
||||
@@ -208,6 +234,11 @@ class Event {
|
||||
: listener{listener},
|
||||
flags{flags},
|
||||
data{LogMessage{level, filename, line, message}} {}
|
||||
Event(NT_Listener listener, unsigned int flags, int64_t serverTimeOffset,
|
||||
int64_t rtt2, bool valid)
|
||||
: listener{listener},
|
||||
flags{flags},
|
||||
data{TimeSyncEventData{serverTimeOffset, rtt2, valid}} {}
|
||||
|
||||
/** Listener that triggered this event. */
|
||||
NT_Listener listener{0};
|
||||
@@ -215,11 +246,12 @@ class Event {
|
||||
/**
|
||||
* Event flags (NT_EventFlags). Also indicates the data included with the
|
||||
* event:
|
||||
* - NT_NOTIFY_CONNECTED or NT_NOTIFY_DISCONNECTED: GetConnectionInfo()
|
||||
* - NT_NOTIFY_PUBLISH, NT_NOTIFY_UNPUBLISH, or NT_NOTIFY_PROPERTIES:
|
||||
* - NT_EVENT_CONNECTED or NT_EVENT_DISCONNECTED: GetConnectionInfo()
|
||||
* - NT_EVENT_PUBLISH, NT_EVENT_UNPUBLISH, or NT_EVENT_PROPERTIES:
|
||||
* GetTopicInfo()
|
||||
* - NT_NOTIFY_VALUE, NT_NOTIFY_VALUE_LOCAL: GetValueData()
|
||||
* - NT_NOTIFY_LOGMESSAGE: GetLogMessage()
|
||||
* - NT_EVENT_VALUE, NT_EVENT_VALUE_LOCAL: GetValueData()
|
||||
* - NT_EVENT_LOGMESSAGE: GetLogMessage()
|
||||
* - NT_EVENT_TIMESYNC: GetTimeSyncEventData()
|
||||
*/
|
||||
unsigned int flags{0};
|
||||
|
||||
@@ -232,31 +264,40 @@ class Event {
|
||||
bool Is(unsigned int kind) const { return (flags & kind) != 0; }
|
||||
|
||||
/** Event data; content depends on flags. */
|
||||
std::variant<ConnectionInfo, TopicInfo, ValueEventData, LogMessage> data;
|
||||
std::variant<ConnectionInfo, TopicInfo, ValueEventData, LogMessage,
|
||||
TimeSyncEventData>
|
||||
data;
|
||||
|
||||
const ConnectionInfo* GetConnectionInfo() const {
|
||||
return std::get_if<nt::ConnectionInfo>(&data);
|
||||
return std::get_if<ConnectionInfo>(&data);
|
||||
}
|
||||
ConnectionInfo* GetConnectionInfo() {
|
||||
return std::get_if<nt::ConnectionInfo>(&data);
|
||||
return std::get_if<ConnectionInfo>(&data);
|
||||
}
|
||||
|
||||
const TopicInfo* GetTopicInfo() const {
|
||||
return std::get_if<nt::TopicInfo>(&data);
|
||||
return std::get_if<TopicInfo>(&data);
|
||||
}
|
||||
TopicInfo* GetTopicInfo() { return std::get_if<nt::TopicInfo>(&data); }
|
||||
TopicInfo* GetTopicInfo() { return std::get_if<TopicInfo>(&data); }
|
||||
|
||||
const ValueEventData* GetValueEventData() const {
|
||||
return std::get_if<nt::ValueEventData>(&data);
|
||||
return std::get_if<ValueEventData>(&data);
|
||||
}
|
||||
ValueEventData* GetValueEventData() {
|
||||
return std::get_if<nt::ValueEventData>(&data);
|
||||
return std::get_if<ValueEventData>(&data);
|
||||
}
|
||||
|
||||
const LogMessage* GetLogMessage() const {
|
||||
return std::get_if<nt::LogMessage>(&data);
|
||||
return std::get_if<LogMessage>(&data);
|
||||
}
|
||||
LogMessage* GetLogMessage() { return std::get_if<LogMessage>(&data); }
|
||||
|
||||
const TimeSyncEventData* GetTimeSyncEventData() const {
|
||||
return std::get_if<TimeSyncEventData>(&data);
|
||||
}
|
||||
TimeSyncEventData* GetTimeSyncEventData() {
|
||||
return std::get_if<TimeSyncEventData>(&data);
|
||||
}
|
||||
LogMessage* GetLogMessage() { return std::get_if<nt::LogMessage>(&data); }
|
||||
};
|
||||
|
||||
/** NetworkTables publish/subscribe options. */
|
||||
@@ -1107,6 +1148,19 @@ std::vector<ConnectionInfo> GetConnections(NT_Inst inst);
|
||||
*/
|
||||
bool IsConnected(NT_Inst inst);
|
||||
|
||||
/**
|
||||
* Get the time offset between server time and local time. Add this value to
|
||||
* local time to get the estimated equivalent server time. In server mode, this
|
||||
* always returns 0. In client mode, this returns the time offset only if the
|
||||
* client and server are connected and have exchanged synchronization messages.
|
||||
* Note the time offset may change over time as it is periodically updated; to
|
||||
* receive updates as events, add a listener to the "time sync" event.
|
||||
*
|
||||
* @param inst instance handle
|
||||
* @return Time offset in microseconds (optional)
|
||||
*/
|
||||
std::optional<int64_t> GetServerTimeOffset(NT_Inst inst);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user