2025年8月5日 星期二

PeakCAN 與 ECU Simulator 通訊利用OpenAI 來找尋DTC 的解釋

📺 影片版(約 6 分鐘):想先看實際操作的話可以直接看這支影片,完整步驟、設定與程式碼在下方文字版。
YouTube:https://youtu.be/ogyptBIosK4(winson-DIY 頻道)
在這一篇文章中PeakCAN Bus 與 ECU Simulator 通訊 擷取 DTC Data
得到DTC錯誤代碼但是有太多的錯誤代碼, 本來想說在本機建立一個database來搜尋, 靈機一動想說現在萬物都OpenAI, 於是就在得到DTC時把OpenAI API的功能寫進去來尋找 DTC的意義!
錯誤碼=>所有常見的 OBD2 錯誤代碼:解讀 P、B、C、U 程式碼
在Microsoft Visual Studio中利用NuGet 安裝OpenAI

using OpenAI;
using OpenAI.Chat;

private async void button3_Click(object sender, EventArgs e)
{
    //string dtcCode = txtDTC.Text.Trim();
    string dtcCode = "P0218".Trim();

    if (string.IsNullOrEmpty(dtcCode))
    {
        MessageBox.Show("請輸入 DTC 代碼,例如 P0202");
        return;
    }

    button3.Enabled = false;
    //txtResult.Text = "查詢中,請稍候...";
    CommonData.WriteMessage("查詢中,請稍候...", "", Color.Blue, Color.Red);

    string myApiKey = "your key";

    ChatClient client = new(
        model: "gpt-4.1",  //gpt-4
        apiKey: myApiKey
    );

    try
    {
        var prompt = $"請解釋 OBD2 故障碼 {dtcCode} 的意思,用繁體中文回答";
        
        ChatCompletion completion = await client.CompleteChatAsync(prompt);   
        CommonData.WriteMessage("查詢結果...", completion.Content[0].Text.ToString(), Color.Blue, Color.Red);
    }
    catch (Exception ex)
    {
        //txtResult.Text = "錯誤:" + ex.Message;
    }
    finally
    {
        button3.Enabled = true;
    }
}


還真的可以用!真是方便!!



補充(2026/10 更新):AI 解釋 DTC 會不會講錯?實測與改良做法
這篇是去年 8 月第一次把 OpenAI 接進 PeakCAN 程式時寫的,當時覺得「還真的可以用」。後來想把它改成不用付費、可以離線的版本,就在 GX10 本機用 Ollama 跑同一句提示詞,結果發現一個很重要的問題:AI 會很有自信地講錯故障碼的意思。這段補充整理實測結果,以及比較可靠的改良做法。

補充一、整個流程回顧
PeakCAN(PCAN-USB)──CAN 500 kbit/s──▶ ECU Simulator
  1. 送 Mode 03 讀取 DTC:ID 0x7DF,資料 01 03 00 00 00 00 00 00
  2. 收到 First Frame:ID 0x7E8,10 10 43 02 01 68 03 03
  3. 回 Flow Control:ID 0x7E0,30 00 00 00 00 00 00 00
  4. 解碼:01 68 → P0168、03 03 → P0303
  5. 把故障碼送給 AI,取得中文解釋,顯示在下方訊息區
第 1~4 步的細節在前一篇:PeakCAN 與 ECU Simulator 通訊 擷取 DTC Data。這篇補充的重點在第 5 步。

補充二、實測:本機模型會把故障碼講錯
用跟文章 C# 程式完全相同的提示詞「請解釋 OBD2 故障碼 {代碼} 的意思,用繁體中文回答」,在 GX10 上用 Ollama 問兩個本機模型,結果如下:
故障碼SAE 標準定義gemma4:12b 的回答qwen3.6:35b 的回答
P0168Fuel Temperature Too High(燃油溫度過高)❌ 燃燒過稀 Bank 1(這其實是 P0171)❌ 第三列氧感測器電路異常
P0303Cylinder 3 Misfire Detected(第 3 缸失火)✔ 正確✔ 正確
P0218Transmission Fluid Over Temperature Condition(變速箱油溫過高)❌ 冷卻液溫度感測器電路❌ 怠速時冷卻液溫度偏高

三個故障碼錯了兩個,而且錯的答案寫得非常完整,有原因、有症狀、有維修建議,看起來就像真的一樣。如果沒有對照標準定義,很容易就相信了。

文章截圖裡雲端的 gpt-4.1 這三個都答對了(P0218 變速箱溫度過高、P0168 燃油溫度過高、P0303 第 3 缸失火),大模型對常見的 P 碼確實比較準。但「這次答對」不代表每次都對,尤其是車廠自訂的 P1xxx、U 碼這類比較冷門的故障碼,大模型一樣可能亂講。

補充三、改良做法:先查表,AI 只負責解釋
一開始「本來想在本機建一個 database 來搜尋」的想法其實是對的,只是不用二選一,兩個一起用:
  1. 標準定義從本地 DTC 表查:SAE J2012 的通用碼(P0xxx、C0xxx、B0xxx、U0xxx)定義是固定的,存成一個 CSV 或字典就好
  2. 把查到的定義放進提示詞,請 AI「只根據這個定義」用中文說明代表什麼、常見原因
  3. 表裡查不到的碼不送 AI,直接顯示「未知代碼,請查原廠手冊」,不讓 AI 自己猜
