DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
hw_health.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file hw_health.cpp
6 * @brief Hardware-health diagnostics (see hw_health.h).
7 */
8
10
11#if DETWS_ENABLE_HW_HEALTH
12
13#include <string.h>
14
15void detws_hwhealth_rail_init(HwRailMonitor *m, uint32_t nominal_mv, uint32_t warn_mv, uint32_t crit_mv)
16{
17 if (!m)
18 return;
19 m->nominal_mv = nominal_mv;
20 m->warn_mv = warn_mv;
21 m->crit_mv = crit_mv;
22 m->min_mv = nominal_mv;
23 m->sag_events = 0;
24 m->brownout_events = 0;
25}
26
27HwRailVerdict detws_hwhealth_rail_sample(HwRailMonitor *m, uint32_t mv)
28{
29 if (!m)
30 return HwRailVerdict::HW_RAIL_OK;
31 if (mv < m->min_mv)
32 m->min_mv = mv;
33 if (mv < m->crit_mv)
34 {
35 m->brownout_events++;
36 return HwRailVerdict::HW_RAIL_BROWNOUT;
37 }
38 if (mv < m->warn_mv)
39 {
40 m->sag_events++;
41 return HwRailVerdict::HW_RAIL_SAG;
42 }
43 return HwRailVerdict::HW_RAIL_OK;
44}
45
46namespace
47{
48struct Buf
49{
50 char *p;
51 size_t cap;
52 size_t len;
53 bool ok;
54};
55
56void put(Buf *b, const char *s)
57{
58 if (!b->ok)
59 return;
60 size_t sl = strnlen(s, b->cap + 1);
61 if (b->len + sl >= b->cap)
62 {
63 b->ok = false;
64 return;
65 }
66 memcpy(b->p + b->len, s, sl);
67 b->len += sl;
68}
69
70void put_u32(Buf *b, uint32_t v)
71{
72 char t[10];
73 int n = 0;
74 do
75 {
76 t[n++] = (char)('0' + v % 10);
77 v /= 10;
78 } while (v);
79 char o[11];
80 for (int i = 0; i < n; i++)
81 o[i] = t[n - 1 - i];
82 o[n] = '\0';
83 put(b, o);
84}
85} // namespace
86
87size_t detws_hwhealth_rail_json(const HwRailMonitor *m, char *out, size_t cap)
88{
89 if (!m || !out || cap == 0)
90 return 0;
91 Buf b = {out, cap, 0, true};
92 put(&b, "{\"nominal_mv\":");
93 put_u32(&b, m->nominal_mv);
94 put(&b, ",\"min_mv\":");
95 put_u32(&b, m->min_mv);
96 put(&b, ",\"sag\":");
97 put_u32(&b, m->sag_events);
98 put(&b, ",\"brownout\":");
99 put_u32(&b, m->brownout_events);
100 put(&b, "}");
101 if (!b.ok)
102 return 0;
103 out[b.len] = '\0';
104 return b.len;
105}
106
107void detws_hwhealth_spi_init(HwSpiBackoff *s, uint32_t start_hz, uint32_t min_hz, uint32_t max_hz, uint16_t fail_trip,
108 uint16_t ok_trip)
109{
110 if (!s)
111 return;
112 s->min_hz = min_hz;
113 s->max_hz = max_hz;
114 if (start_hz < min_hz)
115 s->hz = min_hz;
116 else if (start_hz > max_hz)
117 s->hz = max_hz;
118 else
119 s->hz = start_hz;
120 s->fail_streak = 0;
121 s->ok_streak = 0;
122 s->fail_trip = fail_trip ? fail_trip : 1;
123 s->ok_trip = ok_trip ? ok_trip : 1;
124}
125
126uint32_t detws_hwhealth_spi_result(HwSpiBackoff *s, bool crc_ok)
127{
128 if (!s)
129 return 0;
130 if (crc_ok)
131 {
132 s->fail_streak = 0;
133 if (++s->ok_streak >= s->ok_trip)
134 {
135 s->ok_streak = 0;
136 uint32_t up = s->hz << 1;
137 if (up < s->hz || up > s->max_hz) // overflow or past ceiling
138 up = s->max_hz;
139 s->hz = up;
140 }
141 }
142 else
143 {
144 s->ok_streak = 0;
145 if (++s->fail_streak >= s->fail_trip)
146 {
147 s->fail_streak = 0;
148 uint32_t down = s->hz >> 1;
149 if (down < s->min_hz)
150 down = s->min_hz;
151 s->hz = down;
152 }
153 }
154 return s->hz;
155}
156
157HwGpioVerdict detws_hwhealth_gpio_short(bool driven_high, bool read_high)
158{
159 if (driven_high && !read_high)
160 return HwGpioVerdict::HW_GPIO_SHORT_GND;
161 if (!driven_high && read_high)
162 return HwGpioVerdict::HW_GPIO_SHORT_VCC;
163 return HwGpioVerdict::HW_GPIO_OK;
164}
165
166HwCapVerdict detws_hwhealth_cap_leak(uint32_t measured_ms, uint32_t expected_ms, uint8_t tol_pct)
167{
168 if (expected_ms == 0)
169 return HwCapVerdict::HW_CAP_OK;
170 // Tolerance band around expected, computed in 64-bit to avoid overflow.
171 uint64_t band = (uint64_t)expected_ms * tol_pct / 100;
172 uint64_t lo = (uint64_t)expected_ms > band ? (uint64_t)expected_ms - band : 0;
173 uint64_t hi = (uint64_t)expected_ms + band;
174 if (measured_ms < lo)
175 return HwCapVerdict::HW_CAP_LEAK; // discharges too fast
176 if (measured_ms > hi)
177 return HwCapVerdict::HW_CAP_HIGH_ESR; // discharges too slow
178 return HwCapVerdict::HW_CAP_OK;
179}
180
181#endif // DETWS_ENABLE_HW_HEALTH
Hardware-health diagnostics: rail droop, SPI CRC backoff, GPIO short, cap leakage (DETWS_ENABLE_HW_HE...