blob: 97509cb323a900e75fdf6c7faec21828434d8cd7 [file] [log] [blame]
James Kuszmaulcf324122023-01-14 14:07:17 -08001// 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 "wpinet/DsClient.h"
6
7#include <fmt/format.h>
8#include <wpi/Logger.h>
9#include <wpi/StringExtras.h>
10#include <wpi/json.h>
11
12#include "wpinet/uv/Tcp.h"
13#include "wpinet/uv/Timer.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)} {
James Kuszmaulb13e13f2023-11-22 20:44:04 -080024 if (!m_tcp || !m_timer) {
25 return;
26 }
James Kuszmaulcf324122023-01-14 14:07:17 -080027 m_tcp->end.connect([this] {
28 WPI_DEBUG4(m_logger, "DS connection closed");
29 clearIp();
30 // try to connect again
31 m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
32 });
33 m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) {
34 HandleIncoming({buf.base, len});
35 });
36 m_timer->timeout.connect([this] { Connect(); });
37 Connect();
38}
39
40DsClient::~DsClient() = default;
41
42void DsClient::Close() {
43 m_tcp->Close();
44 m_timer->Close();
45 clearIp();
46}
47
48void DsClient::Connect() {
49 auto connreq = std::make_shared<uv::TcpConnectReq>();
50 connreq->connected.connect([this] {
51 m_json.clear();
52 m_tcp->StopRead();
53 m_tcp->StartRead();
54 });
55
56 connreq->error = [this](uv::Error err) {
57 WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str());
58 // try to connect again
59 m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
60 };
61
62 WPI_DEBUG4(m_logger, "Starting DS connection attempt");
63 m_tcp->Connect("127.0.0.1", 1742, connreq);
64}
65
66void DsClient::HandleIncoming(std::string_view in) {
67 // this is very bare-bones, as there are never nested {} in these messages
68 while (!in.empty()) {
69 // if json is empty, look for the first { (and discard)
70 if (m_json.empty()) {
71 auto start = in.find('{');
72 in = wpi::slice(in, start, std::string_view::npos);
73 }
74
75 // look for the terminating } (and save)
76 auto end = in.find('}');
77 if (end == std::string_view::npos) {
78 m_json.append(in);
79 return; // nothing left to read
80 }
81
82 // have complete json message
83 ++end;
84 m_json.append(wpi::slice(in, 0, end));
85 in = wpi::slice(in, end, std::string_view::npos);
86 ParseJson();
87 m_json.clear();
88 }
89}
90
91void DsClient::ParseJson() {
92 WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json);
93 unsigned int ip = 0;
94 try {
95 ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>();
96 } catch (wpi::json::exception& e) {
97 WPI_INFO(m_logger, "DsClient JSON error: {}", e.what());
98 return;
99 }
100
101 if (ip == 0) {
102 clearIp();
103 } else {
104 // Convert number into dotted quad
105 auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
106 (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
107 WPI_INFO(m_logger, "DS received server IP: {}", newip);
108 setIp(newip);
109 }
110}