├── .gitignore
├── ELM327DS.pdf
├── README.md
├── pom.xml
└── src
├── lib
└── jssc.jar
├── main
├── assembly
│ └── runnable.xml
├── java
│ └── cardiag
│ │ ├── Main.java
│ │ ├── obd2
│ │ ├── AirStatus.java
│ │ ├── EcuCompatibility.java
│ │ ├── Fault.java
│ │ ├── FuelStatus.java
│ │ ├── Mode.java
│ │ ├── MonitorStatus.java
│ │ ├── OBD2Exception.java
│ │ ├── OBD2Standard.java
│ │ ├── PID.java
│ │ ├── Report.java
│ │ ├── Response.java
│ │ └── ResponseWithNoData.java
│ │ ├── output
│ │ ├── OutputFileWriter.java
│ │ └── ReportFileWriter.java
│ │ ├── serial
│ │ ├── PortCommunication.java
│ │ ├── PortCommunicationException.java
│ │ ├── PortConfiguration.java
│ │ └── SerialUtils.java
│ │ └── user
│ │ ├── Action.java
│ │ ├── ConsoleCommunication.java
│ │ ├── ConsoleWrapper.java
│ │ └── UserCommunication.java
└── resources
│ └── log4j.properties
└── test
├── java
└── cardiag
│ ├── obd2
│ └── OBD2StandardITest.java
│ ├── serial
│ ├── PortCommunicationIT.java
│ └── SerialUtilsIT.java
│ ├── test
│ └── TestConfiguration.java
│ └── user
│ ├── ConsoleMock.java
│ └── UserCommunicationTest.java
└── resources
├── log4j.properties
└── test.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .settings
3 | .project
4 | .classpath
5 |
6 |
--------------------------------------------------------------------------------
/ELM327DS.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmatej/java-cardiag/6d5d2200be24aa5a8fa3532ac1cfc16450295d0b/ELM327DS.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | java-cardiag
2 | ============
3 |
4 | Description
5 | -----------
6 | Car diagnostic software for vehicles with OBD2 compatible interface.
7 |
8 | Example
9 | -------
10 | - now we can run two actions. Program looks for serial interfaces and lets the user to select one.
11 | Then it runs the action.
12 | - report creates a text file in the current directory containing all implemented values.
13 | - clear_trouble_codes erases the saved errors.
14 |
15 | ```
16 | sudo java -jar ./java-cardiag-0.0.1-SNAPSHOT-jar-with-dependencies.jar report;
17 | sudo java -jar ./java-cardiag-0.0.1-SNAPSHOT-jar-with-dependencies.jar CLEAR_TROUBLE_CODES;
18 | ```
19 |
20 | Targets
21 | -------
22 | - support for most of today's operating systems
23 | - communication via USB ELM327/OBD2 car interface, probably other in future
24 | - stability
25 | - simple startup, no system modifications needed (only JRE6 and higher)
26 | - pretty maintainable code covered by tests, both unit (without real serial interface)
27 | and integration (with interface and car)
28 | - open source free code
29 |
30 | Problems
31 | ---------
32 | - how
33 | - only available hardware for testing - ELM327 clone (1.5a)
34 | - only few available cars for testing - Lada Kalina 1.6 8V 2007, Škoda Fabia 2010, Ford Focus 2007, Seat Ibiza 2001.
35 | - need help with a selection of the license ... GNU GPL v3? EPL? BSD? MIT?
36 |
37 | Troubleshooting
38 | ---------------
39 | Port name - /dev/ttyUSB0; Method name - openPort(); Exception type - Permission denied. (Linux)
40 | - sudo gpasswd --add ${USER} dialout
41 | - or run the program with sudo.
42 |
43 | Current stage
44 | -------------
45 | - early development.
46 | - can reset error codes
47 | - can produce a report file with the current values
48 | - version 0.0.1 will be released after I will fix my problem with my car (P300, P303, P304) ... or later, in 2017 ;)
49 | - if someone will start implementing GUI, many people will be finally happy ;)
50 |
51 | Useful links
52 | ------------
53 | - http://code.google.com/p/java-simple-serial-connector/
54 | - http://en.wikipedia.org/wiki/OBD-II_PIDs#Bitwise_encoded_PIDs
55 | - http://www.obd-codes.com/trouble_codes/
56 | - http://www.obd-codes.com/faq/obd2-codes-explained.php
57 | - http://www.multitronics.ru/kody_vaz/
58 | - http://www.multitronics.ru/kody_obd2/
59 | - http://www.multitronics.ru/terminy_obd2/
60 | - http://www.obdii.com/obdii_library.asp
61 | - http://www.outilsobdfacile.com/obd-mode-pid.php
62 | - http://www.genisysotc.com/pdfs/DriveabilityDiagnostics.pdf
63 |
64 | Maybe useful links
65 | ------------------
66 | These libraries were not selected due to license or incompatibilities or other reason:
67 | - http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-misc-419423.html
68 | - http://www.oracle.com/technetwork/java/index-139971.html
69 | - http://rxtx.qbang.org/wiki/index.php/Download
70 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | java-cardiag
4 | java-cardiag
5 | 0.0.1-SNAPSHOT
6 | Car diagnostic software
7 | https://github.com/dmatej/java-cardiag
8 |
9 |
10 | scm:git:git://github.com/dmatej/java-cardiag.git
11 | git@github.com:dmatej/java-cardiag.git
12 | https://github.com/dmatej/java-cardiag.git
13 |
14 |
15 |
16 | cardiag.Main
17 | src/main/run
18 | UTF-8
19 | 1.8
20 | 1.8
21 | true
22 |
23 | /dev/ttyUSB0
24 |
25 |
26 |
27 |
28 |
29 | src/test/resources
30 |
31 | **/*.properties
32 |
33 | true
34 |
35 |
36 |
37 |
38 |
39 | org.apache.maven.plugins
40 | maven-resources-plugin
41 | 2.5
42 |
43 |
44 | compile-run-scripts
45 | compile
46 |
47 | copy-resources
48 |
49 |
50 | ${project.build.directory}/run
51 |
52 |
53 | ${run.srcDir}
54 |
55 | **/*
56 |
57 | false
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | org.apache.maven.plugins
67 | maven-surefire-plugin
68 | 2.18.1
69 |
70 | false
71 | false
72 | true
73 | true
74 |
75 | **/*ITest.class
76 |
77 |
78 |
79 |
80 |
81 | org.apache.maven.plugins
82 | maven-failsafe-plugin
83 | 2.18.1
84 |
85 | false
86 | false
87 | true
88 | true
89 |
90 |
91 |
92 | integration-test
93 |
94 | integration-test
95 |
96 |
97 |
98 | verify
99 |
100 | verify
101 |
102 |
103 |
104 |
105 |
106 |
107 | maven-jar-plugin
108 | 2.3.1
109 |
110 |
111 |
112 | true
113 | lib/
114 | ${mainClass}
115 |
116 |
117 |
118 |
119 |
120 |
121 | maven-assembly-plugin
122 | 2.3
123 |
124 |
125 | make-assembly
126 | package
127 |
128 | single
129 |
130 |
131 |
132 |
133 |
134 |
135 | ${mainClass}
136 |
137 |
138 |
139 | jar-with-dependencies
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | org.scream3r
149 | jssc
150 | 2.8.0
151 | compile
152 |
153 |
154 | org.apache.commons
155 | commons-lang3
156 | 3.5
157 | compile
158 |
159 |
160 | commons-io
161 | commons-io
162 | 2.4
163 | compile
164 |
165 |
166 | org.slf4j
167 | slf4j-api
168 | 1.7.25
169 | compile
170 |
171 |
172 | org.slf4j
173 | slf4j-log4j12
174 | 1.7.25
175 | runtime
176 |
177 |
178 | log4j
179 | log4j
180 | 1.2.17
181 | compile
182 |
183 |
184 | junit
185 | junit
186 | 4.11
187 | test
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/src/lib/jssc.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmatej/java-cardiag/6d5d2200be24aa5a8fa3532ac1cfc16450295d0b/src/lib/jssc.jar
--------------------------------------------------------------------------------
/src/main/assembly/runnable.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 | runnable-cardiag
9 |
10 |
11 | zip
12 |
13 | false
14 |
15 |
16 |
17 | ${project.build.directory}/${project.build.finalName}.one-jar.jar
18 | ${project.artifactId}.jar
19 | 664
20 |
21 |
22 |
23 |
24 |
25 |
26 | ${project.build.directory}/run
27 |
28 |
29 | **/*.sh
30 |
31 | false
32 |
33 |
34 | ${project.build.directory}/run
35 |
36 |
37 | **/*.sh
38 |
39 | false
40 | 774
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/Main.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag;
5 |
6 | import java.io.File;
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 | import java.util.List;
10 |
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | import cardiag.obd2.OBD2Standard;
15 | import cardiag.obd2.Report;
16 | import cardiag.output.ReportFileWriter;
17 | import cardiag.serial.PortConfiguration;
18 | import cardiag.serial.SerialUtils;
19 | import cardiag.user.Action;
20 | import cardiag.user.ConsoleCommunication;
21 | import cardiag.user.ConsoleWrapper;
22 | import cardiag.user.UserCommunication;
23 |
24 | /**
25 | * The main program. Initiates the user interface.
26 | *
27 | * @author David Matějček
28 | */
29 | public class Main {
30 |
31 | private static final Logger LOG = LoggerFactory.getLogger(Main.class);
32 |
33 |
34 | /**
35 | * The main method.
36 | *
37 | * @param args
38 | * @throws Exception
39 | */
40 | public static void main(final String... args) throws Exception {
41 | LOG.debug("main(args={})", (Object) args);
42 |
43 | final ConsoleCommunication console = new ConsoleWrapper(System.console());
44 | final UserCommunication user = new UserCommunication(console);
45 |
46 | final List portNames = SerialUtils.getPortNames();
47 | final PortConfiguration cfg = user.readPortConfiguration(portNames);
48 |
49 | final Action action = parseAction(args);
50 | final File homeDir = parseHomeDir(args);
51 |
52 | final OBD2Standard obd2 = new OBD2Standard(cfg);
53 | try {
54 | if (action == Action.WATCH) {
55 | watch(obd2, homeDir, 1000);
56 | } else if (action == Action.REPORT) {
57 | watch(obd2, homeDir, 1);
58 | } else if (action == Action.CLEAR_TROUBLE_CODES) {
59 | obd2.clearTroubleCodes();
60 | }
61 | } finally {
62 | obd2.close();
63 | }
64 | }
65 |
66 |
67 | private static void watch(final OBD2Standard obd2, final File homeDir, final int iterations) {
68 | LOG.info("watch(obd2={}, homeDir={}, iterations={})", obd2, homeDir, iterations);
69 | final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss.SSS");
70 | if (!homeDir.exists()) {
71 | homeDir.mkdirs();
72 | }
73 | final File file = new File(homeDir, "report" + sdf.format(new Date()) + ".txt");
74 | final ReportFileWriter writer = new ReportFileWriter(file);
75 | int i = iterations;
76 | while (i-- > 0) {
77 | final Report report = obd2.createReport();
78 | writer.write(report);
79 | }
80 | }
81 |
82 |
83 | private static Action parseAction(final String... args) {
84 | LOG.trace("parseAction(args={})", (Object[]) args);
85 |
86 | if (args == null || args.length == 0) {
87 | return Action.REPORT;
88 | }
89 | return Action.parse(args[0]);
90 | }
91 |
92 |
93 | private static File parseHomeDir(final String... args) {
94 | LOG.trace("parseHomeDir(args={})", (Object[]) args);
95 |
96 | if (args == null || args.length < 2) {
97 | final File tmpDir = new File(System.getProperty("java.io.tmpdir"));
98 | return new File(tmpDir, "java-cardiag");
99 | }
100 | return new File(args[1]);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/AirStatus.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | /**
7 | * @author David Matějček
8 | */
9 | public enum AirStatus {
10 | /***/
11 | UPSTREAM(0x01, "Upstream of catalytic converter"),
12 | /***/
13 | DOWNSTREAM(0x02, "Downstream of catalytic converter"),
14 | /***/
15 | OFF_OR_OUTSIDE(0x04, "From the outside atmosphere or off"),
16 | /***/
17 | PUMP_COMMANDED_ON_FOR_DIAGNOSTIC(0x08, "Pump commanded on for diagnostics");
18 |
19 | final int code;
20 | final String description;
21 |
22 |
23 | AirStatus(int value, String description) {
24 | this.code = value;
25 | this.description = description;
26 | }
27 |
28 |
29 | /**
30 | * @return the code.
31 | */
32 | public int getCode() {
33 | return code;
34 | }
35 |
36 |
37 | /**
38 | * @return the description.
39 | */
40 | public String getDescription() {
41 | return description;
42 | }
43 |
44 |
45 | /**
46 | * @return code and description
47 | */
48 | @Override
49 | public String toString() {
50 | return this.description;
51 | }
52 |
53 |
54 | public static AirStatus parseHex(final String hex) {
55 | if (hex == null) {
56 | throw new IllegalArgumentException("Invalid hex code: " + hex);
57 | }
58 |
59 | int code1 = Integer.parseInt(hex, 16);
60 | for (AirStatus status : AirStatus.values()) {
61 | if (status.code == code1) {
62 | return status;
63 | }
64 | }
65 |
66 | throw new IllegalArgumentException("Unknown status: " + hex);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/EcuCompatibility.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | /**
7 | * @author David Matějček
8 | */
9 | public enum EcuCompatibility {
10 | CARB(0x01, "OBD-II as defined by the CARB"), EPA(0x02, "OBD as defined by the EPA"), OBD_OBDII(0x03, "OBD and OBD-II"), OBDI(
11 | 0x04, "OBD-I"), INCOMPATIBLE(0x05, "Not meant to comply with any OBD standard"), EOBD_OBDII(0x06,
12 | "EOBD and OBD-II"), EOBD_OBD(0x08, "EOBD and OBD"), EOBD_OBD_OBDII(0x09, "EOBD, OBD and OBD II"), JOBD(0x0A,
13 | "JOBD (Japan)"), JOBD_OBDII(0x0B, "JOBD and OBD II"), JOBD_EOBD(0x0C, "JOBD and EOBD"), JOBD_EOBD_OBDII(0x0D,
14 | "JOBD, EOBD, and OBD II");
15 |
16 | private int code;
17 | private String description;
18 |
19 |
20 | EcuCompatibility(int code, String description) {
21 | this.code = code;
22 | this.description = description;
23 | }
24 |
25 |
26 | /**
27 | * @return the code.
28 | */
29 | public int getCode() {
30 | return code;
31 | }
32 |
33 |
34 | /**
35 | * @return the description.
36 | */
37 | public String getDescription() {
38 | return description;
39 | }
40 |
41 |
42 | /**
43 | * @return code and description
44 | */
45 | @Override
46 | public String toString() {
47 | return this.description;
48 | }
49 |
50 |
51 | public static EcuCompatibility parseHex(final String hex) {
52 | if (hex == null) {
53 | throw new IllegalArgumentException("Invalid hex code: " + hex);
54 | }
55 |
56 | int code1 = Integer.parseInt(hex, 16);
57 | for (EcuCompatibility status : EcuCompatibility.values()) {
58 | if (status.code == code1) {
59 | return status;
60 | }
61 | }
62 |
63 | throw new IllegalArgumentException("Unknown status: " + hex);
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/Fault.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import cardiag.serial.SerialUtils;
7 |
8 |
9 | /**
10 | * @author David Matějček
11 | *
12 | */
13 | public class Fault {
14 |
15 | private final String code;
16 |
17 |
18 | /**
19 | * @param string
20 | */
21 | public Fault(final String code) {
22 | this.code = code;
23 | }
24 |
25 |
26 | public String getCode() {
27 | return this.code;
28 | }
29 |
30 |
31 | @Override
32 | public String toString() {
33 | return getCode();
34 | }
35 |
36 |
37 | /**
38 | * @param a
39 | * @param b
40 | * @return
41 | */
42 | public static Fault decode(final boolean[] a, final boolean[] b) {
43 | final StringBuilder parsed = new StringBuilder();
44 | final char first;
45 | if (a[0]) {
46 | if (a[1]) {
47 | first = 'U';
48 | } else {
49 | first = 'B';
50 | }
51 | } else {
52 | if (a[1]) {
53 | first = 'C';
54 | } else {
55 | first = 'P';
56 | }
57 | }
58 | parsed.append(first);
59 |
60 | final char second;
61 | if (a[2]) {
62 | if (a[3]) {
63 | second = '3';
64 | } else {
65 | second = '2';
66 | }
67 | } else {
68 | if (a[3]) {
69 | second = '1';
70 | } else {
71 | second = '0';
72 | }
73 | }
74 | parsed.append(second);
75 |
76 | parsed.append(toHex(a[4], a[5], a[6], a[7]));
77 | parsed.append(toHex(b[0], b[1], b[2], b[3]));
78 | parsed.append(toHex(b[4], b[5], b[6], b[7]));
79 |
80 | return new Fault(parsed.toString());
81 | }
82 |
83 |
84 | private static String toHex(boolean... bools) {
85 | return Integer.toHexString(SerialUtils.toInteger(bools));
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/FuelStatus.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | /**
7 | * @author David Matějček
8 | */
9 | public enum FuelStatus {
10 |
11 | OPEN_LOW_TEMP(1, "Open loop due to insufficient engine temperature"), //
12 | CLOSED_USING_OXYGEN(2, "Closed loop, using oxygen sensor feedback to determine fuel mix"), //
13 | OPEN_LOAD(4, "Open loop due to engine load OR fuel cut due to deceleration"), //
14 | OPEN_FAILURE(8, "Open loop due to system failure"), //
15 | CLOSED_FAILURE_FEEDBACK(16,
16 | "Closed loop, using at least one oxygen sensor but there is a fault in the feedback system"), //
17 | ZERO5(32, "Always zero"), ZERO6(0x06, "Always zero"), ZERO7(0x07, "Always zero"), ;
18 |
19 | final int code;
20 | final String description;
21 |
22 |
23 | FuelStatus(int value, String description) {
24 | this.code = value;
25 | this.description = description;
26 | }
27 |
28 |
29 | /**
30 | * @return the code.
31 | */
32 | public int getCode() {
33 | return code;
34 | }
35 |
36 |
37 | /**
38 | * @return the description.
39 | */
40 | public String getDescription() {
41 | return description;
42 | }
43 |
44 |
45 | /**
46 | * @return code and description
47 | */
48 | @Override
49 | public String toString() {
50 | return this.description;
51 | }
52 |
53 |
54 | public static FuelStatus parseHex(final String hex) {
55 | if (hex == null) {
56 | throw new IllegalArgumentException("Invalid hex code: " + hex);
57 | }
58 |
59 | int code1 = Integer.parseInt(hex, 16);
60 | for (FuelStatus status : FuelStatus.values()) {
61 | if (status.code == code1) {
62 | return status;
63 | }
64 | }
65 |
66 | throw new IllegalArgumentException("Unknown status: " + hex);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/Mode.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | /**
9 | * @author David Matějček
10 | */
11 | public enum Mode {
12 | CURRENT_DATA(1), FREEZE_FRAME_DATA(2), DIAGNOSTIC(3), CLEAR_TROUBLE_CODES(4), VEHICLE_INFO(9);
13 |
14 | private int code;
15 |
16 |
17 | Mode(int code) {
18 | this.code = code;
19 | }
20 |
21 |
22 | public String hex() {
23 | return StringUtils.leftPad(Integer.toHexString(code), 2, '0');
24 | }
25 |
26 |
27 | public static Mode parseHex(final String hexMode) {
28 | if (hexMode == null) {
29 | throw new IllegalArgumentException("Invalid mode: " + hexMode);
30 | }
31 | int code = Integer.parseInt(hexMode, 16);
32 | for (Mode mode : Mode.values()) {
33 | if (mode.code == code) {
34 | return mode;
35 | }
36 | }
37 | throw new IllegalArgumentException("Invalid mode: " + hexMode);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/MonitorStatus.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
7 |
8 |
9 | /**
10 | * @author David Matějček
11 | *
12 | */
13 | public class MonitorStatus {
14 |
15 | private boolean mil;
16 | private int emmissionRelatedDTCs;
17 | private boolean comporessionIgnited;
18 | private boolean missfireAvailable;
19 | private boolean missfireIncomplete;
20 | private boolean fuelAvailable;
21 | private boolean fuelIncomplete;
22 | private boolean componentsAvailable;
23 | private boolean componentsIncomplete;
24 | private boolean reservedBitB7;
25 |
26 | public void setMIL(boolean mil) {
27 | this.mil = mil;
28 | }
29 |
30 | public void setEmissionRelatedDTCs(int dtcs) {
31 | this.emmissionRelatedDTCs = dtcs;
32 | }
33 |
34 | /**
35 | * @param compression (or spark)
36 | */
37 | public void setIgnition(boolean compression) {
38 | this.comporessionIgnited = compression;
39 | }
40 |
41 | public void setMissfire(boolean available, boolean incomplete) {
42 | this.missfireAvailable = available;
43 | this.missfireIncomplete = incomplete;
44 | }
45 |
46 | public void setFuel(boolean available, boolean incomplete) {
47 | this.fuelAvailable = available;
48 | this.fuelIncomplete = incomplete;
49 | }
50 |
51 |
52 | public void setComponents(boolean available, boolean incomplete) {
53 | this.componentsAvailable = available;
54 | this.componentsIncomplete = incomplete;
55 | }
56 |
57 | public void setReservedBitB7(boolean bit) {
58 | this.reservedBitB7 = bit;
59 | }
60 |
61 |
62 |
63 | @Override
64 | public String toString() {
65 | return ReflectionToStringBuilder.toString(this);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/OBD2Exception.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 |
7 | /**
8 | * Port communication exception. Command was not successfully executed.
9 | *
10 | * @author David Matějček
11 | */
12 | public class OBD2Exception extends Exception {
13 | private static final long serialVersionUID = -3794508502469587712L;
14 |
15 |
16 | /**
17 | * @param message - a short description of exception
18 | */
19 | public OBD2Exception(final String message) {
20 | super(message);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/OBD2Standard.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import java.io.Closeable;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | import org.apache.commons.lang3.StringUtils;
13 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 |
17 | import cardiag.serial.PortCommunication;
18 | import cardiag.serial.PortCommunicationException;
19 | import cardiag.serial.PortConfiguration;
20 | import cardiag.serial.SerialUtils;
21 |
22 | /**
23 | * Standard OBD-II functions.
24 | *
25 | * @author David Matějček
26 | */
27 | public class OBD2Standard implements Closeable {
28 |
29 | private static final Logger LOG = LoggerFactory.getLogger(OBD2Standard.class);
30 |
31 | private final PortCommunication comm;
32 |
33 |
34 | /**
35 | * Starts the port communication with some initial conversation.
36 | *
37 | * @param cfg
38 | * @throws PortCommunicationException
39 | */
40 | public OBD2Standard(final PortConfiguration cfg) throws PortCommunicationException {
41 | LOG.debug("OBD2Standard(cfg={})", cfg);
42 | this.comm = new PortCommunication(cfg);
43 | this.reset();
44 | }
45 |
46 |
47 | /**
48 | * Resets the communication.
49 | *
50 | * @throws PortCommunicationException
51 | */
52 | public void reset() throws PortCommunicationException {
53 | LOG.debug("reset()");
54 | // TODO: why is it here twice???
55 | this.comm.reset();
56 | this.comm.reset();
57 | this.comm.setEcho(false);
58 | this.comm.setLineTermination(false);
59 | }
60 |
61 |
62 | /**
63 | * Closes the communication and port.
64 | */
65 | public void close() {
66 | LOG.debug("close()");
67 | this.comm.close();
68 | }
69 |
70 |
71 | /**
72 | * @return a {@link Report}
73 | */
74 | public Report createReport() {
75 | LOG.trace("createReport()");
76 | final Report report = new Report();
77 | report.setSupportedPIDS(getSupportedPIDs());
78 | report.setEcuCompatibility(getEcuCompatibility());
79 | report.setMonitorStatus(getMonitorStatus());
80 | report.setFaults(getErrorReport());
81 |
82 | report.setDistanceSinceErrorCodesCleared(getDistanceSinceCodesCleared(false));
83 | report.setDistanceWithMalfunction(getDistanceWithMalfunction(false));
84 |
85 | report.setAmbientAirTemperature(getAmbientAirTemperature(false));
86 | report.setEngineOilTemperature(getEngineOilTemperature(false));
87 | report.setEngineCoolantTemperature(getEngineCoolantTemperature(false));
88 | report.setManifoldSurfaceTemperature(getManifoldSurfaceTemperature(false));
89 |
90 | report.setEngineLoad(getEngineLoad(false));
91 | report.setExhaustGasRecirculationTemperature(getExhaustGasRecirculationTemperature(false));
92 | report.setFuelInjectionTiming(getFuelInjectionTiming(false));
93 | report.setFuelLevelInput(getFuelLevelInput(false));
94 | report.setFuelRate(getFuelRate(false));
95 | report.setFuelStatus(getFuelStatus(false));
96 | report.setIntakeAirTemperature(getIntakeAirTemperature(false));
97 | report.setIntakeAirTemperatureSensor(getIntakeAirTemperatureSensor(false));
98 | report.setCatalystTemperatureSensor1(getCatalystTemperature(false, 2, 1));
99 | report.setCatalystTemperatureSensor2(getCatalystTemperature(false, 2, 2));
100 | report.setSecondaryAirStatus(getSecondaryAirStatus(false));
101 | report.setCommandedEgr(getCommandedEgr(false));
102 | report.setEgrError(getEgrError(false));
103 |
104 | // only bank 1
105 | report.setFuelTrimPercentShortTerm(getFuelTrimPercent(false, false, 1));
106 | report.setFuelTrimPercentLongTerm(getFuelTrimPercent(false, true, 1));
107 | report.setEthanolFuel(getEthanolFuel(false));
108 |
109 | return report;
110 | }
111 |
112 |
113 | /**
114 | * Sends the request to OBD II unit and returns a multiline response.
115 | *
116 | * @param mode
117 | * @param pid
118 | * @param hexParams
119 | * @return a list of responses. Never null, but may be empty.
120 | */
121 | protected List ask(final Mode mode, final PID pid, final String... hexParams) {
122 | LOG.debug("ask(mode={}, pid={}, hexParams={})", mode, pid, hexParams);
123 | final String[] params;
124 | if (hexParams == null) {
125 | params = new String[2];
126 | } else {
127 | params = new String[hexParams.length + 2];
128 | }
129 | params[0] = mode.hex();
130 | params[1] = pid.hex();
131 | if (hexParams != null) {
132 | for (int i = 0; i < hexParams.length; i++) {
133 | params[i + 2] = hexParams[i];
134 | }
135 | }
136 | comm.writeln(params);
137 | final String responseString = removeIgnoredText(comm.readResponse(StringUtils.join(params)));
138 | final List responses = new ArrayList<>();
139 | // Ford Focus 1.4
140 | if ("NO DATA".equals(responseString)) {
141 | responses.add(new ResponseWithNoData(mode, pid));
142 | return responses;
143 | }
144 |
145 | final String[] vals = split(responseString);
146 | LOG.trace("vals: \n {}", Arrays.toString(vals));
147 | final String firstWord = vals[0];
148 | if (firstWord.length() != 2) {
149 | throw new PortCommunicationException("Invalid response: '" + responseString + "'");
150 | }
151 | if ("7F".equals(firstWord)) {
152 | LOG.warn("Error response: '{}'", responseString);
153 | final Response response = new Response(true, mode, pid, vals);
154 | responses.add(response);
155 | return responses;
156 | }
157 | final String[] data;
158 | final PID responsePID;
159 | final int dataOffset;
160 | if (mode == Mode.DIAGNOSTIC || mode == Mode.CLEAR_TROUBLE_CODES) {
161 | dataOffset = 1;
162 | responsePID = null;
163 | } else {
164 | dataOffset = 2;
165 | responsePID = PID.parseHex(vals[1], mode);
166 | }
167 | if (vals.length > dataOffset) {
168 | data = Arrays.copyOfRange(vals, dataOffset, vals.length);
169 | } else {
170 | data = null;
171 | }
172 | final Response response = new Response(false, mode, responsePID, data);
173 | responses.add(response);
174 | return responses;
175 | }
176 |
177 |
178 | private String[] split(final String responseString) {
179 | final ArrayList response = new ArrayList<>();
180 | Character first = null;
181 | for (int i = 0; i < responseString.length(); i++) {
182 | final char c = responseString.charAt(i);
183 | if (Character.isWhitespace(c)) {
184 | continue;
185 | }
186 | if (first == null) {
187 | first = Character.valueOf(c);
188 | } else {
189 | response.add(StringUtils.join(first, c));
190 | first = null;
191 | }
192 |
193 | }
194 | return response.toArray(new String[response.size()]);
195 | }
196 |
197 |
198 | private String removeIgnoredText(final String responseString) {
199 | LOG.trace("removeIgnoredText(responseString={})", responseString);
200 | final String fixed = StringUtils.removeIgnoreCase(responseString, "SEARCHING...");
201 | if (fixed.length() != responseString.length()) {
202 | LOG.warn("Removed ignored text from the response: '{}'. Result: '{}'", responseString, fixed);
203 | }
204 | return fixed;
205 | }
206 |
207 |
208 | /**
209 | * Sends the request to OBD II unit and returns a singleline response.
210 | *
211 | * @param mode
212 | * @param pid
213 | * @param hexParams
214 | * @return a response, may be null.
215 | */
216 | protected Response askOneLine(final Mode mode, final PID pid, final String... hexParams) {
217 | LOG.trace("askOneLine(mode={}, pid={}, hexParams={})", mode, pid, hexParams);
218 | final List responses = ask(mode, pid, hexParams);
219 | if (responses.size() > 1) {
220 | // bug or something weird.
221 | throw new IllegalStateException(
222 | "Too many responses for this command: " + ReflectionToStringBuilder.toString(responses));
223 | }
224 | final Response response = responses.get(0);
225 | return response;
226 | }
227 |
228 |
229 | private Mode getMode(final boolean freezed) {
230 | return freezed ? Mode.FREEZE_FRAME_DATA : Mode.CURRENT_DATA;
231 | }
232 |
233 |
234 | /**
235 | * @return the VIN code of the car.
236 | */
237 | public String getVIN() {
238 | LOG.debug("getVIN()");
239 | final Response responseBytes = askOneLine(Mode.VEHICLE_INFO, PID.VIN_COUNT_OF_BYTES);
240 | LOG.info("Count of VIN bytes: {}", Integer.parseInt(responseBytes.getData()[0]), 16);
241 |
242 | // TODO: Kalina did not returned any.
243 | final List response = ask(Mode.VEHICLE_INFO, PID.VIN);
244 | return response.toString();
245 | }
246 |
247 |
248 | public boolean[] getSupportedPIDs() {
249 | LOG.debug("getSupportedPIDs()");
250 | final Response line = askOneLine(Mode.CURRENT_DATA, PID.PIDS_SUPPORTED);
251 | if (line.isError()) {
252 | return null;
253 | }
254 | return SerialUtils.convertHexToBooleanArray(line.getData());
255 | }
256 |
257 |
258 | public FuelStatus getFuelStatus(final boolean freezed) {
259 | LOG.debug("getFuelStatus(freezed={})", freezed);
260 | final Response line = askOneLine(getMode(freezed), PID.FUEL_STATUS);
261 | if (line.isError()) {
262 | return null;
263 | }
264 | // TODO: two bytes = two systems
265 | return FuelStatus.parseHex(line.getData()[0]);
266 | }
267 |
268 |
269 | public EcuCompatibility getEcuCompatibility() {
270 | LOG.debug("getEcuCompatibility()");
271 | final Response response = askOneLine(Mode.CURRENT_DATA, PID.ECU_COMPATIBILITY);
272 | if (response.isError()) {
273 | return null;
274 | }
275 | return EcuCompatibility.parseHex(response.getData()[0]);
276 | }
277 |
278 |
279 | public void clearTroubleCodes() {
280 | LOG.debug("clearTroubleCodes()");
281 | askOneLine(Mode.CLEAR_TROUBLE_CODES, PID.CLEAR_TROUBLE_CODES);
282 | }
283 |
284 |
285 | public MonitorStatus getMonitorStatus() {
286 | LOG.debug("getMonitorStatus()");
287 | final Response response = askOneLine(Mode.CURRENT_DATA, PID.MONITOR_STATUS);
288 | if (response.isError()) {
289 | return null;
290 | }
291 | final String[] data = response.getData();
292 | final boolean[] a = SerialUtils.convertHexToBooleanArray(data[0]);
293 | final boolean[] b = SerialUtils.convertHexToBooleanArray(data[1]);
294 | final boolean[] c = SerialUtils.convertHexToBooleanArray(data[2]);
295 | final boolean[] d = SerialUtils.convertHexToBooleanArray(data[3]);
296 | final MonitorStatus status = new MonitorStatus();
297 | status.setMIL(a[0]);
298 | status.setEmissionRelatedDTCs(SerialUtils.toInteger(Arrays.copyOfRange(a, 1, 8)));
299 | status.setMissfire(b[7], b[3]);
300 | status.setFuel(b[6], b[2]);
301 | status.setComponents(b[5], b[1]);
302 | status.setIgnition(b[4]);
303 | status.setReservedBitB7(b[0]);
304 | // TODO: C and D
305 | return status;
306 | }
307 |
308 |
309 | public List getErrorReport() {
310 | LOG.debug("getErrorReport()");
311 | final List responses = ask(Mode.DIAGNOSTIC, PID.DIAGNOSTIC_CODES);
312 | if (responses.isEmpty()) {
313 | return Collections.emptyList();
314 | }
315 | if (responses.get(0).isError()) {
316 | return null;
317 | }
318 | final List faults = new ArrayList();
319 | if (responses.get(0) instanceof ResponseWithNoData) {
320 | return faults;
321 | }
322 | for (final Response response : responses) {
323 | final String[] data = response.getData();
324 | final boolean[] a1 = SerialUtils.convertHexToBooleanArray(data[0]);
325 | final boolean[] b1 = SerialUtils.convertHexToBooleanArray(data[1]);
326 | final Fault fault1 = Fault.decode(a1, b1);
327 | LOG.info("Parsed fault code1: {}", fault1.getCode());
328 | if (!"P0000".equals(fault1.getCode())) {
329 | faults.add(fault1);
330 | }
331 | final boolean[] a2 = SerialUtils.convertHexToBooleanArray(data[2]);
332 | final boolean[] b2 = SerialUtils.convertHexToBooleanArray(data[3]);
333 | final Fault fault2 = Fault.decode(a2, b2);
334 | LOG.info("Parsed fault code2: {}", fault2.getCode());
335 | if (!"P0000".equals(fault2.getCode())) {
336 | faults.add(fault2);
337 | }
338 | final boolean[] a3 = SerialUtils.convertHexToBooleanArray(data[4]);
339 | final boolean[] b3 = SerialUtils.convertHexToBooleanArray(data[5]);
340 | final Fault fault3 = Fault.decode(a3, b3);
341 | LOG.info("Parsed fault code3: {}", fault3.getCode());
342 | if (!"P0000".equals(fault3.getCode())) {
343 | faults.add(fault3);
344 | }
345 | }
346 |
347 | return faults;
348 | }
349 |
350 |
351 | /**
352 | * @param freezed
353 | * @return engine load in percents.
354 | * @throws OBD2Exception
355 | */
356 | public Double getEngineLoad(final boolean freezed) {
357 | LOG.debug("getEngineLoad(freezed={})", freezed);
358 | final Response line = askOneLine(getMode(freezed), PID.ENGINE_LOAD);
359 | if (line.isError()) {
360 | return null;
361 | }
362 | final int encoded = Integer.parseInt(line.getData()[0], 16);
363 | return (encoded * 100.0) / 255.0;
364 | }
365 |
366 |
367 | public Integer getEngineCoolantTemperature(final boolean freezed) {
368 | LOG.debug("getEngineCoolantTemperature(freezed={})", freezed);
369 | final Response line = askOneLine(getMode(freezed), PID.ENGINE_COOLANT_TEMPERATURE);
370 | if (line.isError()) {
371 | return null;
372 | }
373 | // TODO: three bytes?!
374 | final int encoded = Integer.parseInt(line.getData()[0], 16);
375 | return encoded - 40;
376 | }
377 |
378 |
379 | public Integer getEngineOilTemperature(final boolean freezed) {
380 | LOG.debug("getEngineOilTemperature(freezed={})", freezed);
381 | final Response line = askOneLine(getMode(freezed), PID.ENGINE_OIL_TEMPERATURE);
382 | if (line.isError()) {
383 | return null;
384 | }
385 | final int encoded = Integer.parseInt(line.getData()[0], 16);
386 | return encoded - 40;
387 | }
388 |
389 |
390 | public Integer getIntakeAirTemperature(final boolean freezed) {
391 | LOG.debug("getIntakeAirTemperature(freezed={})", freezed);
392 | final Response line = askOneLine(getMode(freezed), PID.INTAKE_AIR_TEMPERATURE);
393 | if (line.isError()) {
394 | return null;
395 | }
396 | final int encoded = Integer.parseInt(line.getData()[0], 16);
397 | return encoded - 40;
398 | }
399 |
400 |
401 | public Integer getIntakeAirTemperatureSensor(final boolean freezed) {
402 | LOG.debug("getIntakeAirTemperatureSensor(freezed={})", freezed);
403 | final Response line = askOneLine(getMode(freezed), PID.INTAKE_AIR_TEMPERATURE_SENSOR);
404 | if (line.isError()) {
405 | return null;
406 | }
407 | // TODO: what is it?
408 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
409 | // final int encodedB = Integer.parseInt(line.getData()[0], 16);
410 | // final int encodedC = Integer.parseInt(line.getData()[0], 16);
411 | // final int encodedD = Integer.parseInt(line.getData()[0], 16);
412 | // final int encodedE = Integer.parseInt(line.getData()[0], 16);
413 | // final int encodedF = Integer.parseInt(line.getData()[0], 16);
414 | // final int encodedG = Integer.parseInt(line.getData()[0], 16);
415 | return encodedA - 40;
416 | }
417 |
418 |
419 | public Integer getExhaustGasRecirculationTemperature(final boolean freezed) {
420 | LOG.debug("getExhaustGasRecirculationTemperature(freezed={})", freezed);
421 |
422 | final Response line = askOneLine(getMode(freezed), PID.EXHAUST_GAS_RECIRCULATION_TEMPERATURE);
423 | if (line.isError()) {
424 | return null;
425 | }
426 | // TODO: what is it?
427 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
428 | // final int encodedB = Integer.parseInt(line.getData()[0], 16);
429 | // final int encodedC = Integer.parseInt(line.getData()[0], 16);
430 | // final int encodedD = Integer.parseInt(line.getData()[0], 16);
431 | // final int encodedE = Integer.parseInt(line.getData()[0], 16);
432 | return encodedA - 40;
433 |
434 | }
435 |
436 |
437 | public Integer getAmbientAirTemperature(final boolean freezed) {
438 | LOG.debug("getAmbientAirTemperature(freezed={})", freezed);
439 | final Response line = askOneLine(getMode(freezed), PID.AMBIENT_AIR_TEMPERATURE);
440 | if (line.isError()) {
441 | return null;
442 | }
443 | final int encoded = Integer.parseInt(line.getData()[0], 16);
444 | return encoded - 40;
445 | }
446 |
447 |
448 | public Integer getManifoldSurfaceTemperature(final boolean freezed) {
449 | LOG.debug("getManifoldSurfaceTemperature(freezed={})", freezed);
450 | final Response line = askOneLine(getMode(freezed), PID.MANIFOLD_SURFACE_TEMPERATURE);
451 | if (line.isError()) {
452 | return null;
453 | }
454 | // TODO: how many bytes?
455 | final int encoded = Integer.parseInt(line.getData()[0], 16);
456 | return encoded - 40;
457 | }
458 |
459 |
460 | public Double getFuelTrimPercent(final boolean freezed, final boolean longTerm, final int bank) {
461 | LOG.debug("getFuelTrimPercent(freezed={}, longTerm={}, bank={})", freezed, longTerm, bank);
462 | final PID pid;
463 | if (bank == 1) {
464 | pid = longTerm ? PID.FUEL_TRIM_PERCENT_LONG_BANK1 : PID.FUEL_TRIM_PERCENT_SHORT_BANK1;
465 | } else if (bank == 2) {
466 | pid = longTerm ? PID.FUEL_TRIM_PERCENT_LONG_BANK2 : PID.FUEL_TRIM_PERCENT_SHORT_BANK2;
467 | } else {
468 | throw new IllegalArgumentException("Invalid bank: " + bank);
469 | }
470 | final Response line = askOneLine(getMode(freezed), pid);
471 | if (line.isError()) {
472 | return null;
473 | }
474 | final int encoded = Integer.parseInt(line.getData()[0], 16);
475 | return ((encoded - 128.0) * 100.0) / 128.0;
476 | }
477 |
478 |
479 | public Double getCatalystTemperature(final boolean freezed, final int bank, final int sensor) {
480 | LOG.debug("getCatalystTemperature(freezed={}, bank={}, sensor={})", freezed, bank, sensor);
481 | final PID pid;
482 | if (sensor != 1 && sensor != 2) {
483 | throw new IllegalArgumentException("Invalid sensor: " + sensor);
484 | }
485 | if (bank == 1) {
486 | if (sensor == 1) {
487 | pid = PID.CATALYST_TEMPERATURE_BANK1_SENSOR1;
488 | } else {
489 | pid = PID.CATALYST_TEMPERATURE_BANK1_SENSOR2;
490 | }
491 | } else if (bank == 2) {
492 | if (sensor == 1) {
493 | pid = PID.CATALYST_TEMPERATURE_BANK2_SENSOR1;
494 | } else {
495 | pid = PID.CATALYST_TEMPERATURE_BANK2_SENSOR2;
496 | }
497 | } else {
498 | throw new IllegalArgumentException("Invalid bank: " + bank);
499 | }
500 |
501 | final Response response = askOneLine(getMode(freezed), pid);
502 | if (response.isError()) {
503 | return null;
504 | }
505 | final int encodedA = Integer.parseInt(response.getData()[0], 16);
506 | final int encodedB = Integer.parseInt(response.getData()[1], 16);
507 | return ((encodedA * 256.0 + encodedB) / 10.0) - 40.0;
508 | }
509 |
510 |
511 | public Integer getDistanceWithMalfunction(final boolean freezed) {
512 | LOG.debug("getDistanceWithMalfunction(freezed={})", freezed);
513 | final Response line = askOneLine(getMode(freezed), PID.DISTANCE_WITH_MALFUNCTION);
514 | if (line.isError()) {
515 | return null;
516 | }
517 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
518 | final int encodedB = Integer.parseInt(line.getData()[1], 16);
519 | return encodedA * 256 + encodedB;
520 | }
521 |
522 |
523 | public Integer getDistanceSinceCodesCleared(final boolean freezed) {
524 | LOG.debug("getDistanceSinceCodesCleared(freezed={})", freezed);
525 | final Response line = askOneLine(getMode(freezed), PID.DISTANCE_FROM_CODES_CLEARED);
526 | if (line.isError()) {
527 | return null;
528 | }
529 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
530 | final int encodedB = Integer.parseInt(line.getData()[1], 16);
531 | return encodedA * 256 + encodedB;
532 | }
533 |
534 |
535 | public Double getEthanolFuel(final boolean freezed) {
536 | LOG.debug("getEthanolFuel(freezed={})", freezed);
537 | final Response line = askOneLine(getMode(freezed), PID.ETHANOL_FUEL);
538 | if (line.isError()) {
539 | return null;
540 | }
541 | final int encoded = Integer.parseInt(line.getData()[0], 16);
542 | return (encoded * 100.0) / 255.0;
543 | }
544 |
545 |
546 | public Double getFuelLevelInput(final boolean freezed) {
547 | LOG.debug("getFuelLevelInput(freezed={})", freezed);
548 | final Response line = askOneLine(getMode(freezed), PID.FUEL_LEVEL_INPUT);
549 | if (line.isError()) {
550 | return null;
551 | }
552 | final int encoded = Integer.parseInt(line.getData()[0], 16);
553 | return (encoded * 100.0) / 255.0;
554 | }
555 |
556 |
557 | public Double getFuelInjectionTiming(final boolean freezed) {
558 | LOG.debug("getFuelInjectionTiming(freezed={})", freezed);
559 | final Response line = askOneLine(getMode(freezed), PID.FUEL_INJECTION_TIMING);
560 | if (line.isError()) {
561 | return null;
562 | }
563 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
564 | final int encodedB = Integer.parseInt(line.getData()[1], 16);
565 | return (encodedA * 256.0 + encodedB - 26880.0) / 128.0;
566 | }
567 |
568 |
569 | public Double getFuelRate(final boolean freezed) {
570 | LOG.debug("getFuelRate(freezed={})", freezed);
571 | final Response line = askOneLine(getMode(freezed), PID.FUEL_RATE);
572 | if (line.isError()) {
573 | return null;
574 | }
575 | final int encodedA = Integer.parseInt(line.getData()[0], 16);
576 | final int encodedB = Integer.parseInt(line.getData()[1], 16);
577 | return (encodedA * 256.0 + encodedB) * 0.05;
578 | }
579 |
580 |
581 | public AirStatus getSecondaryAirStatus(final boolean freezed) {
582 | LOG.debug("getSecondaryAirStatus(freezed={})", freezed);
583 | final Response line = askOneLine(getMode(freezed), PID.SECONDARY_AIR_STATUS);
584 | if (line.isError()) {
585 | return null;
586 | }
587 | try {
588 | return AirStatus.parseHex(line.getData()[0]);
589 | } catch (final IllegalArgumentException e) {
590 | // see Issue #11
591 | LOG.warn(e.getMessage());
592 | return null;
593 | }
594 | }
595 |
596 |
597 | public Double getCommandedEgr(final boolean freezed) {
598 | LOG.debug("getCommandedEgr(freezed={})", freezed);
599 | final Response line = askOneLine(getMode(freezed), PID.COMMANDED_EGR);
600 | if (line.isError()) {
601 | return null;
602 | }
603 | final int encoded = Integer.parseInt(line.getData()[0], 16);
604 | return (encoded * 100.0) / 255.0;
605 | }
606 |
607 |
608 | public Double getEgrError(final boolean freezed) {
609 | LOG.debug("getEgrError(freezed={})", freezed);
610 | final Response line = askOneLine(getMode(freezed), PID.EGR_ERROR);
611 | if (line.isError()) {
612 | return null;
613 | }
614 | final int encoded = Integer.parseInt(line.getData()[0], 16);
615 | return ((encoded - 128.0) * 100.0) / 128.0;
616 | }
617 | }
618 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/PID.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import static cardiag.obd2.Mode.CURRENT_DATA;
7 | import static cardiag.obd2.Mode.DIAGNOSTIC;
8 | import static cardiag.obd2.Mode.FREEZE_FRAME_DATA;
9 | import static cardiag.obd2.Mode.VEHICLE_INFO;
10 |
11 | import org.apache.commons.lang3.StringUtils;
12 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | /**
17 | * @author David Matějček
18 | */
19 | public class PID {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(PID.class);
22 |
23 | public static final PID DIAGNOSTIC_CODES = new PID(0, DIAGNOSTIC);
24 | public static final PID CLEAR_TROUBLE_CODES = new PID(0, Mode.CLEAR_TROUBLE_CODES);
25 | public static final PID VIN_COUNT_OF_BYTES = new PID(1, VEHICLE_INFO);
26 | public static final PID VIN = new PID(2, VEHICLE_INFO);
27 |
28 | public static final PID PIDS_SUPPORTED = new PID(0, CURRENT_DATA);
29 | public static final PID MONITOR_STATUS = new PID(1, FREEZE_FRAME_DATA, CURRENT_DATA);
30 | public static final PID FUEL_STATUS = new PID(3, FREEZE_FRAME_DATA, CURRENT_DATA);
31 | public static final PID ENGINE_LOAD = new PID(4, FREEZE_FRAME_DATA, CURRENT_DATA);
32 | public static final PID ENGINE_COOLANT_TEMPERATURE = new PID(5, FREEZE_FRAME_DATA, CURRENT_DATA);
33 | public static final PID FUEL_TRIM_PERCENT_SHORT_BANK1 = new PID(6, FREEZE_FRAME_DATA, CURRENT_DATA);
34 | public static final PID FUEL_TRIM_PERCENT_LONG_BANK1 = new PID(7, FREEZE_FRAME_DATA, CURRENT_DATA);
35 | public static final PID FUEL_TRIM_PERCENT_SHORT_BANK2 = new PID(8, FREEZE_FRAME_DATA, CURRENT_DATA);
36 | public static final PID FUEL_TRIM_PERCENT_LONG_BANK2 = new PID(9, FREEZE_FRAME_DATA, CURRENT_DATA);
37 |
38 | public static final PID INTAKE_AIR_TEMPERATURE = new PID(0x0F, FREEZE_FRAME_DATA, CURRENT_DATA);
39 | public static final PID INTAKE_AIR_TEMPERATURE_SENSOR = new PID(0x68, FREEZE_FRAME_DATA, CURRENT_DATA);
40 |
41 | public static final PID SECONDARY_AIR_STATUS = new PID(0x12, FREEZE_FRAME_DATA, CURRENT_DATA);
42 | public static final PID ECU_COMPATIBILITY = new PID(0x1c, CURRENT_DATA);
43 |
44 | public static final PID DISTANCE_WITH_MALFUNCTION = new PID(0x21, FREEZE_FRAME_DATA, CURRENT_DATA);
45 | public static final PID COMMANDED_EGR = new PID(0x2c, FREEZE_FRAME_DATA, CURRENT_DATA);
46 | public static final PID EGR_ERROR = new PID(0x2d, FREEZE_FRAME_DATA, CURRENT_DATA);
47 | public static final PID FUEL_LEVEL_INPUT = new PID(0x2F, FREEZE_FRAME_DATA, CURRENT_DATA);
48 |
49 | public static final PID DISTANCE_FROM_CODES_CLEARED = new PID(0x31, FREEZE_FRAME_DATA, CURRENT_DATA);
50 | public static final PID CATALYST_TEMPERATURE_BANK1_SENSOR1 = new PID(0x3c, FREEZE_FRAME_DATA, CURRENT_DATA);
51 | public static final PID CATALYST_TEMPERATURE_BANK2_SENSOR1 = new PID(0x3d, FREEZE_FRAME_DATA, CURRENT_DATA);
52 | public static final PID CATALYST_TEMPERATURE_BANK1_SENSOR2 = new PID(0x3e, FREEZE_FRAME_DATA, CURRENT_DATA);
53 | public static final PID CATALYST_TEMPERATURE_BANK2_SENSOR2 = new PID(0x3f, FREEZE_FRAME_DATA, CURRENT_DATA);
54 |
55 | public static final PID AMBIENT_AIR_TEMPERATURE = new PID(0x46, FREEZE_FRAME_DATA, CURRENT_DATA);
56 |
57 | public static final PID ETHANOL_FUEL = new PID(0x51, FREEZE_FRAME_DATA, CURRENT_DATA);
58 | public static final PID ENGINE_OIL_TEMPERATURE = new PID(0x5c, FREEZE_FRAME_DATA, CURRENT_DATA);
59 | public static final PID FUEL_INJECTION_TIMING = new PID(0x5d, FREEZE_FRAME_DATA, CURRENT_DATA);
60 | public static final PID FUEL_RATE = new PID(0x5e, FREEZE_FRAME_DATA, CURRENT_DATA);
61 |
62 | public static final PID EXHAUST_GAS_RECIRCULATION_TEMPERATURE = new PID(0x6b, FREEZE_FRAME_DATA, CURRENT_DATA);
63 |
64 | public static final PID MANIFOLD_SURFACE_TEMPERATURE = new PID(0x84, FREEZE_FRAME_DATA, CURRENT_DATA);
65 |
66 |
67 | private final Mode[] allowedModes;
68 | private final int code;
69 |
70 |
71 | protected PID(final int pidCode, final Mode... modesAllowed) {
72 | this.code = pidCode;
73 | this.allowedModes = modesAllowed;
74 | }
75 |
76 |
77 | public String hex() {
78 | return StringUtils.leftPad(Integer.toHexString(code), 2, '0');
79 | }
80 |
81 |
82 | @Override
83 | public String toString() {
84 | return ReflectionToStringBuilder.toString(this);
85 | }
86 |
87 |
88 | // the problem is that some pids have very different meaning in different modes.
89 | public static PID parseHex(final String hex, final Mode mode) {
90 | LOG.debug("parseHex(hex={}, mode={})", hex, mode);
91 | if (hex == null) {
92 | throw new IllegalArgumentException("Invalid PID: " + hex);
93 | }
94 | final int code = Integer.parseInt(hex, 16);
95 | switch (code) {
96 | case 0:
97 | switch (mode) {
98 | case CURRENT_DATA:
99 | return valid(mode, PIDS_SUPPORTED);
100 | case DIAGNOSTIC:
101 | return valid(mode, DIAGNOSTIC_CODES);
102 | case CLEAR_TROUBLE_CODES:
103 | return valid(mode, CLEAR_TROUBLE_CODES);
104 | default:
105 | break;
106 | }
107 | case 1:
108 | switch (mode) {
109 | case VEHICLE_INFO:
110 | return valid(mode, VIN_COUNT_OF_BYTES);
111 | default:
112 | return valid(mode, MONITOR_STATUS);
113 | }
114 | case 2:
115 | return valid(mode, VIN);
116 | case 3:
117 | return valid(mode, FUEL_STATUS);
118 | case 4:
119 | return valid(mode, ENGINE_LOAD);
120 | case 5:
121 | return valid(mode, ENGINE_COOLANT_TEMPERATURE);
122 | case 6:
123 | return valid(mode, FUEL_TRIM_PERCENT_SHORT_BANK1);
124 | case 7:
125 | return valid(mode, FUEL_TRIM_PERCENT_LONG_BANK2);
126 | case 8:
127 | return valid(mode, FUEL_TRIM_PERCENT_SHORT_BANK1);
128 | case 9:
129 | return valid(mode, FUEL_TRIM_PERCENT_LONG_BANK2);
130 | case 0x0F:
131 | return valid(mode, INTAKE_AIR_TEMPERATURE);
132 | case 0x12:
133 | return valid(mode, SECONDARY_AIR_STATUS);
134 | case 0x1C:
135 | return valid(mode, ECU_COMPATIBILITY);
136 | case 0x21:
137 | return valid(mode, DISTANCE_WITH_MALFUNCTION);
138 | case 0x2c:
139 | return valid(mode, COMMANDED_EGR);
140 | case 0x2d:
141 | return valid(mode, EGR_ERROR);
142 | case 0x2F:
143 | return valid(mode, FUEL_LEVEL_INPUT);
144 | case 0x31:
145 | return valid(mode, DISTANCE_FROM_CODES_CLEARED);
146 | case 0x3c:
147 | return valid(mode, CATALYST_TEMPERATURE_BANK1_SENSOR1);
148 | case 0x3d:
149 | return valid(mode, CATALYST_TEMPERATURE_BANK2_SENSOR1);
150 | case 0x3e:
151 | return valid(mode, CATALYST_TEMPERATURE_BANK1_SENSOR2);
152 | case 0x3f:
153 | return valid(mode, CATALYST_TEMPERATURE_BANK2_SENSOR2);
154 | case 0x46:
155 | return valid(mode, AMBIENT_AIR_TEMPERATURE);
156 | case 0x51:
157 | return valid(mode, ETHANOL_FUEL);
158 | case 0x5c:
159 | return valid(mode, ENGINE_OIL_TEMPERATURE);
160 | case 0x5d:
161 | return valid(mode, FUEL_INJECTION_TIMING);
162 | case 0x5e:
163 | return valid(mode, FUEL_RATE);
164 | case 0x68:
165 | return valid(mode, INTAKE_AIR_TEMPERATURE_SENSOR);
166 | case 0x6b:
167 | return valid(mode, EXHAUST_GAS_RECIRCULATION_TEMPERATURE);
168 | case 0x84:
169 | return valid(mode, MANIFOLD_SURFACE_TEMPERATURE);
170 | default:
171 | throw new IllegalArgumentException("Invalid PID: " + hex);
172 | }
173 | }
174 |
175 |
176 | /**
177 | * @param mode
178 | * @param pidsSupported
179 | * @return
180 | */
181 | private static PID valid(Mode mode, PID pid) {
182 | for (Mode allowed : pid.allowedModes) {
183 | if (allowed == mode) {
184 | return pid;
185 | }
186 | }
187 | throw new IllegalArgumentException(String.format("The pid %s is not allowed to run in mode %s.", pid, mode));
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/Report.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.Date;
9 | import java.util.List;
10 |
11 | /**
12 | * @author David Matějček
13 | */
14 | public class Report {
15 |
16 | private Date timestamp = new Date();
17 | private boolean[] supportedPIDS;
18 | private MonitorStatus monitorStatus;
19 | private List faults;
20 | private Integer distanceSinceErrorCodesCleared;
21 | private Integer distanceWithMalfunction;
22 | private Integer engineCoolantTemperature;
23 | private Double engineLoad;
24 | private Double fuelInjectionTiming;
25 | private Double fuelLevelInput;
26 | private Double fuelRate;
27 | private FuelStatus fuelStatus;
28 | private Double fuelTrimPercentShortTerm;
29 | private Double fuelTrimPercentLongTerm;
30 | private Integer intakeAirTemperature;
31 | private AirStatus secondaryAirStatus;
32 | private EcuCompatibility ecuCompatibility;
33 | private Double catalystTemperatureSensor1;
34 | private Double catalystTemperatureSensor2;
35 | private Integer ambientAirTemperature;
36 | private Integer engineOilTemperature;
37 | private Integer intakeAirTemperatureSensor;
38 | private Integer exhaustGasRecirculationTemperature;
39 | private Integer manifoldSurfaceTemperature;
40 | private Double commandedEgr;
41 | private Double egrError;
42 | private Double ethanolFuel;
43 |
44 |
45 | public Date getTimestamp() {
46 | return timestamp;
47 | }
48 |
49 | public boolean[] getSupportedPIDS() {
50 | return Arrays.copyOf(supportedPIDS, supportedPIDS.length);
51 | }
52 |
53 |
54 | public void setSupportedPIDS(final boolean[] supportedPIDS) {
55 | if (supportedPIDS == null) {
56 | this.supportedPIDS = new boolean[0];
57 | } else {
58 | this.supportedPIDS = Arrays.copyOf(supportedPIDS, supportedPIDS.length);
59 | }
60 | }
61 |
62 |
63 | public MonitorStatus getMonitorStatus() {
64 | return monitorStatus;
65 | }
66 |
67 |
68 | public void setMonitorStatus(final MonitorStatus monitorStatus) {
69 | this.monitorStatus = monitorStatus;
70 | }
71 |
72 |
73 | public Integer getDistanceSinceErrorCodesCleared() {
74 | return distanceSinceErrorCodesCleared;
75 | }
76 |
77 |
78 | public void setDistanceSinceErrorCodesCleared(final Integer distanceSinceErrorCodesCleared) {
79 | this.distanceSinceErrorCodesCleared = distanceSinceErrorCodesCleared;
80 | }
81 |
82 |
83 | public Integer getDistanceWithMalfunction() {
84 | return distanceWithMalfunction;
85 | }
86 |
87 |
88 | public void setDistanceWithMalfunction(final Integer distanceWithMalfunction) {
89 | this.distanceWithMalfunction = distanceWithMalfunction;
90 | }
91 |
92 |
93 | public Integer getEngineCoolantTemperature() {
94 | return engineCoolantTemperature;
95 | }
96 |
97 |
98 | public void setEngineCoolantTemperature(final Integer engineCoolantTemperature) {
99 | this.engineCoolantTemperature = engineCoolantTemperature;
100 | }
101 |
102 |
103 | public Double getEngineLoad() {
104 | return engineLoad;
105 | }
106 |
107 |
108 | public void setEngineLoad(final Double engineLoad) {
109 | this.engineLoad = engineLoad;
110 | }
111 |
112 |
113 | public Double getFuelInjectionTiming() {
114 | return fuelInjectionTiming;
115 | }
116 |
117 |
118 | public void setFuelInjectionTiming(final Double fuelInjectionTiming) {
119 | this.fuelInjectionTiming = fuelInjectionTiming;
120 | }
121 |
122 |
123 | public Double getFuelLevelInput() {
124 | return fuelLevelInput;
125 | }
126 |
127 |
128 | public void setFuelLevelInput(final Double fuelLevelInput) {
129 | this.fuelLevelInput = fuelLevelInput;
130 | }
131 |
132 |
133 | public Double getFuelRate() {
134 | return fuelRate;
135 | }
136 |
137 |
138 | public void setFuelRate(final Double fuelRate) {
139 | this.fuelRate = fuelRate;
140 | }
141 |
142 |
143 | public FuelStatus getFuelStatus() {
144 | return fuelStatus;
145 | }
146 |
147 |
148 | public void setFuelStatus(final FuelStatus fuelStatus) {
149 | this.fuelStatus = fuelStatus;
150 | }
151 |
152 |
153 | public Double getFuelTrimPercentShortTerm() {
154 | return fuelTrimPercentShortTerm;
155 | }
156 |
157 |
158 | public void setFuelTrimPercentShortTerm(final Double fuelTrimPercentShortTerm) {
159 | this.fuelTrimPercentShortTerm = fuelTrimPercentShortTerm;
160 | }
161 |
162 |
163 | public Double getFuelTrimPercentLongTerm() {
164 | return fuelTrimPercentLongTerm;
165 | }
166 |
167 |
168 | public void setFuelTrimPercentLongTerm(final Double fuelTrimPercentLongTerm) {
169 | this.fuelTrimPercentLongTerm = fuelTrimPercentLongTerm;
170 | }
171 |
172 |
173 | public Integer getIntakeAirTemperature() {
174 | return intakeAirTemperature;
175 | }
176 |
177 |
178 | public void setIntakeAirTemperature(final Integer intakeAirTemperature) {
179 | this.intakeAirTemperature = intakeAirTemperature;
180 | }
181 |
182 |
183 | public void setFaults(final List faults) {
184 | this.faults = faults;
185 | }
186 |
187 |
188 | public List getFaults() {
189 | if (this.faults == null) {
190 | return null;
191 | }
192 | return Collections.unmodifiableList(this.faults);
193 | }
194 |
195 |
196 | public AirStatus getSecondaryAirStatus() {
197 | return secondaryAirStatus;
198 | }
199 |
200 |
201 | public void setSecondaryAirStatus(final AirStatus secondaryAirStatus) {
202 | this.secondaryAirStatus = secondaryAirStatus;
203 | }
204 |
205 |
206 | public EcuCompatibility getEcuCompatibility() {
207 | return ecuCompatibility;
208 | }
209 |
210 |
211 | public void setEcuCompatibility(final EcuCompatibility ecuCompatibility) {
212 | this.ecuCompatibility = ecuCompatibility;
213 | }
214 |
215 |
216 | public Double getCatalystTemperatureSensor1() {
217 | return catalystTemperatureSensor1;
218 | }
219 |
220 |
221 | public void setCatalystTemperatureSensor1(final Double catalystTemperatureSensor1) {
222 | this.catalystTemperatureSensor1 = catalystTemperatureSensor1;
223 | }
224 |
225 |
226 | public Double getCatalystTemperatureSensor2() {
227 | return catalystTemperatureSensor2;
228 | }
229 |
230 |
231 | public void setCatalystTemperatureSensor2(final Double catalystTemperatureSensor2) {
232 | this.catalystTemperatureSensor2 = catalystTemperatureSensor2;
233 | }
234 |
235 |
236 | public Integer getAmbientAirTemperature() {
237 | return ambientAirTemperature;
238 | }
239 |
240 |
241 | public void setAmbientAirTemperature(final Integer ambientAirTemperature) {
242 | this.ambientAirTemperature = ambientAirTemperature;
243 | }
244 |
245 |
246 | public Integer getEngineOilTemperature() {
247 | return engineOilTemperature;
248 | }
249 |
250 |
251 | public void setEngineOilTemperature(final Integer engineOilTemperature) {
252 | this.engineOilTemperature = engineOilTemperature;
253 | }
254 |
255 |
256 | public Integer getIntakeAirTemperatureSensor() {
257 | return intakeAirTemperatureSensor;
258 | }
259 |
260 |
261 | public void setIntakeAirTemperatureSensor(final Integer intakeAirTemperatureSensor) {
262 | this.intakeAirTemperatureSensor = intakeAirTemperatureSensor;
263 | }
264 |
265 |
266 | public Integer getExhaustGasRecirculationTemperature() {
267 | return exhaustGasRecirculationTemperature;
268 | }
269 |
270 |
271 | public void setExhaustGasRecirculationTemperature(final Integer exhaustGasRecirculationTemperature) {
272 | this.exhaustGasRecirculationTemperature = exhaustGasRecirculationTemperature;
273 | }
274 |
275 |
276 | public Integer getManifoldSurfaceTemperature() {
277 | return manifoldSurfaceTemperature;
278 | }
279 |
280 |
281 | public void setManifoldSurfaceTemperature(final Integer manifoldSurfaceTemperature) {
282 | this.manifoldSurfaceTemperature = manifoldSurfaceTemperature;
283 | }
284 |
285 |
286 | public Double getCommandedEgr() {
287 | return commandedEgr;
288 | }
289 |
290 |
291 | public void setCommandedEgr(final Double commandedEgr) {
292 | this.commandedEgr = commandedEgr;
293 | }
294 |
295 |
296 | public Double getEgrError() {
297 | return egrError;
298 | }
299 |
300 |
301 | public void setEgrError(final Double egrError) {
302 | this.egrError = egrError;
303 | }
304 |
305 |
306 | public Double getEthanolFuel() {
307 | return ethanolFuel;
308 | }
309 |
310 |
311 | public void setEthanolFuel(final Double ethanolFuel) {
312 | this.ethanolFuel = ethanolFuel;
313 | }
314 |
315 | }
316 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/Response.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | /**
7 | * OBD2 protocol response.
8 | *
9 | * @author David Matějček
10 | */
11 | public class Response {
12 |
13 | private final Mode mode;
14 | private final PID pid;
15 | private final String[] data;
16 | private boolean error;
17 |
18 |
19 | /**
20 | * Only a simple constructor.
21 | *
22 | * @param error - if true, data are an error message
23 | * @param mode - same as the mode of the request.
24 | * @param pid - same as the pid of the request.
25 | * @param data - returned data.
26 | */
27 | public Response(final boolean error, final Mode mode, final PID pid, final String... data) {
28 | this.mode = mode;
29 | this.pid = pid;
30 | this.data = data == null ? new String[1] : data;
31 | this.error = error;
32 | }
33 |
34 |
35 | /**
36 | * @return a data part of the response - several lines of hex numbers separated by a space.
37 | */
38 | public String[] getData() {
39 | return data;
40 | }
41 |
42 |
43 | /**
44 | * @return true if the data contains only error message.
45 | */
46 | public boolean isError() {
47 | return error;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/obd2/ResponseWithNoData.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 |
7 | /**
8 | * @author David Matějček
9 | *
10 | */
11 | public class ResponseWithNoData extends Response {
12 |
13 | /**
14 | * @param mode
15 | * @param pid
16 | */
17 | public ResponseWithNoData(Mode mode, PID pid) {
18 | super(true, mode, pid);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/output/OutputFileWriter.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.output;
5 |
6 | import java.io.Closeable;
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.OutputStreamWriter;
11 | import java.nio.charset.Charset;
12 |
13 | import org.apache.commons.io.IOUtils;
14 | import org.apache.commons.lang3.StringUtils;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | /**
19 | * @author David Matějček
20 | */
21 | public class OutputFileWriter implements Closeable {
22 |
23 | private static final Logger LOG = LoggerFactory.getLogger(OutputFileWriter.class);
24 |
25 | private final OutputStreamWriter writer;
26 | private final File outputFile;
27 |
28 |
29 | /**
30 | * Initializes the output.
31 | *
32 | * @param outputFile
33 | */
34 | public OutputFileWriter(final File outputFile) {
35 | this.outputFile = outputFile;
36 | this.writer = openWriter(outputFile);
37 | }
38 |
39 |
40 | private OutputStreamWriter openWriter(final File file) {
41 | LOG.trace("openWriter(outputFile={})", file);
42 |
43 | FileOutputStream stream = null;
44 | try {
45 | stream = new FileOutputStream(file, true);
46 | return new OutputStreamWriter(stream, Charset.forName("UTF-8"));
47 | } catch (final IOException e) {
48 | IOUtils.closeQuietly(stream);
49 | throw new IllegalArgumentException("Cannot write the report to the outputFile: " + file, e);
50 | }
51 | }
52 |
53 |
54 | /**
55 | * Writes a header.
56 | *
57 | * @param header
58 | */
59 | public void writeHeader(final String header) {
60 | LOG.debug("writeHeader(header={})", header);
61 | try {
62 | this.writer.write('\n');
63 | final String lineSeparator = StringUtils.repeat("-", header.length()) + '\n';
64 | this.writer.write(lineSeparator);
65 | this.writer.write(header + '\n');
66 | this.writer.write(lineSeparator);
67 | } catch (IOException e) {
68 | throw new IllegalStateException("Cannot write to the outputFile: " + outputFile, e);
69 | }
70 | }
71 |
72 |
73 | /**
74 | * Writes parameters into the single line in the outputFile.
75 | *
76 | * @param label
77 | * @param value - may be null, then -- are written.
78 | * @param unit - may be null, then not written.
79 | */
80 | public void writeData(final String label, final Object value, final String unit) {
81 | LOG.debug("writeData(label={}, value={}, unit={})", label, value, unit);
82 |
83 | final StringBuilder string = new StringBuilder(256);
84 | string.append(label).append(": ");
85 | if (value == null) {
86 | string.append("--");
87 | } else {
88 | string.append(value);
89 | }
90 | if (unit != null) {
91 | string.append(' ').append(unit);
92 | }
93 | string.append('\n');
94 |
95 | try {
96 | this.writer.write(string.toString());
97 | } catch (IOException e) {
98 | throw new IllegalStateException("Cannot write to the outputFile: " + outputFile, e);
99 | }
100 | }
101 |
102 |
103 | /**
104 | * Silently closes the outputFile.
105 | */
106 | public void close() {
107 | IOUtils.closeQuietly(writer);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/output/ReportFileWriter.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.output;
5 |
6 | import java.io.File;
7 | import java.text.SimpleDateFormat;
8 |
9 | import org.apache.commons.io.IOUtils;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import cardiag.obd2.Report;
14 |
15 | /**
16 | * @author David Matějček
17 | */
18 | public class ReportFileWriter {
19 |
20 | private static final Logger LOG = LoggerFactory.getLogger(ReportFileWriter.class);
21 |
22 | private static final String DEGREES = "°";
23 | private static final String DEGREES_OF_CENTIGRADE = "°C";
24 | private static final String KILOMETERS = "km";
25 | private static final String PERCENTS = "%";
26 |
27 | private final File outputFile;
28 |
29 |
30 | /**
31 | * @param outputFile
32 | */
33 | public ReportFileWriter(final File outputFile) {
34 | this.outputFile = outputFile;
35 | }
36 |
37 |
38 | /**
39 | * Opens the file, writes the report and closes the file.
40 | *
41 | * @param report
42 | */
43 | public void write(final Report report) {
44 | final OutputFileWriter writer = new OutputFileWriter(outputFile);
45 | try {
46 | write(writer, report);
47 | } finally {
48 | IOUtils.closeQuietly(writer);
49 | }
50 | }
51 |
52 |
53 | private void write(final OutputFileWriter writer, final Report report) {
54 | LOG.trace("write(writer, report)");
55 |
56 | writer.writeHeader("Basic info");
57 |
58 | final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy, HH:mm:ss.SSS");
59 | writer.writeData("Time:", sdf.format(report.getTimestamp()), null);
60 | writer.writeData("ECU compatibility", report.getEcuCompatibility(), null);
61 | writer.writeData("Supported PIDs", report.getSupportedPIDS(), null);
62 | writer.writeData("Monitor status", report.getMonitorStatus(), null);
63 | writer.writeData("Ambient temperature", report.getAmbientAirTemperature(), DEGREES_OF_CENTIGRADE);
64 |
65 | writer.writeHeader("Errors");
66 | writer.writeData("Distance since errors cleared", report.getDistanceSinceErrorCodesCleared(), KILOMETERS);
67 | writer.writeData("Distance with malfunction", report.getDistanceWithMalfunction(), KILOMETERS);
68 | writer.writeData("Reported faults", report.getFaults(), null);
69 |
70 | writer.writeHeader("Engine Temperatures");
71 | writer.writeData("Engine coolant temperature", report.getEngineCoolantTemperature(), DEGREES_OF_CENTIGRADE);
72 | writer.writeData("Engine oil temperature", report.getEngineOilTemperature(), DEGREES_OF_CENTIGRADE);
73 | writer.writeData("Intake air temperature", report.getIntakeAirTemperature(), DEGREES_OF_CENTIGRADE);
74 | writer.writeData("Intake air temperature sensor", report.getIntakeAirTemperatureSensor(), DEGREES_OF_CENTIGRADE);
75 | writer.writeData("Catalyst temperature - sensor1", report.getCatalystTemperatureSensor1(), DEGREES_OF_CENTIGRADE);
76 | writer.writeData("Catalyst temperature - sensor2", report.getCatalystTemperatureSensor2(), DEGREES_OF_CENTIGRADE);
77 | writer.writeData("Exhaust gas recirculation temperature", report.getExhaustGasRecirculationTemperature(),
78 | DEGREES_OF_CENTIGRADE);
79 | writer.writeData("Manifold surface temperature", report.getManifoldSurfaceTemperature(), DEGREES_OF_CENTIGRADE);
80 |
81 | writer.writeHeader("Engine timing and fuel");
82 | writer.writeData("Engine load", report.getEngineLoad(), PERCENTS);
83 | writer.writeData("Fuel injection timing", report.getFuelInjectionTiming(), DEGREES);
84 | writer.writeData("Secondary air status", report.getSecondaryAirStatus(), null);
85 | writer.writeData("Fuel status", report.getFuelStatus(), null);
86 | writer.writeData("Fuel rate", report.getFuelRate(), "L/h");
87 | writer.writeData("Fuel level input", report.getFuelLevelInput(), PERCENTS);
88 | writer.writeData("Fuel trim pecent in long term", report.getFuelTrimPercentLongTerm(), PERCENTS);
89 | writer.writeData("Fuel trim pecent in short term", report.getFuelTrimPercentShortTerm(), PERCENTS);
90 | writer.writeData("Commanded EGR", report.getCommandedEgr(), PERCENTS);
91 | writer.writeData("EGR error", report.getEgrError(), PERCENTS);
92 | writer.writeData("Ethanol fuel", report.getEthanolFuel(), PERCENTS);
93 |
94 | // writer.writeData("", report.get, "");
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/serial/PortCommunication.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | import java.io.Closeable;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | import org.apache.commons.lang3.StringUtils;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 |
15 | import jssc.SerialPort;
16 | import jssc.SerialPortException;
17 |
18 | /**
19 | * Communication with the serial port device.
20 | *
21 | * @author David Matějček
22 | */
23 | public class PortCommunication implements Closeable {
24 |
25 | private static final Logger LOG = LoggerFactory.getLogger(PortCommunication.class);
26 | private static final String RESPONSE_OK = "OK";
27 | private static final String RESPONSE_TERMINALCHAR = ">";
28 |
29 | private final PortConfiguration cfg;
30 | private final SerialPort port;
31 |
32 |
33 | /**
34 | * Initializes and opens the port.
35 | *
36 | * @param cfg - a port configuration
37 | * @throws PortCommunicationException - cannot initialize the communication.
38 | */
39 | public PortCommunication(final PortConfiguration cfg) throws PortCommunicationException {
40 | LOG.debug("PortCommunication(cfg={})", cfg);
41 | try {
42 | this.cfg = cfg;
43 | this.port = new SerialPort(cfg.getPortName());
44 | if (!this.port.openPort()) {
45 | throw new PortCommunicationException("Cannot open port!");
46 | }
47 | // see page 7 in elm327.pdf - do not change!
48 | if (!this.port.setParams(38400, 8, 1, 0, true, true)) {
49 | throw new PortCommunicationException("Setting parameters was unsuccessful!");
50 | }
51 | } catch (final SerialPortException e) {
52 | throw new PortCommunicationException(e);
53 | }
54 | }
55 |
56 |
57 | /**
58 | * Reads responses line by line from the buffer until {@value #RESPONSE_TERMINALCHAR} comes or
59 | * timeout occurs.
60 | *
61 | * @return a response, never null and never empty.
62 | * @throws PortCommunicationException - if there was no response or prompt was missing.
63 | */
64 | public String readResponse(final String request) throws PortCommunicationException {
65 | LOG.debug("readResponse(request={})", request);
66 |
67 | try {
68 | final String response = readAll(request);
69 | LOG.debug("Received response: {}", response);
70 | if (response == null) {
71 | throw new PortCommunicationException("Retrieved no response from the port.");
72 | }
73 | return response;
74 | } catch (final SerialPortException e) {
75 | throw new PortCommunicationException(e);
76 | }
77 | }
78 |
79 |
80 | private String readAll(final String request) throws SerialPortException {
81 | LOG.trace("readAll(request={})", request);
82 | final StringBuilder buffer = new StringBuilder(256);
83 | final long start = System.currentTimeMillis();
84 | final long timeout = cfg.getCommandTimeout();
85 | while (true) {
86 | if (start + timeout < System.currentTimeMillis()) {
87 | throw new PortCommunicationException(String.format("Timeout %d ms occured.", timeout));
88 | }
89 | if (this.port.getInputBufferBytesCount() == 0) {
90 | Thread.yield();
91 | continue;
92 | }
93 | final String string = StringUtils.trimToNull(this.port.readString());
94 | LOG.trace("response string={}", string);
95 | if (string == null) {
96 | sleep(100L);
97 | continue;
98 | }
99 | buffer.append(string);
100 | if (StringUtils.endsWith(string, RESPONSE_TERMINALCHAR)) {
101 | LOG.trace("Response terminated with the character {}. Character deleted.", RESPONSE_TERMINALCHAR);
102 | buffer.setLength(buffer.length() - 1);
103 | if (buffer.indexOf(request) == 0) {
104 | buffer.replace(0, request.length(), "");
105 | }
106 | return buffer.toString().trim();
107 | }
108 | }
109 | }
110 |
111 |
112 | private void sleep(final long timeInMillis) {
113 | try {
114 | Thread.sleep(100L);
115 | } catch (final InterruptedException e) {
116 | LOG.warn("Interrupted.");
117 | }
118 | }
119 |
120 |
121 | /**
122 | * Writes command and arguments to the port.
123 | *
124 | * @param command - command and it's arguments.
125 | * @throws PortCommunicationException
126 | */
127 | public void writeln(final String... command) throws PortCommunicationException {
128 | LOG.debug("writeln(command={})", Arrays.toString(command));
129 | try {
130 | for (final String commandPart : command) {
131 | this.port.writeString(commandPart);
132 | }
133 | this.port.writeString("\r\n");
134 | } catch (final SerialPortException e) {
135 | throw new PortCommunicationException(e);
136 | }
137 | }
138 |
139 |
140 | /**
141 | * @param value
142 | * @return 1 for true, 0 for false.
143 | * @throws PortCommunicationException
144 | */
145 | protected final String translate(final boolean value) throws PortCommunicationException {
146 | LOG.trace("translate(value={})", value);
147 | return value ? "1" : "0";
148 | }
149 |
150 |
151 | /**
152 | * Checks for OK in port response.
153 | * @param request
154 | *
155 | * @throws PortCommunicationException
156 | */
157 | protected void checkOkResponse(final String request) throws PortCommunicationException {
158 | LOG.trace("checkOkResponse(request={})", request);
159 | final String response = readResponse(request);
160 | if (response == null || response.isEmpty()) {
161 | throw new PortCommunicationException("No response.");
162 | }
163 | if (!RESPONSE_OK.equalsIgnoreCase(response)) {
164 | throw new PortCommunicationException("Command unsuccessful! Response: '" + response + "'");
165 | }
166 | }
167 |
168 |
169 | /**
170 | * Executes an AT command and returns an answer.
171 | *
172 | * @param command - an AT command to execute.
173 | * @return an answer of the command.
174 | * @throws PortCommunicationException
175 | */
176 | public String at(final String command) throws PortCommunicationException {
177 | LOG.info("at(command={})", command);
178 | writeln("AT", command);
179 | return readResponse("AT".concat(command));
180 | }
181 |
182 |
183 | /**
184 | * Sends ATE signal and sets the command echo on/off
185 | *
186 | * @param on
187 | * @throws PortCommunicationException
188 | */
189 | public void setEcho(final boolean on) throws PortCommunicationException {
190 | LOG.debug("setEcho(on={})", on);
191 | final String onTranslated = translate(on);
192 | writeln("ATE", onTranslated);
193 | checkOkResponse("ATE".concat(onTranslated));
194 | }
195 |
196 |
197 | /**
198 | * Sends ATL signal and sets the line termination on/off
199 | *
200 | * @param on
201 | * @throws PortCommunicationException
202 | */
203 | public void setLineTermination(final boolean on) throws PortCommunicationException {
204 | LOG.debug("setLineTermination(on={})", on);
205 | final String onTranslated = translate(on);
206 | writeln("ATL", onTranslated);
207 | checkOkResponse("ATL".concat(onTranslated));
208 | }
209 |
210 |
211 | /**
212 | * Sends AT Z command, resets the communication.
213 | *
214 | * @throws PortCommunicationException
215 | */
216 | public void reset() throws PortCommunicationException {
217 | LOG.debug("reset()");
218 | final String response = at("Z");
219 | // with echo on the first line will be ATZ (sent command)
220 | // another line will be a device type identification.
221 | if (response == null) {
222 | throw new PortCommunicationException("Command unsuccessful! Response: " + response);
223 | }
224 | }
225 |
226 |
227 | /**
228 | * Closes the port.
229 | */
230 | @Override
231 | public void close() {
232 | try {
233 | this.port.closePort();
234 | } catch (final SerialPortException e) {
235 | throw new IllegalStateException("Cannot close the port.", e);
236 | }
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/serial/PortCommunicationException.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | import jssc.SerialPortException;
7 |
8 | /**
9 | * Port communication exception. Command was not successfully executed.
10 | *
11 | * @author David Matějček
12 | */
13 | public class PortCommunicationException extends RuntimeException {
14 |
15 | private static final long serialVersionUID = 5530145486386126281L;
16 |
17 |
18 | /**
19 | * @param message - a short description of exception
20 | */
21 | public PortCommunicationException(final String message) {
22 | super(message);
23 | }
24 |
25 |
26 | /**
27 | * @param cause - a cause of the exception.
28 | */
29 | public PortCommunicationException(final SerialPortException cause) {
30 | super(cause);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/serial/PortConfiguration.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | /**
7 | * @author David Matějček
8 | */
9 | public class PortConfiguration {
10 |
11 | private String portName;
12 | private Long commandTimeout;
13 |
14 |
15 | public String getPortName() {
16 | return portName;
17 | }
18 |
19 |
20 | public void setPortName(final String portName) {
21 | this.portName = portName;
22 | }
23 |
24 |
25 | public Long getCommandTimeout() {
26 | return commandTimeout;
27 | }
28 |
29 |
30 | /**
31 | * @param commandTimeout - time in millis, must not be null.
32 | */
33 | public void setCommandTimeout(final Long commandTimeout) {
34 | this.commandTimeout = commandTimeout;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/serial/SerialUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | import org.apache.commons.lang3.StringUtils;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | import jssc.SerialPortList;
15 |
16 |
17 | /**
18 | * @author David Matějček
19 | */
20 | public class SerialUtils {
21 | private static final Logger LOG = LoggerFactory.getLogger(SerialUtils.class);
22 |
23 | public static List getPortNames() {
24 | final String[] portNames = SerialPortList.getPortNames();
25 | if (portNames == null || portNames.length == 0) {
26 | return Collections.emptyList();
27 | }
28 |
29 | return Arrays.asList(portNames);
30 | }
31 |
32 | /**
33 | * @param line
34 | * @return
35 | */
36 | public static boolean[] convertHexToBooleanArray(final String... line) {
37 | LOG.debug("convertHexToBooleanArray(line={})", (Object) line);
38 | final boolean[] bools = new boolean[line.length * 8];
39 | int k = 0;
40 | for (int i = 0; i < line.length; i++) {
41 | String hex = line[i];
42 | int val = Integer.parseInt(hex, 16);
43 | String binary = StringUtils.leftPad(Integer.toBinaryString(val), 8, '0');
44 | for (int j = 0; j < binary.length(); j++) {
45 | bools[k++] = binary.charAt(j) == '1';
46 | }
47 | }
48 | return bools;
49 | }
50 |
51 |
52 | public static int toInteger(boolean... bools) {
53 | int base = 1;
54 | int index = bools.length - 1;
55 | int value = 0;
56 | while(index >= 0) {
57 | value += bools[index] ? base : 0;
58 | base *= 2;
59 | index--;
60 | }
61 | return value;
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/user/Action.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 |
7 | /**
8 | * @author David Matějček
9 | */
10 | public enum Action {
11 |
12 | REPORT,
13 | WATCH,
14 | CLEAR_TROUBLE_CODES;
15 |
16 |
17 | public static Action parse(final String str) {
18 | for (Action action : Action.values()) {
19 | if (action.name().equalsIgnoreCase(str)) {
20 | return action;
21 | }
22 | }
23 | throw new IllegalArgumentException("Invalid action: " + str);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/user/ConsoleCommunication.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 |
7 | /**
8 | * @author David Matějček
9 | *
10 | */
11 | public interface ConsoleCommunication {
12 |
13 | void format(String fmt, Object... args);
14 | void flush();
15 | String readLine();
16 | String readLine(String fmt, Object... args);
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/user/ConsoleWrapper.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 | import java.io.Console;
7 |
8 | /**
9 | * @author David Matějček
10 | */
11 | public class ConsoleWrapper implements ConsoleCommunication {
12 |
13 | private final Console console;
14 |
15 |
16 | public ConsoleWrapper(final Console console) {
17 | if (console == null) {
18 | throw new IllegalArgumentException("Console not present, program cannot interact with user!");
19 | }
20 | this.console = console;
21 | }
22 |
23 |
24 | @Override
25 | public void format(String fmt, Object... args) {
26 | this.console.format(fmt, args);
27 | }
28 |
29 |
30 | @Override
31 | public void flush() {
32 | this.console.flush();
33 | }
34 |
35 |
36 | @Override
37 | public String readLine() {
38 | return this.console.readLine();
39 | }
40 |
41 |
42 | @Override
43 | public String readLine(String fmt, Object... args) {
44 | return this.console.readLine(fmt, args);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/cardiag/user/UserCommunication.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 | import java.util.List;
7 |
8 | import org.apache.commons.lang3.StringUtils;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import cardiag.serial.PortConfiguration;
13 |
14 | /**
15 | * @author David Matějček
16 | */
17 | public class UserCommunication {
18 |
19 | private static final Logger LOG = LoggerFactory.getLogger(UserCommunication.class);
20 | private static final String EXIT = "X";
21 |
22 | private final ConsoleCommunication console;
23 |
24 |
25 | /**
26 | * @param console
27 | */
28 | public UserCommunication(final ConsoleCommunication console) {
29 | LOG.trace("UserCommunication(console={})", console);
30 | if (console == null) {
31 | throw new IllegalArgumentException("Console not present, program cannot interact with user!");
32 | }
33 | this.console = console;
34 | }
35 |
36 |
37 | /**
38 | * @param portNames
39 | * @return
40 | */
41 | public PortConfiguration readPortConfiguration(final List portNames) {
42 | LOG.debug("readPortConfiguration(portNames={})", portNames);
43 | final PortConfiguration cfg = new PortConfiguration();
44 | final String selectedPortName = selectPort(portNames);
45 | cfg.setPortName(selectedPortName);
46 | cfg.setCommandTimeout(10000L);
47 | return cfg;
48 | }
49 |
50 |
51 | protected String selectPort(final List portNames) {
52 | LOG.debug("selectPort(portNames={})", portNames);
53 |
54 | // FIXME: program should not end in the future. User maybe forgot to connect the cable.
55 | if (portNames.isEmpty()) {
56 | throw new IllegalStateException("No serial port to select.");
57 | }
58 | while (true) {
59 | console.format("----\nSelect the port (type number):\n");
60 | for (int i = 0; i < portNames.size(); i++) {
61 | final String portName = portNames.get(i);
62 | console.format("%d: %s\n", i, portName);
63 | }
64 | console.format("X: Exit\n");
65 | console.flush();
66 | final String selectedPort = StringUtils.trimToNull(console.readLine("Port: "));
67 | if (StringUtils.isNumeric(selectedPort)) {
68 | final String portName = portNames.get(Integer.valueOf(selectedPort));
69 | if (portName != null) {
70 | return portName;
71 | }
72 | }
73 | if (EXIT.equalsIgnoreCase(selectedPort)) {
74 | return null;
75 | }
76 | }
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.FILEdevelop=org.apache.log4j.FileAppender
2 | log4j.appender.FILEdevelop.File=development.log
3 | log4j.appender.FILEdevelop.encoding=utf-8
4 | log4j.appender.FILEdevelop.layout=org.apache.log4j.PatternLayout
5 | log4j.appender.FILEdevelop.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%t] %25c{3}:%-4L: %m%n
6 | log4j.appender.FILEdevelop.Append=true
7 | log4j.appender.FILEdevelop.Threshold=TRACE
8 |
9 | log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
10 | log4j.appender.STDOUT.Target=System.out
11 | log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
12 | log4j.appender.STDOUT.layout.ConversionPattern=%m%n
13 | log4j.appender.STDOUT.Threshold=INFO
14 |
15 | log4j.rootLogger=INFO, STDOUT, FILEdevelop
16 | log4j.logger.cardiag=TRACE
17 | #log4j.additivity.cardiag=false
18 |
19 | #log4j.debug=on
20 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/obd2/OBD2StandardITest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.obd2;
5 |
6 | import static org.junit.Assert.assertEquals;
7 | import static org.junit.Assert.assertNotNull;
8 |
9 | import org.junit.AfterClass;
10 | import org.junit.BeforeClass;
11 | import org.junit.Ignore;
12 | import org.junit.Test;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | import cardiag.test.TestConfiguration;
17 |
18 | /**
19 | * @author David Matějček
20 | */
21 | public class OBD2StandardITest {
22 |
23 | private static final Logger LOG = LoggerFactory.getLogger(OBD2StandardITest.class);
24 |
25 | private static final TestConfiguration testConfig = new TestConfiguration();
26 | private static OBD2Standard obd2;
27 |
28 |
29 | @BeforeClass
30 | public static void initCommunication() throws OBD2Exception {
31 | obd2 = new OBD2Standard(testConfig.getPortConfiguration());
32 | }
33 |
34 |
35 | @AfterClass
36 | public static void close() {
37 | if (obd2 != null) {
38 | obd2.close();
39 | }
40 | }
41 |
42 |
43 | /**
44 | * Received response: [41 03 01 00]
45 | *
46 | * @throws OBD2Exception
47 | */
48 | @Test
49 | public void testSupportedPIDs() throws OBD2Exception {
50 | final boolean[] pids = obd2.getSupportedPIDs();
51 | LOG.debug("pids={}", pids);
52 | assertNotNull(pids);
53 | assertEquals(32, pids.length);
54 | }
55 |
56 |
57 | @Test
58 | public void testFuelStatus() throws OBD2Exception {
59 | assertEquals(FuelStatus.CLOSED_USING_OXYGEN, obd2.getFuelStatus(false));
60 | // assertEquals(FuelStatus.CLOSED_USING_OXYGEN, obd2.getFuelStatus(true));
61 | }
62 |
63 |
64 | @Test
65 | public void testGetErrorReport() throws OBD2Exception {
66 | LOG.info("Error report: {}", obd2.getErrorReport());
67 | }
68 |
69 |
70 | // @Test
71 | // public void testClearTroubleCodes() throws OBD2Exception {
72 | // obd2.clearTroubleCodes();
73 | // LOG.warn("Trouble codes cleared!");
74 | // }
75 |
76 | /**
77 | * [49 02 01 00 00 00 FF, 49 02 02 FF FF FF FF, 49 02 03 FF FF FF FF, 49 02 04 FF FF FF FF, 49 02
78 | * 05 FF FF FF FF]
79 | *
80 | * @throws OBD2Exception
81 | */
82 | @Test
83 | public void testGetVIN() throws OBD2Exception {
84 | LOG.info("VIN={}", obd2.getVIN());
85 | }
86 |
87 |
88 | /**
89 | * [41 04 00] = 0.0%
90 | *
91 | * @throws OBD2Exception
92 | */
93 | @Test
94 | public void testGetEngineLoad() throws OBD2Exception {
95 | LOG.info("engine load: {}%", obd2.getEngineLoad(false));
96 | // obd2.getEngineLoad(true);
97 | }
98 |
99 |
100 | /**
101 | * [41 05 37] = 15°C
102 | *
103 | * @throws OBD2Exception
104 | */
105 | @Test
106 | public void testGetEngineCoolantTemp() throws OBD2Exception {
107 | LOG.info("engine coolant temperature: {}°C", obd2.getEngineCoolantTemperature(false));
108 | // obd2.getEngineCoolantTemperature(true);
109 | }
110 |
111 |
112 | /**
113 | * [41 0F 36] = 14°C
114 | *
115 | * @throws OBD2Exception
116 | */
117 | @Test
118 | public void testGetIntakeAirTemp() throws OBD2Exception {
119 | LOG.info("intake air temperature: {}°C", obd2.getIntakeAirTemperature(false));
120 | }
121 |
122 |
123 | /**
124 | * [41 06 80] = 0.0%
125 | * [41 07 80] = 0.0%
126 | *
127 | * @throws OBD2Exception
128 | */
129 | @Test
130 | public void testGetFuelTrimPercent() throws OBD2Exception {
131 | LOG.info("fuel trim bank1, short term: {}%", obd2.getFuelTrimPercent(false, false, 1));
132 | LOG.info("fuel trim bank1, long term: {}%", obd2.getFuelTrimPercent(false, true, 1));
133 | // LOG.info("fuel trim bank2, short term: {}%", obd2.getFuelTrimPercent(false, false, 2));
134 | // LOG.info("fuel trim bank2, long term: {}%", obd2.getFuelTrimPercent(false, true, 2));
135 | }
136 |
137 |
138 | /**
139 | * [41 21 00 00]
140 | *
141 | * @throws OBD2Exception
142 | */
143 | @Test
144 | public void testGetDistanceWithMalfunction() throws OBD2Exception {
145 | LOG.info("distance with malfunction: {}", obd2.getDistanceWithMalfunction(false));
146 | }
147 |
148 |
149 | @Test
150 | @Ignore("For Kalina causes error response 7F 01 12")
151 | public void testGetDistanceSinceCodesCleared() throws OBD2Exception {
152 | LOG.info("distance since codes cleared: {}", obd2.getDistanceSinceCodesCleared(false));
153 | }
154 |
155 |
156 | @Test
157 | @Ignore("For Kalina causes error response 7F 01 12")
158 | public void testGetFuelLevelInput() throws OBD2Exception {
159 | LOG.info("fuel level input: {}%", obd2.getFuelLevelInput(false));
160 | }
161 |
162 |
163 | @Test
164 | @Ignore("For Kalina causes error response 7F 01 12")
165 | public void testGetFuelInjectionTiming() throws OBD2Exception {
166 | LOG.info("fuel injection timing: {}°", obd2.getFuelInjectionTiming(false));
167 | }
168 |
169 |
170 | @Test
171 | // @Ignore("For Kalina causes error response 7F 01 12")
172 | public void testGetFuelRate() throws OBD2Exception {
173 | LOG.info("fuel rate: {} L/h", obd2.getFuelRate(false));
174 | }
175 |
176 |
177 | @Test
178 | /**
179 | * [41 01 03 07 65 00]
180 | * [41 01 00 07 65 65]
181 | * @throws OBD2Exception
182 | */
183 | public void testGetMonitorStatus() throws OBD2Exception {
184 | LOG.info("monitor status: {}", obd2.getMonitorStatus());
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/serial/PortCommunicationIT.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | import static org.junit.Assert.assertEquals;
7 |
8 | import org.junit.After;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 |
12 | import cardiag.test.TestConfiguration;
13 |
14 | /**
15 | * @author David Matějček
16 | */
17 | public class PortCommunicationIT {
18 |
19 | private final TestConfiguration testConfig = new TestConfiguration();
20 | private PortCommunication comm;
21 |
22 |
23 | @Before
24 | public void initCommunication() throws PortCommunicationException {
25 | this.comm = new PortCommunication(testConfig.getPortConfiguration());
26 | this.comm.reset();
27 | this.comm.setEcho(false);
28 | this.comm.setLineTermination(false);
29 | }
30 |
31 |
32 | @After
33 | public void close() {
34 | this.comm.close();
35 | }
36 |
37 |
38 | @Test
39 | public void testAt() throws PortCommunicationException {
40 | // FIXME: should be parametrized - other tester can have another hardware.
41 | assertEquals("ELM327 v1.5", comm.at("I"));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/serial/SerialUtilsIT.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.serial;
5 |
6 | import static org.junit.Assert.*;
7 |
8 | import java.util.List;
9 |
10 | import org.junit.Test;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 |
15 | /**
16 | * @author David Matějček
17 | */
18 | public class SerialUtilsIT {
19 | private static final Logger LOG = LoggerFactory.getLogger(SerialUtilsIT.class);
20 |
21 | @Test
22 | public void testGetPortNames() {
23 | final List names = SerialUtils.getPortNames();
24 | LOG.info("port names={}", names);
25 | assertNotNull(names);
26 | assertFalse("You must have at last one serial port. No serial port found.", names.isEmpty());
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/test/TestConfiguration.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.test;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.util.Properties;
9 |
10 | import org.apache.commons.io.IOUtils;
11 | import org.junit.Assert;
12 |
13 | import cardiag.serial.PortConfiguration;
14 |
15 |
16 | /**
17 | * @author David Matějček
18 | */
19 | public class TestConfiguration {
20 |
21 | private Properties properties;
22 |
23 |
24 | public TestConfiguration() {
25 | final InputStream stream = TestConfiguration.class.getClassLoader().getResourceAsStream("test.properties");
26 | try {
27 | Properties properties = new Properties();
28 | properties.load(stream);
29 | this.properties = properties;
30 | } catch (IOException e) {
31 | Assert.fail("Cannot load test.properties file");
32 | } finally {
33 | IOUtils.closeQuietly(stream);
34 | }
35 | }
36 |
37 |
38 | public String getPortName() {
39 | return properties.getProperty("serialPort");
40 | }
41 |
42 |
43 | public PortConfiguration getPortConfiguration() {
44 | final PortConfiguration cfg = new PortConfiguration();
45 | cfg.setPortName(getPortName());
46 | cfg.setCommandTimeout(10000L);
47 | return cfg;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/user/ConsoleMock.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 | import java.util.LinkedList;
7 |
8 | /**
9 | * @author David Matějček
10 | */
11 | public class ConsoleMock implements ConsoleCommunication {
12 |
13 | private LinkedList lines = new LinkedList();
14 |
15 |
16 | @Override
17 | public void format(String fmt, Object... args) {
18 | System.out.format(fmt, args);
19 | }
20 |
21 |
22 | @Override
23 | public void flush() {
24 | System.out.flush();
25 | }
26 |
27 |
28 | public void appendLine(final String line) {
29 | this.lines.add(line);
30 | }
31 |
32 |
33 | @Override
34 | public String readLine() {
35 | final String line = this.lines.pollFirst();
36 | System.out.println(line);
37 | return line;
38 | }
39 |
40 |
41 | @Override
42 | public String readLine(final String fmt, final Object... args) {
43 | format(fmt, args);
44 | return readLine();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/cardiag/user/UserCommunicationTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package cardiag.user;
5 |
6 | import static org.junit.Assert.*;
7 | import java.util.Arrays;
8 |
9 | import org.junit.Test;
10 |
11 | /**
12 | * @author David Matějček
13 | */
14 | public class UserCommunicationTest {
15 |
16 | @Test
17 | public void testSelectPort() {
18 | final ConsoleMock console = new ConsoleMock();
19 | final UserCommunication comm = new UserCommunication(console);
20 | final String[] ports = {"/dev/ttyUSB0", "/dev/ttyS10"};
21 | console.appendLine("0");
22 | String selectedPort = comm.selectPort(Arrays.asList(ports));
23 | assertEquals(ports[0], selectedPort);
24 | console.appendLine("1");
25 | selectedPort = comm.selectPort(Arrays.asList(ports));
26 | assertEquals(ports[1], selectedPort);
27 | console.appendLine("X");
28 | selectedPort = comm.selectPort(Arrays.asList(ports));
29 | assertNull(selectedPort);
30 | console.appendLine("SOMETHING STUPID");
31 | console.appendLine("X");
32 | selectedPort = comm.selectPort(Arrays.asList(ports));
33 | assertNull(selectedPort);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
2 | log4j.appender.STDOUT.Target=System.out
3 | log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
4 | log4j.appender.STDOUT.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%t] %25c{3}:%-4L: %m%n
5 | log4j.appender.STDOUT.Threshold=TRACE
6 |
7 | log4j.rootLogger=TRACE, STDOUT
8 | log4j.logger.cardiag=INFO
9 | #log4j.additivity.cardiag=false
10 |
--------------------------------------------------------------------------------
/src/test/resources/test.properties:
--------------------------------------------------------------------------------
1 | serialPort=${cardiag.test.serialPort}
2 |
--------------------------------------------------------------------------------