package uk.org.smithfamily.mslogger.comms; import java.io.IOException; import java.util.ArrayList; import java.util.List; import uk.org.smithfamily.mslogger.log.DebugLogManager; import android.util.Log; /** * Main connection class that wrap all the Bluetooth stuff that communicate with the Megasquirt */ public class ECUConnectionManager extends ConnectionManager { // Private constructor prevents instantiation from other classes private ECUConnectionManager() { } /** * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder { public static final ECUConnectionManager INSTANCE = new ECUConnectionManager(); } public static ECUConnectionManager getInstance() { return SingletonHolder.INSTANCE; } @Override public String getInstanceName() { return "ECUConnectionManager"; } /** * Check if the application should auto-connect and automatically connect if it should * * @throws IOException */ @Override protected synchronized void checkConnection() throws IOException { if (currentState == ConnectionState.STATE_DISCONNECTED) { connect(); } } /** * Write a command to the Bluetooth stream * * @param cmd Command to be send * @param d Delay to wait after sending command * @throws IOException */ public synchronized void writeCommand(byte[] command, final int d, final boolean isCRC32) throws IOException { if (!conn.isConnected()) { throw new IOException("Not connected"); } // drain(); if (isCRC32) { command = CRC32ProtocolHandler.wrap(command); } DebugLogManager.INSTANCE.log("Writing", command, Log.VERBOSE); if ((command.length == 7) && ((command[0] == 'r') || (command[0] == 'w') || (command[0] == 'e'))) { // MS2 hack final byte[] select = new byte[3]; final byte[] range = new byte[4]; System.arraycopy(command, 0, select, 0, 3); System.arraycopy(command, 3, range, 0, 4); this.mmOutStream.write(select); delay(200); this.mmOutStream.write(range); } else { this.mmOutStream.write(command); } this.mmOutStream.flush(); delay(d); } /** * Write a command to the Bluetooth stream and return the result * * @param cmd Command to be send * @param d Delay to wait after sending command * @return * @throws IOException * @throws CRC32Exception */ public byte[] writeAndRead(final byte[] cmd, final int d, final boolean isCRC32) throws IOException, CRC32Exception { checkConnection(); writeCommand(cmd, d, isCRC32); final byte[] result = readBytes(isCRC32); return result; } /** * Write a command to the Bluetooth stream and read the result * * @param cmd Command to be send * @param result Result of the command sent by the Megasquirt * @param d Delay to wait after sending command * @throws IOException * @throws CRC32Exception * @throws BTTimeoutException */ public void writeAndRead(final byte[] cmd, final byte[] result, final int d, final boolean isCRC32) throws IOException, CRC32Exception, BTTimeoutException { checkConnection(); writeCommand(cmd, d, isCRC32); readBytes(result, isCRC32); } /** * Read bytes available on Bluetooth stream * * @param bytes * @throws IOException * @throws CRC32Exception * @throws BTTimeoutException */ public void readBytes(final byte[] bytes, final boolean isCRC32) throws IOException, CRC32Exception, BTTimeoutException { final boolean logit = DebugLogManager.checkLogLevel(Log.VERBOSE); int target = bytes.length; byte[] buffer = bytes; if (isCRC32) { target += CRC32ProtocolHandler.getValidationLength(); buffer = new byte[target]; } int read = 0; final int toRead = buffer.length; int available = 0; int remainder = toRead; final long startTime = System.currentTimeMillis(); final long deadline = startTime + IO_TIMEOUT; synchronized (this) { while ((remainder > 0) && (System.currentTimeMillis() < deadline)) { available = mmInStream.available(); if (available > 0) { final int numRead = mmInStream.read(buffer, read, Math.min(available, remainder)); if (numRead == -1) { throw new IOException("EOF!"); } read += numRead; remainder -= numRead; } else { delay(10); } } if (read < toRead) { throw new BTTimeoutException(String.format("Timeout! : toRead = %d, read = %d", toRead, read)); } } if (logit) { DebugLogManager.INSTANCE.log("readBytes[]", buffer, Log.VERBOSE); } if (isCRC32) { if (!CRC32ProtocolHandler.check(buffer)) { throw new CRC32Exception("CRC32 check failed"); } final byte[] actual = CRC32ProtocolHandler.unwrap(buffer); System.arraycopy(actual, 0, bytes, 0, bytes.length); } } /** * Read bytes available on Bluetooth stream and return the resulting array * * @return Array of bytes read from Bluetooth stream * @throws IOException * @throws CRC32Exception */ public byte[] readBytes(final boolean isCRC32) throws IOException, CRC32Exception { final List<Byte> read = new ArrayList<Byte>(); synchronized (this) { while (mmInStream.available() > 0) { final byte b = (byte) mmInStream.read(); read.add(b); } } byte[] result = new byte[read.size()]; int i = 0; for (final Byte b : read) { result[i++] = b; } DebugLogManager.INSTANCE.log("readBytes", result, Log.VERBOSE); if (isCRC32) { if (!CRC32ProtocolHandler.check(result)) { throw new CRC32Exception("CRC32 check failed"); } result = CRC32ProtocolHandler.unwrap(result); } return result; } }