From 77edf1e103aff250d9e83d447b357064c8b2c988 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 4 Nov 2016 16:01:42 -0700 Subject: [PATCH] Add FRC Driver Station connection support. The 2017 FRC Driver Station supports getting the robot IP via a TCP connection that returns JSON. Use this to support overriding the server IP address used for client connections. Default to using this approach for client connections in the NetworkTable interfaces. Add support for setting the server address without stopping and restarting the client. SetTeam now also round-robins by default. --- include/networktables/NetworkTable.h | 7 + include/ntcore_c.h | 36 +++++ include/ntcore_cpp.h | 5 + java/lib/NetworkTablesJNI.cpp | 80 ++++++++++ .../wpilibj/networktables/NetworkTable.java | 65 +++++--- .../networktables/NetworkTablesJNI.java | 5 + ntcore-jni.def | 10 ++ ntcore.def | 5 + src/Dispatcher.cpp | 59 +++++-- src/Dispatcher.h | 18 ++- src/DsClient.cpp | 150 ++++++++++++++++++ src/DsClient.h | 38 +++++ src/networktables/NetworkTable.cpp | 84 +++++++--- src/ntcore_c.cpp | 19 +++ src/ntcore_cpp.cpp | 23 ++- 15 files changed, 538 insertions(+), 66 deletions(-) create mode 100644 src/DsClient.cpp create mode 100644 src/DsClient.h diff --git a/include/networktables/NetworkTable.h b/include/networktables/NetworkTable.h index fa751c9e19..9be56baf03 100644 --- a/include/networktables/NetworkTable.h +++ b/include/networktables/NetworkTable.h @@ -29,6 +29,7 @@ class NetworkTable : public ITable { static std::vector s_ip_addresses; static std::string s_persistent_filename; static bool s_client; + static bool s_enable_ds; static bool s_running; static unsigned int s_port; @@ -86,6 +87,12 @@ class NetworkTable : public ITable { */ static void SetPort(unsigned int port); + /** + * @param enabled whether to enable the connection to the local DS to get + * the robot IP address (defaults to enabled) + */ + static void SetDSClientEnabled(bool enabled); + /** * Sets the persistent filename. * @param filename the filename that the network tables server uses for diff --git a/include/ntcore_c.h b/include/ntcore_c.h index d51c31d575..0744e4a609 100644 --- a/include/ntcore_c.h +++ b/include/ntcore_c.h @@ -362,6 +362,11 @@ void NT_StartServer(const char* persist_filename, const char* listen_address, */ void NT_StopServer(void); +/** Starts Client + * Starts a client. Use NT_SetServer to set the server name and port. + */ +void NT_StartClientNone(void); + /** Starts Client * Starts a client using the specified server and port * @@ -389,6 +394,37 @@ void NT_StartClientMulti(size_t count, const char** server_names, */ void NT_StopClient(void); +/** Sets server address for client (without restarting client). + * + * @param server_name server name (UTF-8 string, null terminated) + * @param port port to communicate over + * + */ +void NT_SetServer(const char* server_name, unsigned int port); + +/** Sets server addresses for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param count length of the server_names and ports arrays + * @param server_names array of server names (each a UTF-8 string, null + * terminated) + * @param ports array of ports to communicate over (one for each server) + * + */ +void NT_SetServerMulti(size_t count, const char** server_names, + const unsigned int* ports); + +/** Starts requesting server address from Driver Station. + * This connects to the Driver Station running on localhost to obtain the + * server IP address. + * + * @param port server port to use in combination with IP from DS + */ +void NT_StartDSClient(unsigned int port); + +/** Stops requesting server address from Driver Station. */ +void NT_StopDSClient(void); + /** Stop Rpc Server * Stops the Rpc server if it is running. */ diff --git a/include/ntcore_cpp.h b/include/ntcore_cpp.h index 7d7a69a565..67928f7eb8 100644 --- a/include/ntcore_cpp.h +++ b/include/ntcore_cpp.h @@ -255,9 +255,14 @@ void SetNetworkIdentity(StringRef name); void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); void StopServer(); +void StartClient(); void StartClient(const char* server_name, unsigned int port); void StartClient(ArrayRef> servers); void StopClient(); +void SetServer(const char* server_name, unsigned int port); +void SetServer(ArrayRef> servers); +void StartDSClient(unsigned int port); +void StopDSClient(); void StopRpcServer(); void StopNotifier(); void SetUpdateRate(double interval); diff --git a/java/lib/NetworkTablesJNI.cpp b/java/lib/NetworkTablesJNI.cpp index 1dae51d831..03c77ebdb4 100644 --- a/java/lib/NetworkTablesJNI.cpp +++ b/java/lib/NetworkTablesJNI.cpp @@ -1147,6 +1147,17 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI nt::StopServer(); } +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: startClient + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__ + (JNIEnv *env, jclass) +{ + nt::StartClient(); +} + /* * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI * Method: startClient @@ -1205,6 +1216,75 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI nt::StopClient(); } +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: setServer + * Signature: (Ljava/lang/String;I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer__Ljava_lang_String_2I + (JNIEnv *env, jclass, jstring serverName, jint port) +{ + nt::SetServer(JStringRef{env, serverName}.c_str(), port); +} + +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: setServer + * Signature: ([Ljava/lang/String;[I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer___3Ljava_lang_String_2_3I + (JNIEnv *env, jclass, jobjectArray serverNames, jintArray ports) +{ + int len = env->GetArrayLength(serverNames); + if (len != env->GetArrayLength(ports)) { + env->ThrowNew(illegalArgEx, + "serverNames and ports arrays must be the same size"); + return; + } + jint* portInts = env->GetIntArrayElements(ports, nullptr); + if (!portInts) return; + + std::vector names; + std::vector> servers; + names.reserve(len); + servers.reserve(len); + for (int i = 0; i < len; ++i) { + JLocal elem{ + env, static_cast(env->GetObjectArrayElement(serverNames, i))}; + if (!elem) { + env->ThrowNew(illegalArgEx, "null string in serverNames"); + return; + } + names.emplace_back(JStringRef{env, elem}.str()); + servers.emplace_back(std::make_pair(nt::StringRef(names.back()), + portInts[i])); + } + env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT); + nt::SetServer(servers); +} + +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: startDSClient + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startDSClient + (JNIEnv *env, jclass, jint port) +{ + nt::StartDSClient(port); +} + +/* + * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Method: stopDSClient + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopDSClient + (JNIEnv *env, jclass) +{ + nt::StopDSClient(); +} + /* * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI * Method: setUpdateRate diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java index 0c4d2169f7..c9194db5fa 100644 --- a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java @@ -21,9 +21,9 @@ public class NetworkTable implements ITable, IRemote { public static final int DEFAULT_PORT = 1735; private static boolean client = false; + private static boolean enableDS = true; private static boolean running = false; private static int port = DEFAULT_PORT; - private static String[] ipAddresses = new String[0]; private static String persistentFilename = "networktables.ini"; private synchronized static void checkInit() { @@ -39,10 +39,9 @@ public class NetworkTable implements ITable, IRemote { if (running) shutdown(); if (client) { - int[] ports = new int[ipAddresses.length]; - for (int i=0; i 0 && + (addresses[0].equals("localhost") || addresses[0].equals("127.0.0.1"))) + NetworkTablesJNI.stopDSClient(); + else if (enableDS) + NetworkTablesJNI.startDSClient(port); } /** @@ -136,6 +137,18 @@ public class NetworkTable implements ITable, IRemote { port = aport; } + /** + * @param enabled whether to enable the connection to the local DS to get + * the robot IP address (defaults to enabled) + */ + public synchronized static void setDSClientEnabled(boolean enabled) { + enableDS = enabled; + if (enableDS) + NetworkTablesJNI.startDSClient(port); + else + NetworkTablesJNI.stopDSClient(); + } + /** * Sets the persistent filename. * @param filename the filename that the network tables server uses for diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java index 7d3df88a79..236e7c0c71 100644 --- a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java @@ -148,9 +148,14 @@ public class NetworkTablesJNI { public static native void setNetworkIdentity(String name); public static native void startServer(String persistFilename, String listenAddress, int port); public static native void stopServer(); + public static native void startClient(); public static native void startClient(String serverName, int port); public static native void startClient(String[] serverNames, int[] ports); public static native void stopClient(); + public static native void setServer(String serverName, int port); + public static native void setServer(String[] serverNames, int[] ports); + public static native void startDSClient(int port); + public static native void stopDSClient(); public static native void setUpdateRate(double interval); public static native ConnectionInfo[] getConnections(); diff --git a/ntcore-jni.def b/ntcore-jni.def index 2e9d82229b..9970a95f57 100644 --- a/ntcore-jni.def +++ b/ntcore-jni.def @@ -113,6 +113,11 @@ NT_FreeRpcResultsDefForTesting @114 NT_GetRpcDefinitionForTesting @115 NT_GetRpcCallInfoForTesting @116 +NT_SetServer @117 +NT_SetServerMulti @118 +NT_StartDSClient @119 +NT_StopDSClient @120 + ; JNI functions JNI_OnLoad JNI_OnUnload @@ -167,9 +172,14 @@ Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_St Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setNetworkIdentity Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startServer Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopServer +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__ Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__Ljava_lang_String_2I Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient___3Ljava_lang_String_2_3I Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopClient +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer__Ljava_lang_String_2I +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer___3Ljava_lang_String_2_3I +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startDSClient +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopDSClient Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setUpdateRate Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getConnections Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_savePersistent diff --git a/ntcore.def b/ntcore.def index 9b7320df31..f47d22f366 100644 --- a/ntcore.def +++ b/ntcore.def @@ -112,3 +112,8 @@ NT_GetRpcResultsDefForTesting @113 NT_FreeRpcResultsDefForTesting @114 NT_GetRpcDefinitionForTesting @115 NT_GetRpcCallInfoForTesting @116 + +NT_SetServer @117 +NT_SetServerMulti @118 +NT_StartDSClient @119 +NT_StopDSClient @120 diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 45642f52a3..c65514be53 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -26,16 +26,16 @@ void Dispatcher::StartServer(llvm::StringRef persist_filename, static_cast(port), listen_address, Logger::GetInstance()))); } -void Dispatcher::StartClient(const char* server_name, unsigned int port) { +void Dispatcher::SetServer(const char* server_name, unsigned int port) { std::string server_name_copy(server_name); - DispatcherBase::StartClient([=]() -> std::unique_ptr { + SetConnector([=]() -> std::unique_ptr { return wpi::TCPConnector::connect(server_name_copy.c_str(), static_cast(port), Logger::GetInstance(), 1); }); } -void Dispatcher::StartClient( +void Dispatcher::SetServer( ArrayRef> servers) { std::vector connectors; for (const auto& server : servers) { @@ -47,9 +47,20 @@ void Dispatcher::StartClient( Logger::GetInstance(), 1); }); } - DispatcherBase::StartClient(std::move(connectors)); + SetConnector(std::move(connectors)); } +void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) { + std::string server_name_copy(server_name); + SetConnectorOverride([=]() -> std::unique_ptr { + return wpi::TCPConnector::connect(server_name_copy.c_str(), + static_cast(port), + Logger::GetInstance(), 1); + }); +} + +void Dispatcher::ClearServerOverride() { ClearConnectorOverride(); } + Dispatcher::Dispatcher() : Dispatcher(Storage::GetInstance(), Notifier::GetInstance()) {} @@ -98,18 +109,11 @@ void DispatcherBase::StartServer( m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this); } -void DispatcherBase::StartClient(Connector connector) { - std::vector connectors; - connectors.push_back(connector); - StartClient(std::move(connectors)); -} - -void DispatcherBase::StartClient(std::vector&& connectors) { +void DispatcherBase::StartClient() { { std::lock_guard lock(m_user_mutex); if (m_active) return; m_active = true; - m_client_connectors = std::move(connectors); } m_server = false; using namespace std::placeholders; @@ -195,6 +199,27 @@ void DispatcherBase::NotifyConnections( for (const auto& conn : m_connections) conn->NotifyIfActive(callback); } +void DispatcherBase::SetConnector(Connector connector) { + std::vector connectors; + connectors.push_back(connector); + SetConnector(std::move(connectors)); +} + +void DispatcherBase::SetConnector(std::vector&& connectors) { + std::lock_guard lock(m_user_mutex); + m_client_connectors = std::move(connectors); +} + +void DispatcherBase::SetConnectorOverride(Connector connector) { + std::lock_guard lock(m_user_mutex); + m_client_connector_override = std::move(connector); +} + +void DispatcherBase::ClearConnectorOverride() { + std::lock_guard lock(m_user_mutex); + m_client_connector_override = nullptr; +} + void DispatcherBase::DispatchThreadMain() { auto timeout_time = std::chrono::steady_clock::now(); @@ -320,9 +345,13 @@ void DispatcherBase::ClientThreadMain() { // get next server to connect to { std::lock_guard lock(m_user_mutex); - if (m_client_connectors.empty()) continue; - if (i >= m_client_connectors.size()) i = 0; - connect = m_client_connectors[i++]; + if (m_client_connector_override) { + connect = m_client_connector_override; + } else { + if (m_client_connectors.empty()) continue; + if (i >= m_client_connectors.size()) i = 0; + connect = m_client_connectors[i++]; + } } // try to connect (with timeout) diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 87b97e29f2..2bfa228bb6 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -41,8 +41,7 @@ class DispatcherBase { void StartServer(llvm::StringRef persist_filename, std::unique_ptr acceptor); - void StartClient(Connector connector); - void StartClient(std::vector&& connectors); + void StartClient(); void Stop(); void SetUpdateRate(double interval); void SetIdentity(llvm::StringRef name); @@ -50,6 +49,12 @@ class DispatcherBase { std::vector GetConnections() const; void NotifyConnections(ConnectionListenerCallback callback) const; + void SetConnector(Connector connector); + void SetConnector(std::vector&& connectors); + + void SetConnectorOverride(Connector connector); + void ClearConnectorOverride(); + bool active() const { return m_active; } DispatcherBase(const DispatcherBase&) = delete; @@ -85,6 +90,7 @@ class DispatcherBase { std::thread m_clientserver_thread; std::unique_ptr m_server_acceptor; + Connector m_client_connector_override; std::vector m_client_connectors; // Mutex for user-accessible items @@ -118,8 +124,12 @@ class Dispatcher : public DispatcherBase { void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); - void StartClient(const char* server_name, unsigned int port); - void StartClient(ArrayRef> servers); + + void SetServer(const char* server_name, unsigned int port); + void SetServer(ArrayRef> servers); + + void SetServerOverride(const char* server_name, unsigned int port); + void ClearServerOverride(); private: Dispatcher(); diff --git a/src/DsClient.cpp b/src/DsClient.cpp new file mode 100644 index 0000000000..1e2c5c2e07 --- /dev/null +++ b/src/DsClient.cpp @@ -0,0 +1,150 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "DsClient.h" + +#include "llvm/raw_ostream.h" +#include "llvm/SmallString.h" +#include "support/raw_socket_istream.h" +#include "tcpsockets/TCPConnector.h" + +#include "Dispatcher.h" +#include "Log.h" + +using namespace nt; + +ATOMIC_STATIC_INIT(DsClient) + +class DsClient::Thread : public wpi::SafeThread { + public: + Thread(unsigned int port) : m_port(port) {} + + void Main(); + + unsigned int m_port; + std::unique_ptr m_stream; +}; + +void DsClient::Start(unsigned int port) { + auto thr = m_owner.GetThread(); + if (!thr) + m_owner.Start(new Thread(port)); + else + thr->m_port = port; +} + +void DsClient::Stop() { + { + // Close the stream so the read (if any) terminates. + auto thr = m_owner.GetThread(); + if (thr) { + thr->m_active = false; + if (thr->m_stream) thr->m_stream->close(); + } + } + m_owner.Stop(); +} + +void DsClient::Thread::Main() { + unsigned int oldip = 0; + wpi::Logger nolog; // to silence log messages from TCPConnector + + while (m_active) { + // wait for periodic reconnect or termination + auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::milliseconds(500); + unsigned int port; + { + std::unique_lock lock(m_mutex); + m_cond.wait_until(lock, timeout_time, [&] { return !m_active; }); + port = m_port; + } + if (!m_active) goto done; + + // Try to connect to DS on the local machine + m_stream = + wpi::TCPConnector::connect("127.0.0.1", 1742, nolog, 1); + if (!m_active) goto done; + if (!m_stream) continue; + + DEBUG3("connected to DS"); + wpi::raw_socket_istream is(*m_stream); + + while (m_active && !is.has_error()) { + // Read JSON "{...}". This is very limited, does not handle quoted "}" or + // nested {}, but is sufficient for this purpose. + llvm::SmallString<128> json; + char ch; + + // Throw away characters until { + do { + is.read(ch); + if (is.has_error()) break; + if (!m_active) goto done; + } while (ch != '{'); + json += '{'; + + if (is.has_error()) { + m_stream = nullptr; + break; + } + + // Read characters until } + do { + is.read(ch); + if (is.has_error()) break; + if (!m_active) goto done; + json += ch; + } while (ch != '}'); + + if (is.has_error()) { + m_stream = nullptr; + break; + } + DEBUG3("json=" << json); + + // Look for "robotIP":12345, and get 12345 portion + size_t pos = json.find("\"robotIP\""); + if (pos == llvm::StringRef::npos) continue; // could not find? + pos += 9; + pos = json.find(':', pos); + if (pos == llvm::StringRef::npos) continue; // could not find? + size_t endpos = json.find_first_not_of("0123456789", pos + 1); + DEBUG3("found robotIP=" << json.slice(pos + 1, endpos)); + + // Parse into number + unsigned int ip; + if (json.slice(pos + 1, endpos).getAsInteger(10, ip)) continue; // error + + // If zero, clear the server override + if (ip == 0) { + Dispatcher::GetInstance().ClearServerOverride(); + oldip = 0; + continue; + } + + // If unchanged, don't reconnect + if (ip == oldip) continue; + oldip = ip; + + // Convert number into dotted quad + json.clear(); + llvm::raw_svector_ostream os{json}; + os << ((ip >> 24) & 0xff) << "." << ((ip >> 16) & 0xff) << "." + << ((ip >> 8) & 0xff) << "." << (ip & 0xff); + INFO("client: DS overriding server IP to " << os.str()); + Dispatcher::GetInstance().SetServerOverride(json.c_str(), port); + } + + // We disconnected from the DS, clear the server override + Dispatcher::GetInstance().ClearServerOverride(); + oldip = 0; + } + +done: + Dispatcher::GetInstance().ClearServerOverride(); +} diff --git a/src/DsClient.h b/src/DsClient.h new file mode 100644 index 0000000000..9dd905c911 --- /dev/null +++ b/src/DsClient.h @@ -0,0 +1,38 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_DSCLIENT_H_ +#define NT_DSCLIENT_H_ + +#include "support/atomic_static.h" +#include "support/SafeThread.h" + +namespace nt { + +class DsClient { + public: + static DsClient& GetInstance() { + ATOMIC_STATIC(DsClient, instance); + return instance; + } + ~DsClient() = default; + + void Start(unsigned int port); + void Stop(); + + private: + DsClient() = default; + + class Thread; + wpi::SafeThreadOwner m_owner; + + ATOMIC_STATIC_DECL(DsClient) +}; + +} // namespace nt + +#endif // NT_DSCLIENT_H_ diff --git a/src/networktables/NetworkTable.cpp b/src/networktables/NetworkTable.cpp index c45cac8a46..5a689e9311 100644 --- a/src/networktables/NetworkTable.cpp +++ b/src/networktables/NetworkTable.cpp @@ -2,6 +2,7 @@ #include +#include "llvm/raw_ostream.h" #include "llvm/SmallString.h" #include "llvm/StringMap.h" #include "tables/ITableListener.h" @@ -11,20 +12,17 @@ using llvm::StringRef; const char NetworkTable::PATH_SEPARATOR_CHAR = '/'; -std::vector NetworkTable::s_ip_addresses; std::string NetworkTable::s_persistent_filename = "networktables.ini"; bool NetworkTable::s_client = false; +bool NetworkTable::s_enable_ds = true; bool NetworkTable::s_running = false; unsigned int NetworkTable::s_port = NT_DEFAULT_PORT; void NetworkTable::Initialize() { if (s_running) Shutdown(); if (s_client) { - std::vector> servers; - servers.reserve(s_ip_addresses.size()); - for (const auto& ip_address : s_ip_addresses) - servers.emplace_back(std::make_pair(ip_address, s_port)); - nt::StartClient(servers); + nt::StartClient(); + if (s_enable_ds) nt::StartDSClient(s_port); } else nt::StartServer(s_persistent_filename, "", s_port); s_running = true; @@ -32,9 +30,10 @@ void NetworkTable::Initialize() { void NetworkTable::Shutdown() { if (!s_running) return; - if (s_client) + if (s_client) { + nt::StopDSClient(); nt::StopClient(); - else + } else nt::StopServer(); s_running = false; } @@ -44,27 +43,74 @@ void NetworkTable::SetClientMode() { s_client = true; } void NetworkTable::SetServerMode() { s_client = false; } void NetworkTable::SetTeam(int team) { - char tmp[30]; -#ifdef _MSC_VER - sprintf_s(tmp, "roboRIO-%d-FRC.local", team); -#else - using namespace std; - snprintf(tmp, 30, "roboRIO-%d-FRC.local", team); -#endif - SetIPAddress(tmp); + std::pair servers[4]; + + // 10.te.am.2 + llvm::SmallString<32> fixed; + { + llvm::raw_svector_ostream oss{fixed}; + oss << "10." << static_cast(team / 100) << '.' + << static_cast(team % 100) << ".2"; + servers[0] = std::make_pair(oss.str(), s_port); + } + + // 172.22.11.2 + servers[1] = std::make_pair("172.22.11.2", s_port); + + // roboRIO--FRC.local + llvm::SmallString<32> mdns; + { + llvm::raw_svector_ostream oss{mdns}; + oss << "roboRIO-" << team << "-FRC.local"; + servers[2] = std::make_pair(oss.str(), s_port); + } + + // roboRIO--FRC.lan + llvm::SmallString<32> mdns_lan; + { + llvm::raw_svector_ostream oss{mdns_lan}; + oss << "roboRIO-" << team << "-FRC.lan"; + servers[3] = std::make_pair(oss.str(), s_port); + } + + nt::SetServer(servers); } void NetworkTable::SetIPAddress(StringRef address) { - s_ip_addresses.clear(); - s_ip_addresses.emplace_back(address); + llvm::SmallString<32> addr_copy{address}; + nt::SetServer(addr_copy.c_str(), s_port); + + // Stop the DS client if we're explicitly connecting to localhost + if (address == "localhost" || address == "127.0.0.1") + nt::StopDSClient(); + else if (s_enable_ds) + nt::StartDSClient(s_port); } void NetworkTable::SetIPAddress(llvm::ArrayRef addresses) { - s_ip_addresses = addresses; + llvm::SmallVector, 8> servers; + for (const auto& ip_address : addresses) + servers.emplace_back(std::make_pair(ip_address, s_port)); + nt::SetServer(servers); + + // Stop the DS client if we're explicitly connecting to localhost + if (!addresses.empty() && + (addresses[0] == "localhost" || addresses[0] == "127.0.0.1")) + nt::StopDSClient(); + else if (s_enable_ds) + nt::StartDSClient(s_port); } void NetworkTable::SetPort(unsigned int port) { s_port = port; } +void NetworkTable::SetDSClientEnabled(bool enabled) { + s_enable_ds = enabled; + if (s_enable_ds) + nt::StartDSClient(s_port); + else + nt::StopDSClient(); +} + void NetworkTable::SetPersistentFilename(StringRef filename) { s_persistent_filename = filename; } diff --git a/src/ntcore_c.cpp b/src/ntcore_c.cpp index e8a40f1ba8..34ffb301bd 100644 --- a/src/ntcore_c.cpp +++ b/src/ntcore_c.cpp @@ -372,6 +372,8 @@ void NT_StartServer(const char* persist_filename, const char* listen_address, void NT_StopServer(void) { nt::StopServer(); } +void NT_StartClientNone(void) { nt::StartClient(); } + void NT_StartClient(const char* server_name, unsigned int port) { nt::StartClient(server_name, port); } @@ -387,6 +389,23 @@ void NT_StartClientMulti(size_t count, const char** server_names, void NT_StopClient(void) { nt::StopClient(); } +void NT_SetServer(const char* server_name, unsigned int port) { + nt::SetServer(server_name, port); +} + +void NT_SetServerMulti(size_t count, const char** server_names, + const unsigned int* ports) { + std::vector> servers; + servers.reserve(count); + for (size_t i = 0; i < count; ++i) + servers.emplace_back(std::make_pair(server_names[i], ports[i])); + nt::SetServer(servers); +} + +void NT_StartDSClient(unsigned int port) { nt::StartDSClient(port); } + +void NT_StopDSClient(void) { nt::StopDSClient(); } + void NT_StopRpcServer(void) { nt::StopRpcServer(); } void NT_StopNotifier(void) { nt::StopNotifier(); } diff --git a/src/ntcore_cpp.cpp b/src/ntcore_cpp.cpp index f80fcc871a..5ae962b4be 100644 --- a/src/ntcore_cpp.cpp +++ b/src/ntcore_cpp.cpp @@ -14,6 +14,7 @@ #include "support/timestamp.h" #include "Log.h" #include "Dispatcher.h" +#include "DsClient.h" #include "Notifier.h" #include "RpcServer.h" #include "Storage.h" @@ -246,16 +247,34 @@ void StartServer(StringRef persist_filename, const char* listen_address, void StopServer() { Dispatcher::GetInstance().Stop(); } +void StartClient() { Dispatcher::GetInstance().StartClient(); } + void StartClient(const char* server_name, unsigned int port) { - Dispatcher::GetInstance().StartClient(server_name, port); + auto& d = Dispatcher::GetInstance(); + d.SetServer(server_name, port); + d.StartClient(); } void StartClient(ArrayRef> servers) { - Dispatcher::GetInstance().StartClient(servers); + auto& d = Dispatcher::GetInstance(); + d.SetServer(servers); + d.StartClient(); } void StopClient() { Dispatcher::GetInstance().Stop(); } +void SetServer(const char* server_name, unsigned int port) { + Dispatcher::GetInstance().SetServer(server_name, port); +} + +void SetServer(ArrayRef> servers) { + Dispatcher::GetInstance().SetServer(servers); +} + +void StartDSClient(unsigned int port) { DsClient::GetInstance().Start(port); } + +void StopDSClient() { DsClient::GetInstance().Stop(); } + void StopRpcServer() { RpcServer::GetInstance().Stop(); } void StopNotifier() { Notifier::GetInstance().Stop(); }