[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

@@ -1,75 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "AccelerometerGui.h"
#include <cstdio>
#include <memory>
#include <hal/Value.h>
#include <hal/simulation/AccelerometerData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerX, "X Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerY, "Y Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerZ, "Z Accel");
} // namespace
static std::unique_ptr<AccelerometerXSource> gAccelXSource;
static std::unique_ptr<AccelerometerYSource> gAccelYSource;
static std::unique_ptr<AccelerometerZSource> gAccelZSource;
static void UpdateAccelSources() {
if (!HALSIM_GetAccelerometerActive(0)) return;
if (!gAccelXSource) gAccelXSource = std::make_unique<AccelerometerXSource>(0);
if (!gAccelYSource) gAccelYSource = std::make_unique<AccelerometerYSource>(0);
if (!gAccelZSource) gAccelZSource = std::make_unique<AccelerometerZSource>(0);
}
static void DisplayAccelerometers() {
if (!HALSIM_GetAccelerometerActive(0)) return;
if (SimDeviceGui::StartDevice("BuiltInAccel")) {
HAL_Value value;
// Range
value = HAL_MakeEnum(HALSIM_GetAccelerometerRange(0));
static const char* rangeOptions[] = {"2G", "4G", "8G"};
SimDeviceGui::DisplayValue("Range", true, &value, rangeOptions, 3);
// X Accel
value = HAL_MakeDouble(gAccelXSource->GetValue());
if (SimDeviceGui::DisplayValueSource("X Accel", false, &value,
gAccelXSource.get()))
HALSIM_SetAccelerometerX(0, value.data.v_double);
// Y Accel
value = HAL_MakeDouble(gAccelYSource->GetValue());
if (SimDeviceGui::DisplayValueSource("Y Accel", false, &value,
gAccelYSource.get()))
HALSIM_SetAccelerometerY(0, value.data.v_double);
// Z Accel
value = HAL_MakeDouble(gAccelZSource->GetValue());
if (SimDeviceGui::DisplayValueSource("Z Accel", false, &value,
gAccelZSource.get()))
HALSIM_SetAccelerometerZ(0, value.data.v_double);
SimDeviceGui::FinishDevice();
}
}
void AccelerometerGui::Initialize() {
HALSimGui::AddExecute(UpdateAccelSources);
SimDeviceGui::Add(DisplayAccelerometers);
}

View File

