GCC Code Coverage Report


Directory: ./
File: firmware/controllers/can/obd2.cpp
Date: 2025-10-03 00:57:22
Warnings: 1 unchecked decisions!
Coverage Exec Excl Total
Lines: 63.6% 68 0 107
Functions: 66.7% 4 0 6
Branches: 50.9% 27 0 53
Decisions: 47.1% 16 - 34

Line Branch Decision Exec Source
1 /*
2 * @file obd2.cpp
3 *
4 * ISO 15765-4
5 * http://en.wikipedia.org/wiki/OBD-II_PIDs
6 *
7 * @date Jun 9, 2015
8 * @author Andrey Belomutskiy, (c) 2012-2020
9 *
10 * This file is part of rusEfi - see http://rusefi.com
11 *
12 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
13 * the GNU General Public License as published by the Free Software Foundation; either
14 * version 3 of the License, or (at your option) any later version.
15 *
16 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
17 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along with this program.
21 * If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "pch.h"
25
26 #if EFI_CAN_SUPPORT || EFI_UNIT_TEST
27
28 #include "obd2.h"
29 #include "can.h"
30 #include "can_msg_tx.h"
31 #include "fuel_math.h"
32
33 static const int16_t supportedPids0120[] = {
34 PID_MONITOR_STATUS,
35 PID_FUEL_SYSTEM_STATUS,
36 PID_ENGINE_LOAD,
37 PID_COOLANT_TEMP,
38 PID_STFT_BANK1,
39 PID_STFT_BANK2,
40 PID_INTAKE_MAP,
41 PID_RPM,
42 PID_SPEED,
43 PID_TIMING_ADVANCE,
44 PID_INTAKE_TEMP,
45 PID_THROTTLE,
46 -1
47 };
48
49 static const int16_t supportedPids2140[] = {
50 PID_FUEL_AIR_RATIO_1,
51 -1
52 };
53
54 static const int16_t supportedPids4160[] = {
55 PID_CONTROL_UNIT_VOLTAGE,
56 PID_ETHANOL,
57 PID_FUEL_RATE,
58 PID_OIL_TEMPERATURE,
59 -1
60 };
61
62 15 void obdSendPacket(int mode, int PID, int numBytes, uint32_t iValue, size_t busIndex) {
63
1/1
✓ Branch 2 taken 15 times.
15 CanTxMessage resp(CanCategory::OBD, OBD_TEST_RESPONSE);
64
65 // Respond on the same bus we got the request from
66 15 resp.busIndex = busIndex;
67
68 // write number of bytes
69
1/1
✓ Branch 1 taken 15 times.
15 resp[0] = (uint8_t)(2 + numBytes);
70 // write 2 bytes of header
71
1/1
✓ Branch 1 taken 15 times.
15 resp[1] = (uint8_t)(0x40 + mode);
72
1/1
✓ Branch 1 taken 15 times.
15 resp[2] = (uint8_t)PID;
73 // write N data bytes
74
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 15 times.
2/2
✓ Decision 'true' taken 29 times.
✓ Decision 'false' taken 15 times.
44 for (int i = 8 * (numBytes - 1), j = 3; i >= 0; i -= 8, j++) {
75
1/1
✓ Branch 1 taken 29 times.
29 resp[j] = (uint8_t)((iValue >> i) & 0xff);
76 }
77 30 }
78
79 #define _1_MODE 1
80
81 10 static void obdSendValue(int mode, int PID, int numBytes, float value, size_t busIndex) {
82
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 efiAssertVoid(ObdCode::CUSTOM_ERR_6662, numBytes <= 2, "invalid numBytes");
83 10 int iValue = (int)efiRound(value, 1.0f);
84 // clamp to uint8_t (0..255) or uint16_t (0..65535)
85
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 iValue = maxI(minI(iValue, (numBytes == 1) ? 255 : 65535), 0);
86 10 obdSendPacket(mode, PID, numBytes, iValue, busIndex);
87 }
88
89
90 // #define MOCK_SUPPORTED_PIDS 0xffffffff
91
92 3 void obdWriteSupportedPids(int PID, int bitOffset, const int16_t *supportedPids, size_t busIndex) {
93 3 uint32_t value = 0;
94 // gather all 32 bit fields
95
3/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 3 times.
0/1
? Decision couldn't be analyzed.
20 for (int i = 0; i < 32 && supportedPids[i] > 0; i++)
96 17 value |= 1 << (31 + bitOffset - supportedPids[i]);
97
98 #ifdef MOCK_SUPPORTED_PIDS
99 // for OBD debug
100 value = MOCK_SUPPORTED_PIDS;
101 #endif
102
103 3 obdSendPacket(1, PID, 4, value, busIndex);
104 3 }
105
106 14 void handleGetDataRequest(const CANRxFrame& rx, size_t busIndex) {
107 14 int pid = rx.data8[2];
108
14/22
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 time.
✓ Branch 14 taken 1 time.
✓ Branch 15 taken 1 time.
✓ Branch 16 taken 1 time.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
14 switch (pid) {
109
1/1
✓ Decision 'true' taken 1 time.
1 case PID_SUPPORTED_PIDS_REQUEST_01_20:
110 1 obdWriteSupportedPids(pid, 1, supportedPids0120, busIndex);
111 1 break;
112
1/1
✓ Decision 'true' taken 1 time.
1 case PID_SUPPORTED_PIDS_REQUEST_21_40:
113 1 obdWriteSupportedPids(pid, 0x21, supportedPids2140, busIndex);
114 1 break;
115
1/1
✓ Decision 'true' taken 1 time.
1 case PID_SUPPORTED_PIDS_REQUEST_41_60:
116 1 obdWriteSupportedPids(pid, 0x41, supportedPids4160, busIndex);
117 1 break;
118 case PID_MONITOR_STATUS:
119 obdSendPacket(1, pid, 4, 0, busIndex); // todo: add statuses
120 break;
121 case PID_FUEL_SYSTEM_STATUS:
122 // todo: add statuses
123 obdSendValue(_1_MODE, pid, 2, (2<<8)|(0), busIndex); // 2 = "Closed loop, using oxygen sensor feedback to determine fuel mix"
124 break;
125
1/1
✓ Decision 'true' taken 1 time.
1 case PID_ENGINE_LOAD:
126 1 obdSendValue(_1_MODE, pid, 1, getFuelingLoad() * ODB_TPS_BYTE_PERCENT, busIndex);
127 1 break;
128
1/1
✓ Decision 'true' taken 1 time.
1 case PID_COOLANT_TEMP:
129 1 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::Clt) + ODB_TEMP_EXTRA, busIndex);
130 1 break;
131
1/1
✓ Decision 'true' taken 1 time.
1 case PID_STFT_BANK1:
132 1 obdSendValue(_1_MODE, pid, 1, 128 * engine->engineState.stftCorrection[0], busIndex);
133 1 break;
134
1/1
✓ Decision 'true' taken 1 time.
1 case PID_STFT_BANK2:
135 1 obdSendValue(_1_MODE, pid, 1, 128 * engine->engineState.stftCorrection[1], busIndex);
136 1 break;
137
1/1
✓ Decision 'true' taken 1 time.
1 case PID_INTAKE_MAP:
138 1 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::Map), busIndex);
139 1 break;
140
1/1
✓ Decision 'true' taken 1 time.
1 case PID_RPM:
141 1 obdSendValue(_1_MODE, pid, 2, Sensor::getOrZero(SensorType::Rpm) * ODB_RPM_MULT, busIndex); // rotation/min. (A*256+B)/4
142 1 break;
143
1/1
✓ Decision 'true' taken 1 time.
1 case PID_SPEED:
144 1 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::VehicleSpeed), busIndex);
145 1 break;
146 case PID_TIMING_ADVANCE: {
147 float timing = engine->engineState.timingAdvance[0];
148 timing = (timing > 360.0f) ? (timing - 720.0f) : timing;
149 obdSendValue(_1_MODE, pid, 1, (timing + 64.0f) * 2.0f, busIndex); // angle before TDC. (A/2)-64
150 break;
151 }
152
1/1
✓ Decision 'true' taken 1 time.
1 case PID_INTAKE_TEMP:
153 1 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::Iat) + ODB_TEMP_EXTRA, busIndex);
154 1 break;
155
1/1
✓ Decision 'true' taken 1 time.
1 case PID_INTAKE_MAF:
156 1 obdSendValue(_1_MODE, pid, 2, Sensor::getOrZero(SensorType::Maf) * 100.0f, busIndex); // grams/sec (A*256+B)/100
157 1 break;
158
1/1
✓ Decision 'true' taken 1 time.
1 case PID_THROTTLE:
159 1 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::Tps1) * ODB_TPS_BYTE_PERCENT, busIndex); // (A*100/255)
160 1 break;
161
1/1
✓ Decision 'true' taken 1 time.
1 case PID_FUEL_AIR_RATIO_1: {
162 1 float lambda = clampF(0, Sensor::getOrZero(SensorType::Lambda1), 1.99f);
163
164 1 uint16_t scaled = lambda * 32768;
165
166 1 obdSendPacket(1, pid, 4, scaled << 16, busIndex);
167 1 break;
168 } case PID_FUEL_RATE: {
169
170 #ifdef MODULE_ODOMETER
171 float gPerSecond = engine->module<TripOdometer>()->getConsumptionGramPerSecond();
172 #else
173 float gPerSecond = 0;
174 #endif // MODULE_ODOMETER
175
176 float gPerHour = gPerSecond * 3600;
177 float literPerHour = gPerHour * 0.00139f;
178 obdSendValue(_1_MODE, pid, 2, literPerHour * 20.0f, busIndex); // L/h. (A*256+B)/20
179 break;
180 } case PID_CONTROL_UNIT_VOLTAGE: {
181 obdSendValue(_1_MODE, pid, 2, 1000 * Sensor::getOrZero(SensorType::BatteryVoltage), busIndex);
182 break;
183 } case PID_ETHANOL: {
184 obdSendValue(_1_MODE, pid, 1, (255.0f / 100) * Sensor::getOrZero(SensorType::FuelEthanolPercent), busIndex);
185 break;
186 } case PID_OIL_TEMPERATURE: {
187 obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::OilTemperature) + ODB_TEMP_EXTRA, busIndex);
188 break;
189 } default:
190 // ignore unhandled PIDs
191 break;
192 }
193 14 }
194
195 static void handleDtcRequest(int numCodes, ObdCode* dtcCode) {
196 // TODO: this appears to be unfinished?
197 UNUSED(numCodes);
198 UNUSED(dtcCode);
199
200 // int numBytes = numCodes * 2;
201 // // write CAN-TP Single Frame header?
202 // txmsg.data8[0] = (uint8_t)((0 << 4) | numBytes);
203 // for (int i = 0, j = 1; i < numCodes; i++) {
204 // txmsg.data8[j++] = (uint8_t)((dtcCode[i] >> 8) & 0xff);
205 // txmsg.data8[j++] = (uint8_t)(dtcCode[i] & 0xff);
206 // }
207 }
208
209 #if HAS_CAN_FRAME
210 void obdOnCanPacketRx(const CANRxFrame& rx, size_t busIndex) {
211 if (CAN_SID(rx) != OBD_TEST_REQUEST) {
212 return;
213 }
214
215 if (rx.data8[0] == _OBD_2 && rx.data8[1] == OBD_CURRENT_DATA) {
216 handleGetDataRequest(rx, busIndex);
217 } else if (rx.data8[0] == 1 && rx.data8[1] == OBD_STORED_DIAGNOSTIC_TROUBLE_CODES) {
218 // todo: implement stored/pending difference?
219 handleDtcRequest(1, &engine->engineState.warnings.lastErrorCode);
220 } else if (rx.data8[0] == 1 && rx.data8[1] == OBD_PENDING_DIAGNOSTIC_TROUBLE_CODES) {
221 // todo: implement stored/pending difference?
222 handleDtcRequest(1, &engine->engineState.warnings.lastErrorCode);
223 }
224 }
225 #endif /* HAS_CAN_FRAME */
226
227 #endif /* EFI_CAN_SUPPORT */
228