Valkyrie 2026
Loading...
Searching...
No Matches
Loggable.h
1#pragma once
2#include <algorithm>
3#include <memory>
4#include <string>
5#include <tuple>
6#include <unordered_map>
7#include <utility>
8#include <vector>
9
10#include <frc/Errors.h>
11#include <frc/Timer.h>
12#include <magic_enum/magic_enum.hpp>
13#include <networktables/BooleanArrayTopic.h>
14#include <networktables/BooleanTopic.h>
15#include <networktables/DoubleArrayTopic.h>
16#include <networktables/DoubleTopic.h>
17#include <networktables/FloatTopic.h>
18#include <networktables/IntegerArrayTopic.h>
19#include <networktables/IntegerTopic.h>
20#include <networktables/NetworkTableInstance.h>
21#include <networktables/ProtobufTopic.h>
22#include <networktables/StringArrayTopic.h>
23#include <networktables/StringTopic.h>
24#include <networktables/StructArrayTopic.h>
25#include <networktables/StructTopic.h>
26#include <units/acceleration.h>
27#include <units/angular_acceleration.h>
28#include <units/angular_velocity.h>
29#include <units/base.h>
30#include <units/current.h>
31#include <units/temperature.h>
32#include <units/time.h>
33#include <wpi/json.h>
34#include <wpi/protobuf/Protobuf.h>
35#include <wpi/sendable/Sendable.h>
36#include <wpi/struct/Struct.h>
37
38namespace valor {
39
40namespace detail {
41template <typename T>
42inline constexpr bool is_unit_vector = false;
43
44template <typename T>
45inline constexpr bool is_unit_vector<std::vector<T>> = units::traits::is_unit_t_v<T>;
46
47template <typename T>
48struct nt_traits {
49 static_assert(sizeof(T) == 0, "No valid NetworkTables trait found for the specified data type");
50};
51
52template <>
53struct nt_traits<bool> {
54 static constexpr auto func = &nt::NetworkTable::GetBooleanTopic;
55 static constexpr std::string_view type = "boolean";
56};
57
58template <>
59struct nt_traits<float> {
60 static constexpr auto func = &nt::NetworkTable::GetFloatTopic;
61 static constexpr std::string_view type = "float";
62};
63
64template <>
65struct nt_traits<double> {
66 static constexpr auto func = &nt::NetworkTable::GetDoubleTopic;
67 static constexpr std::string_view type = "double";
68};
69
70template <typename T>
71 requires units::traits::is_unit_t_v<T>
72struct nt_traits<T> : nt_traits<double> {};
73
74template <std::integral T>
75struct nt_traits<T> {
76 static constexpr auto func = &nt::NetworkTable::GetIntegerTopic;
77 static constexpr std::string_view type = "int";
78};
79
80template <typename T>
81 requires std::convertible_to<T, std::string_view>
82struct nt_traits<T> {
83 static constexpr auto func = &nt::NetworkTable::GetStringTopic;
84 static constexpr std::string_view type = "string";
85};
86
87template <typename T>
88 requires std::is_enum_v<T>
89struct nt_traits<T> : nt_traits<std::string_view> {};
90
91template <typename T>
92 requires wpi::StructSerializable<T> && (!std::is_arithmetic_v<T>)
93struct nt_traits<T> {
94 static constexpr auto func = &nt::NetworkTable::GetStructTopic<T>;
95 inline static std::string type = "struct:" + std::string{wpi::Struct<T>::GetTypeName()};
96};
97
98template <typename T>
99 requires wpi::ProtobufSerializable<T> && (!wpi::StructSerializable<T>)
100struct nt_traits<T> {
101 static constexpr auto func = &nt::NetworkTable::GetProtobufTopic<T>;
102 static constexpr std::string_view type = "proto:";
103};
104
105template <>
106struct nt_traits<std::vector<bool>> {
107 static constexpr auto func = &nt::NetworkTable::GetBooleanArrayTopic;
108 static constexpr std::string_view type = "boolean[]";
109};
110
111template <>
112struct nt_traits<std::vector<float>> {
113 static constexpr auto func = &nt::NetworkTable::GetFloatArrayTopic;
114 static constexpr std::string_view type = "float[]";
115};
116
117template <>
118struct nt_traits<std::vector<double>> {
119 static constexpr auto func = &nt::NetworkTable::GetDoubleArrayTopic;
120 static constexpr std::string_view type = "double[]";
121};
122
123template <typename T>
124 requires is_unit_vector<T>
125struct nt_traits<T> : nt_traits<std::vector<double>> {};
126
127template <std::integral T>
128struct nt_traits<std::vector<T>> {
129 static constexpr auto func = &nt::NetworkTable::GetIntegerArrayTopic;
130 static constexpr std::string_view type = "int64[]";
131};
132
133template <typename T>
134 requires std::convertible_to<T, std::string_view>
135struct nt_traits<std::vector<T>> {
136 static constexpr auto func = &nt::NetworkTable::GetStringArrayTopic;
137 static constexpr std::string_view type = "string[]";
138};
139
140template <typename T>
141 requires std::is_enum_v<T>
142struct nt_traits<std::vector<T>> : nt_traits<std::vector<std::string>> {};
143
144template <typename T>
145 requires wpi::StructSerializable<T> && (!std::is_arithmetic_v<T>)
146struct nt_traits<std::vector<T>> {
147 static constexpr auto func = &nt::NetworkTable::GetStructArrayTopic<T>;
148 inline static std::string type = "struct:" + std::string{wpi::Struct<T>::GetTypeName()} + "[]";
149};
150
151template <typename T>
152inline constexpr std::string_view unit_string = "";
153
154#define UNIT_STRING(unit, str) \
155 template <> \
156 inline constexpr std::string_view unit_string<units::unit##_t> = str
157UNIT_STRING(turn, "rot");
158
159UNIT_STRING(radians_per_second, "rad per sec");
160UNIT_STRING(degrees_per_second, "deg per sec");
161UNIT_STRING(turns_per_second, "rps");
162
163UNIT_STRING(meters_per_second_squared, "mps2");
164UNIT_STRING(feet_per_second_squared, "fps2");
165UNIT_STRING(standard_gravity, "g");
166
167UNIT_STRING(radians_per_second_squared, "rad per sec2");
168UNIT_STRING(degrees_per_second_squared, "deg per sec2");
169UNIT_STRING(turns_per_second_squared, "rps2");
170
171UNIT_STRING(day, "day");
172
173UNIT_STRING(celsius, "c");
174UNIT_STRING(fahrenheit, "f");
175
176UNIT_STRING(ampere, "amp");
177#undef UNIT_STRING
178
180 using is_transparent = void;
181
182 size_t operator()(std::string_view sv) const { return std::hash<std::string_view>{}(sv); }
183
184 size_t operator()(const std::string& s) const { return std::hash<std::string>{}(s); }
185};
186
188 using is_transparent = void;
189
190 bool operator()(std::string_view lhs, std::string_view rhs) const { return lhs == rhs; }
191};
192
193} // namespace detail
194
196enum class LogLevel {
197 Force = 0,
198 Normal = 100,
199 Important = 50,
200 Critical = 20,
201};
202
218class Loggable {
219 std::unordered_map<std::string, std::pair<std::unique_ptr<nt::Publisher>, units::second_t>, detail::string_hash, detail::string_equal>
220 publishers;
221 std::unordered_map<std::string, std::unique_ptr<nt::Subscriber>, detail::string_hash, detail::string_equal> subscribers;
222
223 public:
235 explicit Loggable(std::string_view name);
236
257 explicit Loggable(std::shared_ptr<nt::NetworkTable> table);
258
267
279 void LogChild(std::string_view name, Loggable* child);
280
296 void LogChild(std::string_view name, wpi::Sendable* child);
297
298 static units::millisecond_t GetLoggingTime();
299
307 void SetPeriodicLevel(LogLevel level) { periodicLevel = level; }
308
325 template <typename T>
326 inline T WriteLog(std::string_view field, const T& data, LogLevel level = LogLevel::Normal) {
327 loggingProfiler.Start();
328 if (!table) {
329 loggingProfiler.Stop();
330 return data;
331 }
332 auto pub = publishers.find(field);
333 if (pub == publishers.end())
334 pub = addPublisher<T>(field);
335 else if (!inPeriodic && frc::Timer::GetTimestamp() - pub->second.second <
336 units::millisecond_t{static_cast<double>(level)} - RATE_LIMIT_TIME_TOLERANCE) {
337 loggingProfiler.Stop();
338 return data;
339 }
340 // else
341 // checkTopicType<T>(pub->second->GetTopic().GetTypeString(), field);
342 WriteLogImpl(pub->second.first.get(), data);
343 pub->second.second = frc::Timer::GetTimestamp();
344 loggingProfiler.Stop();
345 return data;
346 }
347
361 template <typename T>
362 inline T ReadLog(std::string_view field, const T& defaultValue = {}) {
363 loggingProfiler.Start();
364 if (!table) {
365 loggingProfiler.Stop();
366 return defaultValue;
367 }
368 auto sub = subscribers.find(field);
369 if (sub == subscribers.end())
370 sub = addSubscriber(field, defaultValue);
371 // else
372 // checkTopicType<T>(sub->second->GetTopic().GetTypeString(), field);
373 T value = ReadLogImpl<T>(sub->second.get());
374 if (value == defaultValue && !publishers.contains(field))
375 WriteLog(field, defaultValue);
376 loggingProfiler.Stop();
377 return value;
378 }
379
380 static constexpr Loggable& GetRoot() { return rootLoggable; }
381
382 protected:
389 virtual void OnLoggingStart() {}
390
398 virtual void LoggablePeriodic() {}
399
400 private:
401 template <typename T>
402 using TopicType = decltype((std::declval<nt::NetworkTable>().*detail::nt_traits<T>::func)(""));
403
410 static constexpr units::second_t RATE_LIMIT_TIME_TOLERANCE = 3_ms;
411
422 void EnableLogging();
423
430 template <typename T>
431 inline void WriteLogImpl(nt::Publisher* pub, const T& data) {
432 static_cast<typename TopicType<T>::PublisherType*>(pub)->Set(data);
433 }
434
435 template <typename T>
436 requires units::traits::is_unit_t_v<T>
437 inline void WriteLogImpl(nt::Publisher* pub, const T& data) {
438 WriteLogImpl(pub, data.value());
439 }
440
441 template <typename T>
442 requires detail::is_unit_vector<T>
443 void WriteLogImpl(nt::Publisher* pub, const T& data) {
444 std::vector<double> castedData;
445 castedData.reserve(data.size());
446 std::transform(data.begin(), data.end(), std::back_inserter(castedData), [](auto val) { return val.value(); });
447 WriteLogImpl(pub, castedData);
448 }
449
450 template <typename T>
451 requires std::is_enum_v<T>
452 void WriteLogImpl(nt::Publisher* pub, const T& data) {
453 WriteLogImpl(pub, magic_enum::enum_name(data));
454 }
455
456 template <typename T>
457 requires std::is_enum_v<T>
458 void WriteLogImpl(nt::Publisher* pub, const std::vector<T>& data) {
459 std::vector<std::string> castedData;
460 castedData.reserve(data.size());
461 for (T val : data)
462 castedData.emplace_back(magic_enum::enum_name(val));
463 WriteLogImpl(pub, castedData);
464 }
465
471 template <typename T>
472 inline T ReadLogImpl(nt::Subscriber* sub) {
473 return static_cast<TopicType<T>::SubscriberType*>(sub)->Get();
474 }
475
476 template <typename T>
477 requires units::traits::is_unit_t_v<T>
478 inline T ReadLogImpl(nt::Subscriber* sub) {
479 return T{ReadLogImpl<double>(sub)};
480 }
481
482 template <typename T>
483 requires detail::is_unit_vector<T>
484 T ReadLogImpl(nt::Subscriber* sub) {
485 auto rawData = ReadLogImpl<std::vector<double>>(sub);
486 T castedData;
487 castedData.reserve(rawData.size());
488 for (double data : rawData)
489 castedData.emplace_back(data);
490 return castedData;
491 }
492
493 template <typename T>
494 requires std::is_enum_v<T>
495 inline T ReadLogImpl(nt::Subscriber* sub) {
496 return magic_enum::enum_cast<T>(ReadLogImpl<std::string_view>(sub)).value_or(T{});
497 }
498
504 template <typename T>
505 inline decltype(publishers)::iterator addPublisher(std::string_view field) {
506 return publishers
507 .try_emplace(
508 std::string{field},
509 std::make_unique<typename TopicType<T>::PublisherType>((table.get()->*detail::nt_traits<T>::func)(field).Publish()), 0_s)
510 .first;
511 }
512
513 template <typename T>
514 requires units::traits::is_unit_t_v<T>
515 decltype(publishers)::iterator addPublisher(std::string_view field) {
516 std::string unitValue{detail::unit_string<T>};
517 if (unitValue.empty())
518 unitValue = T{}.abbreviation();
519
520 return publishers
521 .try_emplace(std::string{field},
522 std::make_unique<nt::DoublePublisher>(table->GetDoubleTopic(field).PublishEx("double", {{"unit", unitValue}})),
523 0_s)
524 .first;
525 }
526
533 template <typename T>
534 inline decltype(subscribers)::iterator addSubscriber(std::string_view field, const T& defaultValue) {
535 return subscribers
536 .emplace(field, std::make_unique<typename TopicType<T>::SubscriberType>(
537 (table.get()->*detail::nt_traits<T>::func)(field).Subscribe(defaultValue)))
538 .first;
539 }
540
541 template <typename T>
542 requires units::traits::is_unit_t_v<T>
543 inline decltype(subscribers)::iterator addSubscriber(std::string_view field, const T& defaultValue) {
544 return addSubscriber(field, defaultValue.value());
545 }
546
547 template <typename T>
548 requires detail::is_unit_vector<T>
549 decltype(subscribers)::iterator addSubscriber(std::string_view field, const T& defaultValue) {
550 thread_local std::vector<double> defaultData;
551 defaultData.clear();
552 defaultData.reserve(defaultValue.size());
553 std::transform(defaultValue.begin(), defaultValue.end(), std::back_inserter(defaultData), [](auto val) { return val.value(); });
554 return addSubscriber(field, defaultData);
555 }
556
557 template <typename T>
558 requires std::is_enum_v<T>
559 decltype(subscribers)::iterator addSubscriber(std::string_view field, const T& defaultValue) {
560 return addSubscriber(field, magic_enum::enum_name(defaultValue));
561 }
562
573 template <typename T>
574 void checkTopicType(std::string_view topicType, std::string_view field) {
575 if (topicType.empty())
576 return;
577 FRC_AssertMessage(topicType == detail::nt_traits<T>::type,
578 "Current type of '{}' doesn't match original topic type (received '{}', expected '{}')", field,
579 detail::nt_traits<T>::type, topicType);
580 }
581
582 std::shared_ptr<nt::NetworkTable> table;
583 std::unordered_map<std::string, Loggable*> loggableChildren;
584 std::unordered_map<std::string, wpi::Sendable*> sendableChildren;
585 frc::Timer periodicProfiler;
586 bool inPeriodic = false;
587 LogLevel periodicLevel = LogLevel::Normal;
588 units::second_t lastPeriodicTime = 0_s;
589 static frc::Timer loggingProfiler;
590 static Loggable rootLoggable;
591};
592
593template <>
594inline decltype(Loggable::subscribers)::iterator Loggable::addSubscriber(std::string_view field, const std::vector<bool>& defaultValue) {
595 return subscribers
596 .emplace(field, std::make_unique<nt::BooleanArraySubscriber>(
597 table->GetBooleanArrayTopic(field).Subscribe(std::vector<int>{defaultValue.begin(), defaultValue.end()})))
598 .first;
599}
600
601template <>
602inline void Loggable::WriteLogImpl(nt::Publisher* pub, const std::vector<bool>& data) {
603 static_cast<nt::BooleanArrayPublisher*>(pub)->Set(std::vector<int>{data.begin(), data.end()});
604}
605
606template <>
607inline std::vector<bool> Loggable::ReadLogImpl(nt::Subscriber* sub) {
608 auto data = static_cast<nt::BooleanArraySubscriber*>(sub)->Get();
609 return {data.begin(), data.end()};
610}
611
612} // namespace valor
Base helper for publishing and subscribing values to NetworkTables.
Definition Loggable.h:218
T ReadLog(std::string_view field, const T &defaultValue={})
Read a value from NetworkTables for the given field.
Definition Loggable.h:362
void SetPeriodicLevel(LogLevel level)
Set the logging level for LoggablePeriodic.
Definition Loggable.h:307
T WriteLog(std::string_view field, const T &data, LogLevel level=LogLevel::Normal)
Publish a value to NetworkTables under the given field.
Definition Loggable.h:326
virtual void OnLoggingStart()
Hook invoked when logging is started for this object.
Definition Loggable.h:389
void LogChild(std::string_view name, Loggable *child)
Register a child Loggable under a named subtree.
void LogChild(std::string_view name, wpi::Sendable *child)
Register a child Sendable under a named subtree.
virtual void LoggablePeriodic()
Periodic callback for logging updates.
Definition Loggable.h:398
Loggable()
Default construct an uninitialized Loggable.
Loggable(std::string_view name)
Construct a Loggable that registers a top-level table name.
Loggable(std::shared_ptr< nt::NetworkTable > table)
Construct a Loggable that uses an existing NetworkTable.
Template specializations for std::tuple_size and std::tuple_element to support UnitVector.
Definition Formatters.h:8
Definition Loggable.h:48
Definition Loggable.h:187
Definition Loggable.h:179