/*
* 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.LoggerConnection;
import com.romraider.logger.ecu.comms.learning.flkctable.SSMFlkcTableQueryBuilder;
import com.romraider.logger.ecu.comms.learning.parameter.SSMParameter;
import com.romraider.logger.ecu.comms.learning.parameter.SSMParameterCrossReference;
import com.romraider.logger.ecu.comms.learning.parameter.ParameterIdComparator;
import com.romraider.logger.ecu.comms.learning.tableaxis.SSMTableAxisQueryParameterSet;
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.SSMLearningTableValuesResultsPanel;
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 SSMLearningTableValues extends SwingWorker<Void, Void>
implements LearningTableValues {
private static final Logger LOGGER =
Logger.getLogger(SSMLearningTableValues.class);
private static final List<String> AF_TABLE_NAMES = Arrays.asList(
"A/F Learning #1 Airflow Ranges",
"A/F Learning #1 Airflow Ranges ",
"A/F Learning Airflow Ranges");
private static final List<String> FLKC_LOAD_TABLE_NAMES = Arrays.asList(
"Fine Correction Columns (Load)",
"Fine Correction Columns (Load) ");
private static final List<String> FLKC_RPM_TABLE_NAMES = Arrays.asList(
"Fine Correction Rows (RPM)",
"Fine Correction Rows (RPM) ");
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 SSMLearningTableValues() {}
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. Learning\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 {
LoggerConnection connection = 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 A/F Learning ranges...";
messageListener.reportMessage(message);
String[] afRanges = new String[0];
queries.clear();
queries = getTableAxisRanges(document, ecuDef, AF_TABLE_NAMES);
if (queries != null && !queries.isEmpty()) {
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("A/F Learning ranges retrieved.");
afRanges = formatRanges(queries, "%.2f");
}
message = "Retrieving FLKC Load ranges...";
messageListener.reportMessage(message);
String[] flkcLoad = new String[0];
queries.clear();
queries = getTableAxisRanges(document, ecuDef, FLKC_LOAD_TABLE_NAMES);
if (queries != null && !queries.isEmpty()) {
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("FLKC Load ranges retrieved.");
flkcLoad = formatRanges(queries, "%.2f");
}
message = "Retrieving FLKC RPM ranges...";
messageListener.reportMessage(message);
String[] flkcRpm = new String[0];
queries.clear();
queries = getTableAxisRanges(document, ecuDef, FLKC_RPM_TABLE_NAMES);
if (queries != null && !queries.isEmpty()) {
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("FLKC RPM ranges retrieved.");
flkcRpm = formatRpmRanges(queries);
}
List<List<EcuQuery>> flkcQueryGroups = new ArrayList<List<EcuQuery>>();
if (flkc != null) {
flkcQueryGroups = new SSMFlkcTableQueryBuilder().build(
flkc,
flkcAddr,
flkcRpm.length,
flkcLoad.length - 1);
for (int i = 0; i < flkcQueryGroups.size(); i++) {
queries.clear();
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 FLKC row %d values...", i);
messageListener.reportMessage(message);
LOGGER.info(message);
connection.sendAddressReads(
queries,
settings.getDestinationTarget(),
new PollingStateImpl());
LOGGER.info("FLKC row " + i + " values retrieved.");
}
}
else {
message = String.format("Error retrieving FLKC data values, missing FLKC reference");
messageListener.reportMessage(message);
LOGGER.error(message);
}
messageListener.reportMessage(
"Learning Table Values retrieved successfully.");
final SSMLearningTableValuesResultsPanel results =
new SSMLearningTableValuesResultsPanel(
logger, vehicleInfo,
afRanges, afLearning,
flkcLoad, flkcRpm, flkcQueryGroups);
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 learning values");
LOGGER.error(message + " Error retrieving values", e);
showMessageDialog(logger,
message +
"\nError performing Learning 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",
"Learning 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 IAM and FLKC
* parameters used to locate and calculate the FLKC 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 SSMParameter parameterId =
SSMParameter.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 FLKC table in RAM base on a Extended
* parameter if defined.
* Also isolate the FLKC extended parameter to use the data converter
* when building the FLKC table queries.
*/
private final void setFlkcTableAddress(
ParameterRow parameterRow,
SSMParameter parameterId) {
switch (parameterId) {
case E1:
if (flkcAddr == 0) {
flkcAddr = getParameterAddr(parameterRow) + 0x02;
}
break;
case E31:
if (flkcAddr == 0) {
flkcAddr = getParameterAddr(parameterRow) + 0x14;
}
break;
case E12:
case E41:
if (flkc == null) {
flkc = parameterRow;
}
break;
case E173:
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("Subaru ", ""));
}
/**
* Retrieve the table axis values from the ECU definition. First try the
* 4-cyl table names, if still empty try the 6-cyl table name.
*/
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 SSMParameterCrossReference parameterMap = new SSMParameterCrossReference();
final List<Object> afLearningBank1 = new ArrayList<Object>();
final List<Object> afLearningBank2 = new ArrayList<Object>();
for (EcuQuery query : queries) {
final SSMParameter parameterId =
SSMParameter.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 E1:
result = String.format("%.0f", query.getResponse());
vehicleInfo.put(paramDesc, result);
break;
case E31:
result = String.format("%.3f", query.getResponse());
vehicleInfo.put(paramDesc, result);
break;
case E12:
case E41:
case E173:
break;
case E13:
case E44:
afLearningBank1.add((Object) "#1");
afLearningBank1.add((Object) result);
break;
case E14:
case E45:
case E15:
case E46:
afLearningBank1.add((Object) result);
break;
case E16:
case E47:
afLearningBank1.add((Object) result);
afLearning.add(afLearningBank1);
break;
case E62:
afLearningBank2.add((Object) "#2");
afLearningBank2.add((Object) result);
break;
case E63:
case E64:
afLearningBank2.add((Object) result);
break;
case E65:
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 = SSMTableAxisQueryParameterSet.build(
tableMap.get("storageaddress"),
tableMap.get("storagetype"),
tableMap.get("expression"),
tableMap.get("units"),
tableMap.get("sizey")
);
}
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 rangeMin = 0;
double rangeMax = 0;
for (EcuQuery ecuQuery : axisRanges) {
rangeMax = ecuQuery.getResponse() - 0.01;
final String range = String.format(
numberFormat + " - " + numberFormat,
rangeMin,
rangeMax);
ranges.add(range);
rangeMin = ecuQuery.getResponse();
rangeMax = ecuQuery.getResponse();
}
final String range = String.format(numberFormat + "+", rangeMax);
ranges.add(range);
return ranges.toArray(new String[0]);
}
/**
* Format the RPM range data to be used as FLKC table row header values.
*/
private final String[] formatRpmRanges(Collection<EcuQuery> axisRanges) {
final List<String> ranges = new ArrayList<String>();
double rangeMin = 0;
double rangeMax = 0;
for (EcuQuery ecuQuery : axisRanges) {
rangeMax = ecuQuery.getResponse() - 1;
final String range = String.format(
"%.0f - %.0f",
rangeMin,
rangeMax);
ranges.add(range);
rangeMin = ecuQuery.getResponse();
rangeMax = ecuQuery.getResponse();
}
final String range = String.format("%.0f+", rangeMax);
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;
}
}