Skip to content

Is it feasible to use CPPPO to turn a PC into an adapter? #118

@dalnel

Description

@dalnel

I’d like to ask: is it possible to turn the PC into an adapter? What I’m trying to do is make my PC act as an adapter so that the PLC can continuously read the values I want to respond with. I’ve already imported the corresponding EDS file, but it still doesn’t work. I’m stuck at the part where I need to set up the server. Below is my code. mycode is from copliot.

import threading
import time
import logging

# 匯入 cpppo 的 EIP Server 和數據模型
from cpppo.server.enip import server
from cpppo.server.enip.poll import poll
from cpppo.server.enip.get_attribute import attribute_name

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# --- 參數設定 ---
PC_IP_ADDRESS = '172.16.0.201' # 替換為您的 PC 實際 IP 地址

# PLC (Scanner) 期望讀寫的數據模型 (Assembly Object)
# 使用 DINT 是因為 INT (16位) 在許多 EIP 實現中容易被忽略,DINT (32位) 更通用。
TAG_NAME = "My_EIP_Data"
DATA_TYPE = "DINT"
ARRAY_LENGTH = 10 
DINT_SIZE = 4 # 每個 DINT 佔 4 bytes
# 總 I/O 數據長度: 10 * 4 = 40 bytes

# --- 核心邏輯 ---

def run_eip_adapter():
    # 1. 定義 Server 的數據字典 (這是 PLC 會讀寫的 I/O 記憶體)
    parameters = {
        TAG_NAME: [0] * ARRAY_LENGTH # 初始值
    }
    
    logging.info(f"🟢 EIP Adapter 啟動中...")
    logging.info(f"   - PC IP 地址: {PC_IP_ADDRESS}")
    logging.info(f"   - 數據模型: {TAG_NAME}={DATA_TYPE}[{ARRAY_LENGTH}] ({ARRAY_LENGTH * DINT_SIZE} bytes)")
    logging.info(f"   - 監聽 TCP/44818 (等待 PLC 的 Forward_Open 請求)...")

    # 2. 啟動 EIP Server
    try:
        # 創建 EIP Server 實例
        srv = server.server(
            bindings=[PC_IP_ADDRESS], # 綁定到特定 IP,確保監聽正確
            parameters=parameters,
            timeout=0.1, # 短暫超時,讓主迴圈可以更新數據
            workers=1    # 使用單一工作執行緒
        )
        
        logging.info("✨ EIP Adapter 成功運行!請切換到 PLC 配置。")
        
        counter = 0
        while True:
            # 讓 Server 運行一小段時間 (處理連線請求和數據交換)
            srv.loop_iter() 
            
            # --- 模擬數據更新 (用於測試 PLC 是否成功讀到變化) ---
            # 讓第一個值不斷變化
            parameters[TAG_NAME][0] = counter
            
            # 檢查是否有來自 PLC 的寫入 (如果 PLC 配置為寫入,值會變動)
            plc_write_value = parameters[TAG_NAME][1]
            
            counter += 1
            if counter > 65535:
                counter = 0

            # 顯示當前狀態和 PLC 寫入的值
            print(f"\r[Server 狀態]: Counter={parameters[TAG_NAME][0]} | PLC寫入值: {plc_write_value} | 等待連線...", end='', flush=True)

            time.sleep(0.5)

    except KeyboardInterrupt:
        logging.info("\n程式被使用者中斷,正在停止 EIP Adapter...")
    except Exception as e:
        logging.error(f"❌ 發生致命錯誤: {e}")
    finally:
        if 'srv' in locals() and srv:
            srv.stop()
            logging.info("Adapter 已停止。")

if __name__ == "__main__":
    run_eip_adapter()

Right now, I’m stuck at the line from cpppo.server.enip import server. It says “cannot import name 'server' from 'cpppo.server.enip'.” How should I resolve this?

Here is the content of my EDS file.

$ EZ-EDS Version 3.36.1.20241010 Generated Electronic Data Sheet

[File]
        DescText = "EDS file for Simple IO Device";
        CreateDate = 09-24-2025;
        CreateTime = 12:00:00;
        ModDate = 09-24-2025;
        ModTime = 12:00:00;
        Revision = 1.00;
        HomeURL = "https://github.com/";

[Device]
        VendCode = 1234;
        VendName = "test";
        ProdType = 43;
        ProdTypeStr = "Generic Device";
        ProdCode = 1;
        MajRev = 1;
        MinRev = 1;
        ProdName = "Simple IO Device";
        Catalog = "SimpleIO";

[Device Classification]
        Class1 = EtherNetIP;

