7188af0d73898acb59b30546ed7a927a7255f334
[apps/agl-service-can-low-level.git] / src / obd2 / obd2.c
1 #include <obd2/obd2.h>
2
3 #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6
4 #define MODE_BYTE_INDEX 0
5 #define PID_BYTE_INDEX 1
6
7 DiagnosticShims diagnostic_init_shims(LogShim log,
8         SendCanMessageShim send_can_message,
9         SetTimerShim set_timer) {
10     DiagnosticShims shims = {
11         send_can_message: send_can_message,
12         set_timer: set_timer,
13         log: log
14     };
15     return shims;
16 }
17
18 DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims,
19         DiagnosticRequest* request, DiagnosticResponseReceived callback) {
20     DiagnosticRequestHandle handle = {
21         type: DIAGNOSTIC_REQUEST_TYPE_PID,
22         callback: callback,
23         success: false,
24         completed: false
25     };
26     uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE];
27     payload[MODE_BYTE_INDEX] = request->mode;
28     if(request->pid_length > 0) {
29         copy_bytes_right_aligned(request->pid, sizeof(request->pid),
30                 PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload));
31     }
32     if(request->payload_length > 0) {
33         memcpy(payload[PID_BYTE_INDEX + request->pid_length],
34                 request->payload, request->payload_length);
35     }
36
37     handle.isotp_shims = isotp_init_shims(shims->log,
38             shims->send_can_message,
39             shims->set_timer);
40     handle.isotp_send_handle = isotp_send(&handle.isotp_shims,
41             request->arbitration_id, payload,
42             1 + request->payload_length + request->pid_length,
43             // TODO is this ok to pass null here?
44             NULL);
45
46     handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims,
47             // TODO need to either always add 0x8 or let the user specify
48             request->arbitration_id + 0x8,
49             // TODO this callback is mostly useful for debugging stuff as it
50             // doesn't have the internal state we need to complete the
51             // diagnositc request - can we pass NULL or will that 'splode?
52             NULL);
53
54     // when a can frame is received and passes to the diagnostic handle
55     // if we haven't successfuly sent the entire message yet, give it to the
56     // isottp send handle
57     // if we have sent it, give it to the isotp rx handle
58     // if we've received properly, mark this request as completed
59     // how do you get the result? we have it set up for callbacks but that's
60     // getting to be kind of awkward
61     //
62     // say it were to call a callback...what state would it need to pass?
63     //
64     // the iso-tp message received callback needs to pass the handle and the
65     // received message
66     //
67     // so in the obd2 library, we get a callback with an isotp message. how do
68     // we know what diag request i went with, and which diag callback to use? we
69     // could store context with the isotp handle. the problem is that context is
70     // self referential and on the stack, so we really can't get a pointer to
71     // it.
72     //
73     // the diagnostic response received callback needs to pass the diagnostic
74     // handle and the diag response
75     //
76     // let's say we simplify things and drop the callbacks.
77     //
78     // isotp_receive_can_frame returns an isotp handle with a complete message
79     // in it if one was received
80     //
81     // diagnostic_receive_can_frame returns a diaghandle if one was received
82     //
83     // so in the user code you would throw the can frame at each of your active
84     // diag handles and see if any return a completed message.
85     //
86     // is there any advantage to a callback approach?  callbacks are useful when
87     // you have something that will block, bt we don't have anything that will
88     // block. it's async but we return immediately from each partial parsing
89     // attempt.
90     //
91     // argh, but will we need the callbacks when we add timers for isotp multi
92     // frame?
93     //
94     // what are the timers for exactly?
95     //
96     // when sending multi frame, send 1 frame, wait for a response
97     // if it says send all, send all right away
98     // if it says flow control, set the time for the next send
99     // instead of creating a timer with an async callback, add a process_handle
100     // function that's called repeatedly in the main loop - if it's time to
101     // send, we do it. so there's a process_handle_send and receive_can_frame
102     // that are just called continuously from the main loop. it's a waste of a
103     // few cpu cycles but it may be more  natural than callbacks.
104     //
105     // what woudl a timer callback look like...it would need to pass the handle
106     // and that's all. seems like a context void* would be able to capture all
107     // of the information but arg, memory allocation. look at how it's done in
108     // the other library again
109     //
110     return handle;
111 }
112
113 DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims,
114         DiagnosticPidRequestType pid_request_type, uint16_t pid,
115         DiagnosticResponseReceived callback) {
116     DiagnosticRequest request = {
117         mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22,
118         pid: pid
119     };
120
121     return diagnostic_request(shims, &request, callback);
122 }
123
124 DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims,
125         DiagnosticRequestHandle* handle,
126         const uint16_t arbitration_id, const uint8_t data[],
127         const uint8_t size) {
128     if(!handle->isotp_send_handle.completed) {
129     } else if(!handle->isotp_receive_handle.completed) {
130     } else {
131         shims->log("Handle is already completed");
132     }
133     // TODO determine which isotp handler to pass it to based on our state
134     IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims,
135             &handle->isotp_send_handle, arbitration_id, data, size);
136
137     DiagnosticResponse response = {
138         success: false,
139         completed: false
140     };
141
142     if(message.completed) {
143     }
144 }
145
146 // TODO everything below here is for future work...not critical for now.
147
148 DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status(
149         DiagnosticShims* shims,
150         DiagnosticMilStatusReceived callback) {
151     // TODO request malfunction indicator light (MIL) status - request mode 1
152     // pid 1, parse first bit
153 }
154
155 DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims,
156         DiagnosticVinReceived callback) {
157 }
158
159 DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims,
160         DiagnosticTroubleCodeType dtc_type,
161         DiagnosticTroubleCodesReceived callback) {
162 }
163
164 bool diagnostic_clear_dtc(DiagnosticShims* shims) {
165 }
166
167 DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims,
168         DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) {
169     // before calling the callback, split up the received bytes into 1 or 2 byte
170     // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs
171     // TODO request supported PIDs  - request PID 0 and parse 4 bytes in response
172 }