加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 一、FINS TCP與FINS UDP
    • 二、程序?qū)崿F(xiàn)
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

python實(shí)現(xiàn)FINS協(xié)議的UDP服務(wù)端

12/06 13:47
1045
閱讀需 16 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

python實(shí)現(xiàn)FINS協(xié)議的UDP服務(wù)端是一件稍微麻煩點(diǎn)的事情。它不像modbusTCP那樣,可以使用現(xiàn)成的pymodbus模塊去實(shí)現(xiàn)。但是,我們可以根據(jù)協(xié)議幀進(jìn)行組包,自己去實(shí)現(xiàn)幀的格式,而這一切可以基于socket模塊。本文基于原先?FINS協(xié)議的TCP服務(wù)端的文章進(jìn)行修改。

一、FINS TCP與FINS UDP

1、FINS_UDP與FINS_TCP有什么不同

FINS(Factory Interface Network Service)是歐姆龍(Omron)PLC(可編程邏輯控制器)的通信協(xié)議。FINS支持兩種主要的傳輸方式:FINS over TCP(FINS_TCP)和FINS over UDP(FINS_UDP)。

下面是它們之間的主要區(qū)別:

(1)傳輸層協(xié)議:

FINS_TCP: 使用TCP(Transmission Control Protocol)作為傳輸層協(xié)議。TCP是面向連接的、可靠的協(xié)議,確保數(shù)據(jù)的可靠性和順序傳輸。

FINS_UDP: 使用UDP(User Datagram Protocol)作為傳輸層協(xié)議。UDP是面向無連接的協(xié)議,它不保證數(shù)據(jù)的可靠性和順序傳輸,但通常具有更低的延遲。

(2)連接方式:

FINS_TCP: 建立連接后進(jìn)行通信,類似于常見的TCP通信方式。

FINS_UDP: 無連接,每個(gè)數(shù)據(jù)包獨(dú)立發(fā)送,適用于對實(shí)時(shí)性要求較高的應(yīng)用場景。

(3)可靠性和順序性:

FINS_TCP: 提供TCP的可靠性和順序性,適用于對數(shù)據(jù)完整性和傳輸順序有要求的應(yīng)用。

FINS_UDP: 不提供可靠性和順序性的保證,適用于對實(shí)時(shí)性要求較高,可以容忍一些數(shù)據(jù)丟失的場景。

(4)用途:

FINS_TCP: 適用于對數(shù)據(jù)完整性和傳輸順序要求較高的應(yīng)用,例如需要確保每個(gè)數(shù)據(jù)包都被正確接收的場景。

FINS_UDP: 適用于實(shí)時(shí)性要求較高,可以容忍一些數(shù)據(jù)丟失的應(yīng)用,例如對于實(shí)時(shí)控制要求較高的系統(tǒng)。

選擇使用哪種方式取決于具體的應(yīng)用場景和對通信特性的要求。

2、FINS_UDP與FINS_TCP的協(xié)議包有什么不同

(1)握手包

FINS_TCP有握手包,而FINS_UDP沒有握手包。

(2)請求頭

FINS_TCP的請求頭是FINS,而FINS_UDP沒有請求頭。

(3)其他部分

一致。相關(guān)文檔請查閱我之前寫的“python實(shí)現(xiàn)FINS協(xié)議的TCP服務(wù)端(篇一)”等文章,現(xiàn)對比如下:

FINS_TCP

46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 02 00 01 00 00 01 00 3D 01 01 82 00 64 00 00 01

FINS_UDP

80 00 02 00 FF 00 00 05 00 64 01 01 82 00 64 00 00 01

二、程序?qū)崿F(xiàn)

1、構(gòu)建響應(yīng)幀