[Params]
        Param1 =
                0,                      $ reserved, shall equal 0
                ,,                      $ Link Path Size, Link Path
                0x0000,                 $ Descriptor
                0xC6,                   $ Data Type (USINT)
                1,                      $ Data Size in bytes
                "Write Byte",           $ name
                "",                     $ units
                "Write a single byte to the device.",    $ help string
                0,255,0,                $ min, max, default data values
                ,,,,                    $ mult, div, base, offset scaling
                ,,,,                    $ mult, div, base, offset links
                ;                       $ decimal places
        Param2 =
                0,                      $ reserved, shall equal 0
                ,,                      $ Link Path Size, Link Path
                0x0030,                 $ Descriptor
                0xC6,                   $ Data Type (USINT)
                1,                      $ Data Size in bytes
                "Read Byte",            $ name
                "",                     $ units
                "Read a single byte from the device.",    $ help string
                0,255,0,                $ min, max, default data values
                ,,,,                    $ mult, div, base, offset scaling
                ,,,,                    $ mult, div, base, offset links
                ;                       $ decimal places
        Param10 =
                0,                      $ reserved, shall equal 0
                ,,                      $ Link Path Size, Link Path
                0x0000,                 $ Descriptor
                0xC8,                   $ Data Type
                4,                      $ Data Size in bytes
                "RPI",                  $ name
                "us",                   $ units
                "Sets the RPI.",        $ help string
                1000,3200000,10000,     $ min, max, default data values
                ,,,,                    $ mult, div, base, offset scaling
                ,,,,                    $ mult, div, base, offset links
                ;                       $ decimal places

[Assembly]
        Revision = 2;
        Object_Name = "Assembly Object";
        Object_Class_Code = 0x04;
        MaxInst = 2;
        Number_Of_Static_Instances = 2;
        Max_Number_Of_Dynamic_Instances = 0;
        Assem1 =
                "Output (O->T)",
                "20 04 24 01 30 03",
                ,
                0x0001,
                ,,
                8,Param1;
        Assem2 =
                "Input (T->O)",
                "20 04 24 01 30 03",
                ,
                0x0000,
                ,,
                8,Param2;

[Connection Manager]
        Object_Name = "Connection Manager Object";
        Object_Class_Code = 0x06;
        Connection1 =
                0x04010002,             $ Connection type flags
                0x44640455,             $ Connection flags
                Param10,,Assem1,        $ O->T RPI, size, format
                Param10,,Assem2,        $ T->O RPI, size, format
                ,,                      $ proxy config size, format
                ,,                      $ target config size, format
                "Exclusive Owner",      $ Connection Name
                "",                     $ help string
                "20 04 2C 01 2C 02";    $ Path
        Connection2 =
                0x01010002,             $ Connection type flags
                0x44640355,             $ Connection flags
                Param10,0,,             $ O->T RPI, size, format
                Param10,,Assem2,        $ T->O RPI, size, format
                ,,                      $ proxy config size, format
                ,,                      $ target config size, format
                "Listen Only",          $ Connection Name
                "",                     $ help string
                "20 04 2C C6 2C 02";    $ Path
        Connection3 =
                0x02010002,             $ Connection type flags
                0x44640355,             $ Connection flags
                Param10,0,,             $ O->T RPI, size, format
                Param10,,Assem2,        $ T->O RPI, size, format
                ,,                      $ proxy config size, format
                ,,                      $ target config size, format
                "Input Only",           $ Connection Name
                "",                     $ help string
                "20 04 2C C5 2C 02";    $ Path

[Capacity]
        MaxIOConnections = 2;
        MaxMsgConnections = 6;
        MaxConsumersPerMcast = 2;
        TSpec1 = TxRx, 8, 2000;

[DLR Class]
        Revision = 3;
        Object_Name = "Device Level Ring Object";
        Object_Class_Code = 0x47;
        MaxInst = 1;
        Number_Of_Static_Instances = 1;
        Max_Number_Of_Dynamic_Instances = 0;
        Ring_Supervisor_Capable = No;

[TCP/IP Interface Class]
        Revision = 4;
        Object_Name = "TCP/IP Interface Object";
        Object_Class_Code = 0xF5;
        MaxInst = 1;
        Number_Of_Static_Instances = 1;
        Max_Number_Of_Dynamic_Instances = 0;

[Ethernet Link Class]
        Revision = 3;
        Object_Name = "Ethernet Link Object";
        Object_Class_Code = 0xF6;
        MaxInst = 1;
        Number_Of_Static_Instances = 1;
        Max_Number_Of_Dynamic_Instances = 0;
        InterfaceLabel1 = "Ethernet Port 1";
        InterfaceType1 = 2;

[Identity Class]
        Revision = 2;
        Object_Name = "Identity Object";
        Object_Class_Code = 0x01;
        MaxInst = 1;
        Number_Of_Static_Instances = 1;
        Max_Number_Of_Dynamic_Instances = 0;

[QoS Class]
        Revision = 1;
        Object_Name = "QoS Object";
        Object_Class_Code = 0x48;
        MaxInst = 1;
        Number_Of_Static_Instances = 1;
        Max_Number_Of_Dynamic_Instances = 0;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions