16uint64_t
ms_between(uint64_t later_ns, uint64_t earlier_ns) {
17 return later_ns > earlier_ns ? (later_ns - earlier_ns) /
NS_PER_MS : 0;
20double mean_of(
const std::vector<double>& v,
size_t from,
size_t to) {
22 for (
size_t i = from; i < to; i++)
24 return sum /
static_cast<double>(to - from);
35 const uint64_t span_limit_ns =
kRingSpanMs * NS_PER_MS;
36 while (
ring.size() > 1 &&
ring.back().ts_ns >
ring.front().ts_ns &&
37 ring.back().ts_ns -
ring.front().ts_ns > span_limit_ns) {
48 auto it =
rings_.find(uid);
49 if (it ==
rings_.end() || it->second.empty()) {
53 const std::deque<AdcSample>&
ring = it->second;
61 uint64_t period_ms = 0;
63 period_ms = ms_between(newest.
ts_ns,
ring.front().ts_ns) / (
ring.size() - 1);
71 r.age_ms = ms_between(now_rx_ns, newest.
rx_ns);
72 if (
r.age_ms >
r.max_age_ms) {
79 const uint64_t window_ns =
r.window_ms * NS_PER_MS;
80 const uint64_t floor_ns = newest.
ts_ns > window_ns ? newest.
ts_ns - window_ns : 0;
82 std::vector<double> vals;
83 vals.reserve(
ring.size());
84 uint64_t oldest_ts = newest.
ts_ns;
86 if (s.ts_ns < floor_ns)
88 vals.push_back(
static_cast<double>(s.adc));
89 oldest_ts = std::min(oldest_ts, s.ts_ns);
93 r.span_ms = ms_between(newest.
ts_ns, oldest_ts);
100 r.adc_avg = mean_of(vals, 0, vals.size());
101 const auto mm = std::minmax_element(vals.begin(), vals.end());
102 r.spread = *mm.second - *mm.first;
107 const size_t half = vals.size() / 2;
108 const double m1 = mean_of(vals, 0, half);
109 const double m2 = mean_of(vals, half, vals.size());
110 r.drift = std::fabs(m2 - m1);
118 for (
size_t i = 0; i < half; i++)
119 ss += (vals[i] - m1) * (vals[i] - m1);
120 for (
size_t i = half; i < vals.size(); i++)
121 ss += (vals[i] - m2) * (vals[i] - m2);
122 const size_t n1 = half, n2 = vals.size() - half;
123 const double dof =
static_cast<double>(n1 + n2) - 2.0;
124 const double sd_pooled = dof > 0.0 ? std::sqrt(ss / dof) : 0.0;
127 const double se = std::max(
128 sd_pooled * std::sqrt(1.0 /
static_cast<double>(n1) + 1.0 /
static_cast<double>(n2)), 1.0);
129 r.drift_z =
r.drift / se;
146 std::vector<uint16_t> out;
147 out.reserve(
rings_.size());
152 std::sort(out.begin(), out.end());
157 auto it =
rings_.find(uid);
158 return it ==
rings_.end() ? 0 : it->second.size();
The ADC a calibration capture records: a bounded, recent, checked mean.
static uint8_t ring[RING_SIZE]
std::unordered_map< uint16_t, std::deque< AdcSample > > rings_
CaptureResult capture(uint16_t uid, uint64_t now_rx_ns) const
What this sensor reads now, or why that cannot be answered.
void clear()
Forget everything.
void push(uint16_t uid, int32_t adc, uint64_t ts_ns, uint64_t rx_ns)
Record one sample.
std::vector< uint16_t > uids() const
Every uid with at least one sample.
size_t size(uint16_t uid) const
Samples currently held for a uid (diagnostics and tests).
static STAR_MCP3201 adc(MCP3201_CS_PIN, &mcp_spi)
constexpr uint64_t NS_PER_MS
double mean_of(const std::vector< double > &v, size_t from, size_t to)
uint64_t ms_between(uint64_t later_ns, uint64_t earlier_ns)
Saturating difference — clocks are unsigned and callers may pass a now that predates a sample (a reor...
Output smoothing for calibrated PT pressure, per sensor uid (ESP32 and LabJack T7 alike).
constexpr const char * kReasonTooFew
constexpr const char * kReasonNoSamples
constexpr uint64_t kCaptureWindowMs
How far back a capture averages.
constexpr const char * kReasonOk
Why a capture was refused.
constexpr const char * kReasonStale
constexpr uint64_t kStaleSamplePeriods
Liveness allowance for a slow sensor, in its own sample periods: one late sample is jitter,...
constexpr size_t kMinCaptureSamples
Below this a "mean" is not one, and the settle check has nothing to say.
constexpr uint64_t kMaxCaptureWindowMs
Upper bound on the adaptive window and staleness allowance.
constexpr size_t kRingMax
constexpr double kSettleZ
Half-mean difference, in standard errors, above which the window is not settled.
constexpr uint64_t kRingSpanMs
Ring eviction: by span so it self-sizes across 1-250 Hz, by count so memory is bounded if a board eve...
constexpr uint64_t kMaxSampleAgeMs
Newest sample older than this (in OUR clock) means the sensor is not live.
Motorized ball valve position: raw ADC counts -> % open.
uint64_t ts_ns
sample time, board-derived (spacing is what matters)
uint64_t rx_ns
arrival time on OUR monotonic clock