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 
35 import java.io.IOException;
36 import java.util.HashMap;
37 import java.util.Objects;
38 import java.util.Optional;
39 import java.util.Set;
40 
41 /**
42  * Base class of OBD2 command objects that query a "vehicle" and return an individual data point
43  * represented as a Java type.
44  *
45  * @param <ValueType> The Java type that represents the value of this command's output.
46  */
47 public abstract class Obd2Command<ValueType> {
48 
49     /**
50      * Abstract representation of an object whose job it is to receive the bytes read from the OBD2
51      * connection and return a Java representation of a command's value.
52      *
53      * @param <ValueType>
54      */
55     public interface OutputSemanticHandler<ValueType> {
getPid()56         int getPid();
57 
consume(IntegerArrayStream data)58         Optional<ValueType> consume(IntegerArrayStream data);
59     }
60 
61     public static final int LIVE_FRAME = 1;
62     public static final int FREEZE_FRAME = 2;
63 
64     private static final HashMap<Integer, OutputSemanticHandler<Integer>>
65             SUPPORTED_INTEGER_COMMANDS = new HashMap<>();
66     private static final HashMap<Integer, OutputSemanticHandler<Float>> SUPPORTED_FLOAT_COMMANDS =
67             new HashMap<>();
68 
addSupportedIntegerCommands( OutputSemanticHandler<Integer>.... integerOutputSemanticHandlers)69     private static void addSupportedIntegerCommands(
70             OutputSemanticHandler<Integer>... integerOutputSemanticHandlers) {
71         for (OutputSemanticHandler<Integer> integerOutputSemanticHandler :
72                 integerOutputSemanticHandlers) {
73             SUPPORTED_INTEGER_COMMANDS.put(
74                     integerOutputSemanticHandler.getPid(), integerOutputSemanticHandler);
75         }
76     }
77 
addSupportedFloatCommands( OutputSemanticHandler<Float>.... floatOutputSemanticHandlers)78     private static void addSupportedFloatCommands(
79             OutputSemanticHandler<Float>... floatOutputSemanticHandlers) {
80         for (OutputSemanticHandler<Float> floatOutputSemanticHandler :
81                 floatOutputSemanticHandlers) {
82             SUPPORTED_FLOAT_COMMANDS.put(
83                     floatOutputSemanticHandler.getPid(), floatOutputSemanticHandler);
84         }
85     }
86 
getSupportedIntegerCommands()87     public static Set<Integer> getSupportedIntegerCommands() {
88         return SUPPORTED_INTEGER_COMMANDS.keySet();
89     }
90 
getSupportedFloatCommands()91     public static Set<Integer> getSupportedFloatCommands() {
92         return SUPPORTED_FLOAT_COMMANDS.keySet();
93     }
94 
getIntegerCommand(int pid)95     public static OutputSemanticHandler<Integer> getIntegerCommand(int pid) {
96         return SUPPORTED_INTEGER_COMMANDS.get(pid);
97     }
98 
getFloatCommand(int pid)99     public static OutputSemanticHandler<Float> getFloatCommand(int pid) {
100         return SUPPORTED_FLOAT_COMMANDS.get(pid);
101     }
102 
103     static {
addSupportedFloatCommands( new AmbientAirTemperature(), new CalculatedEngineLoad(), new FuelTankLevel(), new Bank2ShortTermFuelTrimCommand(), new Bank2LongTermFuelTrimCommand(), new Bank1LongTermFuelTrimCommand(), new Bank1ShortTermFuelTrimCommand(), new ThrottlePosition())104         addSupportedFloatCommands(
105                 new AmbientAirTemperature(),
106                 new CalculatedEngineLoad(),
107                 new FuelTankLevel(),
108                 new Bank2ShortTermFuelTrimCommand(),
109                 new Bank2LongTermFuelTrimCommand(),
110                 new Bank1LongTermFuelTrimCommand(),
111                 new Bank1ShortTermFuelTrimCommand(),
112                 new ThrottlePosition());
addSupportedIntegerCommands( new EngineOilTemperature(), new EngineCoolantTemperature(), new FuelGaugePressure(), new FuelSystemStatus(), new RPM(), new EngineRuntime(), new Speed())113         addSupportedIntegerCommands(
114                 new EngineOilTemperature(),
115                 new EngineCoolantTemperature(),
116                 new FuelGaugePressure(),
117                 new FuelSystemStatus(),
118                 new RPM(),
119                 new EngineRuntime(),
120                 new Speed());
121     }
122 
123     protected final int mMode;
124     protected final OutputSemanticHandler<ValueType> mSemanticHandler;
125 
Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler)126     Obd2Command(int mode, OutputSemanticHandler<ValueType> semanticHandler) {
127         mMode = mode;
128         mSemanticHandler = Objects.requireNonNull(semanticHandler);
129     }
130 
run(Obd2Connection connection)131     public abstract Optional<ValueType> run(Obd2Connection connection) throws Exception;
132 
getPid()133     public int getPid() {
134         return mSemanticHandler.getPid();
135     }
136 
getLiveFrameCommand(OutputSemanticHandler handler)137     public static final <T> LiveFrameCommand<T> getLiveFrameCommand(OutputSemanticHandler handler) {
138         return new LiveFrameCommand<>(handler);
139     }
140 
getFreezeFrameCommand( OutputSemanticHandler handler, int frameId)141     public static final <T> FreezeFrameCommand<T> getFreezeFrameCommand(
142             OutputSemanticHandler handler, int frameId) {
143         return new FreezeFrameCommand<>(handler, frameId);
144     }
145 
146     /**
147      * An OBD2 command that returns live frame data.
148      *
149      * @param <ValueType> The Java type that represents the command's result type.
150      */
151     public static class LiveFrameCommand<ValueType> extends Obd2Command<ValueType> {
152         private static final int RESPONSE_MARKER = 0x41;
153 
LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler)154         LiveFrameCommand(OutputSemanticHandler<ValueType> semanticHandler) {
155             super(LIVE_FRAME, semanticHandler);
156         }
157 
run(Obd2Connection connection)158         public Optional<ValueType> run(Obd2Connection connection)
159                 throws IOException, InterruptedException {
160             String command = String.format("%02X%02X", mMode, mSemanticHandler.getPid());
161             int[] data = connection.run(command);
162             IntegerArrayStream stream = new IntegerArrayStream(data);
163             if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid())) {
164                 return mSemanticHandler.consume(stream);
165             }
166             return Optional.empty();
167         }
168     }
169 
170     /**
171      * An OBD2 command that returns freeze frame data.
172      *
173      * @param <ValueType> The Java type that represents the command's result type.
174      */
175     public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
176         private static final int RESPONSE_MARKER = 0x42;
177 
178         private int mFrameId;
179 
FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId)180         FreezeFrameCommand(OutputSemanticHandler<ValueType> semanticHandler, int frameId) {
181             super(FREEZE_FRAME, semanticHandler);
182             mFrameId = frameId;
183         }
184 
run(Obd2Connection connection)185         public Optional<ValueType> run(Obd2Connection connection)
186                 throws IOException, InterruptedException {
187             String command =
188                     String.format("%02X%02X %02X", mMode, mSemanticHandler.getPid(), mFrameId);
189             int[] data = connection.run(command);
190             IntegerArrayStream stream = new IntegerArrayStream(data);
191             if (stream.expect(RESPONSE_MARKER, mSemanticHandler.getPid(), mFrameId)) {
192                 return mSemanticHandler.consume(stream);
193             }
194             return Optional.empty();
195         }
196     }
197 }
198