移至主內容

用AWS Lambda解析Modbus/RS-485感測器封包資料

工廠IT技術人員專用AWS LambdaModbus封包解析ATLANTIS 自有品牌

用AWS Lambda解析Modbus/RS-485感測器封包資料

台灣31年工業儀錶製造商 ATLANTIS 昶特有限公司系列教學文章第四篇:前三篇我們示範了「感測器發布」與「Lambda事件觸發」的最小架構,但現實世界的Modbus封包資料往往不是乾淨的JSON——而是一串16進位暫存器數值,牽涉位元組順序、資料型態與換算比例。本篇專門解決這個問題:教你在Lambda函式中正確解析Modbus RTU封包,把原始暫存器數值轉換成有意義的溫度與壓力讀值。

在ATLANTIS的品牌故事裡,「Re-Atlantis」代表對精密秩序的重現——而在工業數據的世界中,精密不只在感測器本身,更在於資料被正確解讀的每一個環節。一個位元組順序判斷錯誤,可能讓68.4°C的讀值變成完全不同的數字。這正是本篇要仔細拆解的主題。詳見 ATLANTIS 品牌故事

一、為什麼「解析」是資料管線中最容易出錯的一環?

第一篇第三篇的範例中,我們假設現場閘道器已經把資料整理成乾淨的JSON格式(例如{"temperature": 68.4})再發布上雲。但實務上,很多工廠的Modbus閘道器只負責「轉送原始暫存器數值」,真正的「數值換算」與「單位轉換」工作,其實落在Lambda這一層。這也是本篇要補齊的關鍵拼圖。

4種
常見暫存器
資料型態
4種
32位元浮點數
位元組順序組合
1個位元組
順序判斷錯誤
足以讓讀值完全失真
CRC16
Modbus RTU
封包完整性驗證機制

常見的解析錯誤場景

工廠IT技術人員在第一次處理Modbus封包解析時,最容易遇到三種狀況:把有號整數當成無號整數解析(導致負溫度顯示成一個超大正數)、32位元浮點數的兩個暫存器順序放反(導致數值完全錯亂),以及忘記除以換算比例(例如暫存器實際存的是684,代表68.4°C,換算比例為10)。本篇會逐一拆解這些情境的正確處理方式。

二、架構總覽:從原始封包到工程單位數值

① Modbus RTU封包 原始16進位暫存器值 例:2B 68 00 00 ② MQTT發布 payload內含 raw_hex欄位 ③ Lambda函式 位元組順序判斷 資料型態轉換 乘上換算比例 ④ 工程單位數值 temperature: 68.4 unit: "C" ⑤ 儲存 DynamoDB /告警

本篇聚焦在③:Lambda函式如何把一串看似無意義的16進位字串,正確還原成有意義的工程單位數值。這一步做對了,後續的告警判斷、資料庫儲存、儀表板顯示才有意義;這一步做錯了,即使前面的雲端架構再完美,顯示出來的數字也是錯的。

三、Modbus暫存器資料型態完整對照表

在解析封包之前,第一件事是確認目標暫存器儲存的是哪一種資料型態。以下整理ATLANTIS產品規格書中常見的四種資料型態,以及它們在暫存器中的儲存方式。

資料型態佔用暫存器數數值範圍常見應用
UInt16(無號16位元整數)1個(16-bit)0~65535單純正值溫度、狀態碼
Int16(有號16位元整數)1個(16-bit)-32768~32767可能出現負值的溫度(如冷凍庫)
UInt32 / Int32(32位元整數)2個(合併為32-bit)依正負號而定,範圍更大大範圍壓力值、累積流量
Float32(IEEE-754浮點數)2個(合併為32-bit)依IEEE-754標準高精度傳送器直接輸出工程單位值

值得特別注意的是Int16有號整數的處理:如果暫存器數值是0xFF38(十進位65336),若誤當成UInt16處理,會得到一個看似合理但完全錯誤的正值;正確做法應判斷最高位元是否為1,若是則需轉換為對應的負數(此例應為-200,代表-20.0°C,視換算比例而定)。

32位元浮點數的位元組順序(Byte Order / Word Order)

