package org.envirocar.obd.adapter; import android.util.Base64; import org.envirocar.core.logging.Logger; import org.envirocar.obd.commands.request.BasicCommand; import org.envirocar.obd.commands.request.PIDCommand; import org.envirocar.obd.exception.AdapterFailedException; import java.util.ArrayDeque; import java.util.Arrays; import java.util.List; import java.util.Queue; public class CarTrendAdapter extends SyncAdapter { private static final Logger logger = Logger.getLogger(CarTrendAdapter.class); private static final int MAX_METADATA_COUNT = 25; private int requiredCount; private boolean protocolFound; private boolean identifySuccess; private int metadataResponseCount; private boolean connectionEstablished; private int dataStartPosition = -1; private Queue<BasicCommand> initializeRing; private int ringSize; private int initialCount; @Override protected BasicCommand pollNextInitializationCommand() { if (this.initializeRing == null) { this.initializeRing = new ArrayDeque<>(); this.initializeRing.add(new EmptyCommand()); this.initializeRing.add(new IdentifyCommand()); this.initializeRing.add(new EmptyCommand()); this.initializeRing.add(new ProtocolCommand("S")); this.initializeRing.add(new ProtocolCommand("1")); this.initializeRing.add(new ProtocolCommand("2")); this.initializeRing.add(new ProtocolCommand("3")); this.initializeRing.add(new ProtocolCommand("4")); this.initializeRing.add(new ProtocolCommand("5")); this.initializeRing.add(new ProtocolCommand("6")); this.initializeRing.add(new ConfigCommand("@E0")); this.initializeRing.add(new ConfigCommand("@H0")); this.ringSize = this.initializeRing.size(); } if (++initialCount == ringSize) { logger.info("One cycle of config commands sent, trying another round"); ringSize = this.initializeRing.size(); initialCount = 0; } BasicCommand next = this.initializeRing.poll(); if (next instanceof ProtocolCommand) { /** * re-add protocol selection */ this.initializeRing.offer(next); } return next; } @Override protected List<PIDCommand> providePendingCommands() { return super.defaultCycleCommands(); } @Override protected boolean analyzeMetadataResponse(byte[] response, BasicCommand sentCommand) throws AdapterFailedException { logger.info("Parsing meta response: "+ Base64.encodeToString(response, Base64.DEFAULT)+ "; sentCommand="+Base64.encodeToString(sentCommand.getOutputBytes(), Base64.DEFAULT)); if (response == null || response.length == 0) { return false; } String asString = new String(response).toLowerCase(); if (asString.contains("ms4200")) { identifySuccess = true; logger.info("Received Identity response. This should be a CarTrend: "+asString); } if (identifySuccess && asString.contains("onnected")) { this.connectionEstablished = true; logger.info(String.format("Connected on Protocol %s. Adapter responded '%s'", new String(sentCommand.getOutputBytes()), new String(response))); } if (sentCommand instanceof ProtocolCommand && asString.contains("error") && asString.contains("unable")) { /** * the adapter understood the command but could not connect --> it is still a certified * connection as no '?' was returned */ identifySuccess = true; } if (++this.metadataResponseCount > MAX_METADATA_COUNT && !this.connectionEstablished) { throw new AdapterFailedException("Too many tries. Could not establish data link"); } return this.connectionEstablished; } @Override protected byte[] preProcess(byte[] bytes) throws AdapterFailedException { if (dataStartPosition == -1) { String data = new String(bytes); /** * search for "41" (= status ok) */ dataStartPosition = data.indexOf("41"); logger.info(String.format("Identified start position %s by response '%s'", dataStartPosition, new String(bytes))); if (dataStartPosition == -1) { //still -1, throw exception throw new AdapterFailedException("Could not determine start position of CarTrend response"); } } if (dataStartPosition != -1 && dataStartPosition < bytes.length) { return Arrays.copyOfRange(bytes, dataStartPosition, bytes.length); } else { return bytes; } } @Override public boolean supportsDevice(String deviceName) { return deviceName.toLowerCase().contains("cartrend"); } @Override public boolean hasCertifiedConnection() { return this.identifySuccess; } @Override public long getExpectedInitPeriod() { return 35000; } private static class ConfigCommand extends GenericCommand { protected ConfigCommand(String content) { super(content); } } private class ProtocolCommand extends GenericCommand { protected ProtocolCommand(String id) { super("@P".concat(id)); } @Override public byte[] getOutputBytes() { return CarTrendAdapter.this.protocolFound ? new byte[0] : super.getOutputBytes(); } } private static class EmptyCommand extends GenericCommand { protected EmptyCommand() { super(""); } } private static class IdentifyCommand extends GenericCommand { protected IdentifyCommand() { super("@"); } } private static class GenericCommand implements BasicCommand { private final String name; protected GenericCommand(String content) { this.name = content; } @Override public byte[] getOutputBytes() { try { Thread.sleep(500); } catch (InterruptedException e) { logger.warn(e.getMessage(), e); } return this.name.getBytes(); } @Override public boolean awaitsResults() { return true; } @Override public String toString() { return this.name; } } }