blob: 455f10c2e8a98420ef6d7a0fda45b9f4ceca8e30 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include "wpi/DsClient.h"
6
7#include <fmt/format.h>
8#include <wpi/StringExtras.h>
9#include <wpi/json.h>
10#include <wpi/uv/Tcp.h>
11#include <wpi/uv/Timer.h>
12
13#include "wpi/Logger.h"
14
15using namespace wpi;
16
17static constexpr uv::Timer::Time kReconnectTime{500};
18
19DsClient::DsClient(wpi::uv::Loop& loop, wpi::Logger& logger,
20 const private_init&)
21 : m_logger{logger},
22 m_tcp{uv::Tcp::Create(loop)},
23 m_timer{uv::Timer::Create(loop)} {
24 m_tcp->end.connect([this] {
25 WPI_DEBUG4(m_logger, "{}", "DS connection closed");
26 clearIp();
27 // try to connect again
28 m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
29 });
30 m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) {
31 HandleIncoming({buf.base, len});
32 });
33 m_timer->timeout.connect([this] { Connect(); });
34 Connect();
35}
36
37DsClient::~DsClient() = default;
38
39void DsClient::Close() {
40 m_tcp->Close();
41 m_timer->Close();
42 clearIp();
43}
44
45void DsClient::Connect() {
46 auto connreq = std::make_shared<uv::TcpConnectReq>();
47 connreq->connected.connect([this] {
48 m_json.clear();
49 m_tcp->StopRead();
50 m_tcp->StartRead();
51 });
52
53 connreq->error = [this](uv::Error err) {
54 WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str());
55 // try to connect again
56 m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
57 };
58
59 WPI_DEBUG4(m_logger, "{}", "Starting DS connection attempt");
60 m_tcp->Connect("127.0.0.1", 1742, connreq);
61}
62
63void DsClient::HandleIncoming(std::string_view in) {
64 // this is very bare-bones, as there are never nested {} in these messages
65 while (!in.empty()) {
66 // if json is empty, look for the first { (and discard)
67 if (m_json.empty()) {
68 auto start = in.find('{');
69 in = wpi::slice(in, start, std::string_view::npos);
70 }
71
72 // look for the terminating } (and save)
73 auto end = in.find('}');
74 if (end == std::string_view::npos) {
75 m_json.append(in);
76 return; // nothing left to read
77 }
78
79 // have complete json message
80 ++end;
81 m_json.append(wpi::slice(in, 0, end));
82 in = wpi::slice(in, end, std::string_view::npos);
83 ParseJson();
84 m_json.clear();
85 }
86}
87
88void DsClient::ParseJson() {
89 WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json);
90 unsigned int ip = 0;
91 try {
92 ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>();
93 } catch (wpi::json::exception& e) {
94 WPI_INFO(m_logger, "DsClient JSON error: {}", e.what());
95 return;
96 }
97
98 if (ip == 0) {
99 clearIp();
100 } else {
101 // Convert number into dotted quad
102 auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
103 (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
104 WPI_INFO(m_logger, "DS received server IP: {}", newip);
105 setIp(newip);
106 }
107}