當一個數值需要用兩個16位元暫存器合併表示時,不同廠牌設備對「先放高位還是低位」的定義並不統一,業界常見四種排列組合:

順序代號排列方式常見於
ABCD(Big-Endian)暫存器1高位元組在前,依序排列部分歐系設備預設格式
DCBA(Little-Endian)與ABCD完全相反的位元組順序部分低階微控制器晶片
BADC(Byte-Swap)每個暫存器內部位元組互換,暫存器順序不變部分Modbus閘道器轉換行為
CDAB(Word-Swap)暫存器順序互換,暫存器內部位元組不變ATLANTIS部分數位傳送器與多數工業慣例

務必在對照產品規格書後再撰寫解析程式——這是我們在協助工廠客戶除錯時,最常發現的問題來源。若不確定,可以先用已知數值(例如常溫下應顯示約25°C)反推目前使用的是哪一種排列組合。

四、Lambda解析範例:從16進位字串到工程單位數值

以下範例假設MQTT發布的payload中包含一個raw_hex欄位,儲存兩個暫存器合併後的4個位元組16進位字串(例如"43094CCD"代表一個Float32浮點數),Lambda函式負責解析並輸出正確的溫度數值。

範例一:解析Word-Swap(CDAB)格式的Float32溫度值

import json
import struct

def parse_float32_cdab(raw_hex: str) -> float:
    # raw_hex範例:"4C09CD43"(暫存器順序:CDAB,需重組為ABCD再轉換)
    raw_bytes = bytes.fromhex(raw_hex)
    # 將暫存器順序由CDAB重組回ABCD:交換前後兩個16-bit字組
    reordered = raw_bytes[2:4] + raw_bytes[0:2]
    value = struct.unpack('>f', reordered)[0]
    return round(value, 2)

def lambda_handler(event, context):
    raw_hex = event.get("raw_hex")
    device_id = event.get("device_id", "unknown")

    if not raw_hex:
        return {"statusCode": 400, "body": "缺少raw_hex欄位"}

    temperature = parse_float32_cdab(raw_hex)
    print(f"裝置 {device_id} 解析後溫度:{temperature}°C")

    return {
        "statusCode": 200,
        "body": json.dumps({"device_id": device_id, "temperature": temperature})
    }

這段程式的關鍵在parse_float32_cdab函式:先把16進位字串轉成位元組(bytes),依CDAB規則重新排列成標準ABCD順序,再用Python內建的struct.unpack依IEEE-754標準還原成浮點數。如果你的設備使用不同的位元組順序,只需調整重組那一行的位元組切片方式

範例二:處理有號整數(Int16)並套用換算比例

另一種常見情境是暫存器存的是簡單的有號整數,需要先判斷正負號,再乘上規格書標示的換算比例(例如除以10代表小數點一位)。

Int16有號整數轉換範例

def parse_int16_signed(raw_value: int, scale: float = 10.0) -> float:
    # raw_value為Modbus讀取到的原始16-bit數值(0~65535)
    if raw_value > 32767:
        # 超過有號16位元最大正值,代表這是負數,需轉換
        raw_value -= 65536
    return raw_value / scale

# 範例:暫存器讀到 65336(0xFF38)
result = parse_int16_signed(65336, scale=10.0)
print(f"換算後溫度:{result}°C")  # 輸出:-20.0°C

