據(jù)采集Demo用C#實(shí)現(xiàn)全流程詳解:EZSocket通信與宏變量讀取)
簡(jiǎn)介C#數(shù)據(jù)采集Demo面向三菱CNC系統(tǒng)專為機(jī)床自動(dòng)化工程師、上位機(jī)開(kāi)發(fā)者和工業(yè)物聯(lián)網(wǎng)從業(yè)者設(shè)計(jì)旨在解決機(jī)床實(shí)時(shí)監(jiān)控、加工參數(shù)讀取、設(shè)備狀態(tài)追蹤及故障日志獲取等工業(yè)數(shù)據(jù)采集難題。壓縮包約3.52MB內(nèi)部包含PDF格式的參考手冊(cè)和三菱CNC模擬環(huán)境SimCNC的源碼目錄參考手冊(cè)用于查詢通訊協(xié)議、寄存器地址與數(shù)據(jù)格式SimCNC則支持在無(wú)實(shí)體機(jī)床的情況下搭建仿真調(diào)試環(huán)境方便反復(fù)驗(yàn)證采集邏輯。Demo源碼演示了配合A2.exe通訊軟件與CNC建立連接通過(guò)C#實(shí)現(xiàn)對(duì)多種型號(hào)三菱機(jī)臺(tái)的數(shù)據(jù)讀取并將采集功能封裝為獨(dú)立軟件或插件可靈活接入現(xiàn)有產(chǎn)線信息化系統(tǒng)。整體代碼結(jié)構(gòu)清晰包含連接建立、數(shù)據(jù)解析、異常處理等關(guān)鍵模塊是學(xué)習(xí)工業(yè)通訊編程的良好范本。已有620人學(xué)習(xí)下載適合需要快速落地三菱CNC數(shù)采項(xiàng)目或進(jìn)行技術(shù)預(yù)研的開(kāi)發(fā)者參考能顯著縮短環(huán)境搭建與排錯(cuò)時(shí)間。1. 三菱CNC數(shù)據(jù)采集Demo先想清楚要采什么別急著開(kāi)VS車間里一臺(tái)三菱加工中心正在跑活操作員盯著控制面板你在辦公室電腦前想看到它主軸轉(zhuǎn)速、當(dāng)前坐標(biāo)、有沒(méi)有報(bào)警——這就是三菱CNC數(shù)據(jù)采集Demo附C#源碼這個(gè)標(biāo)題真正要解決的事。它不靠串口線不靠人抄屏而是走CNC自帶的以太網(wǎng)口用C#把宏變量、坐標(biāo)、報(bào)警號(hào)實(shí)時(shí)拉回來(lái)。反直覺(jué)的地方在于新手最想要的往往是能把數(shù)讀出來(lái)的激動(dòng)感但真正決定這活兒成不成的是動(dòng)手之前先想清楚要采哪幾個(gè)地址、用什么協(xié)議、幀怎么解析。常見(jiàn)做法是走三菱的EZSocket中間件DLL一引C#里P/Invoke一調(diào)十幾行代碼就能連上但參數(shù)不對(duì)時(shí)一個(gè)Open函數(shù)能卡你一下午。這個(gè)方向適合做設(shè)備聯(lián)網(wǎng)、MES對(duì)接、機(jī)加工自動(dòng)化的工程師也適合剛?cè)胄邢肽靡慌_(tái)真設(shè)備練手的同學(xué)??赐赀@篇文章你至少能把這套Demo跑通并且知道坑在哪。2. 準(zhǔn)備工作三菱CNC數(shù)據(jù)采集Demo的網(wǎng)絡(luò)前提與協(xié)議選型2.1 網(wǎng)口通信是前提串口方案到了Demo階段就該放棄很多老工程師的第一反應(yīng)是“CNC后面不是有RS232口嗎直接串口讀不就行了”。這個(gè)思路在二十年前沒(méi)問(wèn)題但現(xiàn)在做數(shù)據(jù)采集串口的三個(gè)短板非常明顯一是傳輸速度慢115200波特率下刷新一次狀態(tài)要幾百毫秒別說(shuō)做高頻采集二是好多老舊串口方案只能單向輸出想寫宏變量回機(jī)臺(tái)很難三是串口線超過(guò)15米信號(hào)就開(kāi)始飄車間現(xiàn)場(chǎng)拉線很不現(xiàn)實(shí)。所以這個(gè)Demo的正確起點(diǎn)是以太網(wǎng)口。三菱的中高端系統(tǒng)比如M70、M700、M80系列標(biāo)準(zhǔn)配置里都帶以太網(wǎng)口參數(shù)里面設(shè)好IP地址、子網(wǎng)掩碼之后上位機(jī)直接用網(wǎng)線連交換機(jī)就能訪問(wèn)。通信鏈路的示意圖其實(shí)很簡(jiǎn)單C#程序通過(guò)TCP協(xié)議發(fā)一個(gè)ASCII字符串命令幀給CNCCNC回一個(gè)帶數(shù)據(jù)的響應(yīng)幀程序把幀解析出來(lái)就完事了。這里有個(gè)關(guān)鍵選型點(diǎn)直接用TCP發(fā)MC協(xié)議幀雖然可行但工程上沒(méi)人這么干因?yàn)閹^、校驗(yàn)、分包都得自己拼出錯(cuò)率太高。業(yè)界通用做法是安裝三菱的EZSocket通信庫(kù)它本質(zhì)上是把MC協(xié)議封裝成了C風(fēng)格函數(shù)提供固定接口往上位機(jī)這邊暴露動(dòng)態(tài)鏈接庫(kù)C#項(xiàng)目里聲明一下就能調(diào)用。Demo階段選它省掉的是協(xié)議拼幀的麻煩留下的是數(shù)據(jù)解析的核心邏輯。2.2 訪問(wèn)對(duì)象A/B/C/DDemo該用哪一個(gè)通道EZSocket里有一個(gè)概念叫“訪問(wèn)對(duì)象”英文叫Access Target官方分為A、B、C、D四種。A類走的是三菱PLC的MC協(xié)議用來(lái)讀PLC軟元件B類是Windows下和PC內(nèi)部數(shù)據(jù)交換用的C類是訪問(wèn)CNC數(shù)值數(shù)據(jù)的通道宏變量、坐標(biāo)、刀補(bǔ)、報(bào)警都?xì)w它管D類是訪問(wèn)機(jī)床側(cè)的PLC數(shù)據(jù)比如IO信號(hào)、繼電器狀態(tài)。寫成表格更直觀訪問(wèn)對(duì)象面向的數(shù)據(jù)Demo里有沒(méi)有用APLC軟元件D、M、Y等一般不用BWindows環(huán)境內(nèi)部通信不用CCNC數(shù)值數(shù)據(jù)宏變量、坐標(biāo)、報(bào)警核心通道D機(jī)床側(cè)PLC數(shù)據(jù)IO、繼電器按需C#做CNC采集絕大多數(shù)需求都集中在C類上。你在MES里看到的主軸轉(zhuǎn)速當(dāng)前X軸坐標(biāo)報(bào)警號(hào)全部通過(guò)C類讀出來(lái)。有些車間還想看機(jī)床門有沒(méi)有關(guān)、紅燈有沒(méi)有亮這些信號(hào)在D類里但那是后話Demo階段先把C類吃透。有個(gè)細(xì)節(jié)要注意EZSocket的DLL同時(shí)支持A類訪問(wèn)所以如果你這臺(tái)CNC的PLC里存了工件計(jì)數(shù)之類的數(shù)據(jù)也可以用同一套代碼去讀只需要把打開(kāi)連接時(shí)的訪問(wèn)對(duì)象類型改一下。Demo階段不建議一上來(lái)就通吃先把C類讀通再往D類擴(kuò)展。2.3 CNC側(cè)參數(shù)核對(duì)IP、端口、系統(tǒng)版本差異連不上CNC的時(shí)候八成不是代碼的問(wèn)題而是CNC側(cè)的網(wǎng)絡(luò)參數(shù)沒(méi)核對(duì)。三菱系統(tǒng)里以太網(wǎng)參數(shù)一般在維護(hù)菜單的以太網(wǎng)設(shè)定或者網(wǎng)絡(luò)設(shè)定頁(yè)面里。需要確認(rèn)四項(xiàng)參數(shù)典型值說(shuō)明IP地址192.168.1.50和上位機(jī)在同一網(wǎng)段子網(wǎng)掩碼255.255.255.0不跨網(wǎng)段就夠用端口號(hào)6000不同系統(tǒng)版本可能有差異通信協(xié)議TCP打開(kāi)對(duì)應(yīng)通道端口號(hào)是個(gè)血淚高發(fā)區(qū)。部分老系統(tǒng)默認(rèn)用6000部分系統(tǒng)在參數(shù)里單獨(dú)開(kāi)了一個(gè)端口還有的系統(tǒng)要同時(shí)開(kāi)UDP和TCP兩個(gè)通道。我一般會(huì)在CNC旁邊先用筆記本電腦ping一下IP通了再跑Demoping不通就別折騰代碼先查網(wǎng)線和IP段。還有兩個(gè)前置條件容易被忽略一是EZSocket服務(wù)器端程序要和系統(tǒng)匹配二是CNC側(cè)參數(shù)里的傳輸協(xié)議要選成允許外部設(shè)備訪問(wèn)。某些系統(tǒng)出廠時(shí)默認(rèn)關(guān)閉了外部訪問(wèn)需要到維護(hù)模式里打開(kāi)。這些操作需要操作員權(quán)限做Demo之前最好和車間師傅打個(gè)招呼別自己偷摸改參數(shù)改完機(jī)臺(tái)報(bào)警就麻煩了。3. 核心代碼C#讀三菱CNC宏變量、坐標(biāo)與報(bào)警3.1 創(chuàng)建一個(gè)控制臺(tái)Demo把EZSocket.dll引進(jìn)來(lái)拿到三菱EZSocket安裝包之后安裝目錄下會(huì)有一個(gè)動(dòng)態(tài)鏈接庫(kù)名字一般是EZSocket.dll。C#這邊不能直接添加引用因?yàn)樗荂風(fēng)格導(dǎo)出函數(shù)不是托管DLL正確做法是用DllImport聲明外部函數(shù)。先建一個(gè)控制臺(tái)項(xiàng)目平臺(tái)目標(biāo)改成x86然后寫一個(gè)靜態(tài)類來(lái)包裝接口using System; using System.Runtime.InteropServices; public static class EZSocketWrapper { // 聲明三個(gè)核心函數(shù)打開(kāi)、讀取、關(guān)閉 [DllImport(EZSocket.dll, EntryPoint EZSocket_Open)] public static extern int Open( IntPtr[, ,] pCNC, // 連接句柄三維數(shù)組 int nType, // 訪問(wèn)對(duì)象類型2表示C類CNC數(shù)據(jù) int nOpen, // 通信方式1表示TCP string pIPAddr, // CN C的IP地址 string pPort, // 端口號(hào) int nTimeOut); // 超時(shí)時(shí)間毫秒 [DllImport(EZSocket.dll, EntryPoint EZSocket_Read)] public static extern int Read( IntPtr[, ,] pCNC, // 連接句柄 int nType, // 訪問(wèn)對(duì)象類型 string pReadBuf, // 讀命令幀 ref string pRetBuf, // 返回?cái)?shù)據(jù)緩沖區(qū) string pErrMsg); // 錯(cuò)誤信息輸出 [DllImport(EZSocket.dll, EntryPoint EZSocket_Close)] public static extern int Close( IntPtr[, ,] pCNC, int nType); }這段聲明里最容易栽跟頭的是句柄類型。EZSocket的手冊(cè)上寫的是三維指針數(shù)組C#這邊的對(duì)應(yīng)寫法就是IntPtr的三維數(shù)組初始化時(shí)每個(gè)維度給3就夠了。nType傳2代表C類訪問(wèn)nOpen傳1代表走TCP。端口參數(shù)是字符串不是整數(shù)這是很多人第一次調(diào)用時(shí)報(bào)錯(cuò)的原因。超時(shí)時(shí)間建議給3000毫秒太短在車間網(wǎng)絡(luò)抖動(dòng)時(shí)容易誤報(bào)失敗太長(zhǎng)則程序卡死在那里。注意DLL的位數(shù)必須和編譯目標(biāo)一致。EZSocket.dll在多數(shù)版本里是32位的所以項(xiàng)目平臺(tái)目標(biāo)一定要選x86否則運(yùn)行到Open那行就拋BadImageFormatException這個(gè)在第5章避坑里展開(kāi)。3.2 連接到CNCOpen和Close的寫法連接這塊的套路是固定的先創(chuàng)建句柄數(shù)組調(diào)Open判斷返回值用完調(diào)Close。下面是完整的最小連接代碼static void Main(string[] args) { // 連接句柄三維數(shù)組結(jié)構(gòu) IntPtr[,,] pCNC new IntPtr[3, 3, 3]; // 打開(kāi)C類訪問(wèn)TCP方式連192.168.1.50的6000端口 int ret EZSocketWrapper.Open( pCNC, 2, 1, 192.168.1.50, 6000, 3000); if (ret 0) { Console.WriteLine(連接成功); // 在這里執(zhí)行讀取操作 // ... EZSocketWrapper.Close(pCNC, 2); } else { Console.WriteLine(連接失敗錯(cuò)誤碼 ret); } }返回值ret為0表示成功非0為失敗。這一步很多人第一次跑通后會(huì)特別興奮但別高興太早——連接成功只代表TCP鏈路通了真正讀數(shù)據(jù)時(shí)的幀格式錯(cuò)一丁點(diǎn)Read照樣返回失敗。另外Close操作不能省有的現(xiàn)場(chǎng)出現(xiàn)過(guò)程序崩潰后CNC側(cè)連接資源沒(méi)釋放導(dǎo)致后續(xù)無(wú)法再次連接的情況雖然重啟機(jī)臺(tái)能恢復(fù)但車間里重啟設(shè)備你可得等操作員排完當(dāng)前工件。3.3 讀宏變量#100的實(shí)時(shí)值怎么取回來(lái)三菱系統(tǒng)的宏變量是機(jī)臺(tái)邏輯里最常用的數(shù)據(jù)區(qū)比如把當(dāng)前工件計(jì)數(shù)寫到#100把主軸倍率寫到#101MES要的就是這些。讀取宏變量用到EZSocket_Read命令幀格式我一般按R地址長(zhǎng)度來(lái)拼static void ReadMacro(IntPtr[,,] pCNC, int macroNum, out float value) { // 拼讀命令幀讀#100就填 R0,100,1 string cmd R0, macroNum ,1; string retBuf ; string errMsg ; int ret EZSocketWrapper.Read(pCNC, 2, cmd, ref retBuf, ref errMsg); if (ret 0) { // 返回?cái)?shù)據(jù)從第6個(gè)字符開(kāi)始是有效數(shù)據(jù)區(qū) string hexData retBuf.Substring(5); // 三菱返回的是倒序十六進(jìn)制字符串要反轉(zhuǎn)字節(jié)再轉(zhuǎn)float byte[] bytes HexToBytes(hexData); Array.Reverse(bytes); value BitConverter.ToSingle(bytes, 0); Console.WriteLine(# macroNum value); } else { Console.WriteLine(讀取失敗 errMsg); value 0f; } } static byte[] HexToBytes(string hex) { int length hex.Length / 2; byte[] result new byte[length]; for (int i 0; i length; i) result[i] Convert.ToByte(hex.Substring(i * 2, 2), 16); return result; }這里有個(gè)工藝問(wèn)題CNC讀回來(lái)的數(shù)據(jù)是十六進(jìn)制字符串而且是倒序字節(jié)序。比如浮點(diǎn)數(shù)1.5在內(nèi)存里是3F C0 00 00三菱回包里往往是0000C03F必須反轉(zhuǎn)之后用BitConverter轉(zhuǎn)float。這個(gè)字節(jié)序坑每一臺(tái)新機(jī)型都得試一次建議把轉(zhuǎn)出來(lái)的值和面板上顯示的值對(duì)一下對(duì)不上就交換一下高低字節(jié)順序。宏變量號(hào)本身也有講究公共變量#100到#149一般默認(rèn)為空變量區(qū)可以隨便用#150到#199在某些系統(tǒng)里是模擬輸入通道數(shù)據(jù)#500到#999是保持型變量斷電不丟。讀#500之前先確認(rèn)一下機(jī)床廠有沒(méi)有把這部分區(qū)域分配給專屬功能避免讀到無(wú)意義數(shù)據(jù)還當(dāng)成寶貝存進(jìn)數(shù)據(jù)庫(kù)。3.4 讀坐標(biāo)和報(bào)警把機(jī)臺(tái)狀態(tài)拼出來(lái)宏變量能解決數(shù)據(jù)交換但MES上層更關(guān)心的是坐標(biāo)和報(bào)警。讀取絕對(duì)坐標(biāo)和報(bào)警號(hào)用到的命令幀前綴分別是E和A下面是讀絕對(duì)坐標(biāo)和報(bào)警的核心片段static void ReadPositionAndAlarm(IntPtr[,,] pCNC) { string posBuf ; string almBuf ; string errMsg ; // 讀絕對(duì)坐標(biāo) int posRet EZSocketWrapper.Read(pCNC, 2, E, ref posBuf, ref errMsg); if (posRet 0 posBuf.Length 61) { // 第1~3位是附加信息每個(gè)坐標(biāo)占6個(gè)字節(jié) string xHex posBuf.Substring(6, 12); string yHex posBuf.Substring(18, 12); string zHex posBuf.Substring(30, 12); // 轉(zhuǎn)字節(jié)再反轉(zhuǎn)再轉(zhuǎn)float邏輯同上 float x ConvertHexToFloat(xHex); float y ConvertHexToFloat(yHex); float z ConvertHexToFloat(zHex); Console.WriteLine($X:{x:F3} Y:{y:F3} Z:{z:F3}); } // 讀報(bào)警號(hào) int almRet EZSocketWrapper.Read(pCNC, 2, A, ref almBuf, ref errMsg); if (almRet 0 almBuf.Length 9) { // 報(bào)警號(hào)也是倒序轉(zhuǎn)int string alarmHex almBuf.Substring(5, 4); byte[] bytes HexToBytes(alarmHex); Array.Reverse(bytes); int alarmCode BitConverter.ToInt16(bytes, 0); if (alarmCode ! 0) Console.WriteLine(當(dāng)前報(bào)警號(hào) alarmCode); else Console.WriteLine(無(wú)報(bào)警); } }坐標(biāo)讀取返回的幀長(zhǎng)度是固定的每個(gè)軸占6個(gè)字節(jié)十六進(jìn)制排列順序是X、Y、Z有些系統(tǒng)還帶A軸、B軸長(zhǎng)度就不一樣解析前先判斷返回幀長(zhǎng)度能省不少麻煩。報(bào)警讀取時(shí)要注意報(bào)警碼按有符號(hào)短整型解析負(fù)號(hào)代表報(bào)警級(jí)別。讀報(bào)警還分NC報(bào)警和機(jī)床報(bào)警A命令返回的是系統(tǒng)報(bào)警機(jī)床報(bào)警走D類訪問(wèn)做Demo時(shí)別把兩者混在一起。一個(gè)實(shí)用技巧是把坐標(biāo)和宏變量的讀取放在同一個(gè)循環(huán)里每隔200到500毫秒刷一次屏。刷新太快沒(méi)必要太慢又看不出實(shí)時(shí)效果車間里大部分采集需求到500毫秒級(jí)別的刷新率已經(jīng)足夠。4. 參數(shù)與內(nèi)存映射宏變量、公共變量和回復(fù)幀的邊界4.1 三菱宏變量的分區(qū)公共變量、保持型變量和刀補(bǔ)拿到一套Demo代碼之后最先要問(wèn)的問(wèn)題是我該讀哪些地址。三菱CNC的宏變量地址分布有規(guī)律可循但不同系統(tǒng)之間有一點(diǎn)差異常用分區(qū)是這樣的變量號(hào)范圍類型Demo應(yīng)用場(chǎng)景#100 ~ #149空公共變量跨程序傳遞數(shù)據(jù)#150 ~ #199部分系統(tǒng)為模擬量輸入讀主軸電流等模擬量#500 ~ #999保持型變量工件計(jì)數(shù)、生產(chǎn)統(tǒng)計(jì)#2000 ~ #2199刀補(bǔ)變量部分系統(tǒng)刀具磨損管理#3000 ~ #3999系統(tǒng)設(shè)定相關(guān)一般不碰Demo階段建議優(yōu)先讀#100區(qū)間和#500區(qū)間。#100區(qū)間斷電會(huì)清零適合讀當(dāng)班產(chǎn)量這類臨時(shí)數(shù)據(jù)#500區(qū)間是斷電保持的適合存班長(zhǎng)累計(jì)數(shù)。很多工廠的做法是讓PLC在換班時(shí)把#500里的值搬運(yùn)到另一個(gè)地址上位機(jī)只負(fù)責(zé)讀不負(fù)責(zé)寫。刀補(bǔ)的地址范圍需要重點(diǎn)提示不同系統(tǒng)版本對(duì)#2000系列的映射并不一致有的系統(tǒng)里#2001對(duì)應(yīng)1號(hào)刀的X向刀補(bǔ)有的系統(tǒng)里卻跳過(guò)了部分保留段。讀取刀補(bǔ)之前找操作員確認(rèn)一下機(jī)床廠的宏變量分配表或者干脆用機(jī)臺(tái)面板手動(dòng)改一個(gè)已知值再讀出來(lái)驗(yàn)證——這是最樸素的測(cè)試方法。4.2 回復(fù)幀的字節(jié)對(duì)齊和大小端坑EZSocket返回的數(shù)據(jù)區(qū)是十六進(jìn)制字符串規(guī)則是所有數(shù)據(jù)都按字倒序。這里說(shuō)的字倒序不是字節(jié)倒序而是兩個(gè)字節(jié)一組顛倒。舉例說(shuō)明數(shù)據(jù)區(qū)里如果收到3412實(shí)際值應(yīng)該是0x1234。浮點(diǎn)數(shù)則四個(gè)字節(jié)整體反轉(zhuǎn)前面代碼里用了Array.Reverse其實(shí)就是把大小端問(wèn)題用最簡(jiǎn)單的方式抹平了。解析時(shí)的另一層坑在于命令幀和數(shù)據(jù)幀之間可能帶空格或控制符。不同版本EZSocket返回的幀長(zhǎng)度有細(xì)微差異有的帶起始符有的不帶。我建議在寫解析邏輯之前先做一步打印裸包操作// 調(diào)試用把Read返回的原始幀原樣打印出來(lái) Console.WriteLine(RAW:[ retBuf ] len retBuf.Length);把這行加到Demo里跑一次對(duì)照三菱手冊(cè)里的幀結(jié)構(gòu)看一遍再寫Substring的截取邏輯比對(duì)著網(wǎng)上抄解析代碼靠譜得多。不同系統(tǒng)的返回幀長(zhǎng)度不一樣在M70上試好的偏移量拿到M80上可能就得加一位。還有一個(gè)小提醒全部用Substring截取十六進(jìn)制字符串看起來(lái)簡(jiǎn)單但在解析多個(gè)軸坐標(biāo)時(shí)容易數(shù)錯(cuò)位數(shù)。碰到解析結(jié)果數(shù)值怪異的情況先數(shù)一下每個(gè)字符的下標(biāo)用調(diào)試器逐幀看不要靠猜。4.3 采集頻率與系統(tǒng)負(fù)荷別把Demo寫成死循環(huán)數(shù)據(jù)采集最常見(jiàn)的失控寫法是while(true)里不加延時(shí)CPU占滿不說(shuō)CNC側(cè)的EZSocket服務(wù)也會(huì)被高頻請(qǐng)求拖累。三菱官方對(duì)采集頻率沒(méi)有硬性指標(biāo)但業(yè)界經(jīng)驗(yàn)是狀態(tài)數(shù)據(jù)坐標(biāo)、轉(zhuǎn)速500毫秒刷一次報(bào)警數(shù)據(jù)可以做到200毫秒甚至事件觸發(fā)產(chǎn)量數(shù)據(jù)按分鐘或按工件觸發(fā)即可。一個(gè)推薦的安全采集框架是這樣的// 用定時(shí)器代替死循環(huán)避免阻塞UI線程 System.Timers.Timer timer new System.Timers.Timer(); timer.Interval 500; // 500毫秒刷一次 timer.Elapsed (sender, e) { ReadPositionAndAlarm(pCNC); ReadMacro(pCNC, 100, out float count); }; timer.Start();注意這里有個(gè)隱患Timer觸發(fā)的事件如果在上一輪讀取還沒(méi)結(jié)束時(shí)又進(jìn)來(lái)就會(huì)導(dǎo)致消息堆積。保險(xiǎn)做法是加一個(gè)bool鎖標(biāo)志讀取中則跳過(guò)本次觸發(fā)。Demo階段可以不考慮并發(fā)但往生產(chǎn)走這一步是必須的。另外不要同一時(shí)間開(kāi)多個(gè)線程去調(diào)EZSocket_ReadDLL內(nèi)部不是線程安全的。多個(gè)采集線程同時(shí)調(diào)用同一個(gè)連接句柄輕則返回?cái)?shù)據(jù)錯(cuò)亂重則直接讓連接斷掉。需要采集多組數(shù)據(jù)時(shí)串行讀別并行讀。5. 三菱CNC采集Demo的常見(jiàn)踩坑現(xiàn)象、原因與解決5.1 Open返回失敗但Ping得通CNC現(xiàn)象上位機(jī)能ping通CNC的IP但EZSocket_Open返回非0讀取完全無(wú)法進(jìn)行。原因TCP鏈路通不代表CNC側(cè)的EZSocket服務(wù)在監(jiān)聽(tīng)。最常見(jiàn)的情況有三種CNC系統(tǒng)參數(shù)里沒(méi)打開(kāi)外部訪問(wèn)權(quán)限端口號(hào)和實(shí)際監(jiān)聽(tīng)端口不一致訪問(wèn)對(duì)象類型選錯(cuò)了類型比如C類通道沒(méi)開(kāi)但A類通道是好的。解決登錄CNC維護(hù)畫面檢查以太網(wǎng)設(shè)定里的端口和訪問(wèn)許可。先把訪問(wèn)對(duì)象類型換成A類試一次如果能連上說(shuō)明C類通道沒(méi)開(kāi)如果A類也連不上檢查端口號(hào)。還有一招是看防火墻Windows自帶的防火墻經(jīng)常攔截TCP入站連接臨時(shí)關(guān)掉再測(cè)一下就能定位。5.2 調(diào)用DLL時(shí)拋BadImageFormatException現(xiàn)象代碼編譯通過(guò)一運(yùn)行到Open函數(shù)就拋出BadImageFormatException異常信息里通常寫著32位還是64位不匹配。原因EZSocket.dll是32位動(dòng)態(tài)庫(kù)而Visual Studio里新建的.NET項(xiàng)目默認(rèn)平臺(tái)目標(biāo)是AnyCPU在64位系統(tǒng)上運(yùn)行時(shí)會(huì)用64位進(jìn)程加載32位DLL直接崩。解決打開(kāi)項(xiàng)目屬性找到生成選項(xiàng)卡把平臺(tái)目標(biāo)改成x86。改完重新生成問(wèn)題消失。這個(gè)坑幾乎每個(gè)第一次接觸EZSocket的C#開(kāi)發(fā)者都會(huì)遇到網(wǎng)上搜相關(guān)報(bào)錯(cuò)一搜一大片但知道原理之后十秒鐘就能解決。5.3 讀到的宏變量是全零或者NaN現(xiàn)象Read調(diào)用返回成功但解析出的變量值要么全是0要么是NaN。原因三菱宏變量分為數(shù)值型和邏輯型而數(shù)值型里又有整型和浮點(diǎn)型之分。系統(tǒng)默認(rèn)按雙字節(jié)整數(shù)返回時(shí)你用float解析就會(huì)得到詭異值。另外宏變量所在的地址區(qū)間可能根本沒(méi)被寫入有效數(shù)據(jù)空變量區(qū)讀出0是正?,F(xiàn)象。解決先在CNC面板上手動(dòng)給#100賦一個(gè)已知值比如12.34然后跑Demo讀一次。讀到12.34說(shuō)明解析邏輯正確讀到0說(shuō)明地址區(qū)間為空換個(gè)變量號(hào)測(cè)試讀到一堆亂碼說(shuō)明數(shù)據(jù)類型解析錯(cuò)了——嘗試把BitConverter.ToSingle換成ToInt16或者反過(guò)來(lái)。用已知值校準(zhǔn)是排查這類問(wèn)題最有效的手段。5.4 程序跑一會(huì)兒后連接自己斷開(kāi)現(xiàn)象Demo運(yùn)行幾分鐘后Read開(kāi)始返回錯(cuò)誤重試也無(wú)效只有重啟程序才能恢復(fù)連接。原因EZSocket服務(wù)端對(duì)空閑連接有超時(shí)回收機(jī)制超時(shí)時(shí)間從幾秒到幾分鐘不等。程序長(zhǎng)時(shí)間不發(fā)命令服務(wù)端主動(dòng)斷開(kāi)連接。還有一種情況是網(wǎng)絡(luò)里有廣播風(fēng)暴TCP連接被干擾。解決在采集循環(huán)里加入周期重連機(jī)制。常見(jiàn)做法是每次Read失敗時(shí)自動(dòng)執(zhí)行一次重連先Close再Open。更溫和的做法是縮短Timer間隔把心跳維持在一秒一次以內(nèi)避免空閑超時(shí)。注意重連時(shí)要釋放舊句柄否則句柄會(huì)越積越多最終系統(tǒng)資源耗盡只能重啟。另外排查一下網(wǎng)絡(luò)里有沒(méi)有人在跑大流量傳輸車間里的監(jiān)控系統(tǒng)回傳視頻有時(shí)會(huì)占滿交換機(jī)帶寬把CNC和上位機(jī)挪到同一個(gè)交換機(jī)下或者劃分獨(dú)立VLAN能有效緩解。5.5 報(bào)警號(hào)讀出來(lái)和面板顯示不一致現(xiàn)象報(bào)警命令讀出的編號(hào)和機(jī)臺(tái)面板上顯示的報(bào)警號(hào)對(duì)不上有的差個(gè)幾千有的完全不在一個(gè)數(shù)量級(jí)。原因報(bào)警命令讀出的是內(nèi)部報(bào)警指針不等于面板顯示的報(bào)警號(hào)。面板顯示的是格式化后的報(bào)警文本內(nèi)部編號(hào)需要對(duì)照?qǐng)?bào)警表映射。另外一個(gè)常見(jiàn)干擾是機(jī)床廠在標(biāo)準(zhǔn)系統(tǒng)上疊加了自己的PMC報(bào)警這些報(bào)警由D類訪問(wèn)對(duì)象負(fù)責(zé)C類讀不到。解決先確認(rèn)讀的是NC報(bào)警還是PMC報(bào)警。NC報(bào)警用A命令讀內(nèi)部編號(hào)再查系統(tǒng)報(bào)警表映射成面板報(bào)警號(hào)PMC報(bào)警要切換到D類訪問(wèn)對(duì)象讀取。Demo階段建議直接在CNC上觸發(fā)一個(gè)已知報(bào)警比如急?;蛘唛T開(kāi)關(guān)然后對(duì)比讀回來(lái)的編號(hào)和面板值把對(duì)應(yīng)關(guān)系記錄下來(lái)寫進(jìn)配置表里。這個(gè)工作看起來(lái)笨但做完以后對(duì)接MES報(bào)警模塊就順暢多了。6. 讓Demo往生產(chǎn)方向走數(shù)據(jù)落庫(kù)、心跳重連與MES橋接Demo跑通了接下來(lái)要做的三件事決定了它在生產(chǎn)環(huán)境里能不能站住腳數(shù)據(jù)落庫(kù)、斷線自動(dòng)重連、給上層系統(tǒng)留接口。數(shù)據(jù)落庫(kù)最省事的方案是SQLite不需要額外裝數(shù)據(jù)庫(kù)服務(wù)一個(gè)文件搞定。把每次讀到的坐標(biāo)、宏變量、報(bào)警號(hào)連同時(shí)間戳寫進(jìn)一張表里粒度控制在秒級(jí)就夠保留最近90天數(shù)據(jù)。注意寫入操作要和采集操作解耦采集線程只負(fù)責(zé)讀寫庫(kù)交給獨(dú)立線程或后臺(tái)任務(wù)避免磁盤慢的時(shí)候拖垮采集循環(huán)。斷線自動(dòng)重連是生產(chǎn)環(huán)境剛需。做法是把Open、Read、Close封裝成一個(gè)類內(nèi)部記錄連接狀態(tài)每次Read失敗時(shí)觸發(fā)重連重連次數(shù)達(dá)到上限后向上拋出異常由上層決定是告警還繼續(xù)重試。關(guān)鍵細(xì)節(jié)是重連之前必須Close舊句柄很多重連幾次之后徹底連不上的問(wèn)題都出在句柄泄漏上。給MES橋接的方式有兩種簡(jiǎn)單一點(diǎn)是Demo里直接提供HTTP接口MES通過(guò)網(wǎng)頁(yè)接口拉數(shù)據(jù)更穩(wěn)妥的是把采集到的數(shù)據(jù)推給消息隊(duì)列由消費(fèi)端去落庫(kù)和展示。Demo階段不用做太復(fù)雜把采集類寫干凈數(shù)據(jù)格式定義成統(tǒng)一的JSON結(jié)構(gòu)后續(xù)擴(kuò)展就順理成章。最后說(shuō)一個(gè)我在車間里淌過(guò)的教訓(xùn)有一回為了趕進(jìn)度我把采集循環(huán)直接寫進(jìn)窗體Timer里刷新間隔壓到50毫秒結(jié)果一臺(tái)老款系統(tǒng)被高頻請(qǐng)求打到以太網(wǎng)模塊卡死操作員停機(jī)重啟才恢復(fù)。后來(lái)我養(yǎng)成了兩個(gè)習(xí)慣任何采集代碼先量化頻率再上線任何外掛程序都加總開(kāi)關(guān)和限速。做設(shè)備采集慢一點(diǎn)不會(huì)出事快過(guò)頭一定出事。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取