[glass] Add glass: an application for display of robot data

This reuses many pieces of the current simulation GUI.  The common pieces have
been refactored into the libglass library.

The libglass library is designed to be usable for other standalone data
visualization applications (e.g. viewing data logs).

The name "glass" comes from "glass cockpit", as the application features
several multi-function displays that can be adjusted to display robot
information as needed.
This commit is contained in:
Peter Johnson
2020-09-12 10:55:46 -07:00
parent 727940d847
commit 2a5ca77454
151 changed files with 10386 additions and 4565 deletions

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/Accelerometer.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAccelerometerDevice(AccelerometerModel* model) {
if (!model->Exists()) return;
if (BeginDevice("BuiltInAccel")) {
// Range
{
int value = model->GetRange();
static const char* rangeOptions[] = {"2G", "4G", "8G"};
DeviceEnum("Range", true, &value, rangeOptions, 3);
}
// X Accel
if (auto xData = model->GetXData()) {
double value = xData->GetValue();
if (DeviceDouble("X Accel", false, &value, xData)) {
model->SetX(value);
}
}
// Y Accel
if (auto yData = model->GetYData()) {
double value = yData->GetValue();
if (DeviceDouble("Y Accel", false, &value, yData)) {
model->SetY(value);
}
}
// Z Accel
if (auto zData = model->GetZData()) {
double value = zData->GetValue();
if (DeviceDouble("Z Accel", false, &value, zData)) {
model->SetZ(value);
}
}
EndDevice();
}
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/AnalogGyro.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogGyroDevice(AnalogGyroModel* model, int index) {
char name[32];
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", index);
if (BeginDevice(name)) {
// angle
if (auto angleData = model->GetAngleData()) {
double value = angleData->GetValue();
if (DeviceDouble("Angle", false, &value, angleData)) {
model->SetAngle(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (DeviceDouble("Rate", false, &value, rateData)) {
model->SetRate(value);
}
}
EndDevice();
}
}
void glass::DisplayAnalogGyrosDevice(AnalogGyrosModel* model) {
model->ForEachAnalogGyro(
[&](AnalogGyroModel& gyro, int i) { DisplayAnalogGyroDevice(&gyro, i); });
}

View File

@@ -0,0 +1,66 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/AnalogInput.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
auto voltageData = model->GetVoltageData();
if (!voltageData) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "In[%d]###name", index);
}
if (model->IsGyro()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "AnalogGyro[%d]", index);
ImGui::PopStyleColor();
} else if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "%s", simDevice);
ImGui::PopStyleColor();
} else {
float val = voltageData->GetValue();
if (voltageData->SliderFloat(label, &val, 0.0, 5.0)) model->SetVoltage(val);
}
// context menu to change name
if (PopupEditName("name", name)) voltageData->SetName(name->c_str());
}
void glass::DisplayAnalogInputs(AnalogInputsModel* model,
wpi::StringRef noneMsg) {
ImGui::Text("(Use Ctrl+Click to edit value)");
bool hasAny = false;
bool first = true;
model->ForEachAnalogInput([&](AnalogInputModel& input, int i) {
if (!first) {
ImGui::Spacing();
ImGui::Spacing();
} else {
first = false;
}
PushID(i);
DisplayAnalogInput(&input, i);
PopID();
hasAny = true;
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,47 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/AnalogOutput.h"
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
int count = 0;
model->ForEachAnalogOutput([&](auto&, int) { ++count; });
if (count == 0) return;
if (BeginDevice("Analog Outputs")) {
model->ForEachAnalogOutput([&](auto& analogOut, int i) {
auto analogOutData = analogOut.GetVoltageData();
if (!analogOutData) return;
PushID(i);
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
} else {
std::snprintf(label, sizeof(label), "Out[%d]###name", i);
}
double value = analogOutData->GetValue();
DeviceDouble(label, true, &value, analogOutData);
if (PopupEditName("name", name)) {
if (analogOutData) analogOutData->SetName(name->c_str());
}
PopID();
});
EndDevice();
}
}

View File

@@ -0,0 +1,118 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/DIO.h"
#include <imgui.h>
#include "glass/DataSource.h"
#include "glass/hardware/Encoder.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static void LabelSimDevice(const char* name, const char* simDeviceName) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(name, "%s", simDeviceName);
ImGui::PopStyleColor();
}
void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
auto dpwm = model->GetDPWM();
auto dutyCycle = model->GetDutyCycle();
auto encoder = model->GetEncoder();
auto dioData = model->GetValueData();
auto dpwmData = dpwm ? dpwm->GetValueData() : nullptr;
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
bool exists = model->Exists();
auto& info = dioData->GetNameInfo();
char label[128];
if (exists && dpwmData) {
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
if (auto simDevice = dpwm->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
dpwmData->LabelText(label, "%0.3f", dpwmData->GetValue());
}
} else if (exists && encoder) {
info.GetLabel(label, sizeof(label), " In", index);
if (auto simDevice = encoder->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "Encoder[%d,%d]", encoder->GetChannelA(),
encoder->GetChannelB());
ImGui::PopStyleColor();
}
} else if (exists && dutyCycleData) {
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
if (auto simDevice = dutyCycle->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
double val = dutyCycleData->GetValue();
if (dutyCycleData->InputDouble(label, &val)) {
dutyCycle->SetValue(val);
}
}
} else {
const char* name = model->GetName();
if (name[0] != '\0')
info.GetLabel(label, sizeof(label), name);
else
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
index);
if (auto simDevice = model->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
if (!exists) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
dioData->LabelText(label, "unknown");
ImGui::PopStyleColor();
} else if (model->IsReadOnly()) {
dioData->LabelText(
label, "%s",
outputsEnabled ? (dioData->GetValue() != 0 ? "1 (high)" : "0 (low)")
: "1 (disabled)");
} else {
static const char* options[] = {"0 (low)", "1 (high)"};
int val = dioData->GetValue() != 0 ? 1 : 0;
if (dioData->Combo(label, &val, options, 2)) {
model->SetValue(val);
}
}
}
}
if (info.PopupEditName(index)) {
if (dpwmData) dpwmData->SetName(info.GetName());
if (dutyCycleData) dutyCycleData->SetName(info.GetName());
}
}
void glass::DisplayDIO(DIOModel* model, int index, bool outputsEnabled) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
DisplayDIOImpl(model, index, outputsEnabled);
ImGui::PopItemWidth();
}
void glass::DisplayDIOs(DIOsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
model->ForEachDIO([&](DIOModel& dio, int i) {
hasAny = true;
ImGui::PushID(i);
DisplayDIOImpl(&dio, i, outputsEnabled);
ImGui::PopID();
});
ImGui::PopItemWidth();
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,165 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/Encoder.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void EncoderModel::SetName(const wpi::Twine& name) {
if (name.str().empty()) {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName("");
}
if (auto count = GetCountData()) {
count->SetName("");
}
if (auto period = GetPeriodData()) {
period->SetName("");
}
if (auto direction = GetDirectionData()) {
direction->SetName("");
}
if (auto distance = GetDistanceData()) {
distance->SetName("");
}
if (auto rate = GetRateData()) {
rate->SetName("");
}
} else {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName(name + " Distance/Count");
}
if (auto count = GetCountData()) {
count->SetName(name + " Count");
}
if (auto period = GetPeriodData()) {
period->SetName(name + " Period");
}
if (auto direction = GetDirectionData()) {
direction->SetName(name + " Direction");
}
if (auto distance = GetDistanceData()) {
distance->SetName(name + " Distance");
}
if (auto rate = GetRateData()) {
rate->SetName(name + " Rate");
}
}
}
void glass::DisplayEncoder(EncoderModel* model) {
if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::TextUnformatted(simDevice);
ImGui::PopStyleColor();
return;
}
int chA = model->GetChannelA();
int chB = model->GetChannelB();
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
chB);
} else {
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
}
// header
bool open = CollapsingHeader(label);
// context menu to change name
if (PopupEditName("name", name)) {
model->SetName(name->c_str());
}
if (!open) return;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
// distance per pulse
if (auto distancePerPulseData = model->GetDistancePerPulseData()) {
double value = distancePerPulseData->GetValue();
distancePerPulseData->LabelText("Dist/Count", "%.6f", value);
}
// count
if (auto countData = model->GetCountData()) {
int value = countData->GetValue();
if (ImGui::InputInt("##input", &value)) model->SetCount(value);
ImGui::SameLine();
if (ImGui::Button("Reset")) {
model->SetCount(0);
}
ImGui::SameLine();
ImGui::Selectable("Count");
countData->EmitDrag();
}
// max period
{
double maxPeriod = model->GetMaxPeriod();
ImGui::LabelText("Max Period", "%.6f", maxPeriod);
}
// period
if (auto periodData = model->GetPeriodData()) {
double value = periodData->GetValue();
if (periodData->InputDouble("Period", &value, 0, 0, "%.6g")) {
model->SetPeriod(value);
}
}
// reverse direction
ImGui::LabelText("Reverse Direction", "%s",
model->GetReverseDirection() ? "true" : "false");
// direction
if (auto directionData = model->GetDirectionData()) {
static const char* options[] = {"reverse", "forward"};
int value = directionData->GetValue() ? 1 : 0;
if (directionData->Combo("Direction", &value, options, 2)) {
model->SetDirection(value != 0);
}
}
// distance
if (auto distanceData = model->GetDistanceData()) {
double value = distanceData->GetValue();
if (distanceData->InputDouble("Distance", &value, 0, 0, "%.6g")) {
model->SetDistance(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (rateData->InputDouble("Rate", &value, 0, 0, "%.6g")) {
model->SetRate(value);
}
}
ImGui::PopItemWidth();
}
void glass::DisplayEncoders(EncodersModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachEncoder([&](EncoderModel& encoder, int i) {
hasAny = true;
PushID(i);
DisplayEncoder(&encoder);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,91 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/LEDDisplay.h"
#include "glass/Context.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
namespace {
struct IndicatorData {
std::vector<int> values;
std::vector<ImU32> colors;
};
} // namespace
void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
wpi::SmallVector<LEDDisplayModel::Data, 64> dataBuf;
auto data = model->GetData(dataBuf);
int length = data.size();
bool running = model->IsRunning();
auto& storage = GetStorage();
int* numColumns = storage.GetIntRef("columns", 10);
bool* serpentine = storage.GetBoolRef("serpentine", false);
int* order = storage.GetIntRef("order", LEDConfig::RowMajor);
int* start = storage.GetIntRef("start", LEDConfig::UpperLeft);
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
ImGui::Combo("Order", order, options, 2);
}
{
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
"Lower Right"};
ImGui::Combo("Start", start, options, 4);
}
ImGui::Checkbox("Serpentine", serpentine);
if (*numColumns < 1) *numColumns = 1;
ImGui::PopItemWidth();
// show as LED indicators
auto iData = storage.GetData<IndicatorData>();
if (!iData) {
storage.SetData(std::make_shared<IndicatorData>());
iData = storage.GetData<IndicatorData>();
}
if (length > static_cast<int>(iData->values.size()))
iData->values.resize(length);
if (length > static_cast<int>(iData->colors.size()))
iData->colors.resize(length);
if (!running) {
iData->colors[0] = IM_COL32(128, 128, 128, 255);
for (int j = 0; j < length; ++j) iData->values[j] = -1;
} else {
for (int j = 0; j < length; ++j) {
iData->values[j] = j + 1;
iData->colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
}
}
LEDConfig config;
config.serpentine = *serpentine;
config.order = static_cast<LEDConfig::Order>(*order);
config.start = static_cast<LEDConfig::Start>(*start);
DrawLEDs(iData->values.data(), length, *numColumns, iData->colors.data(), 0,
0, config);
}
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
bool hasAny = false;
model->ForEachLEDDisplay([&](LEDDisplayModel& display, int i) {
hasAny = true;
if (model->GetNumLEDDisplays() > 1) ImGui::Text("LEDs[%d]", i);
PushID(i);
DisplayLEDDisplay(&display, i);
PopID();
});
if (!hasAny) ImGui::Text("No addressable LEDs");
}

