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
|
#ifndef __OBD2_H__
#define __OBD2_H__
#include <isotp/isotp.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_OBD2_PAYLOAD_LENGTH 7
#define VIN_LENGTH 17
typedef struct {
uint16_t arbitration_id;
uint8_t mode;
uint16_t pid;
uint8_t pid_length;
uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH];
uint8_t payload_length;
} DiagnosticRequest;
// TODO I don't like this, it's hard coding isotp library stuff here
typedef bool (*SendIsoTpMessageShim)(IsoTpHandler* handler,
const uint8_t* payload, uint16_t payload_size);
// Thanks to
// http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1
// for the list of NRCs
typedef enum {
NRC_SUCCESS = 0x0,
NRC_SERVICE_NOT_SUPPORTED = 0x11,
NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12,
NRC_CONDITIONS_NOT_CORRECT = 0x22,
NRC_REQUEST_OUT_OF_RANGE = 0x31,
NRC_SECURITY_ACCESS_DENIED = 0x33,
NRC_INVALID_KEY = 0x35,
NRC_TOO_MANY_ATTEMPS = 0x36,
NRC_TIME_DELAY_NOT_EXPIRED = 0x37,
NRC_RESPONSE_PENDING = 0x78
} DiagnosticNegativeResponseCode;
typedef enum {
OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1,
OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2,
OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3,
OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4,
// 0x5 is for non-CAN only
// OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5,
OBD2_MODE_TEST_RESULTS = 0x6,
OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7,
OBD2_MODE_CONTROL = 0x8,
OBD2_MODE_VEHICLE_INFORMATION = 0x9,
OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa,
// this one isn't technically in OBD2, but both of the enhanced standards
// have their PID requests at 0x22
OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22
} DiagnosticMode;
typedef enum {
DTC_EMISSIONS,
DTC_DRIVE_CYCLE,
DTC_PERMANENT
} DiagnosticTroubleCodeType;
typedef struct {
uint16_t arbitration_id;
uint8_t mode;
bool success;
// if mode is one with a PID, read the correct numbers of PID bytes (1 or 2)
// into this field, then store the remainder of the payload in the payload
// field
uint16_t pid;
DiagnosticNegativeResponseCode negative_response_code;
// if response mode is a negative response, read first byte of payload into
// NRC and store remainder of payload in payload field
uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH];
uint8_t payload_length;
} DiagnosticResponse;
typedef enum {
POWERTRAIN = 0x0,
CHASSIS = 0x1,
BODY = 0x2,
NETWORK = 0x3
} DiagnosticTroubleCodeGroup;
typedef struct {
DiagnosticTroubleCodeGroup group;
uint8_t group_num;
uint8_t code;
} DiagnosticTroubleCode;
typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response);
typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status);
typedef void (*DiagnosticVinReceived)(uint8_t vin[]);
typedef void (*DiagnosticTroubleCodesReceived)(
DiagnosticMode mode, DiagnosticTroubleCode* codes);
typedef void (*DiagnosticPidEnumerationReceived)(
const DiagnosticResponse* response, uint16_t* pids);
// TODO should we enumerate every OBD-II PID? need conversion formulas, too
typedef struct {
uint16_t pid;
uint8_t bytes_returned;
float min_value;
float max_value;
} DiagnosticParameter;
typedef enum {
DIAGNOSTIC_REQUEST_TYPE_PID,
DIAGNOSTIC_REQUEST_TYPE_DTC,
DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS,
DIAGNOSTIC_REQUEST_TYPE_VIN
} DiagnosticRequestType;
typedef struct {
IsoTpHandler isotp_handler;
// TODO the Handle may need to keep the original request, otherwise we can't
// compare an incoming CAN message to see if it matches the service / PID!
// TODO i'm not sure this type/callback in here is too useful - see the
// comments in obd2.c:diagnostic_request
DiagnosticRequestType type;
DiagnosticResponseReceived callback;
DiagnosticMilStatusReceived mil_status_callback;
DiagnosticVinReceived vin_callback;
bool status;
} DiagnosticRequestHandle;
typedef enum {
DIAGNOSTIC_STANDARD_PID,
DIAGNOSTIC_ENHANCED_PID
} DiagnosticPidRequestType;
typedef struct {
SetTimerShim set_timer;
SendCanMessageShim send_can_message;
LogShim log;
} DiagnosticShims;
DiagnosticShims diagnostic_init_shims(LogShim log,
SendCanMessageShim send_can_message,
SetTimerShim set_timer);
DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
DiagnosticRequest* request, DiagnosticResponseReceived callback);
// decide mode 0x1 / 0x22 based on pid type
DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
DiagnosticPidRequestType pid_request_type, uint16_t pid,
DiagnosticResponseReceived callback);
DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status(
DiagnosticShims* shims,
DiagnosticMilStatusReceived callback);
DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims,
DiagnosticVinReceived callback);
DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims,
DiagnosticTroubleCodeType dtc_type,
DiagnosticTroubleCodesReceived callback);
bool diagnostic_clear_dtc(DiagnosticShims* shims);
DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims,
DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback);
// TODO
// void diagnostic_receive_isotp_message(DiagnosticRequestHandle* handle,
// const IsoTpMessage* message);
void diagnostic_receive_isotp_message(const IsoTpMessage* message);
void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle,
const uint16_t arbitration_id, const uint8_t data[],
const uint8_t size);
#ifdef __cplusplus
}
#endif
#endif // __OBD2_H__
|