package home;
|
|
import java.io.*;
|
import java.util.Arrays;
|
import java.util.function.Consumer;
|
|
public class FirmwareUpgrader {
|
private static final int SOH = 0x01; // Start of Header
|
private static final int STX = 0x02; // Start of Text (for 1024-byte blocks)
|
private static final int EOT = 0x04; // End of Transmission
|
private static final int ACK = 0x06; // Acknowledge
|
private static final int NAK = 0x15; // Negative Acknowledge
|
private static final int CAN = 0x18; // Cancel
|
private static final int C = 0x43; // 'C' for CRC mode
|
|
private final SerialPortService serialService;
|
|
public FirmwareUpgrader(SerialPortService serialService) {
|
this.serialService = serialService;
|
}
|
|
public void upgradeFirmware(String filePath, ProgressCallback progressCallback) throws IOException {
|
File file = new File(filePath);
|
if (!file.exists() || !file.isFile()) {
|
throw new IOException("File not found: " + filePath);
|
}
|
|
// ·¢ËÍ¿ªÊ¼ÐźÅ
|
sendStartSignal();
|
|
// ·¢ËÍÎļþ
|
sendFile(file, progressCallback);
|
|
// ·¢ËͽáÊøÐźÅ
|
sendEndSignal();
|
}
|
|
private void sendStartSignal() throws IOException {
|
// ·¢ËÍ'C'±íʾʹÓÃCRCУÑé
|
serialService.send(new byte[]{C});
|
|
// µÈ´ýACK
|
if (!waitForAck()) {
|
throw new IOException("Device not ready for YModem transfer");
|
}
|
}
|
|
private void sendFile(File file, ProgressCallback progressCallback) throws IOException {
|
long fileSize = file.length();
|
String fileName = file.getName();
|
|
// ·¢ËÍÎļþÍ·¿é
|
byte[] header = new byte[133];
|
Arrays.fill(header, (byte)0);
|
header[0] = SOH;
|
header[1] = 0x00; // ¿é±àºÅ
|
header[2] = (byte)0xFF; // ¿é±àºÅ²¹Âë
|
|
// ÎļþÃûºÍ´óС
|
byte[] fileNameBytes = fileName.getBytes();
|
System.arraycopy(fileNameBytes, 0, header, 3, Math.min(fileNameBytes.length, 99));
|
String sizeStr = String.valueOf(fileSize);
|
byte[] sizeBytes = sizeStr.getBytes();
|
System.arraycopy(sizeBytes, 0, header, 3 + 99, Math.min(sizeBytes.length, 99));
|
|
// ¼ÆËãCRC
|
int crc = calculateCRC(header, 3, 128);
|
header[131] = (byte)((crc >> 8) & 0xFF);
|
header[132] = (byte)(crc & 0xFF);
|
|
serialService.send(header);
|
|
// µÈ´ýACK
|
if (!waitForAck()) {
|
throw new IOException("Header not acknowledged");
|
}
|
|
// ·¢ËÍÎļþÊý¾Ý
|
try (FileInputStream fis = new FileInputStream(file)) {
|
byte[] buffer = new byte[1024];
|
int bytesRead;
|
int blockNum = 1;
|
long totalSent = 0;
|
|
while ((bytesRead = fis.read(buffer)) != -1) {
|
// Ìî³ä²»×ãµÄ²¿·Ö
|
if (bytesRead < 1024) {
|
Arrays.fill(buffer, bytesRead, 1024, (byte)0x1A);
|
}
|
|
// ·¢ËÍÊý¾Ý¿é
|
sendDataBlock(blockNum, buffer);
|
blockNum++;
|
totalSent += bytesRead;
|
|
// ¸üнø¶È
|
int progress = (int)((totalSent * 100) / fileSize);
|
progressCallback.onProgress(progress);
|
|
// µÈ´ýACK
|
if (!waitForAck()) {
|
throw new IOException("Data block not acknowledged");
|
}
|
}
|
}
|
}
|
|
private void sendDataBlock(int blockNum, byte[] data) throws IOException {
|
byte[] block = new byte[1029];
|
block[0] = STX; // 1024×Ö½Ú¿é
|
block[1] = (byte)(blockNum & 0xFF);
|
block[2] = (byte)(~blockNum & 0xFF);
|
|
// ¿½±´Êý¾Ý
|
System.arraycopy(data, 0, block, 3, data.length);
|
|
// ¼ÆËãCRC
|
int crc = calculateCRC(block, 3, 1024);
|
block[1027] = (byte)((crc >> 8) & 0xFF);
|
block[1028] = (byte)(crc & 0xFF);
|
|
serialService.send(block);
|
}
|
|
private void sendEndSignal() throws IOException {
|
// ·¢ËÍEOT
|
serialService.send(new byte[]{EOT});
|
|
// µÈ´ýACK
|
if (!waitForAck()) {
|
throw new IOException("EOT not acknowledged");
|
}
|
|
// ·¢ËÍ¿ÕÎļþÍ·¿é±íʾ½áÊø
|
byte[] endHeader = new byte[133];
|
Arrays.fill(endHeader, (byte)0);
|
endHeader[0] = SOH;
|
endHeader[1] = 0x00; // ¿é±àºÅ
|
endHeader[2] = (byte)0xFF; // ¿é±àºÅ²¹Âë
|
|
// ¼ÆËãCRC
|
int crc = calculateCRC(endHeader, 3, 128);
|
endHeader[131] = (byte)((crc >> 8) & 0xFF);
|
endHeader[132] = (byte)(crc & 0xFF);
|
|
serialService.send(endHeader);
|
|
// µÈ´ýACK
|
if (!waitForAck()) {
|
throw new IOException("End header not acknowledged");
|
}
|
}
|
|
private boolean waitForAck() {
|
final Object lock = new Object();
|
final boolean[] result = new boolean[1];
|
final boolean[] receivedResponse = new boolean[1];
|
|
// ´´½¨Ò»¸öÁÙʱÏû·ÑÕßÀ´´¦Àí½ÓÊÕµ½µÄÊý¾Ý
|
Consumer<byte[]> ackConsumer = data -> {
|
if (data.length > 0) {
|
synchronized (lock) {
|
if (data[0] == ACK) {
|
result[0] = true;
|
receivedResponse[0] = true;
|
} else if (data[0] == NAK) {
|
result[0] = false;
|
receivedResponse[0] = true;
|
} else if (data[0] == CAN) {
|
throw new RuntimeException("Transfer canceled by device");
|
}
|
lock.notifyAll();
|
}
|
}
|
};
|
|
// ×¢²áÁÙʱÏû·ÑÕß
|
serialService.setResponseConsumer(ackConsumer);
|
|
long startTime = System.currentTimeMillis();
|
synchronized (lock) {
|
while (System.currentTimeMillis() - startTime < 5000 && !receivedResponse[0]) {
|
try {
|
lock.wait(100);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
return false;
|
}
|
}
|
}
|
|
// È¡Ïû×¢²áÁÙʱÏû·ÑÕß
|
serialService.setResponseConsumer(null);
|
|
return receivedResponse[0] && result[0];
|
}
|
|
private int calculateCRC(byte[] data, int offset, int length) {
|
int crc = 0;
|
for (int i = 0; i < length; i++) {
|
crc = crc ^ (data[offset + i] << 8);
|
for (int j = 0; j < 8; j++) {
|
if ((crc & 0x8000) != 0) {
|
crc = (crc << 1) ^ 0x1021;
|
} else {
|
crc = crc << 1;
|
}
|
}
|
}
|
return crc & 0xFFFF;
|
}
|
|
public interface ProgressCallback {
|
void onProgress(int progress);
|
}
|
}
|