[sim] Add NetworkTables view to Simulation GUI (#2327)

This enables seeing the tables locally, including update timestamps and
external connections.
This commit is contained in:
Peter Johnson
2020-04-05 23:09:50 -07:00
committed by GitHub
parent b46b5df16a
commit 5b0122fed4
5 changed files with 383 additions and 1 deletions

View File

@@ -0,0 +1,362 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 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 "NetworkTablesGui.h"
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <vector>
#include <imgui.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableValue.h>
#include <wpi/Format.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include "HALSimGui.h"
using namespace halsimgui;
static void BooleanArrayToString(wpi::SmallVectorImpl<char>& out,
wpi::ArrayRef<int> in) {
out.clear();
wpi::raw_svector_ostream os{out};
os << '[';
bool first = true;
for (auto v : in) {
if (!first) os << ',';
first = false;
if (v)
os << "true";
else
os << "false";
}
os << ']';
}
static std::shared_ptr<nt::Value> StringToBooleanArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeBooleanArray(
std::initializer_list<bool>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
wpi::SmallVector<int, 16> out;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
if (val.equals_lower("true")) {
out.emplace_back(1);
} else if (val.equals_lower("false")) {
out.emplace_back(0);
} else {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
}
return nt::NetworkTableValue::MakeBooleanArray(out);
}
static void DoubleArrayToString(wpi::SmallVectorImpl<char>& out,
wpi::ArrayRef<double> in) {
out.clear();
wpi::raw_svector_ostream os{out};
os << '[';
bool first = true;
for (auto v : in) {
if (!first) os << ',';
first = false;
os << wpi::format("%.6f", v);
}
os << ']';
}
static std::shared_ptr<nt::Value> StringToDoubleArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeBooleanArray(
std::initializer_list<bool>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
wpi::SmallVector<double, 16> out;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
wpi::SmallString<32> valStr = val;
double d;
if (std::sscanf(valStr.c_str(), "%lf", &d) == 1) {
out.emplace_back(d);
} else {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
}
return nt::NetworkTableValue::MakeDoubleArray(out);
}
static void StringArrayToString(wpi::SmallVectorImpl<char>& out,
wpi::ArrayRef<std::string> in) {
out.clear();
wpi::raw_svector_ostream os{out};
os << '[';
bool first = true;
for (auto&& v : in) {
if (!first) os << ',';
first = false;
os << '"';
os.write_escaped(v);
os << '"';
}
os << ']';
}
static int fromxdigit(char ch) {
if (ch >= 'a' && ch <= 'f')
return (ch - 'a' + 10);
else if (ch >= 'A' && ch <= 'F')
return (ch - 'A' + 10);
else
return ch - '0';
}
static wpi::StringRef UnescapeString(wpi::StringRef source,
wpi::SmallVectorImpl<char>& buf) {
assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
buf.clear();
buf.reserve(source.size() - 2);
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
if (*s != '\\') {
buf.push_back(*s);
continue;
}
switch (*++s) {
case 't':
buf.push_back('\t');
break;
case 'n':
buf.push_back('\n');
break;
case 'x': {
if (!isxdigit(*(s + 1))) {
buf.push_back('x'); // treat it like a unknown escape
break;
}
int ch = fromxdigit(*++s);
if (std::isxdigit(*(s + 1))) {
ch <<= 4;
ch |= fromxdigit(*++s);
}
buf.push_back(static_cast<char>(ch));
break;
}
default:
buf.push_back(*s);
break;
}
}
return wpi::StringRef{buf.data(), buf.size()};
}
static std::shared_ptr<nt::Value> StringToStringArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeStringArray(
std::initializer_list<std::string>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
std::vector<std::string> out;
wpi::SmallString<32> buf;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
if (val.empty()) continue;
if (val.front() != '"' || val.back() != '"') {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
out.emplace_back(UnescapeString(val, buf));
}
return nt::NetworkTableValue::MakeStringArray(std::move(out));
}
static constexpr size_t kTextBufferSize = 4096;
static char* GetTextBuffer(wpi::StringRef in) {
static char textBuffer[kTextBufferSize];
size_t len = (std::min)(in.size(), kTextBufferSize - 1);
std::memcpy(textBuffer, in.data(), len);
textBuffer[len] = '\0';
return textBuffer;
}
static void DisplayNetworkTables() {
static auto inst = nt::NetworkTableInstance::GetDefault();
if (ImGui::CollapsingHeader("Connections")) {
ImGui::Columns(4, "connections");
ImGui::Text("Id");
ImGui::NextColumn();
ImGui::Text("Address");
ImGui::NextColumn();
ImGui::Text("Updated");
ImGui::NextColumn();
ImGui::Text("Proto");
ImGui::NextColumn();
ImGui::Separator();
for (auto&& i : inst.GetConnections()) {
ImGui::Text("%s", i.remote_id.c_str());
ImGui::NextColumn();
ImGui::Text("%s", i.remote_ip.c_str());
ImGui::NextColumn();
ImGui::Text("%llu",
static_cast<unsigned long long>( // NOLINT(runtime/int)
i.last_update));
ImGui::NextColumn();
ImGui::Text("%d.%d", i.protocol_version >> 8, i.protocol_version & 0xff);
ImGui::NextColumn();
}
ImGui::Columns();
}
if (ImGui::CollapsingHeader("Values", ImGuiTreeNodeFlags_DefaultOpen)) {
static bool first = true;
ImGui::Columns(4, "values");
if (first) ImGui::SetColumnWidth(-1, 0.5f * ImGui::GetWindowWidth());
ImGui::Text("Name");
ImGui::NextColumn();
ImGui::Text("Value");
ImGui::NextColumn();
if (first) ImGui::SetColumnWidth(-1, 12 * ImGui::GetFontSize());
ImGui::Text("Flags");
ImGui::NextColumn();
ImGui::Text("Changed");
ImGui::NextColumn();
ImGui::Separator();
first = false;
auto info = inst.GetEntryInfo("", 0);
std::sort(info.begin(), info.end(),
[](const auto& a, const auto& b) { return a.name < b.name; });
for (auto&& i : info) {
ImGui::Text("%s", i.name.c_str());
ImGui::NextColumn();
if (auto val = nt::GetEntryValue(i.entry)) {
ImGui::PushID(i.name.c_str());
switch (val->type()) {
case NT_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
int v = val->GetBoolean() ? 1 : 0;
if (ImGui::Combo("boolean", &v, boolOptions, 2))
nt::SetEntryValue(i.entry, nt::NetworkTableValue::MakeBoolean(v));
break;
}
case NT_DOUBLE: {
double v = val->GetDouble();
if (ImGui::InputDouble("double", &v, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue))
nt::SetEntryValue(i.entry, nt::NetworkTableValue::MakeDouble(v));
break;
}
case NT_STRING: {
char* v = GetTextBuffer(val->GetString());
if (ImGui::InputText("string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue))
nt::SetEntryValue(i.entry, nt::NetworkTableValue::MakeString(v));
break;
}
case NT_BOOLEAN_ARRAY: {
wpi::SmallString<64> buf;
BooleanArrayToString(buf, val->GetBooleanArray());
char* v = GetTextBuffer(buf);
if (ImGui::InputText("boolean[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToBooleanArray(v))
nt::SetEntryValue(i.entry, std::move(outv));
}
break;
}
case NT_DOUBLE_ARRAY: {
wpi::SmallString<64> buf;
DoubleArrayToString(buf, val->GetDoubleArray());
char* v = GetTextBuffer(buf);
if (ImGui::InputText("double[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToDoubleArray(v))
nt::SetEntryValue(i.entry, std::move(outv));
}
break;
}
case NT_STRING_ARRAY: {
wpi::SmallString<64> buf;
StringArrayToString(buf, val->GetStringArray());
char* v = GetTextBuffer(buf);
if (ImGui::InputText("string[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToStringArray(v))
nt::SetEntryValue(i.entry, std::move(outv));
}
break;
}
case NT_RAW:
ImGui::LabelText("raw", "[...]");
break;
case NT_RPC:
ImGui::LabelText("rpc", "[...]");
break;
default:
ImGui::LabelText("other", "?");
break;
}
ImGui::PopID();
}
ImGui::NextColumn();
if ((i.flags & NT_PERSISTENT) != 0)
ImGui::Text("Persistent");
else if (i.flags != 0)
ImGui::Text("%02x", i.flags);
ImGui::NextColumn();
ImGui::Text("%llu",
static_cast<unsigned long long>( // NOLINT(runtime/int)
i.last_change));
ImGui::NextColumn();
ImGui::Separator();
}
ImGui::Columns();
}
}
void NetworkTablesGui::Initialize() {
HALSimGui::AddWindow("NetworkTables", DisplayNetworkTables);
HALSimGui::SetDefaultWindowPos("NetworkTables", 250, 260);
HALSimGui::SetDefaultWindowSize("NetworkTables", 1500, 375);
}

View File

@@ -0,0 +1,17 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 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. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace halsimgui {
class NetworkTablesGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -19,6 +19,7 @@
#include "EncoderGui.h"
#include "Field2D.h"
#include "HALSimGui.h"
#include "NetworkTablesGui.h"
#include "PDPGui.h"
#include "PWMGui.h"
#include "RelayGui.h"
@@ -44,6 +45,7 @@ __declspec(dllexport)
HALSimGui::Add(DIOGui::Initialize);
HALSimGui::Add(EncoderGui::Initialize);
HALSimGui::Add(Field2D::Initialize);
HALSimGui::Add(NetworkTablesGui::Initialize);
HALSimGui::Add(PDPGui::Initialize);
HALSimGui::Add(PWMGui::Initialize);
HALSimGui::Add(RelayGui::Initialize);