320 lines
9.5 KiB
Markdown
320 lines
9.5 KiB
Markdown
# 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
|
|
|
|
```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
|
|
```
|
|
|
|
### 迴圈邏輯
|
|
|
|
```java
|
|
// 遍历 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)
|
|
|
|
```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)
|
|
|
|
```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)
|
|
|
|
```java
|
|
// 原始資料: 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 類別使用
|
|
|
|
```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();
|
|
```
|