@@ -0,0 +1,67 @@
/*----------------------------------------------------------------------------*/
/* 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 "AccelerometerSimGui.h"
#include <glass/hardware/Accelerometer.h>
#include <glass/other/DeviceTree.h>
#include <memory>
#include <hal/Value.h>
#include <hal/simulation/AccelerometerData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace glass;
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerX, "X Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerY, "Y Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerZ, "Z Accel");
class AccelerometerSimModel : public glass::AccelerometerModel {
public:
explicit AccelerometerSimModel(int32_t index)
: m_index{index}, m_xData{m_index}, m_yData{m_index}, m_zData{m_index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAccelerometerActive(m_index); }
glass::DataSource* GetXData() override { return &m_xData; }
glass::DataSource* GetYData() override { return &m_yData; }
glass::DataSource* GetZData() override { return &m_zData; }
int GetRange() override { return HALSIM_GetAccelerometerRange(m_index); }
void SetX(double val) override { HALSIM_SetAccelerometerX(m_index, val); }
void SetY(double val) override { HALSIM_SetAccelerometerY(m_index, val); }
void SetZ(double val) override { HALSIM_SetAccelerometerZ(m_index, val); }
void SetRange(int val) override {
HALSIM_SetAccelerometerRange(m_index,
static_cast<HAL_AccelerometerRange>(val));
}
private:
int32_t m_index;
AccelerometerXSource m_xData;
AccelerometerYSource m_yData;
AccelerometerZSource m_zData;
};
} // namespace
void AccelerometerSimGui::Initialize() {
SimDeviceGui::GetDeviceTree().Add(
std::make_unique<AccelerometerSimModel>(0), [](glass::Model* model) {
glass::DisplayAccelerometerDevice(
static_cast<AccelerometerSimModel*>(model));
});
}

View File

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

View File

@@ -7,120 +7,103 @@
#include "AddressableLEDGui.h"
#include <glass/hardware/LEDDisplay.h>
#include <hal/Ports.h>
#include <hal/simulation/AddressableLEDData.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/SmallVector.h>
#include <wpi/StringRef.h>
#include "ExtraGuiWidgets.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
struct LEDDisplayInfo {
int numColumns = 10;
LEDConfig config;
class AddressableLEDModel : public glass::LEDDisplayModel {
public:
explicit AddressableLEDModel(int32_t index) : m_index{index} {}
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
void Update() override {}
bool Exists() override {
return HALSIM_GetAddressableLEDInitialized(m_index);
}
bool IsRunning() override { return HALSIM_GetAddressableLEDRunning(m_index); }
wpi::ArrayRef<Data> GetData(wpi::SmallVectorImpl<Data>&) override {
size_t length = HALSIM_GetAddressableLEDData(m_index, m_data);
return {reinterpret_cast<Data*>(m_data), length};
}
private:
int32_t m_index;
HAL_AddressableLEDData m_data[HAL_kAddressableLEDMaxLength];
};
class AddressableLEDsModel : public glass::LEDDisplaysModel {
public:
AddressableLEDsModel() : m_models(HAL_GetNumAddressableLEDs()) {}
void Update() override;
bool Exists() override;
size_t GetNumLEDDisplays() override { return m_models.size(); }
void ForEachLEDDisplay(
wpi::function_ref<void(glass::LEDDisplayModel& model, int index)> func)
override;
private:
std::vector<std::unique_ptr<AddressableLEDModel>> m_models;
};
} // namespace
static IniSaver<LEDDisplayInfo> gDisplaySettings{"AddressableLED"};
bool LEDDisplayInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "columns") {
int num;
if (value.getAsInteger(10, num)) return true;
numColumns = num;
} else if (name == "serpentine") {
int num;
if (value.getAsInteger(10, num)) return true;
config.serpentine = num != 0;
} else if (name == "order") {
int num;
if (value.getAsInteger(10, num)) return true;
config.order = static_cast<LEDConfig::Order>(num);
} else if (name == "start") {
int num;
if (value.getAsInteger(10, num)) return true;
config.start = static_cast<LEDConfig::Start>(num);
} else {
return false;
}
return true;
}
void LEDDisplayInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("columns=%d\nserpentine=%d\norder=%d\nstart=%d\n", numColumns,
config.serpentine ? 1 : 0, static_cast<int>(config.order),
static_cast<int>(config.start));
}
static void DisplayAddressableLEDs() {
bool hasAny = false;
static const int numLED = HAL_GetNumAddressableLEDs();
for (int i = 0; i < numLED; ++i) {
if (!HALSIM_GetAddressableLEDInitialized(i)) continue;
hasAny = true;
if (numLED > 1) ImGui::Text("LEDs[%d]", i);
static HAL_AddressableLEDData data[HAL_kAddressableLEDMaxLength];
int length = HALSIM_GetAddressableLEDData(i, data);
bool running = HALSIM_GetAddressableLEDRunning(i);
auto& info = gDisplaySettings[i];
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", &info.numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
int val = info.config.order;
if (ImGui::Combo("Order", &val, options, 2))
info.config.order = static_cast<LEDConfig::Order>(val);
}
{
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
"Lower Right"};
int val = info.config.start;
if (ImGui::Combo("Start", &val, options, 4))
info.config.start = static_cast<LEDConfig::Start>(val);
}
ImGui::Checkbox("Serpentine", &info.config.serpentine);
if (info.numColumns < 1) info.numColumns = 1;
ImGui::PopItemWidth();
// show as LED indicators
static int values[HAL_kAddressableLEDMaxLength];
static ImU32 colors[HAL_kAddressableLEDMaxLength];
if (!running) {
colors[0] = IM_COL32(128, 128, 128, 255);
for (int j = 0; j < length; ++j) values[j] = -1;
} else {
for (int j = 0; j < length; ++j) {
values[j] = j + 1;
colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
void AddressableLEDsModel::Update() {
for (int i = 0; i < static_cast<int>(m_models.size()); ++i) {
auto& model = m_models[i];
if (HALSIM_GetAddressableLEDInitialized(i)) {
if (!model) {
model = std::make_unique<AddressableLEDModel>(i);
}
if (model) model->Update();
} else {
model.reset();
}
DrawLEDs(values, length, info.numColumns, colors, 0, 0, info.config);
}
if (!hasAny) ImGui::Text("No addressable LEDs");
}
bool AddressableLEDsModel::Exists() {
for (auto&& model : m_models) {
if (model && model->Exists()) return true;
}
return false;
}
void AddressableLEDsModel::ForEachLEDDisplay(
wpi::function_ref<void(glass::LEDDisplayModel& model, int index)> func) {
for (int i = 0; i < static_cast<int>(m_models.size()); ++i) {
if (m_models[i]) func(*m_models[i], i);
}
}
static bool AddressableLEDsExists() {
static const int numLED = HAL_GetNumAddressableLEDs();
for (int i = 0; i < numLED; ++i) {
if (HALSIM_GetAddressableLEDInitialized(i)) return true;
}
return false;
}
void AddressableLEDGui::Initialize() {
gDisplaySettings.Initialize();
HALSimGui::AddWindow("Addressable LEDs", DisplayAddressableLEDs,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetWindowVisibility("Addressable LEDs", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("Addressable LEDs", 290, 100);
HALSimGui::halProvider.Register(
"Addressable LEDs", [] { return AddressableLEDsExists(); },
[] { return std::make_unique<AddressableLEDsModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(290, 100);
return glass::MakeFunctionView([=] {
glass::DisplayLEDDisplays(static_cast<AddressableLEDsModel*>(model));
});
});
}

View File

@@ -1,80 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogGyroGui.h"
#include <cstdio>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/AnalogGyroData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroAngle, "AGyro Angle");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroRate, "AGyro Rate");
struct AnalogGyroSource {
explicit AnalogGyroSource(int32_t index) : angle{index}, rate{index} {}
AnalogGyroAngleSource angle;
AnalogGyroRateSource rate;
};
} // namespace
static std::vector<std::unique_ptr<AnalogGyroSource>> gAnalogGyroSources;
static void UpdateAnalogGyroSources() {
for (int i = 0, iend = gAnalogGyroSources.size(); i < iend; ++i) {
auto& source = gAnalogGyroSources[i];
if (HALSIM_GetAnalogGyroInitialized(i)) {
if (!source) {
source = std::make_unique<AnalogGyroSource>(i);
}
} else {
source.reset();
}
}
}
static void DisplayAnalogGyros() {
for (int i = 0, iend = gAnalogGyroSources.size(); i < iend; ++i) {
if (auto source = gAnalogGyroSources[i].get()) {
char name[32];
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", i);
if (SimDeviceGui::StartDevice(name)) {
HAL_Value value;
// angle
value = HAL_MakeDouble(source->angle.GetValue());
if (SimDeviceGui::DisplayValueSource("Angle", false, &value,
&source->angle))
HALSIM_SetAnalogGyroAngle(i, value.data.v_double);
// rate
value = HAL_MakeDouble(source->rate.GetValue());
if (SimDeviceGui::DisplayValueSource("Rate", false, &value,
&source->rate))
HALSIM_SetAnalogGyroRate(i, value.data.v_double);
SimDeviceGui::FinishDevice();
}
}
}
}
void AnalogGyroGui::Initialize() {
gAnalogGyroSources.resize(HAL_GetNumAccumulators());
HALSimGui::AddExecute(UpdateAnalogGyroSources);
SimDeviceGui::Add(DisplayAnalogGyros);
}

View File

@@ -0,0 +1,101 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogGyroSimGui.h"
#include <glass/hardware/AnalogGyro.h>
#include <glass/other/DeviceTree.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/AnalogGyroData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroAngle, "AGyro Angle");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroRate, "AGyro Rate");
class AnalogGyroSimModel : public glass::AnalogGyroModel {
public:
explicit AnalogGyroSimModel(int32_t index)
: m_index{index}, m_angle{index}, m_rate{index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAnalogGyroInitialized(m_index); }
glass::DataSource* GetAngleData() override { return &m_angle; }
glass::DataSource* GetRateData() override { return &m_rate; }
void SetAngle(double val) override {
HALSIM_SetAnalogGyroAngle(m_index, val);
}
void SetRate(double val) override { HALSIM_SetAnalogGyroRate(m_index, val); }
private:
int32_t m_index;
AnalogGyroAngleSource m_angle;
AnalogGyroRateSource m_rate;
};
class AnalogGyrosSimModel : public glass::AnalogGyrosModel {
public:
AnalogGyrosSimModel() : m_models(HAL_GetNumAccumulators()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachAnalogGyro(
wpi::function_ref<void(glass::AnalogGyroModel& model, int index)> func)
override;
private:
// indexed by channel
std::vector<std::unique_ptr<AnalogGyroSimModel>> m_models;
};
} // namespace
void AnalogGyrosSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetAnalogGyroInitialized(i)) {
if (!model) {
model = std::make_unique<AnalogGyroSimModel>(i);
}
} else {
model.reset();
}
}
}
void AnalogGyrosSimModel::ForEachAnalogGyro(
wpi::function_ref<void(glass::AnalogGyroModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
void AnalogGyroSimGui::Initialize() {
SimDeviceGui::GetDeviceTree().Add(
std::make_unique<AnalogGyrosSimModel>(), [](glass::Model* model) {
glass::DisplayAnalogGyrosDevice(
static_cast<AnalogGyrosSimModel*>(model));
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class AccelerometerGui {
class AnalogGyroSimGui {
public:
static void Initialize();
};

View File

@@ -1,102 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogInputGui.h"
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AnalogGyroData.h>
#include <hal/simulation/AnalogInData.h>
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogInVoltage, "AIn");
} // namespace
// indexed by channel
static IniSaver<NameInfo> gAnalogInputs{"AnalogInput"};
static std::vector<std::unique_ptr<AnalogInVoltageSource>> gAnalogInputSources;
static void UpdateAnalogInputSources() {
for (int i = 0, iend = gAnalogInputSources.size(); i < iend; ++i) {
auto& source = gAnalogInputSources[i];
if (HALSIM_GetAnalogInInitialized(i)) {
if (!source) {
source = std::make_unique<AnalogInVoltageSource>(i);
source->SetName(gAnalogInputs[i].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayAnalogInputs() {
ImGui::Text("(Use Ctrl+Click to edit value)");
bool hasInputs = false;
static const int numAccum = HAL_GetNumAccumulators();
bool first = true;
for (int i = 0, iend = gAnalogInputSources.size(); i < iend; ++i) {
if (auto source = gAnalogInputSources[i].get()) {
ImGui::PushID(i);
hasInputs = true;
if (!first) {
ImGui::Spacing();
ImGui::Spacing();
} else {
first = false;
}
auto& info = gAnalogInputs[i];
// build label
char label[128];
info.GetLabel(label, sizeof(label), "In", i);
if (i < numAccum && HALSIM_GetAnalogGyroInitialized(i)) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "AnalogGyro[%d]", i);
ImGui::PopStyleColor();
} else if (auto simDevice = HALSIM_GetAnalogInSimDevice(i)) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "%s", HALSIM_GetSimDeviceName(simDevice));
ImGui::PopStyleColor();
} else {
float val = source->GetValue();
if (source->SliderFloat(label, &val, 0.0, 5.0))
HALSIM_SetAnalogInVoltage(i, val);
}
// context menu to change name
if (info.PopupEditName(i)) {
source->SetName(info.GetName());
}
ImGui::PopID();
}
}
if (!hasInputs) ImGui::Text("No analog inputs");
}
void AnalogInputGui::Initialize() {
gAnalogInputs.Initialize();
gAnalogInputSources.resize(HAL_GetNumAnalogInputs());
HALSimGui::AddExecute(UpdateAnalogInputSources);
HALSimGui::AddWindow("Analog Inputs", DisplayAnalogInputs,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Analog Inputs", 640, 20);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 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 AnalogInputGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -0,0 +1,123 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogInputSimGui.h"
#include <glass/View.h>
#include <glass/hardware/AnalogInput.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AnalogGyroData.h>
#include <hal/simulation/AnalogInData.h>
#include <hal/simulation/SimDeviceData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogInVoltage, "AIn");
class AnalogInputSimModel : public glass::AnalogInputModel {
public:
explicit AnalogInputSimModel(int32_t index)
: m_index{index}, m_voltageData{m_index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAnalogInInitialized(m_index); }
bool IsGyro() const override {
return m_index < HAL_GetNumAccumulators() &&
HALSIM_GetAnalogGyroInitialized(m_index);
}
const char* GetSimDevice() const override {
if (auto simDevice = HALSIM_GetAnalogInSimDevice(m_index)) {
return HALSIM_GetSimDeviceName(simDevice);
} else {
return nullptr;
}
}
glass::DataSource* GetVoltageData() override { return &m_voltageData; }
void SetVoltage(double val) override {
HALSIM_SetAnalogInVoltage(m_index, val);
}
private:
int32_t m_index;
AnalogInVoltageSource m_voltageData;
};
class AnalogInputsSimModel : public glass::AnalogInputsModel {
public:
AnalogInputsSimModel() : m_models(HAL_GetNumAnalogInputs()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachAnalogInput(
wpi::function_ref<void(glass::AnalogInputModel& model, int index)> func)
override;
private:
// indexed by channel
std::vector<std::unique_ptr<AnalogInputSimModel>> m_models;
};
} // namespace
void AnalogInputsSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetAnalogInInitialized(i)) {
if (!model) {
model = std::make_unique<AnalogInputSimModel>(i);
}
} else {
model.reset();
}
}
}
void AnalogInputsSimModel::ForEachAnalogInput(
wpi::function_ref<void(glass::AnalogInputModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
static bool AnalogInputsAnyInitialized() {
static const int32_t num = HAL_GetNumAnalogInputs();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetAnalogInInitialized(i)) return true;
}
return false;
}
void AnalogInputSimGui::Initialize() {
HALSimGui::halProvider.Register(
"Analog Inputs", AnalogInputsAnyInitialized,
[] { return std::make_unique<AnalogInputsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(640, 20);
return glass::MakeFunctionView([=] {
glass::DisplayAnalogInputs(static_cast<AnalogInputsSimModel*>(model));
});
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class AnalogOutGui {
class AnalogInputSimGui {
public:
static void Initialize();
};

View File

@@ -1,81 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogOutGui.h"
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AnalogOutData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogOutVoltage, "AOut");
} // namespace
static IniSaver<NameInfo> gAnalogOuts{"AnalogOut"}; // indexed by channel
static std::vector<std::unique_ptr<AnalogOutVoltageSource>> gAnalogOutSources;
static void UpdateAnalogOutSources() {
for (int i = 0, iend = gAnalogOutSources.size(); i < iend; ++i) {
auto& source = gAnalogOutSources[i];
if (HALSIM_GetAnalogOutInitialized(i)) {
if (!source) {
source = std::make_unique<AnalogOutVoltageSource>(i);
source->SetName(gAnalogOuts[i].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayAnalogOutputs() {
int count = 0;
for (auto&& source : gAnalogOutSources) {
if (source) ++count;
}
if (count == 0) return;
if (SimDeviceGui::StartDevice("Analog Outputs")) {
for (int i = 0, iend = gAnalogOutSources.size(); i < iend; ++i) {
if (auto source = gAnalogOutSources[i].get()) {
ImGui::PushID(i);
auto& info = gAnalogOuts[i];
char label[128];
info.GetLabel(label, sizeof(label), "Out", i);
HAL_Value value = HAL_MakeDouble(source->GetValue());
SimDeviceGui::DisplayValueSource(label, true, &value, source);
if (info.PopupEditName(i)) {
if (source) source->SetName(info.GetName());
}
ImGui::PopID();
}
}
SimDeviceGui::FinishDevice();
}
}
void AnalogOutGui::Initialize() {
gAnalogOuts.Initialize();
gAnalogOutSources.resize(HAL_GetNumAnalogOutputs());
HALSimGui::AddExecute(UpdateAnalogOutSources);
SimDeviceGui::Add(DisplayAnalogOutputs);
}

View File

@@ -0,0 +1,96 @@
/*----------------------------------------------------------------------------*/
/* 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 "AnalogOutputSimGui.h"
#include <glass/hardware/AnalogOutput.h>
#include <glass/other/DeviceTree.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AnalogOutData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogOutVoltage, "AOut");
class AnalogOutputSimModel : public glass::AnalogOutputModel {
public:
explicit AnalogOutputSimModel(int32_t index)
: m_index{index}, m_voltageData{m_index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAnalogOutInitialized(m_index); }
glass::DataSource* GetVoltageData() override { return &m_voltageData; }
void SetVoltage(double val) override {
HALSIM_SetAnalogOutVoltage(m_index, val);
}
private:
int32_t m_index;
AnalogOutVoltageSource m_voltageData;
};
class AnalogOutputsSimModel : public glass::AnalogOutputsModel {
public:
AnalogOutputsSimModel() : m_models(HAL_GetNumAnalogOutputs()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachAnalogOutput(
wpi::function_ref<void(glass::AnalogOutputModel& model, int index)> func)
override;
private:
// indexed by channel
std::vector<std::unique_ptr<AnalogOutputSimModel>> m_models;
};
} // namespace
void AnalogOutputsSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetAnalogOutInitialized(i)) {
if (!model) {
model = std::make_unique<AnalogOutputSimModel>(i);
}
} else {
model.reset();
}
}
}
void AnalogOutputsSimModel::ForEachAnalogOutput(
wpi::function_ref<void(glass::AnalogOutputModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
void AnalogOutputSimGui::Initialize() {
SimDeviceGui::GetDeviceTree().Add(
std::make_unique<AnalogOutputsSimModel>(), [](glass::Model* model) {
glass::DisplayAnalogOutputsDevice(
static_cast<AnalogOutputsSimModel*>(model));
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class AnalogGyroGui {
class AnalogOutputSimGui {
public:
static void Initialize();
};

View File

@@ -1,104 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "CompressorGui.h"
#include <cstdio>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/PCMData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMCompressorOn, "Compressor On");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMClosedLoopEnabled, "Closed Loop");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMPressureSwitch, "Pressure Switch");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PCMCompressorCurrent, "Comp Current");
struct CompressorSource {
explicit CompressorSource(int32_t index)
: running{index}, enabled{index}, pressureSwitch{index}, current{index} {}
PCMCompressorOnSource running;
PCMClosedLoopEnabledSource enabled;
PCMPressureSwitchSource pressureSwitch;
PCMCompressorCurrentSource current;
};
} // namespace
static std::vector<std::unique_ptr<CompressorSource>> gCompressorSources;
static void UpdateCompressorSources() {
for (int i = 0, iend = gCompressorSources.size(); i < iend; ++i) {
auto& source = gCompressorSources[i];
if (HALSIM_GetPCMCompressorInitialized(i)) {
if (!source) {
source = std::make_unique<CompressorSource>(i);
}
} else {
source.reset();
}
}
}
static void DisplayCompressors() {
for (int i = 0, iend = gCompressorSources.size(); i < iend; ++i) {
if (auto source = gCompressorSources[i].get()) {
char name[32];
std::snprintf(name, sizeof(name), "Compressor[%d]", i);
if (SimDeviceGui::StartDevice(name)) {
HAL_Value value;
// enabled
if (HALSimGui::AreOutputsDisabled())
value = HAL_MakeBoolean(false);
else
value = HAL_MakeBoolean(source->running.GetValue());
if (SimDeviceGui::DisplayValueSource("Running", false, &value,
&source->running))
HALSIM_SetPCMCompressorOn(i, value.data.v_boolean);
// closed loop
value = HAL_MakeEnum(source->enabled.GetValue() ? 1 : 0);
static const char* enabledOptions[] = {"disabled", "enabled"};
if (SimDeviceGui::DisplayValueSource("Closed Loop", true, &value,
&source->enabled, enabledOptions,
2))
HALSIM_SetPCMClosedLoopEnabled(i, value.data.v_enum);
// pressure switch
value = HAL_MakeEnum(source->pressureSwitch.GetValue() ? 1 : 0);
static const char* switchOptions[] = {"full", "low"};
if (SimDeviceGui::DisplayValueSource("Pressure", false, &value,
&source->pressureSwitch,
switchOptions, 2))
HALSIM_SetPCMPressureSwitch(i, value.data.v_enum);
// compressor current
value = HAL_MakeDouble(source->current.GetValue());
if (SimDeviceGui::DisplayValueSource("Current (A)", false, &value,
&source->current))
HALSIM_SetPCMCompressorCurrent(i, value.data.v_double);
SimDeviceGui::FinishDevice();
}
}
}
}
void CompressorGui::Initialize() {
gCompressorSources.resize(HAL_GetNumPCMModules());
HALSimGui::AddExecute(UpdateCompressorSources);
SimDeviceGui::Add(DisplayCompressors);
}

View File

@@ -1,214 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "DIOGui.h"
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/DIOData.h>
#include <hal/simulation/DigitalPWMData.h>
#include <hal/simulation/DutyCycleData.h>
#include <hal/simulation/EncoderData.h>
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(DIOValue, "DIO");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(DigitalPWMDutyCycle, "DPWM");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(DutyCycleOutput, "DutyCycle");
} // namespace
static IniSaver<NameInfo> gDIO{"DIO"};
static std::vector<std::unique_ptr<DIOValueSource>> gDIOSources;
static std::vector<std::unique_ptr<DigitalPWMDutyCycleSource>> gDPWMSources;
static std::vector<std::unique_ptr<DutyCycleOutputSource>> gDutyCycleSources;
static void LabelSimDevice(const char* name, HAL_SimDeviceHandle simDevice) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(name, "%s", HALSIM_GetSimDeviceName(simDevice));
ImGui::PopStyleColor();
}
static void UpdateDIOSources() {
for (int i = 0, iend = gDIOSources.size(); i < iend; ++i) {
auto& source = gDIOSources[i];
if (HALSIM_GetDIOInitialized(i)) {
if (!source) {
source = std::make_unique<DIOValueSource>(i);
source->SetName(gDIO[i].GetName());
}
} else {
source.reset();
}
}
}
static void UpdateDPWMSources() {
const int numDIO = gDIOSources.size();
for (int i = 0, iend = gDPWMSources.size(); i < iend; ++i) {
auto& source = gDPWMSources[i];
if (HALSIM_GetDigitalPWMInitialized(i)) {
if (!source) {
int channel = HALSIM_GetDigitalPWMPin(i);
if (channel >= 0 && channel < numDIO) {
source = std::make_unique<DigitalPWMDutyCycleSource>(i, channel);
source->SetName(gDIO[channel].GetName());
}
}
} else {
source.reset();
}
}
}
static void UpdateDutyCycleSources() {
const int numDIO = gDIOSources.size();
for (int i = 0, iend = gDutyCycleSources.size(); i < iend; ++i) {
auto& source = gDutyCycleSources[i];
if (HALSIM_GetDutyCycleInitialized(i)) {
if (!source) {
int channel = HALSIM_GetDutyCycleDigitalChannel(i);
if (channel >= 0 && channel < numDIO) {
source = std::make_unique<DutyCycleOutputSource>(i, channel);
source->SetName(gDIO[channel].GetName());
}
}
} else {
source.reset();
}
}
}
static void DisplayDIO() {
bool hasAny = false;
const int numDIO = gDIOSources.size();
const int numPWM = gDPWMSources.size();
static const int numEncoder = HAL_GetNumEncoders();
const int numDutyCycle = gDutyCycleSources.size();
static auto pwmMap = std::make_unique<int[]>(numDIO);
static auto encoderMap = std::make_unique<int[]>(numDIO);
static auto dutyCycleMap = std::make_unique<int[]>(numDIO);
std::memset(pwmMap.get(), 0, numDIO * sizeof(pwmMap[0]));
std::memset(encoderMap.get(), 0, numDIO * sizeof(encoderMap[0]));
std::memset(dutyCycleMap.get(), 0, numDIO * sizeof(dutyCycleMap[0]));
for (int i = 0; i < numPWM; ++i) {
if (auto source = gDPWMSources[i].get()) {
int channel = source->GetChannel();
if (channel >= 0 && channel < numDIO) pwmMap[channel] = i + 1;
}
}
for (int i = 0; i < numEncoder; ++i) {
if (HALSIM_GetEncoderInitialized(i)) {
int channel;
channel = HALSIM_GetEncoderDigitalChannelA(i);
if (channel >= 0 && channel < numDIO) encoderMap[channel] = i + 1;
channel = HALSIM_GetEncoderDigitalChannelB(i);
if (channel >= 0 && channel < numDIO) encoderMap[channel] = i + 1;
}
}
for (int i = 0; i < numDutyCycle; ++i) {
if (auto source = gDutyCycleSources[i].get()) {
int channel = source->GetChannel();
if (channel >= 0 && channel < numDIO) dutyCycleMap[channel] = i + 1;
}
}
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
for (int i = 0; i < numDIO; ++i) {
if (auto dioSource = gDIOSources[i].get()) {
ImGui::PushID(i);
hasAny = true;
DigitalPWMDutyCycleSource* dpwmSource = nullptr;
DutyCycleOutputSource* dutyCycleSource = nullptr;
auto& info = gDIO[i];
char label[128];
if (pwmMap[i] > 0) {
dpwmSource = gDPWMSources[pwmMap[i] - 1].get();
info.GetLabel(label, sizeof(label), "PWM", i);
if (auto simDevice = HALSIM_GetDIOSimDevice(i)) {
LabelSimDevice(label, simDevice);
} else {
dpwmSource->LabelText(label, "%0.3f", dpwmSource->GetValue());
}
} else if (encoderMap[i] > 0) {
info.GetLabel(label, sizeof(label), " In", i);
if (auto simDevice = HALSIM_GetEncoderSimDevice(encoderMap[i] - 1)) {
LabelSimDevice(label, simDevice);
} else {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "Encoder[%d,%d]",
HALSIM_GetEncoderDigitalChannelA(encoderMap[i] - 1),
HALSIM_GetEncoderDigitalChannelB(encoderMap[i] - 1));
ImGui::PopStyleColor();
}
} else if (dutyCycleMap[i] > 0) {
dutyCycleSource = gDutyCycleSources[dutyCycleMap[i] - 1].get();
info.GetLabel(label, sizeof(label), "Dty", i);
if (auto simDevice =
HALSIM_GetDutyCycleSimDevice(dutyCycleMap[i] - 1)) {
LabelSimDevice(label, simDevice);
} else {
double val = dutyCycleSource->GetValue();
if (dutyCycleSource->InputDouble(label, &val))
HALSIM_SetDutyCycleOutput(dutyCycleMap[i] - 1, val);
}
} else if (!HALSIM_GetDIOIsInput(i)) {
info.GetLabel(label, sizeof(label), "Out", i);
if (auto simDevice = HALSIM_GetDIOSimDevice(i)) {
LabelSimDevice(label, simDevice);
} else {
dioSource->LabelText(
label, "%s", dioSource->GetValue() != 0 ? "1 (high)" : "0 (low)");
}
} else {
info.GetLabel(label, sizeof(label), " In", i);
if (auto simDevice = HALSIM_GetDIOSimDevice(i)) {
LabelSimDevice(label, simDevice);
} else {
static const char* options[] = {"0 (low)", "1 (high)"};
int val = dioSource->GetValue() != 0 ? 1 : 0;
if (dioSource->Combo(label, &val, options, 2))
HALSIM_SetDIOValue(i, val);
}
}
if (info.PopupEditName(i)) {
dioSource->SetName(info.GetName());
if (dpwmSource) dpwmSource->SetName(info.GetName());
if (dutyCycleSource) dutyCycleSource->SetName(info.GetName());
}
ImGui::PopID();
}
}
ImGui::PopItemWidth();
if (!hasAny) ImGui::Text("No Digital I/O");
}
void DIOGui::Initialize() {
gDIO.Initialize();
gDIOSources.resize(HAL_GetNumDigitalChannels());
gDPWMSources.resize(HAL_GetNumDigitalPWMOutputs());
gDutyCycleSources.resize(HAL_GetNumDutyCycles());
HALSimGui::AddExecute(UpdateDIOSources);
HALSimGui::AddExecute(UpdateDPWMSources);
HALSimGui::AddExecute(UpdateDutyCycleSources);
HALSimGui::AddWindow("DIO", DisplayDIO, ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("DIO", 470, 20);
}

View File

@@ -0,0 +1,244 @@
/*----------------------------------------------------------------------------*/
/* 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 "DIOSimGui.h"
#include <glass/hardware/DIO.h>
#include <glass/hardware/Encoder.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/DIOData.h>
#include <hal/simulation/DigitalPWMData.h>
#include <hal/simulation/DutyCycleData.h>
#include <hal/simulation/EncoderData.h>
#include <hal/simulation/SimDeviceData.h>
#include "EncoderSimGui.h"
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(DIOValue, "DIO");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(DigitalPWMDutyCycle, "DPWM");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(DutyCycleOutput, "DutyCycle");
class DPWMSimModel : public glass::DPWMModel {
public:
DPWMSimModel(int32_t index, int32_t dioChannel)
: m_dioChannel{dioChannel}, m_index{index}, m_valueData{index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetDigitalPWMInitialized(m_index); }
const char* GetSimDevice() const override {
if (auto simDevice = HALSIM_GetDIOSimDevice(m_dioChannel)) {
return HALSIM_GetSimDeviceName(simDevice);
} else {
return nullptr;
}
}
glass::DataSource* GetValueData() override { return &m_valueData; }
void SetValue(double val) override {
HALSIM_SetDigitalPWMDutyCycle(m_index, val);
}
private:
int32_t m_dioChannel;
int32_t m_index;
DigitalPWMDutyCycleSource m_valueData;
};
class DutyCycleSimModel : public glass::DutyCycleModel {
public:
explicit DutyCycleSimModel(int32_t index)
: m_index{index}, m_valueData{index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetDutyCycleInitialized(m_index); }
const char* GetSimDevice() const override {
if (auto simDevice = HALSIM_GetDutyCycleSimDevice(m_index)) {
return HALSIM_GetSimDeviceName(simDevice);
} else {
return nullptr;
}
}
glass::DataSource* GetValueData() override { return &m_valueData; }
void SetValue(double val) override {
HALSIM_SetDutyCycleOutput(m_index, val);
}
private:
int32_t m_index;
DutyCycleOutputSource m_valueData;
};
class DIOSimModel : public glass::DIOModel {
public:
explicit DIOSimModel(int32_t channel)
: m_channel{channel}, m_valueData{channel} {}
void Update() override {}
bool Exists() override { return HALSIM_GetDIOInitialized(m_channel); }
bool IsReadOnly() override { return !IsInput(); }
const char* GetName() const override { return ""; }
const char* GetSimDevice() const override {
if (auto simDevice = HALSIM_GetDIOSimDevice(m_channel)) {
return HALSIM_GetSimDeviceName(simDevice);
} else {
return nullptr;
}
}
DPWMSimModel* GetDPWM() override { return m_dpwmSource; }
DutyCycleSimModel* GetDutyCycle() override { return m_dutyCycleSource; }
glass::EncoderModel* GetEncoder() override { return m_encoderSource; }
void SetDPWM(DPWMSimModel* model) { m_dpwmSource = model; }
void SetDutyCycle(DutyCycleSimModel* model) { m_dutyCycleSource = model; }
void SetEncoder(glass::EncoderModel* model) { m_encoderSource = model; }
bool IsInput() const override { return HALSIM_GetDIOIsInput(m_channel); }
glass::DataSource* GetValueData() override { return &m_valueData; }
void SetValue(bool val) override { HALSIM_SetDIOValue(m_channel, val); }
private:
int32_t m_channel;
DIOValueSource m_valueData;
DPWMSimModel* m_dpwmSource = nullptr;
DutyCycleSimModel* m_dutyCycleSource = nullptr;
glass::EncoderModel* m_encoderSource = nullptr;
};
class DIOsSimModel : public glass::DIOsModel {
public:
DIOsSimModel()
: m_dioModels(HAL_GetNumDigitalChannels()),
m_dpwmModels(HAL_GetNumDigitalPWMOutputs()),
m_dutyCycleModels(HAL_GetNumDutyCycles()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachDIO(
wpi::function_ref<void(glass::DIOModel& model, int index)> func) override;
private:
// indexed by channel
std::vector<std::unique_ptr<DIOSimModel>> m_dioModels;
// indexed by index
std::vector<std::unique_ptr<DPWMSimModel>> m_dpwmModels;
std::vector<std::unique_ptr<DutyCycleSimModel>> m_dutyCycleModels;
};
} // namespace
void DIOsSimModel::Update() {
const int32_t numDIO = m_dioModels.size();
for (int i = 0; i < numDIO; ++i) {
auto& model = m_dioModels[i];
if (HALSIM_GetDIOInitialized(i)) {
if (!model) {
model = std::make_unique<DIOSimModel>(i);
}
model->SetDPWM(nullptr);
model->SetDutyCycle(nullptr);
model->SetEncoder(nullptr);
} else {
model.reset();
}
}
const int32_t numPWM = m_dpwmModels.size();
for (int32_t i = 0; i < numPWM; ++i) {
auto& model = m_dpwmModels[i];
if (HALSIM_GetDigitalPWMInitialized(i)) {
if (!model) {
int channel = HALSIM_GetDigitalPWMPin(i);
if (channel >= 0 && channel < numDIO && m_dioModels[channel]) {
model = std::make_unique<DPWMSimModel>(i, channel);
m_dioModels[channel]->SetDPWM(model.get());
}
}
} else {
model.reset();
}
}
const int32_t numDutyCycle = m_dutyCycleModels.size();
for (int32_t i = 0; i < numDutyCycle; ++i) {
auto& model = m_dutyCycleModels[i];
if (HALSIM_GetDutyCycleInitialized(i)) {
if (!model) {
int channel = HALSIM_GetDutyCycleDigitalChannel(i);
if (channel >= 0 && channel < numDIO && m_dioModels[channel]) {
model = std::make_unique<DutyCycleSimModel>(i);
m_dioModels[channel]->SetDutyCycle(model.get());
}
}
} else {
model.reset();
}
}
EncoderSimGui::GetEncodersModel().ForEachEncoder([&](auto& encoder, int i) {
int channel = encoder.GetChannelA();
if (channel >= 0 && channel < numDIO && m_dioModels[channel])
m_dioModels[channel]->SetEncoder(&encoder);
channel = encoder.GetChannelB();
if (channel >= 0 && channel < numDIO && m_dioModels[channel])
m_dioModels[channel]->SetEncoder(&encoder);
});
}
void DIOsSimModel::ForEachDIO(
wpi::function_ref<void(glass::DIOModel& model, int index)> func) {
const int32_t numDIO = m_dioModels.size();
for (int32_t i = 0; i < numDIO; ++i) {
if (auto model = m_dioModels[i].get()) {
func(*model, i);
}
}
}
static bool DIOAnyInitialized() {
static const int32_t num = HAL_GetNumDigitalChannels();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetDIOInitialized(i)) return true;
}
return false;
}
void DIOSimGui::Initialize() {
HALSimGui::halProvider.Register(
"DIO", DIOAnyInitialized, [] { return std::make_unique<DIOsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(470, 20);
return glass::MakeFunctionView([=] {
glass::DisplayDIOs(static_cast<DIOsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
});
});
}

View File

@@ -9,7 +9,7 @@
namespace halsimgui {
class EncoderGui {
class DIOSimGui {
public:
static void Initialize();
};

View File

@@ -7,6 +7,10 @@
#include "DriverStationGui.h"
#include <glass/other/FMS.h>
#include <glass/support/ExtraGuiWidgets.h>
#include <glass/support/IniSaverInfo.h>
#include <atomic>
#include <cstring>
#include <memory>
@@ -22,11 +26,10 @@
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
#include "ExtraGuiWidgets.h"
#include "GuiDataSource.h"
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
@@ -50,7 +53,7 @@ struct SystemJoystick {
};
struct RobotJoystick {
NameInfo name;
glass::NameInfo name;
std::string guid;
const SystemJoystick* sys = nullptr;
bool useGamepad = false;
@@ -67,24 +70,24 @@ struct RobotJoystick {
bool IsButtonPressed(int i) { return (buttons.buttons & (1u << i)) != 0; }
};
class JoystickSource {
class JoystickModel {
public:
explicit JoystickSource(int index);
~JoystickSource() {
explicit JoystickModel(int index);
~JoystickModel() {
HALSIM_CancelDriverStationNewDataCallback(m_callback);
for (int i = 0; i < buttonCount; ++i) delete buttons[i];
}
JoystickSource(const JoystickSource&) = delete;
JoystickSource& operator=(const JoystickSource&) = delete;
JoystickModel(const JoystickModel&) = delete;
JoystickModel& operator=(const JoystickModel&) = delete;
int axisCount;
int buttonCount;
int povCount;
std::unique_ptr<GuiDataSource> axes[HAL_kMaxJoystickAxes];
std::unique_ptr<glass::DataSource> axes[HAL_kMaxJoystickAxes];
// use pointer instead of unique_ptr to allow it to be passed directly
// to DrawLEDSources()
GuiDataSource* buttons[32];
std::unique_ptr<GuiDataSource> povs[HAL_kMaxJoystickPOVs];
glass::DataSource* buttons[32];
std::unique_ptr<glass::DataSource> povs[HAL_kMaxJoystickPOVs];
private:
static void CallbackFunc(const char*, void* param, const HAL_Value*);
@@ -92,6 +95,77 @@ class JoystickSource {
int m_index;
int32_t m_callback;
};
class FMSSimModel : public glass::FMSModel {
public:
FMSSimModel();
glass::DataSource* GetFmsAttachedData() override { return &m_fmsAttached; }
glass::DataSource* GetDsAttachedData() override { return &m_dsAttached; }
glass::DataSource* GetAllianceStationIdData() override {
return &m_allianceStationId;
}
glass::DataSource* GetMatchTimeData() override { return &m_matchTime; }
glass::DataSource* GetEStopData() override { return &m_estop; }
glass::DataSource* GetEnabledData() override { return &m_enabled; }
glass::DataSource* GetTestData() override { return &m_test; }
glass::DataSource* GetAutonomousData() override { return &m_autonomous; }
wpi::StringRef GetGameSpecificMessage(
wpi::SmallVectorImpl<char>& buf) override {
HAL_MatchInfo info;
HALSIM_GetMatchInfo(&info);
buf.clear();
buf.append(info.gameSpecificMessage,
info.gameSpecificMessage + info.gameSpecificMessageSize);
return wpi::StringRef(buf.begin(), buf.size());
}
void SetFmsAttached(bool val) override {
HALSIM_SetDriverStationFmsAttached(val);
}
void SetDsAttached(bool val) override {
HALSIM_SetDriverStationDsAttached(val);
}
void SetAllianceStationId(int val) override {
HALSIM_SetDriverStationAllianceStationId(
static_cast<HAL_AllianceStationID>(val));
}
void SetMatchTime(double val) override {
HALSIM_SetDriverStationMatchTime(val);
int32_t status = 0;
m_startMatchTime = HAL_GetFPGATime(&status) * 1.0e-6 - val;
}
void SetEStop(bool val) override { HALSIM_SetDriverStationEStop(val); }
void SetEnabled(bool val) override { HALSIM_SetDriverStationEnabled(val); }
void SetTest(bool val) override { HALSIM_SetDriverStationTest(val); }
void SetAutonomous(bool val) override {
HALSIM_SetDriverStationAutonomous(val);
}
void SetGameSpecificMessage(const char* val) override {
HALSIM_SetGameSpecificMessage(val);
}
void Update() override;
bool Exists() override { return true; }
bool IsReadOnly() override;
bool m_matchTimeEnabled = true;
private:
glass::DataSource m_fmsAttached{"FMS:FMSAttached"};
glass::DataSource m_dsAttached{"FMS:DSAttached"};
glass::DataSource m_allianceStationId{"FMS:AllianceStationID"};
glass::DataSource m_matchTime{"FMS:MatchTime"};
glass::DataSource m_estop{"FMS:EStop"};
glass::DataSource m_enabled{"FMS:RobotEnabled"};
glass::DataSource m_test{"FMS:TestMode"};
glass::DataSource m_autonomous{"FMS:AutonomousMode"};
double m_startMatchTime = 0.0;
double m_prevTime = 0.0;
};
} // namespace
// system joysticks
@@ -100,7 +174,13 @@ static int gNumSystemJoysticks = 0;
// robot joysticks
static RobotJoystick gRobotJoysticks[HAL_kMaxJoysticks];
static std::unique_ptr<JoystickSource> gJoystickSources[HAL_kMaxJoysticks];
static std::unique_ptr<JoystickModel> gJoystickSources[HAL_kMaxJoysticks];
// FMS
static std::unique_ptr<FMSSimModel> gFMSModel;
// Window management
DSManager DriverStationGui::dsManager{"DSManager"};
static bool gDisableDS = false;
static bool gZeroDisconnectedJoysticks = true;
@@ -110,14 +190,14 @@ static inline bool IsDSDisabled() {
return gDisableDS || (gDSSocketConnected && *gDSSocketConnected);
}
JoystickSource::JoystickSource(int index) : m_index{index} {
JoystickModel::JoystickModel(int index) : m_index{index} {
HAL_JoystickAxes halAxes;
HALSIM_GetJoystickAxes(index, &halAxes);
axisCount = halAxes.count;
for (int i = 0; i < axisCount; ++i) {
axes[i] = std::make_unique<GuiDataSource>("Joystick[" + wpi::Twine{index} +
"] Axis[" + wpi::Twine{i} +
wpi::Twine{']'});
axes[i] = std::make_unique<glass::DataSource>(
"Joystick[" + wpi::Twine{index} + "] Axis[" + wpi::Twine{i} +
wpi::Twine{']'});
}
HAL_JoystickButtons halButtons;
@@ -125,8 +205,8 @@ JoystickSource::JoystickSource(int index) : m_index{index} {
buttonCount = halButtons.count;
for (int i = 0; i < buttonCount; ++i) {
buttons[i] =
new GuiDataSource("Joystick[" + wpi::Twine{index} + "] Button[" +
wpi::Twine{i + 1} + wpi::Twine{']'});
new glass::DataSource("Joystick[" + wpi::Twine{index} + "] Button[" +
wpi::Twine{i + 1} + wpi::Twine{']'});
buttons[i]->SetDigital(true);
}
for (int i = buttonCount; i < 32; ++i) buttons[i] = nullptr;
@@ -135,17 +215,17 @@ JoystickSource::JoystickSource(int index) : m_index{index} {
HALSIM_GetJoystickPOVs(index, &halPOVs);
povCount = halPOVs.count;
for (int i = 0; i < povCount; ++i) {
povs[i] = std::make_unique<GuiDataSource>("Joystick[" + wpi::Twine{index} +
"] POV[" + wpi::Twine{i} +
wpi::Twine{']'});
povs[i] = std::make_unique<glass::DataSource>(
"Joystick[" + wpi::Twine{index} + "] POV[" + wpi::Twine{i} +
wpi::Twine{']'});
}
m_callback =
HALSIM_RegisterDriverStationNewDataCallback(CallbackFunc, this, true);
}
void JoystickSource::CallbackFunc(const char*, void* param, const HAL_Value*) {
auto self = static_cast<JoystickSource*>(param);
void JoystickModel::CallbackFunc(const char*, void* param, const HAL_Value*) {
auto self = static_cast<JoystickModel*>(param);
HAL_JoystickAxes halAxes;
HALSIM_GetJoystickAxes(self->m_index, &halAxes);
@@ -402,7 +482,7 @@ static void DriverStationExecute() {
if (!source || source->axisCount != axisCount ||
source->buttonCount != buttonCount || source->povCount != povCount) {
source.reset();
source = std::make_unique<JoystickSource>(i);
source = std::make_unique<JoystickModel>(i);
}
} else {
source.reset();
@@ -413,9 +493,13 @@ static void DriverStationExecute() {
bool disableDS = IsDSDisabled();
if (disableDS && !prevDisableDS) {
HALSimGui::SetWindowVisibility("System Joysticks", HALSimGui::kDisabled);
if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) {
win->SetVisibility(glass::Window::kDisabled);
}
} else if (!disableDS && prevDisableDS) {
HALSimGui::SetWindowVisibility("System Joysticks", HALSimGui::kShow);
if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) {
win->SetVisibility(glass::Window::kShow);
}
}
prevDisableDS = disableDS;
if (disableDS) return;
@@ -470,83 +554,48 @@ static void DriverStationExecute() {
}
}
static void DisplayFMS() {
bool fmsAttached = HALSIM_GetDriverStationFmsAttached();
bool dsAttached = HALSIM_GetDriverStationDsAttached();
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
"Blue 1", "Blue 2", "Blue 3"};
int allianceStationId = HALSIM_GetDriverStationAllianceStationId();
double matchTime = HALSIM_GetDriverStationMatchTime();
HAL_MatchInfo matchInfo;
HALSIM_GetMatchInfo(&matchInfo);
if (IsDSDisabled()) {
if (!HALSIM_GetDriverStationEnabled())
ImGui::Text("Robot State: Disabled");
else if (HALSIM_GetDriverStationTest())
ImGui::Text("Robot State: Test");
else if (HALSIM_GetDriverStationAutonomous())
ImGui::Text("Robot State: Autonomous");
else
ImGui::Text("Robot State: Teleoperated");
ImGui::Text("FMS Attached: %s", fmsAttached ? "Yes" : "No");
ImGui::Text("DS Attached: %s", dsAttached ? "Yes" : "No");
ImGui::Text("Alliance Station: %s", stations[allianceStationId]);
ImGui::Text("Match Time: %.1f", matchTime);
ImGui::Text("Game Specific: %s", matchInfo.gameSpecificMessage);
return;
}
double curTime = glfwGetTime();
// FMS Attached
if (ImGui::Checkbox("FMS Attached", &fmsAttached))
HALSIM_SetDriverStationFmsAttached(fmsAttached);
// DS Attached
if (ImGui::Checkbox("DS Attached", &dsAttached))
HALSIM_SetDriverStationDsAttached(dsAttached);
// Alliance Station
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::Combo("Alliance Station", &allianceStationId, stations, 6))
HALSIM_SetDriverStationAllianceStationId(
static_cast<HAL_AllianceStationID>(allianceStationId));
// Match Time
static bool matchTimeEnabled = true;
ImGui::Checkbox("Match Time Enabled", &matchTimeEnabled);
static double startMatchTime = 0.0;
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputDouble("Match Time", &matchTime, 0, 0, "%.1f",
ImGuiInputTextFlags_EnterReturnsTrue)) {
HALSIM_SetDriverStationMatchTime(matchTime);
startMatchTime = curTime - matchTime;
} else if (!HALSIM_GetDriverStationEnabled() || HALSIM_IsTimingPaused()) {
startMatchTime = curTime - matchTime;
} else if (matchTimeEnabled) {
HALSIM_SetDriverStationMatchTime(curTime - startMatchTime);
}
ImGui::SameLine();
if (ImGui::Button("Reset")) {
HALSIM_SetDriverStationMatchTime(0.0);
startMatchTime = curTime;
}
// Game Specific Message
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputText("Game Specific",
reinterpret_cast<char*>(matchInfo.gameSpecificMessage),
sizeof(matchInfo.gameSpecificMessage),
ImGuiInputTextFlags_EnterReturnsTrue)) {
matchInfo.gameSpecificMessageSize =
std::strlen(reinterpret_cast<char*>(matchInfo.gameSpecificMessage));
HALSIM_SetMatchInfo(&matchInfo);
}
FMSSimModel::FMSSimModel() {
m_fmsAttached.SetDigital(true);
m_dsAttached.SetDigital(true);
m_estop.SetDigital(true);
m_enabled.SetDigital(true);
m_test.SetDigital(true);
m_autonomous.SetDigital(true);
Update();
}
void FMSSimModel::Update() {
bool enabled = HALSIM_GetDriverStationEnabled();
m_fmsAttached.SetValue(HALSIM_GetDriverStationFmsAttached());
m_dsAttached.SetValue(HALSIM_GetDriverStationDsAttached());
m_allianceStationId.SetValue(HALSIM_GetDriverStationAllianceStationId());
m_estop.SetValue(HALSIM_GetDriverStationEStop());
m_enabled.SetValue(enabled);
m_test.SetValue(HALSIM_GetDriverStationTest());
m_autonomous.SetValue(HALSIM_GetDriverStationAutonomous());
double matchTime = HALSIM_GetDriverStationMatchTime();
if (m_matchTimeEnabled && !IsDSDisabled()) {
int32_t status = 0;
double curTime = HAL_GetFPGATime(&status) * 1.0e-6;
if (m_startMatchTime == 0.0) m_startMatchTime = curTime;
if (enabled) {
matchTime = curTime - m_startMatchTime;
HALSIM_SetDriverStationMatchTime(matchTime);
} else {
if (m_prevTime == 0.0) m_prevTime = curTime;
m_startMatchTime += (curTime - m_prevTime);
}
m_prevTime = curTime;
} else {
m_startMatchTime = 0.0;
m_prevTime = 0.0;
}
m_matchTime.SetValue(matchTime);
}
bool FMSSimModel::IsReadOnly() { return IsDSDisabled(); }
static void DisplaySystemJoysticks() {
ImGui::Text("(Drag and drop to Joysticks)");
int numShowJoysticks = gNumSystemJoysticks < 6 ? 6 : gNumSystemJoysticks;
@@ -678,7 +727,7 @@ static void DisplayJoysticks() {
ImGui::Columns(1);
}
static void DriverStationOptionMenu() {
void DSManager::DisplayMenu() {
if (gDSSocketConnected && *gDSSocketConnected) {
ImGui::MenuItem("Turn off DS (real DS connected)", nullptr, true, false);
} else {
@@ -686,9 +735,14 @@ static void DriverStationOptionMenu() {
ImGui::MenuItem("Zero disconnected joysticks", nullptr,
&gZeroDisconnectedJoysticks, true);
}
ImGui::Separator();
for (auto&& window : m_windows) {
window->DisplayMenuItem();
}
}
void DriverStationGui::Initialize() {
static void DriverStationInitialize() {
// hook ini handler to save joystick settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = "Joystick";
@@ -705,18 +759,35 @@ void DriverStationGui::Initialize() {
iniHandler.ReadLineFn = DriverStationReadLine;
iniHandler.WriteAllFn = DriverStationWriteAll;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
}
HALSimGui::AddExecute(DriverStationExecute);
HALSimGui::AddWindow("FMS", DisplayFMS, ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::AddWindow("System Joysticks", DisplaySystemJoysticks,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::AddWindow("Joysticks", DisplayJoysticks,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::AddOptionMenu(DriverStationOptionMenu);
void DriverStationGui::GlobalInit() {
dsManager.GlobalInit();
HALSimGui::SetDefaultWindowPos("FMS", 5, 540);
HALSimGui::SetDefaultWindowPos("System Joysticks", 5, 385);
HALSimGui::SetDefaultWindowPos("Joysticks", 250, 465);
wpi::gui::AddInit(DriverStationInitialize);
gFMSModel = std::make_unique<FMSSimModel>();
wpi::gui::AddEarlyExecute(DriverStationExecute);
wpi::gui::AddEarlyExecute([] { gFMSModel->Update(); });
if (auto win = dsManager.AddWindow("FMS", [] {
DisplayFMS(gFMSModel.get(), &gFMSModel->m_matchTimeEnabled);
})) {
win->DisableRenamePopup();
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(5, 540);
}
if (auto win =
dsManager.AddWindow("System Joysticks", DisplaySystemJoysticks)) {
win->DisableRenamePopup();
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(5, 385);
}
if (auto win = dsManager.AddWindow("Joysticks", DisplayJoysticks)) {
win->DisableRenamePopup();
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(250, 465);
}
}
void DriverStationGui::SetDSSocketExtension(void* data) {

View File

@@ -7,12 +7,23 @@
#pragma once
#include <glass/WindowManager.h>
namespace halsimgui {
class DSManager : public glass::WindowManager {
public:
explicit DSManager(const wpi::Twine& iniName) : WindowManager{iniName} {}
void DisplayMenu() override;
};
class DriverStationGui {
public:
static void Initialize();
static void GlobalInit();
static void SetDSSocketExtension(void* data);
static DSManager dsManager;
};
} // namespace halsimgui

View File

@@ -1,286 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "EncoderGui.h"
#include <limits>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/EncoderData.h>
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
struct EncoderInfo : public NameInfo, public OpenInfo {
bool ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
return false;
}
void WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
}
};
class EncoderSource {
public:
EncoderSource(const wpi::Twine& id, int32_t index, int channelA, int channelB)
: distancePerPulse(id + " Dist/Count"),
count(id + " Count"),
period(id + " Period"),
direction(id + " Direction"),
distance(id + " Distance"),
rate(id + " Rate"),
m_index{index},
m_channelA{channelA},
m_channelB{channelB},
m_distancePerPulseCallback{
HALSIM_RegisterEncoderDistancePerPulseCallback(
index, DistancePerPulseCallbackFunc, this, true)},
m_countCallback{HALSIM_RegisterEncoderCountCallback(
index, CountCallbackFunc, this, true)},
m_periodCallback{HALSIM_RegisterEncoderPeriodCallback(
index, PeriodCallbackFunc, this, true)},
m_directionCallback{HALSIM_RegisterEncoderDirectionCallback(
index, DirectionCallbackFunc, this, true)} {
direction.SetDigital(true);
}
EncoderSource(int32_t index, int channelA, int channelB)
: EncoderSource("Encoder[" + wpi::Twine(channelA) + wpi::Twine(',') +
wpi::Twine(channelB) + wpi::Twine(']'),
index, channelA, channelB) {}
explicit EncoderSource(int32_t index)
: EncoderSource(index, HALSIM_GetEncoderDigitalChannelA(index),
HALSIM_GetEncoderDigitalChannelB(index)) {}
~EncoderSource() {
if (m_distancePerPulseCallback != 0)
HALSIM_CancelEncoderDistancePerPulseCallback(m_index,
m_distancePerPulseCallback);
if (m_countCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_countCallback);
if (m_periodCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_periodCallback);
if (m_directionCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_directionCallback);
}
void SetName(const wpi::Twine& name) {
if (name.str().empty()) {
distancePerPulse.SetName("");
count.SetName("");
period.SetName("");
direction.SetName("");
distance.SetName("");
rate.SetName("");
} else {
distancePerPulse.SetName(name + " Distance/Count");
count.SetName(name + " Count");
period.SetName(name + " Period");
direction.SetName(name + " Direction");
distance.SetName(name + " Distance");
rate.SetName(name + " Rate");
}
}
int32_t GetIndex() const { return m_index; }
int GetChannelA() const { return m_channelA; }
int GetChannelB() const { return m_channelB; }
GuiDataSource distancePerPulse;
GuiDataSource count;
GuiDataSource period;
GuiDataSource direction;
GuiDataSource distance;
GuiDataSource rate;
private:
static void DistancePerPulseCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_DOUBLE) {
auto self = static_cast<EncoderSource*>(param);
double distPerPulse = value->data.v_double;
self->distancePerPulse.SetValue(distPerPulse);
self->distance.SetValue(self->count.GetValue() * distPerPulse);
double period = self->period.GetValue();
if (period == 0)
self->rate.SetValue(std::numeric_limits<double>::infinity());
else if (period == std::numeric_limits<double>::infinity())
self->rate.SetValue(0);
else
self->rate.SetValue(static_cast<float>(distPerPulse / period));
}
}
static void CountCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_INT) {
auto self = static_cast<EncoderSource*>(param);
double count = value->data.v_int;
self->count.SetValue(count);
self->distance.SetValue(count * self->distancePerPulse.GetValue());
}
}
static void PeriodCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_DOUBLE) {
auto self = static_cast<EncoderSource*>(param);
double period = value->data.v_double;
self->period.SetValue(period);
if (period == 0)
self->rate.SetValue(std::numeric_limits<double>::infinity());
else if (period == std::numeric_limits<double>::infinity())
self->rate.SetValue(0);
else
self->rate.SetValue(
static_cast<float>(self->distancePerPulse.GetValue() / period));
}
}
static void DirectionCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_BOOLEAN) {
static_cast<EncoderSource*>(param)->direction.SetValue(
value->data.v_boolean);
}
}
int32_t m_index;
int m_channelA;
int m_channelB;
int32_t m_distancePerPulseCallback;
int32_t m_countCallback;
int32_t m_periodCallback;
int32_t m_directionCallback;
};
} // namespace
static IniSaver<EncoderInfo> gEncoders{"Encoder"}; // indexed by channel A
static std::vector<std::unique_ptr<EncoderSource>> gEncoderSources;
static void UpdateEncoderSources() {
for (int i = 0, iend = gEncoderSources.size(); i < iend; ++i) {
auto& source = gEncoderSources[i];
if (HALSIM_GetEncoderInitialized(i)) {
if (!source) {
source = std::make_unique<EncoderSource>(i);
source->SetName(gEncoders[source->GetChannelA()].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayEncoders() {
bool hasAny = false;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
for (int i = 0, iend = gEncoderSources.size(); i < iend; ++i) {
if (auto source = gEncoderSources[i].get()) {
hasAny = true;
if (auto simDevice = HALSIM_GetEncoderSimDevice(i)) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::Text("%s", HALSIM_GetSimDeviceName(simDevice));
ImGui::PopStyleColor();
} else {
int chA = source->GetChannelA();
int chB = source->GetChannelB();
// build header name
auto& info = gEncoders[chA];
char name[128];
info.GetLabel(name, sizeof(name), "Encoder", chA, chB);
// header
bool open = ImGui::CollapsingHeader(
name, gEncoders[chA].IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0);
info.SetOpen(open);
// context menu to change name
if (info.PopupEditName(chA)) {
source->SetName(info.GetName());
}
if (open) {
ImGui::PushID(i);
// distance per pulse
double distancePerPulse = source->distancePerPulse.GetValue();
source->distancePerPulse.LabelText("Dist/Count", "%.6f",
distancePerPulse);
// count
int count = source->count.GetValue();
if (ImGui::InputInt("##input", &count))
HALSIM_SetEncoderCount(i, count);
ImGui::SameLine();
if (ImGui::Button("Reset")) HALSIM_SetEncoderCount(i, 0);
ImGui::SameLine();
ImGui::Selectable("Count");
source->count.EmitDrag();
// max period
double maxPeriod = HALSIM_GetEncoderMaxPeriod(i);
ImGui::LabelText("Max Period", "%.6f", maxPeriod);
// period
double period = source->period.GetValue();
if (source->period.InputDouble("Period", &period, 0, 0, "%.6g"))
HALSIM_SetEncoderPeriod(i, period);
// reverse direction
ImGui::LabelText(
"Reverse Direction", "%s",
HALSIM_GetEncoderReverseDirection(i) ? "true" : "false");
// direction
static const char* options[] = {"reverse", "forward"};
int direction = source->direction.GetValue() ? 1 : 0;
if (source->direction.Combo("Direction", &direction, options, 2))
HALSIM_SetEncoderDirection(i, direction);
// distance
double distance = source->distance.GetValue();
if (source->distance.InputDouble("Distance", &distance, 0, 0, "%.6g"))
HALSIM_SetEncoderDistance(i, distance);
// rate
double rate = source->rate.GetValue();
if (source->rate.InputDouble("Rate", &rate, 0, 0, "%.6g"))
HALSIM_SetEncoderRate(i, rate);
ImGui::PopID();
}
}
}
}
ImGui::PopItemWidth();
if (!hasAny) ImGui::Text("No encoders");
}
void EncoderGui::Initialize() {
gEncoders.Initialize();
gEncoderSources.resize(HAL_GetNumEncoders());
HALSimGui::AddExecute(UpdateEncoderSources);
HALSimGui::AddWindow("Encoders", DisplayEncoders,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Encoders", 5, 250);
}

View File

@@ -0,0 +1,257 @@
/*----------------------------------------------------------------------------*/
/* 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 "EncoderSimGui.h"
#include <glass/hardware/Encoder.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/EncoderData.h>
#include <hal/simulation/SimDeviceData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
class EncoderSimModel : public glass::EncoderModel {
public:
EncoderSimModel(const wpi::Twine& id, int32_t index, int channelA,
int channelB)
: m_distancePerPulse(id + " Dist/Count"),
m_count(id + " Count"),
m_period(id + " Period"),
m_direction(id + " Direction"),
m_distance(id + " Distance"),
m_rate(id + " Rate"),
m_index{index},
m_channelA{channelA},
m_channelB{channelB},
m_distancePerPulseCallback{
HALSIM_RegisterEncoderDistancePerPulseCallback(
index, DistancePerPulseCallbackFunc, this, true)},
m_countCallback{HALSIM_RegisterEncoderCountCallback(
index, CountCallbackFunc, this, true)},
m_periodCallback{HALSIM_RegisterEncoderPeriodCallback(
index, PeriodCallbackFunc, this, true)},
m_directionCallback{HALSIM_RegisterEncoderDirectionCallback(
index, DirectionCallbackFunc, this, true)} {
m_direction.SetDigital(true);
}
EncoderSimModel(int32_t index, int channelA, int channelB)
: EncoderSimModel("Encoder[" + wpi::Twine(channelA) + wpi::Twine(',') +
wpi::Twine(channelB) + wpi::Twine(']'),
index, channelA, channelB) {}
explicit EncoderSimModel(int32_t index)
: EncoderSimModel(index, HALSIM_GetEncoderDigitalChannelA(index),
HALSIM_GetEncoderDigitalChannelB(index)) {}
~EncoderSimModel() {
if (m_distancePerPulseCallback != 0)
HALSIM_CancelEncoderDistancePerPulseCallback(m_index,
m_distancePerPulseCallback);
if (m_countCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_countCallback);
if (m_periodCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_periodCallback);
if (m_directionCallback != 0)
HALSIM_CancelEncoderCountCallback(m_index, m_directionCallback);
}
void Update() override {}
bool Exists() override { return HALSIM_GetEncoderInitialized(m_index); }
int32_t GetIndex() const { return m_index; }
const char* GetSimDevice() const override {
if (auto simDevice = HALSIM_GetEncoderSimDevice(m_index))
return HALSIM_GetSimDeviceName(simDevice);
else
return nullptr;
}
int GetChannelA() const override { return m_channelA; }
int GetChannelB() const override { return m_channelB; }
glass::DataSource* GetDistancePerPulseData() override {
return &m_distancePerPulse;
}
glass::DataSource* GetCountData() override { return &m_count; }
glass::DataSource* GetPeriodData() override { return &m_period; }
glass::DataSource* GetDirectionData() override { return &m_direction; }
glass::DataSource* GetDistanceData() override { return &m_distance; }
glass::DataSource* GetRateData() override { return &m_rate; }
double GetMaxPeriod() override { return HALSIM_GetEncoderMaxPeriod(m_index); }
bool GetReverseDirection() override {
return HALSIM_GetEncoderReverseDirection(m_index);
}
void SetDistancePerPulse(double val) override {
HALSIM_SetEncoderDistancePerPulse(m_index, val);
}
void SetCount(int val) override { HALSIM_SetEncoderCount(m_index, val); }
void SetPeriod(double val) override { HALSIM_SetEncoderPeriod(m_index, val); }
void SetDirection(bool val) override {
HALSIM_SetEncoderDirection(m_index, val);
}
void SetDistance(double val) override {
HALSIM_SetEncoderDistance(m_index, val);
}
void SetRate(double val) override { HALSIM_SetEncoderRate(m_index, val); }
void SetMaxPeriod(double val) override {
HALSIM_SetEncoderMaxPeriod(m_index, val);
}
void SetReverseDirection(bool val) override {
HALSIM_SetEncoderReverseDirection(m_index, val);
}
private:
static void DistancePerPulseCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_DOUBLE) {
auto self = static_cast<EncoderSimModel*>(param);
double distPerPulse = value->data.v_double;
self->m_distancePerPulse.SetValue(distPerPulse);
self->m_distance.SetValue(self->m_count.GetValue() * distPerPulse);
double period = self->m_period.GetValue();
if (period == 0)
self->m_rate.SetValue(std::numeric_limits<double>::infinity());
else if (period == std::numeric_limits<double>::infinity())
self->m_rate.SetValue(0);
else
self->m_rate.SetValue(static_cast<float>(distPerPulse / period));
}
}
static void CountCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_INT) {
auto self = static_cast<EncoderSimModel*>(param);
double count = value->data.v_int;
self->m_count.SetValue(count);
self->m_distance.SetValue(count * self->m_distancePerPulse.GetValue());
}
}
static void PeriodCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_DOUBLE) {
auto self = static_cast<EncoderSimModel*>(param);
double period = value->data.v_double;
self->m_period.SetValue(period);
if (period == 0)
self->m_rate.SetValue(std::numeric_limits<double>::infinity());
else if (period == std::numeric_limits<double>::infinity())
self->m_rate.SetValue(0);
else
self->m_rate.SetValue(
static_cast<float>(self->m_distancePerPulse.GetValue() / period));
}
}
static void DirectionCallbackFunc(const char*, void* param,
const HAL_Value* value) {
if (value->type == HAL_BOOLEAN) {
static_cast<EncoderSimModel*>(param)->m_direction.SetValue(
value->data.v_boolean);
}
}
glass::DataSource m_distancePerPulse;
glass::DataSource m_count;
glass::DataSource m_period;
glass::DataSource m_direction;
glass::DataSource m_distance;
glass::DataSource m_rate;
int32_t m_index;
int m_channelA;
int m_channelB;
int32_t m_distancePerPulseCallback;
int32_t m_countCallback;
int32_t m_periodCallback;
int32_t m_directionCallback;
};
class EncodersSimModel : public glass::EncodersModel {
public:
EncodersSimModel() : m_models(HAL_GetNumEncoders()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachEncoder(
wpi::function_ref<void(glass::EncoderModel& model, int index)> func)
override;
private:
std::vector<std::unique_ptr<EncoderSimModel>> m_models;
};
} // namespace
void EncodersSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetEncoderInitialized(i)) {
if (!model) {
model = std::make_unique<EncoderSimModel>(i);
}
} else {
model.reset();
}
}
}
void EncodersSimModel::ForEachEncoder(
wpi::function_ref<void(glass::EncoderModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
static bool EncodersAnyInitialized() {
static const int32_t num = HAL_GetNumEncoders();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetEncoderInitialized(i)) return true;
}
return false;
}
void EncoderSimGui::Initialize() {
HALSimGui::halProvider.Register(
"Encoders", EncodersAnyInitialized,
[] { return std::make_unique<EncodersSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(5, 250);
return glass::MakeFunctionView(
[=] { DisplayEncoders(static_cast<EncodersSimModel*>(model)); });
});
}
glass::EncodersModel& EncoderSimGui::GetEncodersModel() {
static auto model = HALSimGui::halProvider.GetModel("Encoders");
assert(model);
return *static_cast<glass::EncodersModel*>(model);
}

View File

@@ -0,0 +1,22 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace glass {
class EncodersModel;
} // namespace glass
namespace halsimgui {
class EncoderSimGui {
public:
static void Initialize();
static glass::EncodersModel& GetEncodersModel();
};
} // namespace halsimgui

View File

@@ -1,109 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-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 "ExtraGuiWidgets.h"
#include "GuiDataSource.h"
namespace halsimgui {
void DrawLEDSources(const int* values, GuiDataSource** sources, int numValues,
int cols, const ImU32* colors, float size, float spacing,
const LEDConfig& config) {
if (numValues == 0 || cols < 1) return;
if (size == 0) size = ImGui::GetFontSize() / 2.0;
if (spacing == 0) spacing = ImGui::GetFontSize() / 3.0;
int rows = (numValues + cols - 1) / cols;
float inc = size + spacing;
ImDrawList* drawList = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
float sized2 = size / 2;
float ystart, yinc;
if (config.start & 1) {
// lower
ystart = p.y + sized2 + inc * (rows - 1);
yinc = -inc;
} else {
// upper
ystart = p.y + sized2;
yinc = inc;
}
float xstart, xinc;
if (config.start & 2) {
// right
xstart = p.x + sized2 + inc * (cols - 1);
xinc = -inc;
} else {
// left
xstart = p.x + sized2;
xinc = inc;
}
float x = xstart, y = ystart;
int rowcol = 1; // row for row-major, column for column-major
for (int i = 0; i < numValues; ++i) {
if (config.order == LEDConfig::RowMajor) {
if (i >= (rowcol * cols)) {
++rowcol;
if (config.serpentine) {
x -= xinc;
xinc = -xinc;
} else {
x = xstart;
}
y += yinc;
}
} else {
if (i >= (rowcol * rows)) {
++rowcol;
if (config.serpentine) {
y -= yinc;
yinc = -yinc;
} else {
y = ystart;
}
x += xinc;
}
}
if (values[i] > 0)
drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size),
colors[values[i] - 1]);
else if (values[i] < 0)
drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size),
colors[-values[i] - 1], 0.0f, 0, 1.0);
if (sources) {
ImGui::SetCursorScreenPos(ImVec2(x - sized2, y - sized2));
if (sources[i]) {
ImGui::PushID(i);
ImGui::Selectable("", false, 0, ImVec2(inc, inc));
sources[i]->EmitDrag();
ImGui::PopID();
} else {
ImGui::Dummy(ImVec2(inc, inc));
}
}
if (config.order == LEDConfig::RowMajor) {
x += xinc;
} else {
y += yinc;
}
}
if (!sources) ImGui::Dummy(ImVec2(inc * cols, inc * rows));
}
void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
float size, float spacing, const LEDConfig& config) {
DrawLEDSources(values, nullptr, numValues, cols, colors, size, spacing,
config);
}
} // namespace halsimgui

View File

@@ -1,652 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "Field2D.h"
#include <cmath>
#include <hal/SimDevice.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <units/angle.h>
#include <units/length.h>
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
#include "HALSimGui.h"
#include "SimDeviceGui.h"
#include "portable-file-dialogs.h"
using namespace halsimgui;
namespace gui = wpi::gui;
namespace {
// Per-frame field data (not persistent)
struct FieldFrameData {
// in window coordinates
ImVec2 imageMin;
ImVec2 imageMax;
ImVec2 min;
ImVec2 max;
float scale; // scaling from field units to screen units
};
class FieldInfo {
public:
static constexpr float kDefaultWidth = 15.98f;
static constexpr float kDefaultHeight = 8.21f;
std::unique_ptr<pfd::open_file> m_fileOpener;
float m_width = kDefaultWidth;
float m_height = kDefaultHeight;
void Reset();
void LoadImage();
void LoadJson(const wpi::Twine& jsonfile);
FieldFrameData GetFrameData() const;
void Draw(ImDrawList* drawList, const ImVec2& windowPos,
const FieldFrameData& frameData) const;
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out) const;
private:
bool LoadImageImpl(const char* fn);
std::string m_filename;
gui::Texture m_texture;
int m_imageWidth = 0;
int m_imageHeight = 0;
int m_top = 0;
int m_left = 0;
int m_bottom = -1;
int m_right = -1;
};
// Per-frame robot data (not persistent)
struct RobotFrameData {
// in window coordinates
ImVec2 center;
ImVec2 corners[4];
ImVec2 arrow[3];
// scaled width/2 and length/2, in screen units
float width2;
float length2;
};
class RobotInfo {
public:
static constexpr float kDefaultWidth = 0.6858f;
static constexpr float kDefaultLength = 0.8204f;
std::unique_ptr<pfd::open_file> m_fileOpener;
float m_width = kDefaultWidth;
float m_length = kDefaultLength;
void Reset();
void LoadImage();
void UpdateFromSimDevice();
void SetPosition(double x, double y);
// set and get rotation in radians
void SetRotation(double rot);
double GetRotation() const {
return units::convert<units::degrees, units::radians>(m_rot);
}
RobotFrameData GetFrameData(const FieldFrameData& ffd) const;
void Draw(ImDrawList* drawList, const ImVec2& windowPos,
const RobotFrameData& frameData, int hit, float hitRadius) const;
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out) const;
private:
bool LoadImageImpl(const char* fn);
std::string m_filename;
gui::Texture m_texture;
HAL_SimDeviceHandle m_devHandle = 0;
hal::SimDouble m_xHandle;
hal::SimDouble m_yHandle;
hal::SimDouble m_rotHandle;
double m_x = 0;
double m_y = 0;
double m_rot = 0;
};
} // namespace
static FieldInfo gField;
static RobotInfo gRobot;
static int gDragRobot = 0;
static ImVec2 gDragInitialOffset;
static double gDragInitialAngle;
// read/write settings to ini file
static void* Field2DReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name) {
if (name == wpi::StringRef{"Field"}) return &gField;
if (name == wpi::StringRef{"Robot"}) return &gRobot;
return nullptr;
}
static void Field2DReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr) {
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
if (entry == &gField)
gField.ReadIni(name, value);
else if (entry == &gRobot)
gRobot.ReadIni(name, value);
}
static void Field2DWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
gField.WriteIni(out_buf);
gRobot.WriteIni(out_buf);
}
void FieldInfo::Reset() {
m_texture = gui::Texture{};
m_filename.clear();
m_imageWidth = 0;
m_imageHeight = 0;
m_top = 0;
m_left = 0;
m_bottom = -1;
m_right = -1;
}
void FieldInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) {
if (wpi::StringRef(result[0]).endswith(".json")) {
LoadJson(result[0]);
} else {
LoadImageImpl(result[0].c_str());
m_top = 0;
m_left = 0;
m_bottom = -1;
m_right = -1;
}
}
m_fileOpener.reset();
}
if (!m_texture && !m_filename.empty()) {
if (!LoadImageImpl(m_filename.c_str())) m_filename.clear();
}
}
void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
std::error_code ec;
wpi::raw_fd_istream f(jsonfile, ec);
if (ec) {
wpi::errs() << "GUI: could not open field JSON file\n";
return;
}
// parse file
wpi::json j;
try {
j = wpi::json::parse(f);
} catch (const wpi::json::parse_error& e) {
wpi::errs() << "GUI: JSON: could not parse: " << e.what() << '\n';
}
// top level must be an object
if (!j.is_object()) {
wpi::errs() << "GUI: JSON: does not contain a top object\n";
return;
}
// image filename
std::string image;
try {
image = j.at("field-image").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-image: " << e.what()
<< '\n';
return;
}
// corners
int top, left, bottom, right;
try {
top = j.at("field-corners").at("top-left").at(1).get<int>();
left = j.at("field-corners").at("top-left").at(0).get<int>();
bottom = j.at("field-corners").at("bottom-right").at(1).get<int>();
right = j.at("field-corners").at("bottom-right").at(0).get<int>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-corners: " << e.what()
<< '\n';
return;
}
// size
float width;
float height;
try {
width = j.at("field-size").at(0).get<float>();
height = j.at("field-size").at(1).get<float>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-size: " << e.what() << '\n';
return;
}
// units for size
std::string unit;
try {
unit = j.at("field-unit").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-unit: " << e.what() << '\n';
return;
}
// convert size units to meters
if (unit == "foot" || unit == "feet") {
width = units::convert<units::feet, units::meters>(width);
height = units::convert<units::feet, units::meters>(height);
}
// the image filename is relative to the json file
wpi::SmallString<128> pathname;
jsonfile.toVector(pathname);
wpi::sys::path::remove_filename(pathname);
wpi::sys::path::append(pathname, image);
// load field image
if (!LoadImageImpl(pathname.c_str())) return;
// save to field info
m_filename = pathname.str();
m_top = top;
m_left = left;
m_bottom = bottom;
m_right = right;
m_width = width;
m_height = height;
}
bool FieldInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading field image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read field image\n";
return false;
}
m_texture = std::move(texture);
m_imageWidth = m_texture.GetWidth();
m_imageHeight = m_texture.GetHeight();
m_filename = fn;
return true;
}
FieldFrameData FieldInfo::GetFrameData() const {
FieldFrameData ffd;
// get window content region
ffd.imageMin = ImGui::GetWindowContentRegionMin();
ffd.imageMax = ImGui::GetWindowContentRegionMax();
// fit the image into the window
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0)
gui::MaxFit(&ffd.imageMin, &ffd.imageMax, m_imageWidth, m_imageHeight);
ImVec2 min = ffd.imageMin;
ImVec2 max = ffd.imageMax;
// size down the box by the image corners (if any)
if (m_bottom > 0 && m_right > 0) {
min.x += m_left * (max.x - min.x) / m_imageWidth;
min.y += m_top * (max.y - min.y) / m_imageHeight;
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
}
// draw the field "active area" as a yellow boundary box
gui::MaxFit(&min, &max, m_width, m_height);
ffd.min = min;
ffd.max = max;
ffd.scale = (max.x - min.x) / m_width;
return ffd;
}
void FieldInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos,
const FieldFrameData& ffd) const {
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
drawList->AddImage(m_texture, windowPos + ffd.imageMin,
windowPos + ffd.imageMax);
}
// draw the field "active area" as a yellow boundary box
drawList->AddRect(windowPos + ffd.min, windowPos + ffd.max,
IM_COL32(255, 255, 0, 255));
}
bool FieldInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "image") {
m_filename = value;
} else if (name == "top") {
int num;
if (value.getAsInteger(10, num)) return true;
m_top = num;
} else if (name == "left") {
int num;
if (value.getAsInteger(10, num)) return true;
m_left = num;
} else if (name == "bottom") {
int num;
if (value.getAsInteger(10, num)) return true;
m_bottom = num;
} else if (name == "right") {
int num;
if (value.getAsInteger(10, num)) return true;
m_right = num;
} else if (name == "width") {
std::sscanf(value.data(), "%f", &m_width);
} else if (name == "height") {
std::sscanf(value.data(), "%f", &m_height);
} else {
return false;
}
return true;
}
void FieldInfo::WriteIni(ImGuiTextBuffer* out) const {
out->appendf(
"[Field2D][Field]\nimage=%s\ntop=%d\nleft=%d\nbottom=%d\nright=%d\nwidth="
"%f\nheight=%f\n\n",
m_filename.c_str(), m_top, m_left, m_bottom, m_right, m_width, m_height);
}
void RobotInfo::Reset() {
m_texture = gui::Texture{};
m_filename.clear();
}
void RobotInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) LoadImageImpl(result[0].c_str());
m_fileOpener.reset();
}
if (!m_texture && !m_filename.empty()) {
if (!LoadImageImpl(m_filename.c_str())) m_filename.clear();
}
}
bool RobotInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading robot image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read robot image\n";
return false;
}
m_texture = std::move(texture);
m_filename = fn;
return true;
}
void RobotInfo::UpdateFromSimDevice() {
if (m_devHandle == 0) m_devHandle = HALSIM_GetSimDeviceHandle("Field2D");
if (m_devHandle == 0) return;
if (!m_xHandle) m_xHandle = HALSIM_GetSimValueHandle(m_devHandle, "x");
if (m_xHandle) m_x = m_xHandle.Get();
if (!m_yHandle) m_yHandle = HALSIM_GetSimValueHandle(m_devHandle, "y");
if (m_yHandle) m_y = m_yHandle.Get();
if (!m_rotHandle) m_rotHandle = HALSIM_GetSimValueHandle(m_devHandle, "rot");
if (m_rotHandle) m_rot = m_rotHandle.Get();
}
void RobotInfo::SetPosition(double x, double y) {
m_x = x;
m_y = y;
if (m_xHandle) m_xHandle.Set(x);
if (m_yHandle) m_yHandle.Set(y);
}
void RobotInfo::SetRotation(double rot) {
double rotDegrees = units::convert<units::radians, units::degrees>(rot);
// force to -180 to +180 range
rotDegrees = rotDegrees + std::ceil((-rotDegrees - 180) / 360) * 360;
m_rot = rotDegrees;
if (m_rotHandle) m_rotHandle.Set(rotDegrees);
}
RobotFrameData RobotInfo::GetFrameData(const FieldFrameData& ffd) const {
RobotFrameData rfd;
float width2 = ffd.scale * m_width / 2;
float length2 = ffd.scale * m_length / 2;
// (0,0) origin is bottom left
ImVec2 center(ffd.min.x + ffd.scale * m_x, ffd.max.y - ffd.scale * m_y);
// build rotated points around center
double rot = GetRotation();
float cos_a = std::cos(-rot);
float sin_a = std::sin(-rot);
rfd.corners[0] = center + ImRotate(ImVec2(-length2, -width2), cos_a, sin_a);
rfd.corners[1] = center + ImRotate(ImVec2(length2, -width2), cos_a, sin_a);
rfd.corners[2] = center + ImRotate(ImVec2(length2, width2), cos_a, sin_a);
rfd.corners[3] = center + ImRotate(ImVec2(-length2, width2), cos_a, sin_a);
rfd.arrow[0] =
center + ImRotate(ImVec2(-length2 / 2, -width2 / 2), cos_a, sin_a);
rfd.arrow[1] = center + ImRotate(ImVec2(length2 / 2, 0), cos_a, sin_a);
rfd.arrow[2] =
center + ImRotate(ImVec2(-length2 / 2, width2 / 2), cos_a, sin_a);
rfd.center = center;
rfd.width2 = width2;
rfd.length2 = length2;
return rfd;
}
void RobotInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos,
const RobotFrameData& rfd, int hit,
float hitRadius) const {
if (m_texture) {
drawList->AddImageQuad(
m_texture, windowPos + rfd.corners[0], windowPos + rfd.corners[1],
windowPos + rfd.corners[2], windowPos + rfd.corners[3]);
} else {
drawList->AddQuad(windowPos + rfd.corners[0], windowPos + rfd.corners[1],
windowPos + rfd.corners[2], windowPos + rfd.corners[3],
IM_COL32(255, 0, 0, 255), 4.0);
drawList->AddTriangle(windowPos + rfd.arrow[0], windowPos + rfd.arrow[1],
windowPos + rfd.arrow[2], IM_COL32(0, 255, 0, 255),
4.0);
}
if (hit > 0) {
if (hit == 1) {
drawList->AddCircle(windowPos + rfd.center, hitRadius,
IM_COL32(0, 255, 0, 255));
} else {
drawList->AddCircle(windowPos + rfd.corners[hit - 2], hitRadius,
IM_COL32(0, 255, 0, 255));
}
}
}
bool RobotInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "image") {
m_filename = value;
} else if (name == "width") {
std::sscanf(value.data(), "%f", &m_width);
} else if (name == "length") {
std::sscanf(value.data(), "%f", &m_length);
} else {
return false;
}
return true;
}
void RobotInfo::WriteIni(ImGuiTextBuffer* out) const {
out->appendf("[Field2D][Robot]\nimage=%s\nwidth=%f\nlength=%f\n\n",
m_filename.c_str(), m_width, m_length);
}
static void OptionMenuField2D() {
if (ImGui::BeginMenu("2D Field View")) {
if (ImGui::MenuItem("Choose field image...")) {
gField.m_fileOpener = std::make_unique<pfd::open_file>(
"Choose field image", "",
std::vector<std::string>{"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm",
"PathWeaver JSON File", "*.json"});
}
if (ImGui::MenuItem("Reset field image")) {
gField.Reset();
}
if (ImGui::MenuItem("Choose robot image...")) {
gRobot.m_fileOpener = std::make_unique<pfd::open_file>(
"Choose robot image", "",
std::vector<std::string>{"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm"});
}
if (ImGui::MenuItem("Reset robot image")) {
gRobot.Reset();
}
ImGui::EndMenu();
}
}
static void DisplayField2DSettings() {
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
ImGui::InputFloat("Field Width", &gField.m_width);
ImGui::InputFloat("Field Height", &gField.m_height);
// ImGui::InputInt("Field Top", &gField.m_top);
// ImGui::InputInt("Field Left", &gField.m_left);
// ImGui::InputInt("Field Right", &gField.m_right);
// ImGui::InputInt("Field Bottom", &gField.m_bottom);
ImGui::InputFloat("Robot Width", &gRobot.m_width);
ImGui::InputFloat("Robot Length", &gRobot.m_length);
ImGui::PopItemWidth();
}
static void DisplayField2D() {
// load images
gField.LoadImage();
gRobot.LoadImage();
// get robot coordinates from SimDevice
gRobot.UpdateFromSimDevice();
FieldFrameData ffd = gField.GetFrameData();
RobotFrameData rfd = gRobot.GetFrameData(ffd);
ImVec2 windowPos = ImGui::GetWindowPos();
// for dragging to work, there needs to be a button (otherwise the window is
// dragged)
ImVec2 contentSize =
ImGui::GetWindowContentRegionMax() - ImGui::GetWindowContentRegionMin();
if (contentSize.x <= 0 || contentSize.y <= 0) return;
ImGui::InvisibleButton("field", contentSize);
// allow dragging the robot around
ImVec2 cursor = ImGui::GetIO().MousePos - windowPos;
int hit = 0;
float hitRadius = (std::min)(rfd.width2, rfd.length2) / 2;
// only allow initiation of dragging when invisible button is hovered; this
// prevents the window resize handles from simultaneously activating the drag
// functionality
if (ImGui::IsItemHovered()) {
float hitRadiusSquared = hitRadius * hitRadius;
// it's within the hit radius of the center?
if (gui::GetDistSquared(cursor, rfd.center) < hitRadiusSquared)
hit = 1;
else if (gui::GetDistSquared(cursor, rfd.corners[0]) < hitRadiusSquared)
hit = 2;
else if (gui::GetDistSquared(cursor, rfd.corners[1]) < hitRadiusSquared)
hit = 3;
else if (gui::GetDistSquared(cursor, rfd.corners[2]) < hitRadiusSquared)
hit = 4;
else if (gui::GetDistSquared(cursor, rfd.corners[3]) < hitRadiusSquared)
hit = 5;
if (hit > 0 && ImGui::IsMouseClicked(0)) {
if (hit == 1) {
gDragRobot = hit;
gDragInitialOffset = cursor - rfd.center;
} else {
gDragRobot = hit;
ImVec2 off = cursor - rfd.center;
gDragInitialAngle = std::atan2(off.y, off.x) + gRobot.GetRotation();
}
}
}
if (gDragRobot > 0 && ImGui::IsMouseDown(0)) {
if (gDragRobot == 1) {
ImVec2 newPos = cursor - gDragInitialOffset;
gRobot.SetPosition(
(std::clamp(newPos.x, ffd.min.x, ffd.max.x) - ffd.min.x) / ffd.scale,
(ffd.max.y - std::clamp(newPos.y, ffd.min.y, ffd.max.y)) / ffd.scale);
rfd = gRobot.GetFrameData(ffd);
} else {
ImVec2 off = cursor - rfd.center;
gRobot.SetRotation(gDragInitialAngle - std::atan2(off.y, off.x));
}
hit = gDragRobot; // keep it highlighted
} else {
gDragRobot = 0;
}
// draw
auto drawList = ImGui::GetWindowDrawList();
gField.Draw(drawList, windowPos, ffd);
gRobot.Draw(drawList, windowPos, rfd, hit, hitRadius);
}
void Field2D::Initialize() {
// hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = "Field2D";
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = Field2DReadOpen;
iniHandler.ReadLineFn = Field2DReadLine;
iniHandler.WriteAllFn = Field2DWriteAll;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
HALSimGui::AddOptionMenu(OptionMenuField2D);
HALSimGui::AddWindow("2D Field Settings", DisplayField2DSettings,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetWindowVisibility("2D Field Settings", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("2D Field Settings", 200, 150);
HALSimGui::AddWindow("2D Field View", DisplayField2D);
HALSimGui::SetWindowVisibility("2D Field View", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("2D Field View", 200, 200);
HALSimGui::SetDefaultWindowSize("2D Field View", 400, 200);
HALSimGui::SetWindowPadding("2D Field View", 0, 0);
// SimDeviceGui::Hide("Field2D");
}

View File

@@ -1,116 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "GuiDataSource.h"
#include <wpi/StringMap.h>
using namespace halsimgui;
wpi::sig::Signal<const char*, GuiDataSource*> GuiDataSource::sourceCreated;
static wpi::StringMap<GuiDataSource*> gSources;
GuiDataSource::GuiDataSource(const wpi::Twine& id) : m_id{id.str()} {
gSources.try_emplace(m_id, this);
sourceCreated(m_id.c_str(), this);
}
GuiDataSource::GuiDataSource(const wpi::Twine& id, int index)
: GuiDataSource{id + wpi::Twine('[') + wpi::Twine(index) +
wpi::Twine(']')} {}
GuiDataSource::GuiDataSource(const wpi::Twine& id, int index, int index2)
: GuiDataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(',') +
wpi::Twine(index2) + wpi::Twine(']')} {}
GuiDataSource::~GuiDataSource() {
auto it = gSources.find(m_id);
if (it == gSources.end()) return;
if (it->getValue() == this) gSources.erase(it);
}
void GuiDataSource::LabelText(const char* label, const char* fmt, ...) const {
va_list args;
va_start(args, fmt);
LabelTextV(label, fmt, args);
va_end(args);
}
// Add a label+text combo aligned to other label+value widgets
void GuiDataSource::LabelTextV(const char* label, const char* fmt,
va_list args) const {
ImGui::PushID(label);
ImGui::LabelTextV("##input", fmt, args);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
ImGui::PopID();
EmitDrag();
}
bool GuiDataSource::Combo(const char* label, int* current_item,
const char* const items[], int items_count,
int popup_max_height_in_items) const {
ImGui::PushID(label);
bool rv = ImGui::Combo("##input", current_item, items, items_count,
popup_max_height_in_items);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool GuiDataSource::SliderFloat(const char* label, float* v, float v_min,
float v_max, const char* format,
float power) const {
ImGui::PushID(label);
bool rv = ImGui::SliderFloat("##input", v, v_min, v_max, format, power);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool GuiDataSource::InputDouble(const char* label, double* v, double step,
double step_fast, const char* format,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputDouble("##input", v, step, step_fast, format, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool GuiDataSource::InputInt(const char* label, int* v, int step, int step_fast,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputInt("##input", v, step, step_fast, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
void GuiDataSource::EmitDrag(ImGuiDragDropFlags flags) const {
if (ImGui::BeginDragDropSource(flags)) {
auto self = this;
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self));
ImGui::TextUnformatted(m_name.empty() ? m_id.c_str() : m_name.c_str());
ImGui::EndDragDropSource();
}
}
GuiDataSource* GuiDataSource::Find(wpi::StringRef id) {
auto it = gSources.find(id);
if (it == gSources.end()) return nullptr;
return it->getValue();
}

View File

@@ -0,0 +1,91 @@
/*----------------------------------------------------------------------------*/
/* 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 "HALProvider.h"
#include <glass/Model.h>
#include <algorithm>
#include <string>
#include <hal/simulation/DriverStationData.h>
using namespace halsimgui;
static bool gDisableOutputsOnDSDisable = true;
bool HALProvider::AreOutputsDisabled() {
return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
}
void HALProvider::DisplayMenu() {
ImGui::MenuItem("Disable outputs on DS disable", nullptr,
&gDisableOutputsOnDSDisable, true);
ImGui::Separator();
for (auto&& viewEntry : m_viewEntries) {
bool visible = viewEntry->window && viewEntry->window->IsVisible();
bool wasVisible = visible;
bool exists = viewEntry->modelEntry->exists();
ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible,
visible || exists);
if (!wasVisible && visible) {
Show(viewEntry.get(), viewEntry->window);
} else if (wasVisible && !visible && viewEntry->window) {
viewEntry->window->SetVisible(false);
}
}
}
void HALProvider::Update() {
Provider::Update();
// check for visible windows that need displays (typically this is due to
// file loading)
for (auto&& window : m_windows) {
if (!window->IsVisible() || window->HasView()) continue;
auto id = window->GetId();
auto it = FindViewEntry(id);
if (it == m_viewEntries.end() || (*it)->name != id) continue;
Show(it->get(), window.get());
}
}
glass::Model* HALProvider::GetModel(wpi::StringRef name) {
auto it = FindModelEntry(name);
if (it == m_modelEntries.end() || (*it)->name != name) return nullptr;
auto entry = it->get();
// get or create model
if (!entry->model) entry->model = entry->createModel();
return entry->model.get();
}
void HALProvider::Show(ViewEntry* entry, glass::Window* window) {
// if there's already a window, just show it
if (entry->window) {
entry->window->SetVisible(true);
return;
}
// get or create model
if (!entry->modelEntry->model)
entry->modelEntry->model = entry->modelEntry->createModel();
if (!entry->modelEntry->model) return;
// the window might exist and we're just not associated to it yet
if (!window) window = GetOrAddWindow(entry->name, true);
if (!window) return;
entry->window = window;
// create view
auto view = entry->createView(window, entry->modelEntry->model.get());
if (!view) return;
window->SetView(std::move(view));
entry->window->SetVisible(true);
}

View File

@@ -7,337 +7,22 @@
#include "HALSimGui.h"
#include <algorithm>
#include <hal/simulation/DriverStationData.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/StringMap.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
using namespace halsimgui;
namespace gui = wpi::gui;
glass::MainMenuBar HALSimGui::mainMenu;
glass::WindowManager HALSimGui::manager{"SimWindow"};
HALProvider HALSimGui::halProvider{"HALProvider"};
glass::NetworkTablesProvider HALSimGui::ntProvider{"NTProvider"};
namespace {
struct WindowInfo {
WindowInfo() = default;
explicit WindowInfo(const char* name_) : name{name_} {}
WindowInfo(const char* name_, std::function<void()> display_,
ImGuiWindowFlags flags_)
: name{name_}, display{std::move(display_)}, flags{flags_} {}
void HALSimGui::GlobalInit() {
manager.GlobalInit();
halProvider.GlobalInit();
ntProvider.GlobalInit();
std::string name;
std::function<void()> display;
ImGuiWindowFlags flags = 0;
bool visible = true;
bool enabled = true;
ImGuiCond posCond = 0;
ImGuiCond sizeCond = 0;
ImVec2 pos;
ImVec2 size;
bool setPadding = false;
ImVec2 padding;
};
} // namespace
wpi::gui::AddLateExecute([] { mainMenu.Display(); });
static std::vector<WindowInfo> gWindows;
static wpi::StringMap<int> gWindowMap; // index into gWindows
static std::vector<int> gSortedWindows; // index into gWindows
static std::vector<std::function<void()>> gOptionMenus;
static std::vector<std::function<void()>> gMenus;
static bool gDisableOutputsOnDSDisable = true;
// read/write open state to ini file
static void* SimWindowsReadOpen(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
const char* name) {
if (wpi::StringRef{name} == "GLOBAL") return &gDisableOutputsOnDSDisable;
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
if (index == static_cast<int>(gWindows.size())) {
gSortedWindows.push_back(index);
gWindows.emplace_back(name);
std::sort(gSortedWindows.begin(), gSortedWindows.end(),
[](int a, int b) { return gWindows[a].name < gWindows[b].name; });
}
return &gWindows[index];
glass::AddStandardNetworkTablesViews(ntProvider);
}
static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr) {
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
if (entry == &gDisableOutputsOnDSDisable) {
int num;
if (value.getAsInteger(10, num)) return;
if (name == "disableOutputsOnDS") {
gDisableOutputsOnDSDisable = num;
}
return;
}
auto element = static_cast<WindowInfo*>(entry);
if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return;
element->visible = num;
} else if (name == "enabled") {
int num;
if (value.getAsInteger(10, num)) return;
element->enabled = num;
}
}
static void SimWindowsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
out_buf->appendf("[SimWindow][GLOBAL]\ndisableOutputsOnDS=%d\n\n",
gDisableOutputsOnDSDisable ? 1 : 0);
for (auto&& window : gWindows)
out_buf->appendf("[SimWindow][%s]\nvisible=%d\nenabled=%d\n\n",
window.name.c_str(), window.visible ? 1 : 0,
window.enabled ? 1 : 0);
}
void HALSimGui::Add(std::function<void()> initialize) {
gui::AddInit(std::move(initialize));
}
void HALSimGui::AddExecute(std::function<void()> execute) {
gui::AddEarlyExecute(std::move(execute));
}
void HALSimGui::AddWindow(const char* name, std::function<void()> display,
int flags) {
if (display) {
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
if (index < static_cast<int>(gWindows.size())) {
if (gWindows[index].display) {
wpi::errs() << "halsim_gui: ignoring duplicate window '" << name
<< "'\n";
} else {
gWindows[index].display = display;
gWindows[index].flags = flags;
}
return;
}
gSortedWindows.push_back(index);
gWindows.emplace_back(name, std::move(display),
static_cast<ImGuiWindowFlags>(flags));
std::sort(gSortedWindows.begin(), gSortedWindows.end(),
[](int a, int b) { return gWindows[a].name < gWindows[b].name; });
}
}
void HALSimGui::AddMainMenu(std::function<void()> menu) {
if (menu) gMenus.emplace_back(std::move(menu));
}
void HALSimGui::AddOptionMenu(std::function<void()> menu) {
if (menu) gOptionMenus.emplace_back(std::move(menu));
}
void HALSimGui::SetWindowVisibility(const char* name,
WindowVisibility visibility) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
switch (visibility) {
case kHide:
window.visible = false;
window.enabled = true;
break;
case kShow:
window.visible = true;
window.enabled = true;
break;
case kDisabled:
window.enabled = false;
break;
}
}
void HALSimGui::SetDefaultWindowPos(const char* name, float x, float y) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.posCond = ImGuiCond_FirstUseEver;
window.pos = ImVec2{x, y};
}
void HALSimGui::SetDefaultWindowSize(const char* name, float width,
float height) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.sizeCond = ImGuiCond_FirstUseEver;
window.size = ImVec2{width, height};
}
void HALSimGui::SetWindowPadding(const char* name, float x, float y) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.setPadding = true;
window.padding = ImVec2{x, y};
}
bool HALSimGui::AreOutputsDisabled() {
return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
}
void HALSimGui::GlobalInit() { gui::CreateContext(); }
bool HALSimGui::Initialize() {
gui::AddInit([] {
// Hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = "SimWindow";
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = SimWindowsReadOpen;
iniHandler.ReadLineFn = SimWindowsReadLine;
iniHandler.WriteAllFn = SimWindowsWriteAll;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
});
gui::AddWindowScaler([](float windowScale) {
// scale default window positions
for (auto&& window : gWindows) {
if ((window.posCond & ImGuiCond_FirstUseEver) != 0) {
window.pos.x *= windowScale;
window.pos.y *= windowScale;
window.size.x *= windowScale;
window.size.y *= windowScale;
}
}
});
gui::AddLateExecute([] {
{
ImGui::BeginMainMenuBar();
if (ImGui::BeginMenu("Options")) {
ImGui::MenuItem("Disable outputs on DS disable", nullptr,
&gDisableOutputsOnDSDisable, true);
for (auto&& menu : gOptionMenus) {
if (menu) menu();
}
ImGui::EndMenu();
}
gui::EmitViewMenu();
if (ImGui::BeginMenu("Window")) {
for (auto&& windowIndex : gSortedWindows) {
auto& window = gWindows[windowIndex];
ImGui::MenuItem(window.name.c_str(), nullptr, &window.visible,
window.enabled);
}
ImGui::EndMenu();
}
for (auto&& menu : gMenus) {
if (menu) menu();
}
#if 0
char str[64];
std::snprintf(str, sizeof(str), "%.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize(str).x -
10);
ImGui::Text("%s", str);
#endif
ImGui::EndMainMenuBar();
}
for (auto&& window : gWindows) {
if (window.display && window.visible && window.enabled) {
if (window.posCond != 0)
ImGui::SetNextWindowPos(window.pos, window.posCond);
if (window.sizeCond != 0)
ImGui::SetNextWindowSize(window.size, window.sizeCond);
if (window.setPadding)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, window.padding);
if (ImGui::Begin(window.name.c_str(), &window.visible, window.flags))
window.display();
ImGui::End();
if (window.setPadding) ImGui::PopStyleVar();
}
}
});
if (!gui::Initialize("Simulation GUI", 1280, 720)) return false;
return true;
}
void HALSimGui::Main(void*) {
gui::Main();
gui::DestroyContext();
}
void HALSimGui::Exit(void*) { gui::Exit(); }
extern "C" {
void HALSIMGUI_Add(void* param, void (*initialize)(void*)) {
if (initialize) {
HALSimGui::Add([=] { initialize(param); });
}
}
void HALSIMGUI_AddExecute(void* param, void (*execute)(void*)) {
if (execute) {
HALSimGui::AddExecute([=] { execute(param); });
}
}
void HALSIMGUI_AddWindow(const char* name, void* param, void (*display)(void*),
int32_t flags) {
if (display) {
HALSimGui::AddWindow(
name, [=] { display(param); }, flags);
}
}
void HALSIMGUI_AddMainMenu(void* param, void (*menu)(void*)) {
if (menu) {
HALSimGui::AddMainMenu([=] { menu(param); });
}
}
void HALSIMGUI_AddOptionMenu(void* param, void (*menu)(void*)) {
if (menu) {
HALSimGui::AddOptionMenu([=] { menu(param); });
}
}
void HALSIMGUI_SetWindowVisibility(const char* name, int32_t visibility) {
HALSimGui::SetWindowVisibility(
name, static_cast<HALSimGui::WindowVisibility>(visibility));
}
void HALSIMGUI_SetDefaultWindowPos(const char* name, float x, float y) {
HALSimGui::SetDefaultWindowPos(name, x, y);
}
void HALSIMGUI_SetDefaultWindowSize(const char* name, float width,
float height) {
HALSimGui::SetDefaultWindowSize(name, width, height);
}
void HALSIMGUI_SetWindowPadding(const char* name, float x, float y) {
HALSimGui::SetDefaultWindowSize(name, x, y);
}
int HALSIMGUI_AreOutputsDisabled(void) {
return HALSimGui::AreOutputsDisabled();
}
} // extern "C"

View File

@@ -1,141 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "IniSaverInfo.h"
#include <cstdio>
#include <cstring>
#include <imgui_internal.h>
using namespace halsimgui;
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s", m_name);
} else {
std::snprintf(buf, size, "%s", defaultName);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]", m_name, index);
} else {
std::snprintf(buf, size, "%s[%d]", defaultName, index);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]", m_name, index, index2);
} else {
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s###Name%s", m_name, defaultName);
} else {
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]###Name%d", m_name, index, index);
} else {
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name, index, index2,
index);
} else {
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
index);
}
}
bool NameInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name != "name") return false;
size_t len = (std::min)(value.size(), sizeof(m_name) - 1);
std::memcpy(m_name, value.data(), len);
m_name[len] = '\0';
return true;
}
void NameInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("name=%s\n", m_name);
}
void NameInfo::PushEditNameId(int index) {
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
ImGui::PushID(id);
}
void NameInfo::PushEditNameId(const char* name) {
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
ImGui::PushID(id);
}
bool NameInfo::PopupEditName(int index) {
bool rv = false;
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit", ImGuiInputTextFlags_EnterReturnsTrue)) {
ImGui::CloseCurrentPopup();
rv = true;
}
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::PopupEditName(const char* name) {
bool rv = false;
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit", ImGuiInputTextFlags_EnterReturnsTrue)) {
ImGui::CloseCurrentPopup();
rv = true;
}
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::InputTextName(const char* label_id, ImGuiInputTextFlags flags) {
return ImGui::InputText(label_id, m_name, sizeof(m_name), flags);
}
bool OpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name != "open") return false;
int num;
if (value.getAsInteger(10, num)) return true;
m_open = num;
return true;
}
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("open=%d\n", m_open ? 1 : 0);
}

View File

@@ -16,13 +16,13 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
#include <portable-file-dialogs.h>
#include <wpi/json.h>
#include <wpi/math>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include "HALSimGui.h"
#include "portable-file-dialogs.h"
using namespace halsimgui;
@@ -276,17 +276,15 @@ static void readJson(std::string jFile) {
}
}
static void OptionMenuLocateJson() {
if (ImGui::BeginMenu("Mechanism2D")) {
static void DisplayAssembly2D() {
if (ImGui::BeginPopupContextItem()) {
if (ImGui::MenuItem("Load Json")) {
m_fileOpener = std::make_unique<pfd::open_file>(
"Choose Mechanism2D json", "", std::vector<std::string>{"*.json"});
}
ImGui::EndMenu();
ImGui::EndPopup();
}
}
static void DisplayAssembly2D() {
GetJsonFileLocation();
if (!mechanism2DInfo.jsonLocation.empty()) {
// Only read the json file if it changed
@@ -313,10 +311,11 @@ void Mechanism2D::Initialize() {
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
buildColorTable();
HALSimGui::AddWindow("Mechanism 2D", DisplayAssembly2D);
HALSimGui::SetWindowVisibility("Mechanism 2D", HALSimGui::kHide);
HALSimGui::AddOptionMenu(OptionMenuLocateJson);
HALSimGui::SetDefaultWindowPos("Mechanism 2D", 200, 200);
HALSimGui::SetDefaultWindowSize("Mechanism 2D", 600, 600);
HALSimGui::SetWindowPadding("Mechanism 2D", 0, 0);
if (auto win =
HALSimGui::manager.AddWindow("Mechanism 2D", DisplayAssembly2D)) {
win->SetVisibility(glass::Window::kHide);
win->SetDefaultPos(200, 200);
win->SetDefaultSize(600, 600);
win->SetPadding(0, 0);
}
}

View File

@@ -1,409 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 <ntcore_cpp.h>
#include <wpi/DenseMap.h>
#include <wpi/Format.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
static NT_EntryListenerPoller gNetworkTablesPoller;
static wpi::DenseMap<NT_Entry, std::unique_ptr<GuiDataSource>>
gNetworkTableSources;
static void UpdateNetworkTableSources() {
bool timedOut = false;
for (auto&& event :
nt::PollEntryListener(gNetworkTablesPoller, 0, &timedOut)) {
if (!event.value->IsBoolean() && !event.value->IsDouble()) continue;
if (event.flags & NT_NOTIFY_NEW) {
auto& source = gNetworkTableSources[event.entry];
if (!source)
source =
std::make_unique<GuiDataSource>(wpi::Twine{"NT:"} + event.name);
}
if (event.flags & NT_NOTIFY_DELETE) {
if (auto& source = gNetworkTableSources[event.entry]) source.reset();
}
if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
if (auto& source = gNetworkTableSources[event.entry]) {
if (event.value->IsBoolean()) {
source->SetValue(event.value->GetBoolean() ? 1 : 0);
source->SetDigital(true);
} else if (event.value->IsDouble()) {
source->SetValue(event.value->GetDouble());
source->SetDigital(false);
}
}
}
}
}
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) {
if (auto source = gNetworkTableSources[i.entry].get()) {
ImGui::Selectable(i.name.c_str());
source->EmitDrag();
} else {
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() {
gNetworkTablesPoller =
nt::CreateEntryListenerPoller(nt::GetDefaultInstance());
nt::AddPolledEntryListener(gNetworkTablesPoller, "",
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
NT_NOTIFY_IMMEDIATE);
HALSimGui::AddExecute(UpdateNetworkTableSources);
HALSimGui::AddWindow("NetworkTables", DisplayNetworkTables);
HALSimGui::SetDefaultWindowPos("NetworkTables", 250, 277);
HALSimGui::SetDefaultWindowSize("NetworkTables", 750, 185);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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

@@ -0,0 +1,40 @@
/*----------------------------------------------------------------------------*/
/* 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 "NetworkTablesSimGui.h"
#include <glass/networktables/NetworkTables.h>
#include <wpigui.h>
#include "HALSimGui.h"
using namespace halsimgui;
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<glass::NetworkTablesView> gNetworkTablesView;
static glass::Window* gNetworkTablesWindow;
void NetworkTablesSimGui::Initialize() {
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
gNetworkTablesView =
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get());
wpi::gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
gNetworkTablesWindow = HALSimGui::ntProvider.AddWindow(
"NetworkTables", [] { gNetworkTablesView->Display(); });
if (gNetworkTablesWindow) {
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
}
}
void NetworkTablesSimGui::DisplayMenu() {
if (gNetworkTablesWindow) {
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
}
}

View File

@@ -9,9 +9,10 @@
namespace halsimgui {
class Field2D {
class NetworkTablesSimGui {
public:
static void Initialize();
static void DisplayMenu();
};
} // namespace halsimgui

View File

@@ -0,0 +1,234 @@
/*----------------------------------------------------------------------------*/
/* 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 "PCMSimGui.h"
#include <glass/hardware/PCM.h>
#include <glass/other/DeviceTree.h>
#include <cstdio>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/PCMData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMCompressorOn, "Compressor On");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMClosedLoopEnabled, "Closed Loop");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(PCMPressureSwitch, "Pressure Switch");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PCMCompressorCurrent, "Comp Current");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED2(PCMSolenoidOutput, "Solenoid");
class CompressorSimModel : public glass::CompressorModel {
public:
explicit CompressorSimModel(int32_t index)
: m_index{index},
m_running{index},
m_enabled{index},
m_pressureSwitch{index},
m_current{index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetPCMCompressorInitialized(m_index); }
glass::DataSource* GetRunningData() override { return &m_running; }
glass::DataSource* GetEnabledData() override { return &m_enabled; }
glass::DataSource* GetPressureSwitchData() override {
return &m_pressureSwitch;
}
glass::DataSource* GetCurrentData() override { return &m_current; }
void SetRunning(bool val) override {
HALSIM_SetPCMCompressorOn(m_index, val);
}
void SetEnabled(bool val) override {
HALSIM_SetPCMClosedLoopEnabled(m_index, val);
}
void SetPressureSwitch(bool val) override {
HALSIM_SetPCMPressureSwitch(m_index, val);
}
void SetCurrent(double val) override {
HALSIM_SetPCMCompressorCurrent(m_index, val);
}
private:
int32_t m_index;
PCMCompressorOnSource m_running;
PCMClosedLoopEnabledSource m_enabled;
PCMPressureSwitchSource m_pressureSwitch;
PCMCompressorCurrentSource m_current;
};
class SolenoidSimModel : public glass::SolenoidModel {
public:
SolenoidSimModel(int32_t index, int32_t channel)
: m_index{index}, m_channel{channel}, m_output{index, channel} {}
void Update() override {}
bool Exists() override {
return HALSIM_GetPCMSolenoidInitialized(m_index, m_channel);
}
glass::DataSource* GetOutputData() override { return &m_output; }
void SetOutput(bool val) override {
HALSIM_SetPCMSolenoidOutput(m_index, m_channel, val);
}
private:
int32_t m_index;
int32_t m_channel;
PCMSolenoidOutputSource m_output;
};
class PCMSimModel : public glass::PCMModel {
public:
explicit PCMSimModel(int32_t index)
: m_index{index},
m_compressor{index},
m_solenoids(HAL_GetNumSolenoidChannels()) {}
void Update() override;
bool Exists() override { return true; }
CompressorSimModel* GetCompressor() override { return &m_compressor; }
void ForEachSolenoid(
wpi::function_ref<void(glass::SolenoidModel& model, int index)> func)
override;
int GetNumSolenoids() const { return m_solenoidInitCount; }
private:
int32_t m_index;
CompressorSimModel m_compressor;
std::vector<std::unique_ptr<SolenoidSimModel>> m_solenoids;
int m_solenoidInitCount = 0;
};
class PCMsSimModel : public glass::PCMsModel {
public:
PCMsSimModel() : m_models(HAL_GetNumPCMModules()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachPCM(
wpi::function_ref<void(glass::PCMModel& model, int index)> func) override;
private:
std::vector<std::unique_ptr<PCMSimModel>> m_models;
};
} // namespace
void PCMSimModel::Update() {
int32_t numChannels = m_solenoids.size();
m_solenoidInitCount = 0;
for (int32_t i = 0; i < numChannels; ++i) {
auto& model = m_solenoids[i];
if (HALSIM_GetPCMSolenoidInitialized(m_index, i)) {
if (!model) {
model = std::make_unique<SolenoidSimModel>(m_index, i);
}
++m_solenoidInitCount;
} else {
model.reset();
}
}
}
void PCMSimModel::ForEachSolenoid(
wpi::function_ref<void(glass::SolenoidModel& model, int index)> func) {
if (m_solenoidInitCount == 0) return;
int32_t numSolenoids = m_solenoids.size();
for (int32_t i = 0; i < numSolenoids; ++i) {
if (auto model = m_solenoids[i].get()) {
func(*model, i);
}
}
}
void PCMsSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetPCMCompressorInitialized(i) ||
HALSIM_GetPCMAnySolenoidInitialized(i)) {
if (!model) {
model = std::make_unique<PCMSimModel>(i);
}
model->Update();
} else {
model.reset();
}
}
}
void PCMsSimModel::ForEachPCM(
wpi::function_ref<void(glass::PCMModel& model, int index)> func) {
int32_t numPCMs = m_models.size();
for (int32_t i = 0; i < numPCMs; ++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
static bool PCMsAnyInitialized() {
static const int32_t num = HAL_GetNumPCMModules();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetPCMCompressorInitialized(i) ||
HALSIM_GetPCMAnySolenoidInitialized(i))
return true;
}
return false;
}
void PCMSimGui::Initialize() {
HALSimGui::halProvider.RegisterModel("PCMs", PCMsAnyInitialized, [] {
return std::make_unique<PCMsSimModel>();
});
HALSimGui::halProvider.RegisterView(
"Solenoids", "PCMs",
[](glass::Model* model) {
bool any = false;
static_cast<PCMsSimModel*>(model)->ForEachPCM(
[&](glass::PCMModel& pcm, int) {
if (static_cast<PCMSimModel*>(&pcm)->GetNumSolenoids() > 0)
any = true;
});
return any;
},
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(290, 20);
return glass::MakeFunctionView([=] {
glass::DisplayPCMsSolenoids(
static_cast<PCMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
});
});
SimDeviceGui::GetDeviceTree().Add(
HALSimGui::halProvider.GetModel("PCMs"), [](glass::Model* model) {
glass::DisplayCompressorsDevice(
static_cast<PCMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class DIOGui {
class PCMSimGui {
public:
static void Initialize();
};

View File

@@ -1,141 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "PDPGui.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/PDPData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PDPTemperature, "PDP Temp");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PDPVoltage, "PDP Voltage");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED2(PDPCurrent, "PDP Current");
struct PDPSource {
explicit PDPSource(int32_t index) : temp{index}, voltage{index} {
const int numChannels = HAL_GetNumPDPChannels();
currents.reserve(numChannels);
for (int i = 0; i < numChannels; ++i)
currents.emplace_back(std::make_unique<PDPCurrentSource>(index, i));
}
PDPTemperatureSource temp;
PDPVoltageSource voltage;
std::vector<std::unique_ptr<PDPCurrentSource>> currents;
};
} // namespace
static IniSaver<NameInfo> gChannels{"PDP"};
static std::vector<std::unique_ptr<PDPSource>> gPDPSources;
static void UpdatePDPSources() {
for (int i = 0, iend = gPDPSources.size(); i < iend; ++i) {
auto& source = gPDPSources[i];
if (HALSIM_GetPDPInitialized(i)) {
if (!source) {
source = std::make_unique<PDPSource>(i);
}
} else {
source.reset();
}
}
}
static void DisplayPDP() {
bool hasAny = false;
for (int i = 0, iend = gPDPSources.size(); i < iend; ++i) {
if (auto source = gPDPSources[i].get()) {
hasAny = true;
char name[128];
std::snprintf(name, sizeof(name), "PDP[%d]", i);
if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::PushID(i);
// temperature
double temp = source->temp.GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (source->temp.InputDouble("Temp", &temp, 0, 0, "%.3f"))
HALSIM_SetPDPTemperature(i, temp);
// voltage
double volts = source->voltage.GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (source->voltage.InputDouble("Voltage", &volts, 0, 0, "%.3f"))
HALSIM_SetPDPVoltage(i, volts);
// channel currents; show as two columns laid out like PDP
const int numChannels = source->currents.size();
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) {
double val;
ImGui::PushID(left);
auto& leftInfo = gChannels[i * numChannels + left];
leftInfo.GetLabel(name, sizeof(name), "", left);
val = source->currents[left]->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (source->currents[left]->InputDouble(name, &val, 0, 0, "%.3f"))
HALSIM_SetPDPCurrent(i, left, val);
float leftWidth = ImGui::GetItemRectSize().x;
if (leftInfo.PopupEditName(left)) {
source->currents[left]->SetName(leftInfo.GetName());
}
ImGui::PopID();
ImGui::NextColumn();
ImGui::PushID(right);
auto& rightInfo = gChannels[i * numChannels + right];
rightInfo.GetLabel(name, sizeof(name), "", right);
val = source->currents[right]->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (source->currents[right]->InputDouble(name, &val, 0, 0, "%.3f"))
HALSIM_SetPDPCurrent(i, right, val);
float rightWidth = ImGui::GetItemRectSize().x;
if (rightInfo.PopupEditName(right)) {
source->currents[right]->SetName(rightInfo.GetName());
}
ImGui::PopID();
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));
ImGui::PopID();
}
}
}
if (!hasAny) ImGui::Text("No PDPs");
}
void PDPGui::Initialize() {
gChannels.Initialize();
gPDPSources.resize(HAL_GetNumPDPModules());
HALSimGui::AddExecute(UpdatePDPSources);
HALSimGui::AddWindow("PDP", DisplayPDP);
// hide it by default
HALSimGui::SetWindowVisibility("PDP", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("PDP", 245, 155);
}

View File

@@ -0,0 +1,124 @@
/*----------------------------------------------------------------------------*/
/* 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 "PDPSimGui.h"
#include <glass/hardware/PDP.h>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/PDPData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PDPTemperature, "PDP Temp");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PDPVoltage, "PDP Voltage");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED2(PDPCurrent, "PDP Current");
class PDPSimModel : public glass::PDPModel {
public:
explicit PDPSimModel(int32_t index)
: m_index{index}, m_temp{index}, m_voltage{index} {
const int numChannels = HAL_GetNumPDPChannels();
m_currents.reserve(numChannels);
for (int i = 0; i < numChannels; ++i)
m_currents.emplace_back(std::make_unique<PDPCurrentSource>(index, i));
}
void Update() override {}
bool Exists() override { return HALSIM_GetPDPInitialized(m_index); }
int GetNumChannels() const override { return m_currents.size(); }
glass::DataSource* GetTemperatureData() override { return &m_temp; }
glass::DataSource* GetVoltageData() override { return &m_voltage; }
glass::DataSource* GetCurrentData(int channel) override {
return m_currents[channel].get();
}
void SetTemperature(double val) override {
HALSIM_SetPDPTemperature(m_index, val);
}
void SetVoltage(double val) override { HALSIM_SetPDPVoltage(m_index, val); }
void SetCurrent(int channel, double val) override {
HALSIM_SetPDPCurrent(m_index, channel, val);
}
private:
int32_t m_index;
PDPTemperatureSource m_temp;
PDPVoltageSource m_voltage;
std::vector<std::unique_ptr<PDPCurrentSource>> m_currents;
};
class PDPsSimModel : public glass::PDPsModel {
public:
PDPsSimModel() : m_models(HAL_GetNumPDPModules()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachPDP(
wpi::function_ref<void(glass::PDPModel& model, int index)> func) override;
private:
std::vector<std::unique_ptr<PDPSimModel>> m_models;
};
} // namespace
void PDPsSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetPDPInitialized(i)) {
if (!model) {
model = std::make_unique<PDPSimModel>(i);
}
} else {
model.reset();
}
}
}
void PDPsSimModel::ForEachPDP(
wpi::function_ref<void(glass::PDPModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
static bool PDPsAnyInitialized() {
static const int32_t num = HAL_GetNumPDPModules();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetPDPInitialized(i)) return true;
}
return false;
}
void PDPSimGui::Initialize() {
HALSimGui::halProvider.Register(
"PDPs", PDPsAnyInitialized,
[] { return std::make_unique<PDPsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetDefaultPos(245, 155);
return glass::MakeFunctionView(
[=] { DisplayPDPs(static_cast<PDPsSimModel*>(model)); });
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class PDPGui {
class PDPSimGui {
public:
static void Initialize();
};

View File

@@ -1,104 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "PWMGui.h"
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AddressableLEDData.h>
#include <hal/simulation/PWMData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PWMSpeed, "PWM");
} // namespace
static IniSaver<NameInfo> gPWM{"PWM"};
static std::vector<std::unique_ptr<PWMSpeedSource>> gPWMSources;
static void UpdatePWMSources() {
static const int numPWM = HAL_GetNumPWMChannels();
if (static_cast<size_t>(numPWM) != gPWMSources.size())
gPWMSources.resize(numPWM);
for (int i = 0; i < numPWM; ++i) {
auto& source = gPWMSources[i];
if (HALSIM_GetPWMInitialized(i)) {
if (!source) {
source = std::make_unique<PWMSpeedSource>(i);
source->SetName(gPWM[i].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayPWMs() {
bool hasOutputs = false;
static const int numPWM = HAL_GetNumPWMChannels();
static const int numLED = HAL_GetNumAddressableLEDs();
static auto ledMap = std::make_unique<int[]>(numPWM);
std::memset(ledMap.get(), 0, numPWM * sizeof(ledMap[0]));
for (int i = 0; i < numLED; ++i) {
if (HALSIM_GetAddressableLEDInitialized(i)) {
int channel = HALSIM_GetAddressableLEDOutputPort(i);
if (channel >= 0 && channel < numPWM) ledMap[channel] = i + 1;
}
}
bool first = true;
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
for (int i = 0; i < numPWM; ++i) {
if (auto source = gPWMSources[i].get()) {
ImGui::PushID(i);
hasOutputs = true;
if (!first)
ImGui::Separator();
else
first = false;
auto& info = gPWM[i];
char label[128];
info.GetLabel(label, sizeof(label), "PWM", i);
if (ledMap[i] > 0) {
ImGui::LabelText(label, "LED[%d]", ledMap[i] - 1);
} else {
float val = HALSimGui::AreOutputsDisabled() ? 0 : HALSIM_GetPWMSpeed(i);
source->LabelText(label, "%0.3f", val);
}
if (info.PopupEditName(i)) {
source->SetName(info.GetName());
}
ImGui::PopID();
}
}
ImGui::PopItemWidth();
if (!hasOutputs) ImGui::Text("No PWM outputs");
}
void PWMGui::Initialize() {
gPWM.Initialize();
HALSimGui::AddExecute(UpdatePWMSources);
HALSimGui::AddWindow("PWM Outputs", DisplayPWMs,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("PWM Outputs", 910, 20);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 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 PWMGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -0,0 +1,119 @@
/*----------------------------------------------------------------------------*/
/* 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 "PWMSimGui.h"
#include <glass/hardware/PWM.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AddressableLEDData.h>
#include <hal/simulation/PWMData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(PWMSpeed, "PWM");
class PWMSimModel : public glass::PWMModel {
public:
explicit PWMSimModel(int32_t index) : m_index{index}, m_speed{m_index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetPWMInitialized(m_index); }
void SetAddressableLED(int led) { m_led = led; }
int GetAddressableLED() const override { return m_led; }
glass::DataSource* GetSpeedData() override { return &m_speed; }
void SetSpeed(double val) override { HALSIM_SetPWMSpeed(m_index, val); }
private:
int32_t m_index;
int m_led = -1;
PWMSpeedSource m_speed;
};
class PWMsSimModel : public glass::PWMsModel {
public:
PWMsSimModel() : m_sources(HAL_GetNumPWMChannels()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachPWM(
wpi::function_ref<void(glass::PWMModel& model, int index)> func) override;
private:
// indexed by channel
std::vector<std::unique_ptr<PWMSimModel>> m_sources;
};
} // namespace
void PWMsSimModel::Update() {
const int32_t numPWM = m_sources.size();
for (int32_t i = 0; i < numPWM; ++i) {
auto& model = m_sources[i];
if (HALSIM_GetPWMInitialized(i)) {
if (!model) {
model = std::make_unique<PWMSimModel>(i);
}
model->SetAddressableLED(-1);
} else {
model.reset();
}
}
static const int32_t numLED = HAL_GetNumAddressableLEDs();
for (int32_t i = 0; i < numLED; ++i) {
if (HALSIM_GetAddressableLEDInitialized(i)) {
int32_t channel = HALSIM_GetAddressableLEDOutputPort(i);
if (channel >= 0 && channel < numPWM && m_sources[channel])
m_sources[channel]->SetAddressableLED(i);
}
}
}
void PWMsSimModel::ForEachPWM(
wpi::function_ref<void(glass::PWMModel& model, int index)> func) {
const int32_t numPWM = m_sources.size();
for (int32_t i = 0; i < numPWM; ++i) {
if (auto model = m_sources[i].get()) {
func(*model, i);
}
}
}
static bool PWMsAnyInitialized() {
static const int32_t num = HAL_GetNumPWMChannels();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetPWMInitialized(i)) return true;
}
return false;
}
void PWMSimGui::Initialize() {
HALSimGui::halProvider.Register(
"PWM Outputs", PWMsAnyInitialized,
[] { return std::make_unique<PWMsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(910, 20);
return glass::MakeFunctionView([=] {
glass::DisplayPWMs(static_cast<PWMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
});
});
}

View File

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

View File

@@ -1,894 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "PlotGui.h"
#include <algorithm>
#include <atomic>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <hal/simulation/MockHooks.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <implot.h>
#include <wpi/Signal.h>
#include <wpi/SmallVector.h>
#include <wpi/timestamp.h>
#include <wpi/raw_ostream.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaverInfo.h"
#include "IniSaverVector.h"
using namespace halsimgui;
namespace {
struct PlotSeriesRef {
size_t plotIndex;
size_t seriesIndex;
};
class PlotSeries : public NameInfo, public OpenInfo {
public:
explicit PlotSeries(wpi::StringRef id);
explicit PlotSeries(GuiDataSource* source, int yAxis = 0);
const std::string& GetId() const { return m_id; }
void CheckSource();
void SetSource(GuiDataSource* source);
GuiDataSource* GetSource() const { return m_source; }
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
bool EmitPlot(double now, size_t i, size_t plotIndex);
bool EmitSettings(size_t i, size_t plotIndex);
bool EmitSettingsDetail(size_t i);
void EmitDragDropPayload(size_t i, size_t plotIndex);
void GetLabel(char* buf, size_t size) const;
int GetYAxis() const { return m_yAxis; }
void SetYAxis(int yAxis) { m_yAxis = yAxis; }
private:
bool IsDigital() const {
return m_digital == kDigital ||
(m_digital == kAuto && m_source && m_source->IsDigital());
}
void AppendValue(double value);
// source linkage
GuiDataSource* m_source = nullptr;
wpi::sig::ScopedConnection m_sourceCreatedConn;
wpi::sig::ScopedConnection m_newValueConn;
std::string m_id;
// user settings
int m_yAxis = 0;
ImVec4 m_color = IMPLOT_AUTO_COL;
int m_marker = 0;
enum Digital { kAuto, kDigital, kAnalog };
int m_digital = 0;
int m_digitalBitHeight = 8;
int m_digitalBitGap = 4;
// value storage
static constexpr int kMaxSize = 2000;
static constexpr double kTimeGap = 0.05;
std::atomic<int> m_size = 0;
std::atomic<int> m_offset = 0;
ImPlotPoint m_data[kMaxSize];
};
class Plot : public NameInfo, public OpenInfo {
public:
Plot();
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
void GetLabel(char* buf, size_t size, int index) const;
void GetName(char* buf, size_t size, int index) const;
void DragDropTarget(size_t i, bool inPlot);
void EmitPlot(double now, size_t i);
void EmitSettings(size_t i);
std::vector<std::unique_ptr<PlotSeries>> m_series;
private:
void EmitSettingsLimits(int axis);
bool m_visible = true;
unsigned int m_plotFlags = ImPlotFlags_Default;
bool m_lockPrevX = false;
bool m_paused = false;
float m_viewTime = 10;
int m_height = 300;
struct PlotRange {
double min = 0;
double max = 1;
bool lockMin = false;
bool lockMax = false;
bool apply = false;
};
PlotRange m_axisRange[3];
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
};
} // namespace
static IniSaverVector<Plot> gPlots{"Plot"};
PlotSeries::PlotSeries(wpi::StringRef id) : m_id(id) {
if (GuiDataSource* source = GuiDataSource::Find(id)) {
SetSource(source);
return;
}
CheckSource();
}
PlotSeries::PlotSeries(GuiDataSource* source, int yAxis) : m_yAxis(yAxis) {
SetSource(source);
}
void PlotSeries::CheckSource() {
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
m_source = nullptr;
m_sourceCreatedConn = GuiDataSource::sourceCreated.connect_connection(
[this](const char* id, GuiDataSource* source) {
if (m_id == id) {
SetSource(source);
m_sourceCreatedConn.disconnect();
}
});
}
}
void PlotSeries::SetSource(GuiDataSource* source) {
m_source = source;
m_id = source->GetId();
// add initial value
m_data[m_size++] = ImPlotPoint{wpi::Now() * 1.0e-6, source->GetValue()};
m_newValueConn = source->valueChanged.connect_connection(
[this](double value) { AppendValue(value); });
}
void PlotSeries::AppendValue(double value) {
double time = wpi::Now() * 1.0e-6;
if (IsDigital()) {
if (m_size < kMaxSize) {
m_data[m_size] = ImPlotPoint{time, value};
++m_size;
} else {
m_data[m_offset] = ImPlotPoint{time, value};
m_offset = (m_offset + 1) % kMaxSize;
}
} else {
// as an analog graph draws linear lines in between each value,
// insert duplicate value if "long" time between updates so it
// looks appropriately flat
if (m_size < kMaxSize) {
if (m_size > 0) {
if ((time - m_data[m_size - 1].x) > kTimeGap) {
m_data[m_size] = ImPlotPoint{time, m_data[m_size - 1].y};
++m_size;
}
}
m_data[m_size] = ImPlotPoint{time, value};
++m_size;
} else {
if (m_offset == 0) {
if ((time - m_data[kMaxSize - 1].x) > kTimeGap) {
m_data[m_offset] = ImPlotPoint{time, m_data[kMaxSize - 1].y};
++m_offset;
}
} else {
if ((time - m_data[m_offset - 1].x) > kTimeGap) {
m_data[m_offset] = ImPlotPoint{time, m_data[m_offset - 1].y};
m_offset = (m_offset + 1) % kMaxSize;
}
}
m_data[m_offset] = ImPlotPoint{time, value};
m_offset = (m_offset + 1) % kMaxSize;
}
}
}
bool PlotSeries::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
if (name == "yAxis") {
int num;
if (value.getAsInteger(10, num)) return true;
m_yAxis = num;
return true;
} else if (name == "color") {
unsigned int num;
if (value.getAsInteger(10, num)) return true;
m_color = ImColor(num);
return true;
} else if (name == "marker") {
int num;
if (value.getAsInteger(10, num)) return true;
m_marker = num;
return true;
} else if (name == "digital") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digital = num;
return true;
} else if (name == "digitalBitHeight") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digitalBitHeight = num;
return true;
} else if (name == "digitalBitGap") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digitalBitGap = num;
return true;
}
return false;
}
void PlotSeries::WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
out->appendf(
"yAxis=%d\ncolor=%u\nmarker=%d\ndigital=%d\n"
"digitalBitHeight=%d\ndigitalBitGap=%d\n",
m_yAxis, static_cast<ImU32>(ImColor(m_color)), m_marker, m_digital,
m_digitalBitHeight, m_digitalBitGap);
}
void PlotSeries::GetLabel(char* buf, size_t size) const {
const char* name = GetName();
if (name[0] == '\0' && m_newValueConn.connected()) name = m_source->GetName();
if (name[0] == '\0') name = m_id.c_str();
std::snprintf(buf, size, "%s###%s", name, m_id.c_str());
}
bool PlotSeries::EmitPlot(double now, size_t i, size_t plotIndex) {
CheckSource();
char label[128];
GetLabel(label, sizeof(label));
int size = m_size;
int offset = m_offset;
// need to have last value at current time, so need to create fake last value
// we handle the offset logic ourselves to avoid wrap issues with size + 1
struct GetterData {
double now;
ImPlotPoint* data;
int size;
int offset;
};
GetterData getterData = {now, m_data, size, offset};
auto getter = [](void* data, int idx) {
auto d = static_cast<GetterData*>(data);
if (idx == d->size)
return ImPlotPoint{
d->now, d->data[d->offset == 0 ? d->size - 1 : d->offset - 1].y};
if (d->offset + idx < d->size)
return d->data[d->offset + idx];
else
return d->data[d->offset + idx - d->size];
};
if (m_color.w == IMPLOT_AUTO_COL.w) m_color = ImPlot::GetColormapColor(i);
ImPlot::PushStyleColor(ImPlotCol_Line, m_color);
if (IsDigital()) {
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
ImPlot::PlotDigital(label, getter, &getterData, size + 1);
ImPlot::PopStyleVar();
ImPlot::PopStyleVar();
} else {
ImPlot::SetPlotYAxis(m_yAxis);
ImPlot::SetNextMarkerStyle(m_marker - 1);
ImPlot::PlotLine(label, getter, &getterData, size + 1);
}
ImPlot::PopStyleColor();
// DND source for PlotSeries
if (ImPlot::BeginLegendDragDropSource(label)) {
EmitDragDropPayload(i, plotIndex);
ImPlot::EndLegendDragDropSource();
}
// Plot-specific variant of IniSaverInfo::PopupEditName() that also
// allows editing of other settings
bool rv = false;
if (ImPlot::BeginLegendPopup(label)) {
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
ImGui::Text("Edit name:");
if (InputTextName("##edit", ImGuiInputTextFlags_EnterReturnsTrue)) {
ImGui::CloseCurrentPopup();
}
rv = EmitSettingsDetail(i);
ImPlot::EndLegendPopup();
}
return rv;
}
void PlotSeries::EmitDragDropPayload(size_t i, size_t plotIndex) {
PlotSeriesRef ref = {plotIndex, i};
ImGui::SetDragDropPayload("PlotSeries", &ref, sizeof(ref));
const char* name = GetName();
if (name[0] == '\0' && m_newValueConn.connected()) name = m_source->GetName();
if (name[0] == '\0') name = m_id.c_str();
ImGui::TextUnformatted(name);
}
static void MovePlotSeries(size_t fromPlotIndex, size_t fromSeriesIndex,
size_t toPlotIndex, size_t toSeriesIndex,
int yAxis = -1) {
if (fromPlotIndex == toPlotIndex) {
// need to handle this specially as the index of the old location changes
if (fromSeriesIndex != toSeriesIndex) {
auto& plotSeries = gPlots[fromPlotIndex].m_series;
auto val = std::move(plotSeries[fromSeriesIndex]);
// only set Y-axis if actually set
if (yAxis != -1) val->SetYAxis(yAxis);
plotSeries.insert(plotSeries.begin() + toSeriesIndex, std::move(val));
plotSeries.erase(plotSeries.begin() + fromSeriesIndex +
(fromSeriesIndex > toSeriesIndex ? 1 : 0));
}
} else {
auto& fromPlot = gPlots[fromPlotIndex];
auto& toPlot = gPlots[toPlotIndex];
// always set Y-axis if moving plots
fromPlot.m_series[fromSeriesIndex]->SetYAxis(yAxis == -1 ? 0 : yAxis);
toPlot.m_series.insert(toPlot.m_series.begin() + toSeriesIndex,
std::move(fromPlot.m_series[fromSeriesIndex]));
fromPlot.m_series.erase(fromPlot.m_series.begin() + fromSeriesIndex);
}
}
bool PlotSeries::EmitSettings(size_t i, size_t plotIndex) {
char label[128];
GetLabel(label, sizeof(label));
bool open = ImGui::CollapsingHeader(
label, IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0);
// DND source for PlotSeries
if (ImGui::BeginDragDropSource()) {
EmitDragDropPayload(i, plotIndex);
ImGui::EndDragDropSource();
}
// If another PlotSeries is dropped, move it before this series
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("PlotSeries")) {
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
MovePlotSeries(ref->plotIndex, ref->seriesIndex, plotIndex, i);
}
}
SetOpen(open);
PopupEditName(i);
if (!open) return false;
return EmitSettingsDetail(i);
}
bool PlotSeries::EmitSettingsDetail(size_t i) {
if (ImGui::Button("Delete")) {
return true;
}
// Line color
{
ImGui::ColorEdit3("Color", &m_color.x, ImGuiColorEditFlags_NoInputs);
ImGui::SameLine();
if (ImGui::Button("Default")) m_color = ImPlot::GetColormapColor(i);
}
// Digital
{
static const char* const options[] = {"Auto", "Digital", "Analog"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::Combo("Digital", &m_digital, options,
sizeof(options) / sizeof(options[0]));
}
if (IsDigital()) {
// Bit Height
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
ImGui::InputInt("Bit Height", &m_digitalBitHeight);
}
// Bit Gap
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
ImGui::InputInt("Bit Gap", &m_digitalBitGap);
}
} else {
// Y-axis
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
static const char* const options[] = {"1", "2", "3"};
ImGui::Combo("Y-Axis", &m_yAxis, options, 3);
}
// Marker
{
static const char* const options[] = {
"None", "Circle", "Square", "Diamond", "Up", "Down",
"Left", "Right", "Cross", "Plus", "Asterisk"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::Combo("Marker", &m_marker, options,
sizeof(options) / sizeof(options[0]));
}
}
return false;
}
Plot::Plot() {
for (int i = 0; i < 3; ++i) {
m_axisRange[i] = PlotRange{};
}
}
bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return true;
m_visible = num != 0;
return true;
} else if (name == "lockPrevX") {
int num;
if (value.getAsInteger(10, num)) return true;
m_lockPrevX = num != 0;
return true;
} else if (name == "legend") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_Legend;
else
m_plotFlags |= ImPlotFlags_Legend;
return true;
} else if (name == "yaxis2") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_YAxis2;
else
m_plotFlags |= ImPlotFlags_YAxis2;
return true;
} else if (name == "yaxis3") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_YAxis3;
else
m_plotFlags |= ImPlotFlags_YAxis3;
return true;
} else if (name == "viewTime") {
int num;
if (value.getAsInteger(10, num)) return true;
m_viewTime = num / 1000.0;
return true;
} else if (name == "height") {
int num;
if (value.getAsInteger(10, num)) return true;
m_height = num;
return true;
} else if (name.startswith("y")) {
auto [yAxisStr, yName] = name.split('_');
int yAxis;
if (yAxisStr.substr(1).getAsInteger(10, yAxis)) return false;
if (yAxis < 0 || yAxis > 3) return false;
if (yName == "min") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].min = num / 1000.0;
return true;
} else if (yName == "max") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].max = num / 1000.0;
return true;
} else if (yName == "lockMin") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].lockMin = num != 0;
return true;
} else if (yName == "lockMax") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].lockMax = num != 0;
return true;
}
}
return false;
}
void Plot::WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
out->appendf(
"visible=%d\nlockPrevX=%d\nlegend=%d\nyaxis2=%d\nyaxis3=%d\n"
"viewTime=%d\nheight=%d\n",
m_visible ? 1 : 0, m_lockPrevX ? 1 : 0,
(m_plotFlags & ImPlotFlags_Legend) ? 1 : 0,
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
static_cast<int>(m_viewTime * 1000), m_height);
for (int i = 0; i < 3; ++i) {
out->appendf("y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n", i,
static_cast<int>(m_axisRange[i].min * 1000), i,
static_cast<int>(m_axisRange[i].max * 1000), i,
m_axisRange[i].lockMin ? 1 : 0, i,
m_axisRange[i].lockMax ? 1 : 0);
}
}
void Plot::GetLabel(char* buf, size_t size, int index) const {
const char* name = NameInfo::GetName();
if (name[0] != '\0') {
std::snprintf(buf, size, "%s##Plot%d", name, index);
} else {
std::snprintf(buf, size, "Plot %d##Plot%d", index, index);
}
}
void Plot::GetName(char* buf, size_t size, int index) const {
const char* name = NameInfo::GetName();
if (name[0] != '\0') {
std::snprintf(buf, size, "%s", name);
} else {
std::snprintf(buf, size, "Plot %d", index);
}
}
void Plot::DragDropTarget(size_t i, bool inPlot) {
if (!ImGui::BeginDragDropTarget()) return;
// handle dragging onto a specific Y axis
int yAxis = -1;
if (inPlot) {
for (int y = 0; y < 3; ++y) {
if (ImPlot::IsPlotYAxisHovered(y)) {
yAxis = y;
break;
}
}
}
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("DataSource")) {
auto source = *static_cast<GuiDataSource**>(payload->Data);
// don't add duplicates unless it's onto a different Y axis
auto it =
std::find_if(m_series.begin(), m_series.end(), [=](const auto& elem) {
return elem->GetId() == source->GetId() &&
(yAxis == -1 || elem->GetYAxis() == yAxis);
});
if (it == m_series.end()) {
m_series.emplace_back(
std::make_unique<PlotSeries>(source, yAxis == -1 ? 0 : yAxis));
}
} else if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("PlotSeries")) {
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
MovePlotSeries(ref->plotIndex, ref->seriesIndex, i, m_series.size(), yAxis);
} else if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("Plot")) {
auto fromPlotIndex = *static_cast<const size_t*>(payload->Data);
if (i != fromPlotIndex) {
auto val = std::move(gPlots[fromPlotIndex]);
gPlots.insert(gPlots.begin() + i, std::move(val));
gPlots.erase(gPlots.begin() + fromPlotIndex +
(fromPlotIndex > i ? 1 : 0));
}
}
}
void Plot::EmitPlot(double now, size_t i) {
if (!m_visible) return;
bool lockX = (i != 0 && m_lockPrevX);
if (!lockX && ImGui::Button(m_paused ? "Resume" : "Pause"))
m_paused = !m_paused;
char label[128];
GetLabel(label, sizeof(label), i);
if (lockX) {
ImPlot::SetNextPlotLimitsX(gPlots[i - 1].m_xaxisRange.Min,
gPlots[i - 1].m_xaxisRange.Max,
ImGuiCond_Always);
} else {
// also force-pause plots if overall timing is paused
ImPlot::SetNextPlotLimitsX(now - m_viewTime, now,
(m_paused || HALSIM_IsTimingPaused())
? ImGuiCond_Once
: ImGuiCond_Always);
}
ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_Default,
ImPlotAxisFlags_Auxiliary,
ImPlotAxisFlags_Auxiliary};
for (int i = 0; i < 3; ++i) {
ImPlot::SetNextPlotLimitsY(
m_axisRange[i].min, m_axisRange[i].max,
m_axisRange[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
m_axisRange[i].apply = false;
if (m_axisRange[i].lockMin) yFlags[i] |= ImPlotAxisFlags_LockMin;
if (m_axisRange[i].lockMax) yFlags[i] |= ImPlotAxisFlags_LockMax;
}
if (ImPlot::BeginPlot(label, nullptr, nullptr, ImVec2(-1, m_height),
m_plotFlags, ImPlotAxisFlags_Default, yFlags[0],
yFlags[1], yFlags[2])) {
for (size_t j = 0; j < m_series.size(); ++j) {
if (m_series[j]->EmitPlot(now, j, i)) {
m_series.erase(m_series.begin() + j);
}
}
DragDropTarget(i, true);
m_xaxisRange = ImPlot::GetPlotLimits().X;
ImPlot::EndPlot();
}
}
void Plot::EmitSettingsLimits(int axis) {
ImGui::Indent();
ImGui::PushID(axis);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Max", &m_axisRange[axis].max, 0, 0, "%.3f");
ImGui::SameLine();
if (ImGui::Button("Apply")) m_axisRange[axis].apply = true;
ImGui::TextUnformatted("Lock Axis");
ImGui::SameLine();
ImGui::Checkbox("Min##minlock", &m_axisRange[axis].lockMin);
ImGui::SameLine();
ImGui::Checkbox("Max##maxlock", &m_axisRange[axis].lockMax);
ImGui::PopID();
ImGui::Unindent();
}
// Delete button (X in circle), based on ImGui::CloseButton()
static bool DeleteButton(ImGuiID id, const ImVec2& pos) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
// We intentionally allow interaction when clipped so that a mechanical
// Alt,Right,Validate sequence close a window. (this isn't the regular
// behavior of buttons, but it doesn't affect the user much because navigation
// tends to keep items visible).
const ImRect bb(
pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
bool is_clipped = !ImGui::ItemAdd(bb, id);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
if (is_clipped) return pressed;
// Render
ImU32 col =
ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
ImVec2 center = bb.GetCenter();
if (hovered)
window->DrawList->AddCircleFilled(
center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
ImU32 cross_col = ImGui::GetColorU32(ImGuiCol_Text);
window->DrawList->AddCircle(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f),
cross_col, 12);
float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f;
center -= ImVec2(0.5f, 0.5f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent),
center + ImVec2(-cross_extent, -cross_extent),
cross_col, 1.0f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent),
center + ImVec2(-cross_extent, +cross_extent),
cross_col, 1.0f);
return pressed;
}
void Plot::EmitSettings(size_t i) {
char label[128];
GetLabel(label, sizeof(label), i);
bool open = ImGui::CollapsingHeader(
label, ImGuiTreeNodeFlags_AllowItemOverlap |
ImGuiTreeNodeFlags_ClipLabelForTrailingButton |
(IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0));
{
// Create a small overlapping delete button
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiItemHoveredDataBackup last_item_backup;
ImGuiID id = window->GetID(label);
float button_size = g.FontSize;
float button_x = ImMax(window->DC.LastItemRect.Min.x,
window->DC.LastItemRect.Max.x -
g.Style.FramePadding.x * 2.0f - button_size);
float button_y = window->DC.LastItemRect.Min.y;
if (DeleteButton(window->GetID(reinterpret_cast<void*>(
static_cast<intptr_t>(id) + 1)),
ImVec2(button_x, button_y))) {
gPlots.erase(gPlots.begin() + i);
return;
}
last_item_backup.Restore();
}
// DND source for Plot
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("Plot", &i, sizeof(i));
char name[64];
GetName(name, sizeof(name), i);
ImGui::TextUnformatted(name);
ImGui::EndDragDropSource();
}
DragDropTarget(i, false);
SetOpen(open);
PopupEditName(i);
if (!open) return;
ImGui::PushID(i);
#if 0
if (ImGui::Button("Move Up") && i > 0) {
std::swap(gPlots[i - 1], gPlots[i]);
ImGui::PopID();
return;
}
ImGui::SameLine();
if (ImGui::Button("Move Down") && i < (gPlots.size() - 1)) {
std::swap(gPlots[i], gPlots[i + 1]);
ImGui::PopID();
return;
}
ImGui::SameLine();
if (ImGui::Button("Delete")) {
gPlots.erase(gPlots.begin() + i);
ImGui::PopID();
return;
}
#endif
ImGui::Checkbox("Visible", &m_visible);
ImGui::CheckboxFlags("Show Legend", &m_plotFlags, ImPlotFlags_Legend);
if (i != 0) ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
ImGui::TextUnformatted("Primary Y-Axis");
EmitSettingsLimits(0);
ImGui::CheckboxFlags("2nd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis2);
if ((m_plotFlags & ImPlotFlags_YAxis2) != 0) EmitSettingsLimits(1);
ImGui::CheckboxFlags("3rd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis3);
if ((m_plotFlags & ImPlotFlags_YAxis3) != 0) EmitSettingsLimits(2);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::InputFloat("View Time (s)", &m_viewTime, 0.1f, 1.0f, "%.1f");
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
if (ImGui::InputInt("Height", &m_height, 10)) {
if (m_height < 0) m_height = 0;
}
ImGui::Indent();
for (size_t j = 0; j < m_series.size(); ++j) {
ImGui::PushID(j);
if (m_series[j]->EmitSettings(j, i)) {
m_series.erase(m_series.begin() + j);
}
ImGui::PopID();
}
ImGui::Unindent();
ImGui::PopID();
}
static void DisplayPlot() {
if (gPlots.empty()) {
ImGui::Text("No Plots");
return;
}
double now = wpi::Now() * 1.0e-6;
for (size_t i = 0; i < gPlots.size(); ++i) {
ImGui::PushID(i);
gPlots[i].EmitPlot(now, i);
ImGui::PopID();
}
ImGui::Text("(Right double click for more settings)");
}
static void DisplayPlotSettings() {
if (ImGui::Button("Add new plot")) {
gPlots.emplace_back();
}
for (size_t i = 0; i < gPlots.size(); ++i) {
gPlots[i].EmitSettings(i);
}
}
static void* PlotSeries_ReadOpen(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
const char* name) {
wpi::StringRef plotIndexStr, id;
std::tie(plotIndexStr, id) = wpi::StringRef{name}.split(',');
unsigned int plotIndex;
if (plotIndexStr.getAsInteger(10, plotIndex)) return nullptr;
if (plotIndex >= gPlots.size()) gPlots.resize(plotIndex + 1);
auto& plot = gPlots[plotIndex];
auto it = std::find_if(
plot.m_series.begin(), plot.m_series.end(),
[&](const auto& elem) { return elem && elem->GetId() == id; });
if (it != plot.m_series.end()) return it->get();
return plot.m_series.emplace_back(std::make_unique<PlotSeries>(id)).get();
}
static void PlotSeries_ReadLine(ImGuiContext* ctx,
ImGuiSettingsHandler* handler, void* entry,
const char* lineStr) {
auto element = static_cast<PlotSeries*>(entry);
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
element->ReadIni(name, value);
}
static void PlotSeries_WriteAll(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
for (size_t i = 0; i < gPlots.size(); ++i) {
for (const auto& series : gPlots[i].m_series) {
out_buf->appendf("[PlotSeries][%d,%s]\n", static_cast<int>(i),
series->GetId().c_str());
series->WriteIni(out_buf);
out_buf->append("\n");
}
}
}
void PlotGui::Initialize() {
gPlots.Initialize();
// hook ini handler for PlotSeries to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = "PlotSeries";
iniHandler.TypeHash = ImHashStr("PlotSeries");
iniHandler.ReadOpenFn = PlotSeries_ReadOpen;
iniHandler.ReadLineFn = PlotSeries_ReadLine;
iniHandler.WriteAllFn = PlotSeries_WriteAll;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
// HALSimGui::AddExecute([] { ImPlot::ShowDemoWindow(); });
HALSimGui::AddWindow("Plot", DisplayPlot);
HALSimGui::SetDefaultWindowPos("Plot", 600, 75);
HALSimGui::SetDefaultWindowSize("Plot", 300, 200);
HALSimGui::AddWindow("Plot Settings", DisplayPlotSettings);
HALSimGui::SetDefaultWindowPos("Plot Settings", 902, 75);
HALSimGui::SetDefaultWindowSize("Plot Settings", 120, 200);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 PlotGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -1,120 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "RelayGui.h"
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/RelayData.h>
#include <imgui.h>
#include "ExtraGuiWidgets.h"
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(RelayForward, "RelayFwd");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(RelayReverse, "RelayRev");
} // namespace
static IniSaver<NameInfo> gRelays{"Relay"};
static std::vector<std::unique_ptr<RelayForwardSource>> gRelayForwardSources;
static std::vector<std::unique_ptr<RelayReverseSource>> gRelayReverseSources;
static void UpdateRelaySources() {
for (int i = 0, iend = gRelayForwardSources.size(); i < iend; ++i) {
auto& source = gRelayForwardSources[i];
if (HALSIM_GetRelayInitializedForward(i)) {
if (!source) {
source = std::make_unique<RelayForwardSource>(i);
source->SetName(gRelays[i].GetName());
}
} else {
source.reset();
}
}
for (int i = 0, iend = gRelayReverseSources.size(); i < iend; ++i) {
auto& source = gRelayReverseSources[i];
if (HALSIM_GetRelayInitializedReverse(i)) {
if (!source) {
source = std::make_unique<RelayReverseSource>(i);
source->SetName(gRelays[i].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayRelays() {
bool hasOutputs = false;
bool first = true;
for (int i = 0, iend = gRelayForwardSources.size(); i < iend; ++i) {
auto forwardSource = gRelayForwardSources[i].get();
auto reverseSource = gRelayReverseSources[i].get();
if (forwardSource || reverseSource) {
hasOutputs = true;
if (!first)
ImGui::Separator();
else
first = false;
bool forward = false;
bool reverse = false;
if (!HALSimGui::AreOutputsDisabled()) {
if (forwardSource) forward = forwardSource->GetValue();
if (reverseSource) reverse = reverseSource->GetValue();
}
auto& info = gRelays[i];
info.PushEditNameId(i);
if (info.HasName())
ImGui::Text("%s [%d]", info.GetName(), i);
else
ImGui::Text("Relay[%d]", i);
ImGui::PopID();
if (info.PopupEditName(i)) {
if (forwardSource) forwardSource->SetName(info.GetName());
if (reverseSource) reverseSource->SetName(info.GetName());
}
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] = {reverseSource ? (reverse ? 2 : -2) : -3,
forwardSource ? (forward ? 1 : -1) : -3};
GuiDataSource* sources[2] = {reverseSource, forwardSource};
ImGui::PushID(i);
DrawLEDSources(values, sources, 2, 2, colors);
ImGui::PopID();
}
}
if (!hasOutputs) ImGui::Text("No relays");
}
void RelayGui::Initialize() {
gRelays.Initialize();
int numRelays = HAL_GetNumRelayHeaders();
gRelayForwardSources.resize(numRelays);
gRelayReverseSources.resize(numRelays);
HALSimGui::AddExecute(UpdateRelaySources);
HALSimGui::AddWindow("Relays", DisplayRelays,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Relays", 180, 20);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 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 RelayGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -0,0 +1,120 @@
/*----------------------------------------------------------------------------*/
/* 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 "RelaySimGui.h"
#include <glass/hardware/Relay.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/RelayData.h>
#include <wpigui.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(RelayForward, "RelayFwd");
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(RelayReverse, "RelayRev");
class RelaySimModel : public glass::RelayModel {
public:
explicit RelaySimModel(int32_t index)
: m_index{index}, m_forward{index}, m_reverse{index} {}
void Update() override {}
bool Exists() override {
return HALSIM_GetRelayInitializedForward(m_index) ||
HALSIM_GetRelayInitializedReverse(m_index);
}
glass::DataSource* GetForwardData() override {
return HALSIM_GetRelayInitializedForward(m_index) ? &m_forward : nullptr;
}
glass::DataSource* GetReverseData() override {
return HALSIM_GetRelayInitializedReverse(m_index) ? &m_reverse : nullptr;
}
void SetForward(bool val) override { HALSIM_SetRelayForward(m_index, val); }
void SetReverse(bool val) override { HALSIM_SetRelayReverse(m_index, val); }
private:
int32_t m_index;
RelayForwardSource m_forward;
RelayReverseSource m_reverse;
};
class RelaysSimModel : public glass::RelaysModel {
public:
RelaysSimModel() : m_models(HAL_GetNumRelayHeaders()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachRelay(wpi::function_ref<void(glass::RelayModel& model, int index)>
func) override;
private:
// indexed by channel
std::vector<std::unique_ptr<RelaySimModel>> m_models;
};
} // namespace
void RelaysSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetRelayInitializedForward(i) ||
HALSIM_GetRelayInitializedReverse(i)) {
if (!model) {
model = std::make_unique<RelaySimModel>(i);
}
} else {
model.reset();
}
}
}
void RelaysSimModel::ForEachRelay(
wpi::function_ref<void(glass::RelayModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
static bool RelayAnyInitialized() {
static const int32_t num = HAL_GetNumRelayHeaders();
for (int32_t i = 0; i < num; ++i) {
if (HALSIM_GetRelayInitializedForward(i) ||
HALSIM_GetRelayInitializedReverse(i))
return true;
}
return false;
}
void RelaySimGui::Initialize() {
HALSimGui::halProvider.Register(
"Relays", RelayAnyInitialized,
[] { return std::make_unique<RelaysSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(180, 20);
return glass::MakeFunctionView([=] {
glass::DisplayRelays(static_cast<RelaysSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
});
});
}

View File

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

View File

@@ -1,170 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "RoboRioGui.h"
#include <memory>
#include <hal/simulation/RoboRioData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioVInVoltage, "Rio Input Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioVInCurrent, "Rio Input Current");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage6V, "Rio 6V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent6V, "Rio 6V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive6V, "Rio 6V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults6V, "Rio 6V Faults");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage5V, "Rio 5V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent5V, "Rio 5V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive5V, "Rio 5V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults5V, "Rio 5V Faults");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage3V3, "Rio 3.3V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent3V3, "Rio 3.3V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive3V3, "Rio 3.3V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults3V3, "Rio 3.3V Faults");
struct RoboRioSource {
RoboRioVInVoltageSource vInVoltage;
RoboRioVInCurrentSource vInCurrent;
RoboRioUserVoltage6VSource userVoltage6V;
RoboRioUserCurrent6VSource userCurrent6V;
RoboRioUserActive6VSource userActive6V;
RoboRioUserFaults6VSource userFaults6V;
RoboRioUserVoltage5VSource userVoltage5V;
RoboRioUserCurrent5VSource userCurrent5V;
RoboRioUserActive5VSource userActive5V;
RoboRioUserFaults5VSource userFaults5V;
RoboRioUserVoltage3V3Source userVoltage3V3;
RoboRioUserCurrent3V3Source userCurrent3V3;
RoboRioUserActive3V3Source userActive3V3;
RoboRioUserFaults3V3Source userFaults3V3;
};
} // namespace
static std::unique_ptr<RoboRioSource> gRioSource;
static void UpdateRoboRioSources() {
if (!gRioSource) gRioSource = std::make_unique<RoboRioSource>();
}
static void DisplayRoboRio() {
ImGui::Button("User Button");
HALSIM_SetRoboRioFPGAButton(ImGui::IsItemActive());
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::CollapsingHeader("RoboRIO Input")) {
{
double val = gRioSource->vInVoltage.GetValue();
if (gRioSource->vInVoltage.InputDouble("Voltage (V)", &val))
HALSIM_SetRoboRioVInVoltage(val);
}
{
double val = gRioSource->vInCurrent.GetValue();
if (gRioSource->vInCurrent.InputDouble("Current (A)", &val))
HALSIM_SetRoboRioVInCurrent(val);
}
}
if (ImGui::CollapsingHeader("6V Rail")) {
{
double val = gRioSource->userVoltage6V.GetValue();
if (gRioSource->userVoltage6V.InputDouble("Voltage (V)", &val))
HALSIM_SetRoboRioUserVoltage6V(val);
}
{
double val = gRioSource->userCurrent6V.GetValue();
if (gRioSource->userCurrent6V.InputDouble("Current (A)", &val))
HALSIM_SetRoboRioUserCurrent6V(val);
}
{
static const char* options[] = {"inactive", "active"};
int val = gRioSource->userActive6V.GetValue() ? 1 : 0;
if (gRioSource->userActive6V.Combo("Active", &val, options, 2))
HALSIM_SetRoboRioUserActive6V(val);
}
{
int val = gRioSource->userFaults6V.GetValue();
if (gRioSource->userFaults6V.InputInt("Faults", &val))
HALSIM_SetRoboRioUserFaults6V(val);
}
}
if (ImGui::CollapsingHeader("5V Rail")) {
{
double val = gRioSource->userVoltage5V.GetValue();
if (gRioSource->userVoltage5V.InputDouble("Voltage (V)", &val))
HALSIM_SetRoboRioUserVoltage5V(val);
}
{
double val = gRioSource->userCurrent5V.GetValue();
if (gRioSource->userCurrent5V.InputDouble("Current (A)", &val))
HALSIM_SetRoboRioUserCurrent5V(val);
}
{
static const char* options[] = {"inactive", "active"};
int val = gRioSource->userActive5V.GetValue() ? 1 : 0;
if (gRioSource->userActive5V.Combo("Active", &val, options, 2))
HALSIM_SetRoboRioUserActive5V(val);
}
{
int val = gRioSource->userFaults5V.GetValue();
if (gRioSource->userFaults5V.InputInt("Faults", &val))
HALSIM_SetRoboRioUserFaults5V(val);
}
}
if (ImGui::CollapsingHeader("3.3V Rail")) {
{
double val = gRioSource->userVoltage3V3.GetValue();
if (gRioSource->userVoltage3V3.InputDouble("Voltage (V)", &val))
HALSIM_SetRoboRioUserVoltage3V3(val);
}
{
double val = gRioSource->userCurrent3V3.GetValue();
if (gRioSource->userCurrent3V3.InputDouble("Current (A)", &val))
HALSIM_SetRoboRioUserCurrent3V3(val);
}
{
static const char* options[] = {"inactive", "active"};
int val = HALSIM_GetRoboRioUserActive3V3() ? 1 : 0;
if (gRioSource->userActive3V3.Combo("Active", &val, options, 2))
HALSIM_SetRoboRioUserActive3V3(val);
}
{
int val = gRioSource->userFaults3V3.GetValue();
if (gRioSource->userFaults3V3.InputInt("Faults", &val))
HALSIM_SetRoboRioUserFaults3V3(val);
}
}
ImGui::PopItemWidth();
}
void RoboRioGui::Initialize() {
HALSimGui::AddExecute(UpdateRoboRioSources);
HALSimGui::AddWindow("RoboRIO", DisplayRoboRio,
ImGuiWindowFlags_AlwaysAutoResize);
// hide it by default
HALSimGui::SetWindowVisibility("RoboRIO", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("RoboRIO", 5, 125);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 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 RoboRioGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -0,0 +1,139 @@
/*----------------------------------------------------------------------------*/
/* 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 "RoboRioSimGui.h"
#include <glass/hardware/RoboRio.h>
#include <memory>
#include <hal/simulation/RoboRioData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioFPGAButton, "Rio User Button");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioVInVoltage, "Rio Input Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioVInCurrent, "Rio Input Current");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage6V, "Rio 6V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent6V, "Rio 6V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive6V, "Rio 6V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults6V, "Rio 6V Faults");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage5V, "Rio 5V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent5V, "Rio 5V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive5V, "Rio 5V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults5V, "Rio 5V Faults");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserVoltage3V3, "Rio 3.3V Voltage");
HALSIMGUI_DATASOURCE_DOUBLE(RoboRioUserCurrent3V3, "Rio 3.3V Current");
HALSIMGUI_DATASOURCE_BOOLEAN(RoboRioUserActive3V3, "Rio 3.3V Active");
HALSIMGUI_DATASOURCE_INT(RoboRioUserFaults3V3, "Rio 3.3V Faults");
class RoboRioUser6VRailSimModel : public glass::RoboRioRailModel {
public:
void Update() override {}
bool Exists() override { return true; }
glass::DataSource* GetVoltageData() override { return &m_voltage; }
glass::DataSource* GetCurrentData() override { return &m_current; }
glass::DataSource* GetActiveData() override { return &m_active; }
glass::DataSource* GetFaultsData() override { return &m_faults; }
void SetVoltage(double val) override { HALSIM_SetRoboRioUserVoltage6V(val); }
void SetCurrent(double val) override { HALSIM_SetRoboRioUserCurrent6V(val); }
void SetActive(bool val) override { HALSIM_SetRoboRioUserActive6V(val); }
void SetFaults(int val) override { HALSIM_SetRoboRioUserFaults6V(val); }
private:
RoboRioUserVoltage6VSource m_voltage;
RoboRioUserCurrent6VSource m_current;
RoboRioUserActive6VSource m_active;
RoboRioUserFaults6VSource m_faults;
};
class RoboRioUser5VRailSimModel : public glass::RoboRioRailModel {
public:
void Update() override {}
bool Exists() override { return true; }
glass::DataSource* GetVoltageData() override { return &m_voltage; }
glass::DataSource* GetCurrentData() override { return &m_current; }
glass::DataSource* GetActiveData() override { return &m_active; }
glass::DataSource* GetFaultsData() override { return &m_faults; }
void SetVoltage(double val) override { HALSIM_SetRoboRioUserVoltage5V(val); }
void SetCurrent(double val) override { HALSIM_SetRoboRioUserCurrent5V(val); }
void SetActive(bool val) override { HALSIM_SetRoboRioUserActive5V(val); }
void SetFaults(int val) override { HALSIM_SetRoboRioUserFaults5V(val); }
private:
RoboRioUserVoltage5VSource m_voltage;
RoboRioUserCurrent5VSource m_current;
RoboRioUserActive5VSource m_active;
RoboRioUserFaults5VSource m_faults;
};
class RoboRioUser3V3RailSimModel : public glass::RoboRioRailModel {
public:
void Update() override {}
bool Exists() override { return true; }
glass::DataSource* GetVoltageData() override { return &m_voltage; }
glass::DataSource* GetCurrentData() override { return &m_current; }
glass::DataSource* GetActiveData() override { return &m_active; }
glass::DataSource* GetFaultsData() override { return &m_faults; }
void SetVoltage(double val) override { HALSIM_SetRoboRioUserVoltage3V3(val); }
void SetCurrent(double val) override { HALSIM_SetRoboRioUserCurrent3V3(val); }
void SetActive(bool val) override { HALSIM_SetRoboRioUserActive3V3(val); }
void SetFaults(int val) override { HALSIM_SetRoboRioUserFaults3V3(val); }
private:
RoboRioUserVoltage3V3Source m_voltage;
RoboRioUserCurrent3V3Source m_current;
RoboRioUserActive3V3Source m_active;
RoboRioUserFaults3V3Source m_faults;
};
class RoboRioSimModel : public glass::RoboRioModel {
public:
void Update() override {}
bool Exists() override { return true; }
glass::RoboRioRailModel* GetUser6VRail() override { return &m_user6VRail; }
glass::RoboRioRailModel* GetUser5VRail() override { return &m_user5VRail; }
glass::RoboRioRailModel* GetUser3V3Rail() override { return &m_user3V3Rail; }
glass::DataSource* GetUserButton() override { return &m_userButton; }
glass::DataSource* GetVInVoltageData() override { return &m_vInVoltage; }
glass::DataSource* GetVInCurrentData() override { return &m_vInCurrent; }
void SetUserButton(bool val) override { HALSIM_SetRoboRioFPGAButton(val); }
void SetVInVoltage(double val) override { HALSIM_SetRoboRioVInVoltage(val); }
void SetVInCurrent(double val) override { HALSIM_SetRoboRioVInCurrent(val); }
private:
RoboRioFPGAButtonSource m_userButton;
RoboRioVInVoltageSource m_vInVoltage;
RoboRioVInCurrentSource m_vInCurrent;
RoboRioUser6VRailSimModel m_user6VRail;
RoboRioUser5VRailSimModel m_user5VRail;
RoboRioUser3V3RailSimModel m_user3V3Rail;
};
} // namespace
void RoboRioSimGui::Initialize() {
HALSimGui::halProvider.Register(
"RoboRIO", [] { return true; },
[] { return std::make_unique<RoboRioSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(5, 125);
return glass::MakeFunctionView(
[=] { DisplayRoboRio(static_cast<RoboRioSimModel*>(model)); });
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* 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. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class CompressorGui {
class RoboRioSimGui {
public:
static void Initialize();
};

View File

@@ -7,44 +7,24 @@
#include "SimDeviceGui.h"
#include <glass/other/DeviceTree.h>
#include <stdint.h>
#include <functional>
#include <memory>
#include <vector>
#include <hal/SimDevice.h>
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include <wpi/DenseMap.h>
#include "GuiDataSource.h"
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "IniSaverInfo.h"
#include "IniSaverString.h"
using namespace halsimgui;
namespace {
struct ElementInfo : public NameInfo, public OpenInfo {
bool ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
return false;
}
void WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
}
bool visible = true; // not saved
};
class SimValueSource : public GuiDataSource {
class SimValueSource : public glass::DataSource {
public:
explicit SimValueSource(HAL_SimValueHandle handle, const char* device,
const char* name)
: GuiDataSource(wpi::Twine{device} + wpi::Twine{'-'} + name),
: DataSource(wpi::Twine{device} + wpi::Twine{'-'} + name),
m_callback{HALSIM_RegisterSimValueChangedCallback(
handle, this, CallbackFunc, true)} {}
~SimValueSource() {
@@ -67,226 +47,124 @@ class SimValueSource : public GuiDataSource {
int32_t m_callback;
};
class SimDevicesModel : public glass::Model {
public:
void Update() override;
bool Exists() override { return true; }
glass::DataSource* GetSource(HAL_SimValueHandle handle) {
return m_sources[handle].get();
}
private:
wpi::DenseMap<HAL_SimValueHandle, std::unique_ptr<SimValueSource>> m_sources;
};
} // namespace
static std::vector<std::function<void()>> gDeviceExecutors;
static IniSaverString<ElementInfo> gElements{"Device"};
static wpi::DenseMap<HAL_SimValueHandle, std::unique_ptr<SimValueSource>>
gSimValueSources;
static SimDevicesModel* gSimDevicesModel;
static void UpdateSimValueSources() {
void SimDevicesModel::Update() {
HALSIM_EnumerateSimDevices(
"", nullptr, [](const char* name, void*, HAL_SimDeviceHandle handle) {
"", this, [](const char* name, void* self, HAL_SimDeviceHandle handle) {
struct Data {
SimDevicesModel* self;
const char* device;
} data = {static_cast<SimDevicesModel*>(self), name};
HALSIM_EnumerateSimValues(
handle, const_cast<char*>(name),
[](const char* name, void* deviceV, HAL_SimValueHandle handle,
handle, &data,
[](const char* name, void* dataV, HAL_SimValueHandle handle,
HAL_Bool readonly, const HAL_Value* value) {
auto device = static_cast<const char*>(deviceV);
auto& source = gSimValueSources[handle];
auto data = static_cast<Data*>(dataV);
auto& source = data->self->m_sources[handle];
if (!source) {
source = std::make_unique<SimValueSource>(handle, device, name);
source = std::make_unique<SimValueSource>(handle, data->device,
name);
}
});
});
}
void SimDeviceGui::Hide(const char* name) { gElements[name].visible = false; }
static void DisplaySimValue(const char* name, void* data,
HAL_SimValueHandle handle, HAL_Bool readonly,
const HAL_Value* value) {
auto model = static_cast<SimDevicesModel*>(data);
void SimDeviceGui::Add(std::function<void()> execute) {
if (execute) gDeviceExecutors.emplace_back(std::move(execute));
}
HAL_Value valueCopy = *value;
bool SimDeviceGui::StartDevice(const char* label, ImGuiTreeNodeFlags flags) {
auto& element = gElements[label];
if (!element.visible) return false;
char name[128];
element.GetLabel(name, sizeof(name), label);
bool open = ImGui::CollapsingHeader(
name, flags | (element.IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0));
element.SetOpen(open);
element.PopupEditName(label);
if (open) ImGui::PushID(label);
return open;
}
void SimDeviceGui::FinishDevice() { ImGui::PopID(); }
static bool DisplayValueImpl(const char* name, bool readonly, HAL_Value* value,
const char** options, int32_t numOptions) {
// read-only
if (readonly) {
switch (value->type) {
case HAL_BOOLEAN:
ImGui::LabelText(name, "%s", value->data.v_boolean ? "true" : "false");
break;
case HAL_DOUBLE:
ImGui::LabelText(name, "%.6f", value->data.v_double);
break;
case HAL_ENUM: {
int current = value->data.v_enum;
if (current < 0 || current >= numOptions)
ImGui::LabelText(name, "%d (unknown)", current);
else
ImGui::LabelText(name, "%s", options[current]);
break;
}
case HAL_INT:
ImGui::LabelText(name, "%d", static_cast<int>(value->data.v_int));
break;
case HAL_LONG:
ImGui::LabelText(name, "%lld",
static_cast<long long int>( // NOLINT(runtime/int)
value->data.v_long));
break;
default:
break;
}
return false;
}
// writable
switch (value->type) {
case HAL_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
int val = value->data.v_boolean ? 1 : 0;
if (ImGui::Combo(name, &val, boolOptions, 2)) {
value->data.v_boolean = val;
return true;
bool v = value->data.v_boolean;
if (glass::DeviceBoolean(name, readonly, &v, model->GetSource(handle))) {
valueCopy.data.v_boolean = v ? 1 : 0;
HAL_SetSimValue(handle, valueCopy);
}
break;
}
case HAL_DOUBLE: {
if (ImGui::InputDouble(name, &value->data.v_double, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue))
return true;
case HAL_DOUBLE:
if (glass::DeviceDouble(name, readonly, &valueCopy.data.v_double,
model->GetSource(handle))) {
HAL_SetSimValue(handle, valueCopy);
}
break;
}
case HAL_ENUM: {
int current = value->data.v_enum;
if (ImGui::Combo(name, &current, options, numOptions)) {
value->data.v_enum = current;
return true;
int32_t numOptions = 0;
const char** options = HALSIM_GetSimValueEnumOptions(handle, &numOptions);
if (glass::DeviceEnum(name, readonly, &valueCopy.data.v_enum, options,
numOptions, model->GetSource(handle))) {
HAL_SetSimValue(handle, valueCopy);
}
break;
}
case HAL_INT: {
if (ImGui::InputScalar(name, ImGuiDataType_S32, &value->data.v_int,
nullptr, nullptr, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue))
return true;
case HAL_INT:
if (glass::DeviceInt(name, readonly, &valueCopy.data.v_int,
model->GetSource(handle))) {
HAL_SetSimValue(handle, valueCopy);
}
break;
}
case HAL_LONG: {
if (ImGui::InputScalar(name, ImGuiDataType_S64, &value->data.v_long,
nullptr, nullptr, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue))
return true;
case HAL_LONG:
if (glass::DeviceLong(name, readonly, &valueCopy.data.v_long,
model->GetSource(handle))) {
HAL_SetSimValue(handle, valueCopy);
}
break;
}
default:
break;
}
return false;
}
static bool DisplayValueSourceImpl(const char* name, bool readonly,
HAL_Value* value,
const GuiDataSource* source,
const char** options, int32_t numOptions) {
if (!source)
return DisplayValueImpl(name, readonly, value, options, numOptions);
ImGui::PushID(name);
bool rv = DisplayValueImpl("", readonly, value, options, numOptions);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(name);
source->EmitDrag();
ImGui::PopID();
return rv;
}
bool SimDeviceGui::DisplayValue(const char* name, bool readonly,
HAL_Value* value, const char** options,
int32_t numOptions) {
return DisplayValueSource(name, readonly, value, nullptr, options,
numOptions);
}
bool SimDeviceGui::DisplayValueSource(const char* name, bool readonly,
HAL_Value* value,
const GuiDataSource* source,
const char** options,
int32_t numOptions) {
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f);
return DisplayValueSourceImpl(name, readonly, value, source, options,
numOptions);
}
static void SimDeviceDisplayValue(const char* name, void*,
HAL_SimValueHandle handle, HAL_Bool readonly,
const HAL_Value* value) {
int32_t numOptions = 0;
const char** options = nullptr;
if (value->type == HAL_ENUM)
options = HALSIM_GetSimValueEnumOptions(handle, &numOptions);
HAL_Value valueCopy = *value;
if (DisplayValueSourceImpl(name, readonly, &valueCopy,
gSimValueSources[handle].get(), options,
numOptions))
HAL_SetSimValue(handle, valueCopy);
}
static void SimDeviceDisplayDevice(const char* name, void*,
HAL_SimDeviceHandle handle) {
auto it = gElements.find(name);
if (it != gElements.end() && !it->second.visible) return;
if (SimDeviceGui::StartDevice(name)) {
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f);
HALSIM_EnumerateSimValues(handle, nullptr, SimDeviceDisplayValue);
ImGui::PopItemWidth();
SimDeviceGui::FinishDevice();
static void DisplaySimDevice(const char* name, void* data,
HAL_SimDeviceHandle handle) {
if (glass::BeginDevice(name)) {
HALSIM_EnumerateSimValues(handle, data, DisplaySimValue);
glass::EndDevice();
}
}
static void DisplayDeviceTree() {
for (auto&& execute : gDeviceExecutors) {
if (execute) execute();
}
HALSIM_EnumerateSimDevices("", nullptr, SimDeviceDisplayDevice);
}
void SimDeviceGui::Initialize() {
gElements.Initialize();
HALSimGui::AddExecute(UpdateSimValueSources);
HALSimGui::AddWindow("Other Devices", DisplayDeviceTree);
HALSimGui::SetDefaultWindowPos("Other Devices", 1025, 20);
HALSimGui::SetDefaultWindowSize("Other Devices", 250, 695);
HALSimGui::halProvider.Register(
"Other Devices", [] { return true; },
[] { return std::make_unique<glass::DeviceTreeModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetDefaultPos(1025, 20);
win->SetDefaultSize(250, 695);
return glass::MakeFunctionView(
[=] { static_cast<glass::DeviceTreeModel*>(model)->Display(); });
});
auto model = std::make_unique<SimDevicesModel>();
gSimDevicesModel = model.get();
GetDeviceTree().Add(std::move(model), [](glass::Model* model) {
HALSIM_EnumerateSimDevices("", static_cast<SimDevicesModel*>(model),
DisplaySimDevice);
});
}
extern "C" {
void HALSIMGUI_DeviceTreeAdd(void* param, void (*execute)(void*)) {
if (execute) SimDeviceGui::Add([=] { execute(param); });
glass::DataSource* SimDeviceGui::GetValueSource(HAL_SimValueHandle handle) {
return gSimDevicesModel->GetSource(handle);
}
void HALSIMGUI_DeviceTreeHide(const char* name) { SimDeviceGui::Hide(name); }
HAL_Bool HALSIMGUI_DeviceTreeDisplayValue(const char* name, HAL_Bool readonly,
struct HAL_Value* value,
const char** options,
int32_t numOptions) {
return SimDeviceGui::DisplayValue(name, readonly, value, options, numOptions);
glass::DeviceTreeModel& SimDeviceGui::GetDeviceTree() {
static auto model = HALSimGui::halProvider.GetModel("Other Devices");
assert(model);
return *static_cast<glass::DeviceTreeModel*>(model);
}
HAL_Bool HALSIMGUI_DeviceTreeStartDevice(const char* label, int32_t flags) {
return SimDeviceGui::StartDevice(label, flags);
}
void HALSIMGUI_DeviceTreeFinishDevice(void) { SimDeviceGui::FinishDevice(); }
} // extern "C"

View File

@@ -1,130 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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 "SolenoidGui.h"
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/PCMData.h>
#include <imgui.h>
#include <wpi/SmallVector.h>
#include "ExtraGuiWidgets.h"
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED2(PCMSolenoidOutput, "Solenoid");
struct PCMSource {
explicit PCMSource(int numChannels) : solenoids(numChannels) {}
std::vector<std::unique_ptr<PCMSolenoidOutputSource>> solenoids;
int initCount = 0;
};
} // namespace
static IniSaver<OpenInfo> gPCMs{"PCM"};
static IniSaver<NameInfo> gSolenoids{"Solenoid"};
static std::vector<PCMSource> gPCMSources;
static void UpdateSolenoidSources() {
for (int i = 0, iend = gPCMSources.size(); i < iend; ++i) {
auto& pcmSource = gPCMSources[i];
int numChannels = pcmSource.solenoids.size();
pcmSource.initCount = 0;
for (int j = 0; j < numChannels; ++j) {
auto& source = pcmSource.solenoids[j];
if (HALSIM_GetPCMSolenoidInitialized(i, j)) {
if (!source) {
source = std::make_unique<PCMSolenoidOutputSource>(i, j);
source->SetName(gSolenoids[i * numChannels + j].GetName());
}
++pcmSource.initCount;
} else {
source.reset();
}
}
}
}
static void DisplaySolenoids() {
bool hasOutputs = false;
for (int i = 0, iend = gPCMSources.size(); i < iend; ++i) {
auto& pcmSource = gPCMSources[i];
if (pcmSource.initCount == 0) continue;
hasOutputs = true;
int numChannels = pcmSource.solenoids.size();
wpi::SmallVector<int, 16> channels;
channels.resize(numChannels);
for (int j = 0; j < numChannels; ++j) {
if (pcmSource.solenoids[j]) {
channels[j] = (!HALSimGui::AreOutputsDisabled() &&
pcmSource.solenoids[j]->GetValue())
? 1
: -1;
} else {
channels[j] = -2;
}
}
char name[128];
std::snprintf(name, sizeof(name), "PCM[%d]", i);
auto& pcmInfo = gPCMs[i];
bool open = ImGui::CollapsingHeader(
name, pcmInfo.IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0);
pcmInfo.SetOpen(open);
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::PushID(i);
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
for (int j = 0; j < numChannels; ++j) {
if (!pcmSource.solenoids[j]) continue;
auto& info = gSolenoids[i * numChannels + j];
info.GetLabel(name, sizeof(name), "Solenoid", j);
ImGui::PushID(j);
pcmSource.solenoids[j]->LabelText(name, "%s",
channels[j] == 1 ? "On" : "Off");
if (info.PopupEditName(j)) {
pcmSource.solenoids[j]->SetName(info.GetName());
}
ImGui::PopID();
}
ImGui::PopItemWidth();
ImGui::PopID();
}
}
if (!hasOutputs) ImGui::Text("No solenoids");
}
void SolenoidGui::Initialize() {
gPCMs.Initialize();
gSolenoids.Initialize();
const int numModules = HAL_GetNumPCMModules();
const int numChannels = HAL_GetNumSolenoidChannels();
gPCMSources.reserve(numModules);
for (int i = 0; i < numModules; ++i) gPCMSources.emplace_back(numChannels);
HALSimGui::AddExecute(UpdateSolenoidSources);
HALSimGui::AddWindow("Solenoids", DisplaySolenoids,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Solenoids", 290, 20);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 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 SolenoidGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -7,6 +7,9 @@
#include "TimingGui.h"
#include <glass/Model.h>
#include <glass/View.h>
#include <cstdio>
#include <cstring>
#include <vector>
@@ -20,6 +23,14 @@
using namespace halsimgui;
namespace {
class TimingModel : public glass::Model {
public:
void Update() override {}
bool Exists() override { return true; }
};
} // namespace
static void DisplayTiming() {
int32_t status = 0;
uint64_t curTime = HAL_GetFPGATime(&status);
@@ -55,7 +66,14 @@ static void DisplayTiming() {
}
void TimingGui::Initialize() {
HALSimGui::AddWindow("Timing", DisplayTiming,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Timing", 5, 150);
HALSimGui::halProvider.Register(
"Timing", [] { return true; },
[] { return std::make_unique<TimingModel>(); },
[](glass::Window* win, glass::Model* model) {
win->DisableRenamePopup();
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(5, 150);
return glass::MakeFunctionView(DisplayTiming);
});
HALSimGui::halProvider.ShowDefault("Timing");
}

View File

@@ -5,71 +5,118 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include <glass/Context.h>
#include <glass/other/Plot.h>
#include <hal/Extensions.h>
#include <hal/Main.h>
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
#include "AccelerometerGui.h"
#include "AccelerometerSimGui.h"
#include "AddressableLEDGui.h"
#include "AnalogGyroGui.h"
#include "AnalogInputGui.h"
#include "AnalogOutGui.h"
#include "CompressorGui.h"
#include "DIOGui.h"
#include "AnalogGyroSimGui.h"
#include "AnalogInputSimGui.h"
#include "AnalogOutputSimGui.h"
#include "DIOSimGui.h"
#include "DriverStationGui.h"
#include "EncoderGui.h"
#include "Field2D.h"
#include "EncoderSimGui.h"
#include "HALSimGui.h"
#include "Mechanism2D.h"
#include "NetworkTablesGui.h"
#include "PDPGui.h"
#include "PWMGui.h"
#include "PlotGui.h"
#include "RelayGui.h"
#include "RoboRioGui.h"
#include "NetworkTablesSimGui.h"
#include "PCMSimGui.h"
#include "PDPSimGui.h"
#include "PWMSimGui.h"
#include "RelaySimGui.h"
#include "RoboRioSimGui.h"
#include "SimDeviceGui.h"
#include "SolenoidGui.h"
#include "TimingGui.h"
using namespace halsimgui;
namespace gui = wpi::gui;
static glass::PlotProvider gPlotProvider{"Plot"};
extern "C" {
#if defined(WIN32) || defined(_WIN32)
__declspec(dllexport)
#endif
int HALSIM_InitExtension(void) {
HALSimGui::GlobalInit();
HALSimGui::Add(AccelerometerGui::Initialize);
HALSimGui::Add(AddressableLEDGui::Initialize);
HALSimGui::Add(AnalogGyroGui::Initialize);
HALSimGui::Add(AnalogInputGui::Initialize);
HALSimGui::Add(AnalogOutGui::Initialize);
HALSimGui::Add(CompressorGui::Initialize);
HALSimGui::Add(DriverStationGui::Initialize);
HALSimGui::Add(DIOGui::Initialize);
HALSimGui::Add(EncoderGui::Initialize);
HALSimGui::Add(Field2D::Initialize);
HALSimGui::Add(Mechanism2D::Initialize);
HALSimGui::Add(NetworkTablesGui::Initialize);
HALSimGui::Add(PDPGui::Initialize);
HALSimGui::Add(PlotGui::Initialize);
HALSimGui::Add(PWMGui::Initialize);
HALSimGui::Add(RelayGui::Initialize);
HALSimGui::Add(RoboRioGui::Initialize);
HALSimGui::Add(SimDeviceGui::Initialize);
HALSimGui::Add(SolenoidGui::Initialize);
HALSimGui::Add(TimingGui::Initialize);
wpi::outs() << "Simulator GUI Initializing.\n";
if (!HALSimGui::Initialize()) return 0;
gui::CreateContext();
glass::CreateContext();
HALSimGui::GlobalInit();
DriverStationGui::GlobalInit();
gPlotProvider.GlobalInit();
// These need to initialize first
gui::AddInit(EncoderSimGui::Initialize);
gui::AddInit(SimDeviceGui::Initialize);
gui::AddInit(AccelerometerSimGui::Initialize);
gui::AddInit(AddressableLEDGui::Initialize);
gui::AddInit(AnalogGyroSimGui::Initialize);
gui::AddInit(AnalogInputSimGui::Initialize);
gui::AddInit(AnalogOutputSimGui::Initialize);
gui::AddInit(DIOSimGui::Initialize);
gui::AddInit(Mechanism2D::Initialize);
gui::AddInit(NetworkTablesSimGui::Initialize);
gui::AddInit(PCMSimGui::Initialize);
gui::AddInit(PDPSimGui::Initialize);
gui::AddInit(PWMSimGui::Initialize);
gui::AddInit(RelaySimGui::Initialize);
gui::AddInit(RoboRioSimGui::Initialize);
gui::AddInit(TimingGui::Initialize);
HALSimGui::mainMenu.AddMainMenu([] {
if (ImGui::BeginMenu("Hardware")) {
HALSimGui::halProvider.DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("NetworkTables")) {
NetworkTablesSimGui::DisplayMenu();
ImGui::Separator();
HALSimGui::ntProvider.DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("DS")) {
DriverStationGui::dsManager.DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Plot")) {
bool paused = gPlotProvider.IsPaused();
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
gPlotProvider.SetPaused(paused);
}
ImGui::Separator();
gPlotProvider.DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Window")) {
HALSimGui::manager.DisplayMenu();
ImGui::EndMenu();
}
});
if (!gui::Initialize("Robot Simulation", 1280, 720)) return 0;
HAL_RegisterExtensionListener(
nullptr, [](void*, const char* name, void* data) {
if (wpi::StringRef{name} == "ds_socket") {
DriverStationGui::SetDSSocketExtension(data);
}
});
HAL_SetMain(nullptr, HALSimGui::Main, HALSimGui::Exit);
HAL_SetMain(
nullptr,
[](void*) {
gui::Main();
glass::DestroyContext();
gui::DestroyContext();
},
[](void*) { gui::Exit(); });
wpi::outs() << "Simulator GUI Initialized!\n";
return 0;