/*----------------------------------------------------------------------------*/ /* Copyright (c) 2015-2019 FIRST. 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 #include #include #include #include "Dispatcher.h" #include "Log.h" using namespace nt; class DsClient::Thread : public wpi::SafeThread { public: Thread(Dispatcher& dispatcher, wpi::Logger& logger, unsigned int port) : m_dispatcher(dispatcher), m_logger(logger), m_port(port) {} void Main(); Dispatcher& m_dispatcher; wpi::Logger& m_logger; unsigned int m_port; std::unique_ptr m_stream; }; DsClient::DsClient(Dispatcher& dispatcher, wpi::Logger& logger) : m_dispatcher(dispatcher), m_logger(logger) {} void DsClient::Start(unsigned int port) { auto thr = m_owner.GetThread(); if (!thr) m_owner.Start(m_dispatcher, m_logger, 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. wpi::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 == wpi::StringRef::npos) continue; // could not find? pos += 9; pos = json.find(':', pos); if (pos == wpi::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 = 0; if (json.slice(pos + 1, endpos).getAsInteger(10, ip)) continue; // error // If zero, clear the server override if (ip == 0) { m_dispatcher.ClearServerOverride(); oldip = 0; continue; } // If unchanged, don't reconnect if (ip == oldip) continue; oldip = ip; // Convert number into dotted quad json.clear(); wpi::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()); m_dispatcher.SetServerOverride(json.c_str(), port); } // We disconnected from the DS, clear the server override m_dispatcher.ClearServerOverride(); oldip = 0; } done: m_dispatcher.ClearServerOverride(); }