View File

@@ -0,0 +1,150 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/PCM.h"
#include <cstdio>
#include <cstring>
#include <imgui.h>
#include <wpi/SmallVector.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
#include "glass/support/ExtraGuiWidgets.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
bool outputsEnabled) {
wpi::SmallVector<int, 16> channels;
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
if (j >= static_cast<int>(channels.size())) channels.resize(j + 1);
channels[j] = (outputsEnabled && data->GetValue()) ? 1 : -1;
}
});
if (channels.empty()) return false;
// show nonexistent channels as empty
for (auto&& ch : channels) {
if (ch == 0) ch = -2;
}
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
}
// header
bool open = CollapsingHeader(label);
PopupEditName("name", name);
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
// show channels as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(128, 128, 128, 255)};
DrawLEDs(channels.data(), channels.size(), channels.size(), colors);
if (open) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
PushID(j);
char solenoidName[64];
auto& info = data->GetNameInfo();
info.GetLabel(solenoidName, sizeof(solenoidName), "Solenoid", j);
data->LabelText(solenoidName, "%s", channels[j] == 1 ? "On" : "Off");
info.PopupEditName(j);
PopID();
}
});
ImGui::PopItemWidth();
}
return true;
}
void glass::DisplayPCMsSolenoids(PCMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPCM([&](PCMModel& pcm, int i) {
PushID(i);
if (DisplayPCMSolenoids(&pcm, i, outputsEnabled)) hasAny = true;
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}
void glass::DisplayCompressorDevice(PCMModel* model, int index,
bool outputsEnabled) {
auto compressor = model->GetCompressor();
if (!compressor || !compressor->Exists()) return;
DisplayCompressorDevice(compressor, index, outputsEnabled);
}
void glass::DisplayCompressorDevice(CompressorModel* model, int index,
bool outputsEnabled) {
char name[32];
std::snprintf(name, sizeof(name), "Compressor[%d]", index);
if (BeginDevice(name)) {
// output enabled
if (auto runningData = model->GetRunningData()) {
bool value = outputsEnabled && runningData->GetValue();
if (DeviceBoolean("Running", false, &value, runningData)) {
model->SetRunning(value);
}
}
// closed loop enabled
if (auto enabledData = model->GetEnabledData()) {
int value = enabledData->GetValue() ? 1 : 0;
static const char* enabledOptions[] = {"disabled", "enabled"};
if (DeviceEnum("Closed Loop", true, &value, enabledOptions, 2,
enabledData)) {
model->SetEnabled(value != 0);
}
}
// pressure switch
if (auto pressureSwitchData = model->GetPressureSwitchData()) {
int value = pressureSwitchData->GetValue() ? 1 : 0;
static const char* switchOptions[] = {"full", "low"};
if (DeviceEnum("Pressure", false, &value, switchOptions, 2,
pressureSwitchData)) {
model->SetPressureSwitch(value != 0);
}
}
// compressor current
if (auto currentData = model->GetCurrentData()) {
double value = currentData->GetValue();
if (DeviceDouble("Current (A)", false, &value, currentData)) {
model->SetCurrent(value);
}
}
EndDevice();
}
}
void glass::DisplayCompressorsDevice(PCMsModel* model, bool outputsEnabled) {
model->ForEachPCM([&](PCMModel& pcm, int i) {
DisplayCompressorDevice(&pcm, i, outputsEnabled);
});
}

View File

@@ -0,0 +1,92 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/PDP.h"
#include <algorithm>
#include <cstdio>
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static float DisplayChannel(PDPModel& pdp, int channel) {
float width = 0;
if (auto currentData = pdp.GetCurrentData(channel)) {
ImGui::PushID(channel);
auto& leftInfo = currentData->GetNameInfo();
char name[64];
leftInfo.GetLabel(name, sizeof(name), "", channel);
double val = currentData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (currentData->InputDouble(name, &val, 0, 0, "%.3f"))
pdp.SetCurrent(channel, val);
width = ImGui::GetItemRectSize().x;
leftInfo.PopupEditName(channel);
ImGui::PopID();
}
return width;
}
void glass::DisplayPDP(PDPModel* model, int index) {
char name[128];
std::snprintf(name, sizeof(name), "PDP[%d]", index);
if (CollapsingHeader(name)) {
// temperature
if (auto tempData = model->GetTemperatureData()) {
double value = tempData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (tempData->InputDouble("Temp", &value, 0, 0, "%.3f")) {
model->SetTemperature(value);
}
}
// voltage
if (auto voltageData = model->GetVoltageData()) {
double value = voltageData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (voltageData->InputDouble("Voltage", &value, 0, 0, "%.3f")) {
model->SetVoltage(value);
}
}
// channel currents; show as two columns laid out like PDP
const int numChannels = model->GetNumChannels();
ImGui::Text("Channel Current (A)");
ImGui::Columns(2, "channels", false);
float maxWidth = ImGui::GetFontSize() * 13;
for (int left = 0, right = numChannels - 1; left < right; ++left, --right) {
float leftWidth = DisplayChannel(*model, left);
ImGui::NextColumn();
float rightWidth = DisplayChannel(*model, right);
ImGui::NextColumn();
float width =
(std::max)(leftWidth, rightWidth) * 2 + ImGui::GetFontSize() * 4;
if (width > maxWidth) maxWidth = width;
}
ImGui::Columns(1);
ImGui::Dummy(ImVec2(maxWidth, 0));
}
}
void glass::DisplayPDPs(PDPsModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPDP([&](PDPModel& pdp, int i) {
hasAny = true;
PushID(i);
DisplayPDP(&pdp, i);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/PWM.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
auto data = model->GetSpeedData();
if (!data) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PWM[%d]###name", index);
}
int led = model->GetAddressableLED();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (led >= 0) {
ImGui::LabelText(label, "LED[%d]", led);
} else {
float val = outputsEnabled ? data->GetValue() : 0;
data->LabelText(label, "%0.3f", val);
}
if (PopupEditName("name", name)) {
data->SetName(name->c_str());
}
}
void glass::DisplayPWMs(PWMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachPWM([&](PWMModel& pwm, int i) {
hasAny = true;
PushID(i);
if (!first)
ImGui::Separator();
else
first = false;
DisplayPWM(&pwm, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,74 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/Relay.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
auto forwardData = model->GetForwardData();
auto reverseData = model->GetReverseData();
if (!forwardData && !reverseData) {
return;
}
bool forward = false;
bool reverse = false;
if (outputsEnabled) {
if (forwardData) forward = forwardData->GetValue();
if (reverseData) reverse = reverseData->GetValue();
}
std::string* name = GetStorage().GetStringRef("name");
ImGui::PushID("name");
if (!name->empty())
ImGui::Text("%s [%d]", name->c_str(), index);
else
ImGui::Text("Relay[%d]", index);
ImGui::PopID();
if (PopupEditName("name", name)) {
if (forwardData) forwardData->SetName(name->c_str());
if (reverseData) reverseData->SetName(name->c_str());
}
ImGui::SameLine();
// show forward and reverse as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(255, 0, 0, 255),
IM_COL32(128, 128, 128, 255)};
int values[2] = {reverseData ? (reverse ? 2 : -2) : -3,
forwardData ? (forward ? 1 : -1) : -3};
DataSource* sources[2] = {reverseData, forwardData};
DrawLEDSources(values, sources, 2, 2, colors);
}
void glass::DisplayRelays(RelaysModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachRelay([&](RelayModel& relay, int i) {
hasAny = true;
if (!first)
ImGui::Separator();
else
first = false;
PushID(i);
DisplayRelay(&relay, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,87 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-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 "glass/hardware/RoboRio.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
static void DisplayRail(RoboRioRailModel& rail, const char* name) {
if (CollapsingHeader(name)) {
ImGui::PushID(name);
if (auto data = rail.GetVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
rail.SetVoltage(val);
}
}
if (auto data = rail.GetCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
rail.SetCurrent(val);
}
}
if (auto data = rail.GetActiveData()) {
static const char* options[] = {"inactive", "active"};
int val = data->GetValue() ? 1 : 0;
if (data->Combo("Active", &val, options, 2)) {
rail.SetActive(val);
}
}
if (auto data = rail.GetFaultsData()) {
int val = data->GetValue();
if (data->InputInt("Faults", &val)) {
rail.SetFaults(val);
}
}
ImGui::PopID();
}
}
void glass::DisplayRoboRio(RoboRioModel* model) {
ImGui::Button("User Button");
model->SetUserButton(ImGui::IsItemActive());
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
if (CollapsingHeader("RoboRIO Input")) {
ImGui::PushID("RoboRIO Input");
if (auto data = model->GetVInVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
model->SetVInVoltage(val);
}
}
if (auto data = model->GetVInCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
model->SetVInCurrent(val);
}
}
ImGui::PopID();
}
if (auto rail = model->GetUser6VRail()) {
DisplayRail(*rail, "6V Rail");
}
if (auto rail = model->GetUser5VRail()) {
DisplayRail(*rail, "5V Rail");
}
if (auto rail = model->GetUser3V3Rail()) {
DisplayRail(*rail, "3.3V Rail");
}
ImGui::PopItemWidth();
}