4 #define MODE_RESPONSE_OFFSET 0x40
5 #define NEGATIVE_RESPONSE_MODE 0x7f
6 #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6
7 #define MODE_BYTE_INDEX 0
8 #define PID_BYTE_INDEX 1
9 #define NEGATIVE_RESPONSE_MODE_INDEX 1
10 #define NEGATIVE_RESPONSE_NRC_INDEX 2
12 DiagnosticShims diagnostic_init_shims(LogShim log,
13 SendCanMessageShim send_can_message,
14 SetTimerShim set_timer) {
15 DiagnosticShims shims = {
17 send_can_message: send_can_message,
23 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
24 DiagnosticRequest* request, DiagnosticResponseReceived callback) {
25 DiagnosticRequestHandle handle = {
26 // TODO can we copy the request?
33 uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE];
34 payload[MODE_BYTE_INDEX] = request->mode;
35 if(request->pid_length > 0) {
36 copy_bytes_right_aligned(&request->pid, sizeof(request->pid),
37 PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload));
39 if(request->payload_length > 0) {
40 memcpy(&payload[PID_BYTE_INDEX + request->pid_length],
41 request->payload, request->payload_length);
44 handle.isotp_shims = isotp_init_shims(shims->log,
45 shims->send_can_message,
47 handle.isotp_send_handle = isotp_send(&handle.isotp_shims,
48 request->arbitration_id, payload,
49 1 + request->payload_length + request->pid_length,
52 handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims,
53 // TODO need to either always add 0x8 or let the user specify
54 request->arbitration_id + 0x8,
57 // when a can frame is received and passes to the diagnostic handle
58 // if we haven't successfuly sent the entire message yet, give it to the
60 // if we have sent it, give it to the isotp rx handle
61 // if we've received properly, mark this request as completed
62 // how do you get the result? we have it set up for callbacks but that's
63 // getting to be kind of awkward
65 // say it were to call a callback...what state would it need to pass?
67 // the iso-tp message received callback needs to pass the handle and the
70 // so in the obd2 library, we get a callback with an isotp message. how do
71 // we know what diag request i went with, and which diag callback to use? we
72 // could store context with the isotp handle. the problem is that context is
73 // self referential and on the stack, so we really can't get a pointer to
76 // the diagnostic response received callback needs to pass the diagnostic
77 // handle and the diag response
79 // let's say we simplify things and drop the callbacks.
81 // isotp_receive_can_frame returns an isotp handle with a complete message
82 // in it if one was received
84 // diagnostic_receive_can_frame returns a diaghandle if one was received
86 // so in the user code you would throw the can frame at each of your active
87 // diag handles and see if any return a completed message.
89 // is there any advantage to a callback approach? callbacks are useful when
90 // you have something that will block, bt we don't have anything that will
91 // block. it's async but we return immediately from each partial parsing
94 // argh, but will we need the callbacks when we add timers for isotp multi
97 // what are the timers for exactly?
99 // when sending multi frame, send 1 frame, wait for a response
100 // if it says send all, send all right away
101 // if it says flow control, set the time for the next send
102 // instead of creating a timer with an async callback, add a process_handle
103 // function that's called repeatedly in the main loop - if it's time to
104 // send, we do it. so there's a process_handle_send and receive_can_frame
105 // that are just called continuously from the main loop. it's a waste of a
106 // few cpu cycles but it may be more natural than callbacks.
108 // what woudl a timer callback look like...it would need to pass the handle
109 // and that's all. seems like a context void* would be able to capture all
110 // of the information but arg, memory allocation. look at how it's done in
111 // the other library again
116 DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
117 DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id,
118 uint16_t pid, DiagnosticResponseReceived callback) {
119 DiagnosticRequest request = {
120 arbitration_id: arbitration_id,
121 mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22,
123 pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2
126 return diagnostic_request(shims, &request, callback);
129 DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims,
130 DiagnosticRequestHandle* handle, const uint16_t arbitration_id,
131 const uint8_t data[], const uint8_t size) {
133 DiagnosticResponse response = {
134 arbitration_id: arbitration_id,
139 if(!handle->isotp_send_handle.completed) {
140 // TODO when completing a send, this returns...a Message? we have to
141 // check when the isotp_send_handle is completed, and if it is, start
142 isotp_continue_send(&handle->isotp_shims,
143 &handle->isotp_send_handle, arbitration_id, data, size);
144 } else if(!handle->isotp_receive_handle.completed) {
145 IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims,
146 &handle->isotp_receive_handle, arbitration_id, data, size);
148 if(message.completed) {
149 if(message.size > 0) {
150 response.mode = message.payload[0];
151 if(response.mode == NEGATIVE_RESPONSE_MODE) {
152 if(message.size > NEGATIVE_RESPONSE_MODE_INDEX) {
153 // TODO we're setting the mode to the originating
154 // request for the error, so the user can confirm - i
155 // think this is OK since we're storing the failure
156 // status elsewhere, but think about it.
157 response.mode = message.payload[NEGATIVE_RESPONSE_MODE_INDEX];
160 if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) {
161 response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX];
163 response.success = false;
165 if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
166 // hide the "response" version of the mode from the user
168 response.mode = handle->request.mode;
169 if(handle->request.pid_length > 0 && message.size > 1) {
170 if(handle->request.pid_length == 2) {
171 response.pid = *(uint16_t*)&message.payload[PID_BYTE_INDEX];
172 response.pid = ntohs(response.pid);
174 response.pid = message.payload[PID_BYTE_INDEX];
178 uint8_t payload_index = 1 + handle->request.pid_length;
179 response.payload_length = message.size - payload_index;
180 if(response.payload_length > 0) {
181 memcpy(response.payload, &message.payload[payload_index],
182 response.payload_length);
184 response.success = true;
186 shims->log("Response was for a mode 0x%x request, not our mode 0x%x request",
187 response.mode - MODE_RESPONSE_OFFSET,
188 handle->request.mode);
193 response.completed = true;
194 // TODO what does it mean for the handle to be successful, vs. the
195 // request to be successful? if we get a NRC, is that a successful
197 handle->success = true;
198 handle->completed = true;
200 if(handle->callback != NULL) {
201 handle->callback(&response);
206 shims->log("Handle is already completed");
211 // TODO everything below here is for future work...not critical for now.
213 DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status(
214 DiagnosticShims* shims,
215 DiagnosticMilStatusReceived callback) {
216 // TODO request malfunction indicator light (MIL) status - request mode 1
217 // pid 1, parse first bit
220 DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims,
221 DiagnosticVinReceived callback) {
224 DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims,
225 DiagnosticTroubleCodeType dtc_type,
226 DiagnosticTroubleCodesReceived callback) {
229 bool diagnostic_clear_dtc(DiagnosticShims* shims) {
232 DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims,
233 DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) {
234 // before calling the callback, split up the received bytes into 1 or 2 byte
235 // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs
236 // TODO request supported PIDs - request PID 0 and parse 4 bytes in response