GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 81.4% 105 / 0 / 129
Functions: 100.0% 12 / 0 / 12
Branches: 80.3% 49 / 0 / 61
Decisions: 78.4% 29 / - / 37

firmware/controllers/sensors/impl/AemXSeriesLambda.cpp
Line Branch Decision Exec Source
1 #include "pch.h"
2 #include "ego.h"
3
4 #if EFI_CAN_SUPPORT || EFI_UNIT_TEST
5 #include "AemXSeriesLambda.h"
6 #include "wideband_firmware/for_rusefi/wideband_can.h"
7 #include "rusefi_wideband.h"
8
9 static constexpr uint32_t aem_base = 0x180;
10 static constexpr uint32_t rusefi_base = WB_DATA_BASE_ADDR;
11 // sensor transmits at 100hz, allow a frame to be missed
12 static constexpr efidur_t sensor_timeout = MS2NT(3 * WBO_TX_PERIOD_MS);
13
14 8 AemXSeriesWideband::AemXSeriesWideband(uint8_t sensorIndex, SensorType type)
15 : CanSensorBase(
16 0, // ID passed here doesn't matter since we override acceptFrame
17 type,
18 sensor_timeout
19 )
20 8 , m_sensorIndex(sensorIndex)
21 {
22 8 stateCode = m_stateCode = static_cast<uint8_t>(wbo::Status::CanSilent);// silent, initial state is "no one has spoken to us so far"
23 8 isValid = m_isFault = m_afrIsValid = false;
24 // wait for first rusEFI WBO standard frame with protocol version field
25 8 fwUnsupported = true;
26 // Do not light "FW: need update" indicator until we get some data from WBO
27 8 fwOutdated = false;
28 8 }
29
30 56 can_wbo_type_e AemXSeriesWideband::sensorType() const {
31 56 return engineConfiguration->canWbo[m_sensorIndex].type;
32 }
33
34 13 uint32_t AemXSeriesWideband::getReCanId() const {
35 // 0th sensor is 0x190 and 0x191, 1st sensor is 0x192 and 0x193
36 13 return rusefi_base + 2 * engineConfiguration->canWbo[m_sensorIndex].reId;
37 }
38
39 5 uint32_t AemXSeriesWideband::getAemCanId() const {
40 // 0th sensor is 0x00000180, 1st sensor is 0x00000181, etc
41 5 return aem_base + engineConfiguration->canWbo[m_sensorIndex].aemId;
42 }
43
44 21 bool AemXSeriesWideband::acceptFrame(const size_t busIndex, const CANRxFrame& frame) const {
45 #if !EFI_UNIT_TEST
46 if (busIndex != getWidebandBus()) {
47 return false;
48 }
49 #endif
50
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 21 times.
21 if (frame.DLC != 8) {
52 return false;
53 }
54
55 21 can_wbo_type_e type = sensorType();
56
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 21 times.
21 if (type == DISABLED) {
58 return false;
59 }
60
61 // RusEFI wideband uses standard CAN IDs
62
4/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 1 time.
2/2
✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 8 times.
21 if ((!CAN_ISX(frame)) && (type == RUSEFI)) {
63 // 0th sensor is 0x190 and 0x191, 1st sensor is 0x192 and 0x193
64 13 uint32_t rusefiBaseId = getReCanId();
65
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
13 return ((CAN_SID(frame) == rusefiBaseId) || (CAN_SID(frame) == rusefiBaseId + 1));
66 }
67
68 // AEM uses extended CAN ID
69
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 2 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 3 times.
8 if ((CAN_ISX(frame)) && (type == AEM)) {
70 // 0th sensor is 0x00000180, 1st sensor is 0x00000181, etc
71 5 uint32_t aemXSeriesId = getAemCanId();
72 5 return (CAN_EID(frame) == aemXSeriesId);
73 }
74
75 3 return false;
76 }
77
78 12 bool AemXSeriesWideband::isHeaterAllowed() {
79
4/4
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 4 times.
12 return ((sensorType() == AEM) || (engine->engineState.heaterControlEnabled));
80 }
81
82 11 void AemXSeriesWideband::refreshState() {
83 11 can_wbo_type_e type = sensorType();
84
85 // Report ECU to WBO allow state
86 11 allowed = isHeaterAllowed();
87
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 11 times.
11 if (getTimeNowNt() - sensor_timeout > m_lastUpdate) {
88 canSilent = true;
89 isValid = false;
90 return;
91 }
92 11 canSilent = false;
93
94
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
2/2
✓ Decision 'true' taken 8 times.
✓ Decision 'false' taken 3 times.
11 if (type == RUSEFI) {
95 // This is RE WBO
96 8 isValid = m_afrIsValid;
97 // Report state code from WBO
98 8 stateCode = m_stateCode;
99 8 return;
100
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 3 times.
✗ Decision 'false' not taken.
3 } else if (type == AEM) {
101 // This is AEM with two flags only and no debug fields
102 3 heaterDuty = 0;
103 3 pumpDuty = 0;
104 3 tempC = 0;
105 3 nernstVoltage = 0;
106
107
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 2 times.
3 if (m_isFault) {
108 1 stateCode = HACK_INVALID_AEM;
109 1 isValid = false;
110 } else {
111 // TODO: better status code?
112
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 stateCode = m_afrIsValid ?
113 static_cast<uint8_t>(wbo::Status::RunningClosedLoop) :
114 static_cast<uint8_t>(wbo::Status::Warmup);
115 2 isValid = m_afrIsValid;
116 }
117 3 return;
118 } else {
119 // disabled or analog
120 // clear all livedata
121 heaterDuty = 0;
122 pumpDuty = 0;
123 tempC = 0;
124 nernstVoltage = 0;
125 isValid = false;
126 return;
127 }
128
129 // non configured
130 stateCode = static_cast<uint8_t>(wbo::Status::CanSilent);
131 }
132
133 12 void AemXSeriesWideband::decodeFrame(const CANRxFrame& frame, efitick_t nowNt) {
134 12 bool accepted = false;
135 // accept frame has already guaranteed that this message belongs to us
136 // We just have to check if it's AEM or rusEFI
137
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
2/2
✓ Decision 'true' taken 9 times.
✓ Decision 'false' taken 3 times.
12 if (sensorType() == RUSEFI){
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 uint32_t id = CAN_ID(frame);
139
140 // rusEFI custom format
141
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 5 times.
9 if ((id & 0x1) != 0) {
142 // low bit is set, this is the "diag" frame
143 4 decodeRusefiDiag(frame);
144 } else {
145 // low bit not set, this is standard frame
146 5 accepted = decodeRusefiStandard(frame, nowNt);
147 }
148 } else /* if (sensorType() == AEM) */ {
149 3 accepted = decodeAemXSeries(frame, nowNt);
150 }
151
152
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
2/2
✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 6 times.
11 if (accepted) {
153 5 m_lastUpdate = nowNt;
154 }
155
156 // Do refresh on each CAN packet
157 11 refreshState();
158 11 }
159
160 /**
161 * @return true if valid, false if invalid
162 */
163 6 bool AemXSeriesWideband::decodeAemXSeries(const CANRxFrame& frame, efitick_t nowNt) {
164 // we don't care
165 6 fwUnsupported = false;
166 6 fwOutdated = false;
167
168 // reports in 0.0001 lambda per LSB
169 6 uint16_t lambdaInt = SWAP_UINT16(frame.data16[0]);
170 6 float lambdaFloat = 0.0001f * lambdaInt;
171
172 // bit 6 indicates sensor fault
173 6 m_isFault = frame.data8[7] & 0x40;
174 // bit 7 indicates valid
175 6 m_afrIsValid = frame.data8[6] & 0x80;
176
177
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 2 times.
6 if ((m_isFault) || (!m_afrIsValid)) {
178 4 invalidate();
179 4 return false;
180 }
181
182 2 setValidValue(lambdaFloat, nowNt);
183 2 refreshSmoothedLambda(lambdaFloat);
184 2 return true;
185 }
186
187 5 bool AemXSeriesWideband::decodeRusefiStandard(const CANRxFrame& frame, efitick_t nowNt) {
188 5 auto data = reinterpret_cast<const wbo::StandardData*>(&frame.data8[0]);
189
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 5 times.
5 if (data->Version > RUSEFI_WIDEBAND_VERSION) {
191 firmwareError(ObdCode::OBD_WB_FW_Mismatch, "Wideband controller index %d has newer protocol version (0x%02x while 0x%02x supported), please update ECU FW!",
192 m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION);
193 fwUnsupported = true;
194 return false;
195 }
196
197
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 4 times.
5 if (data->Version < RUSEFI_WIDEBAND_VERSION_MIN) {
198 1 firmwareError(ObdCode::OBD_WB_FW_Mismatch, "Wideband controller index %d has outdated protocol version (0x%02x while minimum 0x%02x expected), please update WBO!",
199 1 m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION_MIN);
200 fwUnsupported = true;
201 return false;
202 }
203
204 4 fwUnsupported = false;
205 // compatible, but not latest
206 4 fwOutdated = (data->Version != RUSEFI_WIDEBAND_VERSION);
207 // TODO: request and check builddate
208
209 4 tempC = data->TemperatureC;
210 4 float lambda = 0.0001f * data->Lambda;
211 4 m_afrIsValid = data->Valid & 0x01;
212
213
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2 times.
4 if (!m_afrIsValid) {
214 2 invalidate();
215 } else {
216 2 setValidValue(lambda, nowNt);
217 2 refreshSmoothedLambda(lambda);
218 }
219
220 4 return true;
221 }
222
223 4 void AemXSeriesWideband::refreshSmoothedLambda(float lambda) {
224
1/3
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 switch (type()) {
225
1/1
✓ Decision 'true' taken 4 times.
4 case SensorType::Lambda1: {
226 4 expAverageLambda1.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha);
227 4 smoothedLambda1Sensor.setValidValue(expAverageLambda1.initOrAverage(lambda), getTimeNowNt());
228 4 break;
229 }
230 case SensorType::Lambda2: {
231 expAverageLambda2.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha);
232 smoothedLambda2Sensor.setValidValue(expAverageLambda2.initOrAverage(lambda), getTimeNowNt());
233 break;
234 }
235 default:
236 break;
237 }
238 4 }
239
240 4 void AemXSeriesWideband::decodeRusefiDiag(const CANRxFrame& frame) {
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 4 times.
4 if (fwUnsupported) {
242 return;
243 }
244
245 4 auto data = reinterpret_cast<const wbo::DiagData*>(&frame.data8[0]);
246
247 // convert to percent
248 4 heaterDuty = data->HeaterDuty / 2.55f;
249 4 pumpDuty = data->PumpDuty / 2.55f;
250
251 // convert to volts
252 4 nernstVoltage = data->NernstDc * 0.001f;
253
254 // no conversion, just ohms
255 4 esr = data->Esr;
256
257 // This state is handle in refreshState()
258 //if (!isHeaterAllowed()) {
259 // m_stateCode = HACK_CRANKING_VALUE;
260 // return;
261 //}
262
263 4 m_stateCode = static_cast<uint8_t>(data->status);
264
265
5/6
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 3 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 3 times.
4 if ((isStatusError(static_cast<wbo::Status>(data->status))) && isHeaterAllowed()) {
266
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 auto code = m_sensorIndex == 0 ? ObdCode::Wideband_1_Fault : ObdCode::Wideband_2_Fault;
267 1 warning(code, "Wideband #%d fault: %s", (m_sensorIndex + 1), wbo::describeStatus(data->status));
268 }
269 }
270
271 #endif
272