1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.obd2;
18 
19 import com.android.car.obd2.commands.AmbientAirTemperature;
20 import com.android.car.obd2.commands.CalculatedEngineLoad;
21 import com.android.car.obd2.commands.EngineCoolantTemperature;
22 import com.android.car.obd2.commands.EngineOilTemperature;
23 import com.android.car.obd2.commands.EngineRuntime;
24 import com.android.car.obd2.commands.FuelGaugePressure;
25 import com.android.car.obd2.commands.FuelSystemStatus;
26 import com.android.car.obd2.commands.FuelTankLevel;
27 import com.android.car.obd2.commands.FuelTrimCommand.Bank1LongTermFuelTrimCommand;
28 import com.android.car.obd2.commands.FuelTrimCommand.Bank1ShortTermFuelTrimCommand;
29 import com.android.car.obd2.commands.FuelTrimCommand.Bank2LongTermFuelTrimCommand;
30 import com.android.car.obd2.commands.FuelTrimCommand.Bank2ShortTermFuelTrimCommand;
31 import com.android.car.obd2.commands.RPM;
32 import com.android.car.obd2.commands.Speed;
33 import com.android.car.obd2.commands.ThrottlePosition;
34 import java.io.IOException;
35 import java.util.HashMap;
36 import java.util.Objects;
37 import java.util.Optional;
38 import java.util.Set;
39 
40 /**
41  * Base class of OBD2 command objects that query a "vehicle" and return an individual data point
42  * represented as a Java type.
43  *
44  * @param <ValueType> The Java type that represents the value of this command's output.
45  */
46 public abstract class Obd2Command<ValueType> {
47 
48     /**
49      * Abstract representation of an object whose job it is to receive the bytes read from the OBD2
50      * connection and return a Java representation of a command's value.
51      *
52      * @param <ValueType>
53      */
54     public interface OutputSemanticHandler<ValueType> {
getPid()55         int getPid();
56 
consume(IntegerArrayStream data)57         Optional<ValueType> consume(IntegerArrayStream data);
58     }
59 
60     public static final int LIVE_FRAME = 1;
61     public static final int FREEZE_FRAME = 2;
62 
63     private static final HashMap<Integer, OutputSemanticHandler<Integer>>
64             SUPPORTED_INTEGER_COMMANDS = new HashMap<>();
65     private static final HashMap<Integer, OutputSemanticHandler<Float>> SUPPORTED_FLOAT_COMMANDS =
66             new HashMap<>();
67 
addSupportedIntegerCommands( OutputSemanticHandler<Integer>.... integerOutputSemanticHandlers)68     private static void addSupportedIntegerCommands(
69             OutputSemanticHandler<Integer>... integerOutputSemanticHandlers) {
70         for (OutputSemanticHandler<Integer> integerOutputSemanticHandler :
71                 integerOutputSemanticHandlers) {
72             SUPPORTED_INTEGER_COMMANDS.put(
73                     integerOutputSemanticHandler.getPid(), integerOutputSemanticHandler);
74         }
75     }
76 
addSupportedFloatCommands( OutputSemanticHandler<Float>.... floatOutputSemanticHandlers)77     private static void addSupportedFloatCommands(
78             OutputSemanticHandler<Float>... floatOutputSemanticHandlers) {
79         for (OutputSemanticHandler<Float> floatOutputSemanticHandler :
80                 floatOutputSemanticHandlers) {
81             SUPPORTED_FLOAT_COMMANDS.put(
82                     floatOutputSemanticHandler.getPid(), floatOutputSemanticHandler);
83         }
84     }
85 
getSupportedIntegerCommands()86     public static Set<Integer> getSupportedIntegerCommands() {
87         return SUPPORTED_INTEGER_COMMANDS.keySet();
88     }
89 
getSupportedFloatCommands()90     public static Set<Integer> getSupportedFloatCommands() {
91         return SUPPORTED_FLOAT_COMMANDS.keySet();
92     }
93 
getIntegerCommand(int pid)94     public static OutputSemanticHandler<Integer> getIntegerCommand(int pid) {
95         return SUPPORTED_INTEGER_COMMANDS.get(pid);
96     }
97 
getFloatCommand(int pid)98     public static OutputSemanticHandler<Float> getFloatCommand(int pid) {
99         return SUPPORTED_FLOAT_COMMANDS.get(pid);
100     }
101 
102     static {
addSupportedFloatCommands( new AmbientAirTemperature(), new CalculatedEngineLoad(), new FuelTankLevel(), new Bank2ShortTermFuelTrimCommand(), new Bank2LongTermFuelTrimCommand(), new Bank1LongTermFuelTrimCommand(), new Bank1ShortTermFuelTrimCommand(), new ThrottlePosition())103         addSupportedFloatCommands(
104                 new AmbientAirTemperature(),
105                 new CalculatedEngineLoad(),
106                 new FuelTankLevel(),
107                 new Bank2ShortTermFuelTrimCommand(),
108                 new Bank2LongTermFuelTrimCommand(),
109                 new Bank1LongTermFuelTrimCommand(),
110                 new Bank1ShortTermFuelTrimCommand(),
111                 new ThrottlePosition());
addSupportedIntegerCommands( new EngineOilTemperature(), new EngineCoolantTemperature(), new FuelGaugePressure(), new FuelSystemStatus(), new RPM(), new EngineRuntime(), new Speed())112         addSupportedIntegerCommands(
113                 new EngineOilTemperature(),
114                 new EngineCoolantTemperature(),
115                 new FuelGaugePressure(),
116                 new FuelSystemStatus(),
117                 new RPM(),
118                 new EngineRuntime(),
119                 new Speed());
120     }
121 
122     protected final int mMode;
123     protected final OutputSemanticHandler<ValueType> mSemanticHandler;
124 
Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler)125     Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler) {
126         mMode = mode;
127         mSemanticHandler = Objects.requireNonNull(semanticHandler);
128     }
129 
run(Obd2Connection connection)130     public abstract Optional<ValueType> run(Obd2Connection connection) throws Exception;
131 
getPid()132     public int getPid() {
133         return mSemanticHandler.getPid();
134     }
135 
getLiveFrameCommand(OutputSemanticHandler handler)136     public static final <T> LiveFrameCommand<T> getLiveFrameCommand(OutputSemanticHandler handler) {
137         return new LiveFrameCommand<>(handler);
138     }
139 
getFreezeFrameCommand( OutputSemanticHandler handler, int frameId)140     public static final <T> FreezeFrameCommand<T> getFreezeFrameCommand(
141             OutputSemanticHandler handler, int frameId) {
142         return new FreezeFrameCommand<>(handler, frameId);
143     }
144 
145     /**
146      * An OBD2 command that returns live frame data.
147      *
148      * @param <ValueType> The Java type that represents the command's result type.
149      */
150     public static class LiveFrameCommand<ValueType> extends Obd2Command<ValueType> {
151         private static final int RESPONSE_MARKER = 0x41;
152 
LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler)153         LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler) {
154             super(LIVE_FRAME, semanticHandler);
155         }
156 
run(Obd2Connection connection)157         public Optional<ValueType> run(Obd2Connection connection)
158                 throws IOException, InterruptedException {
159             String command = String.format("%02X%02X", mMode, mSemanticHandler.getPid());
160             int[] data = connection.run(command);
161             IntegerArrayStream stream = new IntegerArrayStream(data);
162             if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid())) {
163                 return mSemanticHandler.consume(stream);
164             }
165             return Optional.empty();
166         }
167     }
168 
169     /**
170      * An OBD2 command that returns freeze frame data.
171      *
172      * @param <ValueType> The Java type that represents the command's result type.
173      */
174     public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
175         private static final int RESPONSE_MARKER = 0x2;
176 
177         private int mFrameId;
178 
FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId)179         FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId) {
180             super(FREEZE_FRAME, semanticHandler);
181             mFrameId = frameId;
182         }
183 
run(Obd2Connection connection)184         public Optional<ValueType> run(Obd2Connection connection)
185                 throws IOException, InterruptedException {
186             String command =
187                     String.format("%02X%02X %02X", mMode, mSemanticHandler.getPid(), mFrameId);
188             int[] data = connection.run(command);
189             IntegerArrayStream stream = new IntegerArrayStream(data);
190             if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid(), mFrameId)) {
191                 return mSemanticHandler.consume(stream);
192             }
193             return Optional.empty();
194         }
195     }
196 }
197