Test basic diag request and response!
[apps/low-level-can-service.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         // 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_receive_can_frame(&handle->isotp_shims,
143                 &handle->isotp_send_handle, arbitration_id, data, size);
144     } else if(!handle->isotp_receive_handle.completed) {
145         IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims,
146                 &handle->isotp_receive_handle, arbitration_id, data, size);
147
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];
158                     }
159
160                     if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) {
161                         response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX];
162                     }
163                     response.success = false;
164                 } else {
165                     if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
166                         // hide the "response" version of the mode from the user
167                         // if it matched
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);
173                             } else {
174                                 response.pid = message.payload[PID_BYTE_INDEX];
175                             }
176                         }
177
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);
183                         }
184                         response.success = true;
185                     } else {
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);
189                     }
190                 }
191             }
192
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
196             // request?
197             handle->success = true;
198             handle->completed = true;
199
200             if(handle->callback != NULL) {
201                 handle->callback(&response);
202             }
203         }
204
205     } else {
206         shims->log("Handle is already completed");
207     }
208     return response;
209 }
210
211 // TODO everything below here is for future work...not critical for now.
212
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
218 }
219
220 DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims,
221         DiagnosticVinReceived callback) {
222 }
223
224 DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims,
225         DiagnosticTroubleCodeType dtc_type,
226         DiagnosticTroubleCodesReceived callback) {
227 }
228
229 bool diagnostic_clear_dtc(DiagnosticShims* shims) {
230 }
231
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
237 }