package com.rusefi; import com.devexperts.logging.Logging; import com.rusefi.config.generated.Fields; import com.rusefi.ui.RpmLabel; import com.rusefi.ui.RpmModel; import com.rusefi.ui.UIContext; import com.rusefi.ui.config.ConfigField; import com.rusefi.ui.config.EnumConfigField; import com.rusefi.ui.engine.EngineSnifferPanel; import com.rusefi.core.preferences.storage.Node; import com.rusefi.ui.util.URLLabel; import com.rusefi.ui.util.UiUtils; import com.rusefi.ui.widgets.AnyCommand; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; import java.util.List; import static com.devexperts.logging.Logging.getLogging; /** * Date: 12/21/13 * Andrey Belomutskiy, (c) 2013-2020 */ public class SensorSnifferPane { private static final Logging log = getLogging(SensorSnifferPane.class); private static final String HELP_URL = "http://rusefi.com/wiki/index.php?title=Manual:DevConsole#Analog_Chart"; private final TreeMap values = new TreeMap<>(); private final SensorSnifferCanvas canvas = new SensorSnifferCanvas(); private double minX; private double maxX; private double minY; private double maxY; private final JPanel content = new JPanel(new BorderLayout()); private final AnyCommand command; private boolean paused = false; public SensorSnifferPane(UIContext uiContext, Node config) { uiContext.sensorSnifferCentral.addListener(new SensorSnifferCentral.AnalogChartListener() { @Override public void onAnalogChart(final String message) { // this callback is invoked from the connectivity thread, need to handle in AWT for thread-safety SwingUtilities.invokeLater(new Runnable() { @Override public void run() { unpackValues(message, values); if (!paused) { processValues(); UiUtils.trueRepaint(canvas); } } }); } }); final JPanel upperPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); final JButton pauseButton = UiUtils.createPauseButton(); JButton clearButton = UiUtils.createClearButton(); clearButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { clear(); UiUtils.trueRepaint(canvas); setPaused(pauseButton, false); } }); upperPanel.add(clearButton); JButton saveImageButton = UiUtils.createSaveImageButton(); upperPanel.add(saveImageButton); saveImageButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int rpm = RpmModel.getInstance().getValue(); String fileName = FileLog.getDate() + "_rpm_" + rpm + "_sensor" + ".png"; UiUtils.saveImageWithPrompt(fileName, upperPanel, canvas); } } ); upperPanel.add(pauseButton); upperPanel.add(new RpmLabel(uiContext,2).getContent()); command = AnyCommand.createField(uiContext, config, true, false); upperPanel.add(command.getContent()); upperPanel.add(new URLLabel(EngineSnifferPanel.HELP_TEXT, HELP_URL)); pauseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setPaused(pauseButton, !paused); } } ); // upperPanel.setBorder(BorderFactory.createLineBorder(Color.orange)); content.add(upperPanel, BorderLayout.NORTH); content.add(canvas, BorderLayout.CENTER); final JPanel lowerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); lowerPanel.setBorder(BorderFactory.createLineBorder(Color.cyan)); content.add(lowerPanel, BorderLayout.SOUTH); lowerPanel.add(new EnumConfigField(uiContext, Fields.SENSORCHARTMODE, "Mode").getContent()); lowerPanel.add(new ConfigField(uiContext, Fields.SENSORSNIFFERRPMTHRESHOLD, "RPM threshold").getContent()); } private void setPaused(JButton pauseButton, boolean isPaused) { paused = isPaused; UiUtils.setPauseButtonText(pauseButton, paused); } private void clear() { minX = maxX = minY = maxY = 0; values.clear(); } private void processValues() { List keys = new ArrayList<>(values.keySet()); minX = keys.get(0); maxX = keys.get(keys.size() - 1); log.info("Analog chart from " + minX + " to " + maxX); TreeSet sortedValues = new TreeSet<>(values.values()); List values = new ArrayList<>(sortedValues); minY = values.get(0); maxY = values.get(values.size() - 1); } public JComponent getPanel() { return content; } public ActionListener getTabSelectedListener() { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { command.requestFocus(); } }; } private class SensorSnifferCanvas extends JComponent { @Override public void paint(Graphics g) { super.paint(g); Dimension size = getSize(); g.drawString("X range from " + minX + " to " + maxX, 4, 20); g.drawString("Y range from " + minY + " to " + maxY, 4, 40); int prevX = 0; int prevY = size.height; double bX = size.width / (maxX - minX); double bY = size.height / (maxY - minY); for (Map.Entry e : values.entrySet()) { int x = (int) ((e.getKey() - minX) * bX); int y = size.height - (int) ((e.getValue() - minY) * bY); g.drawLine(prevX, prevY, x, y); prevX = x; prevY = y; } } } private static void unpackValues(String chart, TreeMap destination) { destination.clear(); String[] tokens = chart.split("\\|"); for (int i = 0; i < tokens.length - 1; ) { String key = tokens[i++]; String value = tokens[i++]; destination.put(Double.parseDouble(key), Double.parseDouble(value)); } } }