CTRE Phoenix 6 C++ 24.3.0
StatusSignal.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) Cross The Road Electronics.  All rights reserved.
3 * License information can be found in CTRE_LICENSE.txt
4 * For support and suggestions contact support@ctr-electronics.com or file
5 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases
6 */
7#pragma once
8
15#include <array>
16#include <functional>
17#include <initializer_list>
18#include <map>
19#include <ostream>
20#include <sstream>
21#include <string>
22#include <units/base.h>
23#include <units/frequency.h>
24#include <units/time.h>
25
26namespace ctre {
27namespace phoenix6 {
28
29 namespace hardware {
30 class ParentDevice;
31 }
32
33 template <typename T>
34 class StatusSignal;
35
36 /**
37 * \brief Class that provides operations to
38 * retrieve information about a status signal.
39 */
41 {
42 friend hardware::ParentDevice; // Allow ParentDevice to set error
43
44 protected:
46 uint16_t spn;
47 std::string units;
49 double baseValue = 0;
51 std::string signalName;
52
53 std::function<void()> _checkFirmVersFunction;
54
55 units::time::second_t _lastTimestamp{0_s};
56
57 template <typename T>
58 friend class StatusSignal;
59
60 void CopyFrom(const BaseStatusSignal &other)
61 {
62 this->units = other.units;
63 this->timestamps = other.timestamps;
64 this->baseValue = other.baseValue;
65 this->error = other.error;
66 // We don't care about copying the signal name, because the user will expect the original name
67 }
68
70 uint16_t spn,
71 std::string signalName,
72 std::function<void()> checkFirmVersFunction) :
74 spn{spn},
76 signalName{std::move(signalName)},
77 _checkFirmVersFunction{std::move(checkFirmVersFunction)}
78 {
79 }
80
81 /* Constructor for an invalid BaseStatusSignal */
82 BaseStatusSignal(ctre::phoenix::StatusCode error) :
83 deviceIdentifier{hardware::DeviceIdentifier{}},
84 spn{0},
85 error{error},
86 signalName{"Invalid"},
88 {
89 }
90
91 /**
92 * \brief Wait for multiple signals to arrive
93 *
94 * \param signals Signals to wait for
95 * \param count Number of signals
96 * \param network Network to wait for the signals on
97 * \param timeoutSeconds Maximum time to wait for all these signals
98 * \returns Status of the wait
99 */
100 static ctre::phoenix::StatusCode Status_WaitForAll(
101 BaseStatusSignal* const *signals,
102 size_t count,
103 const char *network,
104 double timeoutSeconds);
105
106 static ctre::phoenix::StatusCode Status_SetUpdateFrequency(const char *canbus, uint32_t deviceHash, uint16_t spn, double frequencyHz, double timeoutSeconds);
107 static ctre::phoenix::StatusCode Status_SetUpdateFrequencyForAll(BaseStatusSignal* const *signals, size_t count, double frequencyHz, double timeoutSeconds);
108 static double Status_GetAppliedUpdateFrequency(const char *canbus, uint32_t deviceHash, uint16_t spn);
109
110 /**
111 * Gets signal units.
112 *
113 * \param signal Signal to get
114 * \returns Units of the signal
115 */
116 static std::string Status_GetUnits(uint32_t signal);
117
118 /**
119 * Get signal update. Caller either get last received, or wait for an update.
120 *
121 * \param network Name of bus (can, canfd, canivore-name, future protocols)
122 * \param deviceHash Hash id of the device (based on device id and model)
123 * \param signal Signal to get
124 * \param bWaitForUpdate If true, API will wait up to timeoutSeconds for an update. If false, routine will poll last received and use timeoutSeconds to return error code if too old.
125 * \param timeoutSeconds How long to wait or how old the signal can be before error'ing
126 * \param outValue Value of the signal
127 * \param hwtimestamp Timestamp of the signal
128 * \param swtimestamp Timestamp of the signal
129 * \param ecutimestamp Timestamp of the signal
130 * \returns Status of the get
131 */
132 static ctre::phoenix::StatusCode Status_Get(
133 const char *network,
134 int deviceHash,
135 uint32_t signal,
136 bool bWaitForUpdate, double timeoutSeconds,
137 double *outValue,
138 double *hwtimestamp, double *swtimestamp, double *ecutimestamp);
139
140 /**
141 * \brief Implementation of the WaitForAll API.
142 *
143 * \tparam Arr \c std::array or \c std::vector of BaseStatusSignal*
144 * \param location Location of the calling function
145 * \param timeoutSeconds Maximum time to wait for all the signals
146 * \param signals Signals to wait on
147 * \return Status of the wait
148 */
149 template <typename Arr>
150 static ctre::phoenix::StatusCode WaitForAllImpl(const char *location, units::time::second_t timeoutSeconds, const Arr &signals)
151 {
152 if (signals.size() < 1)
153 {
154 /* We don't have any signals to wait for, so return early */
155 ctre::phoenix::StatusCode retval = ctre::phoenix::StatusCode::InvalidParamValue;
156 c_ctre_phoenix_report_error(true, retval, 0,
157 retval.GetDescription(),
158 location,
160 return retval;
161 }
162 const std::string &network = signals[0]->deviceIdentifier.network;
163
164 for (auto signal : signals)
165 {
166 /* Check that they all have the same network */
167 if (network != signal->deviceIdentifier.network)
168 {
169 ctre::phoenix::StatusCode retval = ctre::phoenix::StatusCode::InvalidNetwork; // Networks don't match, return early
170
171 c_ctre_phoenix_report_error(true, retval, 0,
172 retval.GetDescription(),
173 location,
175 return retval;
176 }
177 }
178
179 /* Report if any device firmware versions are too old */
180 for (auto signal : signals)
181 {
182 signal->_checkFirmVersFunction();
183 }
184
185 /* Now wait for all the signals */
186 ctre::phoenix::StatusCode retval = Status_WaitForAll(signals.data(), signals.size(), network.c_str(), timeoutSeconds.to<double>());
187
188 /* error reporting */
189 if (false == retval.IsOK())
190 {
191 c_ctre_phoenix_report_error(retval.IsError(), retval, 0,
192 retval.GetDescription(),
193 location,
195 }
196 return retval;
197 }
198
199 /**
200 * \brief Type trait to verify that all types passed in are subclasses of BaseStatusSignal.
201 */
202 template <typename... Signals>
204 std::conjunction<std::is_base_of<BaseStatusSignal, std::remove_reference_t<Signals>>...>
205 {};
206
207 /**
208 * \brief Whether all types passed in are subclasses of BaseStatusSignal.
209 */
210 template <typename... Signals>
211 static constexpr bool is_all_status_signal_v = is_all_status_signal<Signals...>::value;
212
213 public:
214 virtual ~BaseStatusSignal() = 0; // Declare virtual destructor to make this class abstract
215
216 /**
217 * \brief Waits for new data on all provided signals up to timeout.
218 * This API is typically used with CANivore Bus signals as they will be synced using the
219 * CANivore Timesync feature and arrive simultaneously. Signals on a roboRIO bus cannot
220 * be synced and may require a significantly longer blocking call to receive all signals.
221 *
222 * Note that CANivore Timesync requires Phoenix Pro.
223 *
224 * This can also be used with a timeout of zero to refresh many signals at once, which
225 * is faster than calling Refresh() on every signal. This is equivalent to calling #RefreshAll.
226 *
227 * \param timeoutSeconds Maximum time to wait for all the signals to arrive.
228 * Pass zero to refresh all signals without blocking.
229 * \param signals Signals to wait on, passed as a comma-separated list of signal references.
230 * \return An InvalidParamValue if signals array is empty,
231 * InvalidNetwork if signals are on different CAN bus networks,
232 * RxTimeout if it took longer than timeoutSeconds to receive all the signals,
233 * MultiSignalNotSupported if using the roboRIO bus with more than one signal and a non-zero timeout.
234 * An OK status code means that all signals arrived within timeoutSeconds and they are all OK.
235 *
236 * Any other value represents the StatusCode of the first failed signal.
237 * Call GetStatus() on each signal to determine which ones failed.
238 */
239 template <typename... Signals, typename = std::enable_if_t<is_all_status_signal_v<Signals...>>>
240 static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, Signals &... signals)
241 {
242 return WaitForAll(timeoutSeconds,
243 std::array<BaseStatusSignal *, sizeof...(Signals)>{(&signals)...});
244 }
245 /**
246 * \brief Waits for new data on all provided signals up to timeout.
247 * This API is typically used with CANivore Bus signals as they will be synced using the
248 * CANivore Timesync feature and arrive simultaneously. Signals on a roboRIO bus cannot
249 * be synced and may require a significantly longer blocking call to receive all signals.
250 *
251 * Note that CANivore Timesync requires Phoenix Pro.
252 *
253 * This can also be used with a timeout of zero to refresh many signals at once, which
254 * is faster than calling Refresh() on every signal. This is equivalent to calling #RefreshAll.
255 *
256 * \param timeoutSeconds Maximum time to wait for all the signals to arrive.
257 * Pass zero to refresh all signals without blocking.
258 * \param signals Signals to wait on, passed as a vector or initializer list of signal addresses.
259 * \return An InvalidParamValue if signals array is empty,
260 * InvalidNetwork if signals are on different CAN bus networks,
261 * RxTimeout if it took longer than timeoutSeconds to receive all the signals,
262 * MultiSignalNotSupported if using the roboRIO bus with more than one signal and a non-zero timeout.
263 * An OK status code means that all signals arrived within timeoutSeconds and they are all OK.
264 *
265 * Any other value represents the StatusCode of the first failed signal.
266 * Call GetStatus() on each signal to determine which ones failed.
267 */
268 static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, const std::vector<BaseStatusSignal *> &signals)
269 {
270 /* static string for location */
271 constexpr char kLocation[] = "ctre::phoenix6::BaseStatusSignal::WaitForAll";
272 return WaitForAllImpl(kLocation, timeoutSeconds, signals);
273 }
274 /**
275 * \brief Waits for new data on all provided signals up to timeout.
276 * This API is typically used with CANivore Bus signals as they will be synced using the
277 * CANivore Timesync feature and arrive simultaneously. Signals on a roboRIO bus cannot
278 * be synced and may require a significantly longer blocking call to receive all signals.
279 *
280 * Note that CANivore Timesync requires Phoenix Pro.
281 *
282 * This can also be used with a timeout of zero to refresh many signals at once, which
283 * is faster than calling Refresh() on every signal. This is equivalent to calling #RefreshAll.
284 *
285 * \param timeoutSeconds Maximum time to wait for all the signals to arrive.
286 * Pass zero to refresh all signals without blocking.
287 * \param signals Signals to wait on, passed as an array of signal addresses.
288 * \return An InvalidParamValue if signals array is empty,
289 * InvalidNetwork if signals are on different CAN bus networks,
290 * RxTimeout if it took longer than timeoutSeconds to receive all the signals,
291 * MultiSignalNotSupported if using the roboRIO bus with more than one signal and a non-zero timeout.
292 * An OK status code means that all signals arrived within timeoutSeconds and they are all OK.
293 *
294 * Any other value represents the StatusCode of the first failed signal.
295 * Call GetStatus() on each signal to determine which ones failed.
296 */
297 template <size_t N>
298 static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, const std::array<BaseStatusSignal *, N> &signals)
299 {
300 /* static string for location */
301 constexpr char kLocation[] = "ctre::phoenix6::BaseStatusSignal::WaitForAll";
302 return WaitForAllImpl(kLocation, timeoutSeconds, signals);
303 }
304
305 /**
306 * \brief Performs a non-blocking refresh on all provided signals.
307 *
308 * This provides a performance improvement over separately calling Refresh() on each signal.
309 *
310 * \param signals Signals to refresh, passed as a comma-separated list of signal references.
311 * \return An InvalidParamValue if signals array is empty,
312 * InvalidNetwork if signals are on different CAN bus networks.
313 * An OK status code means that all signals are OK.
314 *
315 * Any other value represents the StatusCode of the first failed signal.
316 * Call GetStatus() on each signal to determine which ones failed.
317 */
318 template <typename... Signals, typename = std::enable_if_t<is_all_status_signal_v<Signals...>>>
319 static ctre::phoenix::StatusCode RefreshAll(Signals &... signals)
320 {
321 return RefreshAll(std::array<BaseStatusSignal *, sizeof...(Signals)>{(&signals)...});
322 }
323 /**
324 * \brief Performs a non-blocking refresh on all provided signals.
325 *
326 * This provides a performance improvement over separately calling Refresh() on each signal.
327 *
328 * \param signals Signals to refresh, passed as a vector or initializer list of signal addresses.
329 * \return An InvalidParamValue if signals array is empty,
330 * InvalidNetwork if signals are on different CAN bus networks.
331 * An OK status code means that all signals are OK.
332 *
333 * Any other value represents the StatusCode of the first failed signal.
334 * Call GetStatus() on each signal to determine which ones failed.
335 */
336 static ctre::phoenix::StatusCode RefreshAll(const std::vector<BaseStatusSignal *> &signals)
337 {
338 /* static string for location */
339 constexpr char kLocation[] = "ctre::phoenix6::BaseStatusSignal::RefreshAll";
340 return WaitForAllImpl(kLocation, 0_s, signals);
341 }
342 /**
343 * \brief Performs a non-blocking refresh on all provided signals.
344 *
345 * This provides a performance improvement over separately calling Refresh() on each signal.
346 *
347 * \param signals Signals to refresh, passed as an array of signal addresses.
348 * \return An InvalidParamValue if signals array is empty,
349 * InvalidNetwork if signals are on different CAN bus networks.
350 * An OK status code means that all signals are OK.
351 *
352 * Any other value represents the StatusCode of the first failed signal.
353 * Call GetStatus() on each signal to determine which ones failed.
354 */
355 template <size_t N>
356 static ctre::phoenix::StatusCode RefreshAll(const std::array<BaseStatusSignal *, N> &signals)
357 {
358 /* static string for location */
359 constexpr char kLocation[] = "ctre::phoenix6::BaseStatusSignal::RefreshAll";
360 return WaitForAllImpl(kLocation, 0_s, signals);
361 }
362
363 /**
364 * \brief Performs latency compensation on signal using the signalSlope and signal's latency to determine
365 * the magnitude of compensation. The caller must refresh these StatusSignals beforehand;
366 * this function only does the math required for latency compensation.
367 *
368 * \details Example usage:
369 * \code
370 * units::turn_t compensatedTurns = BaseStatusSignal::GetLatencyCompensatedValue(fx.GetPosition(), fx.GetVelocity());
371 * \endcode
372 *
373 * \tparam U Type of signal's underlying type. This is the type that the function will return.
374 * \tparam U_PER_SEC Type of signalSlope's underlying type. This must be the derivative of U.
375 * \param signal Signal to be latency compensated. Caller must make sure this signal is up to date
376 * either by calling \c Refresh() or \c WaitForUpdate().
377 * \param signalSlope Derivative of signal that informs compensation magnitude. Caller must make sure this
378 * signal is up to date either by calling \c Refresh() or \c WaitForUpdate().
379 * \param maxLatencySeconds The maximum amount of latency to compensate for in seconds. A negative or zero
380 * value disables the max latency cap. This is used to cap the contribution of
381 * latency compensation for stale signals, such as after the device has been
382 * disconnected from the CAN bus.
383 * \returns Latency compensated value from the signal StatusSignal.
384 */
385 template <typename U, typename U_PER_SEC>
386 static U GetLatencyCompensatedValue(StatusSignal<U> &signal, StatusSignal<U_PER_SEC> &signalSlope, units::time::second_t maxLatencySeconds = 0.300_s)
387 {
388 static_assert(units::traits::is_unit_t_v<U>, "signal must be a unit signal");
389 static_assert(units::traits::is_unit_t_v<U_PER_SEC>, "signalSlope must be a unit signal");
390 using lhs = typename units::traits::unit_t_traits<U>::unit_type;
391 using rhs = typename units::traits::unit_t_traits<U_PER_SEC>::unit_type;
392 static_assert(units::traits::is_convertible_unit_v<lhs, units::compound_unit<rhs, units::seconds>>,
393 "Compensation can only be performed on a signal with its derivative");
394
395 const U nonCompensatedSignal = signal.GetValue();
396 const U_PER_SEC changeInSignal = signalSlope.GetValue();
397 units::second_t latency = signal.GetTimestamp().GetLatency();
398 if (maxLatencySeconds > 0_s && latency > maxLatencySeconds) {
399 latency = maxLatencySeconds;
400 }
401 return nonCompensatedSignal + (changeInSignal * latency);
402 }
403
404 /**
405 * \brief Checks if all signals have an OK error code.
406 *
407 * \param signals Signals to check error code of, passed as a comma-separated list of signal references.
408 * \returns True if all signals are OK, false otherwise
409 */
410 template <typename... Signals, typename = std::enable_if_t<is_all_status_signal_v<Signals...>>>
411 static bool IsAllGood(Signals &... signals)
412 {
413 return IsAllGood(std::array<BaseStatusSignal *, sizeof...(Signals)>{(&signals)...});
414 }
415 /**
416 * \brief Checks if all signals have an OK error code.
417 *
418 * \param signals Signals to check error code of, passed as a vector or initializer list of signal addresses.
419 * \returns True if all signals are OK, false otherwise
420 */
421 static bool IsAllGood(const std::vector<BaseStatusSignal *> &signals)
422 {
423 for (auto signal : signals)
424 {
425 if (!signal->GetStatus().IsOK()) {
426 return false;
427 }
428 }
429 return true;
430 }
431 /**
432 * \brief Checks if all signals have an OK error code.
433 *
434 * \param signals Signals to check error code of, passed as an array of signal addresses.
435 * \returns True if all signals are OK, false otherwise
436 */
437 template <size_t N>
438 static bool IsAllGood(const std::array<BaseStatusSignal *, N> &signals)
439 {
440 for (auto signal : signals)
441 {
442 if (!signal->GetStatus().IsOK()) {
443 return false;
444 }
445 }
446 return true;
447 }
448
449 /**
450 * \brief Sets the update frequency of all specified status signals to the provided common frequency.
451 *
452 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal frequency
453 * is 4 Hz, and the maximum is 1000 Hz.
454 *
455 * If other StatusSignals in the same status frame have been set to an update frequency,
456 * the fastest requested update frequency will be applied to the frame.
457 *
458 * This will wait up to 0.050 seconds (50ms) for each signal.
459 *
460 * \param frequencyHz Rate to publish the signal in Hz.
461 * \param signals Signals to apply the update frequency to, passed as a comma-separated list of signal references.
462 * \returns Status code of the first failed update frequency set call, or OK if all succeeded
463 */
464 template <typename... Signals, typename = std::enable_if_t<is_all_status_signal_v<Signals...>>>
465 static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, Signals &... signals)
466 {
467 return SetUpdateFrequencyForAll(frequencyHz, std::array<BaseStatusSignal *, sizeof...(Signals)>{(&signals)...});
468 }
469 /**
470 * \brief Sets the update frequency of all specified status signals to the provided common frequency.
471 *
472 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal frequency
473 * is 4 Hz, and the maximum is 1000 Hz.
474 *
475 * If other StatusSignals in the same status frame have been set to an update frequency,
476 * the fastest requested update frequency will be applied to the frame.
477 *
478 * This will wait up to 0.050 seconds (50ms) for each signal.
479 *
480 * \param frequencyHz Rate to publish the signal in Hz.
481 * \param signals Signals to apply the update frequency to, passed as a vector or initializer list of signal addresses.
482 * \returns Status code of the first failed update frequency set call, or OK if all succeeded
483 */
484 static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, const std::vector<BaseStatusSignal *> &signals)
485 {
486 return Status_SetUpdateFrequencyForAll(signals.data(), signals.size(), frequencyHz.to<double>(), 0.050);
487 }
488 /**
489 * \brief Sets the update frequency of all specified status signals to the provided common frequency.
490 *
491 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal frequency
492 * is 4 Hz, and the maximum is 1000 Hz.
493 *
494 * If other StatusSignals in the same status frame have been set to an update frequency,
495 * the fastest requested update frequency will be applied to the frame.
496 *
497 * This will wait up to 0.050 seconds (50ms) for each signal.
498 *
499 * \param frequencyHz Rate to publish the signal in Hz.
500 * \param signals Signals to apply the update frequency to, passed as an array of signal addresses.
501 * \returns Status code of the first failed update frequency set call, or OK if all succeeded
502 */
503 template <size_t N>
504 static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, const std::array<BaseStatusSignal *, N> &signals)
505 {
506 return Status_SetUpdateFrequencyForAll(signals.data(), signals.size(), frequencyHz.to<double>(), 0.050);
507 }
508
509 /**
510 * \brief Sets the rate at which the device will publish this signal.
511 *
512 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal
513 * frequency is 4 Hz, and the maximum is 1000 Hz.
514 *
515 * If other StatusSignals in the same status frame have been set to an update frequency,
516 * the fastest requested update frequency will be applied to the frame.
517 *
518 * \param frequencyHz Rate to publish the signal in Hz.
519 * \param timeoutSeconds Maximum amount of time to wait when performing the action
520 * \returns Status code of setting the update frequency
521 */
522 virtual ctre::phoenix::StatusCode SetUpdateFrequency(units::frequency::hertz_t frequencyHz, units::time::second_t timeoutSeconds = 50_ms) = 0;
523
524 /**
525 * \brief Gets the rate at which the device will publish this signal.
526 *
527 * This is typically the last value passed into #SetUpdateFrequency. The returned value
528 * may be higher if another StatusSignal in the same status frame has been set to a higher
529 * update frequency.
530 *
531 * \returns Applied update frequency of the signal in Hz
532 */
533 virtual units::frequency::hertz_t GetAppliedUpdateFrequency() const = 0;
534
535 /**
536 * \brief Gets the name of this signal
537 *
538 * \returns Name of this signal
539 */
540 const std::string &GetName() const { return signalName; }
541 /**
542 * \brief Gets the units for this signal
543 *
544 * \returns String representation of units for this signal
545 */
546 const std::string &GetUnits() const { return units; }
547 /**
548 * \brief Gets the value of this signal as a double
549 *
550 * \return Value of this signal as a double instead of the generic type
551 */
552 double GetValueAsDouble() const { return baseValue; }
553 /**
554 * \brief Get the timestamps of this signals
555 *
556 * \returns All timestamps for this signal
557 */
558 const AllTimestamps &GetAllTimestamps() const { return timestamps; }
559 /**
560 * \brief Get the best timestamp available to this signal
561 *
562 * \returns Best available timestamp for this signal
563 */
564 const Timestamp &GetTimestamp() const { return timestamps.GetBestTimestamp(); }
565 /**
566 * \brief Get the error code from when we last received this signal
567 *
568 * \returns Last cached Error Code
569 */
570 ctre::phoenix::StatusCode GetStatus() const { return error; }
571
572 /**
573 * \brief Check whether the signal has been updated since the last check.
574 *
575 * Note that the signal must be refreshed before calling this routine.
576 *
577 * \returns true if the signal has updated since the previous call of this routine
578 */
580 {
581 bool retval = false;
582 /* did we receive an update */
583 auto const &timestamp = GetAllTimestamps().GetSystemTimestamp();
584 if (timestamp.IsValid())
585 {
586 /* if the update timestamp is new, then a new frame was sent */
587 if (_lastTimestamp != timestamp.GetTime())
588 {
589 _lastTimestamp = timestamp.GetTime();
590 retval = true;
591 }
592 }
593 return retval;
594 }
595 };
596
597 /**
598 * Information from a single measurement of a status signal.
599 */
600 template <typename L>
602 {
603 /**
604 * \brief The value of the signal, this may be an enum so it is stored as a string
605 */
607 /**
608 * \brief Timestamp of when the data point was taken
609 */
610 units::time::second_t timestamp;
611 /**
612 * \brief Status code response of getting the data
613 */
614 ctre::phoenix::StatusCode status;
615 /**
616 * \brief Units that correspond to this point
617 */
618 std::string units;
619 };
620
621 /**
622 * \brief Represents a status signal with data of type T,
623 * and operations available to retrieve information about
624 * the signal.
625 */
626 template <typename T>
628 {
629 bool _containsUnderlyingTypes;
630 std::map<int, StatusSignal<T>> _basicTypeMap;
631
632 void RefreshSwitch(bool block, units::time::second_t timeout)
633 {
634 /* Just make sure we don't do anything if we're not a switching statement */
635 if (!_containsUnderlyingTypes)
636 return;
637
638 double switchValue;
639 /* We only care about the switch value, so get that quickly */
640 ctre::phoenix::StatusCode outputError = Status_Get(
641 this->deviceIdentifier.network.c_str(),
642 this->deviceIdentifier.deviceHash,
643 this->spn,
644 block, timeout.to<double>(),
645 &switchValue,
646 nullptr, nullptr, nullptr);
647
648 if (outputError != ctre::phoenix::StatusCode::OK)
649 {
650 this->error = outputError;
651 return;
652 }
653
654 auto foundValue = _basicTypeMap.find(static_cast<int>(switchValue));
655 if (foundValue != _basicTypeMap.end())
656 {
657 /* Found it, so refresh it and set our values to it */
658 foundValue->second.RefreshValue(block, timeout, false); // Top-level refresh will handle the error
659 CopyFrom(foundValue->second);
660 }
661 else
662 {
663 /* Didn't find it, so set our error */
665 }
666 }
667
668 void RefreshNoSwitch(bool block, units::time::second_t timeout)
669 {
670 /* Just make sure we don't do anything if we are a switching statement */
671 if (_containsUnderlyingTypes)
672 return;
673
674 /* fetch values */
675 double outHwTimestamp;
676 double outSwTimestamp;
677 double outEcuTimestamp;
678
679 ctre::phoenix::StatusCode outputError = Status_Get(
680 this->deviceIdentifier.network.c_str(),
681 this->deviceIdentifier.deviceHash,
682 this->spn,
683 block, timeout.to<double>(),
684 &this->baseValue,
685 &outHwTimestamp, &outSwTimestamp, &outEcuTimestamp);
686
687 /* save to members, use locks if need be (remember though Java locks are not reliable) */
688 if (outputError >= 0)
689 {
690 this->timestamps.Update(Timestamp{units::time::second_t{outSwTimestamp}, Timestamp::TimestampSource::System},
691 Timestamp{units::time::second_t{outHwTimestamp}, Timestamp::TimestampSource::CANivore},
692 Timestamp{units::time::second_t{outEcuTimestamp}, Timestamp::TimestampSource::Device, outEcuTimestamp != 0.0});
693 }
694 this->error = outputError;
695 }
696
697 void RefreshValue(bool block, units::time::second_t timeout, bool ReportOnError)
698 {
699 _checkFirmVersFunction();
700 if (_containsUnderlyingTypes)
701 {
702 RefreshSwitch(block, timeout);
703 }
704 else
705 {
706 RefreshNoSwitch(block, timeout);
707 }
708
709 if (ReportOnError && !(this->error.IsOK()))
710 {
711 std::stringstream location;
712 location << this->deviceIdentifier.ToString() << " Status Signal " << this->signalName;
713 c_ctre_phoenix_report_error(this->error.IsError(), this->error, 0, this->error.GetDescription(), location.str().c_str(), ctre::phoenix::platform::GetStackTrace(1).c_str());
714 }
715 }
716
718
719 StatusSignal(const BaseStatusSignal &other) : BaseStatusSignal{other.deviceIdentifier, other.spn, other.signalName, [] {}},
720 _containsUnderlyingTypes{false},
721 _basicTypeMap{}
722 {
723 CopyFrom(other);
724 }
725
726 StatusSignal(hardware::DeviceIdentifier deviceIdentifier,
727 uint16_t spn,
728 std::function<void()> checkFirmVersFunction,
729 std::string signalName) : BaseStatusSignal{std::move(deviceIdentifier), spn, std::move(signalName), std::move(checkFirmVersFunction)},
730 _containsUnderlyingTypes{false},
731 _basicTypeMap{}
732 {
733 }
734
735 StatusSignal(hardware::DeviceIdentifier deviceIdentifier,
736 uint16_t spn,
737 std::function<void()> checkFirmVersFunction,
738 std::function<std::map<int, StatusSignal<T>>()> map_filler,
739 std::string signalName) : BaseStatusSignal{std::move(deviceIdentifier), spn, std::move(signalName), std::move(checkFirmVersFunction)},
740 _containsUnderlyingTypes{true},
741 _basicTypeMap{map_filler()}
742 {
743 }
744
745 /* Constructor for an invalid StatusSignal */
746 StatusSignal(ctre::phoenix::StatusCode error) : BaseStatusSignal{error},
747 _containsUnderlyingTypes{false},
748 _basicTypeMap{}
749 {
750 }
751
752 public:
753 friend std::ostream &operator<<(std::ostream &os, const StatusSignal<T> &data)
754 {
755 /* Units may contain UTF-8 characters */
757
758 if constexpr(units::traits::is_unit_t_v<T>)
759 {
760 os << data.GetValue().value() << " " << data.GetUnits();
761 }
762 else
763 {
764 os << data.GetValue() << " " << data.GetUnits();
765 }
766 return os;
767 }
768 std::string ToString() const
769 {
770 std::stringstream ss;
771 ss << *this;
772 return ss.str();
773 }
774
775 /**
776 * \brief Gets the cached value from this status signal.
777 *
778 * \details Gets the cached value. To make sure the value is up-to-date
779 * call \c Refresh() or \c WaitForUpdate()
780 *
781 * \returns Cached value
782 */
783 T GetValue() const
784 {
785 if constexpr(units::traits::is_unit_t_v<T>)
786 {
787 return units::make_unit<T>(this->baseValue);
788 }
789 else
790 {
791 return static_cast<T>(this->baseValue);
792 }
793 }
794
795 /**
796 * \brief Refreshes the value of this status signal.
797 *
798 * If the user application caches this StatusSignal object
799 * instead of periodically fetching it from the hardware device,
800 * this function must be called to fetch fresh data.
801 *
802 * \details This performs a non-blocking refresh operation. If
803 * you want to wait until you receive the signal, call
804 * \c WaitForUpdate() instead.
805 *
806 * \param ReportOnError Whether to report any errors to the Driver Station/stderr.
807 *
808 * \returns Reference to itself
809 */
810 StatusSignal<T> &Refresh(bool ReportOnError = true)
811 {
812 RefreshValue(false, 0_s, ReportOnError); // Don't block and error if signal is older than a default timeout
813 return *this;
814 }
815 /**
816 * \brief Waits up to timeoutSec to get the up-to-date status signal value.
817 *
818 * \details This performs a blocking refresh operation. If
819 * you want to non-blocking refresh the signal, call
820 * \c Refresh() instead.
821 *
822 * \param timeoutSec Maximum time to wait for the signal to update
823 * \param ReportOnError Whether to report any errors to the Driver Station/stderr.
824 *
825 * \returns Reference to itself
826 */
827 StatusSignal<T> &WaitForUpdate(units::time::second_t timeoutSec, bool ReportOnError = true)
828 {
829 RefreshValue(true, timeoutSec, ReportOnError);
830 return *this;
831 }
832
833 /**
834 * \brief Sets the rate at which the device will publish this signal.
835 *
836 * A frequency of 0 Hz will turn off the signal. Otherwise, the minimum supported signal
837 * frequency is 4 Hz, and the maximum is 1000 Hz.
838 *
839 * If other StatusSignals in the same status frame have been set to an update frequency,
840 * the fastest requested update frequency will be applied to the frame.
841 *
842 * \param frequencyHz Rate to publish the signal in Hz.
843 * \param timeoutSeconds Maximum amount of time to wait when performing the action
844 * \returns Status code of setting the update frequency
845 */
846 ctre::phoenix::StatusCode SetUpdateFrequency(units::frequency::hertz_t frequencyHz, units::time::second_t timeoutSeconds = 50_ms) override
847 {
848 if (_containsUnderlyingTypes)
849 {
850 return _basicTypeMap.begin()->second.SetUpdateFrequency(frequencyHz, timeoutSeconds);
851 }
852 else
853 {
854 return Status_SetUpdateFrequency(this->deviceIdentifier.network.c_str(),
855 this->deviceIdentifier.deviceHash,
856 this->spn,
857 frequencyHz.to<double>(),
858 timeoutSeconds.to<double>());
859 }
860 }
861
862 /**
863 * \brief Gets the rate at which the device will publish this signal.
864 *
865 * This is typically the last value passed into #SetUpdateFrequency. The returned value
866 * may be higher if another StatusSignal in the same status frame has been set to a higher
867 * update frequency.
868 *
869 * \returns Applied update frequency of the signal in Hz
870 */
871 virtual units::frequency::hertz_t GetAppliedUpdateFrequency() const override
872 {
873 if (_containsUnderlyingTypes)
874 {
875 return _basicTypeMap.begin()->second.GetAppliedUpdateFrequency();
876 }
877 else
878 {
879 return units::frequency::hertz_t{
880 Status_GetAppliedUpdateFrequency(this->deviceIdentifier.network.c_str(),
881 this->deviceIdentifier.deviceHash,
882 this->spn)};
883 }
884 }
885
886 /**
887 * \brief Get a basic data-only container with this information, to be used
888 * for things such as data logging.
889 *
890 * Note if looking for Phoenix 6 logging features, see the SignalLogger class instead.
891 *
892 * \returns Basic structure with all relevant information
893 */
895 {
897 toRet.value = GetValue();
898 toRet.status = GetStatus();
899 toRet.units = GetUnits();
900 toRet.timestamp = GetTimestamp().GetTime();
901 return toRet;
902 }
903
904 /**
905 * \brief Returns a lambda that calls Refresh and GetValue on this object. This is useful for command-based programming.
906 *
907 * \returns std::function<T()> that calls Refresh() and returns this value.
908 */
909 std::function<T()> AsSupplier()
910 {
911 return [this]()
912 { return this->Refresh().GetValue(); };
913 }
914 };
915
916}
917}
CTREXPORT void c_ctre_phoenix_report_error(int isError, int32_t errorCode, int isLVCode, const char *details, const char *location, const char *callStack)
@ OK
No Error.
Definition: StatusCodes.h:1196
@ InvalidModeToGetSignal
The current mode of the device is invalid for getting this signal.
Definition: StatusCodes.h:1844
@ InvalidNetwork
InvalidNetwork.
Definition: StatusCodes.h:1793
@ InvalidParamValue
An invalid argument was passed into the function/VI, such as a null pointer.
Definition: StatusCodes.h:1529
@ SigNotUpdated
No new response to update signal.
Definition: StatusCodes.h:1579
A collection of timestamps for a received signal.
Definition: Timestamp.hpp:132
Class that provides operations to retrieve information about a status signal.
Definition: StatusSignal.hpp:41
std::string signalName
Definition: StatusSignal.hpp:51
void CopyFrom(const BaseStatusSignal &other)
Definition: StatusSignal.hpp:60
units::time::second_t _lastTimestamp
Definition: StatusSignal.hpp:55
virtual ctre::phoenix::StatusCode SetUpdateFrequency(units::frequency::hertz_t frequencyHz, units::time::second_t timeoutSeconds=50_ms)=0
Sets the rate at which the device will publish this signal.
const AllTimestamps & GetAllTimestamps() const
Get the timestamps of this signals.
Definition: StatusSignal.hpp:558
AllTimestamps timestamps
Definition: StatusSignal.hpp:48
ctre::phoenix::StatusCode error
Definition: StatusSignal.hpp:50
static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, const std::vector< BaseStatusSignal * > &signals)
Waits for new data on all provided signals up to timeout.
Definition: StatusSignal.hpp:268
static ctre::phoenix::StatusCode Status_WaitForAll(BaseStatusSignal *const *signals, size_t count, const char *network, double timeoutSeconds)
Wait for multiple signals to arrive.
static ctre::phoenix::StatusCode Status_Get(const char *network, int deviceHash, uint32_t signal, bool bWaitForUpdate, double timeoutSeconds, double *outValue, double *hwtimestamp, double *swtimestamp, double *ecutimestamp)
Get signal update.
uint16_t spn
Definition: StatusSignal.hpp:46
static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, Signals &... signals)
Sets the update frequency of all specified status signals to the provided common frequency.
Definition: StatusSignal.hpp:465
static std::string Status_GetUnits(uint32_t signal)
Gets signal units.
static bool IsAllGood(Signals &... signals)
Checks if all signals have an OK error code.
Definition: StatusSignal.hpp:411
static ctre::phoenix::StatusCode RefreshAll(const std::array< BaseStatusSignal *, N > &signals)
Performs a non-blocking refresh on all provided signals.
Definition: StatusSignal.hpp:356
static ctre::phoenix::StatusCode RefreshAll(Signals &... signals)
Performs a non-blocking refresh on all provided signals.
Definition: StatusSignal.hpp:319
std::string units
Definition: StatusSignal.hpp:47
static bool IsAllGood(const std::vector< BaseStatusSignal * > &signals)
Checks if all signals have an OK error code.
Definition: StatusSignal.hpp:421
static double Status_GetAppliedUpdateFrequency(const char *canbus, uint32_t deviceHash, uint16_t spn)
ctre::phoenix::StatusCode GetStatus() const
Get the error code from when we last received this signal.
Definition: StatusSignal.hpp:570
static ctre::phoenix::StatusCode WaitForAllImpl(const char *location, units::time::second_t timeoutSeconds, const Arr &signals)
Implementation of the WaitForAll API.
Definition: StatusSignal.hpp:150
static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, const std::array< BaseStatusSignal *, N > &signals)
Sets the update frequency of all specified status signals to the provided common frequency.
Definition: StatusSignal.hpp:504
static U GetLatencyCompensatedValue(StatusSignal< U > &signal, StatusSignal< U_PER_SEC > &signalSlope, units::time::second_t maxLatencySeconds=0.300_s)
Performs latency compensation on signal using the signalSlope and signal's latency to determine the m...
Definition: StatusSignal.hpp:386
virtual units::frequency::hertz_t GetAppliedUpdateFrequency() const =0
Gets the rate at which the device will publish this signal.
std::function< void()> _checkFirmVersFunction
Definition: StatusSignal.hpp:53
bool HasUpdated()
Check whether the signal has been updated since the last check.
Definition: StatusSignal.hpp:579
static ctre::phoenix::StatusCode SetUpdateFrequencyForAll(units::frequency::hertz_t frequencyHz, const std::vector< BaseStatusSignal * > &signals)
Sets the update frequency of all specified status signals to the provided common frequency.
Definition: StatusSignal.hpp:484
BaseStatusSignal(ctre::phoenix::StatusCode error)
Definition: StatusSignal.hpp:82
const std::string & GetUnits() const
Gets the units for this signal.
Definition: StatusSignal.hpp:546
static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, Signals &... signals)
Waits for new data on all provided signals up to timeout.
Definition: StatusSignal.hpp:240
static ctre::phoenix::StatusCode Status_SetUpdateFrequency(const char *canbus, uint32_t deviceHash, uint16_t spn, double frequencyHz, double timeoutSeconds)
static bool IsAllGood(const std::array< BaseStatusSignal *, N > &signals)
Checks if all signals have an OK error code.
Definition: StatusSignal.hpp:438
double baseValue
Definition: StatusSignal.hpp:49
const std::string & GetName() const
Gets the name of this signal.
Definition: StatusSignal.hpp:540
double GetValueAsDouble() const
Gets the value of this signal as a double.
Definition: StatusSignal.hpp:552
hardware::DeviceIdentifier deviceIdentifier
Definition: StatusSignal.hpp:45
static ctre::phoenix::StatusCode Status_SetUpdateFrequencyForAll(BaseStatusSignal *const *signals, size_t count, double frequencyHz, double timeoutSeconds)
static ctre::phoenix::StatusCode WaitForAll(units::time::second_t timeoutSeconds, const std::array< BaseStatusSignal *, N > &signals)
Waits for new data on all provided signals up to timeout.
Definition: StatusSignal.hpp:298
const Timestamp & GetTimestamp() const
Get the best timestamp available to this signal.
Definition: StatusSignal.hpp:564
BaseStatusSignal(hardware::DeviceIdentifier deviceIdentifier, uint16_t spn, std::string signalName, std::function< void()> checkFirmVersFunction)
Definition: StatusSignal.hpp:69
static ctre::phoenix::StatusCode RefreshAll(const std::vector< BaseStatusSignal * > &signals)
Performs a non-blocking refresh on all provided signals.
Definition: StatusSignal.hpp:336
Represents a status signal with data of type T, and operations available to retrieve information abou...
Definition: StatusSignal.hpp:628
friend std::ostream & operator<<(std::ostream &os, const StatusSignal< T > &data)
Definition: StatusSignal.hpp:753
std::string ToString() const
Definition: StatusSignal.hpp:768
T GetValue() const
Gets the cached value from this status signal.
Definition: StatusSignal.hpp:783
virtual units::frequency::hertz_t GetAppliedUpdateFrequency() const override
Gets the rate at which the device will publish this signal.
Definition: StatusSignal.hpp:871
StatusSignal< T > & Refresh(bool ReportOnError=true)
Refreshes the value of this status signal.
Definition: StatusSignal.hpp:810
ctre::phoenix::StatusCode SetUpdateFrequency(units::frequency::hertz_t frequencyHz, units::time::second_t timeoutSeconds=50_ms) override
Sets the rate at which the device will publish this signal.
Definition: StatusSignal.hpp:846
SignalMeasurement< T > GetDataCopy() const
Get a basic data-only container with this information, to be used for things such as data logging.
Definition: StatusSignal.hpp:894
std::function< T()> AsSupplier()
Returns a lambda that calls Refresh and GetValue on this object.
Definition: StatusSignal.hpp:909
StatusSignal< T > & WaitForUpdate(units::time::second_t timeoutSec, bool ReportOnError=true)
Waits up to timeoutSec to get the up-to-date status signal value.
Definition: StatusSignal.hpp:827
Information about the timestamp of a signal.
Definition: Timestamp.hpp:20
units::time::second_t GetLatency() const
Get the latency of this timestamp compared to now.
Definition: Timestamp.hpp:114
Definition: DeviceIdentifier.hpp:19
Parent class for all devices.
Definition: ParentDevice.hpp:29
CTREXPORT void EnableConsoleUTF8Output()
Enables UTF-8 console output.
CTREXPORT std::string GetStackTrace(int offset)
Get a stack trace, ignoring the first "offset" symbols.
@ CANivore
Definition: FrcUsageReport.hpp:26
Definition: string_util.hpp:15
Type trait to verify that all types passed in are subclasses of BaseStatusSignal.
Definition: StatusSignal.hpp:205
Information from a single measurement of a status signal.
Definition: StatusSignal.hpp:602
std::string units
Units that correspond to this point.
Definition: StatusSignal.hpp:618
units::time::second_t timestamp
Timestamp of when the data point was taken.
Definition: StatusSignal.hpp:610
L value
The value of the signal, this may be an enum so it is stored as a string.
Definition: StatusSignal.hpp:606
ctre::phoenix::StatusCode status
Status code response of getting the data.
Definition: StatusSignal.hpp:614