refactor out UpdateOperationCallbacks

This commit is contained in:
Matthew Kennedy 2023-10-24 15:57:38 -07:00
parent f237988c73
commit bf5c1a001a
7 changed files with 179 additions and 114 deletions

View File

@ -18,18 +18,18 @@ public class DfuHelper {
private static final Logging log = getLogging(DfuHelper.class);
private static final String PREFIX = "rusefi_bundle";
public static void sendDfuRebootCommand(IoStream stream, StatusConsumer messages, String cmd) {
public static void sendDfuRebootCommand(IoStream stream, UpdateOperationCallbacks callbacks, String cmd) {
byte[] command = BinaryProtocol.getTextCommandBytes(cmd);
try {
stream.sendPacket(command);
stream.close();
messages.append(String.format("Reboot command [%s] sent into %s!\n", cmd, stream));
callbacks.log(String.format("Reboot command [%s] sent into %s!\n", cmd, stream));
} catch (IOException e) {
messages.append("Error " + e);
callbacks.log("Error " + e);
}
}
public static boolean sendDfuRebootCommand(JComponent parent, String signature, IoStream stream, StatusConsumer messages, String command) {
public static boolean sendDfuRebootCommand(JComponent parent, String signature, IoStream stream, UpdateOperationCallbacks callbacks, String command) {
RusEfiSignature controllerSignature = SignatureHelper.parse(signature);
String fileSystemBundleTarget = BundleUtil.getBundleTarget();
if (fileSystemBundleTarget != null && controllerSignature != null) {
@ -55,7 +55,7 @@ public class DfuHelper {
}
}
sendDfuRebootCommand(stream, messages, command);
sendDfuRebootCommand(stream, callbacks, command);
return true;
}
}

View File

@ -0,0 +1,23 @@
package com.rusefi.io;
public interface UpdateOperationCallbacks {
void log(String message);
void done();
void error();
class UpdateOperationDummy implements UpdateOperationCallbacks {
@Override
public void log(String message) {
}
@Override
public void done() {
}
@Override
public void error() {
}
}
public static UpdateOperationCallbacks DUMMY = new UpdateOperationDummy();
}

View File

@ -3,14 +3,12 @@ package com.rusefi;
import com.rusefi.io.LinkManager;
import com.rusefi.io.tcp.TcpConnector;
import com.rusefi.maintenance.DfuFlasher;
import com.rusefi.ui.StatusConsumer;
import com.rusefi.io.UpdateOperationCallbacks;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.rusefi.FileLog.isLinux;
/**
* @author Andrey Belomutskiy
*/
@ -48,7 +46,7 @@ public enum SerialPortScanner {
if (includeSlowLookup) {
ports.addAll(TcpConnector.getAvailablePorts());
dfuConnected = DfuFlasher.detectSTM32BootloaderDriverState(StatusConsumer.VOID);
dfuConnected = DfuFlasher.detectSTM32BootloaderDriverState(UpdateOperationCallbacks.DUMMY);
} else {
dfuConnected = false;
}

View File

@ -9,9 +9,8 @@ import com.rusefi.core.io.BundleUtil;
import com.rusefi.config.generated.Fields;
import com.rusefi.io.DfuHelper;
import com.rusefi.io.IoStream;
import com.rusefi.io.UpdateOperationCallbacks;
import com.rusefi.io.serial.BufferedSerialIoStream;
import com.rusefi.ui.StatusConsumer;
import com.rusefi.ui.StatusWindow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -37,14 +36,11 @@ public class DfuFlasher {
private static final String WMIC_DFU_QUERY_COMMAND = "wmic path win32_pnpentity where \"Caption like '%STM32%' and Caption like '%Bootloader%'\" get Caption,ConfigManagerErrorCode /format:list";
private static final String WMIC_STLINK_QUERY_COMMAND = "wmic path win32_pnpentity where \"Caption like '%STLink%'\" get Caption,ConfigManagerErrorCode /format:list";
public static void doAutoDfu(Object selectedItem, JComponent parent) {
if (selectedItem == null) {
public static void doAutoDfu(JComponent parent, String port, UpdateOperationCallbacks callbacks) {
if (port == null) {
JOptionPane.showMessageDialog(parent, "Failed to locate serial ports");
return;
}
String port = selectedItem.toString();
StatusWindow wnd = createStatusWindow();
boolean needsEraseFirst = false;
if (BundleUtil.getBundleTarget().contains("f7")) {
@ -58,23 +54,23 @@ public class DfuFlasher {
needsEraseFirst = true;
}
AtomicBoolean isSignatureValidated = rebootToDfu(parent, port, wnd, Fields.CMD_REBOOT_DFU);
AtomicBoolean isSignatureValidated = rebootToDfu(parent, port, callbacks, Fields.CMD_REBOOT_DFU);
if (isSignatureValidated == null)
return;
if (isSignatureValidated.get()) {
if (!ProgramSelector.IS_WIN) {
wnd.append("Switched to DFU mode!");
wnd.append("FOME console can only program on Windows");
callbacks.log("Switched to DFU mode!");
callbacks.log("FOME console can only program on Windows");
return;
}
boolean finalNeedsEraseFirst = needsEraseFirst;
submitAction(() -> {
timeForDfuSwitch(wnd);
executeDFU(wnd, finalNeedsEraseFirst);
timeForDfuSwitch(callbacks);
executeDFU(callbacks, finalNeedsEraseFirst);
});
} else {
wnd.append("Please use manual DFU to change bundle type.");
callbacks.log("Please use manual DFU to change bundle type.");
}
}
@ -83,10 +79,10 @@ public class DfuFlasher {
}
@Nullable
public static AtomicBoolean rebootToDfu(JComponent parent, String port, StatusWindow wnd, String command) {
public static AtomicBoolean rebootToDfu(JComponent parent, String port, UpdateOperationCallbacks callbacks, String command) {
AtomicBoolean isSignatureValidated = new AtomicBoolean(true);
if (!PortDetector.isAutoPort(port)) {
wnd.append("Using selected " + port + "\n");
callbacks.log("Using selected " + port + "\n");
IoStream stream = BufferedSerialIoStream.openPort(port);
AtomicReference<String> signature = new AtomicReference<>();
new SerialAutoChecker(PortDetector.DetectorMode.DETECT_TS, port, new CountDownLatch(1)).checkResponse(stream, new Function<SerialAutoChecker.CallbackContext, Void>() {
@ -97,71 +93,62 @@ public class DfuFlasher {
}
});
if (signature.get() == null) {
wnd.append("*** ERROR *** FOME has not responded on selected " + port + "\n" +
callbacks.log("*** ERROR *** FOME has not responded on selected " + port + "\n" +
"Maybe try automatic serial port detection?");
wnd.setErrorState();
callbacks.error();
return null;
}
boolean isSignatureValidatedLocal = DfuHelper.sendDfuRebootCommand(parent, signature.get(), stream, wnd, command);
boolean isSignatureValidatedLocal = DfuHelper.sendDfuRebootCommand(parent, signature.get(), stream, callbacks, command);
isSignatureValidated.set(isSignatureValidatedLocal);
} else {
wnd.append("Auto-detecting port...\n");
callbacks.log("Auto-detecting port...\n");
// instead of opening the just-detected port we execute the command using the same stream we used to discover port
// it's more reliable this way
// ISSUE: that's blocking stuff on UI thread at the moment, TODO smarter threading!
port = PortDetector.autoDetectSerial(callbackContext -> {
boolean isSignatureValidatedLocal = DfuHelper.sendDfuRebootCommand(parent, callbackContext.getSignature(), callbackContext.getStream(), wnd, command);
boolean isSignatureValidatedLocal = DfuHelper.sendDfuRebootCommand(parent, callbackContext.getSignature(), callbackContext.getStream(), callbacks, command);
isSignatureValidated.set(isSignatureValidatedLocal);
return null;
}).getSerialPort();
if (port == null) {
wnd.append("*** ERROR *** FOME serial port not detected");
wnd.setErrorState();
callbacks.log("*** ERROR *** FOME serial port not detected");
callbacks.error();
return null;
} else {
wnd.append("Detected FOME on " + port + "\n");
callbacks.log("Detected FOME on " + port + "\n");
}
}
return isSignatureValidated;
}
@NotNull
protected static StatusWindow createStatusWindow() {
StatusWindow wnd = new StatusWindow();
wnd.showFrame(appendBundleName("DFU status " + Launcher.CONSOLE_VERSION));
return wnd;
}
public static void runDfuErase() {
StatusWindow wnd = createStatusWindow();
public static void runDfuEraseAsync(UpdateOperationCallbacks callbacks) {
submitAction(() -> {
runDfuErase(wnd);
runDfuErase(callbacks);
// it's a lengthy operation let's signal end
Toolkit.getDefaultToolkit().beep();
});
}
private static void runDfuErase(StatusWindow wnd) {
private static void runDfuErase(UpdateOperationCallbacks callbacks) {
ExecHelper.executeCommand(DFU_BINARY_LOCATION,
getDfuEraseCommand(),
DFU_BINARY, wnd, new StringBuffer());
DFU_BINARY, callbacks, new StringBuffer());
}
public static void runDfuProgramming() {
StatusWindow wnd = createStatusWindow();
submitAction(() -> executeDFU(wnd, false));
public static void runDfuProgramming(UpdateOperationCallbacks callbacks) {
submitAction(() -> executeDFU(callbacks, false));
}
private static void executeDFU(StatusWindow wnd, boolean fullErase) {
boolean driverIsHappy = detectSTM32BootloaderDriverState(wnd);
private static void executeDFU(UpdateOperationCallbacks callbacks, boolean fullErase) {
boolean driverIsHappy = detectSTM32BootloaderDriverState(callbacks);
if (!driverIsHappy) {
wnd.append("*** DRIVER ERROR? *** Did you have a chance to try 'Install Drivers' button on top of FOME console start screen?");
wnd.setErrorState();
callbacks.log("*** DRIVER ERROR? *** Did you have a chance to try 'Install Drivers' button on top of FOME console start screen?");
callbacks.error();
return;
}
if (fullErase) {
runDfuErase(wnd);
runDfuErase(callbacks);
}
StringBuffer stdout = new StringBuffer();
@ -169,65 +156,65 @@ public class DfuFlasher {
try {
errorResponse = ExecHelper.executeCommand(DFU_BINARY_LOCATION,
getDfuWriteCommand(),
DFU_BINARY, wnd, stdout);
DFU_BINARY, callbacks, stdout);
} catch (FileNotFoundException e) {
wnd.append("ERROR: " + e);
wnd.setErrorState();
callbacks.log("ERROR: " + e);
callbacks.error();
return;
}
if (stdout.toString().contains("Download verified successfully")) {
// looks like sometimes we are not catching the last line of the response? 'Upgrade' happens before 'Verify'
wnd.append("SUCCESS!");
wnd.append("Please power cycle device to exit DFU mode");
wnd.setSuccessState();
callbacks.log("SUCCESS!");
callbacks.log("Please power cycle device to exit DFU mode");
callbacks.done();
} else if (stdout.toString().contains("Target device not found")) {
wnd.append("ERROR: Device not connected or STM32 Bootloader driver not installed?");
appendWindowsVersion(wnd);
wnd.append("ERROR: Please try installing drivers using 'Install Drivers' button on FOME splash screen");
wnd.append("ERROR: Alternatively please install drivers using Device Manager pointing at 'drivers/silent_st_drivers/DFU_Driver' folder");
appendDeviceReport(wnd);
wnd.setErrorState();
callbacks.log("ERROR: Device not connected or STM32 Bootloader driver not installed?");
appendWindowsVersion(callbacks);
callbacks.log("ERROR: Please try installing drivers using 'Install Drivers' button on FOME splash screen");
callbacks.log("ERROR: Alternatively please install drivers using Device Manager pointing at 'drivers/silent_st_drivers/DFU_Driver' folder");
callbacks.error();
appendDeviceReport(callbacks);
} else {
wnd.append(stdout.length() + " / " + errorResponse.length());
appendWindowsVersion(wnd);
appendDeviceReport(wnd);
wnd.setErrorState();
appendWindowsVersion(callbacks);
appendDeviceReport(callbacks);
callbacks.log(stdout.length() + " / " + errorResponse.length());
callbacks.error();
}
}
public static boolean detectSTM32BootloaderDriverState(StatusConsumer wnd) {
return detectDevice(wnd, WMIC_DFU_QUERY_COMMAND, "ConfigManagerErrorCode=0");
public static boolean detectSTM32BootloaderDriverState(UpdateOperationCallbacks callbacks) {
return detectDevice(callbacks, WMIC_DFU_QUERY_COMMAND, "ConfigManagerErrorCode=0");
}
private static boolean detectDevice(StatusConsumer wnd, String queryCommand, String pattern) {
private static boolean detectDevice(UpdateOperationCallbacks callbacks, String queryCommand, String pattern) {
// long now = System.currentTimeMillis();
StringBuffer output = new StringBuffer();
StringBuffer error = new StringBuffer();
ExecHelper.executeCommand(queryCommand, wnd, output, error, null);
wnd.append(output.toString());
wnd.append(error.toString());
ExecHelper.executeCommand(queryCommand, callbacks, output, error, null);
callbacks.log(output.toString());
callbacks.log(error.toString());
// long cost = System.currentTimeMillis() - now;
// System.out.println("DFU lookup cost " + cost + "ms");
return output.toString().contains(pattern);
}
private static void appendWindowsVersion(StatusWindow wnd) {
wnd.append("ERROR: does not look like DFU has worked!");
private static void appendWindowsVersion(UpdateOperationCallbacks callbacks) {
callbacks.log("ERROR: does not look like DFU has worked!");
}
private static void appendDeviceReport(StatusWindow wnd) {
private static void appendDeviceReport(UpdateOperationCallbacks callbacks) {
for (String line : getDevicesReport()) {
if (line.contains("STM Device in DFU Mode")) {
wnd.append(" ******************************************************************");
wnd.append(" ************* YOU NEED TO REMOVE LEGACY DFU DRIVER ***************");
wnd.append(" ******************************************************************");
callbacks.log(" ******************************************************************");
callbacks.log(" ************* YOU NEED TO REMOVE LEGACY DFU DRIVER ***************");
callbacks.log(" ******************************************************************");
}
wnd.append("Devices: " + line);
callbacks.log("Devices: " + line);
}
}
private static void timeForDfuSwitch(StatusWindow wnd) {
wnd.append("Giving time for USB enumeration...");
private static void timeForDfuSwitch(UpdateOperationCallbacks callbacks) {
callbacks.log("Giving time for USB enumeration...");
try {
// two seconds not enough on my Windows 10
Thread.sleep(3 * Timeouts.SECOND);

View File

@ -1,7 +1,7 @@
package com.rusefi.maintenance;
import com.devexperts.util.TimeUtil;
import com.rusefi.ui.StatusConsumer;
import com.rusefi.io.UpdateOperationCallbacks;
import org.jetbrains.annotations.NotNull;
import java.io.*;
@ -25,7 +25,7 @@ public class ExecHelper {
* This method listens to a data stream from the process, appends messages to UI
* and accumulates output in a buffer
*/
private static void startStreamThread(final Process p, final InputStream stream, final StringBuffer buffer, final StatusConsumer wnd) {
private static void startStreamThread(final Process p, final InputStream stream, final StringBuffer buffer, final UpdateOperationCallbacks callbacks) {
final Thread t = new Thread(() -> {
try {
BufferedReader bis = new BufferedReader(new InputStreamReader(stream, StandardCharsets.ISO_8859_1));
@ -38,12 +38,13 @@ public class ExecHelper {
String line = bis.readLine();
if (line == null)
break;
wnd.append(line);
callbacks.log(line);
buffer.append(line);
wasRunningTime = System.currentTimeMillis();
}
} catch (IOException e) {
wnd.append("Stream " + e);
callbacks.log("Stream " + e);
callbacks.error();
}
});
t.setDaemon(true);
@ -51,8 +52,8 @@ public class ExecHelper {
}
@NotNull
public static String executeCommand(String workingDirPath, String command, String binaryRelativeName, StatusConsumer wnd) {
return executeCommand(workingDirPath, command, binaryRelativeName, wnd, new StringBuffer());
public static String executeCommand(String workingDirPath, String command, String binaryRelativeName, UpdateOperationCallbacks callbacks) {
return executeCommand(workingDirPath, command, binaryRelativeName, callbacks, new StringBuffer());
}
/**
@ -60,32 +61,35 @@ public class ExecHelper {
* @return stderr of invoked command
*/
@NotNull
public static String executeCommand(String workingDirPath, String command, String binaryRelativeName, StatusConsumer wnd, StringBuffer output) {
public static String executeCommand(String workingDirPath, String command, String binaryRelativeName, UpdateOperationCallbacks callbacks, StringBuffer output) {
StringBuffer error = new StringBuffer();
String binaryFullName = workingDirPath + File.separator + binaryRelativeName;
if (!new File(binaryFullName).exists()) {
wnd.append(binaryFullName + " not found :(");
callbacks.log(binaryFullName + " not found :(");
return error.toString();
}
File workingDir = new File(workingDirPath);
return executeCommand(command, wnd, output, error, workingDir);
return executeCommand(command, callbacks, output, error, workingDir);
}
@NotNull
public static String executeCommand(String command, StatusConsumer wnd, StringBuffer output, StringBuffer error, File workingDir) {
wnd.append("Executing " + command);
public static String executeCommand(String command, UpdateOperationCallbacks callbacks, StringBuffer output, StringBuffer error, File workingDir) {
callbacks.log("Executing " + command);
try {
Process p = Runtime.getRuntime().exec(command, null, workingDir);
startStreamThread(p, p.getInputStream(), output, wnd);
startStreamThread(p, p.getErrorStream(), error, wnd);
startStreamThread(p, p.getInputStream(), output, callbacks);
startStreamThread(p, p.getErrorStream(), error, callbacks);
p.waitFor(3, TimeUnit.MINUTES);
} catch (IOException e) {
wnd.append("IOError: " + e);
callbacks.log("IOError: " + e);
callbacks.error();
} catch (InterruptedException e) {
wnd.append("WaitError: " + e);
callbacks.log("WaitError: " + e);
callbacks.error();
}
wnd.append("Done!");
callbacks.done();
return error.toString();
}

View File

@ -4,6 +4,7 @@ import com.rusefi.Launcher;
import com.rusefi.SerialPortScanner;
import com.rusefi.autodetect.PortDetector;
import com.rusefi.config.generated.Fields;
import com.rusefi.io.UpdateOperationCallbacks;
import com.rusefi.ui.StatusWindow;
import com.rusefi.ui.util.URLLabel;
import com.rusefi.ui.util.UiUtils;
@ -26,6 +27,7 @@ public class ProgramSelector {
private static final String AUTO_DFU = "Auto Update";
private static final String MANUAL_DFU = "Manual DFU Update";
private static final String DFU_SWITCH = "Switch to DFU Mode";
private static final String OPENBLT_SWITCH = "Switch to OpenBLT Mode";
private static final String DFU_ERASE = "Full Chip Erase";
private static final String OPENBLT_CAN = "OpenBLT via CAN";
@ -48,7 +50,7 @@ public class ProgramSelector {
controls.add(mode);
String persistedMode = getConfig().getRoot().getProperty(getClass().getSimpleName());
if (Arrays.asList(AUTO_DFU, MANUAL_DFU, OPENBLT_CAN , DFU_ERASE, DFU_SWITCH).contains(persistedMode))
if (Arrays.asList(AUTO_DFU, MANUAL_DFU, OPENBLT_CAN, OPENBLT_SWITCH, DFU_ERASE, DFU_SWITCH).contains(persistedMode))
mode.setSelectedItem(persistedMode);
JButton updateFirmware = new JButton("Update Firmware",
@ -59,45 +61,59 @@ public class ProgramSelector {
@Override
public void actionPerformed(ActionEvent e) {
String selectedMode = (String) mode.getSelectedItem();
String selectedPort = (String) comboPorts.getSelectedItem();
getConfig().getRoot().setProperty(getClass().getSimpleName(), selectedMode);
Objects.requireNonNull(selectedMode);
switch (selectedMode) {
case AUTO_DFU:
DfuFlasher.doAutoDfu(comboPorts.getSelectedItem(), comboPorts);
DfuFlasher.doAutoDfu(comboPorts, selectedPort, createStatusWindow("DFU update"));
break;
case MANUAL_DFU:
DfuFlasher.runDfuProgramming();
DfuFlasher.runDfuProgramming(createStatusWindow("DFU update"));
break;
case DFU_SWITCH:
StatusWindow wnd = DfuFlasher.createStatusWindow();
Object selected = comboPorts.getSelectedItem();
String port = selected == null ? PortDetector.AUTO : selected.toString();
DfuFlasher.rebootToDfu(comboPorts, port, wnd, Fields.CMD_REBOOT_DFU);
rebootToDfu(comboPorts, selectedPort);
break;
case OPENBLT_SWITCH:
rebootToOpenblt(comboPorts, selectedPort);
break;
case OPENBLT_CAN:
flashOpenBltCan();
break;
case DFU_ERASE:
DfuFlasher.runDfuErase();
DfuFlasher.runDfuEraseAsync(createStatusWindow("DFU erase"));
break;
default:
throw new IllegalArgumentException("How did you " + selectedMode);
}
}
});
}
@NotNull
protected static UpdateOperationCallbacks createStatusWindow(String message) {
return new UpdateStatusWindow(appendBundleName(message + " " + Launcher.CONSOLE_VERSION));
}
private static void rebootToDfu(JComponent parent, String selectedPort) {
String port = selectedPort == null ? PortDetector.AUTO : selectedPort;
DfuFlasher.rebootToDfu(parent, port, createStatusWindow("DFU switch"), Fields.CMD_REBOOT_DFU);
}
private static void rebootToOpenblt(JComponent parent, String selectedPort) {
String port = selectedPort == null ? PortDetector.AUTO : selectedPort;
DfuFlasher.rebootToDfu(parent, port, createStatusWindow("OpenBLT switch"), Fields.CMD_REBOOT_OPENBLT);
}
private void flashOpenBltCan() {
StatusWindow wnd = new StatusWindow();
wnd.showFrame(appendBundleName("OpenBLT via CAN " + Launcher.CONSOLE_VERSION));
UpdateOperationCallbacks callbacks = createStatusWindow("OpenBLT via CAN");
ExecHelper.submitAction(() -> {
ExecHelper.executeCommand(OPENBLT_BINARY_LOCATION,
OPENBLT_BINARY_LOCATION + "/" + BOOT_COMMANDER_EXE +
" -s=xcp -t=xcp_can -d=peak_pcanusb -t1=1000 -t3=2000 -t4=10000 -t5=1000 -t7=2000 ../../rusefi_update.srec",
BOOT_COMMANDER_EXE, wnd, new StringBuffer());
BOOT_COMMANDER_EXE, callbacks, new StringBuffer());
// it's a lengthy operation let's signal end
Toolkit.getDefaultToolkit().beep();
}, "OpenBLT via CAN");
@ -116,18 +132,30 @@ public class ProgramSelector {
noHardware.setVisible(currentHardware.isEmpty());
controls.setVisible(!currentHardware.isEmpty());
boolean hasSerialPorts = !currentHardware.getKnownPorts().isEmpty();
boolean hasDfuDevice = currentHardware.isDfuFound();
mode.removeAllItems();
if (IS_WIN) {
if (!currentHardware.getKnownPorts().isEmpty())
if (hasSerialPorts) {
mode.addItem(AUTO_DFU);
if (currentHardware.isDfuFound()) {
}
if (hasDfuDevice) {
mode.addItem(MANUAL_DFU);
mode.addItem(DFU_ERASE);
}
}
if (!currentHardware.getKnownPorts().isEmpty())
// If any serial port is detected, offer the option to switch to DFU
if (hasSerialPorts) {
// mode.addItem(AUTO_OPENBLT);
mode.addItem(DFU_SWITCH);
mode.addItem(OPENBLT_SWITCH);
// mode.addItem(MANUAL_OPENBLT);
}
trueLayout(mode);
UiUtils.trueLayout(content);
trueLayout(content);
}
}

View File

@ -0,0 +1,25 @@
package com.rusefi.maintenance;
import com.rusefi.io.UpdateOperationCallbacks;
import com.rusefi.ui.StatusWindow;
public class UpdateStatusWindow extends StatusWindow implements UpdateOperationCallbacks {
public UpdateStatusWindow(String title) {
showFrame(title);
}
@Override
public void log(String message) {
append(message);
}
@Override
public void done() {
setSuccessState();
}
@Override
public void error() {
setErrorState();
}
}