[hal, wpilib] Add OpMode support (#7744)

User code:
- OpModeRobot used as the robot base class
- LinearOpMode and PeriodicOpMode are provided opmode base classes
- In Java, annotations can be used to automatically register opmode classes

Additional user code functionality:
- OpMode (string) is available in addition to the overall
auto/teleop/test robot mode
- OpMode does not indicate enable (enable/disable is still separate)
- The HAL API uses integer UIDs; these are exposed at the user API level
as well for faster checks
- User code creates opmodes on startup (these have name, category,
description, etc).

DS:
- DS will present opmode selection lists for auto and teleop for
match/practice. During a match, the DS will automatically activate the
selected opmode in the corresponding match period.
- For testing, an overall mode is selected (e.g. teleop/auto/test) and a
single opmode is selected

Future work:
- Command framework support/integration
- Python annotation support
- Unit tests (needs race-free DS sim updates)
- Porting of examples

Co-authored-by: Joseph Eng <91924258+KangarooKoala@users.noreply.github.com>
This commit is contained in:
Peter Johnson
2025-12-12 21:25:57 -07:00
committed by GitHub
parent 2a41b80e00
commit dacded37e5
163 changed files with 7454 additions and 2175 deletions

View File

@@ -42,6 +42,7 @@ model {
}
binaries {
all {
project(':hal').addHalDependency(it, 'shared')
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'

View File

@@ -5,12 +5,11 @@
#include "wpi/halsim/ds_socket/DSCommPacket.hpp"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <span>
#include <thread>
#include <vector>
#include "wpi/hal/DashboardOpMode.hpp"
#include "wpi/hal/DriverStationTypes.h"
#include "wpi/hal/simulation/DriverStationData.h"
#include "wpi/hal/simulation/MockHooks.h"
@@ -54,13 +53,18 @@ DSCommPacket::DSCommPacket() {
**--------------------------------------------------------------------------*/
void DSCommPacket::SetControl(uint8_t control, uint8_t request) {
std::memset(&m_control_word, 0, sizeof(m_control_word));
m_control_word.enabled = (control & kEnabled) != 0;
m_control_word.autonomous = (control & kAutonomous) != 0;
m_control_word.test = (control & kTest) != 0;
m_control_word.eStop = (control & kEmergencyStop) != 0;
m_control_word.fmsAttached = (control & kFMS_Attached) != 0;
m_control_word.dsAttached = (request & kRequestNormalMask) != 0;
HAL_RobotMode robotMode;
if ((control & kAutonomous) != 0) {
robotMode = HAL_ROBOTMODE_AUTONOMOUS;
} else if ((control & kTest) != 0) {
robotMode = HAL_ROBOTMODE_TEST;
} else {
robotMode = HAL_ROBOTMODE_TELEOPERATED;
}
m_control_word = HAL_MakeControlWord(
wpi::hal::GetDashboardSelectedOpMode(robotMode), robotMode,
(control & kEnabled) != 0, (control & kEmergencyStop) != 0,
(control & kFMS_Attached) != 0, (request & kRequestNormalMask) != 0);
m_control_sent = control;
}
@@ -305,17 +309,19 @@ void DSCommPacket::SetupSendHeader(wpi::net::raw_uv_ostream& buf) {
void DSCommPacket::SendUDPToHALSim(void) {
SendJoysticks();
if (!m_control_word.enabled) {
if (!HAL_ControlWord_IsEnabled(m_control_word)) {
m_match_time = -1;
}
HALSIM_SetDriverStationMatchTime(m_match_time);
HALSIM_SetDriverStationEnabled(m_control_word.enabled);
HALSIM_SetDriverStationAutonomous(m_control_word.autonomous);
HALSIM_SetDriverStationTest(m_control_word.test);
HALSIM_SetDriverStationEStop(m_control_word.eStop);
HALSIM_SetDriverStationFmsAttached(m_control_word.fmsAttached);
HALSIM_SetDriverStationDsAttached(m_control_word.dsAttached);
HALSIM_SetDriverStationEnabled(HAL_ControlWord_IsEnabled(m_control_word));
HALSIM_SetDriverStationRobotMode(
HAL_ControlWord_GetRobotMode(m_control_word));
HALSIM_SetDriverStationEStop(HAL_ControlWord_IsEStopped(m_control_word));
HALSIM_SetDriverStationFmsAttached(
HAL_ControlWord_IsFMSAttached(m_control_word));
HALSIM_SetDriverStationDsAttached(
HAL_ControlWord_IsDSAttached(m_control_word));
HALSIM_SetDriverStationAllianceStationId(m_alliance_station);
HALSIM_NotifyDriverStationNewData();

View File

@@ -20,6 +20,7 @@
#include <memory>
#include <string_view>
#include "wpi/hal/DashboardOpMode.hpp"
#include "wpi/hal/Extensions.h"
#include "wpi/halsim/ds_socket/DSCommPacket.hpp"
#include "wpi/net/EventLoopRunner.hpp"
@@ -97,6 +98,7 @@ static void SetupTcp(wpi::net::uv::Loop& loop) {
tcp->Listen([t = tcp.get()] {
auto client = t->Accept();
gDSConnected = true;
wpi::hal::EnableDashboardOpMode();
client->data.connect([t](Buffer& buf, size_t len) {
HandleTcpDataStream(buf, len, *t->GetData<DataStore>());

View File

@@ -27,6 +27,7 @@ model {
lib project: ':glass', library: 'glass', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
project(':hal').addHalDependency(it, 'shared')
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include <atomic>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <string_view>
@@ -23,6 +24,7 @@
#include "wpi/glass/support/ExtraGuiWidgets.hpp"
#include "wpi/glass/support/NameSetting.hpp"
#include "wpi/gui/wpigui.hpp"
#include "wpi/hal/DashboardOpMode.hpp"
#include "wpi/hal/DriverStationTypes.h"
#include "wpi/hal/simulation/DriverStationData.h"
#include "wpi/hal/simulation/MockHooks.h"
@@ -226,9 +228,8 @@ class FMSSimModel : public wpi::glass::FMSModel {
wpi::glass::DoubleSource* GetMatchTimeData() override { return &m_matchTime; }
wpi::glass::BooleanSource* GetEStopData() override { return &m_estop; }
wpi::glass::BooleanSource* GetEnabledData() override { return &m_enabled; }
wpi::glass::BooleanSource* GetTestData() override { return &m_test; }
wpi::glass::BooleanSource* GetAutonomousData() override {
return &m_autonomous;
wpi::glass::IntegerSource* GetRobotModeData() override {
return &m_robotMode;
}
wpi::glass::StringSource* GetGameSpecificMessageData() override {
return &m_gameMessage;
@@ -242,8 +243,7 @@ class FMSSimModel : public wpi::glass::FMSModel {
void SetMatchTime(double val) override { m_matchTime.SetValue(val); }
void SetEStop(bool val) override { m_estop.SetValue(val); }
void SetEnabled(bool val) override { m_enabled.SetValue(val); }
void SetTest(bool val) override { m_test.SetValue(val); }
void SetAutonomous(bool val) override { m_autonomous.SetValue(val); }
void SetRobotMode(RobotMode val) override { m_robotMode.SetValue(val); }
void SetGameSpecificMessage(std::string_view val) override {
m_gameMessage.SetValue(val);
}
@@ -263,8 +263,7 @@ class FMSSimModel : public wpi::glass::FMSModel {
wpi::glass::DoubleSource m_matchTime{"FMS:MatchTime"};
wpi::glass::BooleanSource m_estop{"FMS:EStop"};
wpi::glass::BooleanSource m_enabled{"FMS:RobotEnabled"};
wpi::glass::BooleanSource m_test{"FMS:TestMode"};
wpi::glass::BooleanSource m_autonomous{"FMS:AutonomousMode"};
wpi::glass::IntegerSource m_robotMode{"FMS:RobotMode"};
double m_startMatchTime = -1.0;
wpi::glass::StringSource m_gameMessage{"FMS:GameSpecificMessage"};
};
@@ -287,11 +286,72 @@ static std::unique_ptr<FMSSimModel> gFMSModel;
std::unique_ptr<DSManager> DriverStationGui::dsManager;
static bool* gpDisableDS = nullptr;
static bool* gpDashboardOpModes = nullptr;
static bool* gpZeroDisconnectedJoysticks = nullptr;
static bool* gpUseEnableDisableHotkeys = nullptr;
static bool* gpUseEstopHotkey = nullptr;
static std::atomic<bool>* gpDSSocketConnected = nullptr;
// OpMode options
namespace {
struct OpModeOption {
int64_t id;
std::string name;
std::string description;
int32_t textColor;
int32_t backgroundColor;
};
struct OpModes {
std::map<int64_t, std::string> ids;
wpi::util::StringMap<std::vector<OpModeOption>> groups;
};
} // namespace
static wpi::util::mutex gOpModeOptionsMutex;
static OpModes gAutoOpModes;
static OpModes gTeleopOpModes;
static OpModes gTestOpModes;
static void UpdateOpModes(const char* name, void* param,
const HAL_OpModeOption* opmodes, int32_t count) {
std::scoped_lock lock(gOpModeOptionsMutex);
gAutoOpModes.ids.clear();
gAutoOpModes.groups.clear();
gTeleopOpModes.ids.clear();
gTeleopOpModes.groups.clear();
gTestOpModes.ids.clear();
gTestOpModes.groups.clear();
for (auto&& o : std::span{opmodes, opmodes + count}) {
OpModes* vec;
switch (HAL_OpMode_GetRobotMode(o.id)) {
case HAL_ROBOTMODE_AUTONOMOUS:
vec = &gAutoOpModes;
break;
case HAL_ROBOTMODE_TELEOPERATED:
vec = &gTeleopOpModes;
break;
case HAL_ROBOTMODE_TEST:
vec = &gTestOpModes;
break;
default:
continue;
}
vec->ids[o.id] = wpi::util::to_string_view(&o.name);
vec->groups[wpi::util::to_string_view(&o.group)].emplace_back(
OpModeOption{o.id, std::string{wpi::util::to_string_view(&o.name)},
std::string{wpi::util::to_string_view(&o.description)},
o.textColor, o.backgroundColor});
}
for (auto&& vec : {&gAutoOpModes, &gTeleopOpModes, &gTestOpModes}) {
for (auto&& [group, options] : vec->groups) {
std::sort(options.begin(), options.end(),
[](const OpModeOption& a, const OpModeOption& b) {
return a.name < b.name;
});
}
}
}
static inline bool IsDSDisabled() {
return (gpDisableDS != nullptr && *gpDisableDS) ||
(gpDSSocketConnected && *gpDSSocketConnected);
@@ -1001,19 +1061,24 @@ void RobotJoystick::GetHAL(int i) {
HALSIM_GetJoystickPOVs(i, &data.povs);
}
static void DriverStationConnect(bool enabled, bool autonomous, bool test) {
static void DriverStationSetRobotMode(HAL_RobotMode mode) {
if (!HALSIM_GetDriverStationDsAttached()) {
// initialize FMS bits too
gFMSModel->SetDsAttached(true);
gFMSModel->SetEnabled(enabled);
gFMSModel->SetAutonomous(autonomous);
gFMSModel->SetTest(test);
gFMSModel->SetEnabled(false);
gFMSModel->SetRobotMode(static_cast<FMSSimModel::RobotMode>(mode));
gFMSModel->UpdateHAL();
} else {
HALSIM_SetDriverStationEnabled(enabled);
HALSIM_SetDriverStationAutonomous(autonomous);
HALSIM_SetDriverStationTest(test);
}
HALSIM_SetDriverStationDsAttached(true);
HALSIM_SetDriverStationEnabled(false);
HALSIM_SetDriverStationOpMode(0);
HALSIM_SetDriverStationRobotMode(mode);
}
static void DriverStationSetEnabled(bool enabled) {
gFMSModel->SetEnabled(enabled);
gFMSModel->UpdateHAL();
HALSIM_SetDriverStationEnabled(enabled);
}
static void DriverStationExecute() {
@@ -1061,6 +1126,10 @@ static void DriverStationExecute() {
return;
}
if (*gpDashboardOpModes) {
wpi::hal::EnableDashboardOpMode();
}
double curTime = glfwGetTime();
// update system joysticks
@@ -1082,8 +1151,10 @@ static void DriverStationExecute() {
bool isAttached = HALSIM_GetDriverStationDsAttached();
bool isEnabled = HALSIM_GetDriverStationEnabled();
bool isAuto = HALSIM_GetDriverStationAutonomous();
bool isTest = HALSIM_GetDriverStationTest();
HAL_RobotMode robotMode = HALSIM_GetDriverStationRobotMode();
int64_t opMode = HALSIM_GetDriverStationOpMode();
bool started = HALSIM_GetProgramStarted();
int64_t programOpMode = wpi::hal::sim::GetProgramState().GetOpModeId();
// Robot state
{
@@ -1119,31 +1190,104 @@ static void DriverStationExecute() {
ImGui::Begin(title, nullptr, ImGuiWindowFlags_AlwaysAutoResize);
if (ImGui::Selectable("Disconnected", !isAttached)) {
HALSIM_SetDriverStationEnabled(false);
HALSIM_SetDriverStationOpMode(0);
HALSIM_SetDriverStationDsAttached(false);
isAttached = false;
gFMSModel->Update();
}
if (ImGui::Selectable("Disabled", isAttached && !isEnabled) ||
if (ImGui::Selectable(
"Autonomous",
isAttached && robotMode == HAL_ROBOTMODE_AUTONOMOUS)) {
DriverStationSetRobotMode(HAL_ROBOTMODE_AUTONOMOUS);
}
if (ImGui::Selectable(
"Teleoperated",
isAttached && robotMode == HAL_ROBOTMODE_TELEOPERATED)) {
DriverStationSetRobotMode(HAL_ROBOTMODE_TELEOPERATED);
}
if (ImGui::Selectable("Test",
isAttached && robotMode == HAL_ROBOTMODE_TEST)) {
DriverStationSetRobotMode(HAL_ROBOTMODE_TEST);
}
// OpMode
bool canEnable = isAttached && started;
if (*gpDashboardOpModes) {
HALSIM_SetDriverStationOpMode(
wpi::hal::GetDashboardSelectedOpMode(robotMode));
ImGui::Separator();
} else {
OpModes* modes;
switch (robotMode) {
case HAL_ROBOTMODE_AUTONOMOUS:
modes = &gAutoOpModes;
break;
case HAL_ROBOTMODE_TELEOPERATED:
modes = &gTeleopOpModes;
break;
case HAL_ROBOTMODE_TEST:
modes = &gTestOpModes;
break;
default:
modes = nullptr;
break;
}
if (modes) {
std::scoped_lock lock{gOpModeOptionsMutex};
auto nameIt = modes->ids.find(opMode);
auto name = nameIt != modes->ids.end() ? nameIt->second.c_str() : "";
if (ImGui::BeginCombo("OpMode", name)) {
for (auto&& [groupName, group] : modes->groups) {
if (!groupName.empty()) {
ImGui::TextDisabled("%s", groupName.c_str());
ImGui::Separator();
}
for (auto&& mode : group) {
bool selected = mode.id == opMode;
if (ImGui::Selectable(mode.name.c_str(), selected)) {
HALSIM_SetDriverStationOpMode(mode.id);
}
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (opMode == programOpMode) {
if (opMode == 0) {
ImGui::TextUnformatted("NONE");
if (!modes->ids.empty()) {
canEnable = false;
}
} else {
ImGui::TextUnformatted("GOOD");
}
} else {
ImGui::TextUnformatted("BAD ");
canEnable = false;
}
} else {
if (ImGui::BeginCombo("OpMode", "")) {
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::TextUnformatted(" ");
}
}
// Enable/Disable
if (ImGui::Selectable("Disable", isAttached && !isEnabled,
isAttached ? 0 : ImGuiSelectableFlags_Disabled) ||
disableHotkey) {
DriverStationConnect(false, false, false);
DriverStationSetEnabled(false);
}
if (ImGui::Selectable("Autonomous",
isAttached && isEnabled && isAuto && !isTest)) {
DriverStationConnect(true, true, false);
}
if (ImGui::Selectable("Teleoperated",
isAttached && isEnabled && !isAuto && !isTest) ||
enableHotkey) {
DriverStationConnect(true, false, false);
}
if (ImGui::Selectable("Test", isEnabled && isTest)) {
DriverStationConnect(true, false, true);
if (ImGui::Selectable("Enable", isAttached && isEnabled,
canEnable ? 0 : ImGuiSelectableFlags_Disabled) ||
(canEnable && enableHotkey)) {
DriverStationSetEnabled(true);
}
ImGui::End();
}
// Update HAL
if (isAttached && !isAuto) {
if (isAttached && robotMode != HAL_ROBOTMODE_AUTONOMOUS) {
for (int i = 0, end = gRobotJoysticks.size();
i < end && i < HAL_kMaxJoysticks; ++i) {
gRobotJoysticks[i].SetHAL(i);
@@ -1171,8 +1315,8 @@ void FMSSimModel::UpdateHAL() {
static_cast<HAL_AllianceStationID>(m_allianceStationId.GetValue()));
HALSIM_SetDriverStationEStop(m_estop.GetValue());
HALSIM_SetDriverStationEnabled(m_enabled.GetValue());
HALSIM_SetDriverStationTest(m_test.GetValue());
HALSIM_SetDriverStationAutonomous(m_autonomous.GetValue());
HALSIM_SetDriverStationRobotMode(
static_cast<HAL_RobotMode>(m_robotMode.GetValue()));
HALSIM_SetDriverStationMatchTime(m_matchTime.GetValue());
auto str = wpi::util::make_string(m_gameMessage.GetValue());
HALSIM_SetGameSpecificMessage(&str);
@@ -1186,8 +1330,7 @@ void FMSSimModel::Update() {
m_allianceStationId.SetValue(HALSIM_GetDriverStationAllianceStationId());
m_estop.SetValue(HALSIM_GetDriverStationEStop());
m_enabled.SetValue(enabled);
m_test.SetValue(HALSIM_GetDriverStationTest());
m_autonomous.SetValue(HALSIM_GetDriverStationAutonomous());
m_robotMode.SetValue(HALSIM_GetDriverStationRobotMode());
double matchTime = HALSIM_GetDriverStationMatchTime();
if (!IsDSDisabled() && enabled) {
@@ -1405,6 +1548,9 @@ void DSManager::DisplayMenu() {
if (gpDisableDS != nullptr) {
ImGui::MenuItem("Turn off DS", nullptr, gpDisableDS);
}
if (gpDashboardOpModes != nullptr) {
ImGui::MenuItem("Use Dashboard OpModes", nullptr, gpDashboardOpModes);
}
if (gpZeroDisconnectedJoysticks != nullptr) {
ImGui::MenuItem("Zero disconnected joysticks", nullptr,
gpZeroDisconnectedJoysticks);
@@ -1437,10 +1583,13 @@ void DriverStationGui::GlobalInit() {
gFMSModel = std::make_unique<FMSSimModel>();
HALSIM_RegisterOpModeOptionsCallback(UpdateOpModes, nullptr, true);
wpi::gui::AddEarlyExecute(DriverStationExecute);
storageRoot.SetCustomApply([&storageRoot] {
gpDisableDS = &storageRoot.GetBool("disable", false);
gpDashboardOpModes = &storageRoot.GetBool("dashboardOpModes", false);
gpZeroDisconnectedJoysticks =
&storageRoot.GetBool("zeroDisconnectedJoysticks", true);
gpUseEnableDisableHotkeys =

View File

@@ -28,7 +28,10 @@ model {
return
}
project(':hal').addHalDependency(it, 'shared')
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ":simulation:halsim_ws_core", library: "halsim_ws_core", linkage: "static"
}
}

View File

@@ -48,8 +48,7 @@ HALSimWSProviderDriverStation::~HALSimWSProviderDriverStation() {
void HALSimWSProviderDriverStation::RegisterCallbacks() {
m_enabledCbKey = REGISTER(Enabled, ">enabled", bool, boolean);
m_autonomousCbKey = REGISTER(Autonomous, ">autonomous", bool, boolean);
m_testCbKey = REGISTER(Test, ">test", bool, boolean);
m_robotModeCbKey = REGISTER(RobotMode, ">robotMode", int, enum);
m_estopCbKey = REGISTER(EStop, ">estop", bool, boolean);
m_fmsCbKey = REGISTER(FmsAttached, ">fms", bool, boolean);
m_dsCbKey = REGISTER(DsAttached, ">ds", bool, boolean);
@@ -102,8 +101,7 @@ void HALSimWSProviderDriverStation::CancelCallbacks() {
void HALSimWSProviderDriverStation::DoCancelCallbacks() {
HALSIM_CancelDriverStationEnabledCallback(m_enabledCbKey);
HALSIM_CancelDriverStationAutonomousCallback(m_autonomousCbKey);
HALSIM_CancelDriverStationTestCallback(m_testCbKey);
HALSIM_CancelDriverStationRobotModeCallback(m_robotModeCbKey);
HALSIM_CancelDriverStationEStopCallback(m_estopCbKey);
HALSIM_CancelDriverStationFmsAttachedCallback(m_fmsCbKey);
HALSIM_CancelDriverStationDsAttachedCallback(m_dsCbKey);
@@ -112,8 +110,7 @@ void HALSimWSProviderDriverStation::DoCancelCallbacks() {
HALSIM_CancelDriverStationMatchTimeCallback(m_matchTimeCbKey);
m_enabledCbKey = 0;
m_autonomousCbKey = 0;
m_testCbKey = 0;
m_robotModeCbKey = 0;
m_estopCbKey = 0;
m_fmsCbKey = 0;
m_dsCbKey = 0;
@@ -133,11 +130,8 @@ void HALSimWSProviderDriverStation::OnNetValueChanged(
if ((it = json.find(">enabled")) != json.end()) {
HALSIM_SetDriverStationEnabled(it.value());
}
if ((it = json.find(">autonomous")) != json.end()) {
HALSIM_SetDriverStationAutonomous(it.value());
}
if ((it = json.find(">test")) != json.end()) {
HALSIM_SetDriverStationTest(it.value());
if ((it = json.find(">robotMode")) != json.end()) {
HALSIM_SetDriverStationRobotMode(it.value());
}
if ((it = json.find(">estop")) != json.end()) {
HALSIM_SetDriverStationEStop(it.value());

View File

@@ -26,8 +26,7 @@ class HALSimWSProviderDriverStation : public HALSimWSHalProvider {
private:
int32_t m_enabledCbKey = 0;
int32_t m_autonomousCbKey = 0;
int32_t m_testCbKey = 0;
int32_t m_robotModeCbKey = 0;
int32_t m_estopCbKey = 0;
int32_t m_fmsCbKey = 0;
int32_t m_dsCbKey = 0;

View File

@@ -49,14 +49,17 @@ model {
return
}
project(':hal').addHalDependency(it, 'shared')
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ":simulation:halsim_ws_core", library: "halsim_ws_core", linkage: "static"
}
withType(GoogleTestTestSuiteBinarySpec) {
project(':hal').addHalDependency(it, 'shared')
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib library: pluginName, linkage: 'shared'
}

View File

@@ -165,7 +165,7 @@ TEST_F(WebServerIntegrationTest, DriverStation) {
// Compare results
HAL_ControlWord cw;
HAL_GetControlWord(&cw);
bool test_value = cw.enabled;
bool test_value = HAL_ControlWord_IsEnabled(cw);
EXPECT_EQ(EXPECTED_VALUE, test_value);
}

View File

@@ -30,6 +30,7 @@ model {
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ":simulation:halsim_ws_core", library: "halsim_ws_core", linkage: "static"
}
}