[hal, wpilib] New DS thread model and implementation (#3787)

The current DS thread model has some pretty major issues. It makes it difficult to know if all data is from the same remote packet, and if the data changes while the robot loop is running. Additionally, the DS thread is used for a few other things (MotorSafety and State Tracking for EducationalRobot). This also makes sim difficult, as user code has to wait for the thread to know it has new data.

This change completely rethinks how threading works in the driver station model.

First, the DS HAL system receives a new data callback, either from Netcomm or DriverStationSim. Inside the context of this callback, all the low latency data is read and put into a cache. Doing some investigation on the robot side, this is perfectly safe to do, and also ensures a ds packet will not be parsed before we finish reading the current packet data.

After all data is read, the cache is swapped with a 2nd buffer. This buffer just stores the data, none of the HAL DS calls read from this buffer. An event is then fired, stating there is new data ready to go.

Robot code calls HAL_UpdateDSData(). This swaps the 2nd buffer with a 3rd buffer, which always contains the current data. This data will not be updated until HAL_UpdateDSData is called again. Which solves the state problem.

The high level driver station classes have. an updateData() call, which calls HAL_UpdateDSData, and then update button state variables, then data log and update the NT FMS data table (Java also caches across the JNI boundary here, but that could trivially be removed). An extra event provider is provided, allowing other threads to know when this call has been completed.

IterativeRobotBase calls DS.updateData() at the beginning of each loop, and only once per loop. This means all commands will always have the same state.

All of this means there is no longer a DS thread. Everything happens synchronously. This means Sim and testing is easier, as you can just call DriverStationSim.NotifyNewData(), and then DriverStation.UpdateData(), and you can guarantee that all the DriverStation.*** data is up to date.

As for Motor Safety and Educational Robot State Handling, those can all be handled by their own threads. The Educational Thread only needs to run under EducationalRobot, and MotorSafety will only be started if there is a motor safety object enabled.
This commit is contained in:
Thad House
2022-10-21 22:01:55 -07:00
committed by GitHub
parent c195b4fc46
commit 4a401b89d7
53 changed files with 1649 additions and 1384 deletions

View File

@@ -13,11 +13,14 @@
#include <FRC_NetworkCommunication/FRCComm.h>
#include <FRC_NetworkCommunication/NetCommRPCProxy_Occur.h>
#include <fmt/format.h>
#include <wpi/EventVector.h>
#include <wpi/SafeThread.h>
#include <wpi/SmallVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include "hal/DriverStation.h"
#include "hal/Errors.h"
static_assert(sizeof(int32_t) >= sizeof(int),
"FRC_NetworkComm status variable is larger than 32 bits");
@@ -27,25 +30,45 @@ struct HAL_JoystickAxesInt {
int16_t axes[HAL_kMaxJoystickAxes];
};
static constexpr int kJoystickPorts = 6;
namespace {
struct JoystickDataCache {
JoystickDataCache() { std::memset(this, 0, sizeof(*this)); }
void Update();
HAL_JoystickAxes axes[HAL_kMaxJoysticks];
HAL_JoystickPOVs povs[HAL_kMaxJoysticks];
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
HAL_AllianceStationID allianceStation;
float matchTime;
bool updated;
};
static_assert(std::is_standard_layout_v<JoystickDataCache>);
// static_assert(std::is_trivial_v<JoystickDataCache>);
struct FRCDriverStation {
wpi::EventVector newDataEvents;
};
} // namespace
static ::FRCDriverStation* driverStation;
// Message and Data variables
static wpi::mutex msgMutex;
static int32_t HAL_GetJoystickAxesInternal(int32_t joystickNum,
HAL_JoystickAxes* axes) {
HAL_JoystickAxesInt axesInt;
JoystickAxes_t netcommAxes;
int retVal = FRC_NetworkCommunication_getJoystickAxes(
joystickNum, reinterpret_cast<JoystickAxes_t*>(&axesInt),
HAL_kMaxJoystickAxes);
joystickNum, &netcommAxes, HAL_kMaxJoystickAxes);
// copy integer values to double values
axes->count = axesInt.count;
axes->count = netcommAxes.count;
// current scaling is -128 to 127, can easily be patched in the future by
// changing this function.
for (int32_t i = 0; i < axesInt.count; i++) {
int8_t value = axesInt.axes[i];
for (int32_t i = 0; i < netcommAxes.count; i++) {
int8_t value = netcommAxes.axes[i];
axes->raw[i] = value;
if (value < 0) {
axes->axes[i] = value / 128.0;
} else {
@@ -68,6 +91,30 @@ static int32_t HAL_GetJoystickButtonsInternal(int32_t joystickNum,
return FRC_NetworkCommunication_getJoystickButtons(
joystickNum, &buttons->buttons, &buttons->count);
}
void JoystickDataCache::Update() {
for (int i = 0; i < HAL_kMaxJoysticks; i++) {
HAL_GetJoystickAxesInternal(i, &axes[i]);
HAL_GetJoystickPOVsInternal(i, &povs[i]);
HAL_GetJoystickButtonsInternal(i, &buttons[i]);
}
FRC_NetworkCommunication_getAllianceStation(
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
FRC_NetworkCommunication_getMatchTime(&matchTime);
}
#define CHECK_JOYSTICK_NUMBER(stickNum) \
if ((stickNum) < 0 || (stickNum) >= HAL_kMaxJoysticks) \
return PARAMETER_OUT_OF_RANGE
static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentCache = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
static wpi::mutex cacheMutex;
/**
* Retrieve the Joystick Descriptor for particular slot.
*
@@ -102,12 +149,6 @@ static int32_t HAL_GetJoystickDescriptorInternal(int32_t joystickNum,
return retval;
}
static int32_t HAL_GetControlWordInternal(HAL_ControlWord* controlWord) {
std::memset(controlWord, 0, sizeof(HAL_ControlWord));
return FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(controlWord));
}
static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
MatchType_t matchType = MatchType_t::kMatchType_none;
info->gameSpecificMessageSize = sizeof(info->gameSpecificMessage);
@@ -126,16 +167,11 @@ static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
return status;
}
static wpi::mutex* newDSDataAvailableMutex;
static wpi::condition_variable* newDSDataAvailableCond;
static int newDSDataAvailableCounter{0};
namespace hal::init {
void InitializeFRCDriverStation() {
static wpi::mutex newMutex;
newDSDataAvailableMutex = &newMutex;
static wpi::condition_variable newCond;
newDSDataAvailableCond = &newCond;
std::memset(&newestControlWord, 0, sizeof(newestControlWord));
static FRCDriverStation ds;
driverStation = &ds;
}
} // namespace hal::init
@@ -248,20 +284,39 @@ int32_t HAL_SendConsoleLine(const char* line) {
}
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
return HAL_GetControlWordInternal(controlWord);
std::scoped_lock lock{cacheMutex};
*controlWord = newestControlWord;
return 0;
}
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
return HAL_GetJoystickAxesInternal(joystickNum, axes);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*axes = currentRead->axes[joystickNum];
return 0;
}
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
return HAL_GetJoystickPOVsInternal(joystickNum, povs);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*povs = currentRead->povs[joystickNum];
return 0;
}
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) {
return HAL_GetJoystickButtonsInternal(joystickNum, buttons);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*buttons = currentRead->buttons[joystickNum];
return 0;
}
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons) {
std::scoped_lock lock{cacheMutex};
std::memcpy(axes, currentRead->axes, sizeof(currentRead->axes));
std::memcpy(povs, currentRead->povs, sizeof(currentRead->povs));
std::memcpy(buttons, currentRead->buttons, sizeof(currentRead->buttons));
}
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
@@ -274,10 +329,8 @@ int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
}
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
HAL_AllianceStationID allianceStation;
*status = FRC_NetworkCommunication_getAllianceStation(
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
return allianceStation;
std::scoped_lock lock{cacheMutex};
return currentRead->allianceStation;
}
HAL_Bool HAL_GetJoystickIsXbox(int32_t joystickNum) {
@@ -317,6 +370,7 @@ void HAL_FreeJoystickName(char* name) {
}
int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
CHECK_JOYSTICK_NUMBER(joystickNum);
HAL_JoystickDescriptor joystickDesc;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
return -1;
@@ -327,14 +381,14 @@ int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
int32_t leftRumble, int32_t rightRumble) {
CHECK_JOYSTICK_NUMBER(joystickNum);
return FRC_NetworkCommunication_setJoystickOutputs(joystickNum, outputs,
leftRumble, rightRumble);
}
double HAL_GetMatchTime(int32_t* status) {
float matchTime;
*status = FRC_NetworkCommunication_getMatchTime(&matchTime);
return matchTime;
std::scoped_lock lock{cacheMutex};
return currentRead->matchTime;
}
void HAL_ObserveUserProgramStarting(void) {
@@ -357,55 +411,6 @@ void HAL_ObserveUserProgramTest(void) {
FRC_NetworkCommunication_observeUserProgramTest();
}
static int& GetThreadLocalLastCount() {
// There is a rollover error condition here. At Packet# = n * (uintmax), this
// will return false when instead it should return true. However, this at a
// 20ms rate occurs once every 2.7 years of DS connected runtime, so not
// worth the cycles to check.
thread_local int lastCount{0};
return lastCount;
}
HAL_Bool HAL_IsNewControlData(void) {
std::scoped_lock lock{*newDSDataAvailableMutex};
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount == currentCount) {
return false;
}
lastCount = currentCount;
return true;
}
void HAL_WaitForDSData(void) {
HAL_WaitForDSDataTimeout(0);
}
HAL_Bool HAL_WaitForDSDataTimeout(double timeout) {
std::unique_lock lock{*newDSDataAvailableMutex};
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount != currentCount) {
lastCount = currentCount;
return true;
}
auto timeoutTime =
std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
while (newDSDataAvailableCounter == currentCount) {
if (timeout > 0) {
auto timedOut = newDSDataAvailableCond->wait_until(lock, timeoutTime);
if (timedOut == std::cv_status::timeout) {
return false;
}
} else {
newDSDataAvailableCond->wait(lock);
}
}
lastCount = newDSDataAvailableCounter;
return true;
}
// Constant number to be used for our occur handle
constexpr int32_t refNumber = 42;
@@ -415,45 +420,47 @@ static void newDataOccur(uint32_t refNum) {
if (refNum != refNumber) {
return;
}
std::scoped_lock lock{*newDSDataAvailableMutex};
// Notify all threads
++newDSDataAvailableCounter;
newDSDataAvailableCond->notify_all();
cacheToUpdate->Update();
{
std::scoped_lock lock{cacheMutex};
std::swap(currentCache, cacheToUpdate);
currentCache->updated = true;
}
driverStation->newDataEvents.Wakeup();
}
/*
* Call this to initialize the driver station communication. This will properly
* handle multiple calls. However note that this CANNOT be called from a library
* that interfaces with LabVIEW.
*/
void HAL_InitializeDriverStation(void) {
static std::atomic_bool initialized{false};
static wpi::mutex initializeMutex;
// Initial check, as if it's true initialization has finished
if (initialized) {
return;
void HAL_RefreshDSData(void) {
HAL_ControlWord controlWord;
std::memset(&controlWord, 0, sizeof(controlWord));
FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(&controlWord));
std::scoped_lock lock{cacheMutex};
if (currentCache->updated) {
std::swap(currentCache, currentRead);
currentCache->updated = false;
}
newestControlWord = controlWord;
}
std::scoped_lock lock(initializeMutex);
// Second check in case another thread was waiting
if (initialized) {
return;
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
driverStation->newDataEvents.Add(handle);
}
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
driverStation->newDataEvents.Remove(handle);
}
HAL_Bool HAL_GetOutputsEnabled(void) {
return FRC_NetworkCommunication_getWatchdogActive();
}
} // extern "C"
namespace hal {
void InitializeDriverStation() {
// Set up the occur function internally with NetComm
NetCommRPCProxy_SetOccurFuncPointer(newDataOccur);
// Set up our occur reference number
setNewDataOccurRef(refNumber);
initialized = true;
}
/*
* Releases the DS Mutex to allow proper shutdown of any threads that are
* waiting on it.
*/
void HAL_ReleaseDSMutex(void) {
newDataOccur(refNumber);
}
} // extern "C"
} // namespace hal