上的上位機(jī)開發(fā):HslCommunication與.NET 4.5兼容實(shí)戰(zhàn))
簡介HslCommunication是一款基于.NET Framework 4.5的工業(yè)通信庫組件版本11.3.2面向需要對接PLC、設(shè)備數(shù)據(jù)交互的.NET開發(fā)者或自動(dòng)化項(xiàng)目集成人員。該封裝包已預(yù)先處理授權(quán)信息使用者無需再調(diào)用Authorization.SetAuthorizationCode激活碼即可直接引用并且不會(huì)出現(xiàn)24小時(shí)后過期的問題適合快速搭建原型或在正式項(xiàng)目中嵌入通信層。壓縮包共10個(gè)文件以HslCommunication.dll為核心包含XML注釋文檔便于IDE提示與查閱API另有HslControls.dll界面控件庫、兩個(gè)演示用exe、一個(gè)備份dll以及版本忽略說明txt等整體約5.69MB文件分工清晰按需解壓提取對應(yīng)dll即可。該資源已有3716人學(xué)習(xí)下載解壓后可直接將HslCommunication.dll加入工程借助演示程序快速理解連接與讀寫調(diào)用方式同時(shí)提供舊版本備份與升級(jí)工具可輔助開發(fā)者應(yīng)對版本遷移或調(diào)試排查省去自行處理授權(quán)和找包的步驟。 做上位機(jī)的人誰沒被老工控機(jī)“教育”過。前陣子我接手了一個(gè)老車間數(shù)采改造項(xiàng)目控制室里那臺(tái)Windows 7工控機(jī)已經(jīng)服役快十年底下掛了三套不同品牌的PLC還有幾塊串口儀表。客戶要求很明確不動(dòng)系統(tǒng)不換機(jī)器把數(shù)據(jù)全部采上來。這種場景下新框架再香也上不了現(xiàn)場我最后把技術(shù)棧壓回了HslCommunication 11.3.2加.NET 4.5的組合。這套組合看著舊但恰恰是存量工業(yè)現(xiàn)場里最不容易翻車的方案。文章不會(huì)講太虛的東西就是我這段時(shí)間的真實(shí)排查記錄、Demo寫法和現(xiàn)場調(diào)試經(jīng)驗(yàn)給同樣被老環(huán)境綁住手腳的朋友做個(gè)參考。1. 拒絕.NET 6之后為什么還是選了11.3.2先說結(jié)論HslCommunication不是那種“新潮”的庫但它在工業(yè)上位機(jī)圈子里的地位很穩(wěn)。它解決的是最讓人頭疼的“多品牌設(shè)備互聯(lián)”問題西門子、三菱、歐姆龍、基恩士、Modbus TCP/RTU、AB、松下、臺(tái)達(dá)還有各種儀表、機(jī)器人的通信協(xié)議基本上都能在一個(gè)框架里搞定。對于做數(shù)采或者設(shè)備管理系統(tǒng)的團(tuán)隊(duì)來說這意味著一套代碼模式可以通吃現(xiàn)場大部分設(shè)備不用為每臺(tái)PLC寫一套獨(dú)立的socket解析。版本為什么是11.3.2因?yàn)楹芏啻媪宽?xiàng)目、老工控機(jī)上跑的上位機(jī)就是基于這個(gè)版本開發(fā)的。它對應(yīng)的.NET 4.5目標(biāo)框架對Windows 7 SP1系統(tǒng)兼容性很好網(wǎng)上相關(guān)的demo、報(bào)錯(cuò)案例、二次封裝代碼也最多。真要遇到問題搜索一下基本都能找到答案這在工業(yè)現(xiàn)場調(diào)試時(shí)是非常重要的隱性成本。順帶說下.NET 4.5和.NET Framework 4.x運(yùn)行時(shí)的關(guān)系。.NET 4.5以后4.5、4.6、4.7、4.8都共用同一個(gè)CLR 4.x.NET 4.5編譯出來的程序集在高版本.NET Framework運(yùn)行時(shí)下是能直接跑的。這就意味著哪怕工控機(jī)上最高只裝到.NET 4.6.2也完全不影響你基于.NET 4.5目標(biāo)框架編譯的HslCommunication程序運(yùn)行。真正限制你的反而是開發(fā)機(jī)上的Visual Studio和項(xiàng)目目標(biāo)框架配置。我見過不少團(tuán)隊(duì)一上來就想用.NET 6/8重寫老項(xiàng)目結(jié)果到現(xiàn)場發(fā)現(xiàn)客戶機(jī)器的C運(yùn)行庫、數(shù)據(jù)庫驅(qū)動(dòng)、加密狗、老式采集卡驅(qū)動(dòng)全都是十幾年前的重裝系統(tǒng)的代價(jià)遠(yuǎn)大于軟件升級(jí)的收益。在這種環(huán)境里把目標(biāo)框架定在.NET 4.5不是技術(shù)落后是工程上的必要妥協(xié)。2. Win7裝.NET 4.5失敗的完整排查過程這次項(xiàng)目最折騰我的不是HslCommunication本身而是給一臺(tái)干凈的Windows 7工控機(jī)裝.NET 4.5。如果只在開發(fā)機(jī)上編譯那你裝VS時(shí)勾選“.NET Framework 4.5開發(fā)工具”就行但現(xiàn)場部署必須讓目標(biāo)機(jī)器具備對應(yīng)的運(yùn)行時(shí)環(huán)境。2.1 癥狀離線安裝包也彈“安裝未成功”我拿著離線安裝包NDP452-KB2901907-x86-x64-allos.exe去現(xiàn)場管理員身份雙擊等了半天彈出“安裝未成功”錯(cuò)誤碼0x80070643。網(wǎng)上對這個(gè)錯(cuò)的說法很雜有說系統(tǒng)分區(qū)空間不足的有說Windows更新服務(wù)出問題的還有說殺毒軟件攔截的光看錯(cuò)誤碼很難定位。關(guān)鍵一步是去看安裝日志。.NET Framework安裝器會(huì)在C:\Users\用戶名\AppData\Local\Temp下生成DD_NDP452_*.htm文件打開后找到錯(cuò)誤發(fā)生前最后幾條日志通常能看到是“CBS”相關(guān)還是“Windows Update”相關(guān)。我這個(gè)案例日志里反復(fù)出現(xiàn)CBS_E_NOT_APPLICABLE基本判斷是系統(tǒng)缺少某些前置更新導(dǎo)致.NET 4.5安裝器認(rèn)為當(dāng)前系統(tǒng)狀態(tài)不滿足條件。2.2 根因Windows 7 SP1的系統(tǒng)更新底子沒打牢.NET 4.5在Windows 7上最穩(wěn)妥的安裝前提是先打好SP1補(bǔ)丁再確保關(guān)鍵更新在系統(tǒng)里。很多Ghost版Win7或者精簡版Win7會(huì)砍掉部分更新組件裝.NET自然會(huì)失敗。我當(dāng)時(shí)的處理思路是先把Windows Update能打的補(bǔ)丁全打上特別關(guān)注KB2966826和KB2966827這類和.NET Framework安裝直接相關(guān)的前置更新。如果Windows Update本身一直檢查不到補(bǔ)丁可以手動(dòng)下載這些KB補(bǔ)丁安裝。如果系統(tǒng)里已經(jīng)殘留了安裝一半的.NET組件建議先運(yùn)行微軟官方的“.NET Framework修復(fù)工具”把半成品清掉再重試。修復(fù)工具會(huì)檢測所有.NET版本的注冊表項(xiàng)和文件狀態(tài)對于清理損壞安裝非常有效。2.3 最終能落地的安裝順序我整理一下這次現(xiàn)場驗(yàn)證過的一套安裝步驟照著走能避開絕大部分坑確認(rèn)系統(tǒng)是Windows 7 SP1不是RTM版。右鍵“計(jì)算機(jī)”查看版本沒有SP1就先裝SP1補(bǔ)丁重啟。關(guān)閉殺毒軟件和Windows Defender實(shí)時(shí)防護(hù)部分殺軟會(huì)把安裝器生成的臨時(shí)DLL當(dāng)風(fēng)險(xiǎn)文件隔離導(dǎo)致安裝回滾。把離線安裝包復(fù)制到純英文路徑比如C:\dotnet45\不要放桌面或中文目錄下。管理員身份打開CMD運(yùn)行net stop wuauserv停止Windows Update服務(wù)再把C:\Windows\SoftwareDistribution和C:\Windows\System32\catroot2臨時(shí)改名備份。這兩個(gè)文件夾緩存著舊的更新數(shù)據(jù)有損壞時(shí)會(huì)影響新組件注冊。從CMD里直接執(zhí)行NDP452-KB2901907-x86-x64-allos.exe /quiet /norestart安靜模式安裝比雙擊安裝器更少受UAC和交互彈窗干擾。等到CMD返回后查看C:\Windows\Logs\NetFramework\下的安裝日志確認(rèn)沒有error級(jí)別記錄。注冊表驗(yàn)證reg query HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full /v Release如果返回的Release值為379893或以上說明4.5以上運(yùn)行時(shí)已經(jīng)就位。這套順序里最容易被忽略的是第4步。整個(gè)SoftwareDistribution目錄如果存在權(quán)限錯(cuò)亂很多基于Windows Update通道的組件安裝都會(huì)莫名其妙失敗不光是.NET Framework后續(xù)裝別的補(bǔ)丁也可能踩同樣的坑。3. 第一個(gè)Demo程序用11.3.2讀西門子S7-1500的DB塊環(huán)境準(zhǔn)備好之后開發(fā)這邊就順暢多了。我用的是Visual Studio 2019建的項(xiàng)目是.NET Framework 4.5的WinForms程序通過NuGet引用HslCommunication 11.3.2。如果項(xiàng)目處在離線環(huán)境也可以直接在NuGet緩存目錄里找到對應(yīng)的HslCommunication.dll手動(dòng)添加到引用效果一樣。3.1 最小可運(yùn)行代碼讀西門子S7-1500的DB1.DBD0也就是DB塊第0個(gè)偏移的32位浮點(diǎn)數(shù)代碼可以短到這樣using System; using HslCommunication; using HslCommunication.Profinet.Siemens; class Program { static void Main(string[] args) { // 1. 創(chuàng)建西門子S7客戶端指定PLC類型和IP SiemensS7Net s7 new SiemensS7Net(SiemensPLCS.S1500, 192.168.0.10); // 2. 如果PLC有多個(gè)網(wǎng)卡或需要指定機(jī)架號(hào)可以在這里設(shè)置 s7.SetSlot(0); // 3. 顯式建立連接便于第一時(shí)間發(fā)現(xiàn)網(wǎng)絡(luò)問題 OperateResult connect s7.ConnectServer(); if (!connect.IsSuccess) { Console.WriteLine(連接失敗 connect.Message); return; } // 4. 讀取DB1.DBD0的浮點(diǎn)數(shù)地址寫成 DB1.0 OperateResultfloat result s7.ReadFloat(DB1.0); if (result.IsSuccess) { Console.WriteLine(DB1.DBD0 當(dāng)前值 result.Content); } else { Console.WriteLine(讀取失敗 result.Message); } // 5. 寫入示例把DB1.DBD0寫為80 OperateResult writeResult s7.Write(DB1.0, 80.0f); if (writeResult.IsSuccess) { Console.WriteLine(寫入成功); } // 6. 關(guān)閉連接 s7.ConnectClose(); Console.ReadKey(); } }這段代碼基本體現(xiàn)了HslCommunication的核心使用模式實(shí)例化客戶端對象調(diào)用ConnectServer建連用Read/Write系列方法操作數(shù)據(jù)最后關(guān)閉連接。返回值都是OperateResult類型先判斷IsSuccess再看Content這個(gè)習(xí)慣我會(huì)要求團(tuán)隊(duì)所有人都遵守。因?yàn)楣I(yè)通信里失敗是常態(tài)不判斷返回值直接往下走遲早會(huì)把異常數(shù)據(jù)送進(jìn)數(shù)據(jù)庫。3.2 關(guān)于連接方式的兩個(gè)細(xì)節(jié)第一SiemensS7Net支持隱式連接也就是不調(diào)用ConnectServer直接Read庫內(nèi)部檢測到未連接時(shí)會(huì)自動(dòng)建立連接。但在現(xiàn)場調(diào)試階段我強(qiáng)烈建議顯式ConnectServer這樣能更早暴露網(wǎng)絡(luò)不通、IP填錯(cuò)這些基礎(chǔ)問題。第二很多人不知道SetSlot和SetRack的含義。S7-300/400這類老PLC通常需要考慮機(jī)架號(hào)和槽號(hào)比如CPU在0號(hào)機(jī)架2號(hào)槽就要SetRack(0); SetSlot(2);。而S7-1200/1500走的是優(yōu)化后的訪問機(jī)制默認(rèn)0號(hào)槽在大多數(shù)場景下能直接工作但如果你發(fā)現(xiàn)連接建立后讀不到數(shù)據(jù)嘗試把Slot改成0之外的值再測一遍。3.3 WinForms程序和線程WinForms里用HslCommunication的坑主要在UI線程。通信回調(diào)或異步方法返回后如果直接操作Label、TextBox會(huì)拋出跨線程訪問異常。通常的處理是把數(shù)據(jù)放到一個(gè)公共變量或緩存類里再用Timer定時(shí)刷新UI或者用Control.BeginInvoke回到UI線程。我個(gè)人更喜歡Timer輪詢方案簡單、直觀也方便控制采集頻率。4. 多協(xié)議實(shí)戰(zhàn)三菱、歐姆龍、Modbus TCP的編程模型對比項(xiàng)目里不是只有西門子一家設(shè)備。那套老車間里還有一臺(tái)三菱Q系列PLC、一個(gè)歐姆龍溫控表以及一臺(tái)走M(jìn)odbus TCP協(xié)議的第三方儀表。HslCommunication對它們都有對應(yīng)封裝而且編程模型高度一致這是這個(gè)庫在項(xiàng)目化開發(fā)里最有價(jià)值的地方。4.1 協(xié)議接入速查表設(shè)備品牌/協(xié)議命名空間典型客戶端類地址示例西門子S7HslCommunication.Profinet.SiemensSiemensS7NetM100, DB1.0, I0.0三菱MC協(xié)議HslCommunication.Profinet.MelsecMcTcpClientD100, M0, X0歐姆龍F(tuán)ins TCPHslCommunication.Profinet.OmronOmronFinsTcpD100, CIO0, W0Modbus TCPHslCommunication.ModBusModbusTcpClient100, 4x100, 0核心用法上三菱的讀浮點(diǎn)、寫寄存器和西門子幾乎一模一樣只是地址字符串不同。比如三菱Q系列讀D100這個(gè)32位浮點(diǎn)數(shù)using HslCommunication.Profinet.Melsec; McTcpClient melsec new McTcpClient(192.168.0.20); OperateResult connect melsec.ConnectServer(); OperateResultfloat val melsec.ReadFloat(D100).Content;Modbus TCP稍微有一點(diǎn)區(qū)別它更偏寄存器地址模型。比如讀儀表保持寄存器40001對應(yīng)的實(shí)際地址是0using HslCommunication.ModBus; ModbusTcpClient modbus new ModbusTcpClient(192.168.0.30, 502); OperateResultshort val modbus.ReadInt16(0);如果地址里帶功能碼前綴比如4x0表示讀保持寄存器區(qū)的0號(hào)地址庫內(nèi)部會(huì)自動(dòng)換算成Modbus協(xié)議里的功能碼03。這個(gè)我在現(xiàn)場交接時(shí)經(jīng)常要和電氣工程師確認(rèn)因?yàn)椴煌瑑x表的寄存器表文檔寫法差異很大。4.2 連接管理和線程安全HslCommunication的多數(shù)客戶端類是線程安全的多線程調(diào)用讀寫方法不會(huì)導(dǎo)致協(xié)議錯(cuò)亂。但線程安全不意味著你可以毫無節(jié)制地并發(fā)請求底層網(wǎng)卡和PLC的響應(yīng)能力是有上限的。我的做法是一個(gè)PLC對應(yīng)一個(gè)客戶端實(shí)例全局共享不讓每個(gè)業(yè)務(wù)線程各自new一個(gè)連接。這樣既避免頻繁建連斷開也能讓HslCommunication內(nèi)部的連接管理機(jī)制發(fā)揮作用。高負(fù)載場景下如果多個(gè)線程同時(shí)對同一臺(tái)PLC寫數(shù)據(jù)可能因?yàn)樵O(shè)備響應(yīng)慢而堆積請求。這種情況下我會(huì)引入一個(gè)信號(hào)量或者隊(duì)列把寫請求串行化。實(shí)測下來S7-1500和Q系列對單連接連續(xù)請求的吞吐能力足以應(yīng)對車間級(jí)數(shù)采瓶頸往往在PLC側(cè)的循環(huán)掃描時(shí)間和網(wǎng)絡(luò)質(zhì)量上而不是庫本身的性能。4.3 三分鐘超時(shí)與自動(dòng)重連工業(yè)現(xiàn)場最怕的不是設(shè)備掉線而是掉線后程序進(jìn)入假死狀態(tài)。HslCommunication的ConnectTimeOut屬性控制連接超時(shí)默認(rèn)值是1000毫秒我建議調(diào)大一點(diǎn)比如3000到5000毫秒。對于現(xiàn)場偶發(fā)的網(wǎng)絡(luò)抖動(dòng)稍微容忍一下比頻繁報(bào)錯(cuò)更符合實(shí)際需要。自動(dòng)重連不能全指望庫本身。HslCommunication在連接斷開后下一次讀寫操作會(huì)觸發(fā)重連機(jī)制但如果你用了長輪詢方式定時(shí)讀取最好在讀取失敗后增加一個(gè)延遲重試策略// 偽代碼示意實(shí)際請根據(jù)業(yè)務(wù)封裝 if (!readResult.IsSuccess) { // 連續(xù)失敗后先釋放連接避免底層socket半開 plc.ConnectClose(); Thread.Sleep(2000); plc.ConnectServer(); }這里有個(gè)現(xiàn)場經(jīng)驗(yàn)連續(xù)失敗后先把連接顯式關(guān)閉再重連比直接依賴內(nèi)部重連更可靠。半開連接狀態(tài)下直接重連容易一直卡在舊socket上關(guān)掉再建反而干凈利落。5. 從舊版本遷到11.3.2的API差異編譯報(bào)錯(cuò)別慌如果項(xiàng)目之前用的是更老的版本比如8.x或9.x直接替換成11.3.2后大概率會(huì)編譯報(bào)錯(cuò)。這不一定是用法寫錯(cuò)了而是HslCommunication在版本迭代中對方法命名和命名空間做了不少“整理性重構(gòu)”。5.1 浮點(diǎn)數(shù)讀取方式的變化早期版本里讀取浮點(diǎn)數(shù)時(shí)對字節(jié)序的處理比較混亂同一個(gè)方法在不同設(shè)備上可能得到不同結(jié)果。11.x開始讀浮點(diǎn)拆成了明確大小端的方法比如ReadFloat和ReadFloatL。如果你發(fā)現(xiàn)讀上來的數(shù)值是個(gè)天文數(shù)字或者和實(shí)際值差了好幾個(gè)數(shù)量級(jí)第一時(shí)間檢查當(dāng)前設(shè)備字節(jié)序和你調(diào)用的方法是否匹配。西門子S7默認(rèn)是高字節(jié)在前Modbus TCP的32位浮點(diǎn)通常是AB CD這個(gè)細(xì)節(jié)直接決定數(shù)據(jù)對不對。5.2 命名空間和類名調(diào)整老的HslCommunication.Core里很多擴(kuò)展方法被重新歸類一些設(shè)備類也從原來的命名空間挪到了更細(xì)分的Profinet目錄下。遇到編譯錯(cuò)誤時(shí)不要逐個(gè)類去猜直接把報(bào)錯(cuò)內(nèi)容復(fù)制到搜索引擎里查通常都能找到官方文檔或社區(qū)帖子說明該類遷到了哪里。另外一個(gè)版本遷移的技巧不要一上來就全局替換DLL而是先建一個(gè)獨(dú)立分支讓編譯器把所有報(bào)錯(cuò)列出來然后按“類找不到、方法不存在、返回值類型變化”三類問題分批處理。返回值類型變化是最隱蔽的比如某些方法從直接返回值變成了返回OperateResultT這種改動(dòng)不報(bào)錯(cuò)但業(yè)務(wù)代碼運(yùn)行時(shí)會(huì)拿到不對的對象。所以升級(jí)后必須跑一輪完整的單元測試或者儀表對比測試不能只看編譯通過。5.3 開發(fā)工具鏈的兼容如果你用的是VS2022開發(fā).NET 4.5項(xiàng)目需要在安裝器里勾選“.NET Framework 4.5開發(fā)工具”組件否則項(xiàng)目模板和編譯目標(biāo)里看不到4.5。VS2022本身能正常編譯.NET 4.5項(xiàng)目不用因?yàn)槟繕?biāo)框架老而專門安裝VS2015。NuGet還原時(shí)HslCommunication 11.3.2會(huì)按照項(xiàng)目目標(biāo)框架自動(dòng)匹配兼容的程序集版本這方面基本透明不用手動(dòng)處理。6. 現(xiàn)場設(shè)備連不上的三板斧排查順序最后分享一下我在現(xiàn)場調(diào)試設(shè)備通信時(shí)固定使用的排查順序。不管用HslCommunication還是別的庫這套方法都能幫你少走很多彎路。第一板斧先ping設(shè)備IP。ping不通就不用往下查了查網(wǎng)線、交換機(jī)、IP配置。有些設(shè)備支持ping有些不支持但絕大多數(shù)PLC的以太網(wǎng)口是能ping通的。第二板斧看設(shè)備側(cè)的通信開關(guān)。西門子S7-1200/1500默認(rèn)不開放PUT/GET通信需要在PLC程序里組態(tài)“允許來自遠(yuǎn)程對象的通信”很多第一次調(diào)試西門子的朋友把代碼寫對了也連不上原因就在這。三菱Q系列要確認(rèn)MC協(xié)議在以太網(wǎng)模塊里啟用了TCP端口歐姆龍要確認(rèn)FINS節(jié)點(diǎn)號(hào)設(shè)置。這些設(shè)備側(cè)參數(shù)不核對上位機(jī)代碼寫得再完美也白搭。第三板斧開一個(gè)簡單的協(xié)議測試工具不跑業(yè)務(wù)代碼只做單次讀取。HslCommunication自帶的Demo工具就能干這事填入IP、端口、PLC類型、地址點(diǎn)一下“讀取”能讀到值再回過來檢查自己的代碼。多數(shù)情況下現(xiàn)場問題都是網(wǎng)絡(luò)或設(shè)備配置問題而不是庫本身的問題。作為收尾再分享一個(gè)實(shí)用習(xí)慣生產(chǎn)環(huán)境上位機(jī)一定要有日志記錄每次讀寫操作、連接狀態(tài)變化、錯(cuò)誤信息都要落盤。HslCommunication的OperateResult里帶著錯(cuò)誤消息配合日志組件記錄下來現(xiàn)場出問題時(shí)能大幅縮短排查時(shí)間。我自己就吃過虧沒有日志的上位機(jī)一出問題就像黑箱別人根本沒法判斷是網(wǎng)絡(luò)斷了還是PLC程序被人改了。加日志這個(gè)動(dòng)作比優(yōu)化通信性能重要十倍。本文還有配套的精品資源點(diǎn)擊獲取