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