From 06f31c13df6aaf92124f10b8cb5eee96b75c4f73 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 30 Dec 2013 15:25:32 -0500 Subject: Initial commit. --- .gitignore | 4 ++++ .gitmodules | 6 ++++++ .travis.yml | 11 +++++++++++ CHANGELOG.mkd | 5 +++++ LICENSE | 24 ++++++++++++++++++++++++ Makefile | 39 +++++++++++++++++++++++++++++++++++++++ README.mkd | 20 ++++++++++++++++++++ deps/bitfield-c | 1 + deps/isotp-c | 1 + runtests.sh | 17 +++++++++++++++++ tests/tests.c | 37 +++++++++++++++++++++++++++++++++++++ 11 files changed, 165 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 CHANGELOG.mkd create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.mkd create mode 160000 deps/bitfield-c create mode 160000 deps/isotp-c create mode 100644 runtests.sh create mode 100644 tests/tests.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4225c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +.DS_Store +*~ +*.bin diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d9c0194 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "deps/bitfield-c"] + path = deps/bitfield-c + url = https://github.com/openxc/bitfield-c +[submodule "deps/isotp-c"] + path = deps/isotp-c + url = https://github.com/openxc/isotp-c diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..22b1a91 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: c +compiler: + - gcc +script: make test +before_install: + - git submodule update --init + - sudo apt-get update -qq + - sudo apt-get install check +notifications: + hipchat: + - secure: "ZO/hEAoOTZ4FJytMW0m4LZrsdFVM1/V0Hu13zfj8mdcHkf2MyfxthPDecnn5\naZVn4P8mSSwpp39EnAfa9fBcWcDESnKM1YQKPPGkoxZZHIOd2rYhRv34XfpE\n5qNLkQ/lEPQCBEmvIQ5ZJxsiZjGhO7KxWvdNdruH6cdVCYSh4Xo=" diff --git a/CHANGELOG.mkd b/CHANGELOG.mkd new file mode 100644 index 0000000..edf7f7d --- /dev/null +++ b/CHANGELOG.mkd @@ -0,0 +1,5 @@ +# OBD-II Support Library in C + +## v0.1 + +* Initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..330d61f --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013 Ford Motor Company +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9873038 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CC = gcc +INCLUDES = -Isrc -Ideps/bitfield-c/src -Ideps/isotp-c/src +CFLAGS = $(INCLUDES) -c -w -Wall -Werror -g -ggdb -std=c99 +LDFLAGS = +LDLIBS = -lcheck + +TEST_DIR = tests + +# Guard against \r\n line endings only in Cygwin +OSTYPE := $(shell uname) +ifneq ($(OSTYPE),Darwin) + OSTYPE := $(shell uname -o) + ifeq ($(OSTYPE),Cygwin) + TEST_SET_OPTS = igncr + endif +endif + +SRC = $(wildcard src/**/*.c) +SRC += $(wildcard deps/bitfield-c/src/**/*.c) +SRC += $(wildcard deps/isotp-c/src/**/*.c) +OBJS = $(SRC:.c=.o) +TEST_SRC = $(wildcard $(TEST_DIR)/test_*.c) +TESTS=$(patsubst %.c,%.bin,$(TEST_SRC)) +TEST_SUPPORT_SRC = $(TEST_DIR)/common.c +TEST_SUPPORT_OBJS = $(TEST_SUPPORT_SRC:.c=.o) + +all: $(OBJS) + +test: $(TESTS) + @set -o $(TEST_SET_OPTS) >/dev/null 2>&1 + @export SHELLOPTS + @sh runtests.sh $(TEST_DIR) + +$(TEST_DIR)/%.bin: $(TEST_DIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) + @mkdir -p $(dir $@) + $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS) + +clean: + rm -rf **/*.o $(TEST_DIR)/*.bin diff --git a/README.mkd b/README.mkd new file mode 100644 index 0000000..485f2cc --- /dev/null +++ b/README.mkd @@ -0,0 +1,20 @@ +OBD-II Support Library in C +============================= + +## API + +## Testing + +The library includes a test suite that uses the `check` C unit test library. + + $ make test + +## Authors + +Chris Peplin cpeplin@ford.com + +## License + +Copyright (c) 2013 Ford Motor Company + +Licensed under the BSD license. diff --git a/deps/bitfield-c b/deps/bitfield-c new file mode 160000 index 0000000..cd74b88 --- /dev/null +++ b/deps/bitfield-c @@ -0,0 +1 @@ +Subproject commit cd74b88432054107c439c4e565bea14840dd9ea5 diff --git a/deps/isotp-c b/deps/isotp-c new file mode 160000 index 0000000..368d36c --- /dev/null +++ b/deps/isotp-c @@ -0,0 +1 @@ +Subproject commit 368d36cc15b69882e9d21803f3b8aa7a1c97736e diff --git a/runtests.sh b/runtests.sh new file mode 100644 index 0000000..4781636 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,17 @@ +echo "Running unit tests:" + +for i in $1/*.bin +do + if test -f $i + then + if ./$i + then + echo $i PASS + else + echo "ERROR in test $i:" + exit 1 + fi + fi +done + +echo "${txtbld}$(tput setaf 2)All unit tests passed.$(tput sgr0)" diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..6874565 --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +void setup() { + +} + +START_TEST (test_fail) +{ + fail_unless(false); +} +END_TEST + +Suite* testSuite(void) { + Suite* s = suite_create("obd2"); + TCase *tc_core = tcase_create("core"); + tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_fail); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = testSuite(); + SRunner *sr = srunner_create(s); + // Don't fork so we can actually use gdb + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr, CK_NORMAL); + numberFailed = srunner_ntests_failed(sr); + srunner_free(sr); + return (numberFailed == 0) ? 0 : 1; +} -- cgit 1.2.3-korg From 32f4cbab6bd5769a4b16a584e1880b1deabbd2da Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 30 Dec 2013 18:30:37 -0500 Subject: Add skeleton of the API and data structures. --- README.mkd | 66 +++++++++++++++++++++ deps/isotp-c | 2 +- src/obd2/obd2.c | 47 +++++++++++++++ src/obd2/obd2.h | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/common.c | 0 tests/test_core.c | 36 ++++++++++++ tests/tests.c | 37 ------------ 7 files changed, 323 insertions(+), 38 deletions(-) create mode 100644 src/obd2/obd2.c create mode 100644 src/obd2/obd2.h create mode 100644 tests/common.c create mode 100644 tests/test_core.c delete mode 100644 tests/tests.c diff --git a/README.mkd b/README.mkd index 485f2cc..96b969e 100644 --- a/README.mkd +++ b/README.mkd @@ -1,8 +1,74 @@ OBD-II Support Library in C ============================= +TODO isotp needs to accept responses on an ID other that the request's arb id - +or maybe we have 2 isotp instances, for rx and tx. + +* Response's arb id is assigned ID plus 0x8 +* Functional daignostic request (Broadcast), 0x7df arb id +* Standard ECU requests to 0x7e0 - 0x7e7 + +* Some requests don't need PIDs - e.g. mode 3 + * some responses don't have PIDs, just payload - e.g. mode 3 + +* Service ID + 0x40 == positive response +* Response includes PID echo - use this to match up request/response +* Response ID, ISO-TP PCI, ISO-TP length, service response ID, PID echo, data + +I send the request and give a callback +when the response arrives for the matchin service and PID - on any arb id, since +this may be a broadcast - call my callback. + + +TODO do you want to provide the callback to the Handler, or to each +individual send?o +ok, what are the use cases here. + +you're going to request a few PIDs over and over again at some frequency +you're going to request DTCs once and read the response +you're going to clear DTCs once + +i'd rather use the same diagnostic handler to send all of the differet messages +rather than create one for each use. that way ai can + +but that does complicate the memory management because it'll need to create +some internal data structures. + +at the simplest, what does this handler have to do? + +* store the request arb id, mode, pid, and payload locally +* send a can message +* get all new can messages passed to it +* Check the incoming can message to see if it matches one of the standard ECU + response IDs, or our arb ID + 0x8 +* if it matches, parse the diagnostic response and call the callback + +that seems pretty simple and not worth greatly increasing the internal state to +handle multiple requests + +what we need is another layer on top of that to handle the repeated requests. + ## API + void my_callback(const DiagnosticResponse* response) { + } + + DiagnosticRequestHandler handler = diagnostic_init(my_callback); + DiagnosticRequest request = { + arbitratin_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, + pid: 3 + }; + + diagnostic_send(&handler, &request); + while(true) { + diagnostic_handle_can_frame(&handler, 42, data, 8); + } + + diagnostic_request_pid(&handler, DIAGNOSTIC_STANDARD_PID, 42 + + diagnostic_destroy(&handler); + ## Testing The library includes a test suite that uses the `check` C unit test library. diff --git a/deps/isotp-c b/deps/isotp-c index 368d36c..bc7c020 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 368d36cc15b69882e9d21803f3b8aa7a1c97736e +Subproject commit bc7c0205d8dd7550060f6f0bbe8e2064d28ace8c diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c new file mode 100644 index 0000000..9b4ea92 --- /dev/null +++ b/src/obd2/obd2.c @@ -0,0 +1,47 @@ +#include + +DiagnosticShims diagnostic_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer) { +} + +DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback) { +} + +// decide mode 0x1 / 0x22 based on pid type +DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, + DiagnosticPidRequestType pid_request_type, uint16_t pid, + DiagnosticResponseReceived callback) { +} + +// TODO request malfunction indicator light (MIL) status - request mode 1 pid 1, +// parse first bit +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback) { +} + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback) { +} + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback) { +} + +bool diagnostic_clear_dtc(DiagnosticShims* shims) { +} + +// before calling the callback, split up the received bytes into 1 or 2 byte +// chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs +// TODO request supported PIDs - request PID 0 and parse 4 bytes in response +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { +} + +void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size) { +} diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h new file mode 100644 index 0000000..e6b5bc9 --- /dev/null +++ b/src/obd2/obd2.h @@ -0,0 +1,173 @@ +#ifndef __OBD2_H__ +#define __OBD2_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_OBD2_PAYLOAD_LENGTH 7 +#define VIN_LENGTH 17 + +typedef void (*LogShim)(const char* message); +typedef bool (*SendCanMessageShim)(const uint16_t arbitration_id, + const uint8_t* data, const uint8_t size); +typedef bool (*SetTimerShim)(uint16_t time_ms, void (*callback)); + +typedef struct { + uint16_t arbitration_id; + uint8_t mode; + uint16_t pid; + uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticRequest; + +// Thanks to +// http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 +// for the list of NRCs +typedef enum { + NRC_SERVICE_NOT_SUPPORTED = 0x11, + NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, + NRC_CONDITIONS_NOT_CORRECT = 0x22, + NRC_REQUEST_OUT_OF_RANGE = 0x31, + NRC_SECURITY_ACCESS_DENIED = 0x33, + NRC_INVALID_KEY = 0x35, + NRC_TOO_MANY_ATTEMPS = 0x36, + NRC_TIME_DELAY_NOT_EXPIRED = 0x37, + NRC_RESPONSE_PENDING = 0x78 +} DiagnosticNegativeResponseCode; + +typedef enum { + OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, + OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, + OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3, + OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4, + // 0x5 is for non-CAN only + // OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5, + OBD2_MODE_TEST_RESULTS = 0x6, + OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7, + OBD2_MODE_CONTROL = 0x8, + OBD2_MODE_VEHICLE_INFORMATION = 0x9, + OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa, + // this one isn't technically in OBD2, but both of the enhanced standards + // have their PID requests at 0x22 + OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 +} DiagnosticMode; + +typedef enum { + DTC_EMISSIONS, + DTC_DRIVE_CYCLE, + DTC_PERMANENT +} DiagnosticTroubleCodeType; + +typedef struct { + uint16_t arbitration_id; + uint8_t mode; + bool success; + // if mode is one with a PID, read the correct numbers of PID bytes (1 or 2) + // into this field, then store the remainder of the payload in the payload + // field + uint16_t pid; + DiagnosticNegativeResponseCode negative_response_code; + // if response mode is a negative response, read first byte of payload into + // NRC and store remainder of payload in payload field + uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticResponse; + +typedef enum { + POWERTRAIN = 0x0, + CHASSIS = 0x1, + BODY = 0x2, + NETWORK = 0x3 +} DiagnosticTroubleCodeGroup; + +typedef struct { + DiagnosticTroubleCodeGroup group; + uint8_t group_num; + uint8_t code; +} DiagnosticTroubleCode; + +typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); +typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); +typedef void (*DiagnosticVinReceived)(uint8_t vin[]); +typedef void (*DiagnosticTroubleCodesReceived)( + DiagnosticMode mode, DiagnosticTroubleCode* codes); +typedef void (*DiagnosticPidEnumerationReceived)( + const DiagnosticResponse* response, uint16_t* pids); + +// TODO should we enumerate every OBD-II PID? need conversion formulas, too +typedef struct { + uint16_t pid; + uint8_t bytes_returned; + float min_value; + float max_value; +} DiagnosticParameter; + +typedef enum { + DIAGNOSTIC_REQUEST_TYPE_PID, + DIAGNOSTIC_REQUEST_TYPE_DTC, + DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, + DIAGNOSTIC_REQUEST_TYPE_VIN +} DiagnosticRequestType; + +typedef struct { + IsoTpHandler isotp_handler; + DiagnosticRequestType type; + DiagnosticResponseReceived callback; + DiagnosticMilStatusReceived mil_status_callback; + DiagnosticVinReceived vin_callback; + bool status; +} DiagnosticRequestHandle; + +typedef enum { + DIAGNOSTIC_STANDARD_PID, + DIAGNOSTIC_ENHANCED_PID +} DiagnosticPidRequestType; + +typedef struct { + IsoTpShims isotp_shims; + LogShim log; + SendCanMessageShim send_can_message; +} DiagnosticShims; + +DiagnosticShims diagnostic_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer); + +DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback); + +// decide mode 0x1 / 0x22 based on pid type +DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, + DiagnosticPidRequestType pid_request_type, uint16_t pid, + DiagnosticResponseReceived callback); + +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback); + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback); + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback); + +bool diagnostic_clear_dtc(DiagnosticShims* shims); + +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); + +void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __OBD2_H__ diff --git a/tests/common.c b/tests/common.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_core.c b/tests/test_core.c new file mode 100644 index 0000000..65af858 --- /dev/null +++ b/tests/test_core.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +void setup() { + +} + +START_TEST (test_fail) +{ + fail_unless(false); +} +END_TEST + +Suite* testSuite(void) { + Suite* s = suite_create("obd2"); + TCase *tc_core = tcase_create("core"); + tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_fail); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = testSuite(); + SRunner *sr = srunner_create(s); + // Don't fork so we can actually use gdb + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr, CK_NORMAL); + numberFailed = srunner_ntests_failed(sr); + srunner_free(sr); + return (numberFailed == 0) ? 0 : 1; +} diff --git a/tests/tests.c b/tests/tests.c deleted file mode 100644 index 6874565..0000000 --- a/tests/tests.c +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include -#include -#include - -void setup() { - -} - -START_TEST (test_fail) -{ - fail_unless(false); -} -END_TEST - -Suite* testSuite(void) { - Suite* s = suite_create("obd2"); - TCase *tc_core = tcase_create("core"); - tcase_add_checked_fixture(tc_core, setup, NULL); - tcase_add_test(tc_core, test_fail); - suite_add_tcase(s, tc_core); - - return s; -} - -int main(void) { - int numberFailed; - Suite* s = testSuite(); - SRunner *sr = srunner_create(s); - // Don't fork so we can actually use gdb - srunner_set_fork_status(sr, CK_NOFORK); - srunner_run_all(sr, CK_NORMAL); - numberFailed = srunner_ntests_failed(sr); - srunner_free(sr); - return (numberFailed == 0) ? 0 : 1; -} -- cgit 1.2.3-korg From 217bb7fd1657e35a95024bb5bda598ea136fdfbe Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 30 Dec 2013 20:40:50 -0500 Subject: Add a failing test for a simple diag request/response. --- src/obd2/obd2.h | 8 ++------ tests/common.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_core.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index e6b5bc9..411170d 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -12,11 +12,6 @@ extern "C" { #define MAX_OBD2_PAYLOAD_LENGTH 7 #define VIN_LENGTH 17 -typedef void (*LogShim)(const char* message); -typedef bool (*SendCanMessageShim)(const uint16_t arbitration_id, - const uint8_t* data, const uint8_t size); -typedef bool (*SetTimerShim)(uint16_t time_ms, void (*callback)); - typedef struct { uint16_t arbitration_id; uint8_t mode; @@ -29,6 +24,7 @@ typedef struct { // http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 // for the list of NRCs typedef enum { + NRC_SUCCESS = 0x0, NRC_SERVICE_NOT_SUPPORTED = 0x11, NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, NRC_CONDITIONS_NOT_CORRECT = 0x22, @@ -162,7 +158,7 @@ bool diagnostic_clear_dtc(DiagnosticShims* shims); DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); -void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, +void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); diff --git a/tests/common.c b/tests/common.c index e69de29..e35ef52 100644 --- a/tests/common.c +++ b/tests/common.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +DiagnosticShims SHIMS; + +uint16_t last_can_frame_sent_arb_id; +uint8_t last_can_payload_sent[8]; +uint8_t last_can_payload_size; +bool can_frame_was_sent; + +DiagnosticResponse last_response_received; +bool last_response_was_received; + +void debug(const char* format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + printf("\r\n"); + va_end(args); +} + +void mock_send_can(const uint16_t arbitration_id, const uint8_t* data, + const uint8_t size) { + can_frame_was_sent = true; + last_can_frame_sent_arb_id = arbitration_id; + last_can_payload_size = size; + if(size > 0) { + memcpy(last_can_payload_sent, data, size); + } +} + +void mock_set_timer(uint16_t time_ms, void (*callback)) { +} + +void response_received_handler(const DiagnosticResponse* response) { + last_response_was_received = true; + // TODO not sure if we can copy the struct like this + last_response_received = *response; +} + +void setup() { + SHIMS = diagnostic_init_shims(debug, mock_send_can, mock_set_timer); + memset(last_can_payload_sent, 0, sizeof(last_can_payload_sent)); + can_frame_was_sent = false; + last_response_was_received = false; +} + diff --git a/tests/test_core.c b/tests/test_core.c index 65af858..35e948e 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -1,13 +1,56 @@ +#include #include #include #include #include -void setup() { +extern void setup(); +extern bool last_response_was_received; +extern DiagnosticResponse last_response_received; +extern DiagnosticShims SHIMS; +extern DiagnosticResponseReceived response_received_handler; +START_TEST (test_receive_wrong_arb_id) +{ + ck_assert(false); } +END_TEST + +START_TEST (test_send_diag_request) +{ + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; + diagnostic_receive_can_frame(&handle, request.arbitration_id + 0x8, + can_data, sizeof(can_data)); + fail_unless(last_response_was_received); + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + request.arbitration_id + 0x8); + // TODO should we set it back to the original mode, or leave as mode + 0x40? + ck_assert_int_eq(last_response_received.mode, request.mode); + ck_assert_int_eq(last_response_received.pid, 0); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[2]); +} +END_TEST -START_TEST (test_fail) +START_TEST (test_request_pid_standard) +{ + fail_unless(false); + // TODO test request pid, do the same rigamarole + // kind of leaky, but check that the returned DiagnosticRequest Handle + // has the right PID +} +END_TEST + +START_TEST (test_request_pid_enhanced) { fail_unless(false); } @@ -17,7 +60,17 @@ Suite* testSuite(void) { Suite* s = suite_create("obd2"); TCase *tc_core = tcase_create("core"); tcase_add_checked_fixture(tc_core, setup, NULL); - tcase_add_test(tc_core, test_fail); + tcase_add_test(tc_core, test_send_diag_request); + tcase_add_test(tc_core, test_receive_wrong_arb_id); + tcase_add_test(tc_core, test_request_pid_standard); + tcase_add_test(tc_core, test_request_pid_enhanced); + + // TODO these are future work: + // TODO test request MIL + // TODO test request VIN + // TODO test request DTC + // TODO test clear DTC + // TODO test enumerate PIDs suite_add_tcase(s, tc_core); return s; -- cgit 1.2.3-korg From 0bd237bae324740d6b6b22e3c63ac426267d5bf1 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 30 Dec 2013 20:49:23 -0500 Subject: Add more failing tests before beginning implementation. --- src/obd2/obd2.h | 2 ++ tests/test_core.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 411170d..7445952 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -112,6 +112,8 @@ typedef enum { typedef struct { IsoTpHandler isotp_handler; + // TODO the Handle may need to keep the original request, otherwise we can't + // compare an incoming CAN message to see if it matches the service / PID! DiagnosticRequestType type; DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; diff --git a/tests/test_core.c b/tests/test_core.c index 35e948e..340a326 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -12,7 +12,18 @@ extern DiagnosticResponseReceived response_received_handler; START_TEST (test_receive_wrong_arb_id) { - ck_assert(false); + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; + diagnostic_receive_can_frame(&handle, request.arbitration_id, can_data, + sizeof(can_data)); + fail_if(last_response_was_received); } END_TEST @@ -43,16 +54,45 @@ END_TEST START_TEST (test_request_pid_standard) { - fail_unless(false); - // TODO test request pid, do the same rigamarole - // kind of leaky, but check that the returned DiagnosticRequest Handle - // has the right PID + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_STANDARD_PID, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x3, 0x1 + 0x40, 0x2, 0x45}; + // TODO need a constant for the 7df broadcast functional request + diagnostic_receive_can_frame(&handle, 0x7df + 0x8, + can_data, sizeof(can_data)); + fail_unless(last_response_was_received); + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + 0x7df + 0x8); + // TODO should we set it back to the original mode, or leave as mode + 0x40? + ck_assert_int_eq(last_response_received.mode, 0x1); + ck_assert_int_eq(last_response_received.pid, 0x2); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[3]); } END_TEST START_TEST (test_request_pid_enhanced) { - fail_unless(false); + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_ENHANCED_PID, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; + // TODO need a constant for the 7df broadcast functional request + diagnostic_receive_can_frame(&handle, 0x7df + 0x8, can_data, + sizeof(can_data)); + fail_unless(last_response_was_received); + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + 0x7df + 0x8); + // TODO should we set it back to the original mode, or leave as mode + 0x40? + ck_assert_int_eq(last_response_received.mode, 0x22); + ck_assert_int_eq(last_response_received.pid, 0x2); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[4]); } END_TEST -- cgit 1.2.3-korg From 84bc68873aa53e295853b461b6793b5019d1dacb Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 30 Dec 2013 21:06:57 -0500 Subject: Spray a bunch of notes and code into the implementation file. --- src/obd2/obd2.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++---------- src/obd2/obd2.h | 3 ++- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 9b4ea92..3d501d6 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -3,23 +3,68 @@ DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer) { + DiagnosticShims shims = { + isotp_shims: { + log: log, + send_can_message: send_can_message, + set_timer: set_timer + }, + log: log + }; + return shims; } DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { + // TODO hmm, where is message_received coming from? we would need 2 layers + // of callbacks. if we do it in the obd2 library, we have to have some + // context passed to that message_received handler so we can then retreive + // the obd2 callback. there was an option question of if we should pass a + // context with that callback, and maybe this answers it. + // + // alternatively, what if don't hide isotp and allow that to also be + // injected. the user has the iso_tp message_received callback, and in that + // they call a message_received handler from obd2. + // + // in fact that makes more sense - all the diagnostic_can_frame_received + // function is going to be able to do is call the equivalent function in the + // isotp library. it may or may not have a complete ISO-TP message. huh. + DiagnosticRequestHandle handle = { + // TODO why are teh shims stored as a reference in the isotp handler? + // it's just 3 pointers + isotp_handler: isotp_init(&shims->isotp_shims, request->arbitration_id, + NULL, // TODO need a callback here! + NULL, NULL), + type: 0 //DIAGNOSTIC_.... // TODO how would we know the type? + //does it matter? we were going to have a different callback + }; } -// decide mode 0x1 / 0x22 based on pid type DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, DiagnosticPidRequestType pid_request_type, uint16_t pid, DiagnosticResponseReceived callback) { + // decide mode 0x1 / 0x22 based on pid type + DiagnosticRequest request = { + mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, + pid: pid + }; + + return diagnostic_request(shims, &request, callback); +} + +void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size) { + isotp_receive_can_frame(handler->isotp_handler, arbitration_id, data, size); } -// TODO request malfunction indicator light (MIL) status - request mode 1 pid 1, -// parse first bit +// TODO everything below here is for future work...not critical for now. + DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( DiagnosticShims* shims, DiagnosticMilStatusReceived callback) { + // TODO request malfunction indicator light (MIL) status - request mode 1 + // pid 1, parse first bit } DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, @@ -34,14 +79,9 @@ DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, bool diagnostic_clear_dtc(DiagnosticShims* shims) { } -// before calling the callback, split up the received bytes into 1 or 2 byte -// chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs -// TODO request supported PIDs - request PID 0 and parse 4 bytes in response DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { -} - -void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, - const uint16_t arbitration_id, const uint8_t data[], - const uint8_t size) { + // before calling the callback, split up the received bytes into 1 or 2 byte + // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs + // TODO request supported PIDs - request PID 0 and parse 4 bytes in response } diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 7445952..e4ffc7a 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -114,6 +114,8 @@ typedef struct { IsoTpHandler isotp_handler; // TODO the Handle may need to keep the original request, otherwise we can't // compare an incoming CAN message to see if it matches the service / PID! + // TODO i'm not sure this type/callback in here is too useful - see the + // comments in obd2.c:diagnostic_request DiagnosticRequestType type; DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; @@ -129,7 +131,6 @@ typedef enum { typedef struct { IsoTpShims isotp_shims; LogShim log; - SendCanMessageShim send_can_message; } DiagnosticShims; DiagnosticShims diagnostic_init_shims(LogShim log, -- cgit 1.2.3-korg From 1d6ab7e3c86cfd018977cab89a0dd32762984d58 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 31 Dec 2013 12:59:20 -0500 Subject: Draft reworking of API. --- deps/bitfield-c | 2 +- deps/isotp-c | 2 +- src/obd2/obd2.c | 92 +++++++++++++++++++++++++++++++++++++++------------------ src/obd2/obd2.h | 14 ++++++++- 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/deps/bitfield-c b/deps/bitfield-c index cd74b88..4af52c4 160000 --- a/deps/bitfield-c +++ b/deps/bitfield-c @@ -1 +1 @@ -Subproject commit cd74b88432054107c439c4e565bea14840dd9ea5 +Subproject commit 4af52c415f1668fbd168da74d0aca903c592463f diff --git a/deps/isotp-c b/deps/isotp-c index bc7c020..10a35b0 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit bc7c0205d8dd7550060f6f0bbe8e2064d28ace8c +Subproject commit 10a35b0a7c380d77cdd24ac90d6aa0abd4601f3e diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 3d501d6..33e6da1 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -1,14 +1,15 @@ #include +#define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6 +#define MODE_BYTE_INDEX 0 +#define PID_BYTE_INDEX 1 + DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer) { DiagnosticShims shims = { - isotp_shims: { - log: log, - send_can_message: send_can_message, - set_timer: set_timer - }, + send_can_message: send_can_message, + set_timer: set_timer, log: log }; return shims; @@ -16,34 +17,60 @@ DiagnosticShims diagnostic_init_shims(LogShim log, DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { - // TODO hmm, where is message_received coming from? we would need 2 layers - // of callbacks. if we do it in the obd2 library, we have to have some - // context passed to that message_received handler so we can then retreive - // the obd2 callback. there was an option question of if we should pass a - // context with that callback, and maybe this answers it. - // - // alternatively, what if don't hide isotp and allow that to also be - // injected. the user has the iso_tp message_received callback, and in that - // they call a message_received handler from obd2. - // - // in fact that makes more sense - all the diagnostic_can_frame_received - // function is going to be able to do is call the equivalent function in the - // isotp library. it may or may not have a complete ISO-TP message. huh. DiagnosticRequestHandle handle = { - // TODO why are teh shims stored as a reference in the isotp handler? - // it's just 3 pointers - isotp_handler: isotp_init(&shims->isotp_shims, request->arbitration_id, - NULL, // TODO need a callback here! - NULL, NULL), - type: 0 //DIAGNOSTIC_.... // TODO how would we know the type? - //does it matter? we were going to have a different callback + type: DIAGNOSTIC_REQUEST_TYPE_PID, + callback: callback, + status: true }; + uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; + payload[MODE_BYTE_INDEX] = request->mode; + if(request->pid_length > 0) { + copy_bytes_right_aligned(request->pid, sizeof(request->pid), + PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload)); + } + if(request->payload_length > 0) { + memcpy(payload[PID_BYTE_INDEX + request->pid_length], + request->payload, request->payload_length); + } + + IsoTpShims isotp_shims = isotp_init_shims(shims->log, + shims->send_can_message, + shims->set_timer); + handle.status = isotp_send(&isotp_shims, request->arbitration_id, + payload, 1 + request->payload_length + request->pid_length, + diagnostic_receive_isotp_message); + + // TODO need to set up an isotp receive handler. in isotp, rx and tx are + // kind of intermingled at this point. really, there's not explicit link + // between send and receveice...well except for flow control. hm, damn. + // so there's 2 things: + // + // isotp_send needs to return a handle. if it was a single frame, we + // probably sent it right away so the status true and the callback was hit. + // the handle needs another flag to say if it was completed or not, so you + // know you can destroy it. you will continue to throw can frames at that + // handler until it returns completed (either with a flag, or maybe + // receive_can_frame returns true if it's complete) + // + // the second thing is that we need to be able to arbitrarly set up to + // receive an iso-tp message on a particular arb id. again, you keep + // throwing can frames at it until it returns a handle with the status + // completed and calls your callback + // + // so the diagnostic request needs 2 isotp handles and they should both be + // hidden from the user + // + // when a can frame is received and passes to the diagnostic handle + // if we haven't successfuly sent the entire message yet, give it to the + // isottp send handle + // if we have sent it, give it to the isotp rx handle + // if we've received properly, mark this request as completed + return handle; } DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, DiagnosticPidRequestType pid_request_type, uint16_t pid, DiagnosticResponseReceived callback) { - // decide mode 0x1 / 0x22 based on pid type DiagnosticRequest request = { mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, pid: pid @@ -52,12 +79,21 @@ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, return diagnostic_request(shims, &request, callback); } -void diagnostic_receive_can_frame(DiagnosticRequestHandle* handler, +void diagnostic_receive_isotp_message(const IsoTpMessage* message) { + // TODO +} + +void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size) { - isotp_receive_can_frame(handler->isotp_handler, arbitration_id, data, size); + isotp_receive_can_frame(handle->isotp_handler, arbitration_id, data, size); } +// TODO argh, we're now saying that user code will rx CAN messages, but who does +// it hand them to? isotp handlers are encapsulated in diagnostic handles + + + // TODO everything below here is for future work...not critical for now. DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index e4ffc7a..8d7d664 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -16,10 +16,15 @@ typedef struct { uint16_t arbitration_id; uint8_t mode; uint16_t pid; + uint8_t pid_length; uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; uint8_t payload_length; } DiagnosticRequest; +// TODO I don't like this, it's hard coding isotp library stuff here +typedef bool (*SendIsoTpMessageShim)(IsoTpHandler* handler, + const uint8_t* payload, uint16_t payload_size); + // Thanks to // http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 // for the list of NRCs @@ -116,6 +121,7 @@ typedef struct { // compare an incoming CAN message to see if it matches the service / PID! // TODO i'm not sure this type/callback in here is too useful - see the // comments in obd2.c:diagnostic_request + DiagnosticRequestType type; DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; @@ -129,7 +135,8 @@ typedef enum { } DiagnosticPidRequestType; typedef struct { - IsoTpShims isotp_shims; + SetTimerShim set_timer; + SendCanMessageShim send_can_message; LogShim log; } DiagnosticShims; @@ -161,6 +168,11 @@ bool diagnostic_clear_dtc(DiagnosticShims* shims); DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); +// TODO +// void diagnostic_receive_isotp_message(DiagnosticRequestHandle* handle, + // const IsoTpMessage* message); +void diagnostic_receive_isotp_message(const IsoTpMessage* message); + void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); -- cgit 1.2.3-korg From 7a5e3a7037170aacc3f3438f1267c635358d91a8 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 2 Jan 2014 12:00:52 -0500 Subject: Match isotp receive_can_frame style, depend less on callbacks. --- README.mkd | 10 ++--- deps/isotp-c | 2 +- src/obd2/obd2.c | 121 ++++++++++++++++++++++++++++++++++++++---------------- src/obd2/obd2.h | 18 ++++---- tests/test_core.c | 17 +++++--- 5 files changed, 111 insertions(+), 57 deletions(-) diff --git a/README.mkd b/README.mkd index 96b969e..28aee72 100644 --- a/README.mkd +++ b/README.mkd @@ -53,21 +53,21 @@ what we need is another layer on top of that to handle the repeated requests. void my_callback(const DiagnosticResponse* response) { } - DiagnosticRequestHandler handler = diagnostic_init(my_callback); + DiagnosticRequestHandle handle = diagnostic_init(my_callback); DiagnosticRequest request = { arbitratin_id: 0x7df, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, pid: 3 }; - diagnostic_send(&handler, &request); + diagnostic_send(&handle, &request); while(true) { - diagnostic_handle_can_frame(&handler, 42, data, 8); + diagnostic_handle_can_frame(&handle, 42, data, 8); } - diagnostic_request_pid(&handler, DIAGNOSTIC_STANDARD_PID, 42 + diagnostic_request_pid(&handle, DIAGNOSTIC_STANDARD_PID, 42 - diagnostic_destroy(&handler); + diagnostic_destroy(&handle); ## Testing diff --git a/deps/isotp-c b/deps/isotp-c index 10a35b0..fe20a27 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 10a35b0a7c380d77cdd24ac90d6aa0abd4601f3e +Subproject commit fe20a273bb3979d9e806d828486633249d073ede diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 33e6da1..7188af0 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -20,7 +20,8 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequestHandle handle = { type: DIAGNOSTIC_REQUEST_TYPE_PID, callback: callback, - status: true + success: false, + completed: false }; uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; @@ -33,38 +34,79 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, request->payload, request->payload_length); } - IsoTpShims isotp_shims = isotp_init_shims(shims->log, + handle.isotp_shims = isotp_init_shims(shims->log, shims->send_can_message, shims->set_timer); - handle.status = isotp_send(&isotp_shims, request->arbitration_id, - payload, 1 + request->payload_length + request->pid_length, - diagnostic_receive_isotp_message); - - // TODO need to set up an isotp receive handler. in isotp, rx and tx are - // kind of intermingled at this point. really, there's not explicit link - // between send and receveice...well except for flow control. hm, damn. - // so there's 2 things: - // - // isotp_send needs to return a handle. if it was a single frame, we - // probably sent it right away so the status true and the callback was hit. - // the handle needs another flag to say if it was completed or not, so you - // know you can destroy it. you will continue to throw can frames at that - // handler until it returns completed (either with a flag, or maybe - // receive_can_frame returns true if it's complete) - // - // the second thing is that we need to be able to arbitrarly set up to - // receive an iso-tp message on a particular arb id. again, you keep - // throwing can frames at it until it returns a handle with the status - // completed and calls your callback - // - // so the diagnostic request needs 2 isotp handles and they should both be - // hidden from the user - // + handle.isotp_send_handle = isotp_send(&handle.isotp_shims, + request->arbitration_id, payload, + 1 + request->payload_length + request->pid_length, + // TODO is this ok to pass null here? + NULL); + + handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims, + // TODO need to either always add 0x8 or let the user specify + request->arbitration_id + 0x8, + // TODO this callback is mostly useful for debugging stuff as it + // doesn't have the internal state we need to complete the + // diagnositc request - can we pass NULL or will that 'splode? + NULL); + // when a can frame is received and passes to the diagnostic handle // if we haven't successfuly sent the entire message yet, give it to the // isottp send handle // if we have sent it, give it to the isotp rx handle // if we've received properly, mark this request as completed + // how do you get the result? we have it set up for callbacks but that's + // getting to be kind of awkward + // + // say it were to call a callback...what state would it need to pass? + // + // the iso-tp message received callback needs to pass the handle and the + // received message + // + // so in the obd2 library, we get a callback with an isotp message. how do + // we know what diag request i went with, and which diag callback to use? we + // could store context with the isotp handle. the problem is that context is + // self referential and on the stack, so we really can't get a pointer to + // it. + // + // the diagnostic response received callback needs to pass the diagnostic + // handle and the diag response + // + // let's say we simplify things and drop the callbacks. + // + // isotp_receive_can_frame returns an isotp handle with a complete message + // in it if one was received + // + // diagnostic_receive_can_frame returns a diaghandle if one was received + // + // so in the user code you would throw the can frame at each of your active + // diag handles and see if any return a completed message. + // + // is there any advantage to a callback approach? callbacks are useful when + // you have something that will block, bt we don't have anything that will + // block. it's async but we return immediately from each partial parsing + // attempt. + // + // argh, but will we need the callbacks when we add timers for isotp multi + // frame? + // + // what are the timers for exactly? + // + // when sending multi frame, send 1 frame, wait for a response + // if it says send all, send all right away + // if it says flow control, set the time for the next send + // instead of creating a timer with an async callback, add a process_handle + // function that's called repeatedly in the main loop - if it's time to + // send, we do it. so there's a process_handle_send and receive_can_frame + // that are just called continuously from the main loop. it's a waste of a + // few cpu cycles but it may be more natural than callbacks. + // + // what woudl a timer callback look like...it would need to pass the handle + // and that's all. seems like a context void* would be able to capture all + // of the information but arg, memory allocation. look at how it's done in + // the other library again + // return handle; } @@ -79,20 +121,27 @@ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, return diagnostic_request(shims, &request, callback); } -void diagnostic_receive_isotp_message(const IsoTpMessage* message) { - // TODO -} - -void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle, +DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, + DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size) { - isotp_receive_can_frame(handle->isotp_handler, arbitration_id, data, size); -} - -// TODO argh, we're now saying that user code will rx CAN messages, but who does -// it hand them to? isotp handlers are encapsulated in diagnostic handles + if(!handle->isotp_send_handle.completed) { + } else if(!handle->isotp_receive_handle.completed) { + } else { + shims->log("Handle is already completed"); + } + // TODO determine which isotp handler to pass it to based on our state + IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims, + &handle->isotp_send_handle, arbitration_id, data, size); + DiagnosticResponse response = { + success: false, + completed: false + }; + if(message.completed) { + } +} // TODO everything below here is for future work...not critical for now. diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 8d7d664..727b816 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -22,7 +22,7 @@ typedef struct { } DiagnosticRequest; // TODO I don't like this, it's hard coding isotp library stuff here -typedef bool (*SendIsoTpMessageShim)(IsoTpHandler* handler, +typedef bool (*SendIsoTpMessageShim)(IsoTpHandle* handle, const uint8_t* payload, uint16_t payload_size); // Thanks to @@ -68,6 +68,7 @@ typedef struct { uint16_t arbitration_id; uint8_t mode; bool success; + bool completed; // if mode is one with a PID, read the correct numbers of PID bytes (1 or 2) // into this field, then store the remainder of the payload in the payload // field @@ -116,7 +117,11 @@ typedef enum { } DiagnosticRequestType; typedef struct { - IsoTpHandler isotp_handler; + bool success; + bool completed; + IsoTpShims isotp_shims; + IsoTpHandle isotp_send_handle; + IsoTpHandle isotp_receive_handle; // TODO the Handle may need to keep the original request, otherwise we can't // compare an incoming CAN message to see if it matches the service / PID! // TODO i'm not sure this type/callback in here is too useful - see the @@ -126,7 +131,6 @@ typedef struct { DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; DiagnosticVinReceived vin_callback; - bool status; } DiagnosticRequestHandle; typedef enum { @@ -168,12 +172,8 @@ bool diagnostic_clear_dtc(DiagnosticShims* shims); DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); -// TODO -// void diagnostic_receive_isotp_message(DiagnosticRequestHandle* handle, - // const IsoTpMessage* message); -void diagnostic_receive_isotp_message(const IsoTpMessage* message); - -void diagnostic_receive_can_frame(DiagnosticRequestHandle* handle, +DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, + DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); diff --git a/tests/test_core.c b/tests/test_core.c index 340a326..a466e8e 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -21,8 +21,8 @@ START_TEST (test_receive_wrong_arb_id) fail_if(last_response_was_received); const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; - diagnostic_receive_can_frame(&handle, request.arbitration_id, can_data, - sizeof(can_data)); + diagnostic_receive_can_frame(&SHIMS, &handle, request.arbitration_id, + can_data, sizeof(can_data)); fail_if(last_response_was_received); } END_TEST @@ -36,10 +36,15 @@ START_TEST (test_send_diag_request) DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); + fail_if(handle.completed); + fail_if(last_response_was_received); const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; - diagnostic_receive_can_frame(&handle, request.arbitration_id + 0x8, - can_data, sizeof(can_data)); + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + fail_unless(response.success); + fail_unless(response.completed); + fail_unless(handle.completed); fail_unless(last_response_was_received); ck_assert(last_response_received.success); ck_assert_int_eq(last_response_received.arbitration_id, @@ -60,7 +65,7 @@ START_TEST (test_request_pid_standard) fail_if(last_response_was_received); const uint8_t can_data[] = {0x3, 0x1 + 0x40, 0x2, 0x45}; // TODO need a constant for the 7df broadcast functional request - diagnostic_receive_can_frame(&handle, 0x7df + 0x8, + diagnostic_receive_can_frame(&SHIMS, &handle, 0x7df + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); ck_assert(last_response_received.success); @@ -82,7 +87,7 @@ START_TEST (test_request_pid_enhanced) fail_if(last_response_was_received); const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; // TODO need a constant for the 7df broadcast functional request - diagnostic_receive_can_frame(&handle, 0x7df + 0x8, can_data, + diagnostic_receive_can_frame(&SHIMS, &handle, 0x7df + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); ck_assert(last_response_received.success); -- cgit 1.2.3-korg From 54713fc5deab5de318d79035a0927d828ae239f5 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 2 Jan 2014 15:28:06 -0500 Subject: Draft implemenation of receiving and parsing single fram diag messages. --- README.mkd | 2 ++ deps/isotp-c | 2 +- src/obd2/obd2.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++------- src/obd2/obd2.h | 30 ++++++++------------ tests/common.c | 6 ---- tests/test_core.c | 10 ++++--- 6 files changed, 95 insertions(+), 39 deletions(-) diff --git a/README.mkd b/README.mkd index 28aee72..bd074e9 100644 --- a/README.mkd +++ b/README.mkd @@ -1,6 +1,8 @@ OBD-II Support Library in C ============================= +TODO diagram out a request, response and error response + TODO isotp needs to accept responses on an ID other that the request's arb id - or maybe we have 2 isotp instances, for rx and tx. diff --git a/deps/isotp-c b/deps/isotp-c index fe20a27..e3637d9 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit fe20a273bb3979d9e806d828486633249d073ede +Subproject commit e3637d97ecaef1768d3f9ef40cb0204a0e668ff2 diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 7188af0..d8ef190 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -1,8 +1,12 @@ #include +#define MODE_RESPONSE_OFFSET 0x40 +#define NEGATIVE_RESPONSE_MODE 0x7f #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6 #define MODE_BYTE_INDEX 0 #define PID_BYTE_INDEX 1 +#define NEGATIVE_RESPONSE_MODE_INDEX 1 +#define NEGATIVE_RESPONSE_NRC_INDEX 2 DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, @@ -18,11 +22,13 @@ DiagnosticShims diagnostic_init_shims(LogShim log, DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { - type: DIAGNOSTIC_REQUEST_TYPE_PID, + // TODO can we copy the request? + request: *request, callback: callback, success: false, completed: false }; + uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { @@ -125,22 +131,80 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size) { - if(!handle->isotp_send_handle.completed) { - } else if(!handle->isotp_receive_handle.completed) { - } else { - shims->log("Handle is already completed"); - } - // TODO determine which isotp handler to pass it to based on our state - IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims, - &handle->isotp_send_handle, arbitration_id, data, size); DiagnosticResponse response = { + arbitration_id: arbitration_id, success: false, completed: false }; - if(message.completed) { + if(!handle->isotp_send_handle.completed) { + // TODO when completing a send, this returns...a Message? we have to + // check when the isotp_send_handle is completed, and if it is, start + isotp_receive_can_frame(&handle->isotp_shims, + &handle->isotp_send_handle, arbitration_id, data, size); + } else if(!handle->isotp_receive_handle.completed) { + IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims, + &handle->isotp_receive_handle, arbitration_id, data, size); + + if(message.completed) { + if(message.size > 0) { + response.mode = message.payload[0]; + if(response.mode == NEGATIVE_RESPONSE_MODE) { + if(message.size > NEGATIVE_RESPONSE_MODE_INDEX) { + // TODO we're setting the mode to the originating + // request for the error, so the user can confirm - i + // think this is OK since we're storing the failure + // status elsewhere, but think about it. + response.mode = message.payload[NEGATIVE_RESPONSE_MODE_INDEX]; + } + + if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) { + response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX]; + } + response.success = false; + } else { + if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) { + // hide the "response" version of the mode from the user + // if it matched + response.mode = handle->request.mode; + if(handle->request.pid_length > 0 && message.size > 1) { + copy_bytes_right_aligned(handle->request.pid, sizeof(handle->request.pid), + PID_BYTE_INDEX, handle->request.pid_length, response.pid, + sizeof(response.pid)); + } + + uint8_t payload_index = 1 + handle->request.pid_length; + response.payload_length = message.size - payload_index; + if(response.payload_length > 0) { + memcpy(response.payload, &message.payload[payload_index], + response.payload_length); + } + response.success = true; + } else { + shims->log("Response was for a mode %d request, not our mode %d request", + response.mode - MODE_RESPONSE_OFFSET, + handle->request.mode); + } + } + } + + response.completed = true; + // TODO what does it mean for the handle to be successful, vs. the + // request to be successful? if we get a NRC, is that a successful + // request? + handle->success = true; + handle->completed = true; + + if(handle->callback != NULL) { + handle->callback(&response); + } + } + + } else { + shims->log("Handle is already completed"); } + return response; } // TODO everything below here is for future work...not critical for now. diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 727b816..7589483 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -9,10 +9,20 @@ extern "C" { #endif +// TODO This isn't true for multi frame messages - we may need to dynamically +// allocate this in the future #define MAX_OBD2_PAYLOAD_LENGTH 7 #define VIN_LENGTH 17 +typedef enum { + DIAGNOSTIC_REQUEST_TYPE_PID, + DIAGNOSTIC_REQUEST_TYPE_DTC, + DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, + DIAGNOSTIC_REQUEST_TYPE_VIN +} DiagnosticRequestType; + typedef struct { + DiagnosticRequestType type; uint16_t arbitration_id; uint8_t mode; uint16_t pid; @@ -69,13 +79,8 @@ typedef struct { uint8_t mode; bool success; bool completed; - // if mode is one with a PID, read the correct numbers of PID bytes (1 or 2) - // into this field, then store the remainder of the payload in the payload - // field uint16_t pid; DiagnosticNegativeResponseCode negative_response_code; - // if response mode is a negative response, read first byte of payload into - // NRC and store remainder of payload in payload field uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; uint8_t payload_length; } DiagnosticResponse; @@ -109,25 +114,14 @@ typedef struct { float max_value; } DiagnosticParameter; -typedef enum { - DIAGNOSTIC_REQUEST_TYPE_PID, - DIAGNOSTIC_REQUEST_TYPE_DTC, - DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, - DIAGNOSTIC_REQUEST_TYPE_VIN -} DiagnosticRequestType; - typedef struct { + DiagnosticRequest request; bool success; bool completed; + IsoTpShims isotp_shims; IsoTpHandle isotp_send_handle; IsoTpHandle isotp_receive_handle; - // TODO the Handle may need to keep the original request, otherwise we can't - // compare an incoming CAN message to see if it matches the service / PID! - // TODO i'm not sure this type/callback in here is too useful - see the - // comments in obd2.c:diagnostic_request - - DiagnosticRequestType type; DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; DiagnosticVinReceived vin_callback; diff --git a/tests/common.c b/tests/common.c index e35ef52..99b2f7c 100644 --- a/tests/common.c +++ b/tests/common.c @@ -35,12 +35,6 @@ void mock_send_can(const uint16_t arbitration_id, const uint8_t* data, void mock_set_timer(uint16_t time_ms, void (*callback)) { } -void response_received_handler(const DiagnosticResponse* response) { - last_response_was_received = true; - // TODO not sure if we can copy the struct like this - last_response_received = *response; -} - void setup() { SHIMS = diagnostic_init_shims(debug, mock_send_can, mock_set_timer); memset(last_can_payload_sent, 0, sizeof(last_can_payload_sent)); diff --git a/tests/test_core.c b/tests/test_core.c index a466e8e..2730cd7 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -8,7 +8,12 @@ extern void setup(); extern bool last_response_was_received; extern DiagnosticResponse last_response_received; extern DiagnosticShims SHIMS; -extern DiagnosticResponseReceived response_received_handler; + +void response_received_handler(const DiagnosticResponse* response) { + last_response_was_received = true; + // TODO not sure if we can copy the struct like this + last_response_received = *response; +} START_TEST (test_receive_wrong_arb_id) { @@ -45,11 +50,9 @@ START_TEST (test_send_diag_request) fail_unless(response.success); fail_unless(response.completed); fail_unless(handle.completed); - fail_unless(last_response_was_received); ck_assert(last_response_received.success); ck_assert_int_eq(last_response_received.arbitration_id, request.arbitration_id + 0x8); - // TODO should we set it back to the original mode, or leave as mode + 0x40? ck_assert_int_eq(last_response_received.mode, request.mode); ck_assert_int_eq(last_response_received.pid, 0); ck_assert_int_eq(last_response_received.payload_length, 1); @@ -71,7 +74,6 @@ START_TEST (test_request_pid_standard) ck_assert(last_response_received.success); ck_assert_int_eq(last_response_received.arbitration_id, 0x7df + 0x8); - // TODO should we set it back to the original mode, or leave as mode + 0x40? ck_assert_int_eq(last_response_received.mode, 0x1); ck_assert_int_eq(last_response_received.pid, 0x2); ck_assert_int_eq(last_response_received.payload_length, 1); -- cgit 1.2.3-korg From 8e574cb4798a928ac50ffe6cb4fa5e3d22b8003a Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 2 Jan 2014 17:15:24 -0500 Subject: Test basic diag request and response! --- deps/isotp-c | 2 +- src/obd2/obd2.c | 39 ++++++++++++++++++++------------------- src/obd2/obd2.h | 8 ++++---- tests/test_core.c | 36 ++++++++++++++++++++++++++---------- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index e3637d9..3b25a04 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit e3637d97ecaef1768d3f9ef40cb0204a0e668ff2 +Subproject commit 3b25a0491ce9ef9b55c903c6c7f0929bc2910d1a diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index d8ef190..1dcb827 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -1,4 +1,5 @@ #include +#include #define MODE_RESPONSE_OFFSET 0x40 #define NEGATIVE_RESPONSE_MODE 0x7f @@ -12,9 +13,9 @@ DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer) { DiagnosticShims shims = { + log: log, send_can_message: send_can_message, - set_timer: set_timer, - log: log + set_timer: set_timer }; return shims; } @@ -32,11 +33,11 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { - copy_bytes_right_aligned(request->pid, sizeof(request->pid), + copy_bytes_right_aligned(&request->pid, sizeof(request->pid), PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload)); } if(request->payload_length > 0) { - memcpy(payload[PID_BYTE_INDEX + request->pid_length], + memcpy(&payload[PID_BYTE_INDEX + request->pid_length], request->payload, request->payload_length); } @@ -46,15 +47,11 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, handle.isotp_send_handle = isotp_send(&handle.isotp_shims, request->arbitration_id, payload, 1 + request->payload_length + request->pid_length, - // TODO is this ok to pass null here? NULL); handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims, // TODO need to either always add 0x8 or let the user specify request->arbitration_id + 0x8, - // TODO this callback is mostly useful for debugging stuff as it - // doesn't have the internal state we need to complete the - // diagnositc request - can we pass NULL or will that 'splode? NULL); // when a can frame is received and passes to the diagnostic handle @@ -117,20 +114,21 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, } DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t pid, - DiagnosticResponseReceived callback) { + DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + uint16_t pid, DiagnosticResponseReceived callback) { DiagnosticRequest request = { + arbitration_id: arbitration_id, mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, - pid: pid + pid: pid, + pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2 }; return diagnostic_request(shims, &request, callback); } DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, - DiagnosticRequestHandle* handle, - const uint16_t arbitration_id, const uint8_t data[], - const uint8_t size) { + DiagnosticRequestHandle* handle, const uint16_t arbitration_id, + const uint8_t data[], const uint8_t size) { DiagnosticResponse response = { arbitration_id: arbitration_id, @@ -169,12 +167,15 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, // if it matched response.mode = handle->request.mode; if(handle->request.pid_length > 0 && message.size > 1) { - copy_bytes_right_aligned(handle->request.pid, sizeof(handle->request.pid), - PID_BYTE_INDEX, handle->request.pid_length, response.pid, - sizeof(response.pid)); + if(handle->request.pid_length == 2) { + response.pid = *(uint16_t*)&message.payload[PID_BYTE_INDEX]; + response.pid = ntohs(response.pid); + } else { + response.pid = message.payload[PID_BYTE_INDEX]; + } } - uint8_t payload_index = 1 + handle->request.pid_length; + uint8_t payload_index = 1 + handle->request.pid_length; response.payload_length = message.size - payload_index; if(response.payload_length > 0) { memcpy(response.payload, &message.payload[payload_index], @@ -182,7 +183,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, } response.success = true; } else { - shims->log("Response was for a mode %d request, not our mode %d request", + shims->log("Response was for a mode 0x%x request, not our mode 0x%x request", response.mode - MODE_RESPONSE_OFFSET, handle->request.mode); } diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 7589483..2bb6c77 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -133,9 +133,9 @@ typedef enum { } DiagnosticPidRequestType; typedef struct { - SetTimerShim set_timer; - SendCanMessageShim send_can_message; LogShim log; + SendCanMessageShim send_can_message; + SetTimerShim set_timer; } DiagnosticShims; DiagnosticShims diagnostic_init_shims(LogShim log, @@ -147,8 +147,8 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, // decide mode 0x1 / 0x22 based on pid type DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t pid, - DiagnosticResponseReceived callback); + DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + uint16_t pid, DiagnosticResponseReceived callback); DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( DiagnosticShims* shims, diff --git a/tests/test_core.c b/tests/test_core.c index 2730cd7..4b24734 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -62,18 +62,19 @@ END_TEST START_TEST (test_request_pid_standard) { + // TODO need a constant for the 7df broadcast functional request + uint16_t arb_id = 0x7df; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_STANDARD_PID, 0x2, response_received_handler); + DIAGNOSTIC_STANDARD_PID, arb_id, 0x2, response_received_handler); fail_if(last_response_was_received); const uint8_t can_data[] = {0x3, 0x1 + 0x40, 0x2, 0x45}; - // TODO need a constant for the 7df broadcast functional request - diagnostic_receive_can_frame(&SHIMS, &handle, 0x7df + 0x8, + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); ck_assert(last_response_received.success); ck_assert_int_eq(last_response_received.arbitration_id, - 0x7df + 0x8); + arb_id + 0x8); ck_assert_int_eq(last_response_received.mode, 0x1); ck_assert_int_eq(last_response_received.pid, 0x2); ck_assert_int_eq(last_response_received.payload_length, 1); @@ -83,19 +84,18 @@ END_TEST START_TEST (test_request_pid_enhanced) { + uint16_t arb_id = 0x7df; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); fail_if(last_response_was_received); - const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; - // TODO need a constant for the 7df broadcast functional request - diagnostic_receive_can_frame(&SHIMS, &handle, 0x7df + 0x8, can_data, + const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x2, 0x45}; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); ck_assert(last_response_received.success); ck_assert_int_eq(last_response_received.arbitration_id, - 0x7df + 0x8); - // TODO should we set it back to the original mode, or leave as mode + 0x40? + arb_id + 0x8); ck_assert_int_eq(last_response_received.mode, 0x22); ck_assert_int_eq(last_response_received.pid, 0x2); ck_assert_int_eq(last_response_received.payload_length, 1); @@ -103,6 +103,21 @@ START_TEST (test_request_pid_enhanced) } END_TEST +START_TEST (test_wrong_mode_response) +{ + uint16_t arb_id = 0x7df; + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, + sizeof(can_data)); + fail_unless(last_response_was_received); + fail_if(last_response_received.success); +} +END_TEST + Suite* testSuite(void) { Suite* s = suite_create("obd2"); TCase *tc_core = tcase_create("core"); @@ -111,6 +126,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_receive_wrong_arb_id); tcase_add_test(tc_core, test_request_pid_standard); tcase_add_test(tc_core, test_request_pid_enhanced); + tcase_add_test(tc_core, test_wrong_mode_response); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From 26f5b1e5648c437e22cfcb7c87c22b40424ef198 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:40:46 -0500 Subject: Update version of isotp-c library. --- deps/isotp-c | 2 +- src/obd2/obd2.c | 4 ++-- src/obd2/obd2.h | 8 ++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index 3b25a04..101d36e 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 3b25a0491ce9ef9b55c903c6c7f0929bc2910d1a +Subproject commit 101d36e65bc7cb2b1fc8e4dbae4eacf749c98b00 diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 1dcb827..d3c0fb9 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -139,10 +139,10 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(!handle->isotp_send_handle.completed) { // TODO when completing a send, this returns...a Message? we have to // check when the isotp_send_handle is completed, and if it is, start - isotp_receive_can_frame(&handle->isotp_shims, + isotp_continue_send(&handle->isotp_shims, &handle->isotp_send_handle, arbitration_id, data, size); } else if(!handle->isotp_receive_handle.completed) { - IsoTpMessage message = isotp_receive_can_frame(&handle->isotp_shims, + IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handle, arbitration_id, data, size); if(message.completed) { diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 2bb6c77..a2ea033 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -31,10 +31,6 @@ typedef struct { uint8_t payload_length; } DiagnosticRequest; -// TODO I don't like this, it's hard coding isotp library stuff here -typedef bool (*SendIsoTpMessageShim)(IsoTpHandle* handle, - const uint8_t* payload, uint16_t payload_size); - // Thanks to // http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 // for the list of NRCs @@ -120,8 +116,8 @@ typedef struct { bool completed; IsoTpShims isotp_shims; - IsoTpHandle isotp_send_handle; - IsoTpHandle isotp_receive_handle; + IsoTpSendHandle isotp_send_handle; + IsoTpReceiveHandle isotp_receive_handle; DiagnosticResponseReceived callback; DiagnosticMilStatusReceived mil_status_callback; DiagnosticVinReceived vin_callback; -- cgit 1.2.3-korg From d7ce859269d54441306f018440997fb8a115d60a Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:44:32 -0500 Subject: Compile with test coverage calculation. --- .gitignore | 1 + Makefile | 33 +++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b4225c0..834a305 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .DS_Store *~ *.bin +build diff --git a/Makefile b/Makefile index 9873038..6d1dd5a 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ CC = gcc INCLUDES = -Isrc -Ideps/bitfield-c/src -Ideps/isotp-c/src -CFLAGS = $(INCLUDES) -c -w -Wall -Werror -g -ggdb -std=c99 -LDFLAGS = +CFLAGS = $(INCLUDES) -c -w -Wall -Werror -g -ggdb -std=gnu++0x -coverage +LDFLAGS = -coverage -lm LDLIBS = -lcheck TEST_DIR = tests +TEST_OBJDIR = build # Guard against \r\n line endings only in Cygwin OSTYPE := $(shell uname) @@ -18,22 +19,38 @@ endif SRC = $(wildcard src/**/*.c) SRC += $(wildcard deps/bitfield-c/src/**/*.c) SRC += $(wildcard deps/isotp-c/src/**/*.c) -OBJS = $(SRC:.c=.o) +OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(SRC:.c=.o)) TEST_SRC = $(wildcard $(TEST_DIR)/test_*.c) -TESTS=$(patsubst %.c,%.bin,$(TEST_SRC)) +TESTS=$(patsubst %.c,$(TEST_OBJDIR)/%.bin,$(TEST_SRC)) TEST_SUPPORT_SRC = $(TEST_DIR)/common.c -TEST_SUPPORT_OBJS = $(TEST_SUPPORT_SRC:.c=.o) +TEST_SUPPORT_OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(TEST_SUPPORT_SRC:.c=.o)) all: $(OBJS) test: $(TESTS) @set -o $(TEST_SET_OPTS) >/dev/null 2>&1 @export SHELLOPTS - @sh runtests.sh $(TEST_DIR) + @sh runtests.sh $(TEST_OBJDIR)/$(TEST_DIR) -$(TEST_DIR)/%.bin: $(TEST_DIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) +COVERAGE_INFO_FILENAME = coverage.info +COVERAGE_INFO_PATH = $(TEST_OBJDIR)/$(COVERAGE_INFO_FILENAME) +coverage: + @lcov --base-directory . --directory src --zerocounters -q + @make clean + @make test + @lcov --base-directory . --directory $(TEST_OBJDIR) -c -o $(TEST_OBJDIR)/coverage.info + @lcov --remove $(COVERAGE_INFO_PATH) "deps/*" -o $(COVERAGE_INFO_PATH) + @genhtml -o $(TEST_OBJDIR)/coverage -t "isotp-c test coverage" --num-spaces 4 $(COVERAGE_INFO_PATH) + @$(BROWSER) $(TEST_OBJDIR)/coverage/index.html + @echo "$(GREEN)Coverage information generated in $(TEST_OBJDIR)/coverage/index.html.$(COLOR_RESET)" + +$(TEST_OBJDIR)/%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $< + +$(TEST_OBJDIR)/%.bin: $(TEST_OBJDIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) @mkdir -p $(dir $@) $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS) clean: - rm -rf **/*.o $(TEST_DIR)/*.bin + rm -rf $(TEST_OBJDIR) -- cgit 1.2.3-korg From d8363cdf340fc53815a8fde7f47a6e90714217d3 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:44:58 -0500 Subject: Update bitfield library version. --- deps/bitfield-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/bitfield-c b/deps/bitfield-c index 4af52c4..09dc97c 160000 --- a/deps/bitfield-c +++ b/deps/bitfield-c @@ -1 +1 @@ -Subproject commit 4af52c415f1668fbd168da74d0aca903c592463f +Subproject commit 09dc97c24f9f9a9c2638405167bffe339f666e2a -- cgit 1.2.3-korg From ab831f90eb744af3afafafa42b4f0608512ac243 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:51:42 -0500 Subject: Test sending payload with diagnostic request. --- tests/test_core.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_core.c b/tests/test_core.c index 4b24734..88a753d 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -8,6 +8,9 @@ extern void setup(); extern bool last_response_was_received; extern DiagnosticResponse last_response_received; extern DiagnosticShims SHIMS; +extern uint16_t last_can_frame_sent_arb_id; +extern uint8_t last_can_payload_sent[8]; +extern uint8_t last_can_payload_size; void response_received_handler(const DiagnosticResponse* response) { last_response_was_received = true; @@ -32,6 +35,28 @@ START_TEST (test_receive_wrong_arb_id) } END_TEST +START_TEST (test_send_diag_request_with_payload) +{ + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, + payload: {0x12, 0x34}, + payload_length: 2 + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(handle.completed); + // TODO it'd be better to check the ISO-TP message instead of the CAN frame, + // but we don't have a good way to do that + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_size, 4); + ck_assert_int_eq(last_can_payload_sent[2], request.payload[0]); + ck_assert_int_eq(last_can_payload_sent[3], request.payload[1]); +} +END_TEST + START_TEST (test_send_diag_request) { DiagnosticRequest request = { @@ -42,6 +67,9 @@ START_TEST (test_send_diag_request) response_received_handler); fail_if(handle.completed); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_size, 2); fail_if(last_response_was_received); const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; @@ -123,6 +151,7 @@ Suite* testSuite(void) { TCase *tc_core = tcase_create("core"); tcase_add_checked_fixture(tc_core, setup, NULL); tcase_add_test(tc_core, test_send_diag_request); + tcase_add_test(tc_core, test_send_diag_request_with_payload); tcase_add_test(tc_core, test_receive_wrong_arb_id); tcase_add_test(tc_core, test_request_pid_standard); tcase_add_test(tc_core, test_request_pid_enhanced); -- cgit 1.2.3-korg From 6876f328d9a442275c9b6e0a6a6d2db14ae3a35c Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:52:17 -0500 Subject: Remove a resolved TODO. --- src/obd2/obd2.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index d3c0fb9..627128c 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -137,8 +137,6 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, }; if(!handle->isotp_send_handle.completed) { - // TODO when completing a send, this returns...a Message? we have to - // check when the isotp_send_handle is completed, and if it is, start isotp_continue_send(&handle->isotp_shims, &handle->isotp_send_handle, arbitration_id, data, size); } else if(!handle->isotp_receive_handle.completed) { -- cgit 1.2.3-korg From 183ee8ad424f06850af903668314d7e9cda9a3e0 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 14:54:02 -0500 Subject: Test receiving a CAN frame to an already completed message. --- tests/test_core.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_core.c b/tests/test_core.c index 88a753d..dbe2ead 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -146,6 +146,40 @@ START_TEST (test_wrong_mode_response) } END_TEST +START_TEST (test_handle_completed) +{ + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(handle.completed); + + const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + fail_unless(response.success); + fail_unless(response.completed); + fail_unless(handle.completed); + + response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + fail_if(response.success); + fail_if(response.completed); + fail_unless(handle.completed); + + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + request.arbitration_id + 0x8); + ck_assert_int_eq(last_response_received.mode, request.mode); + ck_assert_int_eq(last_response_received.pid, 0); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[2]); +} +END_TEST + Suite* testSuite(void) { Suite* s = suite_create("obd2"); TCase *tc_core = tcase_create("core"); @@ -156,6 +190,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_request_pid_standard); tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); + tcase_add_test(tc_core, test_handle_completed); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From 3fe62dd9870c59e600eb6f51603a49325372979e Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 15:09:10 -0500 Subject: Test receiving a negative response. --- src/obd2/obd2.c | 8 +++++--- tests/test_core.c | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 627128c..05be834 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -143,6 +143,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handle, arbitration_id, data, size); + // TODO this could be cleaned and DRY'd up a bit if(message.completed) { if(message.size > 0) { response.mode = message.payload[0]; @@ -158,6 +159,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) { response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX]; } + response.success = false; } else { if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) { @@ -189,9 +191,9 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, } response.completed = true; - // TODO what does it mean for the handle to be successful, vs. the - // request to be successful? if we get a NRC, is that a successful - // request? + // TODO clarify what it means for a handle to be successful (we made + // a good request+response) vs a request itself being successfuly + // (the other node didn't return a negative response). handle->success = true; handle->completed = true; diff --git a/tests/test_core.c b/tests/test_core.c index dbe2ead..6e1960a 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -180,6 +180,31 @@ START_TEST (test_handle_completed) } END_TEST +START_TEST (test_negative_response) +{ + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + const uint8_t can_data[] = {0x3, 0x7f, request.mode, NRC_SERVICE_NOT_SUPPORTED}; + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + fail_unless(response.completed); + fail_if(response.success); + fail_unless(handle.completed); + + fail_if(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + request.arbitration_id + 0x8); + ck_assert_int_eq(last_response_received.mode, request.mode); + ck_assert_int_eq(last_response_received.pid, 0); + ck_assert_int_eq(last_response_received.negative_response_code, NRC_SERVICE_NOT_SUPPORTED); + ck_assert_int_eq(last_response_received.payload_length, 0); +} +END_TEST + Suite* testSuite(void) { Suite* s = suite_create("obd2"); TCase *tc_core = tcase_create("core"); @@ -191,6 +216,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); tcase_add_test(tc_core, test_handle_completed); + tcase_add_test(tc_core, test_negative_response); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From 695e488b19d3b6884ccd7ca1010c8180484ccceb Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 15:25:16 -0500 Subject: Clean up the primary diag request handler. --- src/obd2/extras.c | 29 ++++++++ src/obd2/obd2.c | 196 +++++++++++++++++++++--------------------------------- tests/test_core.c | 5 +- 3 files changed, 106 insertions(+), 124 deletions(-) create mode 100644 src/obd2/extras.c diff --git a/src/obd2/extras.c b/src/obd2/extras.c new file mode 100644 index 0000000..ec1d996 --- /dev/null +++ b/src/obd2/extras.c @@ -0,0 +1,29 @@ +#include + +// TODO everything below here is for future work...not critical for now. + +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback) { + // TODO request malfunction indicator light (MIL) status - request mode 1 + // pid 1, parse first bit +} + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback) { +} + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback) { +} + +bool diagnostic_clear_dtc(DiagnosticShims* shims) { +} + +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { + // before calling the callback, split up the received bytes into 1 or 2 byte + // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs + // TODO request supported PIDs - request PID 0 and parse 4 bytes in response +} diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 05be834..0590c1b 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -1,6 +1,7 @@ #include #include +#define ARBITRATION_ID_OFFSET 0x8 #define MODE_RESPONSE_OFFSET 0x40 #define NEGATIVE_RESPONSE_MODE 0x7f #define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6 @@ -23,7 +24,6 @@ DiagnosticShims diagnostic_init_shims(LogShim log, DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { - // TODO can we copy the request? request: *request, callback: callback, success: false, @@ -50,51 +50,11 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, NULL); handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims, - // TODO need to either always add 0x8 or let the user specify - request->arbitration_id + 0x8, + request->arbitration_id + ARBITRATION_ID_OFFSET, NULL); - // when a can frame is received and passes to the diagnostic handle - // if we haven't successfuly sent the entire message yet, give it to the - // isottp send handle - // if we have sent it, give it to the isotp rx handle - // if we've received properly, mark this request as completed - // how do you get the result? we have it set up for callbacks but that's - // getting to be kind of awkward - // - // say it were to call a callback...what state would it need to pass? - // - // the iso-tp message received callback needs to pass the handle and the - // received message - // - // so in the obd2 library, we get a callback with an isotp message. how do - // we know what diag request i went with, and which diag callback to use? we - // could store context with the isotp handle. the problem is that context is - // self referential and on the stack, so we really can't get a pointer to - // it. - // - // the diagnostic response received callback needs to pass the diagnostic - // handle and the diag response - // - // let's say we simplify things and drop the callbacks. - // - // isotp_receive_can_frame returns an isotp handle with a complete message - // in it if one was received - // - // diagnostic_receive_can_frame returns a diaghandle if one was received - // - // so in the user code you would throw the can frame at each of your active - // diag handles and see if any return a completed message. - // - // is there any advantage to a callback approach? callbacks are useful when - // you have something that will block, bt we don't have anything that will - // block. it's async but we return immediately from each partial parsing - // attempt. - // - // argh, but will we need the callbacks when we add timers for isotp multi - // frame? - // - // what are the timers for exactly? + // TODO notes on multi frame: + // TODO what are the timers for exactly? // // when sending multi frame, send 1 frame, wait for a response // if it says send all, send all right away @@ -126,6 +86,55 @@ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, return diagnostic_request(shims, &request, callback); } +static bool handle_negative_response(IsoTpMessage* message, + DiagnosticResponse* response, DiagnosticShims* shims) { + bool response_was_negative = false; + if(response->mode == NEGATIVE_RESPONSE_MODE) { + response_was_negative = true; + if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) { + response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX]; + } + + if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) { + response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX]; + } + + response->success = false; + response->completed = true; + } + return response_was_negative; +} + +static bool handle_positive_response(DiagnosticRequestHandle* handle, + IsoTpMessage* message, DiagnosticResponse* response, + DiagnosticShims* shims) { + bool response_was_positive = false; + if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) { + response_was_positive = true; + // hide the "response" version of the mode from the user + // if it matched + response->mode = handle->request.mode; + if(handle->request.pid_length > 0 && message->size > 1) { + if(handle->request.pid_length == 2) { + response->pid = *(uint16_t*)&message->payload[PID_BYTE_INDEX]; + response->pid = ntohs(response->pid); + } else { + response->pid = message->payload[PID_BYTE_INDEX]; + } + } + + uint8_t payload_index = 1 + handle->request.pid_length; + response->payload_length = message->size - payload_index; + if(response->payload_length > 0) { + memcpy(response->payload, &message->payload[payload_index], + response->payload_length); + } + response->success = true; + response->completed = true; + } + return response_was_positive; +} + DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size) { @@ -143,95 +152,40 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handle, arbitration_id, data, size); - // TODO this could be cleaned and DRY'd up a bit if(message.completed) { if(message.size > 0) { response.mode = message.payload[0]; - if(response.mode == NEGATIVE_RESPONSE_MODE) { - if(message.size > NEGATIVE_RESPONSE_MODE_INDEX) { - // TODO we're setting the mode to the originating - // request for the error, so the user can confirm - i - // think this is OK since we're storing the failure - // status elsewhere, but think about it. - response.mode = message.payload[NEGATIVE_RESPONSE_MODE_INDEX]; - } - - if(message.size > NEGATIVE_RESPONSE_NRC_INDEX) { - response.negative_response_code = message.payload[NEGATIVE_RESPONSE_NRC_INDEX]; - } - - response.success = false; + if(handle_negative_response(&message, &response, shims)) { + shims->log("Received a negative response to mode %d on arb ID 0x%x", + response.mode, response.arbitration_id); + + // TODO clarify what it means for a handle to be successful (we made + // a good request+response) vs a request itself being + // successfully + // (the other node didn't return a negative response). + handle->success = true; + handle->completed = true; + } else if(handle_positive_response(handle, &message, &response, + shims)) { + shims->log("Received a positive mode %d response on arb ID 0x%x", + response.mode, response.arbitration_id); + handle->success = true; + handle->completed = true; } else { - if(response.mode == handle->request.mode + MODE_RESPONSE_OFFSET) { - // hide the "response" version of the mode from the user - // if it matched - response.mode = handle->request.mode; - if(handle->request.pid_length > 0 && message.size > 1) { - if(handle->request.pid_length == 2) { - response.pid = *(uint16_t*)&message.payload[PID_BYTE_INDEX]; - response.pid = ntohs(response.pid); - } else { - response.pid = message.payload[PID_BYTE_INDEX]; - } - } - - uint8_t payload_index = 1 + handle->request.pid_length; - response.payload_length = message.size - payload_index; - if(response.payload_length > 0) { - memcpy(response.payload, &message.payload[payload_index], - response.payload_length); - } - response.success = true; - } else { - shims->log("Response was for a mode 0x%x request, not our mode 0x%x request", - response.mode - MODE_RESPONSE_OFFSET, - handle->request.mode); - } + shims->log("Response was for a mode 0x%x request, not our mode 0x%x request", + response.mode - MODE_RESPONSE_OFFSET, + handle->request.mode); } } - response.completed = true; - // TODO clarify what it means for a handle to be successful (we made - // a good request+response) vs a request itself being successfuly - // (the other node didn't return a negative response). - handle->success = true; - handle->completed = true; - - if(handle->callback != NULL) { + if(handle->completed && handle->callback != NULL) { handle->callback(&response); } } } else { - shims->log("Handle is already completed"); + shims->log("Mode %d request to arb ID 0x%x is already completed", + handle->request.mode, handle->request.arbitration_id); } return response; } - -// TODO everything below here is for future work...not critical for now. - -DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( - DiagnosticShims* shims, - DiagnosticMilStatusReceived callback) { - // TODO request malfunction indicator light (MIL) status - request mode 1 - // pid 1, parse first bit -} - -DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, - DiagnosticVinReceived callback) { -} - -DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, - DiagnosticTroubleCodeType dtc_type, - DiagnosticTroubleCodesReceived callback) { -} - -bool diagnostic_clear_dtc(DiagnosticShims* shims) { -} - -DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { - // before calling the callback, split up the received bytes into 1 or 2 byte - // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs - // TODO request supported PIDs - request PID 0 and parse 4 bytes in response -} diff --git a/tests/test_core.c b/tests/test_core.c index 6e1960a..16863a9 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -14,7 +14,6 @@ extern uint8_t last_can_payload_size; void response_received_handler(const DiagnosticResponse* response) { last_response_was_received = true; - // TODO not sure if we can copy the struct like this last_response_received = *response; } @@ -141,8 +140,8 @@ START_TEST (test_wrong_mode_response) const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); - fail_unless(last_response_was_received); - fail_if(last_response_received.success); + fail_if(last_response_was_received); + fail_if(handle.completed); } END_TEST -- cgit 1.2.3-korg From 9a0b21681c9f2ede882d2ff294190ed2c0603df5 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 16:38:04 -0500 Subject: Greatly expand API example in README. --- README.mkd | 196 ++++++++++++++++++++++++++++++++++++++++-------------- deps/isotp-c | 2 +- src/obd2/obd2.h | 2 +- tests/test_core.c | 2 +- 4 files changed, 150 insertions(+), 52 deletions(-) diff --git a/README.mkd b/README.mkd index bd074e9..5e962f0 100644 --- a/README.mkd +++ b/README.mkd @@ -1,42 +1,17 @@ OBD-II Support Library in C ============================= -TODO diagram out a request, response and error response - -TODO isotp needs to accept responses on an ID other that the request's arb id - -or maybe we have 2 isotp instances, for rx and tx. - -* Response's arb id is assigned ID plus 0x8 -* Functional daignostic request (Broadcast), 0x7df arb id -* Standard ECU requests to 0x7e0 - 0x7e7 - -* Some requests don't need PIDs - e.g. mode 3 - * some responses don't have PIDs, just payload - e.g. mode 3 - -* Service ID + 0x40 == positive response -* Response includes PID echo - use this to match up request/response -* Response ID, ISO-TP PCI, ISO-TP length, service response ID, PID echo, data - -I send the request and give a callback -when the response arrives for the matchin service and PID - on any arb id, since -this may be a broadcast - call my callback. - - -TODO do you want to provide the callback to the Handler, or to each -individual send?o -ok, what are the use cases here. - -you're going to request a few PIDs over and over again at some frequency -you're going to request DTCs once and read the response -you're going to clear DTCs once +This is a platform agnostic C library that implements the standard On Board +Diagnostics system for vehicles. It currently supports OBD-II running over CAN +(ISO 15765-4), which uses the ISO-TP (ISO 15765-2) protocol underneath. -i'd rather use the same diagnostic handler to send all of the differet messages -rather than create one for each use. that way ai can +This library doesn't assume anything about the source of your diagnostic message +requests or underlying interface to the CAN bus. It uses dependency injection to +give you complete control. -but that does complicate the memory management because it'll need to create -some internal data structures. +## OBD-II Basics -at the simplest, what does this handler have to do? +TODO diagram out a request, response and error response * store the request arb id, mode, pid, and payload locally * send a can message @@ -45,31 +20,141 @@ at the simplest, what does this handler have to do? response IDs, or our arb ID + 0x8 * if it matches, parse the diagnostic response and call the callback -that seems pretty simple and not worth greatly increasing the internal state to -handle multiple requests +## Usage -what we need is another layer on top of that to handle the repeated requests. +First, create some shim functions to let this library use your lower level +system: -## API + // required, this must send a single CAN message with the given arbitration + // ID (i.e. the CAN message ID) and data. The size will never be more than 8 + // bytes. + void send_can(const uint16_t arbitration_id, const uint8_t* data, + const uint8_t size) { + ... + } - void my_callback(const DiagnosticResponse* response) { + // optional, provide to receive debugging log messages + void debug(const char* format, ...) { + ... } - DiagnosticRequestHandle handle = diagnostic_init(my_callback); - DiagnosticRequest request = { - arbitratin_id: 0x7df, - mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, - pid: 3 - }; - diagnostic_send(&handle, &request); - while(true) { - diagnostic_handle_can_frame(&handle, 42, data, 8); + // not used in the current version + void set_timer(uint16_t time_ms, void (*callback)) { + ... } - diagnostic_request_pid(&handle, DIAGNOSTIC_STANDARD_PID, 42 +With your shims in place, create a `DiagnosticShims` object to pass them around: - diagnostic_destroy(&handle); + DiagnosticShims shims = diagnostic_init_shims(debug, send_can, set_timer); + +With your shims in hand, send a simple PID request to the stadnard broadcast +address, `0x7df`: + + // Optional: This is your callback that will be called the response to your + // diagnostic request is received. + void response_received_handler(const DiagnosticResponse* response) { + // You received a response! Do something with it. + } + + DiagnosticRequestHandle handle = diagnostic_request_pid(&shims, + DIAGNOSTIC_STANDARD_PID, // this is a standard PID request, not an extended or enhanced one + 0x7df, // the request is going out to the broadcast arbitration ID + 0x2, // we want PID 0x2 + response_received_handler); // our callback (optional, use NULL if you don't have one) + + if(handle.completed) { + if(!handle.success) { + // something happened and it already failed - possibly we aren't + // able to send CAN messages + return; + } else { + // this should never occur right away - you need to receive a fresh + // CAN message first + } + } else { + while(true) { + // Continue to read from CAN, passing off each message to the handle. + // This will return a 'completed' DiagnosticResponse when the when + // the request is completely sent and the response is received + // (which may take more than 1 CAN frames) + DiagnosticResponse response = diagnostic_receive_can_frame(&shims, + &handle, can_message_id, can_data, sizeof(can_data)); + + if(response.completed && handle.completed) { + if(handle.success) { + if(response.success) { + // The request was sent successfully, the response was + // received successfully, and it was a positive response - we + // got back some data! + } else { + // The request was sent successfully, the response was + // received successfully, BUT it was a negative response + // from the other node. + printf("This is the error code: %d", response.negative_response_code); + } + } else { + // Some other fatal error ocurred - we weren't able to send + // the request or receive the response. The CAN connection + // may be down. + } + } + } + } + +## Requests for other modes + +If you want to do more besides PID requests on mode 0x1 and 0x22, there's a +lower level API you can use. Here's how to make a mode 3 request to get DTCs. + + DiagnosticRequest request = { + arbitration_id: 0x7df, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, NULL); + + if(handle.completed) { + if(!handle.success) { + // something happened and it already failed - possibly we aren't + // able to send CAN messages + return; + } else { + // this should never occur right away - you need to receive a fresh + // CAN message first + } + } else { + while(true) { + // Continue to read from CAN, passing off each message to the handle. + // This will return a 'completed' DiagnosticResponse when the when + // the request is completely sent and the response is received + // (which may take more than 1 CAN frames) + DiagnosticResponse response = diagnostic_receive_can_frame(&shims, + &handle, can_message_id, can_data, sizeof(can_data)); + + if(response.completed && handle.completed) { + if(handle.success) { + if(response.success) { + // The request was sent successfully, the response was + // received successfully, and it was a positive response - we + // got back some data! + printf("The DTCs are: "); + for(int i = 0; i < response.payload_length; i++) { + printf("0x%x ", response.payload[i]); + } + } else { + // The request was sent successfully, the response was + // received successfully, BUT it was a negative response + // from the other node. + printf("This is the error code: %d", response.negative_response_code); + } + } else { + // Some other fatal error ocurred - we weren't able to send + // the request or receive the response. The CAN connection + // may be down. + } + } + } + } ## Testing @@ -77,6 +162,19 @@ The library includes a test suite that uses the `check` C unit test library. $ make test +You can also see the test coverage if you have `lcov` installed and the +`BROWSER` environment variable set to your choice of web browsers: + + $ BROWSER=google-chrome-stable make coverage + +## Future Notes + +you're going to request a few PIDs over and over again at some frequency +you're going to request DTCs once and read the response +you're going to clear DTCs once + +we need another layer on top of that to handle the repeated requests. + ## Authors Chris Peplin cpeplin@ford.com diff --git a/deps/isotp-c b/deps/isotp-c index 101d36e..8922abb 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 101d36e65bc7cb2b1fc8e4dbae4eacf749c98b00 +Subproject commit 8922abb7ff8c30e1fa5af078284eb6aebf0052e5 diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index a2ea033..8cdd4a1 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -73,8 +73,8 @@ typedef enum { typedef struct { uint16_t arbitration_id; uint8_t mode; - bool success; bool completed; + bool success; uint16_t pid; DiagnosticNegativeResponseCode negative_response_code; uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; diff --git a/tests/test_core.c b/tests/test_core.c index 16863a9..cf8673a 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -60,7 +60,7 @@ START_TEST (test_send_diag_request) { DiagnosticRequest request = { arbitration_id: 0x7df, - mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); -- cgit 1.2.3-korg From 76d13f3efb227f2d05e52dd1b1b322380058b2c2 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 16:53:25 -0500 Subject: Split up header file for clarity. --- src/obd2/extras.h | 30 ++++++++ src/obd2/obd2.h | 210 ++++++++++++++++---------------------------------- src/obd2/obd2_types.h | 141 +++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 143 deletions(-) create mode 100644 src/obd2/extras.h create mode 100644 src/obd2/obd2_types.h diff --git a/src/obd2/extras.h b/src/obd2/extras.h new file mode 100644 index 0000000..e411307 --- /dev/null +++ b/src/obd2/extras.h @@ -0,0 +1,30 @@ +#ifndef __EXTRAS_H__ +#define __EXTRAS_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback); + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback); + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback); + +bool diagnostic_clear_dtc(DiagnosticShims* shims); + +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); + +#ifdef __cplusplus +} +#endif + +#endif // __EXTRAS_H__ diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 8cdd4a1..7dfdb59 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -1,7 +1,7 @@ #ifndef __OBD2_H__ #define __OBD2_H__ -#include +#include #include #include @@ -9,164 +9,88 @@ extern "C" { #endif -// TODO This isn't true for multi frame messages - we may need to dynamically -// allocate this in the future -#define MAX_OBD2_PAYLOAD_LENGTH 7 -#define VIN_LENGTH 17 - -typedef enum { - DIAGNOSTIC_REQUEST_TYPE_PID, - DIAGNOSTIC_REQUEST_TYPE_DTC, - DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, - DIAGNOSTIC_REQUEST_TYPE_VIN -} DiagnosticRequestType; - -typedef struct { - DiagnosticRequestType type; - uint16_t arbitration_id; - uint8_t mode; - uint16_t pid; - uint8_t pid_length; - uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; - uint8_t payload_length; -} DiagnosticRequest; - -// Thanks to -// http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 -// for the list of NRCs -typedef enum { - NRC_SUCCESS = 0x0, - NRC_SERVICE_NOT_SUPPORTED = 0x11, - NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, - NRC_CONDITIONS_NOT_CORRECT = 0x22, - NRC_REQUEST_OUT_OF_RANGE = 0x31, - NRC_SECURITY_ACCESS_DENIED = 0x33, - NRC_INVALID_KEY = 0x35, - NRC_TOO_MANY_ATTEMPS = 0x36, - NRC_TIME_DELAY_NOT_EXPIRED = 0x37, - NRC_RESPONSE_PENDING = 0x78 -} DiagnosticNegativeResponseCode; - -typedef enum { - OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, - OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, - OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3, - OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4, - // 0x5 is for non-CAN only - // OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5, - OBD2_MODE_TEST_RESULTS = 0x6, - OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7, - OBD2_MODE_CONTROL = 0x8, - OBD2_MODE_VEHICLE_INFORMATION = 0x9, - OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa, - // this one isn't technically in OBD2, but both of the enhanced standards - // have their PID requests at 0x22 - OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 -} DiagnosticMode; - -typedef enum { - DTC_EMISSIONS, - DTC_DRIVE_CYCLE, - DTC_PERMANENT -} DiagnosticTroubleCodeType; - -typedef struct { - uint16_t arbitration_id; - uint8_t mode; - bool completed; - bool success; - uint16_t pid; - DiagnosticNegativeResponseCode negative_response_code; - uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; - uint8_t payload_length; -} DiagnosticResponse; - -typedef enum { - POWERTRAIN = 0x0, - CHASSIS = 0x1, - BODY = 0x2, - NETWORK = 0x3 -} DiagnosticTroubleCodeGroup; - -typedef struct { - DiagnosticTroubleCodeGroup group; - uint8_t group_num; - uint8_t code; -} DiagnosticTroubleCode; - -typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); -typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); -typedef void (*DiagnosticVinReceived)(uint8_t vin[]); -typedef void (*DiagnosticTroubleCodesReceived)( - DiagnosticMode mode, DiagnosticTroubleCode* codes); -typedef void (*DiagnosticPidEnumerationReceived)( - const DiagnosticResponse* response, uint16_t* pids); - -// TODO should we enumerate every OBD-II PID? need conversion formulas, too -typedef struct { - uint16_t pid; - uint8_t bytes_returned; - float min_value; - float max_value; -} DiagnosticParameter; - -typedef struct { - DiagnosticRequest request; - bool success; - bool completed; - - IsoTpShims isotp_shims; - IsoTpSendHandle isotp_send_handle; - IsoTpReceiveHandle isotp_receive_handle; - DiagnosticResponseReceived callback; - DiagnosticMilStatusReceived mil_status_callback; - DiagnosticVinReceived vin_callback; -} DiagnosticRequestHandle; - -typedef enum { - DIAGNOSTIC_STANDARD_PID, - DIAGNOSTIC_ENHANCED_PID -} DiagnosticPidRequestType; - -typedef struct { - LogShim log; - SendCanMessageShim send_can_message; - SetTimerShim set_timer; -} DiagnosticShims; - +/* Public: Initialize an DiagnosticShims with the given callback functions. + * + * If any callbacks are not to be used, set them to NULL. For documentation of + * the function type signatures, see higher up in this header file. This struct + * is a handy encapsulation used to pass the shims around to the various + * diagnostic_* functions. + * + * Returns a struct with the fields initailized to the callbacks. + */ DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer); +/* Public: Initiate a diagnostic request and return a handle, ready to completly + * send the request and process the response via + * diagnostic_receive_can_frame(...). + * + * shims - Low-level shims required to send CAN messages, etc. + * request - + * callback - an optional function to be called when the response is receved + * (use NULL if no callback is required). + * + * Returns a handle to be used with diagnostic_receive_can_frame to complete + * sending the request and receive the response. The 'completed' field in the + * returned DiagnosticRequestHandle will be true when the message is completely + * sent. + */ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback); -// decide mode 0x1 / 0x22 based on pid type +/* Public: Request a PID from the given arbitration ID, determining the mode + * automatically based on the PID type. + * + * shims - Low-level shims required to send CAN messages, etc. + * pid_request_type - either DIAGNOSTIC_STANDARD_PID (will use mode 0x1 and 1 + * byte PIDs) or DIAGNOSTIC_ENHANCED_PID (will use mode 0x22 and 2 byte + * PIDs) + * arbitration_id - The arbitration ID to send the request to. + * pid - The PID to request from the other node. + * callback - an optional function to be called when the response is receved + * (use NULL if no callback is required). + * + * Returns a handle to be used with diagnostic_receive_can_frame to complete + * sending the request and receive the response. The 'completed' field in the + * returned DiagnosticRequestHandle will be true when the message is completely + * sent. + */ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, uint16_t pid, DiagnosticResponseReceived callback); -DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( - DiagnosticShims* shims, - DiagnosticMilStatusReceived callback); - -DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, - DiagnosticVinReceived callback); - -DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, - DiagnosticTroubleCodeType dtc_type, - DiagnosticTroubleCodesReceived callback); - -bool diagnostic_clear_dtc(DiagnosticShims* shims); - -DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); - +/* Public: Continue to send and receive a single diagnostic request, based on a + * freshly received CAN message. + * + * shims - Low-level shims required to send CAN messages, etc. + * handle - A DiagnosticRequestHandle previously returned by one of the + * diagnostic_request*(..) functions. + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns true if the request was completed and response received, or the + * request was otherwise cancelled. Check the 'success' field of the handle to + * see if it was successful. + */ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, DiagnosticRequestHandle* handle, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); +/* Public: Render a DiagnosticResponse as a string into the given buffer. + * + * TODO implement this + * + * message - the response to convert to a string, for debug logging. + * destination - the target string buffer. + * destination_length - the size of the destination buffer, i.e. the max size + * for the rendered string. + */ +// void diagnostic_response_to_string(const DiagnosticResponse* response, + // char* destination, size_t destination_length); + #ifdef __cplusplus } #endif diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h new file mode 100644 index 0000000..247f80c --- /dev/null +++ b/src/obd2/obd2_types.h @@ -0,0 +1,141 @@ +#ifndef __OBD2_TYPES_H__ +#define __OBD2_TYPES_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO This isn't true for multi frame messages - we may need to dynamically +// allocate this in the future +#define MAX_OBD2_PAYLOAD_LENGTH 7 +#define VIN_LENGTH 17 + +typedef enum { + DIAGNOSTIC_REQUEST_TYPE_PID, + DIAGNOSTIC_REQUEST_TYPE_DTC, + DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, + DIAGNOSTIC_REQUEST_TYPE_VIN +} DiagnosticRequestType; + +typedef struct { + DiagnosticRequestType type; + uint16_t arbitration_id; + uint8_t mode; + uint16_t pid; + uint8_t pid_length; + uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticRequest; + +// Thanks to +// http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 +// for the list of NRCs +typedef enum { + NRC_SUCCESS = 0x0, + NRC_SERVICE_NOT_SUPPORTED = 0x11, + NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, + NRC_CONDITIONS_NOT_CORRECT = 0x22, + NRC_REQUEST_OUT_OF_RANGE = 0x31, + NRC_SECURITY_ACCESS_DENIED = 0x33, + NRC_INVALID_KEY = 0x35, + NRC_TOO_MANY_ATTEMPS = 0x36, + NRC_TIME_DELAY_NOT_EXPIRED = 0x37, + NRC_RESPONSE_PENDING = 0x78 +} DiagnosticNegativeResponseCode; + +typedef enum { + OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, + OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, + OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3, + OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4, + // 0x5 is for non-CAN only + // OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5, + OBD2_MODE_TEST_RESULTS = 0x6, + OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7, + OBD2_MODE_CONTROL = 0x8, + OBD2_MODE_VEHICLE_INFORMATION = 0x9, + OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa, + // this one isn't technically in OBD2, but both of the enhanced standards + // have their PID requests at 0x22 + OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 +} DiagnosticMode; + +typedef enum { + DTC_EMISSIONS, + DTC_DRIVE_CYCLE, + DTC_PERMANENT +} DiagnosticTroubleCodeType; + +typedef struct { + uint16_t arbitration_id; + uint8_t mode; + bool completed; + bool success; + uint16_t pid; + DiagnosticNegativeResponseCode negative_response_code; + uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticResponse; + +typedef enum { + POWERTRAIN = 0x0, + CHASSIS = 0x1, + BODY = 0x2, + NETWORK = 0x3 +} DiagnosticTroubleCodeGroup; + +typedef struct { + DiagnosticTroubleCodeGroup group; + uint8_t group_num; + uint8_t code; +} DiagnosticTroubleCode; + +typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); +typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); +typedef void (*DiagnosticVinReceived)(uint8_t vin[]); +typedef void (*DiagnosticTroubleCodesReceived)( + DiagnosticMode mode, DiagnosticTroubleCode* codes); +typedef void (*DiagnosticPidEnumerationReceived)( + const DiagnosticResponse* response, uint16_t* pids); + +// TODO should we enumerate every OBD-II PID? need conversion formulas, too +typedef struct { + uint16_t pid; + uint8_t bytes_returned; + float min_value; + float max_value; +} DiagnosticParameter; + +typedef struct { + DiagnosticRequest request; + bool success; + bool completed; + + IsoTpShims isotp_shims; + IsoTpSendHandle isotp_send_handle; + IsoTpReceiveHandle isotp_receive_handle; + DiagnosticResponseReceived callback; + // DiagnosticMilStatusReceived mil_status_callback; + // DiagnosticVinReceived vin_callback; +} DiagnosticRequestHandle; + +typedef enum { + DIAGNOSTIC_STANDARD_PID, + DIAGNOSTIC_ENHANCED_PID +} DiagnosticPidRequestType; + +typedef struct { + LogShim log; + SendCanMessageShim send_can_message; + SetTimerShim set_timer; +} DiagnosticShims; + +#ifdef __cplusplus +} +#endif + +#endif // __OBD2_TYPES_H__ -- cgit 1.2.3-korg From 30b967cdd8926eb44efa720b8f59a842d836859d Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 16:55:01 -0500 Subject: Remove a resolved TODO. --- src/obd2/obd2.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 0590c1b..8a80da6 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -158,11 +158,6 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(handle_negative_response(&message, &response, shims)) { shims->log("Received a negative response to mode %d on arb ID 0x%x", response.mode, response.arbitration_id); - - // TODO clarify what it means for a handle to be successful (we made - // a good request+response) vs a request itself being - // successfully - // (the other node didn't return a negative response). handle->success = true; handle->completed = true; } else if(handle_positive_response(handle, &message, &response, -- cgit 1.2.3-korg From 122d57e1c70b309542c1b5beb9e62d61726ccc02 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 21:33:45 -0500 Subject: Document all remaining methods and data structures. --- src/obd2/extras.c | 2 +- src/obd2/extras.h | 42 +++++++++++++++- src/obd2/obd2_types.h | 135 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 129 insertions(+), 50 deletions(-) diff --git a/src/obd2/extras.c b/src/obd2/extras.c index ec1d996..905ef89 100644 --- a/src/obd2/extras.c +++ b/src/obd2/extras.c @@ -1,4 +1,4 @@ -#include +#include // TODO everything below here is for future work...not critical for now. diff --git a/src/obd2/extras.h b/src/obd2/extras.h index e411307..50ad3b7 100644 --- a/src/obd2/extras.h +++ b/src/obd2/extras.h @@ -1,12 +1,52 @@ #ifndef __EXTRAS_H__ #define __EXTRAS_H__ -#include +#include #ifdef __cplusplus extern "C" { #endif +// TODO everything in here is unused for the moment! + +typedef enum { + POWERTRAIN = 0x0, + CHASSIS = 0x1, + BODY = 0x2, + NETWORK = 0x3 +} DiagnosticTroubleCodeGroup; + +typedef struct { + DiagnosticTroubleCodeGroup group; + uint8_t group_num; + uint8_t code; +} DiagnosticTroubleCode; + + +/* Private: TODO unused for now + */ +typedef enum { + DTC_EMISSIONS, + DTC_DRIVE_CYCLE, + DTC_PERMANENT +} DiagnosticTroubleCodeType; + + +// TODO should we enumerate every OBD-II PID? need conversion formulas, too +typedef struct { + uint16_t pid; + uint8_t bytes_returned; + float min_value; + float max_value; +} DiagnosticParameter; + +typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); +typedef void (*DiagnosticVinReceived)(uint8_t vin[]); +typedef void (*DiagnosticTroubleCodesReceived)( + DiagnosticMode mode, DiagnosticTroubleCode* codes); +typedef void (*DiagnosticPidEnumerationReceived)( + const DiagnosticResponse* response, uint16_t* pids); + DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( DiagnosticShims* shims, DiagnosticMilStatusReceived callback); diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h index 247f80c..d1c5a3a 100644 --- a/src/obd2/obd2_types.h +++ b/src/obd2/obd2_types.h @@ -14,6 +14,11 @@ extern "C" { #define MAX_OBD2_PAYLOAD_LENGTH 7 #define VIN_LENGTH 17 +/* Private: The four main types of diagnositc requests that determine how the + * request should be parsed and what type of callback should be used. + * + * TODO this may not be used...yet? + */ typedef enum { DIAGNOSTIC_REQUEST_TYPE_PID, DIAGNOSTIC_REQUEST_TYPE_DTC, @@ -21,19 +26,39 @@ typedef enum { DIAGNOSTIC_REQUEST_TYPE_VIN } DiagnosticRequestType; +/* Public: A container for a single diagnostic request. + * + * The only required fields are the arbitration_id and mode. + * + * arbitration_id - The arbitration ID to send the request. + * mode - The OBD-II mode for the request. + * pid - (optional) The PID to request, if the mode requires one. + * pid_length - The length of the PID field, either 1 (standard) or 2 bytes + * (extended). + * payload - (optional) The payload for the request, if the request requires + * one. If payload_length is 0 this field is ignored. + * payload_length - The length of the payload, or 0 if no payload is used. + * type - the type of the request (TODO unused) + */ typedef struct { - DiagnosticRequestType type; uint16_t arbitration_id; uint8_t mode; uint16_t pid; uint8_t pid_length; uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; uint8_t payload_length; + DiagnosticRequestType type; } DiagnosticRequest; -// Thanks to -// http://www.canbushack.com/blog/index.php?title=scanning-for-diagnostic-data&more=1&c=1&tb=1&pb=1 -// for the list of NRCs +/* Public: All possible negative response codes that could be received from a + * requested node. + * + * When a DiagnosticResponse is received and the 'completed' field is true, but + * the 'success' field is false, the 'negative_response_code' field will contain + * one of these values as reported by the requested node. + * + * Thanks to canbushack.com for the list of NRCs. + */ typedef enum { NRC_SUCCESS = 0x0, NRC_SERVICE_NOT_SUPPORTED = 0x11, @@ -47,6 +72,36 @@ typedef enum { NRC_RESPONSE_PENDING = 0x78 } DiagnosticNegativeResponseCode; +/* Public: A partially or fully completed response to a diagnostic request. + * + * completed - True if the request is complete - some functions return a + * DiagnosticResponse even when it's only partially completed, so be sure + * to check this field. + * success - True if the request was successful. The value if this + * field isn't valid if 'completed' isn't true. If this is 'false', check + * the negative_response_code field for the reason. + * arbitration_id - The arbitration ID the response was received on. + * mode - The OBD-II mode for the original request. + * pid - If the request was for a PID, this is the PID echo. + * negative_response_code - If the request was not successful, 'success' will be + * false and this will be set to a DiagnosticNegativeResponseCode returned + * by the other node. + * payload - An optional payload for the response - NULL if no payload. + * payload_length - The length of the payload or 0 if none. + */ +typedef struct { + bool completed; + bool success; + uint16_t arbitration_id; + uint8_t mode; + uint16_t pid; + DiagnosticNegativeResponseCode negative_response_code; + uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticResponse; + +/* Public: Friendly names for all OBD-II modes. + */ typedef enum { OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, @@ -64,57 +119,32 @@ typedef enum { OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 } DiagnosticMode; -typedef enum { - DTC_EMISSIONS, - DTC_DRIVE_CYCLE, - DTC_PERMANENT -} DiagnosticTroubleCodeType; - -typedef struct { - uint16_t arbitration_id; - uint8_t mode; - bool completed; - bool success; - uint16_t pid; - DiagnosticNegativeResponseCode negative_response_code; - uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; - uint8_t payload_length; -} DiagnosticResponse; - -typedef enum { - POWERTRAIN = 0x0, - CHASSIS = 0x1, - BODY = 0x2, - NETWORK = 0x3 -} DiagnosticTroubleCodeGroup; - -typedef struct { - DiagnosticTroubleCodeGroup group; - uint8_t group_num; - uint8_t code; -} DiagnosticTroubleCode; - +/* Public the signature for an optional function to be called when a diagnostic + * request is complete, and a response is received or there is a fatal error. + * + * response - the completed DiagnosticResponse. + */ typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); -typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); -typedef void (*DiagnosticVinReceived)(uint8_t vin[]); -typedef void (*DiagnosticTroubleCodesReceived)( - DiagnosticMode mode, DiagnosticTroubleCode* codes); -typedef void (*DiagnosticPidEnumerationReceived)( - const DiagnosticResponse* response, uint16_t* pids); - -// TODO should we enumerate every OBD-II PID? need conversion formulas, too -typedef struct { - uint16_t pid; - uint8_t bytes_returned; - float min_value; - float max_value; -} DiagnosticParameter; +/* Public: A handle for initiating and continuing a single diagnostic request. + * + * A diagnostic request requires one or more CAN messages to be sent, and one + * or more CAN messages to be received before it is completed. This struct + * encapsulates the local state required to track the request while it is in + * progress. + * + * request - The original DiagnosticRequest that this handle was created for. + * completed - True if the request was completed successfully, or was otherwise + * cancelled. + * success - True if the request send and receive process was successful. The + * value if this field isn't valid if 'completed' isn't true. + */ typedef struct { DiagnosticRequest request; bool success; bool completed; + // Private IsoTpShims isotp_shims; IsoTpSendHandle isotp_send_handle; IsoTpReceiveHandle isotp_receive_handle; @@ -123,11 +153,20 @@ typedef struct { // DiagnosticVinReceived vin_callback; } DiagnosticRequestHandle; +/* Public: The two major types of PIDs that determine the OBD-II mode and PID + * field length. + */ typedef enum { DIAGNOSTIC_STANDARD_PID, DIAGNOSTIC_ENHANCED_PID } DiagnosticPidRequestType; +/* Public: A container for the 3 shim functions used by the library to interact + * with the wider system. + * + * Use the diagnostic_init_shims(...) function to create an instance of this + * struct. + */ typedef struct { LogShim log; SendCanMessageShim send_can_message; -- cgit 1.2.3-korg From c670ab64a078ed882fbf3647334f370bae1e8cf3 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 22:31:32 -0500 Subject: Move notes lower down in README. --- README.mkd | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.mkd b/README.mkd index 5e962f0..dd63ab8 100644 --- a/README.mkd +++ b/README.mkd @@ -9,17 +9,6 @@ This library doesn't assume anything about the source of your diagnostic message requests or underlying interface to the CAN bus. It uses dependency injection to give you complete control. -## OBD-II Basics - -TODO diagram out a request, response and error response - -* store the request arb id, mode, pid, and payload locally -* send a can message -* get all new can messages passed to it -* Check the incoming can message to see if it matches one of the standard ECU - response IDs, or our arb ID + 0x8 -* if it matches, parse the diagnostic response and call the callback - ## Usage First, create some shim functions to let this library use your lower level @@ -167,6 +156,18 @@ You can also see the test coverage if you have `lcov` installed and the $ BROWSER=google-chrome-stable make coverage +## OBD-II Basics + +TODO diagram out a request, response and error response + +* store the request arb id, mode, pid, and payload locally +* send a can message +* get all new can messages passed to it +* Check the incoming can message to see if it matches one of the standard ECU + response IDs, or our arb ID + 0x8 +* if it matches, parse the diagnostic response and call the callback + + ## Future Notes you're going to request a few PIDs over and over again at some frequency -- cgit 1.2.3-korg From af99c224668bdd1e952119cffc274a74b98b5d59 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 3 Jan 2014 22:34:41 -0500 Subject: Add dependencies to README. --- README.mkd | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.mkd b/README.mkd index dd63ab8..041eff4 100644 --- a/README.mkd +++ b/README.mkd @@ -91,7 +91,7 @@ address, `0x7df`: } } -## Requests for other modes +### Requests for other modes If you want to do more besides PID requests on mode 0x1 and 0x22, there's a lower level API you can use. Here's how to make a mode 3 request to get DTCs. @@ -145,6 +145,13 @@ lower level API you can use. Here's how to make a mode 3 request to get DTCs. } } +## Dependencies + +This library requires 2 dependencies: + +* [isotp-c](https://github.com/openxc/isotp-c) +* [bitfield-c](https://github.com/openxc/bitfield-c) + ## Testing The library includes a test suite that uses the `check` C unit test library. -- cgit 1.2.3-korg From 1a3951aa48132f696e76bcad97db8c7133cb35d4 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sun, 5 Jan 2014 15:44:16 -0500 Subject: Add imports and return values to allow compiling with projects. --- src/obd2/extras.c | 10 ++++++++++ src/obd2/obd2.c | 13 ++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/obd2/extras.c b/src/obd2/extras.c index 905ef89..37a55fe 100644 --- a/src/obd2/extras.c +++ b/src/obd2/extras.c @@ -1,4 +1,5 @@ #include +#include // TODO everything below here is for future work...not critical for now. @@ -7,18 +8,25 @@ DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( DiagnosticMilStatusReceived callback) { // TODO request malfunction indicator light (MIL) status - request mode 1 // pid 1, parse first bit + DiagnosticRequestHandle handle; + return handle; } DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, DiagnosticVinReceived callback) { + DiagnosticRequestHandle handle; + return handle; } DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, DiagnosticTroubleCodeType dtc_type, DiagnosticTroubleCodesReceived callback) { + DiagnosticRequestHandle handle; + return handle; } bool diagnostic_clear_dtc(DiagnosticShims* shims) { + return false; } DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, @@ -26,4 +34,6 @@ DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, // before calling the callback, split up the received bytes into 1 or 2 byte // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs // TODO request supported PIDs - request PID 0 and parse 4 bytes in response + DiagnosticRequestHandle handle; + return handle; } diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 8a80da6..83aa1fd 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -1,5 +1,9 @@ #include -#include +#include +#include +#include +#include +#include #define ARBITRATION_ID_OFFSET 0x8 #define MODE_RESPONSE_OFFSET 0x40 @@ -33,7 +37,8 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { - copy_bytes_right_aligned(&request->pid, sizeof(request->pid), + // TODO may need to flip the byte order + copy_bytes_right_aligned((uint8_t*)&request->pid, sizeof(request->pid), PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload)); } if(request->payload_length > 0) { @@ -117,7 +122,9 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, if(handle->request.pid_length > 0 && message->size > 1) { if(handle->request.pid_length == 2) { response->pid = *(uint16_t*)&message->payload[PID_BYTE_INDEX]; - response->pid = ntohs(response->pid); + if(BYTE_ORDER == LITTLE_ENDIAN) { + response->pid = __builtin_bswap32(response->pid << 16); + } } else { response->pid = message->payload[PID_BYTE_INDEX]; } -- cgit 1.2.3-korg From 1fef0b137e38df2f23dc5e98262422ac33e354ee Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sun, 5 Jan 2014 18:56:20 -0500 Subject: Update API in README to match actual code. --- README.mkd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.mkd b/README.mkd index 041eff4..58cc152 100644 --- a/README.mkd +++ b/README.mkd @@ -17,7 +17,7 @@ system: // required, this must send a single CAN message with the given arbitration // ID (i.e. the CAN message ID) and data. The size will never be more than 8 // bytes. - void send_can(const uint16_t arbitration_id, const uint8_t* data, + bool send_can(const uint16_t arbitration_id, const uint8_t* data, const uint8_t size) { ... } -- cgit 1.2.3-korg From 8ca422e95dce9349afbdc399215c9c4d28c89f1a Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sun, 5 Jan 2014 18:56:31 -0500 Subject: Log when sending a new diagnositc request. --- src/obd2/obd2.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 83aa1fd..f8b4a37 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -49,10 +49,26 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, handle.isotp_shims = isotp_init_shims(shims->log, shims->send_can_message, shims->set_timer); + handle.isotp_send_handle = isotp_send(&handle.isotp_shims, request->arbitration_id, payload, 1 + request->payload_length + request->pid_length, NULL); + if(shims->log != NULL) { + 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", + request->arbitration_id, + request->mode, + request->pid, + request->payload[0], + request->payload[1], + request->payload[2], + request->payload[3], + request->payload[4], + request->payload[5], + request->payload[6], + request->payload[7], + request->payload_length); + } handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims, request->arbitration_id + ARBITRATION_ID_OFFSET, -- cgit 1.2.3-korg From 2ea50110062053ef12b1c66956ec25faf7f1cb3c Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sun, 5 Jan 2014 18:56:37 -0500 Subject: Add a TODO about flipping byte order. --- src/obd2/obd2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index f8b4a37..402cc2f 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -37,7 +37,8 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { - // TODO may need to flip the byte order + // TODO need a set bit field that's more natural and checks endianness + // becauase we DO need to flip it copy_bytes_right_aligned((uint8_t*)&request->pid, sizeof(request->pid), PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload)); } -- cgit 1.2.3-korg From c55d6bf59167c75439c1c40e9dcd8c05081d4685 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 6 Jan 2014 14:54:15 -0500 Subject: Use new set_bitfield function to handle swapping endianness. --- deps/bitfield-c | 2 +- src/obd2/obd2.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/deps/bitfield-c b/deps/bitfield-c index 09dc97c..6f24309 160000 --- a/deps/bitfield-c +++ b/deps/bitfield-c @@ -1 +1 @@ -Subproject commit 09dc97c24f9f9a9c2638405167bffe339f666e2a +Subproject commit 6f243098abadbde2e9b2e5b13a94029960ea2e51 diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 402cc2f..fa20f91 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -37,10 +37,8 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { - // TODO need a set bit field that's more natural and checks endianness - // becauase we DO need to flip it - copy_bytes_right_aligned((uint8_t*)&request->pid, sizeof(request->pid), - PID_BYTE_INDEX, request->pid_length, payload, sizeof(payload)); + set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, + request->pid_length * CHAR_BIT, payload, sizeof(payload)); } if(request->payload_length > 0) { memcpy(&payload[PID_BYTE_INDEX + request->pid_length], -- cgit 1.2.3-korg From 50c63116eba8ea3703b44d82f19eb408ad9d7007 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 6 Jan 2014 15:40:57 -0500 Subject: Initialize outgoing payload to 0. --- src/obd2/obd2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index fa20f91..cfede4e 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -34,7 +34,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, completed: false }; - uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE]; + uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; payload[MODE_BYTE_INDEX] = request->mode; if(request->pid_length > 0) { set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, -- cgit 1.2.3-korg From 00fa0aa3e59d49d72c961f893e524fc20563aa67 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 6 Jan 2014 18:21:49 -0500 Subject: Make sure not to use a negative payload length. --- src/obd2/obd2.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index cfede4e..3dad886 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -14,6 +14,10 @@ #define NEGATIVE_RESPONSE_MODE_INDEX 1 #define NEGATIVE_RESPONSE_NRC_INDEX 2 +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer) { @@ -146,7 +150,7 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, } uint8_t payload_index = 1 + handle->request.pid_length; - response->payload_length = message->size - payload_index; + response->payload_length = MAX(0, message->size - payload_index); if(response->payload_length > 0) { memcpy(response->payload, &message->payload[payload_index], response->payload_length); -- cgit 1.2.3-korg From b2705b3ec209311506af2034e021285daf3c9649 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 6 Jan 2014 18:22:06 -0500 Subject: Mark request handle and response completed even if an error ocurred. Add extra logging and a few notes. --- src/obd2/obd2.c | 14 +++++++++++++- tests/test_core.c | 7 +++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 3dad886..c780fd8 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -147,6 +147,8 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, } else { response->pid = message->payload[PID_BYTE_INDEX]; } + // TODO we're not currently throwing an error or anything if the PID + // doesn't match - it may be OK to leave that up to the user. } uint8_t payload_index = 1 + handle->request.pid_length; @@ -197,13 +199,23 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, response.mode - MODE_RESPONSE_OFFSET, handle->request.mode); } + } else { + shims->log("Received an empty response on arb ID 0x%x", + response.arbitration_id); } + // TODO For now even if we got an empty repsonse or something for + // the wrong mode, we're marking this as completed - I'm not sure + // those other cases could or will ever happen in practice. + // Alternatively, we could re-init handle->isotp_receive_handle if + // the current one completed without a valid response to this + // diagnostic request. + response.completed = true; + handle->completed = true; if(handle->completed && handle->callback != NULL) { handle->callback(&response); } } - } else { shims->log("Mode %d request to arb ID 0x%x is already completed", handle->request.mode, handle->request.arbitration_id); diff --git a/tests/test_core.c b/tests/test_core.c index cf8673a..51dff33 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -140,8 +140,11 @@ START_TEST (test_wrong_mode_response) const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); - fail_if(last_response_was_received); - fail_if(handle.completed); + // TODO change this if we even re-request a message receipt on a mode or PID + // mismatch + fail_unless(last_response_was_received); + fail_unless(handle.completed); + fail_if(last_response_received.success); } END_TEST -- cgit 1.2.3-korg From 871b3cb04aaa24637a3b2bfae7082dfacf34f7d2 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 6 Jan 2014 23:48:50 -0500 Subject: Grab PID from response without dereffing a type punned pointer. --- deps/isotp-c | 2 +- src/obd2/obd2.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index 8922abb..513d8c8 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 8922abb7ff8c30e1fa5af078284eb6aebf0052e5 +Subproject commit 513d8c8d7089960618a1fa00a71442dc39294588 diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index c780fd8..93cb888 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -140,10 +140,8 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, response->mode = handle->request.mode; if(handle->request.pid_length > 0 && message->size > 1) { if(handle->request.pid_length == 2) { - response->pid = *(uint16_t*)&message->payload[PID_BYTE_INDEX]; - if(BYTE_ORDER == LITTLE_ENDIAN) { - response->pid = __builtin_bswap32(response->pid << 16); - } + response->pid = get_bitfield(message->payload, message->size, + PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT); } else { response->pid = message->payload[PID_BYTE_INDEX]; } -- cgit 1.2.3-korg From 3aa979db127d362bb84ccab86c0f13185a34e9fa Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 00:02:44 -0500 Subject: Don't supress warnings when compiling, argh! --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6d1dd5a..4adf3ce 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CC = gcc INCLUDES = -Isrc -Ideps/bitfield-c/src -Ideps/isotp-c/src -CFLAGS = $(INCLUDES) -c -w -Wall -Werror -g -ggdb -std=gnu++0x -coverage +CFLAGS = $(INCLUDES) -c -Wall -Werror -g -ggdb -std=gnu99 -coverage LDFLAGS = -coverage -lm LDLIBS = -lcheck -- cgit 1.2.3-korg From e1cc30c83276f4eb52e84777693445443734a878 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 00:02:55 -0500 Subject: Don't walk off the end of the payload array. --- src/obd2/obd2.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 93cb888..73e13b2 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -58,7 +58,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, 1 + request->payload_length + request->pid_length, NULL); if(shims->log != NULL) { - 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", + 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", request->arbitration_id, request->mode, request->pid, @@ -69,7 +69,6 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, request->payload[4], request->payload[5], request->payload[6], - request->payload[7], request->payload_length); } -- cgit 1.2.3-korg From bc15d0970d8fe8441202bb38514b6216e6639685 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 00:05:11 -0500 Subject: Fix warnings, now that they're enabled! --- tests/common.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/common.c b/tests/common.c index 99b2f7c..f79b8ad 100644 --- a/tests/common.c +++ b/tests/common.c @@ -3,6 +3,7 @@ #include #include #include +#include DiagnosticShims SHIMS; @@ -22,7 +23,7 @@ void debug(const char* format, ...) { va_end(args); } -void mock_send_can(const uint16_t arbitration_id, const uint8_t* data, +bool mock_send_can(const uint16_t arbitration_id, const uint8_t* data, const uint8_t size) { can_frame_was_sent = true; last_can_frame_sent_arb_id = arbitration_id; @@ -30,13 +31,11 @@ void mock_send_can(const uint16_t arbitration_id, const uint8_t* data, if(size > 0) { memcpy(last_can_payload_sent, data, size); } -} - -void mock_set_timer(uint16_t time_ms, void (*callback)) { + return true; } void setup() { - SHIMS = diagnostic_init_shims(debug, mock_send_can, mock_set_timer); + SHIMS = diagnostic_init_shims(debug, mock_send_can, NULL); memset(last_can_payload_sent, 0, sizeof(last_can_payload_sent)); can_frame_was_sent = false; last_response_was_received = false; -- cgit 1.2.3-korg From 1525ed0325c7679d4ea42724652119aceb5a4d13 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 16:54:54 -0500 Subject: Add a constant for the functional broadcast address 0x7df. --- README.mkd | 8 ++++---- src/obd2/obd2.h | 2 ++ tests/test_core.c | 17 ++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.mkd b/README.mkd index 58cc152..0c28206 100644 --- a/README.mkd +++ b/README.mkd @@ -37,8 +37,8 @@ With your shims in place, create a `DiagnosticShims` object to pass them around: DiagnosticShims shims = diagnostic_init_shims(debug, send_can, set_timer); -With your shims in hand, send a simple PID request to the stadnard broadcast -address, `0x7df`: +With your shims in hand, send a simple PID request to the standard broadcast +address, `0x7df` (we use the constant `OBD2_FUNCTIONAL_BROADCAST_ID` here): // Optional: This is your callback that will be called the response to your // diagnostic request is received. @@ -48,7 +48,7 @@ address, `0x7df`: DiagnosticRequestHandle handle = diagnostic_request_pid(&shims, DIAGNOSTIC_STANDARD_PID, // this is a standard PID request, not an extended or enhanced one - 0x7df, // the request is going out to the broadcast arbitration ID + OBD2_FUNCTIONAL_BROADCAST_ID, // the request is going out to the broadcast arbitration ID 0x2, // we want PID 0x2 response_received_handler); // our callback (optional, use NULL if you don't have one) @@ -97,7 +97,7 @@ If you want to do more besides PID requests on mode 0x1 and 0x22, there's a lower level API you can use. Here's how to make a mode 3 request to get DTCs. DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_EMISSIONS_DTC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, NULL); diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index 7dfdb59..b301138 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -5,6 +5,8 @@ #include #include +#define OBD2_FUNCTIONAL_BROADCAST_ID 0x7df + #ifdef __cplusplus extern "C" { #endif diff --git a/tests/test_core.c b/tests/test_core.c index 51dff33..b115c1c 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -20,7 +20,7 @@ void response_received_handler(const DiagnosticResponse* response) { START_TEST (test_receive_wrong_arb_id) { DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, @@ -37,7 +37,7 @@ END_TEST START_TEST (test_send_diag_request_with_payload) { DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, payload: {0x12, 0x34}, payload_length: 2 @@ -59,7 +59,7 @@ END_TEST START_TEST (test_send_diag_request) { DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_EMISSIONS_DTC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, @@ -89,8 +89,7 @@ END_TEST START_TEST (test_request_pid_standard) { - // TODO need a constant for the 7df broadcast functional request - uint16_t arb_id = 0x7df; + uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_STANDARD_PID, arb_id, 0x2, response_received_handler); @@ -111,7 +110,7 @@ END_TEST START_TEST (test_request_pid_enhanced) { - uint16_t arb_id = 0x7df; + uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -132,7 +131,7 @@ END_TEST START_TEST (test_wrong_mode_response) { - uint16_t arb_id = 0x7df; + uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -151,7 +150,7 @@ END_TEST START_TEST (test_handle_completed) { DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, @@ -185,7 +184,7 @@ END_TEST START_TEST (test_negative_response) { DiagnosticRequest request = { - arbitration_id: 0x7df, + arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, -- cgit 1.2.3-korg From 352be104317c7c7867046f9eb48f923282b0d45e Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 17:12:05 -0500 Subject: Don't complete requests if mode or PID didn't match. --- src/obd2/obd2.c | 28 +++++++++++++--------------- tests/test_core.c | 23 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 73e13b2..88ce74c 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -137,15 +137,16 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, // hide the "response" version of the mode from the user // if it matched response->mode = handle->request.mode; + bool has_pid = false; if(handle->request.pid_length > 0 && message->size > 1) { + has_pid = true; if(handle->request.pid_length == 2) { response->pid = get_bitfield(message->payload, message->size, PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT); } else { response->pid = message->payload[PID_BYTE_INDEX]; } - // TODO we're not currently throwing an error or anything if the PID - // doesn't match - it may be OK to leave that up to the user. + } uint8_t payload_index = 1 + handle->request.pid_length; @@ -154,8 +155,13 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, memcpy(response->payload, &message->payload[payload_index], response->payload_length); } - response->success = true; - response->completed = true; + + if(!has_pid || response->pid == handle->request.pid) { + response->success = true; + response->completed = true; + } else { + response_was_positive = false; + } } return response_was_positive; } @@ -192,22 +198,14 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, handle->success = true; handle->completed = true; } else { - shims->log("Response was for a mode 0x%x request, not our mode 0x%x request", - response.mode - MODE_RESPONSE_OFFSET, - handle->request.mode); + shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", + response.mode - MODE_RESPONSE_OFFSET, response.pid, + handle->request.mode, handle->request.pid); } } else { shims->log("Received an empty response on arb ID 0x%x", response.arbitration_id); } - // TODO For now even if we got an empty repsonse or something for - // the wrong mode, we're marking this as completed - I'm not sure - // those other cases could or will ever happen in practice. - // Alternatively, we could re-init handle->isotp_receive_handle if - // the current one completed without a valid response to this - // diagnostic request. - response.completed = true; - handle->completed = true; if(handle->completed && handle->callback != NULL) { handle->callback(&response); diff --git a/tests/test_core.c b/tests/test_core.c index b115c1c..8615ff1 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -139,11 +139,23 @@ START_TEST (test_wrong_mode_response) const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); - // TODO change this if we even re-request a message receipt on a mode or PID - // mismatch - fail_unless(last_response_was_received); - fail_unless(handle.completed); - fail_if(last_response_received.success); + fail_if(last_response_was_received); + fail_if(handle.completed); +} +END_TEST + +START_TEST (test_wrong_pid_response) +{ + uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x3, 0x45}; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, + sizeof(can_data)); + fail_if(last_response_was_received); + fail_if(handle.completed); } END_TEST @@ -216,6 +228,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_request_pid_standard); tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); + tcase_add_test(tc_core, test_wrong_pid_response); tcase_add_test(tc_core, test_handle_completed); tcase_add_test(tc_core, test_negative_response); -- cgit 1.2.3-korg From 206686571937528e3b754dfd87e3c02f78896919 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 17:22:58 -0500 Subject: Prep request handler to receive again if we got wrong mode or PID. --- src/obd2/obd2.c | 12 +++++++++--- tests/test_core.c | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 88ce74c..1c3d7bc 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -29,6 +29,13 @@ DiagnosticShims diagnostic_init_shims(LogShim log, return shims; } +static void setup_receive_handle(DiagnosticRequestHandle* handle) { + handle->isotp_receive_handle = isotp_receive(&handle->isotp_shims, + handle->request.arbitration_id + ARBITRATION_ID_OFFSET, + NULL); +} + + DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { @@ -72,9 +79,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, request->payload_length); } - handle.isotp_receive_handle = isotp_receive(&handle.isotp_shims, - request->arbitration_id + ARBITRATION_ID_OFFSET, - NULL); + setup_receive_handle(&handle); // TODO notes on multi frame: // TODO what are the timers for exactly? @@ -201,6 +206,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", response.mode - MODE_RESPONSE_OFFSET, response.pid, handle->request.mode, handle->request.pid); + setup_receive_handle(handle); } } else { shims->log("Received an empty response on arb ID 0x%x", diff --git a/tests/test_core.c b/tests/test_core.c index 8615ff1..9f97923 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -159,6 +159,30 @@ START_TEST (test_wrong_pid_response) } END_TEST +START_TEST (test_wrong_pid_then_right_completes) +{ + uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + + fail_if(last_response_was_received); + uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x3, 0x45}; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, + sizeof(can_data)); + fail_if(last_response_was_received); + fail_if(handle.completed); + + can_data[3] = 0x2; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, + sizeof(can_data)); + fail_unless(last_response_was_received); + fail_unless(handle.completed); + fail_unless(handle.success); + fail_unless(last_response_received.success); + ck_assert_int_eq(last_response_received.pid, 0x2); +} +END_TEST + START_TEST (test_handle_completed) { DiagnosticRequest request = { @@ -229,6 +253,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); tcase_add_test(tc_core, test_wrong_pid_response); + tcase_add_test(tc_core, test_wrong_pid_then_right_completes); tcase_add_test(tc_core, test_handle_completed); tcase_add_test(tc_core, test_negative_response); -- cgit 1.2.3-korg From 3401380ce6fc0f1021674145105632fbf6304007 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 7 Jan 2014 17:34:33 -0500 Subject: Add a placeholder function for decoding OBD-II PIDs. --- src/obd2/extras.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/obd2/extras.h b/src/obd2/extras.h index 50ad3b7..5ec4b47 100644 --- a/src/obd2/extras.h +++ b/src/obd2/extras.h @@ -63,6 +63,9 @@ bool diagnostic_clear_dtc(DiagnosticShims* shims); DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); +// TODO +float diagnostic_decode_obd2_pid(DiagnosticResponse* response); + #ifdef __cplusplus } #endif -- cgit 1.2.3-korg From 2b6a91c5885328894fddbad3acd33d80cf5fb792 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 8 Jan 2014 12:05:54 -0500 Subject: Add a function prototype for payloder decoders. --- src/obd2/obd2_types.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h index d1c5a3a..5260fa8 100644 --- a/src/obd2/obd2_types.h +++ b/src/obd2/obd2_types.h @@ -126,6 +126,8 @@ typedef enum { */ typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); +typedef float (*DiagnosticResponseDecoder)(const DiagnosticResponse* response); + /* Public: A handle for initiating and continuing a single diagnostic request. * * A diagnostic request requires one or more CAN messages to be sent, and one -- cgit 1.2.3-korg From fe38d0d4925be0e0182ec5b511b8f7fe715ffee5 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 8 Jan 2014 17:19:05 -0500 Subject: Reject a response that should have had PID but did not. --- src/obd2/obd2.c | 3 ++- tests/test_core.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 1c3d7bc..c000aa3 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -161,7 +161,8 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, response->payload_length); } - if(!has_pid || response->pid == handle->request.pid) { + if((handle->request.pid_length == 0 && !has_pid) + || response->pid == handle->request.pid) { response->success = true; response->completed = true; } else { diff --git a/tests/test_core.c b/tests/test_core.c index 9f97923..be4d141 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -144,6 +144,21 @@ START_TEST (test_wrong_mode_response) } END_TEST +START_TEST (test_missing_pid) +{ + uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x1, 0x22 + 0x40}; + diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, + sizeof(can_data)); + fail_if(last_response_was_received); + fail_if(handle.completed); +} +END_TEST + START_TEST (test_wrong_pid_response) { uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; @@ -253,6 +268,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); tcase_add_test(tc_core, test_wrong_pid_response); + tcase_add_test(tc_core, test_missing_pid); tcase_add_test(tc_core, test_wrong_pid_then_right_completes); tcase_add_test(tc_core, test_handle_completed); tcase_add_test(tc_core, test_negative_response); -- cgit 1.2.3-korg From 6e08c6f35254273aa9750600be0effe6e7cf276e Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 8 Jan 2014 17:27:54 -0500 Subject: Don't log incorrect negative response mode, just cap it at 0. --- src/obd2/obd2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index c000aa3..25b00e7 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -205,8 +205,9 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, handle->completed = true; } else { shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", - response.mode - MODE_RESPONSE_OFFSET, response.pid, - handle->request.mode, handle->request.pid); + MAX(0, response.mode - MODE_RESPONSE_OFFSET), + response.pid, handle->request.mode, + handle->request.pid); setup_receive_handle(handle); } } else { -- cgit 1.2.3-korg From 1b5a2116bc558ebef5d59c2c37c02c7375bf8712 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 15 Jan 2014 16:49:22 -0500 Subject: Accept functional broadcast responses to a range of arb IDs. --- src/obd2/obd2.c | 84 ++++++++++++++++++++++++++++-------------------- src/obd2/obd2.h | 2 ++ src/obd2/obd2_types.h | 4 ++- tests/test_core.c | 89 ++++++++++++++++++++++++++------------------------- 4 files changed, 99 insertions(+), 80 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 25b00e7..513c7d7 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -30,9 +30,20 @@ DiagnosticShims diagnostic_init_shims(LogShim log, } static void setup_receive_handle(DiagnosticRequestHandle* handle) { - handle->isotp_receive_handle = isotp_receive(&handle->isotp_shims, - handle->request.arbitration_id + ARBITRATION_ID_OFFSET, - NULL); + if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) { + for(uint16_t response_id = 0; + response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { + handle->isotp_receive_handles[response_id] = isotp_receive( + &handle->isotp_shims, OBD2_FUNCTIONAL_RESPONSE_START + response_id, + NULL); + } + handle->isotp_receive_handle_count = OBD2_FUNCTIONAL_RESPONSE_COUNT; + } else { + handle->isotp_receive_handle_count = 1; + handle->isotp_receive_handles[0] = isotp_receive(&handle->isotp_shims, + handle->request.arbitration_id + ARBITRATION_ID_OFFSET, + NULL); + } } @@ -185,43 +196,46 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(!handle->isotp_send_handle.completed) { isotp_continue_send(&handle->isotp_shims, &handle->isotp_send_handle, arbitration_id, data, size); - } else if(!handle->isotp_receive_handle.completed) { - IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, - &handle->isotp_receive_handle, arbitration_id, data, size); - - if(message.completed) { - if(message.size > 0) { - response.mode = message.payload[0]; - if(handle_negative_response(&message, &response, shims)) { - shims->log("Received a negative response to mode %d on arb ID 0x%x", - response.mode, response.arbitration_id); - handle->success = true; - handle->completed = true; - } else if(handle_positive_response(handle, &message, &response, - shims)) { - shims->log("Received a positive mode %d response on arb ID 0x%x", - response.mode, response.arbitration_id); - handle->success = true; - handle->completed = true; + } else { + for(uint8_t i = 0; i < handle->isotp_receive_handle_count; ++i) { + IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, + &handle->isotp_receive_handles[i], arbitration_id, data, size); + + // TODO as of now we're completing the handle as soon as one + // broadcast response is received....need to hang on for 100ms + if(message.completed) { + if(message.size > 0) { + response.mode = message.payload[0]; + if(handle_negative_response(&message, &response, shims)) { + shims->log("Received a negative response to mode %d on arb ID 0x%x", + response.mode, response.arbitration_id); + handle->success = true; + handle->completed = true; + } else if(handle_positive_response(handle, &message, &response, + shims)) { + shims->log("Received a positive mode %d response on arb ID 0x%x", + response.mode, response.arbitration_id); + handle->success = true; + handle->completed = true; + } else { + shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", + MAX(0, response.mode - MODE_RESPONSE_OFFSET), + response.pid, handle->request.mode, + handle->request.pid); + // TODO just leave handles open until the user decides + // to be done with it - keep a count of valid responses + // received. + } } else { - shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", - MAX(0, response.mode - MODE_RESPONSE_OFFSET), - response.pid, handle->request.mode, - handle->request.pid); - setup_receive_handle(handle); + shims->log("Received an empty response on arb ID 0x%x", + response.arbitration_id); } - } else { - shims->log("Received an empty response on arb ID 0x%x", - response.arbitration_id); - } - if(handle->completed && handle->callback != NULL) { - handle->callback(&response); + if(handle->completed && handle->callback != NULL) { + handle->callback(&response); + } } } - } else { - shims->log("Mode %d request to arb ID 0x%x is already completed", - handle->request.mode, handle->request.arbitration_id); } return response; } diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h index b301138..bcd6e76 100644 --- a/src/obd2/obd2.h +++ b/src/obd2/obd2.h @@ -6,6 +6,8 @@ #include #define OBD2_FUNCTIONAL_BROADCAST_ID 0x7df +#define OBD2_FUNCTIONAL_RESPONSE_START 0x7e8 +#define OBD2_FUNCTIONAL_RESPONSE_COUNT 8 #ifdef __cplusplus extern "C" { diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h index 5260fa8..0699fc4 100644 --- a/src/obd2/obd2_types.h +++ b/src/obd2/obd2_types.h @@ -12,6 +12,7 @@ extern "C" { // TODO This isn't true for multi frame messages - we may need to dynamically // allocate this in the future #define MAX_OBD2_PAYLOAD_LENGTH 7 +#define MAX_RESPONDING_ECU_COUNT 8 #define VIN_LENGTH 17 /* Private: The four main types of diagnositc requests that determine how the @@ -149,7 +150,8 @@ typedef struct { // Private IsoTpShims isotp_shims; IsoTpSendHandle isotp_send_handle; - IsoTpReceiveHandle isotp_receive_handle; + IsoTpReceiveHandle isotp_receive_handles[MAX_RESPONDING_ECU_COUNT]; + uint8_t isotp_receive_handle_count; DiagnosticResponseReceived callback; // DiagnosticMilStatusReceived mil_status_callback; // DiagnosticVinReceived vin_callback; diff --git a/tests/test_core.c b/tests/test_core.c index be4d141..182f268 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -20,7 +20,7 @@ void response_received_handler(const DiagnosticResponse* response) { START_TEST (test_receive_wrong_arb_id) { DiagnosticRequest request = { - arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, + arbitration_id: 0x100, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, @@ -37,7 +37,7 @@ END_TEST START_TEST (test_send_diag_request_with_payload) { DiagnosticRequest request = { - arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, + arbitration_id: 0x100, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, payload: {0x12, 0x34}, payload_length: 2 @@ -56,7 +56,7 @@ START_TEST (test_send_diag_request_with_payload) } END_TEST -START_TEST (test_send_diag_request) +START_TEST (test_send_functional_request) { DiagnosticRequest request = { arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, @@ -70,6 +70,41 @@ START_TEST (test_send_diag_request) ck_assert_int_eq(last_can_payload_sent[1], request.mode); ck_assert_int_eq(last_can_payload_size, 2); + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; + for(uint16_t filter = OBD2_FUNCTIONAL_RESPONSE_START; filter < + OBD2_FUNCTIONAL_RESPONSE_START + OBD2_FUNCTIONAL_RESPONSE_COUNT; + filter++) { + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + filter, can_data, sizeof(can_data)); + fail_unless(response.success); + fail_unless(response.completed); + fail_unless(handle.completed); + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + filter); + ck_assert_int_eq(last_response_received.mode, request.mode); + ck_assert_int_eq(last_response_received.pid, 0); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[2]); + } +} +END_TEST + +START_TEST (test_send_diag_request) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(handle.completed); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_size, 2); + fail_if(last_response_was_received); const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, @@ -110,7 +145,7 @@ END_TEST START_TEST (test_request_pid_enhanced) { - uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -131,7 +166,7 @@ END_TEST START_TEST (test_wrong_mode_response) { - uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -146,7 +181,7 @@ END_TEST START_TEST (test_missing_pid) { - uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -161,7 +196,7 @@ END_TEST START_TEST (test_wrong_pid_response) { - uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -176,7 +211,7 @@ END_TEST START_TEST (test_wrong_pid_then_right_completes) { - uint16_t arb_id = OBD2_FUNCTIONAL_BROADCAST_ID; + uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); @@ -198,44 +233,10 @@ START_TEST (test_wrong_pid_then_right_completes) } END_TEST -START_TEST (test_handle_completed) -{ - DiagnosticRequest request = { - arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, - mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST - }; - DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, - response_received_handler); - - fail_if(handle.completed); - - const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; - DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, - request.arbitration_id + 0x8, can_data, sizeof(can_data)); - fail_unless(response.success); - fail_unless(response.completed); - fail_unless(handle.completed); - - response = diagnostic_receive_can_frame(&SHIMS, &handle, - request.arbitration_id + 0x8, can_data, sizeof(can_data)); - fail_if(response.success); - fail_if(response.completed); - fail_unless(handle.completed); - - ck_assert(last_response_received.success); - ck_assert_int_eq(last_response_received.arbitration_id, - request.arbitration_id + 0x8); - ck_assert_int_eq(last_response_received.mode, request.mode); - ck_assert_int_eq(last_response_received.pid, 0); - ck_assert_int_eq(last_response_received.payload_length, 1); - ck_assert_int_eq(last_response_received.payload[0], can_data[2]); -} -END_TEST - START_TEST (test_negative_response) { DiagnosticRequest request = { - arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, + arbitration_id: 0x100, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, @@ -262,6 +263,7 @@ Suite* testSuite(void) { TCase *tc_core = tcase_create("core"); tcase_add_checked_fixture(tc_core, setup, NULL); tcase_add_test(tc_core, test_send_diag_request); + tcase_add_test(tc_core, test_send_functional_request); tcase_add_test(tc_core, test_send_diag_request_with_payload); tcase_add_test(tc_core, test_receive_wrong_arb_id); tcase_add_test(tc_core, test_request_pid_standard); @@ -270,7 +272,6 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_wrong_pid_response); tcase_add_test(tc_core, test_missing_pid); tcase_add_test(tc_core, test_wrong_pid_then_right_completes); - tcase_add_test(tc_core, test_handle_completed); tcase_add_test(tc_core, test_negative_response); // TODO these are future work: -- cgit 1.2.3-korg From 648c2f77afd1dadce1c01de3f1dc42b54da656d0 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 15 Jan 2014 16:58:11 -0500 Subject: Preserve compatibility with C89 mode. --- src/obd2/obd2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 513c7d7..5b282f1 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -31,7 +31,8 @@ DiagnosticShims diagnostic_init_shims(LogShim log, static void setup_receive_handle(DiagnosticRequestHandle* handle) { if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) { - for(uint16_t response_id = 0; + uint16_t response_id; + for(response_id = 0; response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { handle->isotp_receive_handles[response_id] = isotp_receive( &handle->isotp_shims, OBD2_FUNCTIONAL_RESPONSE_START + response_id, @@ -197,7 +198,8 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, isotp_continue_send(&handle->isotp_shims, &handle->isotp_send_handle, arbitration_id, data, size); } else { - for(uint8_t i = 0; i < handle->isotp_receive_handle_count; ++i) { + uint8_t i; + for(i = 0; i < handle->isotp_receive_handle_count; ++i) { IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handles[i], arbitration_id, data, size); -- cgit 1.2.3-korg From 6ebad2aac3ba2f0d7ff5e37c5db4e5b9549247e2 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 17 Jan 2014 15:56:34 -0500 Subject: Mark whether a PID is present in response. --- src/obd2/obd2.c | 6 +++--- src/obd2/obd2_types.h | 6 +++++- tests/test_core.c | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c index 5b282f1..5061dd6 100644 --- a/src/obd2/obd2.c +++ b/src/obd2/obd2.c @@ -154,9 +154,9 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, // hide the "response" version of the mode from the user // if it matched response->mode = handle->request.mode; - bool has_pid = false; + response->has_pid = false; if(handle->request.pid_length > 0 && message->size > 1) { - has_pid = true; + response->has_pid = true; if(handle->request.pid_length == 2) { response->pid = get_bitfield(message->payload, message->size, PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT); @@ -173,7 +173,7 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, response->payload_length); } - if((handle->request.pid_length == 0 && !has_pid) + if((handle->request.pid_length == 0 && !response->has_pid) || response->pid == handle->request.pid) { response->success = true; response->completed = true; diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h index 0699fc4..15552e8 100644 --- a/src/obd2/obd2_types.h +++ b/src/obd2/obd2_types.h @@ -83,7 +83,10 @@ typedef enum { * the negative_response_code field for the reason. * arbitration_id - The arbitration ID the response was received on. * mode - The OBD-II mode for the original request. - * pid - If the request was for a PID, this is the PID echo. + * has_pid - If this is a response to a PID request, this will be true and the + * 'pid' field will be valid. + * pid - If the request was for a PID, this is the PID echo. Only valid if + * 'has_pid' is true. * negative_response_code - If the request was not successful, 'success' will be * false and this will be set to a DiagnosticNegativeResponseCode returned * by the other node. @@ -95,6 +98,7 @@ typedef struct { bool success; uint16_t arbitration_id; uint8_t mode; + bool has_pid; uint16_t pid; DiagnosticNegativeResponseCode negative_response_code; uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; diff --git a/tests/test_core.c b/tests/test_core.c index 182f268..1ab9409 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -84,7 +84,7 @@ START_TEST (test_send_functional_request) ck_assert_int_eq(last_response_received.arbitration_id, filter); ck_assert_int_eq(last_response_received.mode, request.mode); - ck_assert_int_eq(last_response_received.pid, 0); + fail_if(last_response_received.has_pid); ck_assert_int_eq(last_response_received.payload_length, 1); ck_assert_int_eq(last_response_received.payload[0], can_data[2]); } @@ -116,7 +116,7 @@ START_TEST (test_send_diag_request) ck_assert_int_eq(last_response_received.arbitration_id, request.arbitration_id + 0x8); ck_assert_int_eq(last_response_received.mode, request.mode); - ck_assert_int_eq(last_response_received.pid, 0); + fail_if(last_response_received.has_pid); ck_assert_int_eq(last_response_received.payload_length, 1); ck_assert_int_eq(last_response_received.payload[0], can_data[2]); } @@ -137,6 +137,7 @@ START_TEST (test_request_pid_standard) ck_assert_int_eq(last_response_received.arbitration_id, arb_id + 0x8); ck_assert_int_eq(last_response_received.mode, 0x1); + fail_unless(last_response_received.has_pid); ck_assert_int_eq(last_response_received.pid, 0x2); ck_assert_int_eq(last_response_received.payload_length, 1); ck_assert_int_eq(last_response_received.payload[0], can_data[3]); @@ -158,6 +159,7 @@ START_TEST (test_request_pid_enhanced) ck_assert_int_eq(last_response_received.arbitration_id, arb_id + 0x8); ck_assert_int_eq(last_response_received.mode, 0x22); + fail_unless(last_response_received.has_pid); ck_assert_int_eq(last_response_received.pid, 0x2); ck_assert_int_eq(last_response_received.payload_length, 1); ck_assert_int_eq(last_response_received.payload[0], can_data[4]); -- cgit 1.2.3-korg From 46fb0eb96e8efd285c2a0cccf699862ed21717ed Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 20 Jan 2014 15:22:27 -0500 Subject: Rename from simply obd2 to more general UDS (unified diagnostics). Fixed #1. --- README.mkd | 14 +-- src/obd2/extras.c | 39 -------- src/obd2/extras.h | 73 --------------- src/obd2/obd2.c | 243 -------------------------------------------------- src/obd2/obd2.h | 102 --------------------- src/obd2/obd2_types.h | 188 -------------------------------------- src/uds/extras.c | 39 ++++++++ src/uds/extras.h | 73 +++++++++++++++ src/uds/uds.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/uds/uds.h | 102 +++++++++++++++++++++ src/uds/uds_types.h | 188 ++++++++++++++++++++++++++++++++++++++ tests/common.c | 2 +- tests/test_core.c | 4 +- 13 files changed, 656 insertions(+), 654 deletions(-) delete mode 100644 src/obd2/extras.c delete mode 100644 src/obd2/extras.h delete mode 100644 src/obd2/obd2.c delete mode 100644 src/obd2/obd2.h delete mode 100644 src/obd2/obd2_types.h create mode 100644 src/uds/extras.c create mode 100644 src/uds/extras.h create mode 100644 src/uds/uds.c create mode 100644 src/uds/uds.h create mode 100644 src/uds/uds_types.h diff --git a/README.mkd b/README.mkd index 0c28206..8f9574f 100644 --- a/README.mkd +++ b/README.mkd @@ -1,9 +1,11 @@ -OBD-II Support Library in C -============================= - -This is a platform agnostic C library that implements the standard On Board -Diagnostics system for vehicles. It currently supports OBD-II running over CAN -(ISO 15765-4), which uses the ISO-TP (ISO 15765-2) protocol underneath. +Unified Diagnostic Services (UDS) Support Library in C +====================================================== + +This is a platform agnostic C library that implements the Unified Diagnostics +Services protocol for automotive electronics. UDS is documented in ISO 14229 and +is the underpinning for the more well-known On-board Diagnostics (OBD) standard. +The library currently supports UDS running over CAN (ISO 15765-4), which uses +the ISO-TP (ISO 15765-2) protocol for message framing. This library doesn't assume anything about the source of your diagnostic message requests or underlying interface to the CAN bus. It uses dependency injection to diff --git a/src/obd2/extras.c b/src/obd2/extras.c deleted file mode 100644 index 37a55fe..0000000 --- a/src/obd2/extras.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include - -// TODO everything below here is for future work...not critical for now. - -DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( - DiagnosticShims* shims, - DiagnosticMilStatusReceived callback) { - // TODO request malfunction indicator light (MIL) status - request mode 1 - // pid 1, parse first bit - DiagnosticRequestHandle handle; - return handle; -} - -DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, - DiagnosticVinReceived callback) { - DiagnosticRequestHandle handle; - return handle; -} - -DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, - DiagnosticTroubleCodeType dtc_type, - DiagnosticTroubleCodesReceived callback) { - DiagnosticRequestHandle handle; - return handle; -} - -bool diagnostic_clear_dtc(DiagnosticShims* shims) { - return false; -} - -DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { - // before calling the callback, split up the received bytes into 1 or 2 byte - // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs - // TODO request supported PIDs - request PID 0 and parse 4 bytes in response - DiagnosticRequestHandle handle; - return handle; -} diff --git a/src/obd2/extras.h b/src/obd2/extras.h deleted file mode 100644 index 5ec4b47..0000000 --- a/src/obd2/extras.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __EXTRAS_H__ -#define __EXTRAS_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// TODO everything in here is unused for the moment! - -typedef enum { - POWERTRAIN = 0x0, - CHASSIS = 0x1, - BODY = 0x2, - NETWORK = 0x3 -} DiagnosticTroubleCodeGroup; - -typedef struct { - DiagnosticTroubleCodeGroup group; - uint8_t group_num; - uint8_t code; -} DiagnosticTroubleCode; - - -/* Private: TODO unused for now - */ -typedef enum { - DTC_EMISSIONS, - DTC_DRIVE_CYCLE, - DTC_PERMANENT -} DiagnosticTroubleCodeType; - - -// TODO should we enumerate every OBD-II PID? need conversion formulas, too -typedef struct { - uint16_t pid; - uint8_t bytes_returned; - float min_value; - float max_value; -} DiagnosticParameter; - -typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); -typedef void (*DiagnosticVinReceived)(uint8_t vin[]); -typedef void (*DiagnosticTroubleCodesReceived)( - DiagnosticMode mode, DiagnosticTroubleCode* codes); -typedef void (*DiagnosticPidEnumerationReceived)( - const DiagnosticResponse* response, uint16_t* pids); - -DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( - DiagnosticShims* shims, - DiagnosticMilStatusReceived callback); - -DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, - DiagnosticVinReceived callback); - -DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, - DiagnosticTroubleCodeType dtc_type, - DiagnosticTroubleCodesReceived callback); - -bool diagnostic_clear_dtc(DiagnosticShims* shims); - -DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); - -// TODO -float diagnostic_decode_obd2_pid(DiagnosticResponse* response); - -#ifdef __cplusplus -} -#endif - -#endif // __EXTRAS_H__ diff --git a/src/obd2/obd2.c b/src/obd2/obd2.c deleted file mode 100644 index 5061dd6..0000000 --- a/src/obd2/obd2.c +++ /dev/null @@ -1,243 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define ARBITRATION_ID_OFFSET 0x8 -#define MODE_RESPONSE_OFFSET 0x40 -#define NEGATIVE_RESPONSE_MODE 0x7f -#define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6 -#define MODE_BYTE_INDEX 0 -#define PID_BYTE_INDEX 1 -#define NEGATIVE_RESPONSE_MODE_INDEX 1 -#define NEGATIVE_RESPONSE_NRC_INDEX 2 - -#ifndef MAX -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#endif - -DiagnosticShims diagnostic_init_shims(LogShim log, - SendCanMessageShim send_can_message, - SetTimerShim set_timer) { - DiagnosticShims shims = { - log: log, - send_can_message: send_can_message, - set_timer: set_timer - }; - return shims; -} - -static void setup_receive_handle(DiagnosticRequestHandle* handle) { - if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) { - uint16_t response_id; - for(response_id = 0; - response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { - handle->isotp_receive_handles[response_id] = isotp_receive( - &handle->isotp_shims, OBD2_FUNCTIONAL_RESPONSE_START + response_id, - NULL); - } - handle->isotp_receive_handle_count = OBD2_FUNCTIONAL_RESPONSE_COUNT; - } else { - handle->isotp_receive_handle_count = 1; - handle->isotp_receive_handles[0] = isotp_receive(&handle->isotp_shims, - handle->request.arbitration_id + ARBITRATION_ID_OFFSET, - NULL); - } -} - - -DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticResponseReceived callback) { - DiagnosticRequestHandle handle = { - request: *request, - callback: callback, - success: false, - completed: false - }; - - uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; - payload[MODE_BYTE_INDEX] = request->mode; - if(request->pid_length > 0) { - set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, - request->pid_length * CHAR_BIT, payload, sizeof(payload)); - } - if(request->payload_length > 0) { - memcpy(&payload[PID_BYTE_INDEX + request->pid_length], - request->payload, request->payload_length); - } - - handle.isotp_shims = isotp_init_shims(shims->log, - shims->send_can_message, - shims->set_timer); - - handle.isotp_send_handle = isotp_send(&handle.isotp_shims, - request->arbitration_id, payload, - 1 + request->payload_length + request->pid_length, - NULL); - if(shims->log != NULL) { - 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", - request->arbitration_id, - request->mode, - request->pid, - request->payload[0], - request->payload[1], - request->payload[2], - request->payload[3], - request->payload[4], - request->payload[5], - request->payload[6], - request->payload_length); - } - - setup_receive_handle(&handle); - - // TODO notes on multi frame: - // TODO what are the timers for exactly? - // - // when sending multi frame, send 1 frame, wait for a response - // if it says send all, send all right away - // if it says flow control, set the time for the next send - // instead of creating a timer with an async callback, add a process_handle - // function that's called repeatedly in the main loop - if it's time to - // send, we do it. so there's a process_handle_send and receive_can_frame - // that are just called continuously from the main loop. it's a waste of a - // few cpu cycles but it may be more natural than callbacks. - // - // what woudl a timer callback look like...it would need to pass the handle - // and that's all. seems like a context void* would be able to capture all - // of the information but arg, memory allocation. look at how it's done in - // the other library again - // - return handle; -} - -DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, - uint16_t pid, DiagnosticResponseReceived callback) { - DiagnosticRequest request = { - arbitration_id: arbitration_id, - mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, - pid: pid, - pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2 - }; - - return diagnostic_request(shims, &request, callback); -} - -static bool handle_negative_response(IsoTpMessage* message, - DiagnosticResponse* response, DiagnosticShims* shims) { - bool response_was_negative = false; - if(response->mode == NEGATIVE_RESPONSE_MODE) { - response_was_negative = true; - if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) { - response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX]; - } - - if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) { - response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX]; - } - - response->success = false; - response->completed = true; - } - return response_was_negative; -} - -static bool handle_positive_response(DiagnosticRequestHandle* handle, - IsoTpMessage* message, DiagnosticResponse* response, - DiagnosticShims* shims) { - bool response_was_positive = false; - if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) { - response_was_positive = true; - // hide the "response" version of the mode from the user - // if it matched - response->mode = handle->request.mode; - response->has_pid = false; - if(handle->request.pid_length > 0 && message->size > 1) { - response->has_pid = true; - if(handle->request.pid_length == 2) { - response->pid = get_bitfield(message->payload, message->size, - PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT); - } else { - response->pid = message->payload[PID_BYTE_INDEX]; - } - - } - - uint8_t payload_index = 1 + handle->request.pid_length; - response->payload_length = MAX(0, message->size - payload_index); - if(response->payload_length > 0) { - memcpy(response->payload, &message->payload[payload_index], - response->payload_length); - } - - if((handle->request.pid_length == 0 && !response->has_pid) - || response->pid == handle->request.pid) { - response->success = true; - response->completed = true; - } else { - response_was_positive = false; - } - } - return response_was_positive; -} - -DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, - DiagnosticRequestHandle* handle, const uint16_t arbitration_id, - const uint8_t data[], const uint8_t size) { - - DiagnosticResponse response = { - arbitration_id: arbitration_id, - success: false, - completed: false - }; - - if(!handle->isotp_send_handle.completed) { - isotp_continue_send(&handle->isotp_shims, - &handle->isotp_send_handle, arbitration_id, data, size); - } else { - uint8_t i; - for(i = 0; i < handle->isotp_receive_handle_count; ++i) { - IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, - &handle->isotp_receive_handles[i], arbitration_id, data, size); - - // TODO as of now we're completing the handle as soon as one - // broadcast response is received....need to hang on for 100ms - if(message.completed) { - if(message.size > 0) { - response.mode = message.payload[0]; - if(handle_negative_response(&message, &response, shims)) { - shims->log("Received a negative response to mode %d on arb ID 0x%x", - response.mode, response.arbitration_id); - handle->success = true; - handle->completed = true; - } else if(handle_positive_response(handle, &message, &response, - shims)) { - shims->log("Received a positive mode %d response on arb ID 0x%x", - response.mode, response.arbitration_id); - handle->success = true; - handle->completed = true; - } else { - shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", - MAX(0, response.mode - MODE_RESPONSE_OFFSET), - response.pid, handle->request.mode, - handle->request.pid); - // TODO just leave handles open until the user decides - // to be done with it - keep a count of valid responses - // received. - } - } else { - shims->log("Received an empty response on arb ID 0x%x", - response.arbitration_id); - } - - if(handle->completed && handle->callback != NULL) { - handle->callback(&response); - } - } - } - } - return response; -} diff --git a/src/obd2/obd2.h b/src/obd2/obd2.h deleted file mode 100644 index bcd6e76..0000000 --- a/src/obd2/obd2.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __OBD2_H__ -#define __OBD2_H__ - -#include -#include -#include - -#define OBD2_FUNCTIONAL_BROADCAST_ID 0x7df -#define OBD2_FUNCTIONAL_RESPONSE_START 0x7e8 -#define OBD2_FUNCTIONAL_RESPONSE_COUNT 8 - -#ifdef __cplusplus -extern "C" { -#endif - -/* Public: Initialize an DiagnosticShims with the given callback functions. - * - * If any callbacks are not to be used, set them to NULL. For documentation of - * the function type signatures, see higher up in this header file. This struct - * is a handy encapsulation used to pass the shims around to the various - * diagnostic_* functions. - * - * Returns a struct with the fields initailized to the callbacks. - */ -DiagnosticShims diagnostic_init_shims(LogShim log, - SendCanMessageShim send_can_message, - SetTimerShim set_timer); - -/* Public: Initiate a diagnostic request and return a handle, ready to completly - * send the request and process the response via - * diagnostic_receive_can_frame(...). - * - * shims - Low-level shims required to send CAN messages, etc. - * request - - * callback - an optional function to be called when the response is receved - * (use NULL if no callback is required). - * - * Returns a handle to be used with diagnostic_receive_can_frame to complete - * sending the request and receive the response. The 'completed' field in the - * returned DiagnosticRequestHandle will be true when the message is completely - * sent. - */ -DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, - DiagnosticRequest* request, DiagnosticResponseReceived callback); - -/* Public: Request a PID from the given arbitration ID, determining the mode - * automatically based on the PID type. - * - * shims - Low-level shims required to send CAN messages, etc. - * pid_request_type - either DIAGNOSTIC_STANDARD_PID (will use mode 0x1 and 1 - * byte PIDs) or DIAGNOSTIC_ENHANCED_PID (will use mode 0x22 and 2 byte - * PIDs) - * arbitration_id - The arbitration ID to send the request to. - * pid - The PID to request from the other node. - * callback - an optional function to be called when the response is receved - * (use NULL if no callback is required). - * - * Returns a handle to be used with diagnostic_receive_can_frame to complete - * sending the request and receive the response. The 'completed' field in the - * returned DiagnosticRequestHandle will be true when the message is completely - * sent. - */ -DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, - uint16_t pid, DiagnosticResponseReceived callback); - -/* Public: Continue to send and receive a single diagnostic request, based on a - * freshly received CAN message. - * - * shims - Low-level shims required to send CAN messages, etc. - * handle - A DiagnosticRequestHandle previously returned by one of the - * diagnostic_request*(..) functions. - * arbitration_id - The arbitration_id of the received CAN message. - * data - The data of the received CAN message. - * size - The size of the data in the received CAN message. - * - * Returns true if the request was completed and response received, or the - * request was otherwise cancelled. Check the 'success' field of the handle to - * see if it was successful. - */ -DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, - DiagnosticRequestHandle* handle, - const uint16_t arbitration_id, const uint8_t data[], - const uint8_t size); - -/* Public: Render a DiagnosticResponse as a string into the given buffer. - * - * TODO implement this - * - * message - the response to convert to a string, for debug logging. - * destination - the target string buffer. - * destination_length - the size of the destination buffer, i.e. the max size - * for the rendered string. - */ -// void diagnostic_response_to_string(const DiagnosticResponse* response, - // char* destination, size_t destination_length); - -#ifdef __cplusplus -} -#endif - -#endif // __OBD2_H__ diff --git a/src/obd2/obd2_types.h b/src/obd2/obd2_types.h deleted file mode 100644 index 15552e8..0000000 --- a/src/obd2/obd2_types.h +++ /dev/null @@ -1,188 +0,0 @@ -#ifndef __OBD2_TYPES_H__ -#define __OBD2_TYPES_H__ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// TODO This isn't true for multi frame messages - we may need to dynamically -// allocate this in the future -#define MAX_OBD2_PAYLOAD_LENGTH 7 -#define MAX_RESPONDING_ECU_COUNT 8 -#define VIN_LENGTH 17 - -/* Private: The four main types of diagnositc requests that determine how the - * request should be parsed and what type of callback should be used. - * - * TODO this may not be used...yet? - */ -typedef enum { - DIAGNOSTIC_REQUEST_TYPE_PID, - DIAGNOSTIC_REQUEST_TYPE_DTC, - DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, - DIAGNOSTIC_REQUEST_TYPE_VIN -} DiagnosticRequestType; - -/* Public: A container for a single diagnostic request. - * - * The only required fields are the arbitration_id and mode. - * - * arbitration_id - The arbitration ID to send the request. - * mode - The OBD-II mode for the request. - * pid - (optional) The PID to request, if the mode requires one. - * pid_length - The length of the PID field, either 1 (standard) or 2 bytes - * (extended). - * payload - (optional) The payload for the request, if the request requires - * one. If payload_length is 0 this field is ignored. - * payload_length - The length of the payload, or 0 if no payload is used. - * type - the type of the request (TODO unused) - */ -typedef struct { - uint16_t arbitration_id; - uint8_t mode; - uint16_t pid; - uint8_t pid_length; - uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; - uint8_t payload_length; - DiagnosticRequestType type; -} DiagnosticRequest; - -/* Public: All possible negative response codes that could be received from a - * requested node. - * - * When a DiagnosticResponse is received and the 'completed' field is true, but - * the 'success' field is false, the 'negative_response_code' field will contain - * one of these values as reported by the requested node. - * - * Thanks to canbushack.com for the list of NRCs. - */ -typedef enum { - NRC_SUCCESS = 0x0, - NRC_SERVICE_NOT_SUPPORTED = 0x11, - NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, - NRC_CONDITIONS_NOT_CORRECT = 0x22, - NRC_REQUEST_OUT_OF_RANGE = 0x31, - NRC_SECURITY_ACCESS_DENIED = 0x33, - NRC_INVALID_KEY = 0x35, - NRC_TOO_MANY_ATTEMPS = 0x36, - NRC_TIME_DELAY_NOT_EXPIRED = 0x37, - NRC_RESPONSE_PENDING = 0x78 -} DiagnosticNegativeResponseCode; - -/* Public: A partially or fully completed response to a diagnostic request. - * - * completed - True if the request is complete - some functions return a - * DiagnosticResponse even when it's only partially completed, so be sure - * to check this field. - * success - True if the request was successful. The value if this - * field isn't valid if 'completed' isn't true. If this is 'false', check - * the negative_response_code field for the reason. - * arbitration_id - The arbitration ID the response was received on. - * mode - The OBD-II mode for the original request. - * has_pid - If this is a response to a PID request, this will be true and the - * 'pid' field will be valid. - * pid - If the request was for a PID, this is the PID echo. Only valid if - * 'has_pid' is true. - * negative_response_code - If the request was not successful, 'success' will be - * false and this will be set to a DiagnosticNegativeResponseCode returned - * by the other node. - * payload - An optional payload for the response - NULL if no payload. - * payload_length - The length of the payload or 0 if none. - */ -typedef struct { - bool completed; - bool success; - uint16_t arbitration_id; - uint8_t mode; - bool has_pid; - uint16_t pid; - DiagnosticNegativeResponseCode negative_response_code; - uint8_t payload[MAX_OBD2_PAYLOAD_LENGTH]; - uint8_t payload_length; -} DiagnosticResponse; - -/* Public: Friendly names for all OBD-II modes. - */ -typedef enum { - OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, - OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, - OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3, - OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4, - // 0x5 is for non-CAN only - // OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5, - OBD2_MODE_TEST_RESULTS = 0x6, - OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7, - OBD2_MODE_CONTROL = 0x8, - OBD2_MODE_VEHICLE_INFORMATION = 0x9, - OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa, - // this one isn't technically in OBD2, but both of the enhanced standards - // have their PID requests at 0x22 - OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 -} DiagnosticMode; - -/* Public the signature for an optional function to be called when a diagnostic - * request is complete, and a response is received or there is a fatal error. - * - * response - the completed DiagnosticResponse. - */ -typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); - -typedef float (*DiagnosticResponseDecoder)(const DiagnosticResponse* response); - -/* Public: A handle for initiating and continuing a single diagnostic request. - * - * A diagnostic request requires one or more CAN messages to be sent, and one - * or more CAN messages to be received before it is completed. This struct - * encapsulates the local state required to track the request while it is in - * progress. - * - * request - The original DiagnosticRequest that this handle was created for. - * completed - True if the request was completed successfully, or was otherwise - * cancelled. - * success - True if the request send and receive process was successful. The - * value if this field isn't valid if 'completed' isn't true. - */ -typedef struct { - DiagnosticRequest request; - bool success; - bool completed; - - // Private - IsoTpShims isotp_shims; - IsoTpSendHandle isotp_send_handle; - IsoTpReceiveHandle isotp_receive_handles[MAX_RESPONDING_ECU_COUNT]; - uint8_t isotp_receive_handle_count; - DiagnosticResponseReceived callback; - // DiagnosticMilStatusReceived mil_status_callback; - // DiagnosticVinReceived vin_callback; -} DiagnosticRequestHandle; - -/* Public: The two major types of PIDs that determine the OBD-II mode and PID - * field length. - */ -typedef enum { - DIAGNOSTIC_STANDARD_PID, - DIAGNOSTIC_ENHANCED_PID -} DiagnosticPidRequestType; - -/* Public: A container for the 3 shim functions used by the library to interact - * with the wider system. - * - * Use the diagnostic_init_shims(...) function to create an instance of this - * struct. - */ -typedef struct { - LogShim log; - SendCanMessageShim send_can_message; - SetTimerShim set_timer; -} DiagnosticShims; - -#ifdef __cplusplus -} -#endif - -#endif // __OBD2_TYPES_H__ diff --git a/src/uds/extras.c b/src/uds/extras.c new file mode 100644 index 0000000..2be6bdd --- /dev/null +++ b/src/uds/extras.c @@ -0,0 +1,39 @@ +#include +#include + +// TODO everything below here is for future work...not critical for now. + +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback) { + // TODO request malfunction indicator light (MIL) status - request mode 1 + // pid 1, parse first bit + DiagnosticRequestHandle handle; + return handle; +} + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback) { + DiagnosticRequestHandle handle; + return handle; +} + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback) { + DiagnosticRequestHandle handle; + return handle; +} + +bool diagnostic_clear_dtc(DiagnosticShims* shims) { + return false; +} + +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback) { + // before calling the callback, split up the received bytes into 1 or 2 byte + // chunks depending on the mode so the final pid list is actual 1 or 2 byte PIDs + // TODO request supported PIDs - request PID 0 and parse 4 bytes in response + DiagnosticRequestHandle handle; + return handle; +} diff --git a/src/uds/extras.h b/src/uds/extras.h new file mode 100644 index 0000000..c59c7ba --- /dev/null +++ b/src/uds/extras.h @@ -0,0 +1,73 @@ +#ifndef __EXTRAS_H__ +#define __EXTRAS_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO everything in here is unused for the moment! + +typedef enum { + POWERTRAIN = 0x0, + CHASSIS = 0x1, + BODY = 0x2, + NETWORK = 0x3 +} DiagnosticTroubleCodeGroup; + +typedef struct { + DiagnosticTroubleCodeGroup group; + uint8_t group_num; + uint8_t code; +} DiagnosticTroubleCode; + + +/* Private: TODO unused for now + */ +typedef enum { + DTC_EMISSIONS, + DTC_DRIVE_CYCLE, + DTC_PERMANENT +} DiagnosticTroubleCodeType; + + +// TODO should we enumerate every OBD-II PID? need conversion formulas, too +typedef struct { + uint16_t pid; + uint8_t bytes_returned; + float min_value; + float max_value; +} DiagnosticParameter; + +typedef void (*DiagnosticMilStatusReceived)(bool malfunction_indicator_status); +typedef void (*DiagnosticVinReceived)(uint8_t vin[]); +typedef void (*DiagnosticTroubleCodesReceived)( + DiagnosticMode mode, DiagnosticTroubleCode* codes); +typedef void (*DiagnosticPidEnumerationReceived)( + const DiagnosticResponse* response, uint16_t* pids); + +DiagnosticRequestHandle diagnostic_request_malfunction_indicator_status( + DiagnosticShims* shims, + DiagnosticMilStatusReceived callback); + +DiagnosticRequestHandle diagnostic_request_vin(DiagnosticShims* shims, + DiagnosticVinReceived callback); + +DiagnosticRequestHandle diagnostic_request_dtc(DiagnosticShims* shims, + DiagnosticTroubleCodeType dtc_type, + DiagnosticTroubleCodesReceived callback); + +bool diagnostic_clear_dtc(DiagnosticShims* shims); + +DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); + +// TODO +float diagnostic_decode_OBD2_pid(DiagnosticResponse* response); + +#ifdef __cplusplus +} +#endif + +#endif // __EXTRAS_H__ diff --git a/src/uds/uds.c b/src/uds/uds.c new file mode 100644 index 0000000..443b05a --- /dev/null +++ b/src/uds/uds.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include + +#define ARBITRATION_ID_OFFSET 0x8 +#define MODE_RESPONSE_OFFSET 0x40 +#define NEGATIVE_RESPONSE_MODE 0x7f +#define MAX_DIAGNOSTIC_PAYLOAD_SIZE 6 +#define MODE_BYTE_INDEX 0 +#define PID_BYTE_INDEX 1 +#define NEGATIVE_RESPONSE_MODE_INDEX 1 +#define NEGATIVE_RESPONSE_NRC_INDEX 2 + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +DiagnosticShims diagnostic_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer) { + DiagnosticShims shims = { + log: log, + send_can_message: send_can_message, + set_timer: set_timer + }; + return shims; +} + +static void setup_receive_handle(DiagnosticRequestHandle* handle) { + if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) { + uint16_t response_id; + for(response_id = 0; + response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { + handle->isotp_receive_handles[response_id] = isotp_receive( + &handle->isotp_shims, OBD2_FUNCTIONAL_RESPONSE_START + response_id, + NULL); + } + handle->isotp_receive_handle_count = OBD2_FUNCTIONAL_RESPONSE_COUNT; + } else { + handle->isotp_receive_handle_count = 1; + handle->isotp_receive_handles[0] = isotp_receive(&handle->isotp_shims, + handle->request.arbitration_id + ARBITRATION_ID_OFFSET, + NULL); + } +} + + +DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback) { + DiagnosticRequestHandle handle = { + request: *request, + callback: callback, + success: false, + completed: false + }; + + uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; + payload[MODE_BYTE_INDEX] = request->mode; + if(request->pid_length > 0) { + set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, + request->pid_length * CHAR_BIT, payload, sizeof(payload)); + } + if(request->payload_length > 0) { + memcpy(&payload[PID_BYTE_INDEX + request->pid_length], + request->payload, request->payload_length); + } + + handle.isotp_shims = isotp_init_shims(shims->log, + shims->send_can_message, + shims->set_timer); + + handle.isotp_send_handle = isotp_send(&handle.isotp_shims, + request->arbitration_id, payload, + 1 + request->payload_length + request->pid_length, + NULL); + if(shims->log != NULL) { + 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", + request->arbitration_id, + request->mode, + request->pid, + request->payload[0], + request->payload[1], + request->payload[2], + request->payload[3], + request->payload[4], + request->payload[5], + request->payload[6], + request->payload_length); + } + + setup_receive_handle(&handle); + + // TODO notes on multi frame: + // TODO what are the timers for exactly? + // + // when sending multi frame, send 1 frame, wait for a response + // if it says send all, send all right away + // if it says flow control, set the time for the next send + // instead of creating a timer with an async callback, add a process_handle + // function that's called repeatedly in the main loop - if it's time to + // send, we do it. so there's a process_handle_send and receive_can_frame + // that are just called continuously from the main loop. it's a waste of a + // few cpu cycles but it may be more natural than callbacks. + // + // what woudl a timer callback look like...it would need to pass the handle + // and that's all. seems like a context void* would be able to capture all + // of the information but arg, memory allocation. look at how it's done in + // the other library again + // + return handle; +} + +DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, + DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + uint16_t pid, DiagnosticResponseReceived callback) { + DiagnosticRequest request = { + arbitration_id: arbitration_id, + mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, + pid: pid, + pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2 + }; + + return diagnostic_request(shims, &request, callback); +} + +static bool handle_negative_response(IsoTpMessage* message, + DiagnosticResponse* response, DiagnosticShims* shims) { + bool response_was_negative = false; + if(response->mode == NEGATIVE_RESPONSE_MODE) { + response_was_negative = true; + if(message->size > NEGATIVE_RESPONSE_MODE_INDEX) { + response->mode = message->payload[NEGATIVE_RESPONSE_MODE_INDEX]; + } + + if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) { + response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX]; + } + + response->success = false; + response->completed = true; + } + return response_was_negative; +} + +static bool handle_positive_response(DiagnosticRequestHandle* handle, + IsoTpMessage* message, DiagnosticResponse* response, + DiagnosticShims* shims) { + bool response_was_positive = false; + if(response->mode == handle->request.mode + MODE_RESPONSE_OFFSET) { + response_was_positive = true; + // hide the "response" version of the mode from the user + // if it matched + response->mode = handle->request.mode; + response->has_pid = false; + if(handle->request.pid_length > 0 && message->size > 1) { + response->has_pid = true; + if(handle->request.pid_length == 2) { + response->pid = get_bitfield(message->payload, message->size, + PID_BYTE_INDEX * CHAR_BIT, sizeof(uint16_t) * CHAR_BIT); + } else { + response->pid = message->payload[PID_BYTE_INDEX]; + } + + } + + uint8_t payload_index = 1 + handle->request.pid_length; + response->payload_length = MAX(0, message->size - payload_index); + if(response->payload_length > 0) { + memcpy(response->payload, &message->payload[payload_index], + response->payload_length); + } + + if((handle->request.pid_length == 0 && !response->has_pid) + || response->pid == handle->request.pid) { + response->success = true; + response->completed = true; + } else { + response_was_positive = false; + } + } + return response_was_positive; +} + +DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, + DiagnosticRequestHandle* handle, const uint16_t arbitration_id, + const uint8_t data[], const uint8_t size) { + + DiagnosticResponse response = { + arbitration_id: arbitration_id, + success: false, + completed: false + }; + + if(!handle->isotp_send_handle.completed) { + isotp_continue_send(&handle->isotp_shims, + &handle->isotp_send_handle, arbitration_id, data, size); + } else { + uint8_t i; + for(i = 0; i < handle->isotp_receive_handle_count; ++i) { + IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, + &handle->isotp_receive_handles[i], arbitration_id, data, size); + + // TODO as of now we're completing the handle as soon as one + // broadcast response is received....need to hang on for 100ms + if(message.completed) { + if(message.size > 0) { + response.mode = message.payload[0]; + if(handle_negative_response(&message, &response, shims)) { + shims->log("Received a negative response to mode %d on arb ID 0x%x", + response.mode, response.arbitration_id); + handle->success = true; + handle->completed = true; + } else if(handle_positive_response(handle, &message, &response, + shims)) { + shims->log("Received a positive mode %d response on arb ID 0x%x", + response.mode, response.arbitration_id); + handle->success = true; + handle->completed = true; + } else { + shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", + MAX(0, response.mode - MODE_RESPONSE_OFFSET), + response.pid, handle->request.mode, + handle->request.pid); + // TODO just leave handles open until the user decides + // to be done with it - keep a count of valid responses + // received. + } + } else { + shims->log("Received an empty response on arb ID 0x%x", + response.arbitration_id); + } + + if(handle->completed && handle->callback != NULL) { + handle->callback(&response); + } + } + } + } + return response; +} diff --git a/src/uds/uds.h b/src/uds/uds.h new file mode 100644 index 0000000..091d3c9 --- /dev/null +++ b/src/uds/uds.h @@ -0,0 +1,102 @@ +#ifndef __UDS_H__ +#define __UDS_H__ + +#include +#include +#include + +#define OBD2_FUNCTIONAL_BROADCAST_ID 0x7df +#define OBD2_FUNCTIONAL_RESPONSE_START 0x7e8 +#define OBD2_FUNCTIONAL_RESPONSE_COUNT 8 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Initialize an DiagnosticShims with the given callback functions. + * + * If any callbacks are not to be used, set them to NULL. For documentation of + * the function type signatures, see higher up in this header file. This struct + * is a handy encapsulation used to pass the shims around to the various + * diagnostic_* functions. + * + * Returns a struct with the fields initailized to the callbacks. + */ +DiagnosticShims diagnostic_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer); + +/* Public: Initiate a diagnostic request and return a handle, ready to completly + * send the request and process the response via + * diagnostic_receive_can_frame(...). + * + * shims - Low-level shims required to send CAN messages, etc. + * request - + * callback - an optional function to be called when the response is receved + * (use NULL if no callback is required). + * + * Returns a handle to be used with diagnostic_receive_can_frame to complete + * sending the request and receive the response. The 'completed' field in the + * returned DiagnosticRequestHandle will be true when the message is completely + * sent. + */ +DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback); + +/* Public: Request a PID from the given arbitration ID, determining the mode + * automatically based on the PID type. + * + * shims - Low-level shims required to send CAN messages, etc. + * pid_request_type - either DIAGNOSTIC_STANDARD_PID (will use mode 0x1 and 1 + * byte PIDs) or DIAGNOSTIC_ENHANCED_PID (will use mode 0x22 and 2 byte + * PIDs) + * arbitration_id - The arbitration ID to send the request to. + * pid - The PID to request from the other node. + * callback - an optional function to be called when the response is receved + * (use NULL if no callback is required). + * + * Returns a handle to be used with diagnostic_receive_can_frame to complete + * sending the request and receive the response. The 'completed' field in the + * returned DiagnosticRequestHandle will be true when the message is completely + * sent. + */ +DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, + DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + uint16_t pid, DiagnosticResponseReceived callback); + +/* Public: Continue to send and receive a single diagnostic request, based on a + * freshly received CAN message. + * + * shims - Low-level shims required to send CAN messages, etc. + * handle - A DiagnosticRequestHandle previously returned by one of the + * diagnostic_request*(..) functions. + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns true if the request was completed and response received, or the + * request was otherwise cancelled. Check the 'success' field of the handle to + * see if it was successful. + */ +DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, + DiagnosticRequestHandle* handle, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size); + +/* Public: Render a DiagnosticResponse as a string into the given buffer. + * + * TODO implement this + * + * message - the response to convert to a string, for debug logging. + * destination - the target string buffer. + * destination_length - the size of the destination buffer, i.e. the max size + * for the rendered string. + */ +// void diagnostic_response_to_string(const DiagnosticResponse* response, + // char* destination, size_t destination_length); + +#ifdef __cplusplus +} +#endif + +#endif // __UDS_H__ diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h new file mode 100644 index 0000000..f8c7ef0 --- /dev/null +++ b/src/uds/uds_types.h @@ -0,0 +1,188 @@ +#ifndef __UDS_TYPES_H__ +#define __UDS_TYPES_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO This isn't true for multi frame messages - we may need to dynamically +// allocate this in the future +#define MAX_UDS_PAYLOAD_LENGTH 7 +#define MAX_RESPONDING_ECU_COUNT 8 +#define VIN_LENGTH 17 + +/* Private: The four main types of diagnositc requests that determine how the + * request should be parsed and what type of callback should be used. + * + * TODO this may not be used...yet? + */ +typedef enum { + DIAGNOSTIC_REQUEST_TYPE_PID, + DIAGNOSTIC_REQUEST_TYPE_DTC, + DIAGNOSTIC_REQUEST_TYPE_MIL_STATUS, + DIAGNOSTIC_REQUEST_TYPE_VIN +} DiagnosticRequestType; + +/* Public: A container for a single diagnostic request. + * + * The only required fields are the arbitration_id and mode. + * + * arbitration_id - The arbitration ID to send the request. + * mode - The OBD-II mode for the request. + * pid - (optional) The PID to request, if the mode requires one. + * pid_length - The length of the PID field, either 1 (standard) or 2 bytes + * (extended). + * payload - (optional) The payload for the request, if the request requires + * one. If payload_length is 0 this field is ignored. + * payload_length - The length of the payload, or 0 if no payload is used. + * type - the type of the request (TODO unused) + */ +typedef struct { + uint16_t arbitration_id; + uint8_t mode; + uint16_t pid; + uint8_t pid_length; + uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; + uint8_t payload_length; + DiagnosticRequestType type; +} DiagnosticRequest; + +/* Public: All possible negative response codes that could be received from a + * requested node. + * + * When a DiagnosticResponse is received and the 'completed' field is true, but + * the 'success' field is false, the 'negative_response_code' field will contain + * one of these values as reported by the requested node. + * + * Thanks to canbushack.com for the list of NRCs. + */ +typedef enum { + NRC_SUCCESS = 0x0, + NRC_SERVICE_NOT_SUPPORTED = 0x11, + NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, + NRC_CONDITIONS_NOT_CORRECT = 0x22, + NRC_REQUEST_OUT_OF_RANGE = 0x31, + NRC_SECURITY_ACCESS_DENIED = 0x33, + NRC_INVALID_KEY = 0x35, + NRC_TOO_MANY_ATTEMPS = 0x36, + NRC_TIME_DELAY_NOT_EXPIRED = 0x37, + NRC_RESPONSE_PENDING = 0x78 +} DiagnosticNegativeResponseCode; + +/* Public: A partially or fully completed response to a diagnostic request. + * + * completed - True if the request is complete - some functions return a + * DiagnosticResponse even when it's only partially completed, so be sure + * to check this field. + * success - True if the request was successful. The value if this + * field isn't valid if 'completed' isn't true. If this is 'false', check + * the negative_response_code field for the reason. + * arbitration_id - The arbitration ID the response was received on. + * mode - The OBD-II mode for the original request. + * has_pid - If this is a response to a PID request, this will be true and the + * 'pid' field will be valid. + * pid - If the request was for a PID, this is the PID echo. Only valid if + * 'has_pid' is true. + * negative_response_code - If the request was not successful, 'success' will be + * false and this will be set to a DiagnosticNegativeResponseCode returned + * by the other node. + * payload - An optional payload for the response - NULL if no payload. + * payload_length - The length of the payload or 0 if none. + */ +typedef struct { + bool completed; + bool success; + uint16_t arbitration_id; + uint8_t mode; + bool has_pid; + uint16_t pid; + DiagnosticNegativeResponseCode negative_response_code; + uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; + uint8_t payload_length; +} DiagnosticResponse; + +/* Public: Friendly names for all OBD-II modes. + */ +typedef enum { + OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST = 0x1, + OBD2_MODE_POWERTRAIN_FREEZE_FRAME_REQUEST = 0x2, + OBD2_MODE_EMISSIONS_DTC_REQUEST = 0x3, + OBD2_MODE_EMISSIONS_DTC_CLEAR = 0x4, + // 0x5 is for non-CAN only + // OBD2_MODE_OXYGEN_SENSOR_TEST = 0x5, + OBD2_MODE_TEST_RESULTS = 0x6, + OBD2_MODE_DRIVE_CYCLE_DTC_REQUEST = 0x7, + OBD2_MODE_CONTROL = 0x8, + OBD2_MODE_VEHICLE_INFORMATION = 0x9, + OBD2_MODE_PERMANENT_DTC_REQUEST = 0xa, + // this one isn't technically in uds, but both of the enhanced standards + // have their PID requests at 0x22 + OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 +} DiagnosticMode; + +/* Public the signature for an optional function to be called when a diagnostic + * request is complete, and a response is received or there is a fatal error. + * + * response - the completed DiagnosticResponse. + */ +typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); + +typedef float (*DiagnosticResponseDecoder)(const DiagnosticResponse* response); + +/* Public: A handle for initiating and continuing a single diagnostic request. + * + * A diagnostic request requires one or more CAN messages to be sent, and one + * or more CAN messages to be received before it is completed. This struct + * encapsulates the local state required to track the request while it is in + * progress. + * + * request - The original DiagnosticRequest that this handle was created for. + * completed - True if the request was completed successfully, or was otherwise + * cancelled. + * success - True if the request send and receive process was successful. The + * value if this field isn't valid if 'completed' isn't true. + */ +typedef struct { + DiagnosticRequest request; + bool success; + bool completed; + + // Private + IsoTpShims isotp_shims; + IsoTpSendHandle isotp_send_handle; + IsoTpReceiveHandle isotp_receive_handles[MAX_RESPONDING_ECU_COUNT]; + uint8_t isotp_receive_handle_count; + DiagnosticResponseReceived callback; + // DiagnosticMilStatusReceived mil_status_callback; + // DiagnosticVinReceived vin_callback; +} DiagnosticRequestHandle; + +/* Public: The two major types of PIDs that determine the OBD-II mode and PID + * field length. + */ +typedef enum { + DIAGNOSTIC_STANDARD_PID, + DIAGNOSTIC_ENHANCED_PID +} DiagnosticPidRequestType; + +/* Public: A container for the 3 shim functions used by the library to interact + * with the wider system. + * + * Use the diagnostic_init_shims(...) function to create an instance of this + * struct. + */ +typedef struct { + LogShim log; + SendCanMessageShim send_can_message; + SetTimerShim set_timer; +} DiagnosticShims; + +#ifdef __cplusplus +} +#endif + +#endif // __UDS_TYPES_H__ diff --git a/tests/common.c b/tests/common.c index f79b8ad..8d7dd50 100644 --- a/tests/common.c +++ b/tests/common.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/tests/test_core.c b/tests/test_core.c index 1ab9409..489e174 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -261,7 +261,7 @@ START_TEST (test_negative_response) END_TEST Suite* testSuite(void) { - Suite* s = suite_create("obd2"); + Suite* s = suite_create("uds"); TCase *tc_core = tcase_create("core"); tcase_add_checked_fixture(tc_core, setup, NULL); tcase_add_test(tc_core, test_send_diag_request); -- cgit 1.2.3-korg From fed7c4cae3d8dd1a53d0b394f6abcfa9f8c30a64 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 20 Jan 2014 16:19:01 -0500 Subject: Add a function to parse the entire payload as a float. --- src/uds/uds.c | 7 +++++++ src/uds/uds.h | 2 ++ tests/test_core.c | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/uds/uds.c b/src/uds/uds.c index 443b05a..b3f2cda 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -241,3 +242,9 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, } return response; } + +float diagnostic_payload_to_float(const DiagnosticResponse* response) { + return bitfield_parse_float(response->payload, + response->payload_length, 0, + response->payload_length * CHAR_BIT, 1.0, 0); +} diff --git a/src/uds/uds.h b/src/uds/uds.h index 091d3c9..78f70e1 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -83,6 +83,8 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); +float diagnostic_payload_to_float(const DiagnosticResponse* response); + /* Public: Render a DiagnosticResponse as a string into the given buffer. * * TODO implement this diff --git a/tests/test_core.c b/tests/test_core.c index 489e174..c37656e 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -260,6 +260,20 @@ START_TEST (test_negative_response) } END_TEST +START_TEST (test_payload_to_float) +{ + uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; + DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, + DIAGNOSTIC_STANDARD_PID, arb_id, 0x2, response_received_handler); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x2, 0x45, 0x12}; + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, + can_data, sizeof(can_data)); + ck_assert_int_eq(diagnostic_payload_to_float(&response), 0x4512); +} +END_TEST + Suite* testSuite(void) { Suite* s = suite_create("uds"); TCase *tc_core = tcase_create("core"); @@ -275,6 +289,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_missing_pid); tcase_add_test(tc_core, test_wrong_pid_then_right_completes); tcase_add_test(tc_core, test_negative_response); + tcase_add_test(tc_core, test_payload_to_float); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From bd9026391e3c9c107cc6247fbad40da81fd1c2f9 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Mon, 20 Jan 2014 21:41:25 -0500 Subject: Don't try and rx broadcast response on all handles after one completes. --- src/uds/uds.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index b3f2cda..5d41582 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -204,8 +204,6 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handles[i], arbitration_id, data, size); - // TODO as of now we're completing the handle as soon as one - // broadcast response is received....need to hang on for 100ms if(message.completed) { if(message.size > 0) { response.mode = message.payload[0]; @@ -237,6 +235,10 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(handle->completed && handle->callback != NULL) { handle->callback(&response); } + + // TODO as of now we're completing the handle as soon as one + // broadcast response is received....need to hang on for 100ms + break; } } } -- cgit 1.2.3-korg From 4d19569cb3690edad012ed8f3b33cff142274881 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 24 Jan 2014 20:45:42 -0500 Subject: Wrap lines at 80 characters. --- src/uds/uds.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 5d41582..1a52db1 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -36,7 +36,8 @@ static void setup_receive_handle(DiagnosticRequestHandle* handle) { for(response_id = 0; response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { handle->isotp_receive_handles[response_id] = isotp_receive( - &handle->isotp_shims, OBD2_FUNCTIONAL_RESPONSE_START + response_id, + &handle->isotp_shims, + OBD2_FUNCTIONAL_RESPONSE_START + response_id, NULL); } handle->isotp_receive_handle_count = OBD2_FUNCTIONAL_RESPONSE_COUNT; @@ -48,7 +49,6 @@ static void setup_receive_handle(DiagnosticRequestHandle* handle) { } } - DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { @@ -137,7 +137,8 @@ static bool handle_negative_response(IsoTpMessage* message, } if(message->size > NEGATIVE_RESPONSE_NRC_INDEX) { - response->negative_response_code = message->payload[NEGATIVE_RESPONSE_NRC_INDEX]; + response->negative_response_code = + message->payload[NEGATIVE_RESPONSE_NRC_INDEX]; } response->success = false; @@ -202,7 +203,8 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, uint8_t i; for(i = 0; i < handle->isotp_receive_handle_count; ++i) { IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, - &handle->isotp_receive_handles[i], arbitration_id, data, size); + &handle->isotp_receive_handles[i], arbitration_id, data, + size); if(message.completed) { if(message.size > 0) { @@ -212,8 +214,8 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, response.mode, response.arbitration_id); handle->success = true; handle->completed = true; - } else if(handle_positive_response(handle, &message, &response, - shims)) { + } else if(handle_positive_response(handle, &message, + &response, shims)) { shims->log("Received a positive mode %d response on arb ID 0x%x", response.mode, response.arbitration_id); handle->success = true; -- cgit 1.2.3-korg From c706e3a3896dbd9fb0477a715b1aab6ba28ed7ac Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 24 Jan 2014 20:50:39 -0500 Subject: Remove a few resolved TODOs. --- src/uds/uds.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 1a52db1..3295aaa 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -225,9 +225,6 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, MAX(0, response.mode - MODE_RESPONSE_OFFSET), response.pid, handle->request.mode, handle->request.pid); - // TODO just leave handles open until the user decides - // to be done with it - keep a count of valid responses - // received. } } else { shims->log("Received an empty response on arb ID 0x%x", @@ -238,8 +235,6 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, handle->callback(&response); } - // TODO as of now we're completing the handle as soon as one - // broadcast response is received....need to hang on for 100ms break; } } -- cgit 1.2.3-korg From 295f3267b2c41bf28d054f2f43b528be38daac1c Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 24 Jan 2014 21:21:26 -0500 Subject: Automatically set pid length for outgoing requests if not specified. --- src/uds/uds.c | 25 ++++++++++++++++++++----- src/uds/uds_types.h | 8 ++++++-- tests/test_core.c | 26 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 3295aaa..6a4ce45 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -49,6 +49,18 @@ static void setup_receive_handle(DiagnosticRequestHandle* handle) { } } +static uint16_t autoset_pid_length(uint8_t mode, uint16_t pid, + uint8_t pid_length) { + if(pid_length == 0) { + if(pid > 0xffff || mode > 10) { + pid_length = 2; + } else { + pid_length = 1; + } + } + return pid_length; +} + DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { @@ -60,7 +72,10 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; payload[MODE_BYTE_INDEX] = request->mode; - if(request->pid_length > 0) { + if(request->has_pid) { + request->pid_length = autoset_pid_length(request->mode, + request->pid, request->pid_length); + handle.request.pid_length = request->pid_length; set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, request->pid_length * CHAR_BIT, payload, sizeof(payload)); } @@ -120,8 +135,8 @@ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, DiagnosticRequest request = { arbitration_id: arbitration_id, mode: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 0x1 : 0x22, - pid: pid, - pid_length: pid_request_type == DIAGNOSTIC_STANDARD_PID ? 1 : 2 + has_pid: true, + pid: pid }; return diagnostic_request(shims, &request, callback); @@ -157,7 +172,7 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, // if it matched response->mode = handle->request.mode; response->has_pid = false; - if(handle->request.pid_length > 0 && message->size > 1) { + if(handle->request.has_pid && message->size > 1) { response->has_pid = true; if(handle->request.pid_length == 2) { response->pid = get_bitfield(message->payload, message->size, @@ -175,7 +190,7 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, response->payload_length); } - if((handle->request.pid_length == 0 && !response->has_pid) + if((!handle->request.has_pid && !response->has_pid) || response->pid == handle->request.pid) { response->success = true; response->completed = true; diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index f8c7ef0..88c7ce4 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -33,9 +33,12 @@ typedef enum { * * arbitration_id - The arbitration ID to send the request. * mode - The OBD-II mode for the request. - * pid - (optional) The PID to request, if the mode requires one. + * has_pid - (optional) If the requests uses a PID, this should be true. + * pid - (optional) The PID to request, if the mode requires one. has_pid must + * be true. * pid_length - The length of the PID field, either 1 (standard) or 2 bytes - * (extended). + * (extended). If 0, it will be set automatically based on the request + * mode. * payload - (optional) The payload for the request, if the request requires * one. If payload_length is 0 this field is ignored. * payload_length - The length of the payload, or 0 if no payload is used. @@ -44,6 +47,7 @@ typedef enum { typedef struct { uint16_t arbitration_id; uint8_t mode; + bool has_pid; uint16_t pid; uint8_t pid_length; uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; diff --git a/tests/test_core.c b/tests/test_core.c index c37656e..9f118fc 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -122,6 +122,31 @@ START_TEST (test_send_diag_request) } END_TEST +START_TEST (test_autoset_pid_length) +{ + uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; + diagnostic_request_pid(&SHIMS, DIAGNOSTIC_STANDARD_PID, arb_id, 0x2, + response_received_handler); + + ck_assert_int_eq(last_can_frame_sent_arb_id, arb_id); + ck_assert_int_eq(last_can_payload_sent[1], 0x1); + ck_assert_int_eq(last_can_payload_sent[2], 0x2); + ck_assert_int_eq(last_can_payload_size, 3); + + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: 0x22, + has_pid: true, + pid: 2 + }; + diagnostic_request(&SHIMS, &request, response_received_handler); + + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); +ck_assert_int_eq(last_can_payload_size, 4); +} +END_TEST + START_TEST (test_request_pid_standard) { uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; @@ -282,6 +307,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_send_functional_request); tcase_add_test(tc_core, test_send_diag_request_with_payload); tcase_add_test(tc_core, test_receive_wrong_arb_id); + tcase_add_test(tc_core, test_autoset_pid_length); tcase_add_test(tc_core, test_request_pid_standard); tcase_add_test(tc_core, test_request_pid_enhanced); tcase_add_test(tc_core, test_wrong_mode_response); -- cgit 1.2.3-korg From e961e6b49bc302988a0d6e47763a1f83e63ed065 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 24 Jan 2014 22:10:51 -0500 Subject: Add calculations for many OBD-II PID values. --- src/uds/extras.h | 2 -- src/uds/uds.c | 37 +++++++++++++++++++++++++++++++++++++ src/uds/uds.h | 2 ++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/uds/extras.h b/src/uds/extras.h index c59c7ba..126e5d4 100644 --- a/src/uds/extras.h +++ b/src/uds/extras.h @@ -63,8 +63,6 @@ bool diagnostic_clear_dtc(DiagnosticShims* shims); DiagnosticRequestHandle diagnostic_enumerate_pids(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticPidEnumerationReceived callback); -// TODO -float diagnostic_decode_OBD2_pid(DiagnosticResponse* response); #ifdef __cplusplus } diff --git a/src/uds/uds.c b/src/uds/uds.c index 6a4ce45..ff35b24 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -262,3 +262,40 @@ float diagnostic_payload_to_float(const DiagnosticResponse* response) { response->payload_length, 0, response->payload_length * CHAR_BIT, 1.0, 0); } + +/* Public: + * + * Functions pulled from http://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01 + */ +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { + // handles on the single number values, not the bit encoded ones + switch(response->pid) { + case 0xa: + return response->payload[0] * 3; + case 0xc: + return (response->payload[0] * 256 + response->payload[1]) / 4.0; + case 0xd: + case 0x33: + case 0xb: + return response->payload[0]; + case 0x10: + return (response->payload[0] * 256 + response->payload[1]) / 100.0; + case 0x11: + case 0x2f: + case 0x45: + case 0x4c: + case 0x52: + case 0x5a: + case 0x4: + return response->payload[0] * 100.0 / 255.0; + case 0x46: + case 0x5c: + case 0xf: + case 0x5: + return response->payload[0] - 40; + case 0x62: + return response->payload[0] - 125; + default: + return 0; + } +} diff --git a/src/uds/uds.h b/src/uds/uds.h index 78f70e1..a4761ca 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -97,6 +97,8 @@ float diagnostic_payload_to_float(const DiagnosticResponse* response); // void diagnostic_response_to_string(const DiagnosticResponse* response, // char* destination, size_t destination_length); +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response); + #ifdef __cplusplus } #endif -- cgit 1.2.3-korg From 85c06482daca518981b2b0c1d5661b10a8d5c379 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 10:44:03 -0500 Subject: Display mode in more recognizable hex format in log messages. --- src/uds/uds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index ff35b24..f38c6aa 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -225,13 +225,13 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(message.size > 0) { response.mode = message.payload[0]; if(handle_negative_response(&message, &response, shims)) { - shims->log("Received a negative response to mode %d on arb ID 0x%x", + shims->log("Received a negative response to mode 0x%x on arb ID 0x%x", response.mode, response.arbitration_id); handle->success = true; handle->completed = true; } else if(handle_positive_response(handle, &message, &response, shims)) { - shims->log("Received a positive mode %d response on arb ID 0x%x", + shims->log("Received a positive mode 0x%x response on arb ID 0x%x", response.mode, response.arbitration_id); handle->success = true; handle->completed = true; -- cgit 1.2.3-korg From 2e37a1f1000d2ce827a028206e6b42b10536e7b3 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 18:06:40 -0500 Subject: Add an option to pad outgoing CAN frames to 8 bytes (on by default). Fixed #3. --- deps/isotp-c | 2 +- src/uds/uds.c | 3 ++- src/uds/uds_types.h | 5 ++++ tests/test_core.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index 513d8c8..c01a88b 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 513d8c8d7089960618a1fa00a71442dc39294588 +Subproject commit c01a88ba1e56d455c7187a52a5e244500a0d6b0f diff --git a/src/uds/uds.c b/src/uds/uds.c index f38c6aa..9fec9a8 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -87,6 +87,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, handle.isotp_shims = isotp_init_shims(shims->log, shims->send_can_message, shims->set_timer); + handle.isotp_shims.frame_padding = !request->no_frame_padding; handle.isotp_send_handle = isotp_send(&handle.isotp_shims, request->arbitration_id, payload, @@ -121,7 +122,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, // that are just called continuously from the main loop. it's a waste of a // few cpu cycles but it may be more natural than callbacks. // - // what woudl a timer callback look like...it would need to pass the handle + // what would a timer callback look like...it would need to pass the handle // and that's all. seems like a context void* would be able to capture all // of the information but arg, memory allocation. look at how it's done in // the other library again diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index 88c7ce4..10d15d6 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -42,6 +42,10 @@ typedef enum { * payload - (optional) The payload for the request, if the request requires * one. If payload_length is 0 this field is ignored. * payload_length - The length of the payload, or 0 if no payload is used. + * no_frame_padding - false if sent CAN payloads should *not* be padded out to a + * full 8 byte CAN frame. Many ECUs require this, but others require the + * size of the CAN message to only be the actual data. By default padding + * is enabled (so this struct value can default to 0). * type - the type of the request (TODO unused) */ typedef struct { @@ -52,6 +56,7 @@ typedef struct { uint8_t pid_length; uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; uint8_t payload_length; + bool no_frame_padding; DiagnosticRequestType type; } DiagnosticRequest; diff --git a/tests/test_core.c b/tests/test_core.c index 9f118fc..815954b 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -40,7 +40,8 @@ START_TEST (test_send_diag_request_with_payload) arbitration_id: 0x100, mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST, payload: {0x12, 0x34}, - payload_length: 2 + payload_length: 2, + no_frame_padding: true }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); @@ -60,7 +61,8 @@ START_TEST (test_send_functional_request) { DiagnosticRequest request = { arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID, - mode: OBD2_MODE_EMISSIONS_DTC_REQUEST + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: true }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); @@ -91,7 +93,23 @@ START_TEST (test_send_functional_request) } END_TEST -START_TEST (test_send_diag_request) +START_TEST (test_sent_message_no_padding) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: true + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(handle.completed); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_size, 2); +} +END_TEST + +START_TEST (test_sent_message_is_padded_by_default) { DiagnosticRequest request = { arbitration_id: 0x100, @@ -100,6 +118,38 @@ START_TEST (test_send_diag_request) DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); + fail_if(handle.completed); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_size, 8); +} +END_TEST + +START_TEST (test_sent_message_is_padded) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: false + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + fail_if(handle.completed); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_size, 8); +} +END_TEST + +START_TEST (test_send_diag_request) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: true + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + fail_if(handle.completed); ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); ck_assert_int_eq(last_can_payload_sent[1], request.mode); @@ -131,19 +181,22 @@ START_TEST (test_autoset_pid_length) ck_assert_int_eq(last_can_frame_sent_arb_id, arb_id); ck_assert_int_eq(last_can_payload_sent[1], 0x1); ck_assert_int_eq(last_can_payload_sent[2], 0x2); - ck_assert_int_eq(last_can_payload_size, 3); + // padding is on for the diagnostic_request_pid helper function - if you + // need to turn it off, use the more manual diagnostic_request(...) + ck_assert_int_eq(last_can_payload_size, 8); DiagnosticRequest request = { arbitration_id: 0x100, mode: 0x22, has_pid: true, - pid: 2 + pid: 2, + no_frame_padding: true }; diagnostic_request(&SHIMS, &request, response_received_handler); ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); ck_assert_int_eq(last_can_payload_sent[1], request.mode); -ck_assert_int_eq(last_can_payload_size, 4); + ck_assert_int_eq(last_can_payload_size, 4); } END_TEST @@ -303,6 +356,9 @@ Suite* testSuite(void) { Suite* s = suite_create("uds"); TCase *tc_core = tcase_create("core"); tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_sent_message_no_padding); + tcase_add_test(tc_core, test_sent_message_is_padded); + tcase_add_test(tc_core, test_sent_message_is_padded_by_default); tcase_add_test(tc_core, test_send_diag_request); tcase_add_test(tc_core, test_send_functional_request); tcase_add_test(tc_core, test_send_diag_request_with_payload); -- cgit 1.2.3-korg From 2ec5044b09a9c5ff9ffa1531371cc5095e278d3d Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 18:35:24 -0500 Subject: Clarify when we are using int vs float and move decoders up a level. Fixed #4 - the Decoder function type signature actually belonged one level higher, in the application, since there's no capability of actually using it in this library at the moment. --- src/uds/uds.c | 14 +++++--------- src/uds/uds.h | 17 +++++++++++++++-- src/uds/uds_types.h | 4 +--- tests/test_core.c | 6 +++--- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 9fec9a8..412f077 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -258,17 +258,13 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, return response; } -float diagnostic_payload_to_float(const DiagnosticResponse* response) { - return bitfield_parse_float(response->payload, - response->payload_length, 0, - response->payload_length * CHAR_BIT, 1.0, 0); +int diagnostic_payload_to_integer(const DiagnosticResponse* response) { + return get_bitfield(response->payload, response->payload_length, 0, + response->payload_length * CHAR_BIT); } -/* Public: - * - * Functions pulled from http://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01 - */ -float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, + int parsed_payload) { // handles on the single number values, not the bit encoded ones switch(response->pid) { case 0xa: diff --git a/src/uds/uds.h b/src/uds/uds.h index a4761ca..cb13233 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -83,7 +83,11 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, const uint16_t arbitration_id, const uint8_t data[], const uint8_t size); -float diagnostic_payload_to_float(const DiagnosticResponse* response); +/* Public: Parse the entier payload of the reponse as a single integer. + * + * response - the received DiagnosticResponse. + */ +int diagnostic_payload_to_integer(const DiagnosticResponse* response); /* Public: Render a DiagnosticResponse as a string into the given buffer. * @@ -97,7 +101,16 @@ float diagnostic_payload_to_float(const DiagnosticResponse* response); // void diagnostic_response_to_string(const DiagnosticResponse* response, // char* destination, size_t destination_length); -float diagnostic_decode_obd2_pid(const DiagnosticResponse* response); +/* Public: For many OBD-II PIDs with a numerical result, translate a diagnostic + * response payload into a meaningful number using the standard formulas. + * + * Functions pulled from http://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01 + * + * Returns the translated value or 0 if the PID is not in the OBD-II standard or + * does not use a numerical value (e.g. VIN). + */ +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, + int parsed_payload); #ifdef __cplusplus } diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index 10d15d6..a71d53c 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -133,15 +133,13 @@ typedef enum { OBD2_MODE_ENHANCED_DIAGNOSTIC_REQUEST = 0x22 } DiagnosticMode; -/* Public the signature for an optional function to be called when a diagnostic +/* Public: The signature for an optional function to be called when a diagnostic * request is complete, and a response is received or there is a fatal error. * * response - the completed DiagnosticResponse. */ typedef void (*DiagnosticResponseReceived)(const DiagnosticResponse* response); -typedef float (*DiagnosticResponseDecoder)(const DiagnosticResponse* response); - /* Public: A handle for initiating and continuing a single diagnostic request. * * A diagnostic request requires one or more CAN messages to be sent, and one diff --git a/tests/test_core.c b/tests/test_core.c index 815954b..30d19f4 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -338,7 +338,7 @@ START_TEST (test_negative_response) } END_TEST -START_TEST (test_payload_to_float) +START_TEST (test_payload_to_integer) { uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, @@ -348,7 +348,7 @@ START_TEST (test_payload_to_float) const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x2, 0x45, 0x12}; DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); - ck_assert_int_eq(diagnostic_payload_to_float(&response), 0x4512); + ck_assert_int_eq(diagnostic_payload_to_integer(&response), 0x4512); } END_TEST @@ -371,7 +371,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_missing_pid); tcase_add_test(tc_core, test_wrong_pid_then_right_completes); tcase_add_test(tc_core, test_negative_response); - tcase_add_test(tc_core, test_payload_to_float); + tcase_add_test(tc_core, test_payload_to_integer); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From 2d59b8750a91bed55c8a7ee8caf6eb28c9091c05 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 19:02:16 -0500 Subject: Clean up log output if a diag request has no payload. --- src/uds/uds.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 412f077..18f1147 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -94,18 +94,27 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, 1 + request->payload_length + request->pid_length, NULL); if(shims->log != NULL) { - 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", + char message[128] = {0}; + int bytes_used = snprintf(message, sizeof(message), + "Sending diagnostic request: arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", request->arbitration_id, request->mode, - request->pid, - request->payload[0], - request->payload[1], - request->payload[2], - request->payload[3], - request->payload[4], - request->payload[5], - request->payload[6], - request->payload_length); + request->pid); + int remaining_space = sizeof(message) - bytes_used; + if(request->payload_length > 0) { + snprintf(message + bytes_used, remaining_space, + "payload: 0x%02x%02x%02x%02x%02x%02x%02x", + request->payload[0], + request->payload[1], + request->payload[2], + request->payload[3], + request->payload[4], + request->payload[5], + request->payload[6]); + } else { + snprintf(message + bytes_used, remaining_space, "no payload"); + } + shims->log(message); } setup_receive_handle(&handle); -- cgit 1.2.3-korg From 2a5be4ab15def6c876c7f70d0fa60cf6079166e7 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 22:15:31 -0500 Subject: Add helper functions to render requests and responses as strings. --- src/uds/uds.c | 105 +++++++++++++++++++++++++++++++++++++++++----------------- src/uds/uds.h | 16 ++++++--- 2 files changed, 86 insertions(+), 35 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 18f1147..400eedf 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -94,27 +94,9 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, 1 + request->payload_length + request->pid_length, NULL); if(shims->log != NULL) { - char message[128] = {0}; - int bytes_used = snprintf(message, sizeof(message), - "Sending diagnostic request: arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", - request->arbitration_id, - request->mode, - request->pid); - int remaining_space = sizeof(message) - bytes_used; - if(request->payload_length > 0) { - snprintf(message + bytes_used, remaining_space, - "payload: 0x%02x%02x%02x%02x%02x%02x%02x", - request->payload[0], - request->payload[1], - request->payload[2], - request->payload[3], - request->payload[4], - request->payload[5], - request->payload[6]); - } else { - snprintf(message + bytes_used, remaining_space, "no payload"); - } - shims->log(message); + char request_string[128] = {0}; + diagnostic_request_to_string(request, request_string, sizeof(request_string)); + shims->log("Sending diagnostic request: %s", request_string); } setup_receive_handle(&handle); @@ -235,25 +217,39 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(message.size > 0) { response.mode = message.payload[0]; if(handle_negative_response(&message, &response, shims)) { - shims->log("Received a negative response to mode 0x%x on arb ID 0x%x", - response.mode, response.arbitration_id); + if(shims->log != NULL) { + char response_string[128] = {0}; + diagnostic_response_to_string(&response, response_string, sizeof(response_string)); + shims->log("Received a negative response: %s", response_string); + } + handle->success = true; handle->completed = true; } else if(handle_positive_response(handle, &message, &response, shims)) { - shims->log("Received a positive mode 0x%x response on arb ID 0x%x", - response.mode, response.arbitration_id); + if(shims->log != NULL) { + char response_string[128] = {0}; + diagnostic_response_to_string(&response, response_string, sizeof(response_string)); + shims->log("Received a positive response: %s", response_string); + } + handle->success = true; handle->completed = true; } else { - shims->log("Response was for a mode 0x%x request (pid 0x%x), not our mode 0x%x request (pid 0x%x)", - MAX(0, response.mode - MODE_RESPONSE_OFFSET), - response.pid, handle->request.mode, - handle->request.pid); + if(shims->log != NULL) { + char response_string[128] = {0}; + diagnostic_response_to_string(&response, response_string, sizeof(response_string)); + shims->log("Expected a mode 0x%x response to pid 0x%x but received: %s", + MAX(0, response.mode - MODE_RESPONSE_OFFSET), + response.pid, + response_string); + } } } else { - shims->log("Received an empty response on arb ID 0x%x", - response.arbitration_id); + if(shims->log != NULL) { + shims->log("Received an empty response on arb ID 0x%x", + response.arbitration_id); + } } if(handle->completed && handle->callback != NULL) { @@ -305,3 +301,50 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, return 0; } } + +void diagnostic_response_to_string(const DiagnosticResponse* response, + char* destination, size_t destination_length) { + int bytes_used = snprintf(destination, destination_length, + "arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", + response->arbitration_id, + response->mode, + response->pid); + int remaining_space = destination_length - bytes_used; + if(response->payload_length > 0) { + snprintf(destination + bytes_used, remaining_space, + "payload: 0x%02x%02x%02x%02x%02x%02x%02x", + response->payload[0], + response->payload[1], + response->payload[2], + response->payload[3], + response->payload[4], + response->payload[5], + response->payload[6]); + } else { + snprintf(destination + bytes_used, remaining_space, "no payload"); + } +} + +void diagnostic_request_to_string(const DiagnosticRequest* request, + char* destination, size_t destination_length) { + int bytes_used = snprintf(destination, destination_length, + "arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", + request->arbitration_id, + request->mode, + request->pid); + int remaining_space = destination_length - bytes_used; + if(request->payload_length > 0) { + snprintf(destination + bytes_used, remaining_space, + "payload: 0x%02x%02x%02x%02x%02x%02x%02x", + request->payload[0], + request->payload[1], + request->payload[2], + request->payload[3], + request->payload[4], + request->payload[5], + request->payload[6]); + } else { + snprintf(destination + bytes_used, remaining_space, "no payload"); + } +} + diff --git a/src/uds/uds.h b/src/uds/uds.h index cb13233..3389adf 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -91,15 +91,23 @@ int diagnostic_payload_to_integer(const DiagnosticResponse* response); /* Public: Render a DiagnosticResponse as a string into the given buffer. * - * TODO implement this + * response - the response to convert to a string, for debug logging. + * destination - the target string buffer. + * destination_length - the size of the destination buffer, i.e. the max size + * for the rendered string. + */ +void diagnostic_response_to_string(const DiagnosticResponse* response, + char* destination, size_t destination_length); + +/* Public: Render a DiagnosticRequest as a string into the given buffer. * - * message - the response to convert to a string, for debug logging. + * request - the request to convert to a string, for debug logging. * destination - the target string buffer. * destination_length - the size of the destination buffer, i.e. the max size * for the rendered string. */ -// void diagnostic_response_to_string(const DiagnosticResponse* response, - // char* destination, size_t destination_length); +void diagnostic_request_to_string(const DiagnosticRequest* request, + char* destination, size_t destination_length); /* Public: For many OBD-II PIDs with a numerical result, translate a diagnostic * response payload into a meaningful number using the standard formulas. -- cgit 1.2.3-korg From 14dc82b42c45ac58a1030f6c2c27952bb90c1648 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 22:41:21 -0500 Subject: Show negative response codes in log output. --- src/uds/uds.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 400eedf..5a45c0c 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -305,13 +305,26 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, void diagnostic_response_to_string(const DiagnosticResponse* response, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", + "arb_id: 0x%02x, mode: 0x%x, ", response->arbitration_id, - response->mode, - response->pid); - int remaining_space = destination_length - bytes_used; + response->mode); + + if(response->has_pid) { + bytes_used += snprintf(destination + bytes_used, + destination_length - bytes_used, + "pid: 0x%x, ", + response->pid); + } + + if(!response->success) { + bytes_used += snprintf(destination + bytes_used, + destination_length - bytes_used, + "negative response code: 0x%x, ", + response->negative_response_code); + } + if(response->payload_length > 0) { - snprintf(destination + bytes_used, remaining_space, + snprintf(destination + bytes_used, destination_length - bytes_used, "payload: 0x%02x%02x%02x%02x%02x%02x%02x", response->payload[0], response->payload[1], @@ -321,7 +334,8 @@ void diagnostic_response_to_string(const DiagnosticResponse* response, response->payload[5], response->payload[6]); } else { - snprintf(destination + bytes_used, remaining_space, "no payload"); + snprintf(destination + bytes_used, destination_length - bytes_used, + "no payload"); } } -- cgit 1.2.3-korg From 1d44ff2a0d14f8955a4efc26664d049150a8a18b Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Feb 2014 22:43:05 -0500 Subject: Only include PID in log output if request has one. --- src/uds/uds.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 5a45c0c..88d56bc 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -342,10 +342,17 @@ void diagnostic_response_to_string(const DiagnosticResponse* response, void diagnostic_request_to_string(const DiagnosticRequest* request, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%02x, mode: 0x%x, pid: 0x%x, ", + "arb_id: 0x%02x, mode: 0x%x, ", request->arbitration_id, - request->mode, - request->pid); + request->mode); + + if(request->has_pid) { + bytes_used += snprintf(destination + bytes_used, + destination_length - bytes_used, + "pid: 0x%x, ", + request->pid); + } + int remaining_space = destination_length - bytes_used; if(request->payload_length > 0) { snprintf(destination + bytes_used, remaining_space, -- cgit 1.2.3-korg From 96160fc20d2c939e73773f3fbec2f2fd9482dcf4 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Tue, 18 Feb 2014 17:29:08 -0500 Subject: Shorten the negative response code log message. --- src/uds/uds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 88d56bc..b35e241 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -319,7 +319,7 @@ void diagnostic_response_to_string(const DiagnosticResponse* response, if(!response->success) { bytes_used += snprintf(destination + bytes_used, destination_length - bytes_used, - "negative response code: 0x%x, ", + "nrc: 0x%x, ", response->negative_response_code); } -- cgit 1.2.3-korg From 57e39cc9e9840f4d17323ce8cfe1a7c20cf2d3a5 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 19 Feb 2014 12:29:35 -0500 Subject: Leave payload parsing to applications using this library. --- src/uds/uds.c | 3 +-- src/uds/uds.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index b35e241..1bb9ef8 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -268,8 +268,7 @@ int diagnostic_payload_to_integer(const DiagnosticResponse* response) { response->payload_length * CHAR_BIT); } -float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, - int parsed_payload) { +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { // handles on the single number values, not the bit encoded ones switch(response->pid) { case 0xa: diff --git a/src/uds/uds.h b/src/uds/uds.h index 3389adf..0d40561 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -117,8 +117,7 @@ void diagnostic_request_to_string(const DiagnosticRequest* request, * Returns the translated value or 0 if the PID is not in the OBD-II standard or * does not use a numerical value (e.g. VIN). */ -float diagnostic_decode_obd2_pid(const DiagnosticResponse* response, - int parsed_payload); +float diagnostic_decode_obd2_pid(const DiagnosticResponse* response); #ifdef __cplusplus } -- cgit 1.2.3-korg From ce9aaed64f1c180c5d39356abb08cd86ae06b3c4 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 19 Feb 2014 12:54:03 -0500 Subject: Quiet some misleading log messages. --- src/uds/uds.c | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 1bb9ef8..4e23744 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -175,17 +175,17 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, } - uint8_t payload_index = 1 + handle->request.pid_length; - response->payload_length = MAX(0, message->size - payload_index); - if(response->payload_length > 0) { - memcpy(response->payload, &message->payload[payload_index], - response->payload_length); - } - if((!handle->request.has_pid && !response->has_pid) || response->pid == handle->request.pid) { response->success = true; response->completed = true; + + uint8_t payload_index = 1 + handle->request.pid_length; + response->payload_length = MAX(0, message->size - payload_index); + if(response->payload_length > 0) { + memcpy(response->payload, &message->payload[payload_index], + response->payload_length); + } } else { response_was_positive = false; } @@ -216,34 +216,16 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(message.completed) { if(message.size > 0) { response.mode = message.payload[0]; - if(handle_negative_response(&message, &response, shims)) { + if(handle_negative_response(&message, &response, shims) || + handle_positive_response(handle, &message, &response, shims)) { if(shims->log != NULL) { char response_string[128] = {0}; diagnostic_response_to_string(&response, response_string, sizeof(response_string)); - shims->log("Received a negative response: %s", response_string); + shims->log("Diagnostic response received: %s", response_string); } handle->success = true; handle->completed = true; - } else if(handle_positive_response(handle, &message, - &response, shims)) { - if(shims->log != NULL) { - char response_string[128] = {0}; - diagnostic_response_to_string(&response, response_string, sizeof(response_string)); - shims->log("Received a positive response: %s", response_string); - } - - handle->success = true; - handle->completed = true; - } else { - if(shims->log != NULL) { - char response_string[128] = {0}; - diagnostic_response_to_string(&response, response_string, sizeof(response_string)); - shims->log("Expected a mode 0x%x response to pid 0x%x but received: %s", - MAX(0, response.mode - MODE_RESPONSE_OFFSET), - response.pid, - response_string); - } } } else { if(shims->log != NULL) { -- cgit 1.2.3-korg From 9a943761ae920653dd8fe6e7fedfc58bd50c7938 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 26 Feb 2014 16:34:35 -0500 Subject: Optionally split up preparing a diag request and actually sending it. --- src/uds/uds.c | 83 +++++++++++++++++++++++++++++++++++-------------------- src/uds/uds.h | 32 +++++++++++++++++++-- tests/test_core.c | 69 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 145 insertions(+), 39 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 4e23744..7303677 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -61,7 +61,45 @@ static uint16_t autoset_pid_length(uint8_t mode, uint16_t pid, return pid_length; } -DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, +static void send_diagnostic_request(DiagnosticShims* shims, + DiagnosticRequestHandle* handle) { + uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; + payload[MODE_BYTE_INDEX] = handle->request.mode; + if(handle->request.has_pid) { + handle->request.pid_length = autoset_pid_length(handle->request.mode, + handle->request.pid, handle->request.pid_length); + handle->request.pid_length = handle->request.pid_length; + set_bitfield(handle->request.pid, PID_BYTE_INDEX * CHAR_BIT, + handle->request.pid_length * CHAR_BIT, payload, + sizeof(payload)); + } + + if(handle->request.payload_length > 0) { + memcpy(&payload[PID_BYTE_INDEX + handle->request.pid_length], + handle->request.payload, handle->request.payload_length); + } + + handle->isotp_send_handle = isotp_send(&handle->isotp_shims, + handle->request.arbitration_id, payload, + 1 + handle->request.payload_length + handle->request.pid_length, + NULL); + if(shims->log != NULL) { + char request_string[128] = {0}; + diagnostic_request_to_string(&handle->request, request_string, + sizeof(request_string)); + shims->log("Sending diagnostic request: %s", request_string); + } +} + +void start_diagnostic_request(DiagnosticShims* shims, + DiagnosticRequestHandle* handle) { + handle->success = false; + handle->completed = false; + send_diagnostic_request(shims, handle); + setup_receive_handle(handle); +} + +DiagnosticRequestHandle generate_diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback) { DiagnosticRequestHandle handle = { request: *request, @@ -70,37 +108,12 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, completed: false }; - uint8_t payload[MAX_DIAGNOSTIC_PAYLOAD_SIZE] = {0}; - payload[MODE_BYTE_INDEX] = request->mode; - if(request->has_pid) { - request->pid_length = autoset_pid_length(request->mode, - request->pid, request->pid_length); - handle.request.pid_length = request->pid_length; - set_bitfield(request->pid, PID_BYTE_INDEX * CHAR_BIT, - request->pid_length * CHAR_BIT, payload, sizeof(payload)); - } - if(request->payload_length > 0) { - memcpy(&payload[PID_BYTE_INDEX + request->pid_length], - request->payload, request->payload_length); - } - handle.isotp_shims = isotp_init_shims(shims->log, shims->send_can_message, shims->set_timer); handle.isotp_shims.frame_padding = !request->no_frame_padding; - handle.isotp_send_handle = isotp_send(&handle.isotp_shims, - request->arbitration_id, payload, - 1 + request->payload_length + request->pid_length, - NULL); - if(shims->log != NULL) { - char request_string[128] = {0}; - diagnostic_request_to_string(request, request_string, sizeof(request_string)); - shims->log("Sending diagnostic request: %s", request_string); - } - - setup_receive_handle(&handle); - + return handle; // TODO notes on multi frame: // TODO what are the timers for exactly? // @@ -118,6 +131,13 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, // of the information but arg, memory allocation. look at how it's done in // the other library again // +} + +DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback) { + DiagnosticRequestHandle handle = generate_diagnostic_request( + shims, request, callback); + start_diagnostic_request(shims, &handle); return handle; } @@ -217,11 +237,14 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, if(message.size > 0) { response.mode = message.payload[0]; if(handle_negative_response(&message, &response, shims) || - handle_positive_response(handle, &message, &response, shims)) { + handle_positive_response(handle, &message, + &response, shims)) { if(shims->log != NULL) { char response_string[128] = {0}; - diagnostic_response_to_string(&response, response_string, sizeof(response_string)); - shims->log("Diagnostic response received: %s", response_string); + diagnostic_response_to_string(&response, + response_string, sizeof(response_string)); + shims->log("Diagnostic response received: %s", + response_string); } handle->success = true; diff --git a/src/uds/uds.h b/src/uds/uds.h index 0d40561..22ff36a 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -26,8 +26,8 @@ DiagnosticShims diagnostic_init_shims(LogShim log, SendCanMessageShim send_can_message, SetTimerShim set_timer); -/* Public: Initiate a diagnostic request and return a handle, ready to completly - * send the request and process the response via +/* Public: Generate a new diagnostic request, send the first CAN message frame + * and set up the handle required to process the response via * diagnostic_receive_can_frame(...). * * shims - Low-level shims required to send CAN messages, etc. @@ -38,11 +38,37 @@ DiagnosticShims diagnostic_init_shims(LogShim log, * Returns a handle to be used with diagnostic_receive_can_frame to complete * sending the request and receive the response. The 'completed' field in the * returned DiagnosticRequestHandle will be true when the message is completely - * sent. + * sent. The first frame of the message will already be sent. */ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, DiagnosticRequest* request, DiagnosticResponseReceived callback); +/* Public: Generate the handle for a new diagnostic request, but do not send any + * data to CAN yet - you must call start_diagnostic_request(...) on the handle + * returned from this function actually kick off the request. + * + * shims - Low-level shims required to send CAN messages, etc. + * request - + * callback - an optional function to be called when the response is receved + * (use NULL if no callback is required). + * + * Returns a handle to be used with start_diagnostic_request and then + * diagnostic_receive_can_frame to complete sending the request and receive the + * response. The 'completed' field in the returned DiagnosticRequestHandle will + * be true when the message is completely sent. + */ +DiagnosticRequestHandle generate_diagnostic_request(DiagnosticShims* shims, + DiagnosticRequest* request, DiagnosticResponseReceived callback); + +/* Public: Send the first frame of the request to CAN for the handle, generated + * by generate_diagnostic_request. + * + * You can also call this method to re-do the request for a handle that has + * already completed. + */ +void start_diagnostic_request(DiagnosticShims* shims, + DiagnosticRequestHandle* handle); + /* Public: Request a PID from the given arbitration ID, determining the mode * automatically based on the PID type. * diff --git a/tests/test_core.c b/tests/test_core.c index 30d19f4..448c0cd 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -4,6 +4,7 @@ #include #include +extern bool can_frame_was_sent; extern void setup(); extern bool last_response_was_received; extern DiagnosticResponse last_response_received; @@ -77,8 +78,8 @@ START_TEST (test_send_functional_request) for(uint16_t filter = OBD2_FUNCTIONAL_RESPONSE_START; filter < OBD2_FUNCTIONAL_RESPONSE_START + OBD2_FUNCTIONAL_RESPONSE_COUNT; filter++) { - DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, - filter, can_data, sizeof(can_data)); + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, + &handle, filter, can_data, sizeof(can_data)); fail_unless(response.success); fail_unless(response.completed); fail_unless(handle.completed); @@ -151,6 +152,7 @@ START_TEST (test_send_diag_request) response_received_handler); fail_if(handle.completed); + fail_unless(can_frame_was_sent); ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); ck_assert_int_eq(last_can_payload_sent[1], request.mode); ck_assert_int_eq(last_can_payload_size, 2); @@ -172,6 +174,57 @@ START_TEST (test_send_diag_request) } END_TEST +START_TEST (test_generate_then_send_request) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: true + }; + DiagnosticRequestHandle handle = generate_diagnostic_request(&SHIMS, + &request, response_received_handler); + + fail_if(handle.completed); + fail_if(can_frame_was_sent); + + start_diagnostic_request(&SHIMS, &handle); + fail_unless(can_frame_was_sent); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_size, 2); + + fail_if(last_response_was_received); + const uint8_t can_data[] = {0x2, request.mode + 0x40, 0x23}; + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + fail_unless(response.success); + fail_unless(response.completed); + fail_unless(handle.completed); + ck_assert(last_response_received.success); + ck_assert_int_eq(last_response_received.arbitration_id, + request.arbitration_id + 0x8); + ck_assert_int_eq(last_response_received.mode, request.mode); + fail_if(last_response_received.has_pid); + ck_assert_int_eq(last_response_received.payload_length, 1); + ck_assert_int_eq(last_response_received.payload[0], can_data[2]); +} +END_TEST + +START_TEST (test_generate_diag_request) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_EMISSIONS_DTC_REQUEST, + no_frame_padding: true + }; + DiagnosticRequestHandle handle = generate_diagnostic_request(&SHIMS, + &request, response_received_handler); + + fail_if(handle.completed); + fail_if(can_frame_was_sent); +} +END_TEST + START_TEST (test_autoset_pid_length) { uint16_t arb_id = OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST; @@ -321,7 +374,8 @@ START_TEST (test_negative_response) }; DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, response_received_handler); - const uint8_t can_data[] = {0x3, 0x7f, request.mode, NRC_SERVICE_NOT_SUPPORTED}; + const uint8_t can_data[] = {0x3, 0x7f, request.mode, + NRC_SERVICE_NOT_SUPPORTED}; DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, request.arbitration_id + 0x8, can_data, sizeof(can_data)); fail_unless(response.completed); @@ -333,7 +387,8 @@ START_TEST (test_negative_response) request.arbitration_id + 0x8); ck_assert_int_eq(last_response_received.mode, request.mode); ck_assert_int_eq(last_response_received.pid, 0); - ck_assert_int_eq(last_response_received.negative_response_code, NRC_SERVICE_NOT_SUPPORTED); + ck_assert_int_eq(last_response_received.negative_response_code, + NRC_SERVICE_NOT_SUPPORTED); ck_assert_int_eq(last_response_received.payload_length, 0); } END_TEST @@ -346,8 +401,8 @@ START_TEST (test_payload_to_integer) fail_if(last_response_was_received); const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x2, 0x45, 0x12}; - DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, - can_data, sizeof(can_data)); + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + arb_id + 0x8, can_data, sizeof(can_data)); ck_assert_int_eq(diagnostic_payload_to_integer(&response), 0x4512); } END_TEST @@ -359,6 +414,8 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_sent_message_no_padding); tcase_add_test(tc_core, test_sent_message_is_padded); tcase_add_test(tc_core, test_sent_message_is_padded_by_default); + tcase_add_test(tc_core, test_generate_diag_request); + tcase_add_test(tc_core, test_generate_then_send_request); tcase_add_test(tc_core, test_send_diag_request); tcase_add_test(tc_core, test_send_functional_request); tcase_add_test(tc_core, test_send_diag_request_with_payload); -- cgit 1.2.3-korg From 9e7545bc3f144373f0081d52c03858ed821c8d35 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 27 Feb 2014 15:53:58 -0500 Subject: Increase arb IDs to uint32_t to fit extended IDs. --- deps/isotp-c | 2 +- src/uds/uds.c | 14 +++++++++++--- src/uds/uds.h | 10 ++++++++-- src/uds/uds_types.h | 4 ++-- tests/common.c | 4 ++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index c01a88b..a8f4bf4 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit c01a88ba1e56d455c7187a52a5e244500a0d6b0f +Subproject commit a8f4bf42ee73ede0d2d3ddaf6c312c756c042a6b diff --git a/src/uds/uds.c b/src/uds/uds.c index 7303677..510cdd4 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -32,7 +32,7 @@ DiagnosticShims diagnostic_init_shims(LogShim log, static void setup_receive_handle(DiagnosticRequestHandle* handle) { if(handle->request.arbitration_id == OBD2_FUNCTIONAL_BROADCAST_ID) { - uint16_t response_id; + uint32_t response_id; for(response_id = 0; response_id < OBD2_FUNCTIONAL_RESPONSE_COUNT; ++response_id) { handle->isotp_receive_handles[response_id] = isotp_receive( @@ -142,7 +142,7 @@ DiagnosticRequestHandle diagnostic_request(DiagnosticShims* shims, } DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + DiagnosticPidRequestType pid_request_type, uint32_t arbitration_id, uint16_t pid, DiagnosticResponseReceived callback) { DiagnosticRequest request = { arbitration_id: arbitration_id, @@ -214,7 +214,7 @@ static bool handle_positive_response(DiagnosticRequestHandle* handle, } DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, - DiagnosticRequestHandle* handle, const uint16_t arbitration_id, + DiagnosticRequestHandle* handle, const uint32_t arbitration_id, const uint8_t data[], const uint8_t size) { DiagnosticResponse response = { @@ -373,3 +373,11 @@ void diagnostic_request_to_string(const DiagnosticRequest* request, } } +bool diagnostic_request_equals(const DiagnosticRequest* ours, + const DiagnosticRequest* theirs) { + bool equals = ours->arbitration_id == theirs->arbitration_id && + ours->mode == theirs->mode; + equals &= ours->has_pid == theirs->has_pid; + equals &= ours->pid == theirs->pid; + return equals; +} diff --git a/src/uds/uds.h b/src/uds/uds.h index 22ff36a..1546320 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -87,7 +87,7 @@ void start_diagnostic_request(DiagnosticShims* shims, * sent. */ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, - DiagnosticPidRequestType pid_request_type, uint16_t arbitration_id, + DiagnosticPidRequestType pid_request_type, uint32_t arbitration_id, uint16_t pid, DiagnosticResponseReceived callback); /* Public: Continue to send and receive a single diagnostic request, based on a @@ -106,7 +106,7 @@ DiagnosticRequestHandle diagnostic_request_pid(DiagnosticShims* shims, */ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, DiagnosticRequestHandle* handle, - const uint16_t arbitration_id, const uint8_t data[], + const uint32_t arbitration_id, const uint8_t data[], const uint8_t size); /* Public: Parse the entier payload of the reponse as a single integer. @@ -145,6 +145,12 @@ void diagnostic_request_to_string(const DiagnosticRequest* request, */ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response); +/* Public: Returns true if the "fingerprint" of the two diagnostic messages + * matches - the arbitration_id, mode and pid (or lack of pid). + */ +bool diagnostic_request_equals(const DiagnosticRequest* ours, + const DiagnosticRequest* theirs); + #ifdef __cplusplus } #endif diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index a71d53c..89ae18a 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -49,7 +49,7 @@ typedef enum { * type - the type of the request (TODO unused) */ typedef struct { - uint16_t arbitration_id; + uint32_t arbitration_id; uint8_t mode; bool has_pid; uint16_t pid; @@ -105,7 +105,7 @@ typedef enum { typedef struct { bool completed; bool success; - uint16_t arbitration_id; + uint32_t arbitration_id; uint8_t mode; bool has_pid; uint16_t pid; diff --git a/tests/common.c b/tests/common.c index 8d7dd50..fd1e4b2 100644 --- a/tests/common.c +++ b/tests/common.c @@ -7,7 +7,7 @@ DiagnosticShims SHIMS; -uint16_t last_can_frame_sent_arb_id; +uint32_t last_can_frame_sent_arb_id; uint8_t last_can_payload_sent[8]; uint8_t last_can_payload_size; bool can_frame_was_sent; @@ -23,7 +23,7 @@ void debug(const char* format, ...) { va_end(args); } -bool mock_send_can(const uint16_t arbitration_id, const uint8_t* data, +bool mock_send_can(const uint32_t arbitration_id, const uint8_t* data, const uint8_t size) { can_frame_was_sent = true; last_can_frame_sent_arb_id = arbitration_id; -- cgit 1.2.3-korg From 2625fea56a1e220694ad6805bc9047f10f7c8c47 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 27 Feb 2014 16:06:42 -0500 Subject: Use proper format specifier for uint32_t. --- src/uds/uds.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 510cdd4..31205f4 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -5,6 +5,7 @@ #include #include #include +#include #define ARBITRATION_ID_OFFSET 0x8 #define MODE_RESPONSE_OFFSET 0x40 @@ -309,7 +310,7 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { void diagnostic_response_to_string(const DiagnosticResponse* response, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%02x, mode: 0x%x, ", + "arb_id: 0x%" SCNd32 ", mode: 0x%x, ", response->arbitration_id, response->mode); @@ -346,7 +347,7 @@ void diagnostic_response_to_string(const DiagnosticResponse* response, void diagnostic_request_to_string(const DiagnosticRequest* request, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%02x, mode: 0x%x, ", + "arb_id: 0x%" SCNd32 ", mode: 0x%x, ", request->arbitration_id, request->mode); -- cgit 1.2.3-korg From cf4d4348421c7a2e8887fae39c7e9ab21a70666c Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Thu, 6 Mar 2014 00:23:55 -0500 Subject: Add a function to check if a request is completely sent. --- src/uds/uds.c | 4 ++++ src/uds/uds.h | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/uds/uds.c b/src/uds/uds.c index 31205f4..1157949 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -92,6 +92,10 @@ static void send_diagnostic_request(DiagnosticShims* shims, } } +bool diagnostic_request_sent(DiagnosticRequestHandle* handle) { + return handle->isotp_send_handle.completed; +} + void start_diagnostic_request(DiagnosticShims* shims, DiagnosticRequestHandle* handle) { handle->success = false; diff --git a/src/uds/uds.h b/src/uds/uds.h index 1546320..4305834 100644 --- a/src/uds/uds.h +++ b/src/uds/uds.h @@ -151,6 +151,13 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response); bool diagnostic_request_equals(const DiagnosticRequest* ours, const DiagnosticRequest* theirs); +/* Public: Returns true if the request has been completely sent - if false, make + * sure you called start_diagnostic_request once to start it, and then pass + * incoming CAN messages to it with diagnostic_receive_can_frame(...) so it can + * continue the ISO-TP transfer. + */ +bool diagnostic_request_sent(DiagnosticRequestHandle* handle); + #ifdef __cplusplus } #endif -- cgit 1.2.3-korg From 2fbfc901b7d17e87b67e3bef1d2f52e0a19a70be Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Mar 2014 15:37:10 -0400 Subject: Add NRC for incomplete messages. --- src/uds/uds_types.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index 89ae18a..68d6b6f 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -73,6 +73,7 @@ typedef enum { NRC_SUCCESS = 0x0, NRC_SERVICE_NOT_SUPPORTED = 0x11, NRC_SUB_FUNCTION_NOT_SUPPORTED = 0x12, + NRC_INCORRECT_LENGTH_OR_FORMAT = 0x13, NRC_CONDITIONS_NOT_CORRECT = 0x22, NRC_REQUEST_OUT_OF_RANGE = 0x31, NRC_SECURITY_ACCESS_DENIED = 0x33, -- cgit 1.2.3-korg From bc25d4a2fe7afb9ad96e718071be7e7153572956 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Fri, 14 Mar 2014 15:37:15 -0400 Subject: Force 0x3e "pid" (actually service ID) to be 1 byte. --- src/uds/uds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index 1157949..e0de344 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -53,7 +53,7 @@ static void setup_receive_handle(DiagnosticRequestHandle* handle) { static uint16_t autoset_pid_length(uint8_t mode, uint16_t pid, uint8_t pid_length) { if(pid_length == 0) { - if(pid > 0xffff || mode > 10) { + if(pid > 0xffff || (mode != 0x3e && mode > 0xa)) { pid_length = 2; } else { pid_length = 1; -- cgit 1.2.3-korg From 5164a09f1e58bf3225cabf22ae56ad98e516f71c Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Wed, 26 Mar 2014 10:49:09 -0400 Subject: If an OBD-II PID is unrecognized, return fully parsed payload. --- src/uds/uds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index e0de344..ed46b4a 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -307,7 +307,7 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { case 0x62: return response->payload[0] - 125; default: - return 0; + return diagnostic_payload_to_integer(response); } } -- cgit 1.2.3-korg From defe74b77bf96a88ec334afd1871ff0058bf4464 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sat, 19 Jul 2014 14:38:04 -0400 Subject: Remove old hipchat token from Travis CI config. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22b1a91..31bfdeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,3 @@ before_install: - git submodule update --init - sudo apt-get update -qq - sudo apt-get install check -notifications: - hipchat: - - secure: "ZO/hEAoOTZ4FJytMW0m4LZrsdFVM1/V0Hu13zfj8mdcHkf2MyfxthPDecnn5\naZVn4P8mSSwpp39EnAfa9fBcWcDESnKM1YQKPPGkoxZZHIOd2rYhRv34XfpE\n5qNLkQ/lEPQCBEmvIQ5ZJxsiZjGhO7KxWvdNdruH6cdVCYSh4Xo=" -- cgit 1.2.3-korg From f83e53554021a93e280c3faa9a6e6d436b0e1572 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sat, 27 Sep 2014 16:10:57 -0400 Subject: Fix rendering of message arb ID in strings - use hex, not decimal. --- src/uds/uds.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index ed46b4a..d5a40bc 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -314,8 +314,8 @@ float diagnostic_decode_obd2_pid(const DiagnosticResponse* response) { void diagnostic_response_to_string(const DiagnosticResponse* response, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%" SCNd32 ", mode: 0x%x, ", - response->arbitration_id, + "arb_id: 0x%lx, mode: 0x%x, ", + (unsigned long) response->arbitration_id, response->mode); if(response->has_pid) { @@ -351,8 +351,8 @@ void diagnostic_response_to_string(const DiagnosticResponse* response, void diagnostic_request_to_string(const DiagnosticRequest* request, char* destination, size_t destination_length) { int bytes_used = snprintf(destination, destination_length, - "arb_id: 0x%" SCNd32 ", mode: 0x%x, ", - request->arbitration_id, + "arb_id: 0x%lx, mode: 0x%x, ", + (unsigned long) request->arbitration_id, request->mode); if(request->has_pid) { -- cgit 1.2.3-korg From b2033dd734c689701d0e1c8518a04a3304a3c6c3 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sat, 27 Sep 2014 16:11:07 -0400 Subject: Propagate errors from isotp-c when sending requests. --- src/uds/uds.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/uds/uds.c b/src/uds/uds.c index d5a40bc..636bbfc 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -84,7 +84,14 @@ static void send_diagnostic_request(DiagnosticShims* shims, handle->request.arbitration_id, payload, 1 + handle->request.payload_length + handle->request.pid_length, NULL); - if(shims->log != NULL) { + if(handle->isotp_send_handle.completed && + !handle->isotp_send_handle.success) { + handle->completed = true; + handle->success = false; + if(shims->log != NULL) { + shims->log("%s", "Diagnostic request not sent"); + } + } else if(shims->log != NULL) { char request_string[128] = {0}; diagnostic_request_to_string(&handle->request, request_string, sizeof(request_string)); @@ -101,7 +108,9 @@ void start_diagnostic_request(DiagnosticShims* shims, handle->success = false; handle->completed = false; send_diagnostic_request(shims, handle); - setup_receive_handle(handle); + if(!handle->completed) { + setup_receive_handle(handle); + } } DiagnosticRequestHandle generate_diagnostic_request(DiagnosticShims* shims, -- cgit 1.2.3-korg From 6e6ffb3c8871f25b6df649dc35008ec075da0b2f Mon Sep 17 00:00:00 2001 From: Zac Nelson Date: Wed, 14 Sep 2016 09:54:47 -0700 Subject: Multi frame (#6) * increase payload size just for diag responses to support multi-frame responses. limited to 255 bytes for now. * not all diag requests with modes greater than 0xa have a 2 byte pid. need to check was pid_length should be based on the size of the pid. * remove line that does nothing. * add multi_frame field to DiagnosticResponse and update based on IsoTpMessage. Need this upstream for timeout calls specific to multi frame. * update isotp-c submodule. * update autoset_pid tests. check that pid_length is dynamically set based on pid value. adjust other tests to use 2-byte pid for enhanced diagnostic mode requests. * add test for multi-frame response. * update changelog. --- CHANGELOG.mkd | 4 +++ deps/isotp-c | 2 +- src/uds/uds.c | 7 ++-- src/uds/uds_types.h | 14 +++++--- tests/test_core.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 104 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.mkd b/CHANGELOG.mkd index edf7f7d..96eaa17 100644 --- a/CHANGELOG.mkd +++ b/CHANGELOG.mkd @@ -1,5 +1,9 @@ # OBD-II Support Library in C +## v0.2 + +* Add support for multi-frame diagnostic responses. + ## v0.1 * Initial release diff --git a/deps/isotp-c b/deps/isotp-c index a8f4bf4..64fdcc7 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit a8f4bf42ee73ede0d2d3ddaf6c312c756c042a6b +Subproject commit 64fdcc7381a20c992d44c424e7c9677a19f2248c diff --git a/src/uds/uds.c b/src/uds/uds.c index 636bbfc..0114384 100644 --- a/src/uds/uds.c +++ b/src/uds/uds.c @@ -53,7 +53,9 @@ static void setup_receive_handle(DiagnosticRequestHandle* handle) { static uint16_t autoset_pid_length(uint8_t mode, uint16_t pid, uint8_t pid_length) { if(pid_length == 0) { - if(pid > 0xffff || (mode != 0x3e && mode > 0xa)) { + if(mode <= 0xa || mode == 0x3e ) { + pid_length = 1; + } else if(pid > 0xffff || ((pid & 0xFF00) > 0x0)) { pid_length = 2; } else { pid_length = 1; @@ -69,7 +71,6 @@ static void send_diagnostic_request(DiagnosticShims* shims, if(handle->request.has_pid) { handle->request.pid_length = autoset_pid_length(handle->request.mode, handle->request.pid, handle->request.pid_length); - handle->request.pid_length = handle->request.pid_length; set_bitfield(handle->request.pid, PID_BYTE_INDEX * CHAR_BIT, handle->request.pid_length * CHAR_BIT, payload, sizeof(payload)); @@ -233,6 +234,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, DiagnosticResponse response = { arbitration_id: arbitration_id, + multi_frame: false, success: false, completed: false }; @@ -246,6 +248,7 @@ DiagnosticResponse diagnostic_receive_can_frame(DiagnosticShims* shims, IsoTpMessage message = isotp_continue_receive(&handle->isotp_shims, &handle->isotp_receive_handles[i], arbitration_id, data, size); + response.multi_frame = message.multi_frame; if(message.completed) { if(message.size > 0) { diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index 68d6b6f..66bf743 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -9,9 +9,10 @@ extern "C" { #endif -// TODO This isn't true for multi frame messages - we may need to dynamically -// allocate this in the future -#define MAX_UDS_PAYLOAD_LENGTH 7 +// TODO This still doesn't have enough space for the largest possible +// multiframe response. May need to dynamically allocate in the future. +#define MAX_UDS_RESPONSE_PAYLOAD_LENGTH 255 +#define MAX_UDS_REQUEST_PAYLOAD_LENGTH 7 #define MAX_RESPONDING_ECU_COUNT 8 #define VIN_LENGTH 17 @@ -54,7 +55,7 @@ typedef struct { bool has_pid; uint16_t pid; uint8_t pid_length; - uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; + uint8_t payload[MAX_UDS_REQUEST_PAYLOAD_LENGTH]; uint8_t payload_length; bool no_frame_padding; DiagnosticRequestType type; @@ -92,6 +93,8 @@ typedef enum { * field isn't valid if 'completed' isn't true. If this is 'false', check * the negative_response_code field for the reason. * arbitration_id - The arbitration ID the response was received on. + * multi_frame - True if this response (whether completed or not) required + * multi-frame CAN support. Can be used for updating time-out functions. * mode - The OBD-II mode for the original request. * has_pid - If this is a response to a PID request, this will be true and the * 'pid' field will be valid. @@ -106,12 +109,13 @@ typedef enum { typedef struct { bool completed; bool success; + bool multi_frame; uint32_t arbitration_id; uint8_t mode; bool has_pid; uint16_t pid; DiagnosticNegativeResponseCode negative_response_code; - uint8_t payload[MAX_UDS_PAYLOAD_LENGTH]; + uint8_t payload[MAX_UDS_RESPONSE_PAYLOAD_LENGTH]; uint8_t payload_length; } DiagnosticResponse; diff --git a/tests/test_core.c b/tests/test_core.c index 448c0cd..81200cc 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -242,14 +242,26 @@ START_TEST (test_autoset_pid_length) arbitration_id: 0x100, mode: 0x22, has_pid: true, - pid: 2, + pid: 0x1234, no_frame_padding: true }; diagnostic_request(&SHIMS, &request, response_received_handler); ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_sent[2], (request.pid & 0xFF00) >> 8); + ck_assert_int_eq(last_can_payload_sent[3], request.pid & 0xFF); ck_assert_int_eq(last_can_payload_size, 4); + + request.arbitration_id = 0x101; + request.pid = 0x12; + + diagnostic_request(&SHIMS, &request, response_received_handler); + + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[1], request.mode); + ck_assert_int_eq(last_can_payload_sent[2], request.pid); + ck_assert_int_eq(last_can_payload_size, 3); } END_TEST @@ -279,10 +291,10 @@ START_TEST (test_request_pid_enhanced) { uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x1234, response_received_handler); fail_if(last_response_was_received); - const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x2, 0x45}; + const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x12, 0x34, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); @@ -291,7 +303,7 @@ START_TEST (test_request_pid_enhanced) arb_id + 0x8); ck_assert_int_eq(last_response_received.mode, 0x22); fail_unless(last_response_received.has_pid); - ck_assert_int_eq(last_response_received.pid, 0x2); + ck_assert_int_eq(last_response_received.pid, 0x1234); ck_assert_int_eq(last_response_received.payload_length, 1); ck_assert_int_eq(last_response_received.payload[0], can_data[4]); } @@ -301,10 +313,10 @@ START_TEST (test_wrong_mode_response) { uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x1234, response_received_handler); fail_if(last_response_was_received); - const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x0, 0x2, 0x45}; + const uint8_t can_data[] = {0x4, 0x1 + 0x40, 0x12, 0x34, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_if(last_response_was_received); @@ -316,7 +328,7 @@ START_TEST (test_missing_pid) { uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x1234, response_received_handler); fail_if(last_response_was_received); const uint8_t can_data[] = {0x1, 0x22 + 0x40}; @@ -331,10 +343,10 @@ START_TEST (test_wrong_pid_response) { uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x1234, response_received_handler); fail_if(last_response_was_received); - const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x3, 0x45}; + const uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x12, 0x33, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_if(last_response_was_received); @@ -346,23 +358,23 @@ START_TEST (test_wrong_pid_then_right_completes) { uint16_t arb_id = 0x100; DiagnosticRequestHandle handle = diagnostic_request_pid(&SHIMS, - DIAGNOSTIC_ENHANCED_PID, arb_id, 0x2, response_received_handler); + DIAGNOSTIC_ENHANCED_PID, arb_id, 0x1234, response_received_handler); fail_if(last_response_was_received); - uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x0, 0x3, 0x45}; + uint8_t can_data[] = {0x4, 0x22 + 0x40, 0x12, 0x33, 0x45}; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_if(last_response_was_received); fail_if(handle.completed); - can_data[3] = 0x2; + can_data[3] = 0x34; diagnostic_receive_can_frame(&SHIMS, &handle, arb_id + 0x8, can_data, sizeof(can_data)); fail_unless(last_response_was_received); fail_unless(handle.completed); fail_unless(handle.success); fail_unless(last_response_received.success); - ck_assert_int_eq(last_response_received.pid, 0x2); + ck_assert_int_eq(last_response_received.pid, 0x1234); } END_TEST @@ -407,6 +419,65 @@ START_TEST (test_payload_to_integer) } END_TEST +START_TEST (test_response_multi_frame) +{ + DiagnosticRequest request = { + arbitration_id: 0x100, + mode: OBD2_MODE_VEHICLE_INFORMATION, + has_pid: true, + pid: 0x2 + }; + DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, + response_received_handler); + + const uint8_t can_data[] = {0x10, 0x14, 0x9 + 0x40, 0x2, 0x1, 0x31, 0x46, 0x4d}; + DiagnosticResponse response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data, sizeof(can_data)); + + fail_unless(can_frame_was_sent); + fail_unless(!response.success); + fail_unless(!response.completed); + fail_unless(response.multi_frame); + ck_assert_int_eq(last_can_frame_sent_arb_id, request.arbitration_id); + ck_assert_int_eq(last_can_payload_sent[0], 0x30); + + const uint8_t can_data_1[] = {0x21, 0x43, 0x55, 0x39, 0x4a, 0x39, 0x34, 0x48}; + response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data_1, sizeof(can_data_1)); + fail_unless(!response.success); + fail_unless(!response.completed); + fail_unless(response.multi_frame); + + const uint8_t can_data_2[] = {0x22, 0x55, 0x41, 0x30, 0x34, 0x35, 0x32, 0x34}; + response = diagnostic_receive_can_frame(&SHIMS, &handle, + request.arbitration_id + 0x8, can_data_2, sizeof(can_data_2)); + fail_unless(response.success); + fail_unless(response.completed); + fail_unless(response.multi_frame); + ck_assert_int_eq(response.mode, OBD2_MODE_VEHICLE_INFORMATION); + ck_assert_int_eq(response.pid, 0x2); + ck_assert_int_eq(response.payload_length, 18); + ck_assert_int_eq(response.payload[0], 0x01); + ck_assert_int_eq(response.payload[1], 0x31); + ck_assert_int_eq(response.payload[2], 0x46); + ck_assert_int_eq(response.payload[3], 0x4d); + ck_assert_int_eq(response.payload[4], 0x43); + ck_assert_int_eq(response.payload[5], 0x55); + ck_assert_int_eq(response.payload[6], 0x39); + ck_assert_int_eq(response.payload[7], 0x4a); + ck_assert_int_eq(response.payload[8], 0x39); + ck_assert_int_eq(response.payload[9], 0x34); + ck_assert_int_eq(response.payload[10], 0x48); + ck_assert_int_eq(response.payload[11], 0x55); + ck_assert_int_eq(response.payload[12], 0x41); + ck_assert_int_eq(response.payload[13], 0x30); + ck_assert_int_eq(response.payload[14], 0x34); + ck_assert_int_eq(response.payload[15], 0x35); + ck_assert_int_eq(response.payload[16], 0x32); + ck_assert_int_eq(response.payload[17], 0x34); +} +END_TEST + Suite* testSuite(void) { Suite* s = suite_create("uds"); TCase *tc_core = tcase_create("core"); @@ -429,6 +500,7 @@ Suite* testSuite(void) { tcase_add_test(tc_core, test_wrong_pid_then_right_completes); tcase_add_test(tc_core, test_negative_response); tcase_add_test(tc_core, test_payload_to_integer); + tcase_add_test(tc_core, test_response_multi_frame); // TODO these are future work: // TODO test request MIL -- cgit 1.2.3-korg From b28b20aa8e3d14745ed90f80013878e2ccd9ff6c Mon Sep 17 00:00:00 2001 From: Zac Nelson Date: Wed, 14 Sep 2016 10:10:35 -0700 Subject: fix isotp commit hash. --- deps/isotp-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/isotp-c b/deps/isotp-c index 64fdcc7..4e73753 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 64fdcc7381a20c992d44c424e7c9677a19f2248c +Subproject commit 4e7375321a029a32989bc7546329c385e6edb80f -- cgit 1.2.3-korg From e506334e270d77b20c0bc259ac6c7d8c9b702b7a Mon Sep 17 00:00:00 2001 From: Zac Nelson Date: Wed, 5 Oct 2016 10:25:09 -0700 Subject: Reduce max response payload size. (#7) * reduce max payload size. see OpenXC vi-firmware issue #375 https://github.com/openxc/vi-firmware/issues/375. * update isotp dependency with reduce isotp message size. --- deps/isotp-c | 2 +- src/uds/uds_types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/isotp-c b/deps/isotp-c index 4e73753..ee24440 160000 --- a/deps/isotp-c +++ b/deps/isotp-c @@ -1 +1 @@ -Subproject commit 4e7375321a029a32989bc7546329c385e6edb80f +Subproject commit ee24440b7c123ab1b0317e57be33e837a1cb51f1 diff --git a/src/uds/uds_types.h b/src/uds/uds_types.h index 66bf743..4ebc150 100644 --- a/src/uds/uds_types.h +++ b/src/uds/uds_types.h @@ -11,7 +11,7 @@ extern "C" { // TODO This still doesn't have enough space for the largest possible // multiframe response. May need to dynamically allocate in the future. -#define MAX_UDS_RESPONSE_PAYLOAD_LENGTH 255 +#define MAX_UDS_RESPONSE_PAYLOAD_LENGTH 127 #define MAX_UDS_REQUEST_PAYLOAD_LENGTH 7 #define MAX_RESPONDING_ECU_COUNT 8 #define VIN_LENGTH 17 -- cgit 1.2.3-korg From 63bda8b77981ab8dc30c553e585f4b90a5ee8237 Mon Sep 17 00:00:00 2001 From: Romain Forlot Date: Thu, 9 Mar 2017 10:02:17 +0100 Subject: Fix: wrong type not compatible with signature in isotp-c libs. Change-Id: Ie5709e5c0b7f25c4a1eab876a695c2d24f7fe936 Signed-off-by: Romain Forlot --- README.mkd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.mkd b/README.mkd index 8f9574f..aa0d7fd 100644 --- a/README.mkd +++ b/README.mkd @@ -19,7 +19,7 @@ system: // required, this must send a single CAN message with the given arbitration // ID (i.e. the CAN message ID) and data. The size will never be more than 8 // bytes. - bool send_can(const uint16_t arbitration_id, const uint8_t* data, + bool send_can(const uint32_t arbitration_id, const uint8_t* data, const uint8_t size) { ... } -- cgit 1.2.3-korg