Add a TODO about flipping byte order.
[apps/low-level-can-service.git] / src / obd2 / obd2.c
1 #include <obd2/obd2.h>
2 #include <bitfield/bitfield.h>
3 #include <string.h>
4 #include <limits.h>
5 #include <stddef.h>
6 #include <sys/param.h>
7
8 #define ARBITRATION_ID_OFFSET 0x8
9 #define MODE_RESPONSE_OFFSET 0x40
10 #define NEGATIVE_RESPONSE_MODE 0x7f
11 #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6
12 #define MODE_BYTE_INDEX 0
13 #define PID_BYTE_INDEX 1
14 #define NEGATIVE_RESPONSE_MODE_INDEX 1
15 #define NEGATIVE_RESPONSE_NRC_INDEX 2
16
17 DiagnosticShims diagnostic_init_shims(LogShim log,
18         SendCanMessageShim send_can_message,
19         SetTimerShim set_timer) {
20     DiagnosticShims shims = {
21         log: log,
22         send_can_message: send_can_message,
23         set_timer: set_timer
24     };
25     return shims;
26 }
27
28 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
29         DiagnosticRequest* request, DiagnosticResponseReceived callback) {
30     DiagnosticRequestHandle handle = {
31         request: *request,
32         callback: callback,
33         success: false,
34         completed: false
35     };
36
37     uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE];
38     payload[MODE_BYTE_INDEX] = request->mode;
39     if(request->pid_length > 0) {
40         // TODO need a set bit field that's more natural and checks endianness
41         // becauase we DO need to flip it
42         copy_bytes_right_aligned((uint8_t*)&request->pid, sizeof(request->pid),
43                 PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload));
44     }
45     if(request->payload_length > 0) {
46         memcpy(&payload[PID_BYTE_INDEX + request->pid_length],
47                 request->payload, request->payload_length);
48     }
49
50     handle.isotp_shims = isotp_init_shims(shims->log,
51             shims->send_can_message,
52             shims->set_timer);
53
54     handle.isotp_send_handle = isotp_send(&handle.isotp_shims,
55             request->arbitration_id, payload,
56             1 + request->payload_length + request->pid_length,
57             NULL);
58     if(shims->log != NULL) {
59         shims->log("Sending diagnostic request: arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, payload: 0x%02x%02x%02x%02x%02x%02x%02x%02x, size: %d\r\n",
60                 request->arbitration_id,
61                 request->mode,
62                 request->pid,
63                 request->payload[0],
64                 request->payload[1],
65                 request->payload[2],
66                 request->payload[3],
67                 request->payload[4],
68                 request->payload[5],
69                 request->payload[6],
70                 request->payload[7],
71                 request->payload_length);
72     }
73
74     handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims,
75             request->arbitration_id + ARBITRATION_ID_OFFSET,
76             NULL);
77
78     // TODO notes on multi frame:
79     // TODO what are the timers for exactly?
80     //
81     // when sending multi frame, send 1 frame, wait for a response
82     // if it says send all, send all right away
83     // if it says flow control, set the time for the next send
84     // instead of creating a timer with an async callback, add a process_handle
85     // function that's called repeatedly in the main loop - if it's time to
86     // send, we do it. so there's a process_handle_send and receive_can_frame
87     // that are just called continuously from the main loop. it's a waste of a
88     // few cpu cycles but it may be more  natural than callbacks.
89     //
90     // what woudl a timer callback look like...it would need to pass the handle
91     // and that's all. seems like a context void* would be able to capture all
92     // of the information but arg, memory allocation. look at how it's done in
93     // the other library again
94     //
95     return handle;
96 }
97
98 DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
99         DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id,
100         uint16_t pid, DiagnosticResponseReceived callback) {
101     DiagnosticRequest request = {
102         arbitration_id: arbitration_id,
103         mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22,
104         pid: pid,
105         pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2
106     };
107
108     return diagnostic_request(shims, &request, callback);
109 }
110
111 static bool handle_negative_response(IsoTpMessage* message,
112         DiagnosticResponse* response, DiagnosticShims* shims) {
113     bool response_was_negative = false;
114     if(response->mode == NEGATIVE_RESPONSE_MODE) {
115         response_was_negative = true;
116         if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) {
117             response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX];
118         }
119
120         if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) {
121             response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX];
122         }
123
124         response->success = false;
125         response->completed = true;
126     }
127     return response_was_negative;
128 }
129
130 static bool handle_positive_response(DiagnosticRequestHandle* handle,
131         IsoTpMessage* message, DiagnosticResponse* response,
132         DiagnosticShims* shims) {
133     bool response_was_positive = false;
134     if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
135         response_was_positive = true;
136         // hide the "response" version of the mode from the user
137         // if it matched
138         response->mode = handle->request.mode;
139         if(handle->request.pid_length > 0 && message->size > 1) {
140             if(handle->request.pid_length == 2) {
141                 response->pid = *(uint16_t*)&message->payload[PID_BYTE_INDEX];
142                 if(BYTE_ORDER == LITTLE_ENDIAN) {
143                     response->pid = __builtin_bswap32(response->pid << 16);
144                 }
145             } else {
146                 response->pid = message->payload[PID_BYTE_INDEX];
147             }
148         }
149
150         uint8_t payload_index = 1 + handle->request.pid_length;
151         response->payload_length = message->size - payload_index;
152         if(response->payload_length > 0) {
153             memcpy(response->payload, &message->payload[payload_index],
154                     response->payload_length);
155         }
156         response->success = true;
157         response->completed = true;
158     }
159     return response_was_positive;
160 }
161
162 DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims,
163         DiagnosticRequestHandle* handle, const uint16_t arbitration_id,
164         const uint8_t data[], const uint8_t size) {
165
166     DiagnosticResponse response = {
167         arbitration_id: arbitration_id,
168         success: false,
169         completed: false
170     };
171
172     if(!handle->isotp_send_handle.completed) {
173         isotp_continue_send(&handle->isotp_shims,
174                 &handle->isotp_send_handle, arbitration_id, data, size);
175     } else if(!handle->isotp_receive_handle.completed) {
176         IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims,
177                 &handle->isotp_receive_handle, arbitration_id, data, size);
178
179         if(message.completed) {
180             if(message.size > 0) {
181                 response.mode = message.payload[0];
182                 if(handle_negative_response(&message, &response, shims)) {
183                     shims->log("Received a negative response to mode %d on arb ID 0x%x",
184                             response.mode, response.arbitration_id);
185                     handle->success = true;
186                     handle->completed = true;
187                 } else if(handle_positive_response(handle, &message, &response,
188                             shims)) {
189                     shims->log("Received a positive mode %d response on arb ID 0x%x",
190                             response.mode, response.arbitration_id);
191                     handle->success = true;
192                     handle->completed = true;
193                 } else {
194                     shims->log("Response was for a mode 0x%x request, not our mode 0x%x request",
195                             response.mode - MODE_RESPONSE_OFFSET,
196                             handle->request.mode);
197                 }
198             }
199
200             if(handle->completed && handle->callback != NULL) {
201                 handle->callback(&response);
202             }
203         }
204
205     } else {
206         shims->log("Mode %d request to arb ID 0x%x is already completed",
207                 handle->request.mode, handle->request.arbitration_id);
208     }
209     return response;
210 }