/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2015 RomRaider.com
*
* 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 2 of the License, or
* (at your option) 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.romraider.logger.ecu.comms.learning;
import static com.romraider.logger.ecu.comms.io.connection.LoggerConnectionFactory.getConnection;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.WARNING_MESSAGE;
import static javax.swing.JOptionPane.showMessageDialog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.SwingWorker;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.romraider.Settings;
import com.romraider.logger.ecu.EcuLogger;
import com.romraider.logger.ecu.comms.io.connection.DS2LoggerConnection;
import com.romraider.logger.ecu.comms.learning.flkctable.DS2FlkcTableQueryBuilder;
import com.romraider.logger.ecu.comms.learning.parameter.DS2Parameter;
import com.romraider.logger.ecu.comms.learning.parameter.DS2ParameterCrossReference;
import com.romraider.logger.ecu.comms.learning.parameter.ParameterIdComparator;
import com.romraider.logger.ecu.comms.learning.tableaxis.DS2TableAxisQueryParameterSet;
import com.romraider.logger.ecu.comms.manager.PollingStateImpl;
import com.romraider.logger.ecu.comms.query.EcuQuery;
import com.romraider.logger.ecu.comms.query.EcuQueryImpl;
import com.romraider.logger.ecu.definition.EcuData;
import com.romraider.logger.ecu.definition.EcuDefinition;
import com.romraider.logger.ecu.definition.Module;
import com.romraider.logger.ecu.definition.Transport;
import com.romraider.logger.ecu.definition.xml.EcuDefinitionDocumentLoader;
import com.romraider.logger.ecu.definition.xml.EcuDefinitionInheritanceList;
import com.romraider.logger.ecu.definition.xml.EcuTableDefinitionHandler;
import com.romraider.logger.ecu.ui.MessageListener;
import com.romraider.logger.ecu.ui.paramlist.ParameterListTableModel;
import com.romraider.logger.ecu.ui.paramlist.ParameterRow;
import com.romraider.logger.ecu.ui.swing.tools.DS2LearningTableValuesResultsPanel;
import com.romraider.util.ParamChecker;
/**
* This class manages the building of ECU queries and retrieving the data to
* populate the table models which will be used by the Learning Table Values
* display panel.
*/
public final class DS2LearningTableValues extends SwingWorker<Void, Void>
implements LearningTableValues {
private static final Logger LOGGER =
Logger.getLogger(DS2LearningTableValues.class);
private static final String[] AF_RANGE_NAMES = new String[]{" ",
"Additive Adaptation",
"Multiplicative Adaptation"};
private static final List<String> KNK_LOAD_TABLE_NAMES = Arrays.asList(
"Knock Tables X Axis (Load)");
private static final List<String> KNK_RPM_TABLE_NAMES = Arrays.asList(
"Knock Tables Y Axis (Engine Speed)");
private final Map<String, Object> vehicleInfo =
new LinkedHashMap<String, Object>();
private final List<List<Object>> afLearning = new ArrayList<List<Object>>();
private EcuLogger logger;
private Settings settings;
private MessageListener messageListener;
private ParameterListTableModel parmeterList;
private EcuDefinition ecuDef;
private ParameterRow flkc;
private int flkcAddr;
public DS2LearningTableValues() {}
public void init(
EcuLogger logger,
ParameterListTableModel dataTabParamListTableModel,
EcuDefinition ecuDef) {
ParamChecker.checkNotNull(logger, "EcuLogger");
ParamChecker.checkNotNull(dataTabParamListTableModel,
"ParameterListTableModel");
this.logger = logger;
this.settings = logger.getSettings();
this.messageListener = logger;
this.parmeterList = dataTabParamListTableModel;
this.ecuDef = ecuDef;
this.flkc = null;
this.flkcAddr = 0;
}
@Override
public final Void doInBackground() {
Document document = null;
if (ecuDef.getEcuDefFile() == null) {
showMessageDialog(logger,
"ECU definition file not found or undefined. Adaptation\n" +
"Table Values cannot be properly retrieved until an ECU\n" +
"defintion is defined in the Editor's Definition Manager.",
"ECU Defintion Missing", WARNING_MESSAGE);
return null;
}
else {
document = EcuDefinitionDocumentLoader.getDocument(ecuDef);
}
final String transport = settings.getTransportProtocol();
final Module module = settings.getDestinationTarget();
if (settings.isCanBus()) {
settings.setTransportProtocol("ISO9141");
final Module ecuModule = getModule("ECU");
settings.setDestinationTarget(ecuModule);
}
final boolean logging = logger.isLogging();
if (logging) logger.stopLogging();
String message = "Retrieving vehicle info & A/F values...";
messageListener.reportMessage(message);
buildVehicleInfoMap(ecuDef);
try {
final DS2LoggerConnection connection = (DS2LoggerConnection) getConnection(
settings.getLoggerProtocol(), settings.getLoggerPort(),
settings.getLoggerConnectionProperties());
try {
Collection<EcuQuery> queries = buildLearningQueries();
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("Current vehicle info & A/F values retrieved.");
Collections.sort(
(List<EcuQuery>)queries, new ParameterIdComparator());
processEcuQueryResponses((List<EcuQuery>) queries);
message = "Retrieving Knock Load ranges...";
messageListener.reportMessage(message);
String[] flkcLoad = new String[0];
queries.clear();
queries = getTableAxisRanges(document, ecuDef, KNK_LOAD_TABLE_NAMES);
if (queries != null && !queries.isEmpty()) {
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("Knock Load ranges retrieved.");
flkcLoad = formatRanges(queries, "%.0f");
}
message = "Retrieving Knock RPM ranges...";
messageListener.reportMessage(message);
String[] flkcRpm = new String[0];
queries.clear();
queries = getTableAxisRanges(document, ecuDef, KNK_RPM_TABLE_NAMES);
if (queries != null && !queries.isEmpty()) {
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("Knock RPM ranges retrieved.");
flkcRpm = formatRpmRanges(queries);
}
queries.clear();
List<List<List<EcuQuery>>> flkcQueryTables = new ArrayList<List<List<EcuQuery>>>();
List<List<EcuQuery>> flkcQueryGroups = new ArrayList<List<EcuQuery>>();
if (flkc != null) {
for (int k = 0; k < 384; k += 64) {
flkcQueryGroups = new DS2FlkcTableQueryBuilder().build(
flkc,
flkcAddr + k,
flkcRpm.length,
flkcLoad.length - 1);
flkcQueryTables.add(flkcQueryGroups);
for (int i = 0; i < flkcQueryGroups.size(); i++) {
for (int j = 0; j < flkcQueryGroups.get(i).size(); j++) {
if (flkcQueryGroups.get(i).get(j) != null) {
queries.add(flkcQueryGroups.get(i).get(j));
}
}
}
message = String.format("Retrieving Table %d Knock values...",
(k/64+1));
messageListener.reportMessage(message);
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info(String.format("Table %d Knock values retrieved.",
(k/64+1)));
queries.clear();
}
}
else {
message = String.format("Error retrieving Knock data values, missing Knock reference");
messageListener.reportMessage(message);
LOGGER.error(message);
}
messageListener.reportMessage(
"Adaptation Table Values retrieved successfully.");
final DS2LearningTableValuesResultsPanel results =
new DS2LearningTableValuesResultsPanel(
logger, vehicleInfo,
AF_RANGE_NAMES, afLearning,
flkcLoad, flkcRpm, flkcQueryTables);
results.displayLearningResultsPanel();
}
finally {
connection.close();
settings.setTransportProtocol(transport);
settings.setDestinationTarget(module);
if (logging) logger.startLogging();
}
}
catch (Exception e) {
messageListener.reportError(
"Unable to retrieve current ECU adapation values");
LOGGER.error(message + " Error retrieving values", e);
showMessageDialog(logger,
message +
"\nError performing Adaptation Table Values read.\n" +
"Check the following:\n" +
"* Logger has successfully conencted to the ECU\n" +
"* Correct COM port is selected (if not Openport 2)\n" +
"* Cable is connected properly\n* Ignition is ON\n",
"Adaptation Table Values",
ERROR_MESSAGE);
}
return null;
}
/**
* Build a collection of queries based on the initialized values of
* parameters defined for this ECU. Also identify the Knock
* parameters used to locate and calculate the Knock table.
* @return the supported parameter list filtered for only the Learning Table
* Value parameters needed.
*/
private final Collection<EcuQuery> buildLearningQueries() {
final Collection<EcuQuery> query = new ArrayList<EcuQuery>();
final List<ParameterRow> parameterRows = parmeterList.getParameterRows();
if (!ParamChecker.isNullOrEmpty(parameterRows)) {
for (ParameterRow parameterRow : parameterRows) {
final DS2Parameter parameterId =
DS2Parameter.fromValue(parameterRow.getLoggerData().getId());
if (parameterId != null) {
query.add(buildEcuQuery(parameterRow));
setFlkcTableAddress(parameterRow, parameterId); }
}
}
return query;
}
/**
* Build a query object for a parameter item.
* @return a new EcuQuery.
*/
private final EcuQuery buildEcuQuery(ParameterRow parameterRow) {
final EcuQuery ecuQuery =
new EcuQueryImpl((EcuData) parameterRow.getLoggerData());
return ecuQuery;
}
/**
* Define the start address of the Knock table in RAM base on a Extended
* parameter if defined.
* Also isolate the Knock extended parameter to use the data converter
* when building the Knock table queries.
*/
private final void setFlkcTableAddress(
ParameterRow parameterRow,
DS2Parameter parameterId) {
switch (parameterId) {
case E99:
flkcAddr = getParameterAddr(parameterRow);
flkc = parameterRow;
break;
default:
break;
}
}
/**
* Return the parameter's integer address.
*/
private final int getParameterAddr(ParameterRow parameterRow) {
final EcuData ecudata = (EcuData) parameterRow.getLoggerData();
final String addrStr = ecudata.getAddress().getAddresses()[0];
final String addrHexStr = addrStr.replaceAll("0x", "");
return Integer.parseInt(addrHexStr, 16);
}
/**
* Start populating the vehicle information map with passed values.
*/
private final void buildVehicleInfoMap(EcuDefinition ecuDef) {
vehicleInfo.put("CAL ID", ecuDef.getCalId());
vehicleInfo.put("ECU ID", ecuDef.getEcuId());
vehicleInfo.put("Description",
ecuDef.getCarString().replaceAll("BMW ", ""));
}
/**
* Retrieve the table axis values from the ECU definition.
*/
private final List<EcuQuery> getTableAxisRanges(
Document document,
EcuDefinition ecuDef,
List<String> tableNames) {
List<EcuQuery> tableAxis = new ArrayList<EcuQuery>();
for (String tableName : tableNames) {
tableAxis = loadTable(document, ecuDef, tableName);
if (!tableAxis.isEmpty()) {
break;
}
}
return tableAxis;
}
/**
* Once values from the ECU have been populated add the values to the
* table models datasets.
*/
private final void processEcuQueryResponses(List<EcuQuery> queries) {
final DS2ParameterCrossReference parameterMap = new DS2ParameterCrossReference();
final List<Object> afLearningBank1 = new ArrayList<Object>();
final List<Object> afLearningBank2 = new ArrayList<Object>();
for (EcuQuery query : queries) {
final DS2Parameter parameterId =
DS2Parameter.fromValue(query.getLoggerData().getId());
final String paramDesc = parameterMap.getValue(parameterId);
String result = String.format("%.2f %s",
query.getResponse(),
query.getLoggerData().getSelectedConvertor().getUnits());
switch (parameterId) {
case E99:
break;
case E19:
afLearningBank1.add((Object) "#1");
afLearningBank1.add((Object) result);
break;
case E21:
afLearningBank1.add((Object) result);
afLearning.add(afLearningBank1);
break;
case E20:
afLearningBank2.add((Object) "#2");
afLearningBank2.add((Object) result);
break;
case E22:
afLearningBank2.add((Object) result);
afLearning.add(afLearningBank2);
break;
default:
vehicleInfo.put(paramDesc, result);
break;
}
}
}
/**
* Build a List of EcuQueries to retrieve the axis and scaling of a table.
* A table is found when the storageaddress parameter has been identified.
*/
private final List<EcuQuery> loadTable(
Document document,
EcuDefinition ecuDef,
String tableName) {
final List<Node> inheritanceList =
EcuDefinitionInheritanceList.getInheritanceList(document, ecuDef);
final Map<String, String> tableMap =
EcuTableDefinitionHandler.getTableDefinition(
document,
inheritanceList,
tableName);
List<EcuQuery> tableAxisQuery = new ArrayList<EcuQuery>();
if (tableMap.containsKey("storageaddress")) {
tableAxisQuery = DS2TableAxisQueryParameterSet.build(
tableMap.get("storageaddress"),
tableMap.get("storagetype"),
tableMap.get("expression"),
tableMap.get("units"),
tableMap.get("sizey"),
"0x06", "0x00", null,
tableMap.get("endian")
);
}
return tableAxisQuery;
}
/**
* Format the range data to be used as table column header values.
*/
private final String[] formatRanges(
Collection<EcuQuery> axisRanges,
String numberFormat) {
final List<String> ranges = new ArrayList<String>();
ranges.add(" ");
double value = 0;
for (EcuQuery ecuQuery : axisRanges) {
value = ecuQuery.getResponse();
final String range = String.format(
numberFormat,
value);
ranges.add(range);
}
return ranges.toArray(new String[0]);
}
/**
* Format the RPM range data to be used as Knock table row header values.
*/
private final String[] formatRpmRanges(Collection<EcuQuery> axisRanges) {
final List<String> ranges = new ArrayList<String>();
double value = 0;
for (EcuQuery ecuQuery : axisRanges) {
value = ecuQuery.getResponse();
final String range = String.format(
"%.0f", value);
ranges.add(range);
}
return ranges.toArray(new String[0]);
}
/**
* Return a Transport based on its String ID.
*/
private Transport getTransportById(String id) {
for (Transport transport : getTransportMap().keySet()) {
if (transport.getId().equalsIgnoreCase(id))
return transport;
}
return null;
}
/**
* Return a Map of Transport and associated Modules for the current protocol.
*/
private Map<Transport, Collection<Module>> getTransportMap() {
return logger.getProtocolList().get(settings.getLoggerProtocol());
}
/**
* Return a Module based on its String name.
*/
private Module getModule(String name) {
final Collection<Module> modules = getTransportMap().get(
getTransportById(settings.getTransportProtocol()));
for (Module module: modules) {
if (module.getName().equalsIgnoreCase(name))
return module;
}
return null;
}
}