Skip to content

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

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;

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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