這個轉換邏輯的核心是if raw_value > 32767: raw_value -= 65536——這是處理「二補數(Two's Complement)」有號整數最簡潔的寫法。若你的Lambda函式需要同時處理多款不同型號的變送器,建議把每個型號的資料型態、位元組順序與換算比例整理成一份「裝置設定檔(Device Profile)」,如下一節示範。

五、進階:用裝置設定檔管理多種型號的解析邏輯

當工廠現場同時使用多款不同型號的ATLANTIS變送器時,把每個型號的解析規則寫死在程式碼中會讓維護變得困難。更好的做法是建立一份「裝置設定檔」,讓Lambda函式依device_model欄位動態查表決定解析方式。

範例三:裝置設定檔驅動的通用解析函式

import struct

# 裝置設定檔:型號 → 資料型態、位元組順序、換算比例
DEVICE_PROFILES = {
    "STT": {"dtype": "float32_cdab", "scale": 1.0},
    "DTT-P4": {"dtype": "int16_signed", "scale": 10.0},
    "SDPT-3100": {"dtype": "float32_abcd", "scale": 1.0},
    "THT-S81": {"dtype": "uint16", "scale": 10.0},
}

def parse_by_profile(device_model: str, raw_hex: str):
    profile = DEVICE_PROFILES.get(device_model)
    if not profile:
        raise ValueError(f"未知裝置型號:{device_model}")

    raw_bytes = bytes.fromhex(raw_hex)
    dtype = profile["dtype"]

    if dtype == "float32_cdab":
        reordered = raw_bytes[2:4] + raw_bytes[0:2]
        value = struct.unpack('>f', reordered)[0]
    elif dtype == "float32_abcd":
        value = struct.unpack('>f', raw_bytes)[0]
    elif dtype == "int16_signed":
        raw_int = struct.unpack('>h', raw_bytes[:2])[0]
        value = raw_int / profile["scale"]
    elif dtype == "uint16":
        raw_int = struct.unpack('>H', raw_bytes[:2])[0]
        value = raw_int / profile["scale"]
    else:
        raise ValueError(f"不支援的資料型態:{dtype}")

    return round(value, 2)

def lambda_handler(event, context):
    device_model = event.get("device_model")
    raw_hex = event.get("raw_hex")

    try:
        parsed_value = parse_by_profile(device_model, raw_hex)
        print(f"型號 {device_model} 解析結果:{parsed_value}")
        return {"statusCode": 200, "parsed_value": parsed_value}
    except ValueError as e:
        print(f"解析失敗:{e}")
        return {"statusCode": 500, "error": str(e)}

這種「設定檔驅動」的架構,讓你未來新增第五款、第六款變送器型號時,只需要在DEVICE_PROFILES字典裡新增一行設定,而不需要修改核心解析邏輯。這也是我們建議工廠IT技術人員在設計初期就採用的架構思維——為未來的擴充預留彈性。

六、封包完整性驗證:CRC16 校驗的角色

Modbus RTU封包本身在傳輸層已內建CRC16(循環冗餘校驗)機制,用來偵測傳輸過程中是否發生位元錯誤。雖然多數Modbus函式庫(如pymodbus)會在讀取階段自動驗證CRC並拋出錯誤,但若你的架構是「先由閘道器轉發原始位元組,再交給Lambda處理」,建議在Lambda端也加上一層驗證,避免處理到已損毀的資料。

簡易CRC16(Modbus)驗證函式

def modbus_crc16(data: bytes) -> int:
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

實務上,若你採用第一篇範例中的pymodbus函式庫在現場閘道器端讀取數據,CRC驗證已經在函式庫內部自動處理,通常不需要在Lambda端重複驗證;但若架構改為「轉發原始位元組供雲端解析」,這層驗證能提早攔截傳輸錯誤,避免把損毀數據誤判為異常告警。

七、ATLANTIS 支援多種暫存器資料型態的量測產品

DPS-2.5SPD3 多功能壓力開關

DPS-2.5SPD3 多功能壓力開關 —— 可切換7種壓力單位,選配RS-485數位輸出,暫存器資料型態與換算比例詳列於產品規格書

THT-S81 室內溫濕度傳送器

THT-S81 室內溫濕度傳送器 —— 支援RS485 Modbus RTU,溫度與濕度分別以獨立暫存器輸出,適合示範多欄位解析邏輯

DMPT-301-5CD 數位微差壓傳送器

DMPT-301-5CD 數位微差壓傳送器 —— 適合空氣微壓測量與洩漏檢測,數位輸出可搭配Lambda解析後用於潔淨室壓差監控告警

AT803-D 高精度數位差壓錶

AT803-D 高精度數位差壓錶 —— 精度達±0.02%F.S.,適合對暫存器解析精度要求極高的實驗室與校準應用場景

八、案例分享:某新竹科學園區廠務單位的多品牌設備整合解析

以下案例經匿名化處理,客戶為新竹科學園區一家半導體相關廠務單位,現場同時存在多個年份採購的溫度壓力變送器,型號與資料格式不完全一致。

問題現象排查過程根本原因解決方式
部分裝置讀值長期偏高數千倍比對規格書與實際暫存器數值Float32位元組順序誤用ABCD而非CDAB依裝置設定檔區分不同型號的解析規則
冷凍區溫度顯示異常大的正數檢查原始暫存器16進位值負溫度以Int16有號整數表示,程式誤用UInt16解析加入二補數轉換邏輯(如本文範例二)
偶發性數值跳動異常加入CRC16驗證後比對錯誤發生頻率部分RS-485線路接地不良,偶發位元錯誤加上CRC驗證過濾損毀封包,並改善接地與屏蔽

資深工程師賴祥德分享:「封包解析的錯誤通常不會讓系統當掉,而是讓系統『安靜地顯示錯誤數字』——這比系統直接故障更危險,因為工程師往往要花很長時間才會發現。我們建議任何雲端監控專案上線前,都要準備幾組『已知正確答案』的測試數據,逐一驗證解析邏輯,而不是等上線後才發現數字對不上。」

資料來源與延伸閱讀

本文技術規範參考 Modbus組織發布之Modbus應用協定規範(modbus.org)、IEEE 754浮點數運算標準,以及 Python官方struct模組文件。CRC16演算法為Modbus RTU標準通用實作。ATLANTIS產品暫存器對照資訊引用自內部產品規格書。

十、20 大常見問題 FAQ(Modbus/RS-485封包解析)

1. 為什麼Modbus閘道器不直接幫我把數值換算好?
部分閘道器確實提供內建換算功能,但需要手動設定每個暫存器的資料型態與比例;若閘道器僅做「透明轉發」,換算工作就會落在後端程式(如本文的Lambda函式)處理,這也是為什麼理解封包解析很重要。
2. 怎麼知道我的設備是ABCD還是CDAB順序?
最可靠的方式是查閱產品規格書中的Modbus通訊協定章節;若規格書未明確標示,可用已知數值(如常溫下應約25°C)反覆測試不同排列組合,找出能得到合理數值的那一種。
3. struct.unpack('>f', ...)裡面的'>'和'f'是什麼意思?
'>'代表Big-Endian位元組順序(高位元組在前),'f'代表要解析成32位元浮點數(float)
4. Int16和UInt16的差別,具體會造成多大的數值誤差?
當暫存器實際數值超過32767時,若誤用UInt16解析,會直接把應為負數的讀值顯示成一個介於32768~65535之間的超大正數,兩者差距可能達到6萬以上,這也是最容易被察覺的解析錯誤。
5. 為什麼要用裝置設定檔(Device Profile),而不是為每個裝置寫一個函式?
裝置設定檔讓解析邏輯與裝置規格分離,新增裝置型號時只需增加設定資料,不需修改核心程式碼,降低長期維護成本與出錯機率,特別適合多品牌、多型號混用的工廠環境。
6. CRC16驗證失敗時,Lambda函式應該怎麼處理?
建議記錄該筆訊息為「封包驗證失敗」並略過後續解析,同時可考慮發送低優先度通知供工程師事後排查是否為線路品質問題,避免直接把可能損毀的數值當作正常讀值使用。
7. 換算比例(scale)都是除以10嗎?
不一定,換算比例因產品與暫存器設計而異,可能是除以10(一位小數)、除以100(兩位小數)或不需換算,必須依產品規格書標示的實際比例設定,不可套用固定假設。
8. 32位元整數(Int32/UInt32)的解析方式跟Float32一樣嗎?
位元組順序判斷邏輯類似,但格式字元不同:UInt32使用struct.unpack('>I', ...),Int32使用'>i',Float32使用'>f',需依實際資料型態選用正確的格式字元。
9. 如果我完全不知道設備的資料格式,有辦法反推嗎?
可以嘗試「已知條件法」:在常溫、已知壓力等可控條件下讀取原始暫存器數值,逐一測試不同資料型態與位元組順序組合,找出能得到接近已知條件數值的解析方式,再用其他讀值交叉驗證。
10. 解析錯誤會不會影響前面幾篇文章教的告警邏輯?
會,如果解析出來的數值本身就是錯的,即使規則引擎與Lambda的告警邏輯設定完全正確,也會基於錯誤數值做出錯誤判斷,這也是為什麼封包解析被視為整條資料管線中最需要仔細驗證的環節。
11. 我可以把解析邏輯放在Modbus閘道器端,而不是Lambda嗎?
可以,部分工業級閘道器支援設定暫存器資料型態並直接轉換為工程單位數值再發布。放在閘道器端的好處是雲端收到的資料已經是乾淨數值;放在Lambda端的好處是集中管理解析邏輯、方便版本更新,可依團隊架構偏好選擇。
12. 有沒有現成的Python函式庫可以簡化這些解析工作?
有,除了本文使用的struct標準函式庫外,也可以評估使用pymodbus本身提供的資料解碼工具(如BinaryPayloadDecoder),內建對多種位元組順序與資料型態的解析支援,可減少手動處理位元組排列的程式碼量。
13. 為什麼要在Lambda裡用try/except包住解析邏輯?
因為現場資料可能因傳輸雜訊、設備異常或格式不符預期而解析失敗,若不做例外處理,單筆錯誤資料可能導致整個Lambda函式執行中斷,影響其他正常訊息的處理,建議務必加上例外處理並記錄錯誤內容。
14. round(value, 2)這樣四捨五入會不會影響精度判斷?
四捨五入主要影響顯示與記錄層級的精度,若後續告警判斷需要更高精度(例如±0.05°C的門檻判斷),建議保留原始未四捨五入的數值進行邏輯判斷,僅在顯示或記錄時才做適當捨入。
15. 不同批次生產的同型號感測器,資料格式會不會不一樣?
正常情況下同型號產品的通訊協定應保持一致,但若有韌體版本更新或客製化選項,仍建議在批次更換或新增裝置時,重新驗證一次解析邏輯,避免因韌體差異導致資料格式變動。
16. 解析後的數值要不要加上單位驗證(例如溫度不應超過材料極限)?
建議加上合理範圍檢查(Sanity Check),例如溫度讀值超出感測器規格範圍時視為異常,這能同時攔截解析邏輯錯誤與感測器本身故障兩種情況,是提高資料可信度的簡單有效做法。
17. raw_hex欄位的長度不對時,程式會怎樣?
bytes.fromhex()若輸入的16進位字串長度不符合預期(如非偶數長度),會拋出例外;後續的struct.unpack若位元組數量與格式字元不匹配,也會拋出錯誤,這些都應該被try/except攔截並記錄,而非讓函式意外中斷。
18. 這種封包解析的邏輯,適合放在哪一種Lambda觸發架構?
適合搭配第三篇介紹的IoT Core規則引擎直接觸發架構,在規則SQL中選取raw_hexdevice_model等原始欄位,交由Lambda做解析與後續判斷,這也是本文架構與前三篇的銜接方式。
19. 解析邏輯需要寫單元測試嗎?
強烈建議。針對每一種資料型態與位元組順序組合,準備已知輸入與預期輸出的測試案例,可以在每次修改解析邏輯或新增裝置型號時快速驗證是否引入新的錯誤,這對長期維護多型號設備的架構尤其重要。
20. 學會封包解析後,下一步該學什麼?
建議接續學習如何將解析後的乾淨數值透過Amazon SNS觸發告警通知,以及寫入DynamoDB做歷史記錄查詢,這些正是本系列文章後續篇章要展開的主題。

十一、下一步:讓 ATLANTIS 協助你確認產品的暫存器規格與解析邏輯

31年工業儀錶製造經驗 × 完整暫存器規格文件支援

從產品選型、暫存器位址對照,到Modbus封包解析邏輯驗證,我們可以陪工廠IT團隊完成資料管線最容易出錯的這一段。

📞 02-2820-3405 免費選型諮詢 📧 線上快速詢價

業務一部 Ian:ian@atlantis.com.tw | 業務二部 Nori:nori@atlantis.com.tw


文章更新時間:2026年7月|作者:ATLANTIS 應用工程團隊|本文為系列教學文章第四篇,下一篇將深入「溫度異常自動告警:Lambda + SNS 實作工廠即時警報系統」。