-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathObstacleModel.h
More file actions
245 lines (190 loc) · 8.14 KB
/
Copy pathObstacleModel.h
File metadata and controls
245 lines (190 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
ObstacleModel.h
---------------------------------------------------------------------------
Pluggable obstacle / end-of-travel detection.
DESIGN
The model is a list of independent detectors. Each detector sees the same
fully-derived view of one 100 ms measurement window and answers a single
question: "does this look wrong to me?". The engine collects the answers
and applies a voting policy.
Detectors know nothing about each other and nothing about motor control.
Adding, removing or silencing one cannot affect the others.
SEVERITY
ADVISORY reported in telemetry, never stops the motor. Use this to trial
a new detector against real runs before trusting it.
VOTE counts toward MODEL_REQUIRED_VOTES.
HARD stops the motor on its own, regardless of the vote threshold.
TO ADD A MODEL
1. Write a class deriving from ObstacleDetector in ObstacleModel.cpp.
2. Create one static instance of it.
3. Add that instance to the DETECTORS[] array.
Nothing else in the firmware needs to change.
TO REMOVE A MODEL
Delete its line from DETECTORS[], or leave it in place and turn it off at
runtime with the serial command: model <name> off
ENDPOINT vs OBSTRUCTION
Deliberately not distinguished. Reaching the end of travel and hitting an
object are the same event to a safety controller: load left the normal
envelope, so cut power. Since the limit switches are unusable, the end of
travel is detected as an obstruction, which is what stops the cover.
*/
#ifndef OBSTACLE_MODEL_H
#define OBSTACLE_MODEL_H
#include <Arduino.h>
#include "ModelConfig.h"
// =============================================================================
// Shared types
// =============================================================================
enum class MotionDirection : uint8_t {
NONE,
OPEN,
CLOSE
};
enum class DetectorSeverity : uint8_t {
ADVISORY,
VOTE,
HARD
};
const char* severityName(DetectorSeverity severity);
const char* motionDirectionName(MotionDirection direction);
// One completed 100 ms measurement window, exactly as the sampler produced it.
struct CurrentSample {
float rms;
float mean;
int minRaw;
int maxRaw;
int peakToPeak;
unsigned long sampleCount;
};
/*
Everything a detector is allowed to look at.
The engine computes every derived value once per window so that detectors
stay trivial and cannot disagree about basic arithmetic.
*/
struct ModelContext {
MotionDirection direction;
unsigned long elapsedMs; // since the relay closed
unsigned long windowIndex; // 0-based count of windows this run
CurrentSample sample;
// Derived
float negSwing; // mean - min (unclipped side)
float posSwing; // max - mean (clips first)
float swingRatio; // negSwing / posSwing, 0 if unavailable
bool clipped; // reading hit the measurable limit
float slope; // counts of rise over DET_SLOPE_SPAN_MS
bool slopeValid;
float baselineRms; // rolling median of recent steady travel
float baselineMean;
bool baselineValid;
float ratioToBaseline; // rms / baselineRms, 0 if no baseline
float meanSag; // baselineMean - mean, 0 if no baseline
float latchedBaseline; // baseline fixed from early travel, never moves
bool latchedValid;
float ratioToLatched; // rms / latchedBaseline, 0 if not latched yet
unsigned long expectedMs; // expected travel time for this direction
bool pastBlanking; // elapsedMs >= MODEL_BLANKING_MS
bool sampleUsable; // enough ADC samples in the window to trust it
};
// =============================================================================
// Detector base class
// =============================================================================
class ObstacleDetector {
public:
ObstacleDetector(const char* name,
const char* description,
DetectorSeverity severity,
bool enabled);
virtual ~ObstacleDetector() {}
// Called once when a run starts. Clear any per-run state here.
virtual void onRunStart(MotionDirection direction) { (void)direction; }
// Return true when this detector believes the cover must stop.
// Called once per 100 ms window while a run is active.
virtual bool evaluate(const ModelContext& context) = 0;
// Short human-readable explanation of the most recent trip.
virtual const char* reason() const { return "obstacle"; }
/*
Should a trip from this detector block further travel in the same
direction until the cover has been driven the other way?
True for anything that means "the cover cannot go further this way" - an
end stop, an obstruction, a slipping drive. False for faults that say
nothing about position, such as the motor not starting at all.
*/
virtual bool locksDirection() const { return true; }
const char* name() const { return name_; }
const char* description() const { return description_; }
DetectorSeverity severity() const { return severity_; }
bool enabled() const { return enabled_; }
void setEnabled(bool enabled) { enabled_ = enabled; }
void setSeverity(DetectorSeverity severity) { severity_ = severity; }
bool asserted() const { return asserted_; }
bool tripped() const { return tripped_; }
unsigned long trippedAtMs() const { return trippedAtMs_; }
// Engine-internal bookkeeping.
void beginRun(MotionDirection direction);
bool run(const ModelContext& context);
private:
const char* name_;
const char* description_;
DetectorSeverity severity_;
bool enabled_;
bool asserted_; // asserting on the most recent window
bool tripped_; // has asserted at least once this run
unsigned long trippedAtMs_;
};
// =============================================================================
// Result of one model evaluation
// =============================================================================
struct ModelResult {
bool stop; // motor must be stopped now
const char* detector; // which detector caused the stop
const char* reason; // short reason string for telemetry
uint8_t votes; // VOTE-severity detectors asserting
uint16_t flags; // bit i set if DETECTORS[i] is asserting
bool evaluated; // false if the window was skipped
bool locksDirection; // block this direction until the cover reverses
};
// =============================================================================
// The model engine
// =============================================================================
class ObstacleModel {
public:
void begin();
void onRunStart(MotionDirection direction);
void onRunEnd();
// Feed one completed measurement window. Safe to call when disabled.
ModelResult update(const CurrentSample& sample, unsigned long elapsedMs);
const ModelContext& context() const { return context_; }
uint8_t detectorCount() const;
ObstacleDetector* detectorAt(uint8_t index) const;
ObstacleDetector* detectorByName(const char* name) const;
uint8_t enabledCount() const;
// Compact flag string for telemetry, e.g. "abs,slope". Empty when quiet.
const char* flagString() const { return flagString_; }
private:
void buildContext(const CurrentSample& sample, unsigned long elapsedMs);
void pushHistory(const CurrentSample& sample, unsigned long elapsedMs);
void updateBaseline();
void updateLatchedBaseline();
void updateSlope();
void buildFlagString(uint16_t flags);
static const uint8_t HISTORY_CAPACITY = 48;
struct HistoryEntry {
unsigned long elapsedMs;
float rms;
float mean;
};
HistoryEntry history_[HISTORY_CAPACITY];
uint8_t historyCount_ = 0;
uint8_t historyHead_ = 0;
static const uint8_t LATCH_CAPACITY = 40;
float latchSamples_[LATCH_CAPACITY];
uint8_t latchCount_ = 0;
float latchedBaseline_ = 0.0f;
bool latched_ = false;
ModelContext context_;
MotionDirection direction_ = MotionDirection::NONE;
unsigned long windowIndex_ = 0;
char flagString_[96];
};
extern ObstacleModel obstacleModel;
#endif // OBSTACLE_MODEL_H