目录

 

一、引言

二、方案调研

2.1 协议原文

2.2 开源方案资料

2.3 走的弯路

三、初步调试

3.1、EIP在Win上适配

3.2、EIP在Ubuntu上调试

3.2.1 获取从站信息例程

3.2.2 显式连接

3.2.3 隐式连接


 

 

 

 

 

 

一、引言

关于EIP的通讯协议,找了很多资料,没有比较全面完善的。大家都指向,去阅读官方文档,但官方文档只有注册EIP协议的厂家才可使用。本次开发的主要参考来源于国外开源社区。

二、方案调研

2.1 协议原文

原文链接,可自行参考下载查阅

https://ia802308.us.archive.org/4/items/ovda_cip_docs/

2.2 开源方案资料

从过来人角度。这个网址给出的EIP开源方案总结的还是比较可信的。

https://www.cwyyprog.com/2020/06/01/cip-%E5%8D%8F%E8%AE%AE%E7%9A%84%E5%BC%80%E6%BA%90%E5%AE%9E%E7%8E%B0%E6%B1%87%E6%80%BB/

本文最终成功移植跑通的是上述网址所列的EIPScanner方案,开源地址为:

https://github.com/nimbuscontrols/EIPScanner

EIPScanner方案的帮助文档地址为:

https://eipscanner.readthedocs.io/en/latest/index.html

2.3 走的弯路

在采用EIPScanner方案之前,能找到的资料大多指向OpENer。于是从OpENer开始,按照CSDN的博客一步步摸索,最终复现了其中写的”运行起来没有任何输出,不知可否“。在出现问题后,又手动加了打印,调试了一番。

从现在来看,是由于对协议理解不够导致的。我们要找的是主机方案,而不是从机方案。OpENer是适用于EIP从机的开源实现.在cwyyprog的开头就讲到了。

另外,OpENer的支持文档几乎没有。

https://blog.csdn.net/yueni_zhao/article/details/126662297

三、初步调试

3.1、EIP在Win上适配

在移植开发之前,想在win上,通过EIP上位机软件与运控器通讯,用于验证此路径是否可用。(事实证明这个决定是对的,如果没这一步,开源方案很难调通)。

于是寻找”EIP调试助手“(类似于串口调试助手),最终通过B站某博主的视频找到了合适了经验。《Ethernet/IP 从站实战(信捷、基恩士)》。链接如下。

https://www.bilibili.com/video/BV11D4y1w7KK/?spm_id_from=333.999.0.0&vd_source=e9259538915aad2754df86e4eed8a9fa

视频中用到的EtherNET/IP Scanner Demo 软件链接如下:

https://gitcode.com/open-source-toolkit/84fd4/overview?utm_source=tools_gitcode&index=top&type=href&&isLogin=1

网络抓帧工具,WireShark链接如下:

https://www.wireshark.org/download.html

以上环境配置好后,在PLC上设置好生产者链接,及要交互的数据参数,同时在WIn端按相同配置完EtherNET/IP Scanner Demo的设置。通讯自动打通,可以通过上位机接收及下发数据指令。具体上位机EtherNET/IP Scanner Demo的配置过程参考:

使用EtherNET/IP Scanner Demo软件与PLC进行通讯的配置说明_eip scanner怎么添加-CSDN博客

3.2、EIP在Ubuntu上调试

正如2.1、2.2节所述,最终采用EIPScanner方案实现上位机的适配。在EIPScanner的帮助文档中,提到了少许关于EIP协议的事情,若后续深入使用EIP,需要补充相关知识。

这里调试主要是得到源码、编译、跑example。example跑通的情况下再进行适配开发。

得到源码参考上述开源地址、

编码参考官方帮助文档、过程中若无Libgtest LibGlog等库,自行安装即可

经调试经验总结,例程建议按DiscoveryManagerExample、ExplicitMessagingExample、ImplicitMessagingExample的顺序测试。

3.2.1 获取从站信息例程

其中DiscoveryManagerExample通过一般的通讯方式,请求PLC的相关信息,放到device.identityObject结构体中,可自行添加打印,观察得到的数据。经调试后的代码如下:

int main() {
  Logger::setLogLevel(LogLevel::DEBUG);

#if OS_Windows
  WSADATA wsaData;
  int winsockStart = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (winsockStart != 0) {
    Logger(LogLevel::ERROR) << "Failed to start WinSock - error code: " << winsockStart;
    return EXIT_FAILURE;
  }
#endif

  DiscoveryManager discoveryManager("192.168.2.88", 0xAF12, std::chrono::seconds(1));
  auto devices = discoveryManager.discover();

  for (auto& device : devices) {
    Logger(LogLevel::INFO) << "Discovered device: "
      << device.identityObject.getProductName()
      << " with address " << device.socketAddress.toString()<<"VendorID:"<<device.identityObject.getVendorId()\
      <<"ClassID:"<<device.identityObject.getClassId()\
      <<"Numb:"<<device.identityObject.getSerialNumber()\
      <<"InsTance:"<<device.identityObject.getInstanceId();
  }

#if OS_Windows
  WSACleanup();
#endif

  return EXIT_SUCCESS;
}

代码运行打印的log如下:

[DEBUG] Opened UDP socket fd=3
[DEBUG] Close UDP socket fd=3
[INFO] Discovered device: AP700 Series PLC EIP Adapter with address 192.168.2.88:44818VendorID:1660ClassID:1Numb:1309027550InsTance:0

 

3.2.2 显式连接

其中,ExplicitMessagingExample的例程通过Forward Open的形式,与下位机建立CIP的连接。显试连接的调通是隐试连接的基础,后续用到的隐试连接同样按此流程进行。

显试连接可以理解为 消息请求与回答的应用场景 使用,适合于单次的通讯。在自研机械臂中,可以按此方式完成 通断使能、故障复位、版本号获取等通讯场景。

//未调通,暂不贴信息

3.2.3 隐式连接

其中ImplicitMessagingExample,为隐试信息的收发,对应与PLC中为生产者连接方式。

调试之处,对照着PLC的设置,并翻看了导出的EDS文件,寻找关键字,做对应适配,代码如下:

int main() {
  Logger::setLogLevel(LogLevel::DEBUG);

#if OS_Windows
  WSADATA wsaData;
  int winsockStart = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (winsockStart != 0) {
    Logger(LogLevel::ERROR) << "Failed to start WinSock - error code: " << winsockStart;
    return EXIT_FAILURE;
  }
#endif

  auto si = std::make_shared<SessionInfo>("192.168.2.88", 0xAF12);

  // Implicit messaging
  ConnectionManager connectionManager;

  ConnectionParameters parameters;
  parameters.connectionPath = {0x20, 0x04,0x24, 0x78, 0x2C, 0x64, 0x2C, 0x6E};  // config Assm151, output Assm150, intput Assm100
  parameters.o2tRealTimeFormat = true;
  parameters.originatorVendorId = 342;
  parameters.originatorSerialNumber = 32423;
  parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::P2P;
  parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
  parameters.t2oNetworkConnectionParams |= 4; //size of Assm100 =32
  parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::P2P;
  parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
  parameters.o2tNetworkConnectionParams |= 4; //size of Assm150 = 32

  parameters.originatorSerialNumber = 0x12345;
  parameters.o2tRPI = 1000000;
  parameters.t2oRPI = 1000000;
  parameters.transportTypeTrigger |= NetworkConnectionParams::CLASS1;

  auto io = connectionManager.forwardOpen(si, parameters);
  if (auto ptr = io.lock()) {
    ptr->setDataToSend(std::vector<uint8_t>(4));

    ptr->setReceiveDataListener([](auto realTimeHeader, auto sequence, auto data) {
      std::ostringstream ss;
      ss << "secNum=" << sequence << " data=";
      for (auto &byte : data) {
        ss << "[" << std::hex << (int) byte << "]";
      }

      Logger(LogLevel::INFO) << "Received: " << ss.str();
    });

    ptr->setCloseListener([]() {
      Logger(LogLevel::INFO) << "Closed";
    });
  }

  int count = 200;
  while (connectionManager.hasOpenConnections() && count-- > 0) {
    connectionManager.handleConnections(std::chrono::milliseconds(100));
  }

  connectionManager.forwardClose(si, io);

#if OS_Windows
  WSACleanup();
#endif

  return EXIT_SUCCESS;
}

