在工業信息化行業,少不了生產可視化的模塊,其中應用最多的是采用LED屏的方式,通過軟件控制屏幕展示相關的生產計划完成狀態,工位的狀態,產線的運行狀態,以及相關自動化設備的狀態等,這就要求通信實時性,准確性。LED屏控制核心在於控制卡目前市場上各種控制卡種類繁多,今天先介紹下EQ系列的控制卡吧。其實通信實時性要求采用同步卡效果最佳,但是苦於現場不想增加專用的控制電腦(大部分同步卡原理即抓屏模式,實時展現電腦桌面的內容,電腦不能同時進行其他操作),所以決定采用異步卡異步通信,直接在后台通過程序建立和LED屏幕的連接,實時發送數據給LED屏幕。此篇就講下根據官方二次開發包開發的問題。
此處我們采用的是網絡通訊的方式,即EQ控制卡上需固定IP,用以識別並和控制程序建立連接。
EQ2008_Dll.dll是官方提供的開發接口,其中的接口方法包含如下:

using System; using System.Runtime.InteropServices; using ServiceCloud.Devices.Led.EQ2008.Parameters; namespace ServiceCloud.Devices.Led.EQ2008 { /* 本動態庫接口適用於:EQ火鳳凰系列和藍精靈系列控制器! 火鳳凰系列:EQ2013、EQ2023、EQ2033 藍精靈系列:EQ2012、EQ2011、EQ2008-1/2E、EQ2008-M */ internal class LedControllerHandler { # region 1.節目操作函數 /// <summary> /// 添加節目 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="bWaitToEnd">TRUE 等待節目播放完成再播放下個節目,FALSE 節目播放時間為 iPlayTime</param> /// <param name="iPlayTime">節目播放時間,單位為秒</param> /// <returns>節目索引號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddProgram(int CardNum, Boolean bWaitToEnd, int iPlayTime); /// <summary> /// 刪除一個節目 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>0-刪除失敗,1-刪除成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_DelProgram(int CardNum, int iProgramIndex); /// <summary> /// 刪除所有節目 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <returns>0-刪除失敗,1-刪除成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_DelAllProgram(int CardNum); /// <summary> /// 添加文本區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pText">文本參數表指針,參考【參數表】中 9</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加文本區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddText(int CardNum, ref User_Text pText, int iProgramIndex); /// <summary> /// 添加單行文本區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pSingleText">單行文本參數表指針,參考【參數表】中 8</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加單行文本區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddSingleText(int CardNum, ref User_SingleText pSingleText, int iProgramIndex); /// <summary> /// 添加圖文區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pBmp">圖文區參數表指針,參考【參數表】中 7</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加圖文區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddBmpZone(int CardNum, User_Bmp pBmp, int iProgramIndex); /// <summary> /// 指定圖像句柄添加圖片 /// </summary> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_AddBmp(int CardNum, int iBmpPartNum, IntPtr hBitmap, ref User_MoveSet pMoveSet, int iProgramIndex); /// <summary> /// 指定圖像路徑添加圖片 /// </summary> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_AddBmpFile(int CardNum, int iBmpPartNum, string strFileName, ref User_MoveSet pMoveSet, int iProgramIndex); /// <summary> /// 添加時間區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pdateTime">時間參數表指針,參考【參數表】中 6</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加時間區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddTime(int CardNum, ref User_DateTime pdateTime, int iProgramIndex); /// <summary> /// 添加倒計時區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pTimeCount">倒計時參數表指針,參考【參數表】中 4</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加計時區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddTimeCount(int CardNum, User_Timer pTimeCount, int iProgramIndex); /// <summary> /// 添加溫度區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pTemperature">溫度參數表指針,參考【參數表】中 5</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加溫度區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddTemperature(int CardNum, User_Temperature pTemperature, int iProgramIndex); /// <summary> /// 添加 RTF 文件區 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="pRTFt">RTF 文件參數表指針,參考【參數表】中 10</param> /// <param name="iProgramIndex">節目索引號</param> /// <returns>-1-添加文本區失敗,非-1-分區編號</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern int User_AddRTF(int CardNum, User_RTF pRTFt, int iProgramIndex); /// <summary> /// 向控制器發送數據 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <returns>FALSE - 發送失敗,TRUE - 發送成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_SendToScreen(int CardNum); #endregion #region 2.實時發送數據(高頻率發送) //實時建立連接 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_RealtimeConnect(int CardNum); //實時關閉連接 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_RealtimeDisConnect(int CardNum); //實時發送圖片數據 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_RealtimeSendData(int CardNum, int x, int y, int iWidth, int iHeight, IntPtr hBitmap); //實時發送圖片文件 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern Boolean User_RealtimeSendBmpData(int CardNum, int x, int y, int iWidth, int iHeight, string strFileName); //實時發送文本 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern Boolean User_RealtimeSendText(int CardNum, int x, int y, int iWidth, int iHeight, string strText, ref User_FontSet pFontInfo); //實時發送清屏 [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern Boolean User_RealtimeScreenClear(int CardNum); #endregion #region 3.顯示屏控制函數組 /// <summary> /// 校正時間 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <returns>FALSE - 板卡校正時間失敗,TRUE - 板卡校正時間成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern Boolean User_AdjustTime(int CardNum); /// <summary> /// 打開顯示屏 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <returns>FALSE - 打開顯示屏失敗,TRUE - 打開顯示屏成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_OpenScreen(int CardNum); /// <summary> /// 關閉顯示屏 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <returns>FALSE - 關閉顯示屏失敗,TRUE - 關閉顯示屏成功</returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern bool User_CloseScreen(int CardNum); /// <summary> /// 亮度調節 /// </summary> /// <param name="CardNum">控制卡地址,基數為 1,即第一塊控制卡地址為 1</param> /// <param name="iLightDegreen">屏幕亮度值</param> /// <returns></returns> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern Boolean User_SetScreenLight(int CardNum, int iLightDegreen); /// <summary> /// Reload參數文件 /// </summary> /// <param name="strEQ2008_Dll_Set_Path"></param> [DllImport("EQ2008_Dll.dll", CharSet = CharSet.Ansi)] public static extern void User_ReloadIniFile(string strEQ2008_Dll_Set_Path); #endregion [DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); } }
EQ2008_Dll_Set.ini是LED屏幕的通信的配置文件,在屏幕初始化之前就要存在,可手動配置好放至程序運行的根目錄,為了可復用性,在此將其在程序配置【通信地址(ip),端口號(port),屏幕大小(screenHeight,screenWidth),單雙色(colorStyle),控制卡型號(cardType)】,在程序運行前(即Program.cs文件中動態生成EQ2008_Dll_Set.ini文件)

var screens = BLL.Common.DataSetCache.Screens; foreach (var screen in screens) { if (screen.screenType == Setting.屏幕類型.主控屏) ServiceCloud.Devices.Led.EQ2008.LedController.Register(CardType.EQ2023, ScreenColorStyle.Multicolor, 224, 1024, screen.ip, 5005); if (screen.screenType == Setting.屏幕類型.分裝區屏) ServiceCloud.Devices.Led.EQ2008.LedController.Register(CardType.EQ2023, ScreenColorStyle.Multicolor, 192, 192, screen.ip, 5005); if (screen.screenType == Setting.屏幕類型.配發區屏) ServiceCloud.Devices.Led.EQ2008.LedController.Register(CardType.EQ2023, ScreenColorStyle.Multicolor, 64, 384, screen.ip, 5005); } ServiceCloud.Devices.Led.EQ2008.LedController.Initialize();
在生成控制卡的通信配置文件以后再程序里面(如下圖)
通過點擊開始連接按鈕(按鈕事件如下)

private void OpenConnect() { try { var index = 0; var screenNumbers = GetCheckValues(false); if (screenNumbers.Count <= 0) { MessageBoard.Error(this, "請先勾選您所要操作的LED屏"); return; } foreach (var screenNumber in screenNumbers) { var screenModel = DataSetCache.Screens.FirstOrDefault(o => o.number == screenNumber); var ledBasics = DataSetCache.LedBasics.FirstOrDefault(o => o.ScreenModel.ip == screenModel.ip); if (ledBasics.ScreenState == EnumScreenState.Open) continue; if (!ledBasics.ScreenModel.ip.PingIp()) { var message = string.Format("{0}-啟動失敗!可能原因:未找到當前屏幕的通信地址,請檢查當前屏幕網絡通信。", ledBasics.ScreenModel.name); MSGLSBOX(message); Logger.Error(message); continue; } var controller = ServiceCloud.Devices.Led.EQ2008.LedController.GetFromIP(ledBasics.ScreenModel.ip); if (controller == null) { var message = string.Format("{0}-啟動失敗!可能原因:屏幕初始化失敗。", ledBasics.ScreenModel.name); MSGLSBOX(message); Logger.Error(message); continue; } else if (!controller.Open()) { var message = string.Format("{0}-啟動失敗!可能原因:屏幕打開失敗。", ledBasics.ScreenModel.name); MSGLSBOX(message); Logger.Error(message); continue; } else { ledBasics.ScreenState = EnumScreenState.Open; if (ledBasics.ScreenModel.screenType == Setting.屏幕類型.主控屏) { ledBasics.LedThread = new Thread(new ParameterizedThreadStart(LedMainDrive)); ledBasics.LedThread.Start(ledBasics); } else if (ledBasics.ScreenModel.screenType == Setting.屏幕類型.分裝區屏) { ledBasics.LedThread = new Thread(new ParameterizedThreadStart(LedPackingDrive)); ledBasics.LedThread.Start(ledBasics); } else if (ledBasics.ScreenModel.screenType == Setting.屏幕類型.配發區屏) { if (index == 0) ledBasics.LedThread = new Thread(new ParameterizedThreadStart(LedDistributionDriveN4_1)); if (index == 1) ledBasics.LedThread = new Thread(new ParameterizedThreadStart(LedDistributionDriveN4_2)); if (index == 2) ledBasics.LedThread = new Thread(new ParameterizedThreadStart(LedDistributionDriveN4_3)); index++; ledBasics.LedThread.Start(ledBasics); } else MessageBoard.Error(this, string.Format("未識別{0}的屏幕類型", ledBasics.ScreenModel.name)); } } RefreshScreenList(); } catch { throw; } }
通過點擊斷開連接按鈕(按鈕事件如下)

private void CloseConnect() { try { var screenNumbers = GetCheckValues(false); if (screenNumbers.Count <= 0) { MessageBoard.Error(this, "請先勾選所要操作的LED屏"); return; } foreach (var screenNumber in screenNumbers) { var screenModel = DataSetCache.Screens.FirstOrDefault(o => o.number == screenNumber); var ledBasics = DataSetCache.LedBasics.FirstOrDefault(o => o.ScreenModel == screenModel); var controller = ServiceCloud.Devices.Led.EQ2008.LedController.GetFromIP(ledBasics.ScreenModel.ip); if (controller.Close()) { if (ledBasics.LedThread != null) { Thread.Sleep(1000); ledBasics.LedThread.Abort(); ledBasics.LedThread.Join(); } ledBasics.ScreenState = EnumScreenState.Close; RefreshScreenList(); MessageBoard.Info(this, "已成功關閉連接!"); } else MSGLSBOX(string.Format("{0} 連接關閉失敗。", ledBasics.ScreenModel.name)); } } catch { throw; } }
因為當前項目需要控制多個LED屏幕,所以此處采取獨立線程去分別控制顯示,以下是一個線程內的控制通信的代碼

public void LedPackingDrive(object source) { while (true) { try { var ledBasics = source as LedBasics; var packingScreenTwoSwitchingTime = Sys_SystemInfoManager.packingScreenTwoSwitchingTime * 2; var packingScreenOneSwitchingTime = Sys_SystemInfoManager.packingScreenOneSwitchingTime * 2; this.LedControllerForPackingBasic = ServiceCloud.Devices.Led.EQ2008.LedController.GetFromIP(ledBasics.ScreenModel.ip); this.LedControllerForPacking = new LedController.LedControllerForPacking(this.LedControllerForPackingBasic); if (indexForPacking <= packingScreenTwoSwitchingTime) SendToPackingLed(ledBasics, false); else if (indexForPacking > packingScreenTwoSwitchingTime && indexForPacking < packingScreenTwoSwitchingTime + packingScreenOneSwitchingTime) SendToPackingLed(ledBasics, true); else if (indexForPacking >= packingScreenTwoSwitchingTime + packingScreenOneSwitchingTime) { indexForPacking = 0; continue; } indexForPacking++; Thread.Sleep(500); } catch (System.Exception ex) { Logger.Error(ex); } } } private void SendToPackingLed(LedBasics ledBasics, bool isSecondPages) { try { var dataItem = new LedController.LedControllerForPacking.ScreenDataItemForPacking(); List<Obj_TaskManager> tasksForLackMaterial; var tasksForAll = ledBasics.Tasks; tasksForLackMaterial = (from task in tasksForAll where (task.taskState == Setting.作業狀態.待執行 || task.taskState == Setting.作業狀態.分揀中) && task.completeKit == Setting.齊套狀態.缺料 select task).OrderBy(o => o.sequence).ToList(); if (tasksForLackMaterial.Count > 0) { dataItem.WorkNumber1 = tasksForLackMaterial[0].workNumber; dataItem.Line1 = tasksForLackMaterial[0].line; dataItem.WorkNumber2 = tasksForLackMaterial.Count > 1 ? tasksForLackMaterial[1].workNumber : "No Task"; dataItem.Line2 = tasksForLackMaterial.Count > 1 ? tasksForLackMaterial[1].line : "No Task"; var currentTaskChildMaterialCount1 = tasksForLackMaterial[0].CurrentTaskChildMaterialCount.Select(o => string.Format("{0}{1}", RemoveMaterialMemoEnglish(o.childMaterialMemo), o.childMaterialCount)).ToArray().Join(","); if (scroll_Packing1 >= currentTaskChildMaterialCount1.Length) { dataItem.ScrollContent1 = currentTaskChildMaterialCount1; scroll_Packing1 = 0; } else { dataItem.ScrollContent1 = currentTaskChildMaterialCount1.Substring(scroll_Packing1, currentTaskChildMaterialCount1.Length - scroll_Packing1); scroll_Packing1++; } if (tasksForLackMaterial.Count > 1) { var currentTaskChildMaterialCount2 = tasksForLackMaterial[1].CurrentTaskChildMaterialCount.Select(o => string.Format("{0}{1}", RemoveMaterialMemoEnglish(o.childMaterialMemo), o.childMaterialCount)).ToArray().Join(","); if (scroll_Packing2 >= currentTaskChildMaterialCount2.Length) { dataItem.ScrollContent2 = currentTaskChildMaterialCount2; scroll_Packing2 = 0; } else { dataItem.ScrollContent2 = currentTaskChildMaterialCount2.Substring(scroll_Packing2, currentTaskChildMaterialCount2.Length - scroll_Packing2); scroll_Packing2 = scroll_Packing2 + 2; } } if (isSecondPages) this.LedControllerForPacking.SendToScreen(dataItem, EnumScreenPackingType.ScreenDataForScrollContent); else this.LedControllerForPacking.SendToScreen(dataItem, EnumScreenPackingType.ScreenImageForTask); } else this.LedControllerForPacking.SendToScreen(dataItem, EnumScreenPackingType.ScreenImageForNoTask); } catch { throw; } }
其中
this.LedControllerForPacking.SendToScreen(dataItem, EnumScreenPackingType.ScreenImageForNoTask);
就是往控制卡(屏幕)中發送數據。此處只是簡單的介紹下開發包的使用,以及簡單設計。