Display mode in more recognizable hex format in log messages.
[apps/low-level-can-service.git] / src / uds / uds.c
1 #include <uds/uds.h>
2 #include <bitfield/bitfield.h>
3 #include <canutil/read.h>
4 #include <string.h>
5 #include <limits.h>
6 #include <stddef.h>
7 #include <sys/param.h>
8
9 #define ARBITRATION_ID_OFFSET 0x8
10 #define MODE_RESPONSE_OFFSET 0x40
11 #define NEGATIVE_RESPONSE_MODE 0x7f
12 #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6
13 #define MODE_BYTE_INDEX 0
14 #define PID_BYTE_INDEX 1
15 #define NEGATIVE_RESPONSE_MODE_INDEX 1
16 #define NEGATIVE_RESPONSE_NRC_INDEX 2
17
18 #ifndef MAX
19 #define MAX(x, y) (((x) > (y)) ? (x) : (y))
20 #endif
21
22 DiagnosticShims diagnostic_init_shims(LogShim log,
23         SendCanMessageShim send_can_message,
24         SetTimerShim set_timer) {
25     DiagnosticShims shims = {
26         log: log,
27         send_can_message: send_can_message,
28         set_timer: set_timer
29     };
30     return shims;
31 }
32
33 static void setup_receive_handle(DiagnosticRequestHandle* handle) {
34     if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) {
35         uint16_t response_id;
36         for(response_id = 0;
37                 response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) {
38             handle->isotp_receive_handles[response_id] = isotp_receive(
39                     &handle->isotp_shims,
40                     OBD2_FUNCTIONAL_RESPONSE_START + response_id,
41                     NULL);
42         }
43         handle->isotp_receive_handle_count = OBD2_FUNCTIONAL_RESPONSE_COUNT;
44     } else {
45         handle->isotp_receive_handle_count = 1;
46         handle->isotp_receive_handles[0] = isotp_receive(&handle->isotp_shims,
47                 handle->request.arbitration_id + ARBITRATION_ID_OFFSET,
48                 NULL);
49     }
50 }
51
52 static uint16_t autoset_pid_length(uint8_t mode, uint16_t pid,
53         uint8_t pid_length) {
54     if(pid_length == 0) {
55         if(pid > 0xffff || mode > 10) {
56             pid_length = 2;
57         } else {
58             pid_length = 1;
59         }
60     }
61     return pid_length;
62 }
63
64 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
65         DiagnosticRequest* request, DiagnosticResponseReceived callback) {
66     DiagnosticRequestHandle handle = {
67         request: *request,
68         callback: callback,
69         success: false,
70         completed: false
71     };
72
73     uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0};
74     payload[MODE_BYTE_INDEX] = request->mode;
75     if(request->has_pid) {
76         request->pid_length = autoset_pid_length(request->mode,
77                 request->pid, request->pid_length);
78         handle.request.pid_length = request->pid_length;
79         set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT,
80                 request->pid_length * CHAR_BIT, payload, sizeof(payload));
81     }
82     if(request->payload_length > 0) {
83         memcpy(&payload[PID_BYTE_INDEX + request->pid_length],
84                 request->payload, request->payload_length);
85     }
86
87     handle.isotp_shims = isotp_init_shims(shims->log,
88             shims->send_can_message,
89             shims->set_timer);
90
91     handle.isotp_send_handle = isotp_send(&handle.isotp_shims,
92             request->arbitration_id, payload,
93             1 + request->payload_length + request->pid_length,
94             NULL);
95     if(shims->log != NULL) {
96         shims->log("Sending diagnostic request: arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, payload: 0x%02x%02x%02x%02x%02x%02x%02x, size: %d\r\n",
97                 request->arbitration_id,
98                 request->mode,
99                 request->pid,
100                 request->payload[0],
101                 request->payload[1],
102                 request->payload[2],
103                 request->payload[3],
104                 request->payload[4],
105                 request->payload[5],
106                 request->payload[6],
107                 request->payload_length);
108     }
109
110     setup_receive_handle(&handle);
111
112     // TODO notes on multi frame:
113     // TODO what are the timers for exactly?
114     //
115     // when sending multi frame, send 1 frame, wait for a response
116     // if it says send all, send all right away
117     // if it says flow control, set the time for the next send
118     // instead of creating a timer with an async callback, add a process_handle
119     // function that's called repeatedly in the main loop - if it's time to
120     // send, we do it. so there's a process_handle_send and receive_can_frame
121     // that are just called continuously from the main loop. it's a waste of a
122     // few cpu cycles but it may be more  natural than callbacks.
123     //
124     // what woudl a timer callback look like...it would need to pass the handle
125     // and that's all. seems like a context void* would be able to capture all
126     // of the information but arg, memory allocation. look at how it's done in
127     // the other library again
128     //
129     return handle;
130 }
131
132 DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
133         DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id,
134         uint16_t pid, DiagnosticResponseReceived callback) {
135     DiagnosticRequest request = {
136         arbitration_id: arbitration_id,
137         mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22,
138         has_pid: true,
139         pid: pid
140     };
141
142     return diagnostic_request(shims, &request, callback);
143 }
144
145 static bool handle_negative_response(IsoTpMessage* message,
146         DiagnosticResponse* response, DiagnosticShims* shims) {
147     bool response_was_negative = false;
148     if(response->mode == NEGATIVE_RESPONSE_MODE) {
149         response_was_negative = true;
150         if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) {
151             response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX];
152         }
153
154         if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) {
155             response->negative_response_code =
156                     message->payload[NEGATIVE_RESPONSE_NRC_INDEX];
157         }
158
159         response->success = false;
160         response->completed = true;
161     }
162     return response_was_negative;
163 }
164
165 static bool handle_positive_response(DiagnosticRequestHandle* handle,
166         IsoTpMessage* message, DiagnosticResponse* response,
167         DiagnosticShims* shims) {
168     bool response_was_positive = false;
169     if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
170         response_was_positive = true;
171         // hide the "response" version of the mode from the user
172         // if it matched
173         response->mode = handle->request.mode;
174         response->has_pid = false;
175         if(handle->request.has_pid && message->size > 1) {
176             response->has_pid = true;
177             if(handle->request.pid_length == 2) {
178                 response->pid = get_bitfield(message->payload, message->size,
179                         PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT);
180             } else {
181                 response->pid = message->payload[PID_BYTE_INDEX];
182             }
183
184         }
185
186         uint8_t payload_index = 1 + handle->request.pid_length;
187         response->payload_length = MAX(0, message->size - payload_index);
188         if(response->payload_length > 0) {
189             memcpy(response->payload, &message->payload[payload_index],
190                     response->payload_length);
191         }
192
193         if((!handle->request.has_pid && !response->has_pid)
194                 || response->pid == handle->request.pid) {
195             response->success = true;
196             response->completed = true;
197         } else {
198             response_was_positive = false;
199         }
200     }
201     return response_was_positive;
202 }
203
204 DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims,
205         DiagnosticRequestHandle* handle, const uint16_t arbitration_id,
206         const uint8_t data[], const uint8_t size) {
207
208     DiagnosticResponse response = {
209         arbitration_id: arbitration_id,
210         success: false,
211         completed: false
212     };
213
214     if(!handle->isotp_send_handle.completed) {
215         isotp_continue_send(&handle->isotp_shims,
216                 &handle->isotp_send_handle, arbitration_id, data, size);
217     } else {
218         uint8_t i;
219         for(i = 0; i < handle->isotp_receive_handle_count; ++i) {
220             IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims,
221                     &handle->isotp_receive_handles[i], arbitration_id, data,
222                     size);
223
224             if(message.completed) {
225                 if(message.size > 0) {
226                     response.mode = message.payload[0];
227                     if(handle_negative_response(&message, &response, shims)) {
228                         shims->log("Received a negative response to mode 0x%x on arb ID 0x%x",
229                                 response.mode, response.arbitration_id);
230                         handle->success = true;
231                         handle->completed = true;
232                     } else if(handle_positive_response(handle, &message,
233                                 &response, shims)) {
234                         shims->log("Received a positive mode 0x%x response on arb ID 0x%x",
235                                 response.mode, response.arbitration_id);
236                         handle->success = true;
237                         handle->completed = true;
238                     } else {
239                         shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)",
240                                 MAX(0, response.mode - MODE_RESPONSE_OFFSET),
241                                 response.pid, handle->request.mode,
242                                 handle->request.pid);
243                     }
244                 } else {
245                     shims->log("Received an empty response on arb ID 0x%x",
246                             response.arbitration_id);
247                 }
248
249                 if(handle->completed && handle->callback != NULL) {
250                     handle->callback(&response);
251                 }
252
253                 break;
254             }
255         }
256     }
257     return response;
258 }
259
260 float diagnostic_payload_to_float(const DiagnosticResponse* response) {
261     return bitfield_parse_float(response->payload,
262             response->payload_length, 0,
263             response->payload_length * CHAR_BIT, 1.0, 0);
264 }
265
266 /* Public:
267  *
268  * Functions pulled from http://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01
269  */
270 float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) {
271     // handles on the single number values, not the bit encoded ones
272     switch(response->pid) {
273         case 0xa:
274             return response->payload[0] * 3;
275         case 0xc:
276             return (response->payload[0] * 256 + response->payload[1]) / 4.0;
277         case 0xd:
278         case 0x33:
279         case 0xb:
280             return response->payload[0];
281         case 0x10:
282             return (response->payload[0] * 256 + response->payload[1]) / 100.0;
283         case 0x11:
284         case 0x2f:
285         case 0x45:
286         case 0x4c:
287         case 0x52:
288         case 0x5a:
289         case 0x4:
290             return response->payload[0] * 100.0 / 255.0;
291         case 0x46:
292         case 0x5c:
293         case 0xf:
294         case 0x5:
295             return response->payload[0] - 40;
296         case 0x62:
297             return response->payload[0] - 125;
298         default:
299             return 0;
300     }
301 }