FastLED 3.9.15
Loading...
Searching...
No Matches
watchdog.cpp.hpp
Go to the documentation of this file.
1// IWYU pragma: private
2
13
14#include "fl/wdt/watchdog.h"
15
16namespace fl {
17
18// =============================================================================
19// ResetInfo helpers
20// =============================================================================
21
22// Default subcauseName(): empty view. Per-platform richer subcause naming
23// will be wired through a follow-up issue (#2755 tracks the design); for
24// now the empty default works for every backend and describe() handles
25// the subcauseId==0 case by skipping the parenthesized name.
29
30namespace platforms {
31
32// Write `value` as a lowercase hex string into `out`, prefixed with "0x".
33// Returns bytes written (always <= 10, since u32 max is 0xFFFFFFFF = 8 hex
34// digits + 2 prefix chars). Does not write a NUL. Truncates if out is small.
35inline fl::size writeHex(fl::span<char> out, fl::u32 value) FL_NOEXCEPT {
36 char tmp[10];
37 tmp[0] = '0';
38 tmp[1] = 'x';
39 static const char hex[] = "0123456789abcdef";
40 for (int i = 7; i >= 0; --i) {
41 tmp[2 + (7 - i)] = hex[(value >> (i * 4)) & 0xFu];
42 }
43 fl::size n = (out.size() < 10) ? out.size() : 10;
44 for (fl::size i = 0; i < n; ++i) out[i] = tmp[i];
45 return n;
46}
47
48// Copy a string_view into out[]. Returns bytes copied, truncated to out.size().
50 fl::size n = (out.size() < sv.size()) ? out.size() : sv.size();
51 for (fl::size i = 0; i < n; ++i) out[i] = sv[i];
52 return n;
53}
54
55inline fl::size writeChar(fl::span<char> out, char c) FL_NOEXCEPT {
56 if (out.size() == 0) return 0;
57 out[0] = c;
58 return 1;
59}
60
61inline fl::size writeNulIfRoom(fl::span<char> out, fl::size written) FL_NOEXCEPT {
62 if (written < out.size()) out[written] = '\0';
63 return written;
64}
65
66} // namespace platforms
67
68fl::size ResetInfo::describe(fl::span<char> out, bool verbose) const FL_NOEXCEPT {
69 fl::size n = 0;
70 // <cause>
71 n += platforms::writeView(out.subspan(n), causeName());
72 // ( <sub> ) if subcauseId != 0
73 if (subcauseId != 0) {
75 if (sub.size() != 0) {
76 n += platforms::writeView(out.subspan(n), fl::string_view(" ("));
77 n += platforms::writeView(out.subspan(n), sub);
78 n += platforms::writeChar(out.subspan(n), ')');
79 }
80 }
81 if (verbose) {
82 n += platforms::writeView(out.subspan(n), fl::string_view(" raw="));
83 n += platforms::writeHex(out.subspan(n), rawRegister);
84 }
85 return platforms::writeNulIfRoom(out, n);
86}
87
88// =============================================================================
89// Watchdog::lastResetInfo() default — platforms can override
90// =============================================================================
91
92// Default: lift the normalized cause into a ResetInfo, leaving
93// subcauseId/rawRegister at 0. Per-platform richer extraction will be wired
94// through a follow-up to issue #2755 (every backend uses this default for now).
96 ResetInfo info{};
97 info.cause = lastResetCause();
98 info.subcauseId = 0;
99 info.rawRegister = 0;
100 return info;
101}
102
103// =============================================================================
104// ScopedWatchdog
105// =============================================================================
106
107namespace platforms {
108
109// Forward-declared in this TU to avoid pulling in fl::print/println headers
110// from a public header. The dispatcher TU includes the necessary print
111// header before this file.
114
115// Counts simultaneously-alive ScopedWatchdog instances. >1 is almost always
116// a programmer error: nested guards mean the lazy-init's timeout argument
117// of the second instance is silently ignored, and timeout semantics across
118// the two scopes become ambiguous. Singleton-style accessor (function-local
119// static) so the counter has well-defined lifetime even if a guard is
120// constructed before main().
122 static int count = 0;
123 return count;
124}
125
126// First-init helper, used by ScopedWatchdog's constructor. Runs only once
127// per process. Single-threaded loop() is the canonical caller — no atomics
128// are needed for the guard byte on supported MCUs.
129inline void scopedWatchdogFirstInit(fl::u32 timeout_ms) FL_NOEXCEPT {
131 wdt.begin(timeout_ms);
132
133 ResetInfo info = wdt.lastResetInfo();
134 if (info.cause == ResetCause::WATCHDOG ||
135 info.cause == ResetCause::PANIC ||
136 info.cause == ResetCause::LOCKUP) {
137 char buf[128];
138 fl::size n = info.describe(fl::span<char>(buf, sizeof(buf)), /*verbose=*/true);
139 scopedWatchdogPrintLine(fl::string_view("[FastLED.watchdog] recovered from:"));
141
142 if (wdt.hasCrashReport()) {
143 // Hook for Tier 2 platforms. Future revision: also emit a
144 // multi-line CrashReport::describe() result here. For now the
145 // bare cause + raw register is the documented contract.
146 }
148 }
149 scopedWatchdogPrintLine(fl::string_view("[FastLED.watchdog] armed via FL_WATCHDOG_AUTO()"));
150}
151
152} // namespace platforms
153
155 static bool sInitialized = false;
156 if (!sInitialized) {
157 sInitialized = true;
159 }
160
161 // Single-instance enforcement. A second simultaneously-alive ScopedWatchdog
162 // is almost always a bug — the timeout argument here was silently ignored
163 // (only the first instance gets to arm the WDT). Warn once per process so
164 // the developer notices, but still feed so the program survives.
166 if (count >= 1) {
167 static bool sWarnedOnce = false;
168 if (!sWarnedOnce) {
169 sWarnedOnce = true;
171 "[FastLED.watchdog] WARN: nested FL_WATCHDOG_AUTO() detected — "
172 "only one scoped guard should be alive at a time"));
173 }
174 }
175 ++count;
176
178}
179
182 if (count > 0) --count;
184}
185
189
190} // namespace fl
static int activeScopeCount() FL_NOEXCEPT
Observability: number of simultaneously-alive ScopedWatchdog instances.
~ScopedWatchdog() FL_NOEXCEPT
Feed the watchdog at end-of-scope so the next loop() iteration has a clean deadline window.
ScopedWatchdog() FL_NOEXCEPT
Default construct with the library default timeout (15 000 ms).
Definition watchdog.h:208
static Watchdog & instance() FL_NOEXCEPT
void begin(fl::u32 timeout_ms) FL_NOEXCEPT
void feed() FL_NOEXCEPT
bool hasCrashReport() const FL_NOEXCEPT
ResetCause lastResetCause() const FL_NOEXCEPT
ResetInfo lastResetInfo() const FL_NOEXCEPT
Detailed reset information including platform raw register + subcause id.
Unified cross-platform watchdog interface.
Definition watchdog.h:123
constexpr fl::size size() const FL_NOEXCEPT
Definition string_view.h:99
void scopedWatchdogFirstInit(fl::u32 timeout_ms) FL_NOEXCEPT
fl::size writeNulIfRoom(fl::span< char > out, fl::size written) FL_NOEXCEPT
int & scopedWatchdogActiveCount() FL_NOEXCEPT
void scopedWatchdogPrintLine(fl::string_view sv) FL_NOEXCEPT
fl::size writeView(fl::span< char > out, fl::string_view sv) FL_NOEXCEPT
fl::size writeHex(fl::span< char > out, fl::u32 value) FL_NOEXCEPT
void scopedWatchdogPause3s() FL_NOEXCEPT
fl::size writeChar(fl::span< char > out, char c) FL_NOEXCEPT
constexpr int type_rank< T >::value
const hex_t hex
Definition ios.cpp.hpp:6
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
ResetCause cause
Normalized cross-platform enum.
Definition watchdog.h:83
fl::u32 rawRegister
Raw value of the platform's reset-cause register.
Definition watchdog.h:85
fl::string_view causeName() const FL_NOEXCEPT
Cause name (zero-cost, static string_view).
Definition watchdog.h:88
fl::string_view subcauseName() const FL_NOEXCEPT
Platform-specific subcause name (zero-cost, static string_view).
fl::size describe(fl::span< char > out, bool verbose=false) const FL_NOEXCEPT
Write a single-line human-readable description into the caller's buffer.
fl::u8 subcauseId
Platform-specific subcause id (0 = none)
Definition watchdog.h:84
Detailed reset information bundling the normalized cause with a platform-specific subcause id and the...
Definition watchdog.h:82
Unified cross-platform Watchdog Timer API for FastLED.