/*
* 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.ramtune.test;
import static com.romraider.util.HexUtil.asBytes;
import static com.romraider.util.HexUtil.asHex;
import static com.romraider.util.ThreadUtil.runAsDaemon;
import static com.romraider.util.ThreadUtil.sleep;
import static com.romraider.util.ParamChecker.isNullOrEmpty;
import static java.awt.FlowLayout.LEFT;
import static java.awt.Font.PLAIN;
import static java.awt.GridBagConstraints.BOTH;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.WARNING_MESSAGE;
import static javax.swing.JOptionPane.YES_NO_OPTION;
import static javax.swing.JOptionPane.YES_OPTION;
import static javax.swing.JOptionPane.showConfirmDialog;
import static javax.swing.JOptionPane.showMessageDialog;
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
import static javax.swing.border.BevelBorder.LOWERED;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import com.romraider.Settings;
import com.romraider.io.connection.ConnectionProperties;
import com.romraider.io.protocol.Protocol;
import com.romraider.io.protocol.ProtocolFactory;
import com.romraider.io.serial.port.SerialPortRefresher;
import com.romraider.logger.ecu.comms.io.protocol.LoggerProtocol;
import com.romraider.logger.ecu.comms.manager.PollingState;
import com.romraider.logger.ecu.comms.manager.PollingStateImpl;
import com.romraider.logger.ecu.definition.EcuDataLoader;
import com.romraider.logger.ecu.definition.EcuDataLoaderImpl;
import com.romraider.logger.ecu.definition.Module;
import com.romraider.logger.ecu.definition.Transport;
import com.romraider.logger.ecu.ui.SerialPortComboBox;
import com.romraider.ramtune.test.command.executor.CommandExecutor;
import com.romraider.ramtune.test.command.executor.CommandExecutorImpl;
import com.romraider.ramtune.test.command.generator.CommandGenerator;
import com.romraider.ramtune.test.command.generator.EcuInitCommandGenerator;
import com.romraider.ramtune.test.command.generator.ReadCommandGenerator;
import com.romraider.ramtune.test.command.generator.WriteCommandGenerator;
import com.romraider.ramtune.test.io.RamTuneTestAppConnectionProperties;
import com.romraider.swing.AbstractFrame;
import com.romraider.swing.LookAndFeelManager;
import com.romraider.util.LogManager;
import com.romraider.util.SettingsManager;
/*
* This is a test app! Use at your own risk!!
* It borrows some functionality from the logger which should be rewritten/removed before being released!!
*
* It is also a bit of a mess and needs to be cleaned up...
*/
public final class RamTuneTestApp extends AbstractFrame {
private static final long serialVersionUID = 7140513114169019846L;
private static final String REGEX_VALID_ADDRESS_BYTES = "[0-9a-fA-F]{6}";
private static final String REGEX_VALID_DATA_BYTES = "[0-9a-fA-F]{2,}";
private static final PollingState pollMode = new PollingStateImpl();
private static final String ISO9141 = "ISO9141";
private static Protocol protocol;
private final JTextField addressField = new JTextField(6);
private final JTextField lengthField = new JTextField(4);
private final JTextField sendTimeoutField = new JTextField(4);
private final JTextField blocksize = new JTextField(3);
private final JTextArea dataField = new JTextArea(5, 80);
private final JTextArea responseField = new JTextArea(10, 80);
private final JCheckBox blockRead = new JCheckBox("Block Read");
private final SerialPortComboBox portsComboBox;
private final JComboBox commandComboBox;
private static Module module;
private static String userTp;
private static String userLibrary;
private static String target;
private static Settings settings = SettingsManager.getSettings();
private Map<String, Map<Transport, Collection<Module>>> protocolList =
new HashMap<String, Map<Transport, Collection<Module>>>();
public RamTuneTestApp(String title) {
super(title);
final EcuDataLoader dataLoader = new EcuDataLoaderImpl();
if (isNullOrEmpty(settings.getLoggerDefinitionFilePath())) {
showErrorDialog("A Logger definition file needs to be configured before connecting.");
windowClosing(null);
}
dataLoader.loadConfigFromXml(
settings.getLoggerDefinitionFilePath(),
settings.getLoggerProtocol(),
settings.getFileLoggingControllerSwitchId(), null);
protocolList = dataLoader.getProtocols();
target = settings.getTargetModule();
portsComboBox = new SerialPortComboBox();
userTp = settings.getTransportProtocol();
userLibrary = settings.getJ2534Device();
settings.setTransportProtocol(ISO9141);
// Read Address blocks only seems to work with ISO9141, it
// may not be implemented in the ECU for ISO15765
final LoggerProtocol lp = ProtocolFactory.getProtocol(
settings.getLoggerProtocol(),
ISO9141
);
protocol = lp.getProtocol();
commandComboBox = new JComboBox(new CommandGenerator[]{
new EcuInitCommandGenerator(protocol),
new ReadCommandGenerator(protocol),
new WriteCommandGenerator(protocol)});
initUserInterface();
startPortRefresherThread();
}
private void startPortRefresherThread() {
SerialPortRefresher serialPortRefresher = new SerialPortRefresher(portsComboBox, SettingsManager.getSettings().getLoggerPort());
runAsDaemon(serialPortRefresher);
// wait until port refresher fully started before continuing
while (!serialPortRefresher.isStarted()) {
sleep(100);
}
}
private void initUserInterface() {
// setup main panel
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel contentPanel = buildContentPanel();
mainPanel.add(contentPanel, BorderLayout.CENTER);
// add to container
getContentPane().add(mainPanel);
}
private JPanel buildContentPanel() {
GridBagLayout gridBagLayout = new GridBagLayout();
JPanel mainPanel = new JPanel(gridBagLayout);
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = BOTH;
constraints.insets = new Insets(3, 5, 3, 5);
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 2;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 0;
mainPanel.add(buildComPortPanel(), constraints);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
mainPanel.add(buildInputPanel(), constraints);
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
mainPanel.add(buildOutputPanel(), constraints);
return mainPanel;
}
private Component buildInputPanel() {
GridBagLayout gridBagLayout = new GridBagLayout();
JPanel inputPanel = new JPanel(gridBagLayout);
inputPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), "Command"));
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.insets = new Insets(0, 5, 5, 5);
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
inputPanel.add(commandComboBox, constraints);
JPanel addressFieldPanel = new JPanel(new FlowLayout());
addressFieldPanel.add(new JLabel("Address (eg. 020000):"));
addressFieldPanel.add(addressField);
JPanel lengthPanel = new JPanel(new FlowLayout());
lengthPanel.add(new JLabel(" Read Length:"));
lengthField.setText("1");
lengthPanel.add(lengthField);
lengthPanel.add(new JLabel("byte(s)"));
JPanel blockReadPanel = new JPanel(new FlowLayout());
blockRead.setSelected(true);
blockRead.setToolTipText("uncheck to read range byte at a time");
blockReadPanel.add(blockRead);
blockReadPanel.add(new JLabel("Block Size:"));
blocksize.setText("128");
blocksize.setToolTipText("Set to value allowed by the ECU");
blockReadPanel.add(blocksize);
JPanel addressPanel = new JPanel(new FlowLayout(LEFT));
addressPanel.add(addressFieldPanel);
addressPanel.add(lengthPanel);
addressPanel.add(blockReadPanel);
constraints.gridx = 1;
constraints.gridy = 0;
constraints.gridwidth = 4;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
inputPanel.add(addressPanel, constraints);
dataField.setFont(new Font("Monospaced", PLAIN, 12));
dataField.setLineWrap(true);
dataField.setBorder(new BevelBorder(LOWERED));
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 5;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
inputPanel.add(new JScrollPane(dataField, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_NEVER), constraints);
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 5;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
inputPanel.add(buildSendButton(), constraints);
return inputPanel;
}
private Component buildOutputPanel() {
responseField.setFont(new Font("Monospaced", PLAIN, 12));
responseField.setLineWrap(true);
responseField.setEditable(false);
responseField.setBorder(new BevelBorder(LOWERED));
JScrollPane responseScrollPane = new JScrollPane(responseField, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_NEVER);
responseScrollPane.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), "Trace"));
return responseScrollPane;
}
private JButton buildSendButton() {
final JButton button = new JButton("Send Command");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
runAsDaemon(new Runnable() {
@Override
public void run() {
button.setEnabled(false);
CommandExecutor commandExecutor = null;
try {
ConnectionProperties connectionProperties = new RamTuneTestAppConnectionProperties(protocol.getDefaultConnectionProperties(), getSendTimeout());
commandExecutor = new CommandExecutorImpl(connectionProperties, (String) portsComboBox.getSelectedItem());
final CommandGenerator commandGenerator = (CommandGenerator) commandComboBox.getSelectedItem();
if (validateInput(commandGenerator) && confirmCommandExecution(commandGenerator)) {
StringBuilder builder = new StringBuilder();
List<byte[]> commands = commandGenerator.createCommands(module, getData(), getAddress(), getLength(), getBlockRead(), getBlockSize());
for (byte[] command : commands) {
appendResponseLater("SND [" + commandGenerator + "]:\t" + asHex(command) + "\n");
byte[] response = protocol.preprocessResponse(command, commandExecutor.executeCommand(command), pollMode);
appendResponseLater("RCV [" + commandGenerator + "]:\t" + asHex(response) + "\n");
builder.append(asHex(protocol.parseResponseData(response)));
}
appendResponseLater("DATA [Raw]:\t" + builder.toString() + "\n\n");
}
} catch (Exception ex) {
reportError(ex);
} finally {
commandExecutor.close();
button.setEnabled(true);
}
}
});
}
});
return button;
}
private void appendResponseLater(final String text) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
responseField.append(text);
}
});
}
private byte[] getAddress() {
try {
return asBytes(addressField.getText());
} catch (Exception e) {
return null;
}
}
private byte[] getData() {
try {
return asBytes(dataField.getText());
} catch (Exception e) {
return null;
}
}
private int getLength() {
return getIntFromField(lengthField);
}
private boolean getBlockRead() {
return blockRead.isSelected();
}
private int getBlockSize() {
return getIntFromField(blocksize);
}
private int getSendTimeout() {
return getIntFromField(sendTimeoutField);
}
private int getIntFromField(JTextField field) {
try {
return Integer.parseInt(field.getText().trim());
} catch (NumberFormatException e) {
return -1;
}
}
private boolean validateInput(CommandGenerator commandGenerator) {
boolean isReadCommandGenerator = ReadCommandGenerator.class.isAssignableFrom(commandGenerator.getClass());
boolean isWriteCommandGenerator = WriteCommandGenerator.class.isAssignableFrom(commandGenerator.getClass());
if (isReadCommandGenerator || isWriteCommandGenerator) {
String address = addressField.getText();
if (address.trim().length() != 6) {
showErrorDialog("Invalid address - must be 3 bytes long.");
return false;
} else if (!address.matches(REGEX_VALID_ADDRESS_BYTES)) {
showErrorDialog("Invalid address - bad bytes.");
return false;
}
}
if (isReadCommandGenerator) {
try {
int length = Integer.parseInt(lengthField.getText().trim());
if (length <= 0) {
showErrorDialog("Invalid length - must be greater then zero.");
return false;
}
} catch (NumberFormatException e) {
showErrorDialog("Invalid length.");
return false;
}
}
if (isWriteCommandGenerator) {
String data = dataField.getText().trim();
int dataLength = data.length();
if (dataLength == 0) {
showErrorDialog("No data specified.");
return false;
} else if (dataLength % 2 != 0) {
showErrorDialog("Invalid data - odd number of characters.");
return false;
} else if (!data.matches(REGEX_VALID_DATA_BYTES)) {
showErrorDialog("Invalid data - bad bytes.");
return false;
}
}
return true;
}
private boolean confirmCommandExecution(CommandGenerator commandGenerator) {
boolean isWriteCommandGenerator = WriteCommandGenerator.class.isAssignableFrom(commandGenerator.getClass());
return !isWriteCommandGenerator || showConfirmDialog(this, "Are you sure you want to write to ECU memory?",
"Confirm Write Command", YES_NO_OPTION, WARNING_MESSAGE) == YES_OPTION;
}
private JPanel buildComPortPanel() {
JPanel panel = new JPanel(new FlowLayout(LEFT));
panel.add(buildComPorts());
panel.add(buildSendTimeout());
final ButtonGroup moduleGroup = new ButtonGroup();
for (Module module : getModuleList()) {
final JCheckBox cb = new JCheckBox(module.getName().toUpperCase());
final String tipText = String.format(
"%s Polling.", module.getDescription());
cb.setToolTipText(tipText);
if (settings.getTargetModule().equalsIgnoreCase(module.getName())) {
cb.setSelected(true);
setTarget(module.getName());
}
cb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
final JCheckBox source = (JCheckBox) actionEvent.getSource();
if (source.isSelected()) {
setTarget(source.getText());
}
}
});
moduleGroup.add(cb);
panel.add(cb);
}
return panel;
}
private void setTarget(String name) {
for (Module module: getModuleList()) {
if (module.getName().equalsIgnoreCase(name)) {
RamTuneTestApp.module = module;
}
}
}
private Transport getTransportById(String id) {
Transport loggerTransport = null;
for (Transport transport : getTransportMap().keySet()) {
if (transport.getId().equalsIgnoreCase(id))
loggerTransport = transport;
}
return loggerTransport;
}
private Map<Transport, Collection<Module>> getTransportMap() {
return protocolList.get(settings.getLoggerProtocol());
}
private Collection<Module> getModuleList() {
return getTransportMap().get(getTransportById(settings.getTransportProtocol()));
}
private Component buildSendTimeout() {
sendTimeoutField.setText("55");
JPanel panel = new JPanel(new FlowLayout());
panel.add(new JLabel("Send Timeout:"));
panel.add(sendTimeoutField);
panel.add(new JLabel("ms"));
return panel;
}
private JPanel buildComPorts() {
JPanel panel = new JPanel(new FlowLayout());
panel.add(new JLabel("COM Port:"));
panel.add(portsComboBox);
return panel;
}
private void reportError(Exception e) {
final Writer writer = new StringWriter();
final PrintWriter printWriter = new PrintWriter(writer);
e.printStackTrace(printWriter);
responseField.append("\n**************************************************************************\n");
responseField.append("ERROR: ");
responseField.append(writer.toString());
responseField.append("\n**************************************************************************\n\n");
//showErrorDialog("An error occurred:\n\n" + writer.toString());
}
private void showErrorDialog(String message) {
showMessageDialog(this, message, "Error", ERROR_MESSAGE);
}
//**********************************************************************
public static void main(String[] args) {
LogManager.initDebugLogging();
LookAndFeelManager.initLookAndFeel();
startTestApp(EXIT_ON_CLOSE);
}
public static void startTestApp(final int defaultCloseOperation) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
RamTuneTestApp ramTuneTestApp = new RamTuneTestApp("Control Module Read/Write");
ramTuneTestApp.setIconImage(new ImageIcon( getClass().getResource("/graphics/romraider-ico.gif")).getImage());
ramTuneTestApp.setDefaultCloseOperation(defaultCloseOperation);
ramTuneTestApp.addWindowListener(ramTuneTestApp);
ramTuneTestApp.setLocation(100, 50);
ramTuneTestApp.pack();
ramTuneTestApp.setVisible(true);
}
});
}
@Override
public void windowClosing(WindowEvent e) {
setTarget(target);
settings.setTransportProtocol(userTp);
settings.setJ2534Device(userLibrary);
}
}