| 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 | faultCode = m_faultCode = static_cast<uint8_t>(wbo::Fault::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 | 53 | can_wbo_type_e AemXSeriesWideband::sensorType() const { | ||
| 31 | 53 | 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 | 9 | bool AemXSeriesWideband::isHeaterAllowed() { | ||
| 79 |
3/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 4 times.
|
9 | return ((sensorType() == AEM) || (engine->engineState.heaterControlEnabled)); | |
| 80 | } | |||
| 81 | ||||
| 82 | 11 | void AemXSeriesWideband::refreshState() { | ||
| 83 | 11 | can_wbo_type_e type = sensorType(); | ||
| 84 | ||||
| 85 |
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) { |
| 86 | ✗ | faultCode = static_cast<uint8_t>(wbo::Fault::CanSilent); | ||
| 87 | ✗ | isValid = false; | ||
| 88 | ✗ | return; | ||
| 89 | } | |||
| 90 | ||||
| 91 |
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) { |
| 92 | // This is RE WBO | |||
| 93 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 4 times.
|
8 | if (!isHeaterAllowed()) { |
| 94 | 4 | faultCode = static_cast<uint8_t>(wbo::Fault::NotAllowed); | ||
| 95 | 4 | isValid = false; | ||
| 96 | // Do not check for any errors while heater is not allowed | |||
| 97 | 4 | return; | ||
| 98 | } | |||
| 99 | ||||
| 100 | 4 | isValid = m_afrIsValid; | ||
| 101 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 3 times.
|
4 | if (m_faultCode != static_cast<uint8_t>(wbo::Fault::None)) { |
| 102 | // Report error code from WBO | |||
| 103 | 1 | faultCode = m_faultCode; | ||
| 104 | 1 | isValid = false; | ||
| 105 | 1 | return; | ||
| 106 | } | |||
| 107 |
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) { |
| 108 | // This is AEM with two flags only and no debug fields | |||
| 109 | 3 | heaterDuty = 0; | ||
| 110 | 3 | pumpDuty = 0; | ||
| 111 | 3 | tempC = 0; | ||
| 112 | 3 | nernstVoltage = 0; | ||
| 113 | ||||
| 114 | 3 | isValid = m_afrIsValid; | ||
| 115 |
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) { |
| 116 | 1 | faultCode = HACK_INVALID_AEM; | ||
| 117 | 1 | isValid = false; | ||
| 118 | 1 | return; | ||
| 119 | } | |||
| 120 | } else { | |||
| 121 | // disabled or analog | |||
| 122 | // clear all livedata | |||
| 123 | ✗ | heaterDuty = 0; | ||
| 124 | ✗ | pumpDuty = 0; | ||
| 125 | ✗ | tempC = 0; | ||
| 126 | ✗ | nernstVoltage = 0; | ||
| 127 | ✗ | isValid = false; | ||
| 128 | ✗ | return; | ||
| 129 | } | |||
| 130 | ||||
| 131 | 5 | faultCode = static_cast<uint8_t>(wbo::Fault::None); | ||
| 132 | } | |||
| 133 | ||||
| 134 | 12 | void AemXSeriesWideband::decodeFrame(const CANRxFrame& frame, efitick_t nowNt) { | ||
| 135 | 12 | bool accepted = false; | ||
| 136 | // accept frame has already guaranteed that this message belongs to us | |||
| 137 | // We just have to check if it's AEM or rusEFI | |||
| 138 |
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){ |
| 139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | uint32_t id = CAN_ID(frame); | |
| 140 | ||||
| 141 | // rusEFI custom format | |||
| 142 |
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) { |
| 143 | // low bit is set, this is the "diag" frame | |||
| 144 | 4 | decodeRusefiDiag(frame); | ||
| 145 | } else { | |||
| 146 | // low bit not set, this is standard frame | |||
| 147 | 5 | accepted = decodeRusefiStandard(frame, nowNt); | ||
| 148 | } | |||
| 149 | } else /* if (sensorType() == AEM) */ { | |||
| 150 | 3 | accepted = decodeAemXSeries(frame, nowNt); | ||
| 151 | } | |||
| 152 | ||||
| 153 |
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) { |
| 154 | 5 | m_lastUpdate = nowNt; | ||
| 155 | } | |||
| 156 | ||||
| 157 | // Do refresh on each CAN packet | |||
| 158 | 11 | refreshState(); | ||
| 159 | 11 | } | ||
| 160 | ||||
| 161 | /** | |||
| 162 | * @return true if valid, false if invalid | |||
| 163 | */ | |||
| 164 | 6 | bool AemXSeriesWideband::decodeAemXSeries(const CANRxFrame& frame, efitick_t nowNt) { | ||
| 165 | // we don't care | |||
| 166 | 6 | fwUnsupported = false; | ||
| 167 | 6 | fwOutdated = false; | ||
| 168 | ||||
| 169 | // reports in 0.0001 lambda per LSB | |||
| 170 | 6 | uint16_t lambdaInt = SWAP_UINT16(frame.data16[0]); | ||
| 171 | 6 | float lambdaFloat = 0.0001f * lambdaInt; | ||
| 172 | ||||
| 173 | // bit 6 indicates sensor fault | |||
| 174 | 6 | m_isFault = frame.data8[7] & 0x40; | ||
| 175 | // bit 7 indicates valid | |||
| 176 | 6 | m_afrIsValid = frame.data8[6] & 0x80; | ||
| 177 | ||||
| 178 |
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)) { |
| 179 | 4 | invalidate(); | ||
| 180 | 4 | return false; | ||
| 181 | } | |||
| 182 | ||||
| 183 | 2 | setValidValue(lambdaFloat, nowNt); | ||
| 184 | 2 | refreshSmoothedLambda(lambdaFloat); | ||
| 185 | 2 | return true; | ||
| 186 | } | |||
| 187 | ||||
| 188 | 5 | bool AemXSeriesWideband::decodeRusefiStandard(const CANRxFrame& frame, efitick_t nowNt) { | ||
| 189 | 5 | auto data = reinterpret_cast<const wbo::StandardData*>(&frame.data8[0]); | ||
| 190 | ||||
| 191 |
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) { |
| 192 | ✗ | firmwareError(ObdCode::OBD_WB_FW_Mismatch, "Wideband controller index %d has newer protocol version (0x%02x while 0x%02x supported), please update ECU FW!", | ||
| 193 | ✗ | m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION); | ||
| 194 | ✗ | fwUnsupported = true; | ||
| 195 | ✗ | return false; | ||
| 196 | } | |||
| 197 | ||||
| 198 |
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) { |
| 199 | 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!", | ||
| 200 | 1 | m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION_MIN); | ||
| 201 | ✗ | fwUnsupported = true; | ||
| 202 | ✗ | return false; | ||
| 203 | } | |||
| 204 | ||||
| 205 | 4 | fwUnsupported = false; | ||
| 206 | // compatible, but not latest | |||
| 207 | 4 | fwOutdated = (data->Version != RUSEFI_WIDEBAND_VERSION); | ||
| 208 | // TODO: request and check builddate | |||
| 209 | ||||
| 210 | 4 | tempC = data->TemperatureC; | ||
| 211 | 4 | float lambda = 0.0001f * data->Lambda; | ||
| 212 | 4 | m_afrIsValid = data->Valid & 0x01; | ||
| 213 | ||||
| 214 |
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) { |
| 215 | 2 | invalidate(); | ||
| 216 | } else { | |||
| 217 | 2 | setValidValue(lambda, nowNt); | ||
| 218 | 2 | refreshSmoothedLambda(lambda); | ||
| 219 | } | |||
| 220 | ||||
| 221 | 4 | return true; | ||
| 222 | } | |||
| 223 | ||||
| 224 | 4 | void AemXSeriesWideband::refreshSmoothedLambda(float lambda) { | ||
| 225 |
1/3✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | switch (type()) { | |
| 226 |
1/1✓ Decision 'true' taken 4 times.
|
4 | case SensorType::Lambda1: { | |
| 227 | 4 | expAverageLambda1.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha); | ||
| 228 | 4 | smoothedLambda1Sensor.setValidValue(expAverageLambda1.initOrAverage(lambda), getTimeNowNt()); | ||
| 229 | 4 | break; | ||
| 230 | } | |||
| 231 | ✗ | case SensorType::Lambda2: { | ||
| 232 | ✗ | expAverageLambda2.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha); | ||
| 233 | ✗ | smoothedLambda2Sensor.setValidValue(expAverageLambda2.initOrAverage(lambda), getTimeNowNt()); | ||
| 234 | ✗ | break; | ||
| 235 | } | |||
| 236 | ✗ | default: | ||
| 237 | ✗ | break; | ||
| 238 | } | |||
| 239 | 4 | } | ||
| 240 | ||||
| 241 | 4 | void AemXSeriesWideband::decodeRusefiDiag(const CANRxFrame& frame) { | ||
| 242 |
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) { |
| 243 | ✗ | return; | ||
| 244 | } | |||
| 245 | ||||
| 246 | 4 | auto data = reinterpret_cast<const wbo::DiagData*>(&frame.data8[0]); | ||
| 247 | ||||
| 248 | // convert to percent | |||
| 249 | 4 | heaterDuty = data->HeaterDuty / 2.55f; | ||
| 250 | 4 | pumpDuty = data->PumpDuty / 2.55f; | ||
| 251 | ||||
| 252 | // convert to volts | |||
| 253 | 4 | nernstVoltage = data->NernstDc * 0.001f; | ||
| 254 | ||||
| 255 | // no conversion, just ohms | |||
| 256 | 4 | esr = data->Esr; | ||
| 257 | ||||
| 258 | // This state is handle in refreshState() | |||
| 259 | //if (!isHeaterAllowed()) { | |||
| 260 | // m_faultCode = HACK_CRANKING_VALUE; | |||
| 261 | // return; | |||
| 262 | //} | |||
| 263 | ||||
| 264 | 4 | m_faultCode = static_cast<uint8_t>(data->Status); | ||
| 265 | ||||
| 266 |
5/6✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 3 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 3 times.
|
4 | if ((data->Status != wbo::Fault::None) && isHeaterAllowed()) { |
| 267 |
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; | |
| 268 | 1 | warning(code, "Wideband #%d fault: %s", (m_sensorIndex + 1), wbo::describeFault(data->Status)); | ||
| 269 | } | |||
| 270 | } | |||
| 271 | ||||
| 272 | #endif | |||
| 273 |