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.exception.StreamFinishedException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import rx.Observable;
import rx.Subscriber;
public class CommandExecutor {
private static final Logger LOGGER = Logger.getLogger(CommandExecutor.class.getName());
private final Set<Byte> ignoredChars;
private final byte endOfLineOutput;
private final byte endOfLineInput;
private OutputStream outputStream;
private InputStream inputStream;
private ResponseQuirkWorkaround quirk;
public CommandExecutor(InputStream is, OutputStream os,
Set<Character> ignoredChars, Character endOfLineInput, Character endOfLineOutput) {
this.inputStream = is;
this.outputStream = os;
this.ignoredChars = new HashSet<>(ignoredChars.size());
for (Character c : ignoredChars) {
this.ignoredChars.add((byte) c.charValue());
}
this.endOfLineOutput = (byte) endOfLineOutput.charValue();
this.endOfLineInput = (byte) endOfLineInput.charValue();
}
public void setQuirk(ResponseQuirkWorkaround quirk) {
this.quirk = quirk;
}
public void execute(BasicCommand cmd) throws IOException {
if (cmd == null) {
throw new IOException("Command cannot be null!");
}
byte[] bytes = cmd.getOutputBytes();
if (LOGGER.isEnabled(Logger.DEBUG)) {
LOGGER.debug("Sending bytes: "+ new String(bytes));
}
// write to OutputStream, or in this case a BluetoothSocket
synchronized (this) {
outputStream.write(bytes);
outputStream.write(endOfLineOutput);
outputStream.flush();
}
}
public Observable<byte[]> createRawByteObservable() {
return Observable.create(new Observable.OnSubscribe<byte[]>() {
@Override
public void call(Subscriber<? super byte[]> subscriber) {
try {
while (!subscriber.isUnsubscribed()) {
byte[] bytes = readResponseLine();
if (LOGGER.isEnabled(Logger.DEBUG)) {
LOGGER.debug("Received bytes: "+ Base64.encodeToString(bytes, Base64.DEFAULT));
}
subscriber.onNext(bytes);
}
} catch (IOException e) {
subscriber.onError(e);
} catch (StreamFinishedException e) {
subscriber.onCompleted();
}
}
});
}
private byte[] readResponseLine() throws IOException, StreamFinishedException {
// TODO
// LOGGER.info("Reading response line...");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// read until end of line arrives
readUntilLineEnd(baos);
byte[] byteArray = baos.toByteArray();
//some adapter (i.e. the drivedeck) MIGHT respond with linebreaks as actual data - detect this
if (quirk != null && quirk.shouldWaitForNextTokenLine(byteArray)) {
LOGGER.info("Detected quirk: "+this.quirk.getClass().getSimpleName());
//re-add the end of line, it was dismissed previously
baos.write(this.endOfLineInput);
readUntilLineEnd(baos);
byteArray = baos.toByteArray();
}
if (byteArray.length == 0){
LOGGER.info("Unexpected empty line anonmaly detected. Try to read next line.");
baos.reset();
readUntilLineEnd(baos);
byteArray = baos.toByteArray();
}
if (byteArray.length > 0 && LOGGER.isEnabled(Logger.DEBUG)) {
LOGGER.debug("Received bytes: " + Base64.encodeToString(byteArray, Base64.DEFAULT));
}
return byteArray;
}
private void readUntilLineEnd(ByteArrayOutputStream baos) throws IOException, StreamFinishedException {
int i = inputStream.read();
byte b = (byte) i;
while (b != this.endOfLineInput) {
if (i == -1) {
throw new StreamFinishedException("Stream finished");
}
if (!ignoredChars.contains(b)){
baos.write(b);
}
i = inputStream.read();
b = (byte) i;
}
}
public byte[] retrieveLatestResponse() throws IOException, StreamFinishedException {
return readResponseLine();
}
}