同樣用 gemma4:12b,改成這種提示詞後再測一次:
提示詞:OBD2 故障碼 P0168 的 SAE 標準定義是「Fuel Temperature Too High」。
        請只根據這個定義,用繁體中文、三句話以內說明它代表什麼,
        以及最常見的兩個原因。不要更改定義。

P0168 → 引擎偵測到燃油溫度過高。最常見的兩個原因是燃油溫度傳感器故障或燃料相關系統過熱。      ✔
P0303 → 引擎偵測到第三缸發生失火……最常見的兩個原因是火星塞或點火線圈損壞。                    ✔
P0218 → 變速箱油溫過高……最常見的兩個原因是變速箱油量不足,以及內部磨損導致摩擦過熱。          ✔
P1234 → 本地表查不到,不送 AI                                                                  ✔
三個全部正確,連小模型也不會再講錯。另外,要求「三句話以內」也讓回答從 1,400~1,900 個 token 降到幾十個字,訊息區比較好讀,用雲端 API 時費用也跟著降下來。

補充四、C# 程式改良版
原本的程式有幾個地方可以改:
  • 故障碼寫死成 "P0218",實際要用解碼出來的結果;而且 ECU 一次可能回好幾個碼(畫面上的 P0168P0303),要拆開逐一查
  • API 金鑰直接寫在程式裡,改成讀環境變數,程式碼才不會不小心把金鑰一起分享出去
  • catch 區塊是空的,出錯時畫面上什麼都不會顯示,至少要把錯誤訊息印出來
1. 從 CAN 資料解碼 DTC(每 2 bytes 一個碼,高 2 bits 決定 P/C/B/U):
static List<string> DecodeDtcs(byte[] data, int count)
{
    char[] type = { 'P', 'C', 'B', 'U' };
    var list = new List<string>();
    for (int i = 0; i < count; i++)
    {
        byte b1 = data[i * 2], b2 = data[i * 2 + 1];
        if (b1 == 0 && b2 == 0) continue;           // 00 00 是填充,不是故障碼
        list.Add($"{type[b1 >> 6]}{(b1 >> 4) & 0x3}{b1 & 0xF:X}{b2:X2}");
    }
    return list;                                   // 01 68 03 03 → P0168, P0303
}
2. 先查表、再問 AI:
static readonly Dictionary<string, string> DtcTable = new()
{
    ["P0168"] = "Fuel Temperature Too High",
    ["P0303"] = "Cylinder 3 Misfire Detected",
    ["P0218"] = "Transmission Fluid Over Temperature Condition",
    // ……其餘從 CSV 載入
};

async Task<string> ExplainDtcAsync(ChatClient client, string code)
{
    if (!DtcTable.TryGetValue(code, out var name))
        return $"{code}:本地表查不到,請查原廠維修手冊";

    string prompt = $"OBD2 故障碼 {code} 的 SAE 標準定義是「{name}」。" +
                    "請只根據這個定義,用繁體中文、三句話以內說明它代表什麼," +
                    "以及最常見的兩個原因。不要更改定義。";
    ChatCompletion c = await client.CompleteChatAsync(prompt);
    return $"{code}({name}):{c.Content[0].Text}";
}
3. 雲端或本機二選一:OpenAI 的 NuGet 套件也能連 Ollama,因為 Ollama 提供相容 OpenAI 的 API,只要換掉 Endpoint:
using System.ClientModel;
using OpenAI;
using OpenAI.Chat;

// 雲端 OpenAI:金鑰從環境變數讀
ChatClient cloud = new("gpt-4.1-mini",
    Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

// 本機 Ollama(GX10):金鑰隨便填,不會檢查
ChatClient local = new("gemma4:12b",
    new ApiKeyCredential("ollama"),
    new OpenAIClientOptions { Endpoint = new Uri("http://GX10的IP:11434/v1") });
注意:Ollama 預設只聽 127.0.0.1,Windows 測試機要連 GX10 的話,要在 GX10 設 OLLAMA_HOST=0.0.0.0,而且建議走 Tailscale,不要把 11434 埠直接開到公司或家裡的整個網段。

補充五、雲端 OpenAI 與本機 Ollama 怎麼選
雲端 OpenAI(gpt-4.1 / mini)本機 Ollama(GX10)
費用按 token 計費免費
網路要能連外網產線、實驗室內網也能用
速度通常幾秒這次實測 gemma4:12b 一個碼約 20~70 秒
不查表直接問常見碼大多正確實測 3 個錯 2 個,不建議
先查表再問正確實測 3 個全對

結論:不管用雲端還是本機,都建議「先查表、AI 只負責解釋」。這樣小模型就夠用,也不用擔心 AI 編出一個看起來很專業、其實是錯的答案。

延伸閱讀

沒有留言:

張貼留言