627128c32a61203d974c811a6f83b9ac9d56e0d0
[apps/agl-service-can-low-level.git] / src / obd2 / obd2.c
1 #include <obd2/obd2.h>
2 #include <arpa/inet.h>
3
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
11
12 DiagnosticShims diagnostic_init_shims(LogShim log,
13         SendCanMessageShim send_can_message,
14         SetTimerShim set_timer) {
15     DiagnosticShims shims = {
16         log: log,
17         send_can_message: send_can_message,
18         set_timer: set_timer
19     };
20     return shims;
21 }
22
23 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
24         DiagnosticRequest* request, DiagnosticResponseReceived callback) {
25     DiagnosticRequestHandle handle = {
26         // TODO can we copy the request?
27         request: *request,
28         callback: callback,
29         success: false,
30         completed: false
31     };
32
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));
38     }
39     if(request->payload_length > 0) {
40         memcpy(&payload[PID_BYTE_INDEX + request->pid_length],
41                 request->payload, request->payload_length);
42     }
43
44     handle.isotp_shims = isotp_init_shims(shims->log,
45             shims->send_can_message,
46             shims->set_timer);
47     handle.isotp_send_handle = isotp_send(&handle.isotp_shims,
48             request->arbitration_id, payload,
49             1 + request->payload_length + request->pid_length,
50             NULL);
51
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,
55             NULL);
56
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
59     // isottp send handle
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
64     //
65     // say it were to call a callback...what state would it need to pass?
66     //
67     // the iso-tp message received callback needs to pass the handle and the
68     // received message
69     //
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
74     // it.
75     //
76     // the diagnostic response received callback needs to pass the diagnostic
77     // handle and the diag response
78     //
79     // let's say we simplify things and drop the callbacks.
80     //
81     // isotp_receive_can_frame returns an isotp handle with a complete message
82     // in it if one was received
83     //
84     // diagnostic_receive_can_frame returns a diaghandle if one was received
85     //
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.
88     //
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
92     // attempt.
93     //
94     // argh, but will we need the callbacks when we add timers for isotp multi
95     // frame?
96     //
97     // what are the timers for exactly?
98     //
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.
107     //
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
112     //
113     return handle;
114 }
115
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,
122         pid: pid,
123         pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2
124     };
125
126     return diagnostic_request(shims, &request, callback);
127 }
128
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) {
132
133     DiagnosticResponse response = {
134         arbitration_id: arbitration_id,
135         success: false,
136         completed: false
137     };
138
139     if(!handle->isotp_send_handle.completed) {
140         isotp_continue_send(&handle->isotp_shims,
141                 &handle->isotp_send_handle, arbitration_id, data, size);
142     } else if(!handle->isotp_receive_handle.completed) {
143         IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims,
144                 &handle->isotp_receive_handle, arbitration_id, data, size);
145
146         if(message.completed) {
147             if(message.size > 0) {
148                 response.mode = message.payload[0];
149                 if(response.mode == NEGATIVE_RESPONSE_MODE) {
150                     if(message.size > NEGATIVE_RESPONSE_MODE_INDEX) {
151                         // TODO we're setting the mode to the originating
152                         // request for the error, so the user can confirm - i
153                         // think this is OK since we're storing the failure
154                         // status elsewhere, but think about it.
155                         response.mode = message.payload[NEGATIVE_RESPONSE_MODE_INDEX];
156                     }
157
158                     if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) {
159                         response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX];
160                     }
161                     response.success = false;
162                 } else {
163                     if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
164                         // hide the "response" version of the mode from the user
165                         // if it matched
166                         response.mode = handle->request.mode;
167                         if(handle->request.pid_length > 0 && message.size > 1) {
168                             if(handle->request.pid_length == 2) {
169                                 response.pid = *(uint16_t*)&message.payload[PID_BYTE_INDEX];
170                                 response.pid = ntohs(response.pid);
171                             } else {
172                                 response.pid = message.payload[PID_BYTE_INDEX];
173                             }
174                         }
175
176                         uint8_t payload_index = 1 + handle->request.pid_length;
177                         response.payload_length = message.size - payload_index;
178                         if(response.payload_length > 0) {
179                             memcpy(response.payload, &message.payload[payload_index],
180                                     response.payload_length);
181                         }
182                         response.success = true;
183                     } else {
184                         shims->log("Response was for a mode 0x%x request, not our mode 0x%x request",
185                                 response.mode - MODE_RESPONSE_OFFSET,
186                                 handle->request.mode);
187                     }
188                 }
189             }
190
191             response.completed = true;
192             // TODO what does it mean for the handle to be successful, vs. the
193             // request to be successful? if we get a NRC, is that a successful
194             // request?
195             handle->success = true;
196             handle->completed = true;
197
198             if(handle->callback != NULL) {
199                 handle->callback(&response);
200             }
201         }
202
203     } else {
204         shims->log("Handle is already completed");
205     }
206     return response;
207 }
208
209 // TODO everything below here is for future work...not critical for now.
210
211 DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status(
212         DiagnosticShims* shims,
213         DiagnosticMilStatusReceived callback) {
214     // TODO request malfunction indicator light (MIL) status - request mode 1
215     // pid 1, parse first bit
216 }
217
218 DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims,
219         DiagnosticVinReceived callback) {
220 }
221
222 DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims,
223         DiagnosticTroubleCodeType dtc_type,
224         DiagnosticTroubleCodesReceived callback) {
225 }
226
227 bool diagnostic_clear_dtc(DiagnosticShims* shims) {
228 }
229
230 DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims,
231         DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) {
232     // before calling the callback, split up the received bytes into 1 or 2 byte
233     // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs
234     // TODO request supported PIDs  - request PID 0 and parse 4 bytes in response
235 }