[hal] Use MrcLib to talk to DS (#8858)

Using MrcLib on the robot is going to be the plan for the future, to
make things easier.

MrcLib is how sim is supported going forward. The desktop version of
mrclib can act as a robot server.

This is set up where the mrclib interface is in shared code. On robot,
that is the only backend used. On desktop, a default sim backend is
used. However, the sim plugin can switch that to the real robot backend,
so the robot code will exactly look like a real robot.
This commit is contained in:
Thad House
2026-06-06 12:15:17 -07:00
committed by GitHub
parent ddf9306264
commit 6e5171cd8f
47 changed files with 1754 additions and 1721 deletions

View File

@@ -20,16 +20,12 @@
#include "mockdata/DriverStationDataInternal.hpp"
#include "wpi/hal/DriverStationTypes.h"
#include "wpi/hal/Errors.h"
#include "wpi/hal/cpp/MrcLibDs.hpp"
#include "wpi/hal/monotonic_clock.hpp"
#include "wpi/hal/simulation/MockHooks.h"
#include "wpi/util/EventVector.hpp"
#include "wpi/util/mutex.hpp"
#include "wpi/util/string.h"
static wpi::util::mutex msgMutex;
static std::atomic<HALSIM_SendErrorHandler> sendErrorHandler{nullptr};
static std::atomic<HALSIM_SendConsoleLineHandler> sendConsoleLineHandler{
nullptr};
#include "wpi/util/string.hpp"
using namespace wpi::hal;
@@ -53,13 +49,6 @@ static_assert(std::is_standard_layout_v<JoystickDataCache>);
// static_assert(std::is_trivial_v<JoystickDataCache>);
static std::atomic_bool gShutdown{false};
struct FIRSTDriverStation {
~FIRSTDriverStation() { gShutdown = true; }
wpi::util::EventVector newDataEvents;
wpi::util::mutex cacheMutex;
wpi::util::mutex tcpCacheMutex;
};
} // namespace
void JoystickDataCache::Update() {
@@ -83,14 +72,6 @@ void JoystickDataCache::Update() {
if ((stickNum) < 0 || (stickNum) >= HAL_MAX_JOYSTICKS) \
return HAL_PARAMETER_OUT_OF_RANGE;
static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentReadLocal = &caches[0];
static std::atomic<JoystickDataCache*> currentCache{nullptr};
static JoystickDataCache* lastGiven = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
namespace {
struct TcpCache {
TcpCache() { std::memset(this, 0, sizeof(*this)); }
@@ -103,9 +84,6 @@ struct TcpCache {
static_assert(std::is_standard_layout_v<TcpCache>);
} // namespace
static TcpCache tcpCache;
static TcpCache tcpCurrent;
void TcpCache::Update() {
SimDriverStationData->GetMatchInfo(&matchInfo);
@@ -114,23 +92,115 @@ void TcpCache::Update() {
}
}
static ::FIRSTDriverStation* driverStation;
class MrcLibDsSimImpl : public MrcLibDs {
public:
MrcLibDsSimImpl();
~MrcLibDsSimImpl() override { gShutdown = true; }
int32_t sendError(bool isError, int32_t errorCode,
const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack, bool printMsg) override;
namespace wpi::hal::init {
void InitializeDriverStation() {
static FIRSTDriverStation ds;
driverStation = &ds;
int32_t sendConsoleLine(const struct WPI_String* line) override;
int32_t sendProgramCrash(const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack) override;
int32_t getControlWord(HAL_ControlWord* controlWord) override;
int32_t getUncachedControlWord(HAL_ControlWord* controlWord) override;
int32_t setOpModeOptions(const struct HAL_OpModeOption* options,
int32_t count) override;
int32_t getAllianceStation(HAL_AllianceStationID* allianceStation) override;
int32_t getJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) override;
int32_t getJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) override;
int32_t getJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) override;
int32_t getJoystickTouchpads(int32_t joystickNum,
HAL_JoystickTouchpads* touchpads) override;
int32_t getAllJoystickData(int32_t joystickNum, HAL_JoystickAxes* axes,
HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons,
HAL_JoystickTouchpads* touchpads) override;
int32_t getJoystickDescriptor(int32_t joystickNum,
HAL_JoystickDescriptor* desc) override;
int32_t getGameData(HAL_GameData* gameData) override;
int32_t setJoystickRumble(int32_t joystickNum, int32_t leftRumble,
int32_t rightRumble, int32_t leftTriggerRumble,
int32_t rightTriggerRumble) override;
int32_t setJoystickLeds(int32_t joystickNum, int32_t leds) override;
int32_t getMatchTime(double* matchTime) override;
int32_t getMatchInfo(HAL_MatchInfo* info) override;
int32_t getOutputsEnabled(bool* outputsEnabled) override;
int32_t refreshDSData(bool* wasRefreshed) override;
void provideNewDataEventHandle(WPI_EventHandle handle) override;
void removeNewDataEventHandle(WPI_EventHandle handle) override;
int32_t observeUserProgramStarting() override;
int32_t observeUserProgram(HAL_ControlWord controlWord) override;
int32_t getSystemTimeValid(bool* systemTimeValid) override;
wpi::util::EventVector newDataEvents;
void NewDriverStationData();
private:
int32_t BackendPrintFunctionImpl(bool isError, int32_t errorCode,
const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack,
bool* forcePrintMsg);
wpi::util::mutex cacheMutex;
wpi::util::mutex tcpCacheMutex;
BackendPrintFunction backendPrintFunc;
HAL_ControlWord newestControlWord;
JoystickDataCache caches[3];
JoystickDataCache* currentRead = &caches[0];
JoystickDataCache* currentReadLocal = &caches[0];
std::atomic<JoystickDataCache*> currentCache{nullptr};
JoystickDataCache* lastGiven = &caches[1];
JoystickDataCache* cacheToUpdate = &caches[2];
TcpCache tcpCache;
TcpCache tcpCurrent;
};
MrcLibDsSimImpl::MrcLibDsSimImpl() {
newestControlWord.value = 0;
backendPrintFunc =
[this](bool isError, int32_t errorCode, const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack, bool* forcePrintMsg) {
return BackendPrintFunctionImpl(isError, errorCode, details, location,
callStack, forcePrintMsg);
};
}
} // namespace wpi::hal::init
namespace wpi::hal {
static void DefaultPrintErrorImpl(const char* line, size_t size) {
std::fwrite(line, size, 1, stderr);
}
} // namespace wpi::hal
static std::atomic<void (*)(const char* line, size_t size)> gPrintErrorImpl{
wpi::hal::DefaultPrintErrorImpl};
static std::atomic<HALSIM_SendErrorHandler> sendErrorHandler{nullptr};
static std::atomic<HALSIM_SendConsoleLineHandler> sendConsoleLineHandler{
nullptr};
extern "C" {
@@ -141,94 +211,63 @@ void HALSIM_SetSendError(HALSIM_SendErrorHandler handler) {
void HALSIM_SetSendConsoleLine(HALSIM_SendConsoleLineHandler handler) {
sendConsoleLineHandler.store(handler);
}
} // extern "C"
int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
const char* details, const char* location,
const char* callStack, HAL_Bool printMsg) {
int32_t MrcLibDsSimImpl::BackendPrintFunctionImpl(
bool isError, int32_t errorCode, const struct WPI_String* details,
const struct WPI_String* location, const struct WPI_String* callStack,
bool* forcePrintMsg) {
// This will defer to the caller, which needs to force print to true.
*forcePrintMsg = true;
return 0;
}
int32_t MrcLibDsSimImpl::sendError(bool isError, int32_t errorCode,
const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack,
bool printMsg) {
auto errorHandler = sendErrorHandler.load();
if (errorHandler) {
return errorHandler(isError, errorCode, isLVCode, details, location,
callStack, printMsg);
return errorHandler(isError, errorCode, details, location, callStack,
printMsg);
}
// Avoid flooding console by keeping track of previous 5 error
// messages and only printing again if they're longer than 1 second old.
static constexpr int KEEP_MSGS = 5;
std::scoped_lock lock(msgMutex);
static std::string prevMsg[KEEP_MSGS];
static monotonic_clock::time_point prevMsgTime[KEEP_MSGS];
static bool initialized = false;
if (!initialized) {
for (int i = 0; i < KEEP_MSGS; i++) {
prevMsgTime[i] = monotonic_clock::now() - std::chrono::seconds(2);
}
initialized = true;
}
auto curTime = monotonic_clock::now();
int i;
for (i = 0; i < KEEP_MSGS; ++i) {
if (prevMsg[i] == details) {
break;
}
}
int retval = 0;
if (i == KEEP_MSGS || (curTime - prevMsgTime[i]) >= std::chrono::seconds(1)) {
printMsg = true;
if (printMsg) {
fmt::memory_buffer buf;
if (location && location[0] != '\0') {
fmt::format_to(fmt::appender{buf},
"{} at {}: ", isError ? "Error" : "Warning", location);
}
fmt::format_to(fmt::appender{buf}, "{}\n", details);
if (callStack && callStack[0] != '\0') {
fmt::format_to(fmt::appender{buf}, "{}\n", callStack);
}
auto printError = gPrintErrorImpl.load();
printError(buf.data(), buf.size());
}
if (i == KEEP_MSGS) {
// replace the oldest one
i = 0;
auto first = prevMsgTime[0];
for (int j = 1; j < KEEP_MSGS; ++j) {
if (prevMsgTime[j] < first) {
first = prevMsgTime[j];
i = j;
}
}
prevMsg[i] = details;
}
prevMsgTime[i] = curTime;
}
return retval;
return DefaultSendErrorImpl(isError, errorCode, details, location, callStack,
printMsg, backendPrintFunc);
}
void HAL_SetPrintErrorImpl(void (*func)(const char* line, size_t size)) {
gPrintErrorImpl.store(func ? func : wpi::hal::DefaultPrintErrorImpl);
}
int32_t HAL_SendConsoleLine(const char* line) {
int32_t MrcLibDsSimImpl::sendConsoleLine(const struct WPI_String* line) {
auto handler = sendConsoleLineHandler.load();
if (handler) {
return handler(line);
}
std::puts(line);
fmt::print("{}\n", wpi::util::to_string_view(line));
std::fflush(stdout);
return 0;
}
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
int32_t MrcLibDsSimImpl::sendProgramCrash(const struct WPI_String* details,
const struct WPI_String* location,
const struct WPI_String* callStack) {
fmt::print(stderr, "Program Crash: {}\nLocation: {}\nCall Stack:\n{}\n",
wpi::util::to_string_view(details),
wpi::util::to_string_view(location),
wpi::util::to_string_view(callStack));
std::fflush(stderr);
return 0;
}
int32_t MrcLibDsSimImpl::getControlWord(HAL_ControlWord* controlWord) {
if (gShutdown) {
controlWord->value = 0;
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*controlWord = newestControlWord;
return 0;
}
int32_t HAL_GetUncachedControlWord(HAL_ControlWord* controlWord) {
int32_t MrcLibDsSimImpl::getUncachedControlWord(HAL_ControlWord* controlWord) {
if (gShutdown) {
controlWord->value = 0;
return HAL_INCOMPATIBLE_STATE;
@@ -245,181 +284,161 @@ int32_t HAL_GetUncachedControlWord(HAL_ControlWord* controlWord) {
return 0;
}
int32_t HAL_SetOpModeOptions(const struct HAL_OpModeOption* options,
int32_t count) {
int32_t MrcLibDsSimImpl::setOpModeOptions(
const struct HAL_OpModeOption* options, int32_t count) {
if (gShutdown) {
return 0;
}
if (count < 0 || count > 1000 || (count != 0 && !options)) {
return HAL_PARAMETER_OUT_OF_RANGE;
}
SimDriverStationData->SetOpModeOptions({options, options + count});
if (count == 0) {
SimDriverStationData->SetOpModeOptions({});
} else {
SimDriverStationData->SetOpModeOptions({options, options + count});
}
return 0;
}
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
int32_t MrcLibDsSimImpl::getAllianceStation(
HAL_AllianceStationID* allianceStation) {
if (gShutdown) {
return HAL_ALLIANCE_STATION_UNKNOWN;
*allianceStation = HAL_ALLIANCE_STATION_UNKNOWN;
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
return currentRead->allianceStation;
std::scoped_lock lock{cacheMutex};
*allianceStation = currentRead->allianceStation;
return 0;
}
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
int32_t MrcLibDsSimImpl::getJoystickAxes(int32_t joystickNum,
HAL_JoystickAxes* axes) {
if (gShutdown) {
return HAL_INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*axes = currentRead->axes[joystickNum];
return 0;
}
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
int32_t MrcLibDsSimImpl::getJoystickPOVs(int32_t joystickNum,
HAL_JoystickPOVs* povs) {
if (gShutdown) {
return HAL_INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*povs = currentRead->povs[joystickNum];
return 0;
}
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) {
int32_t MrcLibDsSimImpl::getJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) {
if (gShutdown) {
return HAL_INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*buttons = currentRead->buttons[joystickNum];
return 0;
}
int32_t HAL_GetJoystickTouchpads(int32_t joystickNum,
HAL_JoystickTouchpads* touchpads) {
int32_t MrcLibDsSimImpl::getJoystickTouchpads(
int32_t joystickNum, HAL_JoystickTouchpads* touchpads) {
if (gShutdown) {
return HAL_INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*touchpads = currentRead->touchpads[joystickNum];
return 0;
}
void HAL_GetAllJoystickData(int32_t joystickNum, HAL_JoystickAxes* axes,
HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons,
HAL_JoystickTouchpads* touchpads) {
int32_t MrcLibDsSimImpl::getAllJoystickData(int32_t joystickNum,
HAL_JoystickAxes* axes,
HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons,
HAL_JoystickTouchpads* touchpads) {
if (gShutdown) {
return;
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*axes = currentRead->axes[joystickNum];
*povs = currentRead->povs[joystickNum];
*buttons = currentRead->buttons[joystickNum];
*touchpads = currentRead->touchpads[joystickNum];
return 0;
}
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
HAL_JoystickDescriptor* desc) {
int32_t MrcLibDsSimImpl::getJoystickDescriptor(int32_t joystickNum,
HAL_JoystickDescriptor* desc) {
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->tcpCacheMutex};
std::scoped_lock lock{tcpCacheMutex};
*desc = tcpCurrent.descriptors[joystickNum];
return 0;
}
HAL_Bool HAL_GetJoystickIsGamepad(int32_t joystickNum) {
HAL_JoystickDescriptor joystickDesc;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
return 0;
} else {
return joystickDesc.isGamepad;
}
}
int32_t HAL_GetJoystickGamepadType(int32_t joystickNum) {
HAL_JoystickDescriptor joystickDesc;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
return -1;
} else {
return joystickDesc.gamepadType;
}
}
int32_t HAL_GetGameData(HAL_GameData* gameData) {
int32_t MrcLibDsSimImpl::getGameData(HAL_GameData* gameData) {
if (gShutdown) {
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
*gameData = currentRead->gameData;
return 0;
}
int32_t HAL_GetJoystickSupportedOutputs(int32_t joystickNum) {
HAL_JoystickDescriptor joystickDesc;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
return -1;
} else {
return joystickDesc.supportedOutputs;
}
}
void HAL_GetJoystickName(struct WPI_String* name, int32_t joystickNum) {
HAL_JoystickDescriptor joystickDesc;
const char* cName = joystickDesc.name;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
cName = "";
}
auto len = std::strlen(cName);
auto write = WPI_AllocateString(name, len);
std::memcpy(write, cName, len);
}
int32_t HAL_SetJoystickRumble(int32_t joystickNum, int32_t leftRumble,
int32_t rightRumble, int32_t leftTriggerRumble,
int32_t rightTriggerRumble) {
int32_t MrcLibDsSimImpl::setJoystickRumble(int32_t joystickNum,
int32_t leftRumble,
int32_t rightRumble,
int32_t leftTriggerRumble,
int32_t rightTriggerRumble) {
SimDriverStationData->SetJoystickRumbles(joystickNum, leftRumble, rightRumble,
leftTriggerRumble,
rightTriggerRumble);
return 0;
}
int32_t HAL_SetJoystickLeds(int32_t joystickNum, int32_t leds) {
int32_t MrcLibDsSimImpl::setJoystickLeds(int32_t joystickNum, int32_t leds) {
SimDriverStationData->SetJoystickLeds(joystickNum, leds);
return 0;
}
double HAL_GetMatchTime(int32_t* status) {
int32_t MrcLibDsSimImpl::getMatchTime(double* matchTime) {
if (gShutdown) {
return 0;
*matchTime = 0;
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
return currentRead->matchTime;
std::scoped_lock lock{cacheMutex};
*matchTime = currentRead->matchTime;
return 0;
}
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
std::scoped_lock lock{driverStation->tcpCacheMutex};
int32_t MrcLibDsSimImpl::getMatchInfo(HAL_MatchInfo* info) {
std::scoped_lock lock{tcpCacheMutex};
*info = tcpCurrent.matchInfo;
return 0;
}
void HAL_ObserveUserProgramStarting(void) {
int32_t MrcLibDsSimImpl::observeUserProgramStarting(void) {
HALSIM_SetProgramStarted(true);
return 0;
}
void HAL_ObserveUserProgram(HAL_ControlWord word) {
int32_t MrcLibDsSimImpl::observeUserProgram(HAL_ControlWord word) {
HALSIM_SetProgramState(word);
return 0;
}
HAL_Bool HAL_RefreshDSData(void) {
int32_t MrcLibDsSimImpl::refreshDSData(bool* wasRefreshed) {
if (gShutdown) {
return false;
*wasRefreshed = false;
return HAL_INCOMPATIBLE_STATE;
}
bool dsAttached = SimDriverStationData->dsAttached;
JoystickDataCache* prev;
{
std::scoped_lock lock{driverStation->cacheMutex};
std::scoped_lock lock{cacheMutex};
prev = currentCache.exchange(nullptr);
if (prev != nullptr) {
currentRead = prev;
@@ -448,44 +467,46 @@ HAL_Bool HAL_RefreshDSData(void) {
{
tcpCache.Update();
std::scoped_lock tcpLock(driverStation->tcpCacheMutex);
std::scoped_lock tcpLock(tcpCacheMutex);
tcpCache.CloneTo(&tcpCurrent);
}
return prev != nullptr;
*wasRefreshed = prev != nullptr;
return 0;
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
void MrcLibDsSimImpl::provideNewDataEventHandle(WPI_EventHandle handle) {
if (gShutdown) {
return;
}
wpi::hal::init::CheckInit();
driverStation->newDataEvents.Add(handle);
newDataEvents.Add(handle);
}
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
void MrcLibDsSimImpl::removeNewDataEventHandle(WPI_EventHandle handle) {
if (gShutdown) {
return;
}
driverStation->newDataEvents.Remove(handle);
newDataEvents.Remove(handle);
}
HAL_Bool HAL_GetOutputsEnabled(void) {
int32_t MrcLibDsSimImpl::getOutputsEnabled(bool* outputsEnabled) {
if (gShutdown) {
return false;
*outputsEnabled = false;
return HAL_INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
return HAL_ControlWord_IsEnabled(newestControlWord) &&
HAL_ControlWord_IsDSAttached(newestControlWord);
std::scoped_lock lock{cacheMutex};
*outputsEnabled = HAL_ControlWord_IsEnabled(newestControlWord) &&
HAL_ControlWord_IsDSAttached(newestControlWord);
return 0;
}
} // extern "C"
int32_t MrcLibDsSimImpl::getSystemTimeValid(bool* systemTimeValid) {
*systemTimeValid = true;
return 0;
}
namespace wpi::hal {
void NewDriverStationData() {
if (gShutdown) {
return;
}
void MrcLibDsSimImpl::NewDriverStationData() {
SimDriverStationData->dsAttached = true;
cacheToUpdate->Update();
@@ -500,11 +521,24 @@ void NewDriverStationData() {
}
lastGiven = given;
driverStation->newDataEvents.Wakeup();
newDataEvents.Wakeup();
SimDriverStationData->CallNewDataCallbacks();
}
void InitializeDriverStation() {
SimDriverStationData->ResetData();
namespace wpi::hal {
void NewDriverStationData() {
if (gShutdown) {
return;
}
MrcLibDsSimImpl* defaultImpl =
static_cast<MrcLibDsSimImpl*>(GetDefaultDriverStationImpl());
defaultImpl->NewDriverStationData();
}
MrcLibDs* GetDefaultDriverStationImpl() {
static MrcLibDsSimImpl impl;
return &impl;
}
} // namespace wpi::hal