// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "frc/Notifier.h" #include #include #include #include #include #include #include "frc/Errors.h" #include "frc/Timer.h" using namespace frc; Notifier::Notifier(std::function callback) { if (!callback) { throw FRC_MakeError(err::NullParameter, "callback"); } m_callback = callback; int32_t status = 0; m_notifier = HAL_InitializeNotifier(&status); FRC_CheckErrorStatus(status, "InitializeNotifier"); m_thread = std::thread([=, this] { for (;;) { int32_t status = 0; HAL_NotifierHandle notifier = m_notifier.load(); if (notifier == 0) { break; } uint64_t curTime = HAL_WaitForNotifierAlarm(notifier, &status); if (curTime == 0 || status != 0) { break; } std::function callback; { std::scoped_lock lock(m_processMutex); callback = m_callback; if (m_periodic) { m_expirationTime += m_period; UpdateAlarm(); } else { // Need to update the alarm to cause it to wait again UpdateAlarm(UINT64_MAX); } } // Call callback if (callback) { callback(); } } }); } Notifier::Notifier(int priority, std::function callback) { if (!callback) { throw FRC_MakeError(err::NullParameter, "callback"); } m_callback = callback; int32_t status = 0; m_notifier = HAL_InitializeNotifier(&status); FRC_CheckErrorStatus(status, "InitializeNotifier"); m_thread = std::thread([=, this] { int32_t status = 0; HAL_SetCurrentThreadPriority(true, priority, &status); for (;;) { HAL_NotifierHandle notifier = m_notifier.load(); if (notifier == 0) { break; } uint64_t curTime = HAL_WaitForNotifierAlarm(notifier, &status); if (curTime == 0 || status != 0) { break; } std::function callback; { std::scoped_lock lock(m_processMutex); callback = m_callback; if (m_periodic) { m_expirationTime += m_period; UpdateAlarm(); } else { // need to update the alarm to cause it to wait again UpdateAlarm(UINT64_MAX); } } // call callback if (callback) { try { callback(); } catch (const frc::RuntimeError& e) { e.Report(); FRC_ReportError( err::Error, "Error in Notifier thread." " The above stacktrace can help determine where the error " "occurred.\n" " See https://wpilib.org/stacktrace for more information.\n"); throw; } catch (const std::exception& e) { HAL_SendError(1, err::Error, 0, e.what(), "", "", 1); throw; } } } }); } Notifier::~Notifier() { int32_t status = 0; // atomically set handle to 0, then clean HAL_NotifierHandle handle = m_notifier.exchange(0); HAL_StopNotifier(handle, &status); FRC_ReportError(status, "StopNotifier"); // Join the thread to ensure the callback has exited. if (m_thread.joinable()) { m_thread.join(); } HAL_CleanNotifier(handle, &status); } Notifier::Notifier(Notifier&& rhs) : m_thread(std::move(rhs.m_thread)), m_notifier(rhs.m_notifier.load()), m_callback(std::move(rhs.m_callback)), m_expirationTime(std::move(rhs.m_expirationTime)), m_period(std::move(rhs.m_period)), m_periodic(std::move(rhs.m_periodic)) { rhs.m_notifier = HAL_kInvalidHandle; } Notifier& Notifier::operator=(Notifier&& rhs) { m_thread = std::move(rhs.m_thread); m_notifier = rhs.m_notifier.load(); rhs.m_notifier = HAL_kInvalidHandle; m_callback = std::move(rhs.m_callback); m_expirationTime = std::move(rhs.m_expirationTime); m_period = std::move(rhs.m_period); m_periodic = std::move(rhs.m_periodic); return *this; } void Notifier::SetName(std::string_view name) { fmt::memory_buffer buf; fmt::format_to(fmt::appender{buf}, "{}", name); buf.push_back('\0'); // null terminate int32_t status = 0; HAL_SetNotifierName(m_notifier, buf.data(), &status); } void Notifier::SetHandler(std::function callback) { std::scoped_lock lock(m_processMutex); m_callback = callback; } void Notifier::SetCallback(std::function callback) { std::scoped_lock lock(m_processMutex); m_callback = callback; } void Notifier::StartSingle(units::second_t delay) { std::scoped_lock lock(m_processMutex); m_periodic = false; m_period = delay; m_expirationTime = Timer::GetFPGATimestamp() + m_period; UpdateAlarm(); } void Notifier::StartPeriodic(units::second_t period) { std::scoped_lock lock(m_processMutex); m_periodic = true; m_period = period; m_expirationTime = Timer::GetFPGATimestamp() + m_period; UpdateAlarm(); } void Notifier::Stop() { std::scoped_lock lock(m_processMutex); m_periodic = false; int32_t status = 0; HAL_CancelNotifierAlarm(m_notifier, &status); FRC_CheckErrorStatus(status, "CancelNotifierAlarm"); } void Notifier::UpdateAlarm(uint64_t triggerTime) { int32_t status = 0; // Return if we are being destructed, or were not created successfully auto notifier = m_notifier.load(); if (notifier == 0) { return; } HAL_UpdateNotifierAlarm(notifier, triggerTime, &status); FRC_CheckErrorStatus(status, "UpdateNotifierAlarm"); } void Notifier::UpdateAlarm() { UpdateAlarm(static_cast(m_expirationTime * 1e6)); } bool Notifier::SetHALThreadPriority(bool realTime, int32_t priority) { int32_t status = 0; return HAL_SetNotifierThreadPriority(realTime, priority, &status); }