package com.rusefi.ui.engine; import com.devexperts.logging.Logging; import com.rusefi.FileLog; import com.rusefi.config.generated.Fields; import com.rusefi.core.EngineState; import com.rusefi.core.Sensor; import com.rusefi.core.SensorCentral; import com.rusefi.ui.*; import com.rusefi.ui.config.BitConfigField; import com.rusefi.ui.config.ConfigField; 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 com.rusefi.waves.EngineChart; import com.rusefi.waves.EngineChartParser; import com.rusefi.waves.EngineReport; 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; /** * Engine Sniffer control consists of a set of {@link UpDownImage} *

*

* Date: 6/23/13 * Andrey Belomutskiy, (c) 2013-2020 * * @see EngineSnifferStatusPanel status bar * @see com.rusefi.ui.test.WavePanelSandbox */ public class EngineSnifferPanel { private static final Logging log = getLogging(EngineSnifferPanel.class); private static final int EFI_DEFAULT_CHART_SIZE = 180; public static final Comparator INSTANCE = new ImageOrderComparator(); private static final String HELP_URL = "http://rusefi.com/wiki/index.php?title=Manual:DevConsole#Digital_Chart"; public static final String HELP_TEXT = "Click here for online help"; private final JPanel chartPanel = new JPanel(new BorderLayout()); /** * chartPanel in the center and warning panel on the bottom */ private final JPanel mainPanel = new JPanel(new BorderLayout()); /** * imageName -> UpDownImage */ private final TreeMap images = new TreeMap<>(INSTANCE); /** * this is the panel which displays all {@link UpDownImage} using {@link GridLayout} */ private final JPanel imagePanel = new JPanel(new GridLayout(1, 1)) { @Override public Dimension getPreferredSize() { Dimension d = chartPanel.getSize(); Dimension s = super.getPreferredSize(); return new Dimension((int) (d.width * zoomControl.getZoomProvider().getZoomValue()), s.height); } }; private final ZoomControl zoomControl = new ZoomControl(); private final EngineSnifferStatusPanel statusPanel = new EngineSnifferStatusPanel(); private final UpDownImage crank = createImage(Fields.PROTOCOL_CRANK1); private final ChartScrollControl scrollControl; private AnyCommand command; private boolean isPaused; public EngineSnifferPanel(UIContext uiContext, Node config) { statusPanel.setTimeAxisTranslator(crank.createTranslator()); final JButton pauseButton = UiUtils.createPauseButton(); pauseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setPaused(pauseButton, !isPaused); } }); JButton clearButton = UiUtils.createClearButton(); clearButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { for (UpDownImage image : images.values()) image.setWaveReport(EngineReport.MOCK, null); setPaused(pauseButton, false); } }); JButton saveImageButton = UiUtils.createSaveImageButton(); saveImageButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveImage(); } }); JPanel upperPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); upperPanel.add(clearButton); upperPanel.add(saveImageButton); upperPanel.add(pauseButton); upperPanel.add(new RpmLabel(uiContext,2).getContent()); if (!uiContext.getLinkManager().isLogViewer()) { command = AnyCommand.createField(uiContext, config, "chartsize " + EFI_DEFAULT_CHART_SIZE, true, true); upperPanel.add(command.getContent()); } upperPanel.add(zoomControl); scrollControl = ChartRepository.getInstance().createControls(chart -> displayChart(chart)); if (uiContext.getLinkManager().isLogViewer()) upperPanel.add(scrollControl.getContent()); upperPanel.add(new URLLabel(HELP_TEXT, HELP_URL)); JPanel bottomPanel = new JPanel(new BorderLayout()); if (!uiContext.getLinkManager().isLogViewer()) { JPanel lowerButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0)); lowerButtons.add(new ConfigField(uiContext, Fields.GLOBALTRIGGERANGLEOFFSET, "Trigger Offset").getContent()); lowerButtons.add(new BitConfigField(uiContext, Fields.VERBOSETRIGGERSYNCHDETAILS, "Verbose trigger Sync").getContent()); lowerButtons.add(new BitConfigField(uiContext, Fields.VERBOSEVVTDECODING, "Verbose VVT Sync").getContent()); lowerButtons.add(new BitConfigField(uiContext, Fields.ENGINESNIFFERFOCUSONINPUTS, "Focus On Inputs").getContent()); lowerButtons.add(new ConfigField(uiContext, Fields.ENGINECHARTSIZE, "Engine Sniffer size").getContent()); lowerButtons.add(new ConfigField(uiContext, Fields.ENGINESNIFFERRPMTHRESHOLD, "RPM threshold").getContent()); lowerButtons.add(new BitConfigField(uiContext, Fields.INVERTPRIMARYTRIGGERSIGNAL, "Invert Primary Input").getContent()); bottomPanel.add(lowerButtons, BorderLayout.NORTH); } bottomPanel.add(statusPanel.infoPanel, BorderLayout.SOUTH); chartPanel.add(upperPanel, BorderLayout.NORTH); JScrollPane pane = new JScrollPane(imagePanel, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); chartPanel.add(pane, BorderLayout.CENTER); chartPanel.add(bottomPanel, BorderLayout.SOUTH); zoomControl.listener = () -> { System.out.println("onZoomChange"); /** * We have scroll pane size which depends on zoom, that's a long chain of dependencies */ UiUtils.trueLayout(imagePanel.getParent()); }; resetImagePanel(); uiContext.getLinkManager().getEngineState().registerStringValueAction(EngineReport.ENGINE_CHART, new EngineState.ValueCallback() { @Override public void onUpdate(String value) { if (isPaused) return; displayChart(value); } }); mainPanel.add(chartPanel, BorderLayout.CENTER); mainPanel.add(new WarningPanel(config).getPanel(config), BorderLayout.SOUTH); } private void setPaused(JButton pauseButton, boolean isPaused) { this.isPaused = isPaused; UiUtils.setPauseButtonText(pauseButton, this.isPaused); } public void setOutpinListener(EngineState engineState) { engineState.registerStringValueAction(Fields.PROTOCOL_OUTPIN, new EngineState.ValueCallback() { @Override public void onUpdate(String value) { String[] pinInfo = value.split("@"); if (pinInfo.length != 2) return; String channel = pinInfo[0]; String pin = pinInfo[1]; UpDownImage image = images.get(channel); ChannelNaming.INSTANCE.channelName2PhysicalPin.put(channel, pin); if (image != null) image.setPhysicalPin(pin); } }); } private void resetImagePanel() { imagePanel.removeAll(); imagePanel.add(crank); images.put(Fields.PROTOCOL_CRANK1, crank); } public void displayChart(String value) { EngineChart map = EngineChartParser.unpackToMap(value); StringBuilder revolutions = map.get(Fields.TOP_DEAD_CENTER_MESSAGE); statusPanel.setRevolutions(revolutions); // Create images for any new keys for (String imageName : map.getMap().keySet()) { createSecondaryImage(imageName); } // Update existing images for (String imageName : images.keySet()) { UpDownImage image = images.get(imageName); if (image == null) throw new IllegalStateException("image not found for " + imageName); StringBuilder sb = map.getMap().get(imageName); String report = sb == null ? "" : sb.toString(); image.setRevolutions(revolutions); List list = EngineReport.parse(report); EngineReport wr = new EngineReport(list); image.setWaveReport(wr, revolutions); } // Repaint now that we've updated state SwingUtilities.invokeLater(() -> UiUtils.trueRepaint(imagePanel)); } public JPanel getPanel() { return mainPanel; } private void createSecondaryImage(String name) { if (images.containsKey(name)) { // already created, skip return; } // Don't render a row for the TDC mark if (Fields.TOP_DEAD_CENTER_MESSAGE.equalsIgnoreCase(name)) { return; } int index = getInsertIndex(name, images.keySet()); log.info("Engine sniffer register channel " + name + " at idx " + index); UpDownImage image = createImage(name); images.put(name, image); image.setTranslator(crank.createTranslator()); imagePanel.add(image, index); imagePanel.setLayout(new GridLayout(images.size(), 1)); } public static int getInsertIndex(String name, Set strings) { String[] mapKeys = new String[strings.size()]; int pos = 0; for (String key : strings) mapKeys[pos++] = key; // int index = -Arrays.binarySearch(mapKeys, name) - 1; int index = -Arrays.binarySearch(mapKeys, 0, mapKeys.length, name, INSTANCE) - 1; if (index >= strings.size()) index = -1; return index; } private void saveImage() { int rpm = RpmModel.getInstance().getValue(); double maf = SensorCentral.getInstance().getValue(Sensor.MAF); String fileName = FileLog.getDate() + "rpm_" + rpm + "_maf_" + maf + ".png"; UiUtils.saveImageWithPrompt(fileName, mainPanel, imagePanel); } private UpDownImage createImage(final String name) { Color signalBody = Color.lightGray; Color signalBorder = Color.blue; if (name.startsWith("tach") || name.startsWith("dizzy")) { signalBody = Color.yellow; } else if (name.startsWith("t")) { // trigger } else if (name.startsWith("r")) { // trailing coil signalBody = new Color(0xffa400); // golden yellow } else if (name.startsWith("c")) { // coil signalBody = Color.darkGray; } else if (name.startsWith("HIP")) { signalBody = Color.white; } else if (name.startsWith("i")) { // injection signalBody = Color.green; } else if (name.startsWith("map")) { signalBody = Color.pink; } else { signalBody = Color.gray; } UpDownImage image = new UpDownImage(name) { @Override protected boolean isShowTdcLabel() { /** * TDC label is only displayed on the bottom UpDown image */ return name.equals(images.lastKey()); } }; image.setSignalBody(signalBody); image.setSignalBorder(signalBorder); image.addMouseMotionListener(statusPanel.motionAdapter); return image; } public void reloadFile() { displayChart(ChartRepository.getInstance().getChart(0)); scrollControl.reset(); } public ActionListener getTabSelectedListener() { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (command != null) command.requestFocus(); } }; } /** * The job of this comparator is to place Spark charts before Injection charts */ static class ImageOrderComparator implements Comparator { @Override public int compare(String o1, String o2) { return fixNameForNicerOrder(o1).compareTo(fixNameForNicerOrder(o2)); } String fixNameForNicerOrder(String s) { if (s.toLowerCase().startsWith("t")) return "a" + s; // let's place this at the top if (s.toLowerCase().startsWith("hip")) return "z" + s; // let's place this at the bottom if (s.toLowerCase().startsWith("spa")) return "d" + s; return s; } } }