/* CanZE Take a closer look at your ZE car Copyright (C) 2015 - The CanZE Team http://canze.fisch.lu This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package lu.fisch.canze.activities; import android.os.Bundle; import android.os.Environment; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.TextView; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import lu.fisch.canze.R; import lu.fisch.canze.actors.Dtcs; import lu.fisch.canze.actors.Ecu; import lu.fisch.canze.actors.Ecus; import lu.fisch.canze.actors.Field; import lu.fisch.canze.actors.Fields; import lu.fisch.canze.actors.Frame; import lu.fisch.canze.actors.Frames; import lu.fisch.canze.actors.Message; import lu.fisch.canze.actors.StoppableThread; import lu.fisch.canze.bluetooth.BluetoothManager; import static lu.fisch.canze.activities.MainActivity.debug; public class DtcActivity extends CanzeActivity { private TextView textView; BufferedWriter bufferedDumpWriter = null; boolean dumpInProgress = false; private StoppableThread queryThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_dtc); textView = (TextView) findViewById(R.id.textResult); ArrayAdapter <String> arrayAdapter = new ArrayAdapter <> (this,android.R.layout.simple_list_item_1); for (Ecu ecu : Ecus.getInstance().getAllEcus()) { if (ecu.getFromId() != 0) arrayAdapter.add(ecu.getMnemonic()); } // display the list final Spinner spinnerEcu = (Spinner) findViewById(R.id.ecuList); spinnerEcu.setAdapter(arrayAdapter); final Button btnQuery = (Button) findViewById(R.id.ecuQuery); btnQuery.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { doQueryEcu(Ecus.getInstance().getByMnemonic(String.valueOf(spinnerEcu.getSelectedItem()))); } }); final Button btnClear = (Button) findViewById(R.id.ecuClear); btnClear.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { doClearEcu(Ecus.getInstance().getByMnemonic(String.valueOf(spinnerEcu.getSelectedItem()))); } }); final Button btnDiag = (Button) findViewById(R.id.ecuDiag); btnDiag.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { doDiagEcu(Ecus.getInstance().getByMnemonic(String.valueOf(spinnerEcu.getSelectedItem()))); } }); new Thread(new Runnable() { @Override public void run() { appendResult(R.string.message_PollerStopping); if (MainActivity.device != null) { // stop the poller thread MainActivity.device.stopAndJoin(); } if (!BluetoothManager.getInstance().isConnected()) { appendResult(R.string.message_NoConnection); return; } appendResult(MainActivity.getStringSingle(R.string.message_Ready)); } }).start(); } protected void initListeners () {} void doQueryEcu(final Ecu ecu) { clearResult(); appendResult("Query " + ecu.getName() + " (renault ID:" + ecu.getRenaultId() + ")\n"); // here initialize this particular ECU diagnostics fields try { Object diagEcu = Class.forName("lu.fisch.canze.actors.EcuDiag" + ecu.getMnemonic()).newInstance(); java.lang.reflect.Method methodLoad; methodLoad = diagEcu.getClass().getMethod("load"); methodLoad.invoke(diagEcu); } catch (Exception e) { appendResult(R.string.message_NoEcuDefinition); } // re-initialize the device appendResult(R.string.message_SendingInit); // try to stop previous thread if(queryThread!=null) if(queryThread.isAlive()) { queryThread.tryToStop(); try { queryThread.join(); } catch(Exception e) { MainActivity.debug(e.getMessage()); } } queryThread = new StoppableThread(new Runnable() { @Override public void run() { Field field; String filter; Message message; String backRes; // get the from ID from the selected ECU filter = Integer.toHexString(ecu.getFromId()); if (!MainActivity.device.initDevice(1)) { appendResult(R.string.message_InitFailed); return; } // still trying desperately to get to the BCB!!!1 if (filter.equals("793")) { // we are a tester appendResult(R.string.message_StartTestSession); field = Fields.getInstance().getBySID(filter + ".7e01.0"); if (field == null) { appendResult(R.string.message_NoTestSessionField); return; } // query the Field message = MainActivity.device.requestFrame(field.getFrame()); if (message.isError()) { appendResult(message.getError() + "\n"); return; } backRes = message.getData(); // check the response if (!backRes.toLowerCase().startsWith("7e")) { appendResult(MainActivity.getStringSingle(R.string.message_UnexpectedResult) + backRes + "]\n"); return; } // start a tester session appendResult(R.string.message_StartTestSession); field = Fields.getInstance().getBySID(filter + ".5081.0"); if (field == null) { appendResult(R.string.message_NoTestSessionField); return; } // query the Field message = MainActivity.device.requestFrame(field.getFrame()); if (message.isError()) { appendResult(message.getError() + "\n"); return; } backRes = message.getData(); // check the response if (!backRes.startsWith("50")) { appendResult(MainActivity.getStringSingle(R.string.message_UnexpectedResult) + backRes + "]\n"); return; } // start a tester2 session appendResult(R.string.message_StartTestSession); field = Fields.getInstance().getBySID(filter + ".50c0.0"); if (field == null) { appendResult(R.string.message_NoTestSessionField); return; } // query the Field message = MainActivity.device.requestFrame(field.getFrame()); if (message.isError()) { appendResult(message.getError() + "\n"); return; } backRes = message.getData(); // check the response if (!backRes.startsWith("50")) { appendResult(MainActivity.getStringSingle(R.string.message_UnexpectedResult) + backRes + "]\n"); return; } } // compile the field query and get the Field object appendResult(R.string.message_GetDtcs); field = Fields.getInstance().getBySID(filter + "." + ecu.getGetDtcs() + ".0"); // get DTC if (field == null) { appendResult(R.string.message_NoGetDtcsField); return; } // query the Field message = MainActivity.device.requestFrame(field.getFrame()); if (message.isError()) { appendResult(message.getError() + "\n"); return; } backRes = message.getData(); // check the response if (!backRes.startsWith("59")) { appendResult(MainActivity.getStringSingle(R.string.message_UnexpectedResult) + backRes + "]\n"); return; } // loop trough all DTC's // format of the message is // blocks of 4 bytes // first 2 bytes is the DTC // first nibble of DTC is th P0 etc encoding, but we are nit using that // next byte is the test that triggered the DTC // next byte contains the flags // All decoding is done in the Dtcs class boolean onePrinted = false; for (int i = 6; i < backRes.length() - 7; i += 8) { // see if we need to stop right now if(((StoppableThread) Thread.currentThread()).isStopped()) return; int flags = Integer.parseInt(backRes.substring(i + 6, i + 8), 16); // exclude 50 / 10 as it means something like "I have this DTC code, but I have never tested it" if (flags != 0x50 && flags != 0x10) { onePrinted = true; appendResult( "\n*** DTC" + backRes.substring(i, i + 6) + " (" + Dtcs.getInstance().getDisplayCodeById (backRes.substring(i, i + 6)) + ") ***\n" + Dtcs.getInstance().getDescriptionById(backRes.substring(i, i + 6)) + "\nFlags:" + Dtcs.getInstance().getFlagDescription(flags) ); } } if (!onePrinted) appendResult(R.string.message_NoActiveDtcs); } }); queryThread.start(); } void doClearEcu(final Ecu ecu) { clearResult(); appendResult(MainActivity.getStringSingle(R.string.message_clear) + ecu.getName() + " (renault ID:" + ecu.getRenaultId() + ")\n"); // try to stop previous thread if(queryThread!=null) if(queryThread.isAlive()) { queryThread.tryToStop(); try { queryThread.join(); } catch(Exception e) { MainActivity.debug(e.getMessage()); } } queryThread = new StoppableThread(new Runnable() { @Override public void run() { Field field; Frame frame; String filter; // get the from ID from the selected ECU filter = Integer.toHexString(ecu.getFromId()); // compile the field query and get the Field object field = Fields.getInstance().getBySID(filter + ".54.0"); // get DTC Clear if (field == null) { appendResult(R.string.message_NoClearDtcField); return; } frame = field.getFrame(); // re-initialize the device appendResult(R.string.message_SendingInit); if (!MainActivity.device.initDevice(1)) { appendResult(R.string.message_InitFailed); return; } // query the Field Message message = MainActivity.device.requestFrame (frame); if (message.isError()) { appendResult(R.string.message_MessageNull); return; } String backRes = message.getData(); // check the response if (!backRes.startsWith("54")) { appendResult(MainActivity.getStringSingle(R.string.message_UnexpectedResult) + backRes + "]\n"); return; } appendResult(R.string.message_ClearSuccessful); } }); queryThread.start(); } void doDiagEcu(final Ecu ecu) { clearResult(); // clear the screen // here initialize this particular ECU diagnostics fields try { Object diagEcu = Class.forName("lu.fisch.canze.actors.EcuDiag" + ecu.getMnemonic()).newInstance(); java.lang.reflect.Method methodLoad; methodLoad = diagEcu.getClass().getMethod("load"); methodLoad.invoke(diagEcu); //Frames.getInstance().load (diagEcu.framesString ()); //Fields.getInstance().load (diagEcu.fieldsString ()); } catch (Exception e) { appendResult(R.string.message_NoEcuDefinition2); return; } appendResult(R.string.message_SendingInit); // try to stop previous thread if(queryThread!=null) if(queryThread.isAlive()) { queryThread.tryToStop(); try { queryThread.join(); } catch(Exception e) { MainActivity.debug(e.getMessage()); } } queryThread = new StoppableThread(new Runnable() { @Override public void run() { // re-initialize the device if (!MainActivity.device.initDevice(1)) { appendResult(R.string.message_InitFailed); return; } createDump(ecu); for (Frame frame : Frames.getInstance().getAllFrames()) { // see if we need to stop right now if(((StoppableThread) Thread.currentThread()).isStopped()) return; if (frame.getContainingFrame() != null) { // only use subframes // query the Frame Message message = MainActivity.device.requestFrame(frame); if (!message.isError()) { // process the frame by going through all the containing fields // setting their values and notifying all listeners (there should be none) // Fields.getInstance().onMessageCompleteEvent(message); message.onMessageCompleteEvent(); for (Field field : frame.getAllFields()) { if (field.isString()) { appendResult(field.getName() + ":" + field.getStringValue() + "\n"); } else if (field.isList()) { appendResult(field.getName() + ":" + field.getListValue() + "\n"); } else { appendResult(field.getName() + ":" + field.getValue() + field.getUnit() + "\n"); } } } else { appendResult(frame.getHexId() + "." + frame.getResponseId() + ":" + message.getError() + "\n"); if (!MainActivity.device.initDevice(1)) { appendResult(MainActivity.getStringSingle(R.string.message_InitFailed)); return; } } } } closeDump(); } }); queryThread.start(); } // Ensure all UI updates are done on the UiThread private void clearResult() { runOnUiThread(new Runnable() { @Override public void run() { textView.setText(""); } }); } private void appendResult(String str) { if ( dumpInProgress) log (str); final String localStr = str; runOnUiThread(new Runnable() { @Override public void run() { textView.append(localStr); } }); } private void appendResult(int strResource) { final String localStr = MainActivity.getStringSingle(strResource); runOnUiThread(new Runnable() { @Override public void run() { textView.append(localStr); } }); } private void log(String text) { try { bufferedDumpWriter.append(text); bufferedDumpWriter.append(System.getProperty("line.separator")); } catch (IOException e) { e.printStackTrace(); } } public boolean isExternalStorageWritable() { String SDstate = Environment.getExternalStorageState(); return ( Environment.MEDIA_MOUNTED.equals(SDstate)); } private void createDump (Ecu ecu) { dumpInProgress = false; SimpleDateFormat sdf = new SimpleDateFormat(MainActivity.getStringSingle(R.string.format_YMDHMS), Locale.getDefault()); // ensure that there is a CanZE Folder in SDcard if ( ! isExternalStorageWritable()) { debug ( "DiagDump: SDcard not writeable"); return; } String file_path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/CanZE/"; File dir = new File(file_path); if (!dir.exists()) { if (!dir.mkdirs()) { debug("DiagDump: Can't create directory:" + file_path); return; } } debug("DiagDump: file_path:" + file_path); // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); String exportdataFileName = file_path + ecu.getMnemonic() + "-" + sdf.format(Calendar.getInstance().getTime()) + ".txt"; File logFile = new File(exportdataFileName); if (!logFile.exists()) { try { if (!logFile.createNewFile()) { debug("DiagDump: Can't create file:" + exportdataFileName); return; } debug("DiagDump: NewFile:" + exportdataFileName ); } catch (IOException e) { e.printStackTrace(); } } try { //BufferedWriter for performance, true to set append to file flag bufferedDumpWriter = new BufferedWriter(new FileWriter(logFile, true)); dumpInProgress = true; } catch (IOException e) { e.printStackTrace(); } } private void closeDump () { try { if (dumpInProgress) bufferedDumpWriter.close(); } catch (IOException e) { e.printStackTrace(); } } // UI elements @Override protected void onDestroy() { // stop the query thread if still running if(queryThread!=null) if(queryThread.isAlive()) { queryThread.tryToStop(); try { queryThread.join(); } catch(Exception e) { MainActivity.debug(e.getMessage()); } } closeDump(); // Reload the frame & timings Frames.getInstance().load(); Fields.getInstance().load(); // restart the poller if (MainActivity.device != null) { MainActivity.device.initConnection(); // register application wide fields MainActivity.getInstance().registerApplicationFields(); } super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_empty, menu); return true; } }