基本的OpENer程式已經可以運行啦~真是可喜可賀!
/* 其實並沒有QAQQQQQQQQ */
接下來大家最常做的動作應該就是看懂範例程式main.c的架構!
愚鈍如我,一開始我覺得main.c裡面根本就沒有什麼好看的,給的東西少的讓人崩潰,但我就這樣仔細看…仔細看…仔細看……終於讓我在崩潰了兩天之後,看出了門道!!
讓原本已死心的我,在嘗試開始自己建立CIP Class的時候,發現了原來這份範例程式碼已經有建立了第一個!!
接下來就讓我來說說我看出來的心得,有錯誤歡迎指教>A<!
首先,找到OpENer的Target Stack:
http://eipstackgroup.github.io/OpENer/index.html
接著,找到Porting OpENer的部分點進去。
在Target Stack首頁直接拉下去,Further Topics的第一項,或著左手邊目錄的地方也找的到。
Startup Sequence:
第一個大標題,說明的是一個基本的EtherNet/IP Adapter要啟動所需要的步驟。
這5個步驟非常有助於理解main.c的程式碼!!
下面是我有加了一些printf,讓我理解這支程式現在到底在幹嘛的code:
/* 其實並沒有QAQQQQQQQQ */
接下來大家最常做的動作應該就是看懂範例程式main.c的架構!
愚鈍如我,一開始我覺得main.c裡面根本就沒有什麼好看的,給的東西少的讓人崩潰,但我就這樣仔細看…仔細看…仔細看……終於讓我在崩潰了兩天之後,看出了門道!!
讓原本已死心的我,在嘗試開始自己建立CIP Class的時候,發現了原來這份範例程式碼已經有建立了第一個!!
接下來就讓我來說說我看出來的心得,有錯誤歡迎指教>A<!
首先,找到OpENer的Target Stack:
http://eipstackgroup.github.io/OpENer/index.html
接著,找到Porting OpENer的部分點進去。
在Target Stack首頁直接拉下去,Further Topics的第一項,或著左手邊目錄的地方也找的到。
Startup Sequence:
第一個大標題,說明的是一個基本的EtherNet/IP Adapter要啟動所需要的步驟。
這5個步驟非常有助於理解main.c的程式碼!!
下面是我有加了一些printf,讓我理解這支程式現在到底在幹嘛的code:
/*******************************************************************************
* Copyright (c) 2009, Rockwell Automation, Inc.
* All rights reserved.
*
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "generic_networkhandler.h"
#include "opener_api.h"
#include "cipcommon.h"
#include "trace.h"
/******************************************************************************/
/** @brief Signal handler function for ending stack execution
*
* @param signal the signal we received
*/
void LeaveStack(int signal);
/*****************************************************************************/
/** @brief Flag indicating if the stack should end its execution
*/
int g_end_stack = 0;
/******************************************************************************/
int main(int argc, char *arg[]) {
EipUint8 my_mac_address[6];
EipUint16 unique_connection_id;
/* 判斷參數數量合不合法 */
if (argc != 12) {
printf("Wrong number of command line parameters!\n");
printf("The correct command line parameters are:\n");
printf(
"./OpENer ipaddress subnetmask gateway domainname hostaddress macaddress\n");
printf(
" e.g. ./OpENer 192.168.0.2 255.255.255.0 192.168.0.1 test.com testdevice 00 15 C5 BF D0 87\n");
exit(0);
} else {
/* 參數數量對了之後,開始建立Adapter */
/* fetch Internet address info from the platform */
/* Startup Sequence的第1點--
* Configure the network properties :
* EIP_STATUS ConfigureNetworkInterface(const char *ip_address, const char *subnet_mask, const char *gateway_address)
* void ConfigureMACAddress(const EIP_UINT8 *mac_address)
* void ConfigureDomainName(const char *domain_name)
* void ConfigureHostName(const char *host_name)
*/
/* setup the data of the network interface needed */
ConfigureNetworkInterface(arg[1], arg[2], arg[3]);
printf("IP:%s\nSubnetmask:%s\nGateway:%s\n",arg[1],arg[2],arg[3]);
printf("Setup network data success!\n");
printf("\n----\n\n");
ConfigureDomainName(arg[4]);
printf("Domain name:%s\n",arg[4]);
printf("Setup network domain name success!\n");
printf("\n----\n\n");
ConfigureHostName(arg[5]);
printf("Host name:%s\n",arg[5]);
printf("Setup network host name success!\n");
printf("\n----\n\n");
my_mac_address[0] = (EipUint8) strtoul(arg[6], NULL, 16);
my_mac_address[1] = (EipUint8) strtoul(arg[7], NULL, 16);
my_mac_address[2] = (EipUint8) strtoul(arg[8], NULL, 16);
my_mac_address[3] = (EipUint8) strtoul(arg[9], NULL, 16);
my_mac_address[4] = (EipUint8) strtoul(arg[10], NULL, 16);
my_mac_address[5] = (EipUint8) strtoul(arg[11], NULL, 16);
ConfigureMacAddress(my_mac_address);
printf("Mac address:%s %s %s %s %s %s\n",arg[5],arg[6],arg[7],arg[8],arg[9],arg[10]);
printf("Setup mac address success!\n");
printf("\n----\n\n");
}
/* Startup Sequence的第2點--
* Set the device's serial number :
* void setDeviceSerialNumber(EIP_UINT32 serial_number)
*/
/*for a real device the serial number should be unique per device */
SetDeviceSerialNumber(123456789);
printf("Device serial number:123456789\n");
printf("Setup serial number success!\n");
printf("\n----\n\n");
/* nUniqueConnectionID should be sufficiently random or incremented and stored
* in non-volatile memory each time the device boots.
*/
unique_connection_id = rand();
/* Startup Sequence的第3點--
* Initialize OpENer :
* CipStackInit(EIP_UINT16 unique_connection_id)
*/
/* Startup Sequence的第4點--
* Create Application Specific CIP Objects :
* Within the call-back function ApplicationInitialization(void)
* or
* after CipStackInit(void) has finished
*/
/* 這個function裡面建立了第一個Object!下面會獨立拉開來說明!
* 除了初始化OpENer Structure,裡面也呼叫了ApplicationInitialization(void)
* 如果想要建立自己特定應用的CIP Class,
* 可以在CipStackInit(void)之後建立,或著在ApplicationInitialization(void)裡面進行創建
* p.s ApplicationInitialization(void) :
* 該函數可以在/source/src/ports/POSIX/sample_application資料夾下的sampleapplication.c進行修改
*/
/* Setup the CIP Layer */
CipStackInit(unique_connection_id);
printf("Connection ID:%d\n",unique_connection_id);
printf("Setup connection ID success!\n");
printf("\n----\n\n");
/* 另一個可以創建自己特定應用CIP Class的位置 */
/* Can create object here */
/* Startup Sequence的第5點--
* Setup the listening TCP and UDP port :
* configure your network library so that TCP and UDP messages on port will be received
*/
/* TCP和UDP的socket設置在NetworkHandlerInitialize()進行處理
* OpENer的初始port是設爲44818,即0xAF12,通常用來接收EtherNet/IP的explicit messages */
/* Setup Network Handles */
if ( kEipStatusOk == NetworkHandlerInitialize() ) {
g_end_stack = 0;
#ifndef WIN32
/* register for closing signals so that we can trigger the stack to end */
signal(SIGHUP, LeaveStack);
#endif
/* 我自己設來顯示進入while迴圈用的參數 */
int flag = 1;
/* The event loop. Put other processing you need done continualy in here */
while (1 != g_end_stack) {
if(flag){
printf("Here is ready to do job!\n\n");
flag = 0;
}
if ( kEipStatusOk != NetworkHandlerProcessOnce() ) {
OPENER_TRACE_ERR("Error in NetworkHandler loop! Exiting OpENer!\n");
break;
}
}
/* clean up network state */
NetworkHandlerFinish();
}
/* close remaining sessions and connections, cleanup used data */
ShutdownCipStack();
return -1;
}
void LeaveStack(int signal) {
(void) signal; /* kill unused parameter warning */
OPENER_TRACE_STATE("got signal HUP\n");
g_end_stack = 1;
}
接下來是比較重要的地方,如果想自己創建CIP Class的話,這邊可以當作一個範本~
Function:
void CipStackInit(const EipUint16 unique_connection_id)
Location:/source/src/cip/cipcommon.c
void CipStackInit(const EipUint16 unique_connection_id) {
EncapsulationInit();
/* MessageRouter是第一個建立的CIP Object!
* CipMessageRouterInit()裡面就是基本的建立CIP Obect的流程,不過是比較簡陋的版本XD
*/
/* The message router is the first CIP object be initialized!!! */
EipStatus eip_status = CipMessageRouterInit();
OPENER_ASSERT(kEipStatusOk == eip_status);
/* 下面幾個function是用來初始化CIP Stack的函數~
* 也就是OpENer建立時,一些default的CIP Object!
* 算是幾個也能當作創建CIP Object的範例
*/
eip_status = CipIdentityInit();
OPENER_ASSERT(kEipStatusOk == eip_status);
eip_status = CipTcpIpInterfaceInit();
OPENER_ASSERT(kEipStatusOk == eip_status);
eip_status = CipEthernetLinkInit();
OPENER_ASSERT(kEipStatusOk == eip_status);
eip_status = ConnectionManagerInit(unique_connection_id);
OPENER_ASSERT(kEipStatusOk == eip_status);
eip_status = CipAssemblyInitialize();
OPENER_ASSERT(kEipStatusOk == eip_status);
/* 這裡就是上面說的可以實作自己CIP Object的地方
* 不過我還沒有看很懂,所以就先放置了QQ
*/
/* the application has to be initialized at last */
eip_status = ApplicationInitialization();
OPENER_ASSERT(kEipStatusOk == eip_status);
/* Shut up compiler warning with traces disabled */
(void) eip_status;
}
好了,過度的function先看過去就好O3O
主要學習建立CIP Object的內容要看下面這個函數!
Function:
EipStatus CipMessageRouterInit()
Location:/source/src/cip/cipmessagerouter.c
EipStatus CipMessageRouterInit() {
/* 建立一個message router的CIP Class */
/*
* CipClass* CreateCipClass ( const EipUint32 class_id,
* const int number_of_class_attributes,
* const EipUint32 highest_class_attribute_number,
* const int number_of_class_services,
* const int number_of_instance_attributes,
* const EipUint32 highest_instance_attribute_number,
* const int number_of_instance_services,
* const int number_of_instances,
* char *name,
* const EipUint16 revision,
* void(*)(CipClass *) InitializeCipClass )
*/
CipClass *message_router = CreateCipClass(kCipMessageRouterClassCode, /* class ID : 給class一個編號 */
0, /* # of class attributes : class attribute初始的數量,設爲0即一開始沒有初始化class attribute */
7, /* # highest class attribute number : class attribute能夠設置的最大編號 */
2, /* # of class services : 初始化的class service數量 */
0, /* # of instance attributes : 初始化的instance attribut數量,設爲0即一開始沒有初始化instance attribute */
4, /* # highest instance attribute number : instance attribute能夠設置的最大編號 */
1, /* # of instance services : 初始化的instance service數量 */
1, /* # of instances : instance初始化的數量 */
"message router", /* class name : 給class設置一個名字 */
1, /* # class revision : 這個參數沒搞懂過QQ */
NULL); /* # function pointer for initialization : 指定一個初始化的function
* 如果設爲NULL,會自動加入7個class attribute
* 推測是初始化class和instance之後,可以設置一個function來針對兩者進行詳細設置
* 範本可以參考放在/source/src/cip下的cipidentity.c
* void InitializeCipIdentity(CipClass*)是個專門為創建identity這個class寫的函數
* 內容是寫一些特別要放在identity class下的attribute,然後塞給identity class這樣
*/
/* 判斷Class是否create成功 */
if (NULL == message_router) {
/* create失敗的話,就是CIP Object初始化失敗了 */
return kEipStatusError;
}
/* Class初始化成功後,設置Class Service */
/* void InsertService ( const CipClass *const cip_class_to_add_service,
* const EipUint8 service_code,
* const CipServiceFunction service_function,
* char *const service_name )
*/
InsertService(message_router, /* 要加入此Service的Class */
kGetAttributeSingle, /* 給此Service一個編號 */
&GetAttributeSingle, /* 實現該Service的function */
"GetAttributeSingle"); /* 給該Service設置的名字 */
/* g_message_router_response是在cipmessagerouter.c全域定義的一個變數
* 目前推測是用來回應EtherNet/IP message的資料結構
* 把參數初始化,並設定好裝data的buffer:g_message_data_reply_buffer
*/
/* reserved for future use -> set to zero */
g_message_router_response.reserved = 0;
g_message_router_response.data = g_message_data_reply_buffer; /* set reply buffer, using a fixed buffer (about 100 bytes) */
/* 全部都初始化好之後,回傳OK的EtherNet/IP State訊號 */
return kEipStatusOk;
}
認真的扒開這個main.c,對於建立一個CIP Object終於有了一個比較確切的概念了!!ε٩(๑> ₃ <)۶з
接下來,就以這個爲基礎,開始嘗試建立一個自己的CIP Object吧!!!
感謝你的文章
回覆刪除沒有你的文章入手時間會再拉長很多
我遇到了一個奇怪的現象
向你請教
作業系統+環境 : WIN10+mingw
local ip:192.168.1.109
當我按照說明文件執行
OpENer 192.168.1.109
總是回報 Network interface 192.168.1.109 not found.
在一陣胡亂嘗試後
執行 OpENer 1 成功運行了
且利用EIP explorer -> open interface with 127.0.0.1
終於成功抓到裝置了
接下來繼續嘗試直到
OpENer 10 再次成功運行了
並利用EIP explorer -> open interface with 192.168.1.109
又再次成功抓到裝置的資訊
不曉得你知不知道這是為什麼?
謝謝
沒有在win10系統下安裝過這個,這幾天嘗試了一下mingw,被win10的網路問題搞的頭疼,make老是卡在很多相關參數宣告找不到,所以決定放棄了XD
刪除如果你用mingw編譯成功的話,執行的程式碼就是放在\source\src\ports\MINGW裡的main.c編出來的執行檔
在68行那裡是判斷ip的地方,從這邊入手,可以找到為什麼ip這樣判斷不合法
我認為問題應該在於windows系統和linux系統在ip函數庫這塊定義不一樣的緣故,windows有另外自己定義的函數庫跟參數,這部分windows有說明書可以看,這邊給你附上:https://docs.microsoft.com/zh-tw/windows/win32/iphlp/ip-helper-start-page
個人覺得linux系統就比較好懂,反正就是數bit,比較直觀XDDD
而windows則是把它們宣告成了各種參數,要搭著說明書自己研究一下!
最後最後,你是用Visual Studio運行的嗎?因為你的參數設置看起來比較像是用Visual Studio運行下的參數XD
mingw是把library裝好,設置成環境變數後,透過windows的命令提示字元視窗直接編譯運行這樣,執行方式不一樣,你可以再確認一下README~
我是根據Opener說明文件
回覆刪除使用他提供的批次檔
setup_mingw.bat "-DOpENer_TRACES:BOOL=TRUE"
就可以成功了
我也嘗試過自己建立專案來編譯
但是老卡在一個關鍵字 RESTRICT
opener程式內適用大寫的RESTRICT
但是我查到的C 編譯器定義這關鍵字時 都是用小寫的
所以老是編譯不了
只有使用他提供的批次檔才能成功編譯
這樣的缺點就是無法在IDE的環境下偵錯
只能用fprintf慢慢偵錯
謝謝你的回覆