package com.rusefi.trigger; import com.rusefi.enums.trigger_type_e; import com.rusefi.ui.LogoHelper; import com.rusefi.ui.engine.UpDownImage; import com.rusefi.core.ui.FrameHelper; import com.rusefi.ui.util.UiUtils; import com.rusefi.waves.EngineReport; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * This utility produces images of trigger signals supported by rusEFI *

* 06/23/15 * Andrey Belomutskiy, (c) 2013-2020 */ public class TriggerImage { private static final String OUTPUT_FOLDER = "triggers"; private static final String TOP_MESSAGE = LogoHelper.LINK_TEXT; private static final int WHEEL_BORDER = 20; private static final int WHEEL_DIAMETER = 500; private static final int SMALL_DIAMETER = 420; private static final int _180 = 180; public static final int MIN_TIME = 720; /** * number of extra frames */ public static int EXTRA_COUNT = 1; private static int sleepAtEnd; private static trigger_type_e onlyOneTrigger = null; /** * todo: https://github.com/rusefi/rusefi/issues/2077 * * @see TriggerWheelInfo#isCrankBased */ private static String getTriggerName(TriggerWheelInfo triggerName) { switch (findByOrdinal(triggerName.getId())) { case TT_FORD_ASPIRE: return "Ford Aspire"; case TT_VVT_BOSCH_QUICK_START: return "Bosch Quick Start"; case TT_MAZDA_MIATA_NA: return "Miata NA"; case TT_SUBARU_SVX: return "Subaru SVX"; case TT_HONDA_K_CRANK_12_1: return "Honda K 1/12"; case TT_SUBARU_7_6: return "Subaru 7/6"; case TT_GM_24x_3: return "GM 24x 3"; case TT_GM_24x_5: return "GM 24x 5"; case TT_DODGE_NEON_1995: return "Dodge Neon 1995"; case TT_DODGE_NEON_1995_ONLY_CRANK: return "Dodge Neon 1995 crank only"; case TT_SKODA_FAVORIT: return "Skoda Favorit"; case TT_GM_7X: return "GM 7x"; case TT_CHRYSLER_NGC_36_2_2: return "Chrysler NGC 36/2/2"; case TT_HALF_MOON: return "Half Moon"; case TT_NARROW_SINGLE_TOOTH: return "Narrow Single Tooth"; case TT_JEEP_4_CYL: return "Jeep 4 cylinder"; case TT_JEEP_18_2_2_2: return "18/2/2/2"; case TT_RENIX_44_2_2: return "44/2/2"; case TT_RENIX_66_2_2_2: return "66/2/2/2"; case TT_TOOTHED_WHEEL_36_1: return "36/1"; case TT_TOOTHED_WHEEL_36_2: return "36/2"; case TT_TRI_TACH: return "TriTach"; case TT_TOOTHED_WHEEL_60_2: return "60/2"; case TT_GM_60_2_2_2: return "GM 60/2/2/2"; } return triggerName.getTriggerName(); } public static void main(String[] args) throws InvocationTargetException, InterruptedException { final String workingFolder; if (args.length < 1) { workingFolder = TriggerWheelInfo.DEFAULT_WORK_FOLDER; } else { workingFolder = args[0]; } if (args.length > 1) onlyOneTrigger = findByOrdinal(Integer.parseInt(args[1])); if (args.length > 2) sleepAtEnd = Integer.parseInt(args[2]); FrameHelper f = new FrameHelper(); JPanel content = new JPanel(new BorderLayout()); final TriggerPanel triggerPanel = new TriggerPanel() { @Override public Dimension getPreferredSize() { return new Dimension((1 + EXTRA_COUNT) * WIDTH, 480); } }; JPanel topPanel = new JPanel(new FlowLayout()); SwingUtilities.invokeAndWait(() -> { content.add(topPanel, BorderLayout.NORTH); content.add(triggerPanel, BorderLayout.CENTER); f.showFrame(content); f.getFrame().setSize(900, 700); UiUtils.trueRepaint(content); }); SwingUtilities.invokeAndWait(() -> { TriggerWheelInfo.readWheels(workingFolder, wheelInfo -> onWheel(triggerPanel, topPanel, content, wheelInfo)); }); Thread.sleep(1000L * sleepAtEnd); System.exit(-1); } private static void onWheel(TriggerPanel triggerPanel, JPanel topPanel, JPanel content, TriggerWheelInfo triggerWheelInfo) { if (onlyOneTrigger != null && findByOrdinal(triggerWheelInfo.getId()) != onlyOneTrigger) return; topPanel.removeAll(); JPanel firstWheelControl = createWheelPanel(triggerWheelInfo.getFirstWheeTriggerSignals(), true, triggerWheelInfo); topPanel.add(firstWheelControl); topPanel.add(LogoHelper.createLogoLabel()); List waves = TriggerImage.convertSignalsToWaves(triggerWheelInfo.getSignals()); if (!waves.get(1).list.isEmpty()) { JPanel secondWheelControl = createWheelPanel(triggerWheelInfo.getSecondWheeTriggerSignals(), false, triggerWheelInfo); topPanel.add(secondWheelControl); } UiUtils.trueLayout(topPanel); UiUtils.trueLayout(content); triggerPanel.tdcPosition = triggerWheelInfo.getTdcPosition(); triggerPanel.gaps = triggerWheelInfo.getGaps(); triggerPanel.syncEdge = triggerWheelInfo.getSyncEdge(); triggerPanel.isKnown = triggerWheelInfo.isKnownOperationMode(); triggerPanel.isCrankBased = triggerWheelInfo.isCrankBased(); EngineReport re0 = new EngineReport(waves.get(0).list, MIN_TIME, 720 * (1 + EXTRA_COUNT)); System.out.println(re0); EngineReport re1 = new EngineReport(waves.get(1).list, MIN_TIME, 720 * (1 + EXTRA_COUNT)); triggerPanel.removeAll(); UpDownImage upDownImage0 = new UpDownImage(re0, "trigger"); upDownImage0.setRenderText(false); triggerPanel.image = upDownImage0; UpDownImage upDownImage1 = new UpDownImage(re1, "trigger"); upDownImage1.setRenderText(false); boolean isSingleSensor = re1.getList().isEmpty(); int height; if (isSingleSensor) { height = 1; } else { height = 2; } triggerPanel.setLayout(new GridLayout(height, 1)); // always render the first channel triggerPanel.add(upDownImage0); if (!isSingleSensor) triggerPanel.add(upDownImage1); triggerPanel.name = getTriggerName(triggerWheelInfo); // triggerPanel.id = "#" + triggerWheelInfo.id; UiUtils.trueLayout(triggerPanel); UiUtils.trueRepaint(triggerPanel); content.paintImmediately(content.getVisibleRect()); new File(OUTPUT_FOLDER).mkdir(); UiUtils.saveImage(OUTPUT_FOLDER + File.separator + "trigger_" + findByOrdinal(triggerWheelInfo.getId()) + ".png", content); } @NotNull private static JPanel createWheelPanel(List wheel, boolean showTdc, TriggerWheelInfo shape) { return new JPanel() { @Override public void paint(Graphics g) { super.paint(g); int middle = WHEEL_BORDER + WHEEL_DIAMETER / 2; if (showTdc) { double tdcAngle = Math.toRadians(_180 + shape.getTdcPositionIn360()); int smallX = (int) (WHEEL_DIAMETER / 2 * Math.sin(tdcAngle)); int smallY = (int) (WHEEL_DIAMETER / 2 * Math.cos(tdcAngle)); int tdcMarkRadius = 8; g.setColor(UpDownImage.ENGINE_CYCLE_COLOR); // draw TDC mark and text on the round wheel g.fillOval(middle + smallX - tdcMarkRadius, middle + smallY - tdcMarkRadius, 2 * tdcMarkRadius, 2 * tdcMarkRadius); g.drawString("TDC", middle + smallX + tdcMarkRadius * 2, middle + smallY); } g.setColor(Color.black); for (int i = 0; i < wheel.size(); i++) { TriggerSignal current = wheel.get(i); drawRadialLine(g, current.getAngle()); /** * java arc API is * * Angles are interpreted such that 0 degrees * * is at the 3'clock position. * * A positive value indicates a counter-clockwise rotation * * while a negative value indicates a clockwise rotation. * * we want zero to be at 12'clock position and clockwise rotation */ double nextAngle = i == wheel.size() - 1 ? 360 + wheel.get(0).getAngle() : wheel.get(i + 1).getAngle(); int arcDuration = (int) (current.getAngle() - nextAngle); int arcStart = (int) arcToRusEFI(nextAngle); if (current.getState() == 1) { g.drawArc(WHEEL_BORDER, WHEEL_BORDER, WHEEL_DIAMETER, WHEEL_DIAMETER, arcStart, arcDuration); } else { int corner = WHEEL_BORDER + (WHEEL_DIAMETER - SMALL_DIAMETER) / 2; g.drawArc(corner, corner, SMALL_DIAMETER, SMALL_DIAMETER, arcStart, arcDuration); } } int dirArrow = 40; g.drawArc(middle - dirArrow, middle - dirArrow, 2 * dirArrow, 2 * dirArrow, 0, 180); g.drawLine(middle + dirArrow + 5, middle - 15, middle + dirArrow, middle); } @Override public Dimension getPreferredSize() { return new Dimension(WHEEL_DIAMETER + 2 * WHEEL_BORDER, WHEEL_DIAMETER + 2 * WHEEL_BORDER); } }; } private static double arcToRusEFI(double angle) { return angle + _180 - 90; } private static void drawRadialLine(Graphics g, double angle) { int center = WHEEL_BORDER + WHEEL_DIAMETER / 2; double radianAngle = Math.toRadians(_180 + angle); int smallX = (int) (SMALL_DIAMETER / 2 * Math.sin(radianAngle)); int smallY = (int) (SMALL_DIAMETER / 2 * Math.cos(radianAngle)); int largeX = (int) (WHEEL_DIAMETER / 2 * Math.sin(radianAngle)); int largeY = (int) (WHEEL_DIAMETER / 2 * Math.cos(radianAngle)); g.drawLine(center + smallX, center + smallY, center + largeX, center + largeY); } @NotNull static List convertSignalsToWaves(List signals) { /** * todo: what does this code do? does this work? * looks to be repeating trigger share couple of times? but not visible on images somehow? */ List toShow = new ArrayList<>(signals); for (int i = 1; i <= 2 + EXTRA_COUNT; i++) { for (TriggerSignal s : signals) toShow.add(new TriggerSignal(s.getWaveIndex(), s.getState(), s.getAngle() + i * 720, s.getGap())); } List waves = new ArrayList<>(); waves.add(new WaveState()); waves.add(new WaveState()); waves.add(new WaveState()); for (TriggerSignal s : toShow) { WaveState.trigger_value_e signal = (s.getState() == 0) ? WaveState.trigger_value_e.TV_LOW : WaveState.trigger_value_e.TV_HIGH; WaveState waveState = waves.get(s.getWaveIndex()); waveState.handle(signal, s.getAngle(), s.getGap()); } for (WaveState wave : waves) wave.wrap(); return waves; } public static trigger_type_e findByOrdinal(int id) { // todo: do we care about quicker implementation? probably not for (trigger_type_e type : trigger_type_e.values()) { if (type.ordinal() == id) return type; } throw new IllegalArgumentException("No type for " + id); } private static class TriggerPanel extends JPanel { public String name = ""; public String id; // angle public double tdcPosition; public String syncEdge; public boolean isKnown; public boolean isCrankBased; public UpDownImage image; public TriggerWheelInfo.TriggerGaps gaps; @Override public void paint(Graphics g) { super.paint(g); g.setColor(Color.black); int w = getWidth(); int off = g.getFontMetrics().stringWidth(TOP_MESSAGE); g.drawString(TOP_MESSAGE, w - off, g.getFont().getSize()); String line = new Date().toString(); off = g.getFontMetrics().stringWidth(line); g.drawString(line, w - off, 2 * g.getFont().getSize()); Font f = g.getFont(); g.setFont(new Font(f.getName(), Font.BOLD, f.getSize() * 3)); int h = getHeight(); g.drawString(name, 50, (int) (h * 0.75)); if (id != null) g.drawString(id, 0, (int) (h * 0.9)); g.setColor(UpDownImage.ENGINE_CYCLE_COLOR); int tdcFontSize = (int) (f.getSize() * 1.5); g.setFont(new Font(f.getName(), Font.BOLD, tdcFontSize)); String tdcMessage; if (tdcPosition != 0) { tdcMessage = "TDC " + formatTdcPosition() + " degree from synchronization point"; } else { tdcMessage = "TDC at synchronization point"; } int y = tdcFontSize; String prefix = " "; g.drawString(prefix + tdcMessage, 0, y); y += tdcFontSize; g.setColor(Color.darkGray); if (image == null) return; for (int i = 0; i < gaps.gapFrom.length; i++) { String message = "Sync " + (i + 1) + ": From " + gaps.gapFrom[i] + " to " + gaps.gapTo[i]; g.drawString(prefix + " " + message, 0, y); y += tdcFontSize; } if (isKnown) { g.drawString(prefix + (isCrankBased ? "On crankshaft" : "On camshaft"), 0, y); y += tdcFontSize; } if (syncEdge != null) { g.drawString(prefix + syncEdge, 0, y); y += tdcFontSize; } int tdcX = image.engineReport.getTimeAxisTranslator().timeToScreen(MIN_TIME + tdcPosition, w); g.drawLine(tdcX, 0, tdcX, h); Graphics2D g2 = (Graphics2D) g; g2.rotate(Math.PI / 2); g2.drawString("TDC", 160, -tdcX - 3); g2.rotate(-Math.PI / 2); } private String formatTdcPosition() { if ((int) tdcPosition == tdcPosition) return Integer.toString((int) tdcPosition); return Double.toString(tdcPosition); } } }