跑不通,运行log如下:

//省略

后修改了各种参数,做了大量尝试与验证,均未跑通。

后续,在开源社区的issues部分,看大家是否有相同问题。看到默认为WORD类型(16bit),而PLC中为USINT类型(8bit)、在MessageRouter.h中,将use_8_bit_path_segments=false改为true,解决了报错数据长度不对问题.

 

在issues中看到反馈,有在西门子PLC上跑不通的问题,Issues连接如下:

https://github.com/nimbuscontrols/EIPScanner/issues/48

得到,通过WireShark的方式,抓帧分析错误原因。并进一步,在WIN上跑通的配置,抓正确帧。再换到Ubuntu上,抓错误帧。通过调整ubuntu配置,得到正确的帧,从而跑通了IO连接.调整后的代码如下:

int main() {
  Logger::setLogLevel(LogLevel::DEBUG);

#if OS_Windows
  WSADATA wsaData;
  int winsockStart = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (winsockStart != 0) {
    Logger(LogLevel::ERROR) << "Failed to start WinSock - error code: " << winsockStart;
    return EXIT_FAILURE;
  }
#endif
  //auto si = std::make_shared<SessionInfo>("172.28.1.3", 0xAF12);
  auto si = std::make_shared<SessionInfo>("192.168.2.88", 0xAF12);
  // Implicit messaging
  ConnectionManager connectionManager;

  ConnectionParameters parameters;
  //parameters.connectionPath = {0x20, 0x04,0x24, 151, 0x2C, 150, 0x2C, 100};  // config Assm151, output Assm150, intput Assm100
  parameters.connectionPath = {0x34,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20, 0x04,0x24, 0x01, 0x2C, 0x64, 0x2C, 0x6E};  
  parameters.o2tRealTimeFormat = true;
  parameters.t2oRealTimeFormat = true;
  parameters.originatorVendorId = 0xaa;
  //parameters.connectionTimeoutMultiplier=64;
  //parameters.o2tNetworkConnectionId=0x011764f7;
  //parameters.t2oNetworkConnectionId=0x011764f6;

  parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::P2P;
  parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
  parameters.t2oNetworkConnectionParams |= 0; //size of Assm100 =1

  parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::P2P;
  parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
  parameters.o2tNetworkConnectionParams |= 4; //size of Assm100 =1

  parameters.originatorSerialNumber = 0x12345;
  parameters.o2tRPI = 50000;
  parameters.t2oRPI = 50000;//50ms
  parameters.transportTypeTrigger |= NetworkConnectionParams::CLASS1;
  parameters.transportTypeTrigger |= NetworkConnectionParams::TRIG_CYCLIC;  

  auto io = connectionManager.forwardOpen(si, parameters);
  if (auto ptr = io.lock()) {
    ptr->setDataToSend(std::vector<uint8_t>(4));

    ptr->setReceiveDataListener([](auto realTimeHeader, auto sequence, auto data) {
      std::ostringstream ss;
      ss << "secNum=" << sequence << " data=";
      for (auto &byte : data) {
        ss << "[" << std::hex << (int) byte << "]";
      }

      Logger(LogLevel::INFO) << "Received: " << ss.str();
    });

    ptr->setCloseListener([]() {
      Logger(LogLevel::INFO) << "Closed";
    });
  }

  int count = 20000;
  while (connectionManager.hasOpenConnections() && count-- > 0) {
    //connectionManager.handleConnections(std::chrono::milliseconds(100));
    connectionManager.handleConnections(std::chrono::milliseconds(50));
  }

  connectionManager.forwardClose(si, io);

#if OS_Windows
  WSACleanup();
#endif

  return EXIT_SUCCESS;
}

系统输出的log如下:

//省略

 

 

 

此系列的b站视频见:

基于EIPScanner的Linux Ethernet/IP的多生产者连接及Tag读写实现分享(一 引言)_哔哩哔哩_bilibili

参考代码见:

咸鱼ID:tb764914262

 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