| // Copyright (c) FIRST and other WPILib contributors. |
| // Open Source Software; you can modify and/or share it under the terms of |
| // the WPILib BSD license file in the root directory of this project. |
| |
| #include "wpi/DsClient.h" |
| |
| #include <fmt/format.h> |
| #include <wpi/StringExtras.h> |
| #include <wpi/json.h> |
| #include <wpi/uv/Tcp.h> |
| #include <wpi/uv/Timer.h> |
| |
| #include "wpi/Logger.h" |
| |
| using namespace wpi; |
| |
| static constexpr uv::Timer::Time kReconnectTime{500}; |
| |
| DsClient::DsClient(wpi::uv::Loop& loop, wpi::Logger& logger, |
| const private_init&) |
| : m_logger{logger}, |
| m_tcp{uv::Tcp::Create(loop)}, |
| m_timer{uv::Timer::Create(loop)} { |
| m_tcp->end.connect([this] { |
| WPI_DEBUG4(m_logger, "{}", "DS connection closed"); |
| clearIp(); |
| // try to connect again |
| m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); }); |
| }); |
| m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) { |
| HandleIncoming({buf.base, len}); |
| }); |
| m_timer->timeout.connect([this] { Connect(); }); |
| Connect(); |
| } |
| |
| DsClient::~DsClient() = default; |
| |
| void DsClient::Close() { |
| m_tcp->Close(); |
| m_timer->Close(); |
| clearIp(); |
| } |
| |
| void DsClient::Connect() { |
| auto connreq = std::make_shared<uv::TcpConnectReq>(); |
| connreq->connected.connect([this] { |
| m_json.clear(); |
| m_tcp->StopRead(); |
| m_tcp->StartRead(); |
| }); |
| |
| connreq->error = [this](uv::Error err) { |
| WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str()); |
| // try to connect again |
| m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); }); |
| }; |
| |
| WPI_DEBUG4(m_logger, "{}", "Starting DS connection attempt"); |
| m_tcp->Connect("127.0.0.1", 1742, connreq); |
| } |
| |
| void DsClient::HandleIncoming(std::string_view in) { |
| // this is very bare-bones, as there are never nested {} in these messages |
| while (!in.empty()) { |
| // if json is empty, look for the first { (and discard) |
| if (m_json.empty()) { |
| auto start = in.find('{'); |
| in = wpi::slice(in, start, std::string_view::npos); |
| } |
| |
| // look for the terminating } (and save) |
| auto end = in.find('}'); |
| if (end == std::string_view::npos) { |
| m_json.append(in); |
| return; // nothing left to read |
| } |
| |
| // have complete json message |
| ++end; |
| m_json.append(wpi::slice(in, 0, end)); |
| in = wpi::slice(in, end, std::string_view::npos); |
| ParseJson(); |
| m_json.clear(); |
| } |
| } |
| |
| void DsClient::ParseJson() { |
| WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json); |
| unsigned int ip = 0; |
| try { |
| ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>(); |
| } catch (wpi::json::exception& e) { |
| WPI_INFO(m_logger, "DsClient JSON error: {}", e.what()); |
| return; |
| } |
| |
| if (ip == 0) { |
| clearIp(); |
| } else { |
| // Convert number into dotted quad |
| auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff, |
| (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); |
| WPI_INFO(m_logger, "DS received server IP: {}", newip); |
| setIp(newip); |
| } |
| } |