Clean up the primary diag request handler.
[apps/low-level-can-service.git] / src / obd2 / obd2.c
1 #include <obd2/obd2.h>
2 #include <arpa/inet.h>
3
4 #define ARBITRATION_ID_OFFSET 0x8
5 #define MODE_RESPONSE_OFFSET 0x40
6 #define NEGATIVE_RESPONSE_MODE 0x7f
7 #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6
8 #define MODE_BYTE_INDEX 0
9 #define PID_BYTE_INDEX 1
10 #define NEGATIVE_RESPONSE_MODE_INDEX 1
11 #define NEGATIVE_RESPONSE_NRC_INDEX 2
12
13 DiagnosticShims diagnostic_init_shims(LogShim log,
14         SendCanMessageShim send_can_message,
15         SetTimerShim set_timer) {
16     DiagnosticShims shims = {
17         log: log,
18         send_can_message: send_can_message,
19         set_timer: set_timer
20     };
21     return shims;
22 }
23
24 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
25         DiagnosticRequest* request, DiagnosticResponseReceived callback) {
26     DiagnosticRequestHandle handle = {
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             request->arbitration_id + ARBITRATION_ID_OFFSET,
54             NULL);
55
56     // TODO notes on multi frame:
57     // TODO what are the timers for exactly?
58     //
59     // when sending multi frame, send 1 frame, wait for a response
60     // if it says send all, send all right away
61     // if it says flow control, set the time for the next send
62     // instead of creating a timer with an async callback, add a process_handle
63     // function that's called repeatedly in the main loop - if it's time to
64     // send, we do it. so there's a process_handle_send and receive_can_frame
65     // that are just called continuously from the main loop. it's a waste of a
66     // few cpu cycles but it may be more  natural than callbacks.
67     //
68     // what woudl a timer callback look like...it would need to pass the handle
69     // and that's all. seems like a context void* would be able to capture all
70     // of the information but arg, memory allocation. look at how it's done in
71     // the other library again
72     //
73     return handle;
74 }
75
76 DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
77         DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id,
78         uint16_t pid, DiagnosticResponseReceived callback) {
79     DiagnosticRequest request = {
80         arbitration_id: arbitration_id,
81         mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22,
82         pid: pid,
83         pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2
84     };
85
86     return diagnostic_request(shims, &request, callback);
87 }
88
89 static bool handle_negative_response(IsoTpMessage* message,
90         DiagnosticResponse* response, DiagnosticShims* shims) {
91     bool response_was_negative = false;
92     if(response->mode == NEGATIVE_RESPONSE_MODE) {
93         response_was_negative = true;
94         if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) {
95             response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX];
96         }
97
98         if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) {
99             response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX];
100         }
101
102         response->success = false;
103         response->completed = true;
104     }
105     return response_was_negative;
106 }
107
108 static bool handle_positive_response(DiagnosticRequestHandle* handle,
109         IsoTpMessage* message, DiagnosticResponse* response,
110         DiagnosticShims* shims) {
111     bool response_was_positive = false;
112     if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) {
113         response_was_positive = true;
114         // hide the "response" version of the mode from the user
115         // if it matched
116         response->mode = handle->request.mode;
117         if(handle->request.pid_length > 0 && message->size > 1) {
118             if(handle->request.pid_length == 2) {
119                 response->pid = *(uint16_t*)&message->payload[PID_BYTE_INDEX];
120                 response->pid = ntohs(response->pid);
121             } else {
122                 response->pid = message->payload[PID_BYTE_INDEX];
123             }
124         }
125
126         uint8_t payload_index = 1 + handle->request.pid_length;
127         response->payload_length = message->size - payload_index;
128         if(response->payload_length > 0) {
129             memcpy(response->payload, &message->payload[payload_index],
130                     response->payload_length);
131         }
132         response->success = true;
133         response->completed = true;
134     }
135     return response_was_positive;
136 }
137
138 DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims,
139         DiagnosticRequestHandle* handle, const uint16_t arbitration_id,
140         const uint8_t data[], const uint8_t size) {
141
142     DiagnosticResponse response = {
143         arbitration_id: arbitration_id,
144         success: false,
145         completed: false
146     };
147
148     if(!handle->isotp_send_handle.completed) {
149         isotp_continue_send(&handle->isotp_shims,
150                 &handle->isotp_send_handle, arbitration_id, data, size);
151     } else if(!handle->isotp_receive_handle.completed) {
152         IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims,
153                 &handle->isotp_receive_handle, arbitration_id, data, size);
154
155         if(message.completed) {
156             if(message.size > 0) {
157                 response.mode = message.payload[0];
158                 if(handle_negative_response(&message, &response, shims)) {
159                     shims->log("Received a negative response to mode %d on arb ID 0x%x",
160                             response.mode, response.arbitration_id);
161
162                     // TODO clarify what it means for a handle to be successful (we made
163                     // a good request+response) vs a request itself being
164                     // successfully
165                     // (the other node didn't return a negative response).
166                     handle->success = true;
167                     handle->completed = true;
168                 } else if(handle_positive_response(handle, &message, &response,
169                             shims)) {
170                     shims->log("Received a positive mode %d response on arb ID 0x%x",
171                             response.mode, response.arbitration_id);
172                     handle->success = true;
173                     handle->completed = true;
174                 } else {
175                     shims->log("Response was for a mode 0x%x request, not our mode 0x%x request",
176                             response.mode - MODE_RESPONSE_OFFSET,
177                             handle->request.mode);
178                 }
179             }
180
181             if(handle->completed && handle->callback != NULL) {
182                 handle->callback(&response);
183             }
184         }
185
186     } else {
187         shims->log("Mode %d request to arb ID 0x%x is already completed",
188                 handle->request.mode, handle->request.arbitration_id);
189     }
190     return response;
191 }