9.5 KiB
9.5 KiB
Dump Controller Log 實現分析
流程架構
┌─────────────────────────────────────────────────────────────┐
│ frmMain.jmDumpEventLog_actionPerformed() │
│ (根據型號選擇使用 dlgDumpLog 或 dlgDumpLog2) │
└──────────────────────────┬──────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ dlgDumpLog │ │ dlgDumpLog2 │
│ (舊型號) │ │ (新型號) │
└──────────────┘ └──────────────┘
│ │
└───────────┬───────────────────┘
▼
┌──────────────────────────┐
│ dumpEventLog() │
│ TCP 連線 Port 8922 │
└───────────┬──────────────┘
▼
┌──────────────────────────┐
│ OpCode: 0x01 │
│ SubOp: 0x03 │
│ Page: 0x60 ~ 0x64 │
└───────────┬──────────────┘
▼
┌──────────────────────────┐
│ 寫入 ./Log 目錄 │
│ 壓縮為 ZIP 檔 │
└──────────────────────────┘
通訊協定
Request
Head: [0x01, 0x17, 0x00, 0x00, 0x00] (23 bytes)
Data: [0x02, 0x01, SN(16 bytes), CDB(5 bytes)]
CDB 結構
| Offset | 值 | 說明 |
|---|---|---|
| 0 | 0x01 | OpCode - GET_PAGE |
| 1 | 0x03 | SubOp |
| 2 | 0x60-0x64 | Page Code (Log Type) |
| 3 | LSB | Block Index |
| 4 | MSB | Block Index |
Log Types
| Page | 說明 |
|---|---|
| 0x60 | System Log |
| 0x61 | Event Log |
| 0x62 | I/O Error Log |
| 0x63 | Dump Log |
| 0x64 | Reserved |
實現細節
dlgDumpLog.java
// 1. 連線到控制器
theSocket.connect(new InetSocketAddress(PageDataDB.GuiSrvIpAddr, Port), 10000);
// 2. 發送请求 (OpCode 0x01, SubOp 0x03)
Cdb[0] = 0x01; // GET_PAGE
Cdb[1] = 0x03; // SubOp
Cdb[2] = 0x60; // Log type (0x60-0x64)
Cdb[3] = BlockIndexLSB;
Cdb[4] = BlockIndexMSB;
// 3. 解析回應
arg[0] = reply[9]; // Status (0x50 = Success)
arg[1] = reply[12]; // Page code
arg[2] = startBlock;
arg[3] = totalBlocks;
// 4. 寫入檔案 (./Log 目錄)
File file = getFile((byte)arg[1]);
fileOut.write(reply, 15, 512); // 寫入 512 bytes
// 5. 壓縮成 ZIP
compressLog(); // 打包成 Log_YYYYMMDD_HHMMSS.zip
迴圈邏輯
// 遍历 5 種 log 類型
for (byte i = 0x60; i <= 0x64; i++) {
// 取得該類型的 log 總量
arg1 = dumpEventLog(Cdb);
// 遍历每個 block
for (int j = arg1[2]; j < arg1[3]; j++) {
Cdb[3] = (byte)(j & 0xFF); // LSB
Cdb[4] = (byte)((j >> 8) & 0xFF); // MSB
arg2 = dumpEventLog(Cdb);
// 寫入單個 block
fileOut.write(reply, 15, 512);
}
}
輸出檔案
- 目錄:
./Log/ - 格式:
Log_YYYYMMDD_HHMMSS.zip - 內容: 5 個 log 類型的原始資料 (各 512 bytes/block)
關鍵類
| 類別 | 檔案 | 功能 |
|---|---|---|
frmMain |
frmMain.java:4136 | 入口,選擇 dialog 版本 |
dlgDumpLog |
dlgDumpLog.java | 舊型號使用 (單一控制器) |
dlgDumpLog2 |
dlgDumpLog2.java | 新型號使用 (支援多控制器) |
SendCmd |
InBandAPI/SendCmd.java | TCP 通訊底層 |
Code Flow
1. Menu 點擊
frmMain.jmDumpControllerLog (MenuItem)
→ frmMain.jmDumpEventLog_actionPerformed()
→ 檢查型號選擇 dlgDumpLog 或 dlgDumpLog2
2. Dump 執行
dlgDumpLog.run()
→ for (0x60 ~ 0x64) {
dumpEventLog() 取得總量
for (每個 block) {
dumpEventLog() 取得資料
寫入檔案
}
}
→ compressLog() 壓縮
→ dispose()
3. Socket 通訊
dumpEventLog(Cdb)
→ connect(Port 8922)
→ send(Head + SN + CDB)
→ read(reply)
→ parse status & data
→ close()
ZIP 壓縮處理細節
輸出檔案
dlgDumpLog.java (舊型號)
./Log/{SN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip
dlgDumpLog2.java (新型號)
Primary: ./Log/Primary_{SN}_ControllerLog.zip
Secondary: ./Log/Secondary_{SN}_ControllerLog.zip
合併: ./Log/{PrimarySN}_{SecondarySN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip
壓縮前的暫存檔
| Page Code | 檔案名稱 | 說明 |
|---|---|---|
| 0x60 | EventPage.bin | Event Log |
| 0x61 | DebugPage.bin | Debug Log |
| 0x62 | DiskMetadataPage.bin | Disk Metadata |
| 0x63 | SpecialLogPage.bin | Special Log |
| 0x64 | TelMesPages.log | Telemetry Messages |
compressLog() 流程 (dlgDumpLog.java)
public void compressLog() {
File[] logs = new File[5];
for(int i=0; i<=4; i++) {
logs[i] = getFile((byte)(i+0x60)); // 取得 5 個暫存檔
}
// 建立 ZIP 檔案
Calendar rightNow = Calendar.getInstance();
String filename = "./Log/" + PageDataDB.sSN.trim() + "_ControllerLog_"
+ rightNow.get(Calendar.YEAR) + "_"
+ (rightNow.get(Calendar.MONTH)+1) + "_"
+ rightNow.get(Calendar.DAY_OF_MONTH) + "_"
+ rightNow.get(Calendar.HOUR) + "_"
+ rightNow.get(Calendar.MINUTE) + "_"
+ rightNow.get(Calendar.SECOND) + ".zip";
FileOutputStream fout = new FileOutputStream(filename);
ZipOutputStream zout = new ZipOutputStream(fout);
zout.setLevel(5); // 壓縮等級 0-9
// 逐一壓縮每個檔案
for (byte i = 0; i <= 4; i++) {
if(logs[i].exists()) {
ZipEntry ze = new ZipEntry(logs[i].getName());
FileInputStream fin = new FileInputStream(logs[i]);
zout.putNextEntry(ze);
// 特殊處理 Page 0x64 (TelMesPages.log)
if(i != 0x4) {
// 一般檔案:直接複製
for(int c = fin.read(); c != -1; c = fin.read()) {
zout.write(c);
}
} else {
// TelMesPages.log: 每 40 bytes 為一筆記錄
// 移除空字元 (0x00),只保留有效資料
byte data;
boolean skipNull = false;
for(int j=0; j < logs[i].length()/40; j++) {
skipNull = false;
for(int k=0; k<40; k++) {
data = (byte)fin.read();
if(data == 0x0a || skipNull == true) {
skipNull = true;
} else {
zout.write(data);
}
}
zout.write(0x0a); // 加上換行符
}
}
zout.closeEntry();
fin.close();
logs[i].delete(); // 刪除暫存檔
}
}
zout.close();
}
compressLog() 流程 (dlgDumpLog2.java)
// 單一控制器壓縮
public void compressLog(String sSN) {
String prefix = sSN.equals(PageDataDB.sSN) ? "Primary" : "Secondary";
String filename = "./Log/" + prefix + "_" + sSN.trim() + "_ControllerLog.zip";
// ... 同上邏輯
}
// 雙控制器合併壓縮
public void compressLog2() {
File[] logs = new File[2];
logs[0] = new File(sMaterFileNamePatch); // Primary
logs[1] = new File(sSlaveFileNamePatch); // Secondary
// 合併成單一 ZIP,內含兩個 ZIP 檔
String filename = "./Log/" + PageDataDB.sSN.trim() + "_"
+ PageDataDB.sSlaveSN.trim() + "_ControllerLog_{timestamp}.zip";
// ...
}
ZIP 結構
Archive: {SN}_ControllerLog_2024_3_29_10_30_45.zip
├── EventPage.bin (512 bytes/block)
├── DebugPage.bin (512 bytes/block)
├── DiskMetadataPage.bin (512 bytes/block)
├── SpecialLogPage.bin (512 bytes/block)
└── TelMesPages.log (40 bytes/record, 去除 null)
特殊處理:TelMesPages.log (Page 0x64)
// 原始資料: 40 bytes/record,包含 null (0x00) 字元
// 處理邏輯:
// 1. 讀取每 40 bytes
// 2. 跳過開頭的 null 字元
// 3. 遇到換行符 (0x0a) 後,後續全部視為有效資料
// 4. 輸出時每筆記錄加上換行符
// Example:
// Input: [0x00, 0x00, 0x00, 0x31, 0x32, ..., 0x00, 0x00]
// Output: "12...data\n"
Java 類別使用
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.util.Calendar;
// 關鍵類別
ZipOutputStream zout = new ZipOutputStream(fout);
zout.setLevel(5); // 壓縮等級 (0=儲存, 9=最大壓縮)
ZipEntry ze = new ZipEntry(filename);
zout.putNextEntry(ze);
zout.write(byte[] data);
zout.closeEntry();
zout.close();