/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2012 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.ui.tab.injector;
import static com.romraider.logger.ecu.ui.tab.TableFinder.findTableStartsWith;
import static com.romraider.util.ParamChecker.checkNotNull;
import static java.awt.GridBagConstraints.CENTER;
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.NONE;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.OK_OPTION;
import static javax.swing.JOptionPane.WARNING_MESSAGE;
import static javax.swing.JOptionPane.YES_NO_OPTION;
import static javax.swing.JOptionPane.showConfirmDialog;
import static javax.swing.JOptionPane.showMessageDialog;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.border.TitledBorder;
import org.apache.log4j.Logger;
import com.romraider.editor.ecu.ECUEditor;
import com.romraider.logger.ecu.definition.EcuParameter;
import com.romraider.logger.ecu.definition.EcuSwitch;
import com.romraider.logger.ecu.definition.ExternalData;
import com.romraider.logger.ecu.definition.LoggerData;
import com.romraider.logger.ecu.ui.DataRegistrationBroker;
import com.romraider.logger.ecu.ui.tab.LoggerChartPanel;
import com.romraider.maps.DataCell;
import com.romraider.maps.Rom;
import com.romraider.maps.Table;
import com.romraider.maps.Table2D;
public final class InjectorControlPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = -3570410894599258706L;
private static final Logger LOGGER = Logger.getLogger(InjectorControlPanel.class);
private static final String COOLANT_TEMP = "P2";
private static final String ENGINE_SPEED = "P8";
private static final String INTAKE_AIR_TEMP = "P11";
private static final String MASS_AIR_FLOW = "P12";
private static final String MASS_AIR_FLOW_V = "P18";
private static final String AFR = "P58";
private static final String CL_OL_16 = "E3";
private static final String CL_OL_32 = "E33";
private static final String PULSE_WIDTH_16 = "E28";
private static final String PULSE_WIDTH_32 = "E60";
private static final String TIP_IN_THROTTLE_16 = "E23";
private static final String TIP_IN_THROTTLE_32 = "E54";
private static final String ENGINE_LOAD_16 = "E2";
private static final String ENGINE_LOAD_32 = "E32";
private final JToggleButton recordDataButton = new JToggleButton("Record Data");
private final JTextField mafvMin = new JTextField("1.20", 3);
private final JTextField mafvMax = new JTextField("2.60", 3);
private final JTextField afrMin = new JTextField("13.0", 3);
private final JTextField afrMax = new JTextField("16.0", 3);
private final JTextField rpmMin = new JTextField("0", 3);
private final JTextField rpmMax = new JTextField("4500", 3);
private final JTextField mafMin = new JTextField("20", 3);
private final JTextField mafMax = new JTextField("100", 3);
private final JTextField iatMax = new JTextField("45", 3);
private final JTextField coolantMin = new JTextField("70", 3);
private final JTextField mafvChangeMax = new JTextField("0.1", 3);
private final JTextField fuelStoichAfr = new JTextField("14.7", 5);
private final JTextField fuelDensity = new JTextField("732", 5);
private final JTextField flowScaling = new JTextField("", 5);
private final JTextField latencyOffset = new JTextField("", 5);
private final DataRegistrationBroker broker;
private final LoggerChartPanel chartPanel;
private final ECUEditor ecuEditor;
private final Component parent;
private List<EcuParameter> params;
private List<EcuSwitch> switches;
private List<ExternalData> externals;
public InjectorControlPanel(Component parent, DataRegistrationBroker broker, ECUEditor ecuEditor, LoggerChartPanel chartPanel) {
checkNotNull(parent, broker, chartPanel);
this.broker = broker;
this.parent = parent;
this.chartPanel = chartPanel;
this.ecuEditor = ecuEditor;
addControls();
}
public double getFuelStoichAfr() {
return getProperty(fuelStoichAfr, "Fuel Stoich. AFR");
}
public double getFuelDensity() {
return getProperty(fuelDensity, "Fuel Density");
}
public boolean isRecordData() {
return recordDataButton.isSelected();
}
public boolean isValidClOl(double value) {
return value == 8;
}
public boolean isValidAfr(double value) {
return checkInRange("AFR", afrMin, afrMax, value);
}
public boolean isValidRpm(double value) {
return checkInRange("RPM", rpmMin, rpmMax, value);
}
public boolean isValidMaf(double value) {
return checkInRange("MAF", mafMin, mafMax, value);
}
public boolean isValidMafv(double value) {
return checkInRange("MAFv", mafvMin, mafvMax, value);
}
public boolean isValidCoolantTemp(double value) {
return checkGreaterThan("Coolant Temp.", coolantMin, value);
}
public boolean isValidIntakeAirTemp(double value) {
return checkLessThan("Intake Air Temp.", iatMax, value);
}
public boolean isValidMafvChange(double value) {
return checkLessThan("dMAFv/dt", mafvChangeMax, value);
}
public boolean isValidTipInThrottle(double value) {
return value == 0.0;
}
private double getProperty(JTextField field, String name) {
if (isNumber(field)) return parseDouble(field);
showMessageDialog(parent, "Invalid " + name + " value specified.", "Error", ERROR_MESSAGE);
recordDataButton.setSelected(false);
return 0.0;
}
private boolean checkInRange(String name, JTextField min, JTextField max, double value) {
if (isValidRange(min, max)) {
return inRange(value, min, max);
} else {
showMessageDialog(parent, "Invalid " + name + " range specified.", "Error", ERROR_MESSAGE);
recordDataButton.setSelected(false);
return false;
}
}
private boolean checkGreaterThan(String name, JTextField min, double value) {
if (isNumber(min)) {
return value >= parseDouble(min);
} else {
showMessageDialog(parent, "Invalid " + name + " specified.", "Error", ERROR_MESSAGE);
recordDataButton.setSelected(false);
return false;
}
}
private boolean checkLessThan(String name, JTextField max, double value) {
if (isNumber(max)) {
return value <= parseDouble(max);
} else {
showMessageDialog(parent, "Invalid " + name + " specified.", "Error", ERROR_MESSAGE);
recordDataButton.setSelected(false);
return false;
}
}
private void addControls() {
JPanel panel = new JPanel();
GridBagLayout gridBagLayout = new GridBagLayout();
panel.setLayout(gridBagLayout);
add(panel, gridBagLayout, buildFuelPropertiesPanel(), 0, 0, 1, HORIZONTAL);
add(panel, gridBagLayout, buildFilterPanel(), 0, 1, 1, HORIZONTAL);
add(panel, gridBagLayout, buildInterpolatePanel(), 0, 2, 1, HORIZONTAL);
add(panel, gridBagLayout, buildUpdateInjectorPanel(), 0, 3, 1, HORIZONTAL);
add(panel, gridBagLayout, buildResetPanel(), 0, 4, 1, HORIZONTAL);
add(panel);
}
private void add(JPanel panel, GridBagLayout gridBagLayout, JComponent component, int x, int y, int spanX, int fillType) {
GridBagConstraints constraints = buildBaseConstraints();
updateConstraints(constraints, x, y, spanX, 1, 1, 1, fillType);
gridBagLayout.setConstraints(component, constraints);
panel.add(component);
}
private JPanel buildResetPanel() {
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder("Reset"));
panel.add(buildResetButton());
return panel;
}
private JPanel buildInterpolatePanel() {
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder("Interpolate"));
GridBagLayout gridBagLayout = new GridBagLayout();
panel.setLayout(gridBagLayout);
addComponent(panel, gridBagLayout, buildInterpolateButton(), 2);
return panel;
}
private JPanel buildUpdateInjectorPanel() {
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder("Update Injector"));
GridBagLayout gridBagLayout = new GridBagLayout();
panel.setLayout(gridBagLayout);
flowScaling.setEditable(false);
latencyOffset.setEditable(false);
addLabeledComponent(panel, gridBagLayout, "Flow Scaling (cc/min)", flowScaling, 0);
addComponent(panel, gridBagLayout, buildUpdateInjectorScalerButton(), 2);
addLabeledComponent(panel, gridBagLayout, "Latency Offset (ms)", latencyOffset, 3);
addComponent(panel, gridBagLayout, buildUpdateInjectorLatencyButton(), 5);
return panel;
}
private void addLabeledComponent(JPanel panel, GridBagLayout gridBagLayout, String name, JComponent component, int y) {
add(panel, gridBagLayout, new JLabel(name), 0, y, 3, HORIZONTAL);
add(panel, gridBagLayout, component, 0, y + 1, 3, NONE);
}
private JPanel buildFuelPropertiesPanel() {
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder("Fuel Properties"));
GridBagLayout gridBagLayout = new GridBagLayout();
panel.setLayout(gridBagLayout);
addLabeledComponent(panel, gridBagLayout, "Stoich. AFR", fuelStoichAfr, 0);
addLabeledComponent(panel, gridBagLayout, "Density (kg/m3)", fuelDensity, 3);
return panel;
}
private JPanel buildFilterPanel() {
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder("Filter Data"));
GridBagLayout gridBagLayout = new GridBagLayout();
panel.setLayout(gridBagLayout);
addMinMaxFilter(panel, gridBagLayout, "AFR Range", afrMin, afrMax, 0);
addMinMaxFilter(panel, gridBagLayout, "RPM Range", rpmMin, rpmMax, 3);
addMinMaxFilter(panel, gridBagLayout, "MAF Range (g/s)", mafMin, mafMax, 6);
addLabeledComponent(panel, gridBagLayout, "Min. Coolant Temp.", coolantMin, 9);
addLabeledComponent(panel, gridBagLayout, "Max. Intake Temp.", iatMax, 12);
addLabeledComponent(panel, gridBagLayout, "Max. dMAFv/dt (V/s)", mafvChangeMax, 15);
addComponent(panel, gridBagLayout, buildRecordDataButton(), 18);
return panel;
}
private JToggleButton buildRecordDataButton() {
recordDataButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (recordDataButton.isSelected()) {
registerData(COOLANT_TEMP, ENGINE_SPEED, INTAKE_AIR_TEMP, MASS_AIR_FLOW, MASS_AIR_FLOW_V, AFR, CL_OL_16, CL_OL_32, TIP_IN_THROTTLE_16, TIP_IN_THROTTLE_32, PULSE_WIDTH_16, PULSE_WIDTH_32, ENGINE_LOAD_16, ENGINE_LOAD_32);
} else {
deregisterData(COOLANT_TEMP, ENGINE_SPEED, INTAKE_AIR_TEMP, MASS_AIR_FLOW, MASS_AIR_FLOW_V, AFR, CL_OL_16, CL_OL_32, TIP_IN_THROTTLE_16, TIP_IN_THROTTLE_32, PULSE_WIDTH_16, PULSE_WIDTH_32, ENGINE_LOAD_16, ENGINE_LOAD_32);
}
}
});
return recordDataButton;
}
private void registerData(String... ids) {
for (String id : ids) {
LoggerData data = findData(id);
if (data != null) broker.registerLoggerDataForLogging(data);
}
}
private void deregisterData(String... ids) {
for (String id : ids) {
LoggerData data = findData(id);
if (data != null) broker.deregisterLoggerDataFromLogging(data);
}
}
private LoggerData findData(String id) {
for (EcuParameter param : params) {
if (id.equals(param.getId())) return param;
}
for (EcuSwitch sw : switches) {
if (id.equals(sw.getId())) return sw;
}
for (ExternalData external : externals) {
if (id.equals(external.getId())) return external;
}
LOGGER.warn("Logger data not found for id: " + id);
return null;
}
private void addComponent(JPanel panel, GridBagLayout gridBagLayout, JComponent component, int y) {
add(panel, gridBagLayout, component, 0, y, 3, HORIZONTAL);
}
private void addMinMaxFilter(JPanel panel, GridBagLayout gridBagLayout, String name, JTextField min, JTextField max, int y) {
add(panel, gridBagLayout, new JLabel(name), 0, y, 3, HORIZONTAL);
y += 1;
add(panel, gridBagLayout, min, 0, y, 1, NONE);
add(panel, gridBagLayout, new JLabel(" - "), 1, y, 1, NONE);
add(panel, gridBagLayout, max, 2, y, 1, NONE);
}
private GridBagConstraints buildBaseConstraints() {
GridBagConstraints constraints = new GridBagConstraints();
constraints.anchor = CENTER;
constraints.fill = NONE;
return constraints;
}
private void updateConstraints(GridBagConstraints constraints, int gridx, int gridy, int gridwidth, int gridheight, int weightx, int weighty, int fill) {
constraints.gridx = gridx;
constraints.gridy = gridy;
constraints.gridwidth = gridwidth;
constraints.gridheight = gridheight;
constraints.weightx = weightx;
constraints.weighty = weighty;
constraints.fill = fill;
}
private JButton buildResetButton() {
JButton resetButton = new JButton("Reset Data");
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
chartPanel.clear();
parent.repaint();
}
});
return resetButton;
}
private JButton buildInterpolateButton() {
JButton interpolateButton = new JButton("Interpolate");
interpolateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
chartPanel.interpolate(1);
double[] coefficients = chartPanel.getPolynomialCoefficients();
double scaling = coefficients[0] * 1000 * 60;
DecimalFormat format = new DecimalFormat("0.00");
flowScaling.setText(format.format(scaling));
double offset = -1 * coefficients[1] / coefficients[0];
latencyOffset.setText(format.format(offset));
parent.repaint();
}
});
return interpolateButton;
}
private JButton buildUpdateInjectorScalerButton() {
final JButton updateButton = new JButton("Update Scaling");
updateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
if (showUpdateTableConfirmation("Injector Flow Scaling") == OK_OPTION) {
Table2D table = getInjectorFlowTable(ecuEditor);
if (table != null) {
DataCell[] cells = table.getData();
if (cells.length == 1) {
if (isNumber(flowScaling)) {
String value = flowScaling.getText().trim();
cells[0].setRealValue(value);
} else {
showMessageDialog(parent, "Invalid Injector Flow Scaling value.", "Error", ERROR_MESSAGE);
}
}
} else {
showMessageDialog(parent, "Injector Flow Scaling table not found.", "Error", ERROR_MESSAGE);
}
}
} catch (Exception e) {
String msg = e.getMessage() != null && e.getMessage().length() > 0 ? e.getMessage() : "Unknown";
showMessageDialog(parent, "Error: " + msg, "Error", ERROR_MESSAGE);
}
}
});
return updateButton;
}
private JButton buildUpdateInjectorLatencyButton() {
final JButton updateButton = new JButton("Update Latency");
updateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
if (showUpdateTableConfirmation("Injector Latency") == OK_OPTION) {
Table2D table = getInjectorLatencyTable(ecuEditor);
if (table != null) {
DataCell[] cells = table.getData();
if (isNumber(latencyOffset)) {
for (DataCell cell : cells) {
double newLatency = cell.getRealValue() + parseDouble(latencyOffset);
cell.setRealValue("" + newLatency);
}
} else {
showMessageDialog(parent, "Invalid Injector Latency Offset value.", "Error", ERROR_MESSAGE);
}
} else {
showMessageDialog(parent, "Error finding Injector Latency table.", "Error", ERROR_MESSAGE);
}
}
} catch (Exception e) {
String msg = e.getMessage() != null && e.getMessage().length() > 0 ? e.getMessage() : "Unknown";
showMessageDialog(parent, "Error: " + msg, "Error", ERROR_MESSAGE);
}
}
});
return updateButton;
}
private boolean areNumbers(JTextField... textFields) {
for (JTextField field : textFields) {
if (!isNumber(field)) return false;
}
return true;
}
private boolean isValidRange(JTextField min, JTextField max) {
return areNumbers(min, max) && parseDouble(min) < parseDouble(max);
}
private boolean isNumber(JTextField textField) {
try {
parseDouble(textField);
return true;
} catch (Exception e) {
return false;
}
}
private boolean inRange(double val, double min, double max) {
return val >= min && val <= max;
}
private boolean inRange(double value, JTextField min, JTextField max) {
return inRange(value, parseDouble(min), parseDouble(max));
}
private double parseDouble(JTextField field) {
return Double.parseDouble(field.getText().trim());
}
private int showUpdateTableConfirmation(String table) {
return showConfirmDialog(parent, "Update " + table + "?", "Confirm Update", YES_NO_OPTION, WARNING_MESSAGE);
}
private Table2D getInjectorFlowTable(ECUEditor ecuEditor) {
return getTable(ecuEditor, "Injector Flow Scaling");
}
private Table2D getInjectorLatencyTable(ECUEditor ecuEditor) {
return getTable(ecuEditor, "Injector Latency");
}
private <T extends Table> T getTable(ECUEditor ecuEditor, String name) {
try {
Rom rom = ecuEditor.getLastSelectedRom();
return (T) findTableStartsWith(rom, name);
} catch (Exception e) {
LOGGER.warn("Error getting " + name + " table", e);
return null;
}
}
public void setEcuParams(List<EcuParameter> params) {
this.params = new ArrayList<EcuParameter>(params);
}
public void setEcuSwitches(List<EcuSwitch> switches) {
this.switches = new ArrayList<EcuSwitch>(switches);
}
public void setExternalDatas(List<ExternalData> externals) {
this.externals = new ArrayList<ExternalData>(externals);
}
}