mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
[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:
362
simulation/halsim_gui/src/main/native/cpp/NetworkTablesGui.cpp
Normal file
362
simulation/halsim_gui/src/main/native/cpp/NetworkTablesGui.cpp
Normal 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);
|
||||
}
|
||||
17
simulation/halsim_gui/src/main/native/cpp/NetworkTablesGui.h
Normal file
17
simulation/halsim_gui/src/main/native/cpp/NetworkTablesGui.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user