def recognition_frame(req_bytes_frame, Trigger):
    get_frame = req_bytes_frame.hex().upper()
    print("設(shè)備請求:", get_frame)
    SRC_value = get_frame[22:24]  # 判斷讀寫,01為讀,02為寫
    Area_value = get_frame[24:26]  # 判斷寄存器區(qū)域,82為保持寄存器
    # print(SRC_value)
    # print(Area_value)
    if SRC_value == "01":
        if Area_value == "82":
            response_1 = "00000000000000000000010100000001"  # Trigger位為True
            response_0 = "00000000000000000000010100000000"  # Trigger位為False
            if Trigger == True:
                return bytes().fromhex(response_1)
            else:
                return bytes().fromhex(response_0)
        else:
            raise ValueError("Area_value is error!")
    elif SRC_value == "02":
        if Area_value == "82":
            print("***************************************")
            # 寫保持寄存器的響應(yīng)
            print("掃碼器寫入的結(jié)果數(shù)據(jù):", bytes().fromhex(get_frame))
            response = "0000000000000000000001020000"
            return bytes().fromhex(response)
        else:
            raise ValueError("Area_value is error!")
    else:
        raise ValueError("SRC_value is error!")

這個(gè)函數(shù)是針對讀取或?qū)懭氡3旨拇嫫鞯恼埱?,以下是對函?shù)的解釋:

(1)輸入?yún)?shù):

req_bytes_frame: 一個(gè)字節(jié)序列,表示設(shè)備請求的原始幀。Trigger: 一個(gè)布爾值,似乎用于確定設(shè)備是否處于觸發(fā)狀態(tài)。

(2)函數(shù)操作:

將輸入的字節(jié)序列轉(zhuǎn)換為十六進(jìn)制表示,并轉(zhuǎn)換為大寫形式。從幀中提取 SRC(源)和 Area(寄存器區(qū)域)的值。

根據(jù) SRC 和 Area 的值執(zhí)行相應(yīng)的邏輯:

如果 SRC 是 "01",表示讀取請求,繼續(xù)判斷 Area 是否為 "82"(保持寄存器)。

如果不是 "82",拋出 ValueError 異常,表示 Area 值錯(cuò)誤。

如果 SRC 是 "02",表示寫入請求,同樣判斷 Area 是否為 "82"。

如果是,打印掃碼器寫入的結(jié)果數(shù)據(jù),構(gòu)建寫保持寄存器的響應(yīng)幀。如果不是 "82",同樣拋出 ValueError 異常。

如果 SRC 不是 "01" 或 "02",拋出 ValueError 異常,表示 SRC 值錯(cuò)誤。

如果是,根據(jù) Trigger 的值構(gòu)建響應(yīng)幀,其中 Trigger 為 True 時(shí)觸發(fā)位為 1,否則為 0。

(3)返回值:

如果是讀取請求,返回構(gòu)建的響應(yīng)幀(True 觸發(fā)位或 False 觸發(fā)位)。

如果是寫入請求,返回構(gòu)建的寫保持寄存器的響應(yīng)幀。

(4)異常處理:

如果 Area 值不是 "82",或者 SRC 值不是 "01" 或 "02",都會(huì)拋出 ValueError 異常,提示相應(yīng)的錯(cuò)誤信息。

總體來說,該函數(shù)是為了處理設(shè)備的讀取和寫入請求,并根據(jù)請求類型和條件構(gòu)建相應(yīng)的響應(yīng)幀。

2、服務(wù)器實(shí)現(xiàn)

