GCC Code Coverage Report


Directory: ./
File: firmware/controllers/sensors/sensor_checker.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 46.3% 57 0 123
Functions: 100.0% 6 0 6
Branches: 34.6% 27 0 78
Decisions: 29.7% 22 - 74

Line Branch Decision Exec Source
1 #include "pch.h"
2
3 // Decode what OBD code we should use for a particular [sensor, code] problem
4 14 static ObdCode getCode(SensorType type, UnexpectedCode code) {
5
4/11
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
14 switch (type) {
6
1/1
✓ Decision 'true' taken 2 times.
2 case SensorType::Tps1:
7 case SensorType::Tps1Primary:
8
1/5
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1/1
✓ Decision 'true' taken 2 times.
2 switch (code) {
9
1/1
✓ Decision 'true' taken 2 times.
2 case UnexpectedCode::Timeout: return ObdCode::OBD_TPS1_Primary_Timeout;
10 case UnexpectedCode::Low: return ObdCode::OBD_TPS1_Primary_Low;
11 case UnexpectedCode::High: return ObdCode::OBD_TPS1_Primary_High;
12 case UnexpectedCode::Inconsistent: return ObdCode::OBD_TPS1_Correlation;
13 default: break;
14 } break;
15
1/1
✓ Decision 'true' taken 4 times.
4 case SensorType::Tps1Secondary:
16
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 switch (code) {
17 case UnexpectedCode::Timeout: return ObdCode::OBD_TPS1_Secondary_Timeout;
18 case UnexpectedCode::Low: return ObdCode::OBD_TPS1_Secondary_Low;
19 case UnexpectedCode::High: return ObdCode::OBD_TPS1_Secondary_High;
20
1/1
✓ Decision 'true' taken 4 times.
4 default: break;
21 4 } break;
22 case SensorType::Tps2:
23 case SensorType::Tps2Primary:
24 switch (code) {
25 case UnexpectedCode::Timeout: return ObdCode::OBD_TPS2_Primary_Timeout;
26 case UnexpectedCode::Low: return ObdCode::OBD_TPS2_Primary_Low;
27 case UnexpectedCode::High: return ObdCode::OBD_TPS2_Primary_High;
28 case UnexpectedCode::Inconsistent: return ObdCode::OBD_TPS2_Correlation;
29 default: break;
30 } break;
31 case SensorType::Tps2Secondary:
32 switch (code) {
33 case UnexpectedCode::Timeout: return ObdCode::OBD_TPS2_Secondary_Timeout;
34 case UnexpectedCode::Low: return ObdCode::OBD_TPS2_Secondary_Low;
35 case UnexpectedCode::High: return ObdCode::OBD_TPS2_Secondary_High;
36 default: break;
37 } break;
38
39
1/1
✓ Decision 'true' taken 4 times.
4 case SensorType::AcceleratorPedal:
40 case SensorType::AcceleratorPedalPrimary:
41
2/5
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
1/1
✓ Decision 'true' taken 4 times.
4 switch (code) {
42
1/1
✓ Decision 'true' taken 2 times.
2 case UnexpectedCode::Timeout: return ObdCode::OBD_PPS_Primary_Timeout;
43 case UnexpectedCode::Low: return ObdCode::OBD_PPS_Primary_Low;
44 case UnexpectedCode::High: return ObdCode::OBD_PPS_Primary_High;
45
1/1
✓ Decision 'true' taken 2 times.
2 case UnexpectedCode::Inconsistent: return ObdCode::OBD_PPS_Correlation;
46 default: break;
47 } break;
48
1/1
✓ Decision 'true' taken 4 times.
4 case SensorType::AcceleratorPedalSecondary:
49
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 switch (code) {
50 case UnexpectedCode::Timeout: return ObdCode::OBD_PPS_Secondary_Timeout;
51 case UnexpectedCode::Low: return ObdCode::OBD_PPS_Secondary_Low;
52 case UnexpectedCode::High: return ObdCode::OBD_PPS_Secondary_High;
53
1/1
✓ Decision 'true' taken 4 times.
4 default: break;
54 4 } break;
55
56 case SensorType::Map:
57 switch (code) {
58 case UnexpectedCode::Timeout: return ObdCode::OBD_Map_Timeout;
59 case UnexpectedCode::Low: return ObdCode::OBD_Map_Low;
60 case UnexpectedCode::High: return ObdCode::OBD_Map_High;
61 default: break;
62 } break;
63 case SensorType::Clt:
64 switch (code) {
65 case UnexpectedCode::Timeout: return ObdCode::OBD_Clt_Timeout;
66 case UnexpectedCode::Low: return ObdCode::OBD_Clt_Low;
67 case UnexpectedCode::High: return ObdCode::OBD_Clt_High;
68 default: break;
69 } break;
70 case SensorType::Iat:
71 switch (code) {
72 case UnexpectedCode::Timeout: return ObdCode::OBD_Iat_Timeout;
73 case UnexpectedCode::Low: return ObdCode::OBD_Iat_Low;
74 case UnexpectedCode::High: return ObdCode::OBD_Iat_High;
75 default: break;
76 } break;
77 case SensorType::FuelEthanolPercent:
78 switch (code) {
79 case UnexpectedCode::Timeout: return ObdCode::OBD_FlexSensor_Timeout;
80 case UnexpectedCode::Low: return ObdCode::OBD_FlexSensor_Low;
81 case UnexpectedCode::High: return ObdCode::OBD_FlexSensor_High;
82 default: break;
83 } break;
84 default:
85 break;
86 }
87
88 8 return ObdCode::None;
89 }
90
91 6 inline const char* describeUnexpected(UnexpectedCode code) {
92
2/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 switch (code) {
93
1/1
✓ Decision 'true' taken 4 times.
4 case UnexpectedCode::Timeout: return "has timed out";
94 case UnexpectedCode::High: return "input too high";
95 case UnexpectedCode::Low: return "input too low";
96
1/1
✓ Decision 'true' taken 2 times.
2 case UnexpectedCode::Inconsistent: return "is inconsistent";
97 case UnexpectedCode::Configuration: return "is misconfigured";
98 case UnexpectedCode::Unknown:
99 default:
100 return "unknown";
101 }
102 }
103
104 1246 static void check(SensorType type) {
105 // Don't check sensors we don't have
106
3/3
✓ Branch 1 taken 1246 times.
✓ Branch 3 taken 1044 times.
✓ Branch 4 taken 202 times.
2/2
✓ Decision 'true' taken 1044 times.
✓ Decision 'false' taken 202 times.
1246 if (!Sensor::hasSensor(type)) {
107 1044 return;
108 }
109
110
1/1
✓ Branch 2 taken 202 times.
202 auto result = Sensor::get(type);
111
112 // If the sensor is OK, nothing to check.
113
2/2
✓ Branch 1 taken 188 times.
✓ Branch 2 taken 14 times.
2/2
✓ Decision 'true' taken 188 times.
✓ Decision 'false' taken 14 times.
202 if (result) {
114 188 return;
115 }
116
117 14 ObdCode code = getCode(type, result.Code);
118
119
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
2/2
✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 8 times.
14 if (code != ObdCode::None) {
120
2/2
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 6 times.
6 warning(code, "Sensor fault: %s %s", Sensor::getSensorName(type), describeUnexpected(result.Code));
121 }
122 }
123
124 #if BOARD_EXT_GPIOCHIPS > 0 && EFI_PROD_CODE
125 #if EFI_ENGINE_CONTROL
126 static ObdCode getCodeForInjector(int idx, brain_pin_diag_e diag) {
127 if (idx < 0 || idx >= MAX_CYLINDER_COUNT) {
128 return ObdCode::None;
129 }
130
131 if ((diag & PIN_OPEN) || (diag & PIN_SHORT_TO_GND)) {
132 return (ObdCode)((int)ObdCode::OBD_Injector_Circuit_1_Low + (idx * 3));
133 } else if ((diag & PIN_SHORT_TO_BAT) || (diag & PIN_OVERLOAD)) {
134 return (ObdCode)((int)ObdCode::OBD_Injector_Circuit_1_High + (idx * 3));
135 }
136
137 /* else common error code */
138 return (ObdCode)((int)ObdCode::OBD_Injector_Circuit_1 + idx);
139 }
140 #endif // EFI_ENGINE_CONTROL
141
142 static ObdCode getCodeForIgnition(int idx, brain_pin_diag_e diag) {
143 if (idx < 0 || idx >= MAX_CYLINDER_COUNT) {
144 return ObdCode::None;
145 }
146
147 // TODO: do something more intelligent with `diag`?
148 UNUSED(diag);
149
150 return (ObdCode)((int)ObdCode::OBD_Ignition_Circuit_1 + idx);
151 }
152
153 static uint8_t getTSErrorCode(brain_pin_diag_e diag) {
154 /* Error codes reported to TS:
155 * 0 - output is not used
156 * 1 - ok status/no diagnostic available (TODO: separate codes)
157 * >1 - see brain_pin_diag_e, first least significant 1-bit position + 1 *
158 * Keep in sync with outputDiagErrorList in tunerstudio.template.ini
159 * Note:
160 * diag can be combination of few errors,
161 * while we report only one error to simplify handling on TS side
162 * find position of least significant 1-bit */
163 return __builtin_ffs(diag) + TS_ENUM_OFFSET;
164 }
165 #endif // BOARD_EXT_GPIOCHIPS > 0 && EFI_PROD_CODE
166
167 89 PUBLIC_API_WEAK void boardSensorChecker() {
168 89 }
169
170 1089 void SensorChecker::onSlowCallback() {
171 // Don't check when the ignition is off, or when it was just turned on (let things stabilize)
172 // TODO: also inhibit checking if we just did a flash burn, since that blocks the ECU for a few seconds.
173
3/4
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 1000 times.
✓ Branch 3 taken 89 times.
✗ Branch 4 not taken.
1089 bool shouldCheck = m_ignitionIsOn && m_timeSinceIgnOff.hasElapsedSec(5);
174 1089 m_analogSensorsShouldWork = shouldCheck;
175
2/2
✓ Branch 0 taken 1000 times.
✓ Branch 1 taken 89 times.
2/2
✓ Decision 'true' taken 1000 times.
✓ Decision 'false' taken 89 times.
1089 if (!shouldCheck) {
176 1000 return;
177 }
178
179 // Check sensors
180 89 check(SensorType::Tps1Primary);
181 89 check(SensorType::Tps1Secondary);
182 89 check(SensorType::Tps1);
183 89 check(SensorType::Tps2Primary);
184 89 check(SensorType::Tps2Secondary);
185 89 check(SensorType::Tps2);
186
187 89 check(SensorType::AcceleratorPedalPrimary);
188 89 check(SensorType::AcceleratorPedalSecondary);
189 89 check(SensorType::AcceleratorPedal);
190
191 89 check(SensorType::Map);
192 89 check(SensorType::Map2);
193
194 89 check(SensorType::Clt);
195 89 check(SensorType::Iat);
196
197 89 check(SensorType::FuelEthanolPercent);
198
199 #if EFI_PROD_CODE
200 TunerStudioOutputChannels *state = getTunerStudioOutputChannels();
201 // only bother checking these if we have GPIO chips actually capable of reporting an error
202 #if BOARD_EXT_GPIOCHIPS > 0
203 #if EFI_ENGINE_CONTROL
204 // Check injectors
205 int unhappyInjector = 0;
206 for (size_t i = 0; i < efi::size(enginePins.injectors); i++) {
207 InjectorOutputPin& pin = enginePins.injectors[i];
208
209 // Skip not-configured pins
210 if (!isBrainPinValid(pin.brainPin)) {
211 state->injectorDiagnostic[i] = 0;
212 continue;
213 }
214
215 auto diag = pin.getDiag();
216 if (diag != PIN_OK && diag != PIN_UNKNOWN) {
217 unhappyInjector = 1 + i;
218 auto code = getCodeForInjector(i, diag);
219
220 char description[32];
221 pinDiag2string(description, efi::size(description), diag);
222 warning(code, "Injector %d fault: %s", i + 1, description);
223 }
224 state->injectorDiagnostic[i] = getTSErrorCode(diag);
225 }
226 engine->fuelComputer.brokenInjector = unhappyInjector;
227 engine->fuelComputer.injectorHwIssue = (unhappyInjector != 0);
228 #endif // EFI_ENGINE_CONTROL
229
230 // Check ignition
231 for (size_t i = 0; i < efi::size(enginePins.injectors); i++) {
232 IgnitionOutputPin& pin = enginePins.coils[i];
233
234 // Skip not-configured pins
235 if (!isBrainPinValid(pin.brainPin)) {
236 state->ignitorDiagnostic[i] = 0;
237 continue;
238 }
239
240 auto diag = pin.getDiag();
241 if (diag != PIN_OK && diag != PIN_UNKNOWN) {
242 auto code = getCodeForIgnition(i, diag);
243
244 char description[32];
245 pinDiag2string(description, efi::size(description), diag);
246 warning(code, "Ignition %d fault: %s", i + 1, description);
247 }
248 state->ignitorDiagnostic[i] = getTSErrorCode(diag);
249 }
250 #endif // BOARD_EXT_GPIOCHIPS > 0
251
252 // Check ADC(s) and analog inputs
253 auto code = analogGetDiagnostic();
254 if (code != ObdCode::None) {
255 /* TODO: map to more OBD codes? */
256 warning(code, "Analog subsystem fault");
257 state->isAnalogFailure = true;
258 } else {
259 state->isAnalogFailure = false;
260 }
261 #endif // EFI_PROD_CODE
262
263 89 boardSensorChecker();
264 }
265
266 3 void SensorChecker::onIgnitionStateChanged(bool ignitionOn) {
267 3 m_ignitionIsOn = ignitionOn;
268
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 3 times.
3 if (!ignitionOn) {
270 // timer keeps track of how long since the state was turned to on (ie, how long ago was it last off)
271 m_timeSinceIgnOff.reset();
272 }
273 3 }
274