if __name__ == "__main__":
    DM_start = 1000

    # 創(chuàng)建FINS服務(wù)端
    # 創(chuàng)建一個(gè)TCP/IP套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 綁定套接字到特定地址和端口
    server_address = ('192.168.1.188', 9600)  # 服務(wù)器地址和端口
    server_socket.bind(server_address)

    try:
        num = 0  # 觸發(fā)標(biāo)志
        Trigger_rec = 0  # Trigger置為True時(shí),對應(yīng)變?yōu)?,表示觸發(fā)一次
        response = "" # 響應(yīng)
        while True:
            # 接收客戶端請求
            request, client_address = server_socket.recvfrom(1024)
            if request:
                # 如果收到的不是請求頭
                if "800002" in request.hex():
                    # print(request.hex()[22:24])
                    # 實(shí)現(xiàn)掃碼觸發(fā)
                    if request.hex()[22:24] == "01":  # 判斷讀寫,01為讀觸發(fā)指令,02為寫觸發(fā)結(jié)果
                        if Trigger_rec != 2:
                            Trigger_rec += 1
                        if Trigger_rec == 1:
                            response = recognition_frame(request, Trigger=False)  # 先清空觸發(fā)信號(hào)
                            server_socket.sendto(response, client_address)
                        elif Trigger_rec == 2:  # 復(fù)位Trigger信號(hào)
                            response = recognition_frame(request, Trigger=True)  # 再置位觸發(fā)信號(hào)
                            server_socket.sendto(response, client_address)
                    # 實(shí)現(xiàn)結(jié)果接收
                    elif request.hex()[22:24] == "02":
                        print(request.hex())
                        # print("---------------", int(request.hex()[26:30], 16))
                        if int(request.hex()[26:30], 16) == DM_start + 4:
                            if any(c != '0' for c in request.hex()[36:]):  # 不全為0
                                print("掃碼結(jié)果:", request.hex()[36:])
                                num += 1
                                Trigger_rec = 0
                            else:
                                response = recognition_frame(request, Trigger=True)
                                server_socket.sendto(response, client_address)
                                print("還沒有收到結(jié)果,繼續(xù)等待掃碼結(jié)果!")
                        else:
                            response = recognition_frame(request, Trigger=True)
                            server_socket.sendto(response, client_address)
                # 處理其他請求
                else:
                    response = recognition_frame(request, Trigger=True)
                    server_socket.sendto(response, client_address)
                print("服務(wù)響應(yīng):", response.hex())  # 可以響應(yīng)為空
                if num == 1:
                    assert bytes().fromhex(request.hex()[36:]).decode() != "NG", "實(shí)際掃碼結(jié)果為:{},不符合預(yù)期".format(bytes().fromhex(request.hex()[36:]).decode())
                    break
                request = False
    finally:
        # 清理連接
        server_socket.close()

這段代碼是一個(gè)服務(wù)端程序,是套接字UDP服務(wù)端。讓我們分析主要的部分:

(1)服務(wù)端設(shè)置:

創(chuàng)建一個(gè)UDP套接字(socket.AF_INET, socket.SOCK_DGRAM)用于與客戶端通信。綁定套接字到特定的地址和端口(('192.168.1.188', 9600))。

(2)主循環(huán):

在一個(gè)無限循環(huán)中,服務(wù)端等待從客戶端接收請求。對于收到的請求,根據(jù)請求的內(nèi)容進(jìn)行不同的處理。

(3)請求處理:

判斷請求是否包含特定的頭部標(biāo)識(shí) "800002"。如果是讀觸發(fā)指令("01"),則處理觸發(fā)邏輯。如果是寫觸發(fā)結(jié)果指令("02"),則處理掃碼結(jié)果。如果是其他請求,統(tǒng)一進(jìn)行處理。

(4)觸發(fā)邏輯:

根據(jù)觸發(fā)標(biāo)志 (Trigger_rec) 的狀態(tài),對觸發(fā)指令進(jìn)行相應(yīng)的處理。首先清空觸發(fā)信號(hào),然后再置位觸發(fā)信號(hào)。

(5)掃碼結(jié)果處理:

對于寫觸發(fā)結(jié)果指令,檢查是否收到了預(yù)期的掃碼結(jié)果。如果掃碼結(jié)果不全為0,則認(rèn)為收到有效的掃碼結(jié)果,增加計(jì)數(shù)。如果結(jié)果為0,繼續(xù)等待掃碼結(jié)果。

(6)響應(yīng)處理:

根據(jù)處理后的結(jié)果,調(diào)用 recognition_frame 函數(shù)構(gòu)建響應(yīng)幀。將響應(yīng)發(fā)送給客戶端。

(7)斷言和終止條件:

當(dāng)計(jì)數(shù) num 達(dá)到1時(shí),使用斷言檢查掃碼結(jié)果是否符合預(yù)期,并終止程序。

(8)清理:

finally 塊中關(guān)閉套接字,確保程序退出時(shí)資源被釋放。

總體來說,這個(gè)服務(wù)端程序用于處理來自客戶端的請求,其中包括了特定的觸發(fā)指令和掃碼結(jié)果指令。在處理這些指令時(shí),它通過 recognition_frame 函數(shù)構(gòu)建響應(yīng),并對結(jié)果進(jìn)行相應(yīng)的處理。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