C#簡單組態軟件開發

C#簡單組態軟件開發

組態軟件(SCADA/HMI)是工業自動化領域的核心軟件,用于監控和控制工業過程。

系統架構設計

一個基本的組態軟件應包含以下模塊:

  1. 圖形界面編輯器
  2. 設備通信模塊
  3. 實時數據庫
  4. 運行時引擎
  5. 報警系統
  6. 歷史數據存儲

開發環境搭建

  1. 開發工具

    • Visual Studio 2019/2022
    • .NET Framework 4.7+ 或 .NET 5/6
  2. 主要依賴庫

    <PackageReference Include="Opc.Ua.Core" Version="1.4.365" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
    <PackageReference Include="SharpDX" Version="4.2.0" />
    <PackageReference Include="Serilog" Version="2.10.0" />
    

核心模塊實現

1. 圖形界面編輯器

// 圖形元素基類
public abstract class GraphicElement : INotifyPropertyChanged
{public string Id { get; set; } = Guid.NewGuid().ToString();public string Name { get; set; }public double X { get; set; }public double Y { get; set; }public double Width { get; set; }public double Height { get; set; }public double Rotation { get; set; }public Brush Background { get; set; } = Brushes.White;public Brush Foreground { get; set; } = Brushes.Black;public Pen Border { get; set; } = new Pen(Brushes.Black, 1);public abstract void Draw(DrawingContext drawingContext);public virtual bool HitTest(Point point){return new Rect(X, Y, Width, Height).Contains(point);}public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}// 矩形元素
public class RectangleElement : GraphicElement
{public override void Draw(DrawingContext drawingContext){drawingContext.DrawRectangle(Background, Border, new Rect(X, Y, Width, Height));}
}// 文本元素
public class TextElement : GraphicElement
{public string Text { get; set; } = "Text";public string FontFamily { get; set; } = "Arial";public double FontSize { get; set; } = 12;public FontWeight FontWeight { get; set; } = FontWeights.Normal;public override void Draw(DrawingContext drawingContext){var formattedText = new FormattedText(Text,CultureInfo.CurrentCulture,FlowDirection.LeftToRight,new Typeface(FontFamily, FontStyle.Normal, FontWeight, FontStretches.Normal),FontSize,Foreground,VisualTreeHelper.GetDpi(Application.Current.MainWindow).PixelsPerDip);drawingContext.DrawText(formattedText, new Point(X, Y));}
}// 畫面類
public class GraphicScreen
{public string Name { get; set; }public double Width { get; set; } = 800;public double Height { get; set; } = 600;public ObservableCollection<GraphicElement> Elements { get; set; } = new ObservableCollection<GraphicElement>();public void Render(DrawingContext drawingContext){foreach (var element in Elements){element.Draw(drawingContext);}}
}

2. 設備通信模塊

// 通信驅動接口
public interface IDeviceDriver
{string Name { get; }bool IsConnected { get; }Task<bool> ConnectAsync();Task DisconnectAsync();Task<object> ReadTagAsync(string tagName);Task<bool> WriteTagAsync(string tagName, object value);event EventHandler<DataChangedEventArgs> DataChanged;
}// Modbus TCP驅動示例
public class ModbusTcpDriver : IDeviceDriver
{private ModbusFactory _factory;private IModbusMaster _master;private string _ipAddress;private int _port;public string Name => "ModbusTCP";public bool IsConnected => _master != null && _master.Transport != null && _master.Transport.IsConnected;public ModbusTcpDriver(string ipAddress, int port = 502){_ipAddress = ipAddress;_port = port;_factory = new ModbusFactory();}public async Task<bool> ConnectAsync(){try{_master = _factory.CreateMaster(new TcpClientAdapter(_ipAddress, _port));return true;}catch (Exception ex){Logger.Error(ex, "Modbus連接失敗");return false;}}public async Task DisconnectAsync(){_master?.Dispose();_master = null;}public async Task<object> ReadTagAsync(string tagName){// 解析標簽地址,如 "40001" 表示保持寄存器地址1if (int.TryParse(tagName, out int address)){try{ushort[] values = await _master.ReadHoldingRegistersAsync(1, (ushort)(address - 40001), 1);return values[0];}catch (Exception ex){Logger.Error(ex, "讀取Modbus標簽失敗");return null;}}return null;}public async Task<bool> WriteTagAsync(string tagName, object value){if (int.TryParse(tagName, out int address) && value is short shortValue){try{await _master.WriteSingleRegisterAsync(1, (ushort)(address - 40001), (ushort)shortValue);return true;}catch (Exception ex){Logger.Error(ex, "寫入Modbus標簽失敗");return false;}}return false;}public event EventHandler<DataChangedEventArgs> DataChanged;
}// OPC UA驅動示例
public class OpcUaDriver : IDeviceDriver
{private OpcUaClient _client;private string _endpointUrl;public string Name => "OPCUA";public bool IsConnected => _client != null && _client.Connected;public OpcUaDriver(string endpointUrl){_endpointUrl = endpointUrl;}public async Task<bool> ConnectAsync(){try{_client = new OpcUaClient();await _client.Connect(_endpointUrl);return true;}catch (Exception ex){Logger.Error(ex, "OPC UA連接失敗");return false;}}public async Task DisconnectAsync(){_client?.Disconnect();}public async Task<object> ReadTagAsync(string tagName){try{return await _client.ReadNode(tagName);}catch (Exception ex){Logger.Error(ex, "讀取OPC UA標簽失敗");return null;}}public async Task<bool> WriteTagAsync(string tagName, object value){try{await _client.WriteNode(tagName, value);return true;}catch (Exception ex){Logger.Error(ex, "寫入OPC UA標簽失敗");return false;}}public event EventHandler<DataChangedEventArgs> DataChanged;
}

3. 實時數據庫

// 標簽點類
public class Tag : INotifyPropertyChanged
{private object _value;public string Name { get; set; }public string Address { get; set; }public string DataType { get; set; } = "Int16";public string Description { get; set; }public string DriverName { get; set; }public object Value{get => _value;set{if (!Equals(_value, value)){_value = value;OnPropertyChanged();ValueChanged?.Invoke(this, EventArgs.Empty);}}}public DateTime Timestamp { get; set; }public Quality Quality { get; set; } = Quality.Good;public event EventHandler ValueChanged;public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}// 實時數據庫
public class RealTimeDatabase
{private readonly ConcurrentDictionary<string, Tag> _tags = new ConcurrentDictionary<string, Tag>();private readonly List<IDeviceDriver> _drivers = new List<IDeviceDriver>();private Timer _scanTimer;public void AddDriver(IDeviceDriver driver){_drivers.Add(driver);driver.DataChanged += OnDriverDataChanged;}public void AddTag(Tag tag){_tags[tag.Name] = tag;}public Tag GetTag(string name){return _tags.TryGetValue(name, out var tag) ? tag : null;}public void StartScan(int intervalMs = 1000){_scanTimer = new Timer(async _ => await ScanAllTagsAsync(), null, 0, intervalMs);}public void StopScan(){_scanTimer?.Dispose();}private async Task ScanAllTagsAsync(){foreach (var tag in _tags.Values){var driver = _drivers.FirstOrDefault(d => d.Name == tag.DriverName);if (driver != null && driver.IsConnected){try{var value = await driver.ReadTagAsync(tag.Address);tag.Value = value;tag.Timestamp = DateTime.Now;tag.Quality = Quality.Good;}catch (Exception ex){Logger.Error(ex, $"掃描標簽{tag.Name}失敗");tag.Quality = Quality.Bad;}}}}private void OnDriverDataChanged(object sender, DataChangedEventArgs e){// 處理設備主動上報的數據變化foreach (var tag in _tags.Values.Where(t => t.Address == e.Address && t.DriverName == ((IDeviceDriver)sender).Name)){tag.Value = e.Value;tag.Timestamp = DateTime.Now;tag.Quality = Quality.Good;}}public async Task<bool> WriteTag(string tagName, object value){var tag = GetTag(tagName);if (tag == null) return false;var driver = _drivers.FirstOrDefault(d => d.Name == tag.DriverName);if (driver == null || !driver.IsConnected) return false;try{return await driver.WriteTagAsync(tag.Address, value);}catch (Exception ex){Logger.Error(ex, $"寫入標簽{tagName}失敗");return false;}}
}

4. 圖形元素數據綁定

// 數據綁定系統
public class DataBindingManager
{private readonly RealTimeDatabase _database;private readonly Dictionary<GraphicElement, List<BindingInfo>> _bindings = new Dictionary<GraphicElement, List<BindingInfo>>();public DataBindingManager(RealTimeDatabase database){_database = database;}public void BindProperty(GraphicElement element, string propertyName, string tagName, BindingMode mode = BindingMode.OneWay){if (!_bindings.ContainsKey(element)){_bindings[element] = new List<BindingInfo>();}var tag = _database.GetTag(tagName);if (tag == null) return;var bindingInfo = new BindingInfo{PropertyName = propertyName,Tag = tag,Mode = mode};_bindings[element].Add(bindingInfo);// 初始值UpdateElementProperty(element, bindingInfo);// 訂閱變化if (mode != BindingMode.OneTime){tag.ValueChanged += (s, e) => UpdateElementProperty(element, bindingInfo);}// 雙向綁定if (mode == BindingMode.TwoWay){// 這里需要根據元素類型設置相應的事件處理if (element is ButtonElement button){button.Clicked += async (s, e) => {await _database.WriteTag(tagName, !(bool)(tag.Value ?? false));};}}}private void UpdateElementProperty(GraphicElement element, BindingInfo bindingInfo){var property = element.GetType().GetProperty(bindingInfo.PropertyName);if (property != null && property.CanWrite){// 在主線程更新UIApplication.Current.Dispatcher.Invoke(() =>{try{var convertedValue = ConvertValue(bindingInfo.Tag.Value, property.PropertyType);property.SetValue(element, convertedValue);}catch (Exception ex){Logger.Error(ex, $"更新元素屬性{bindingInfo.PropertyName}失敗");}});}}private object ConvertValue(object value, Type targetType){if (value == null) return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;if (targetType.IsInstanceOfType(value)) return value;try{return Convert.ChangeType(value, targetType);}catch{return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;}}
}public class BindingInfo
{public string PropertyName { get; set; }public Tag Tag { get; set; }public BindingMode Mode { get; set; }
}public enum BindingMode
{OneTime,OneWay,TwoWay
}

5. 主界面和編輯器

// 主窗口
public partial class MainWindow : Window
{private RealTimeDatabase _database;private DataBindingManager _bindingManager;private GraphicScreen _currentScreen;public MainWindow(){InitializeComponent();// 初始化數據庫和綁定管理器_database = new RealTimeDatabase();_bindingManager = new DataBindingManager(_database);// 加載配置LoadConfiguration();// 啟動掃描_database.StartScan();}private void LoadConfiguration(){// 加載設備驅動var modbusDriver = new ModbusTcpDriver("192.168.1.10");_database.AddDriver(modbusDriver);// 加載標簽點var tags = ConfigLoader.LoadTags("tags.json");foreach (var tag in tags){_database.AddTag(tag);}// 加載畫面_currentScreen = ConfigLoader.LoadScreen("main_screen.json");}protected override void OnRender(DrawingContext drawingContext){base.OnRender(drawingContext);_currentScreen?.Render(drawingContext);}protected override void OnMouseDown(MouseButtonEventArgs e){base.OnMouseDown(e);var position = e.GetPosition(this);// 檢查是否點擊了某個元素foreach (var element in _currentScreen.Elements.Reverse()){if (element.HitTest(position)){SelectElement(element);break;}}}private void SelectElement(GraphicElement element){// 顯示屬性面板propertyGrid.SelectedObject = element;}protected override void OnClosed(EventArgs e){base.OnClosed(e);_database.StopScan();}
}

參考代碼 基于C#簡單的組態軟件開發 www.youwenfan.com/contentcse/111974.html

項目結構和擴展功能

項目結構建議

SCADA-Solution/
├── SCADA.Core/          # 核心庫
│   ├── Drivers/         # 設備驅動
│   ├── Graphics/        # 圖形元素
│   ├── Database/        # 實時數據庫
│   └── Binding/         # 數據綁定
├── SCADA.Editor/        # 圖形編輯器
├── SCADA.Runtime/       # 運行時環境
└── SCADA.Common/        # 公共工具類

這個簡單的組態軟件開發指南涵蓋了核心功能和實現方法。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/95026.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/95026.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/95026.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Maya綁定:人物綁定詳細案例(創建骨骼、鏡像骨骼、IK創建、IK打組、IK控制器、FK控制器、烘焙動畫、導出)

目錄 壹 創建骨骼 1 準備一個模型 2 創建骨骼 腿部骨骼 軀體骨骼 嘴巴骨骼 披風骨骼 手臂骨骼 手指骨骼 3 給骨骼命名 4 調整關節的坐標軸 測試 5 鏡像骨骼 貳 控制器 一 腳部控制 IK 1 腳部IK創建 腿部IK 腳掌IK 2 腳部IK打組 動作1&#xff1a;腳掌著地&…

手寫MyBatis第46彈:多插件責任鏈模式的實現原理與執行順序奧秘--MyBatis插件架構深度解析

&#x1f942;(???)您的點贊&#x1f44d;?評論&#x1f4dd;?收藏?是作者創作的最大動力&#x1f91e;&#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;點贊&#x1f44d;收藏??留言&#x1f4dd;歡迎留言討論&#x1f525;&#x1f525;&am…

宜春城區光纖鋪設及接口實地調研

一、研究方向與近期關注 因為課題研究的原因&#xff0c;最近對城市骨干網非常感興趣。前期我討論了5G&#xff0c;WiFi及自組網等無線通信網絡情況&#xff0c;感興趣的朋友可以移步我的博客閱讀&#xff1a; 5G無線通信網絡場景&#xff08;日常、工業&#xff09;及拓撲結…

Tomcat 企業級運維實戰系列(六):綜合項目實戰:Java 前后端分離架構部署

Tomcat 企業級運維實戰系列&#xff08;六&#xff09;&#xff1a;綜合項目實戰&#xff1a;Java 前后端分離架構部署一&#xff1a;概述二&#xff1a;部署1&#xff09;環境準備2&#xff09;部署數據庫3&#xff09;部署后端4&#xff09;部署前端總結&#x1f680; Tomcat…

《Unity Shader入門精要》學習筆記四(高級紋理)

1、立方體紋理解釋&#xff1a;站在一個完全透明的玻璃盒子中心&#xff0c;就可以看到6個面。把這個玻璃盒子的6個面都貼上一張照片。這6張照片合起來&#xff0c;就記錄了周圍360度的環境&#xff0c;比如藍天、地面、建筑、樹木等。在2D紋理中&#xff0c;使用坐標來找顏色&…

局域網中使用Nginx部署https前端和后端

目錄 一.前端部署https 二.后端部署https 一.前端部署https 1.前端正常創建項目即可,打包后,文件夾的格式是dist 2.下載認證的證書 也可以使用其他軟件,這里推薦使用mkcert,下載地址如下: Releases FiloSottile/mkcert GitHub 3.輸入 mkcert -install

K8s卷機制:數據持久化與共享

在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;卷&#xff08;Volume&#xff09; 是用于解決容器內數據持久化、容器間數據共享以及與外部存儲交互的核心機制。它本質上是一個可供 Pod 中容器訪問的存儲目錄&#xff0c;生命周期獨立于容器&#xff08;容器重啟或銷…

線性回歸原理推導與應用(十一):多重共線性

多重共線性的定義與影響 多重共線性&#xff08;Multicollinearity&#xff09;是指線性回歸模型中的解釋變量之間由于存在精確相關關系或高度相關關系而使模型估計失真或難以估計準確。 根據定義和影響程度&#xff0c;可以將多重共線性分為極端共線性和一般共線性。極端共線…

day082-初識ElasticStack

文章目錄0. 老男孩思想-人性十大需求1. ElasticStack介紹1.1 ELK&#xff08;**Elastic Stack**&#xff09;1.2 logstash和filebeat的區別2. ElasticSearch單點部署2.1 下載ElasticSearch軟件包2.2 安裝軟件并修改配置文件2.3 啟動并測試服務3. ElasticSearch集群部署3.1 安裝…

軟考 系統架構設計師系列知識點之雜項集萃(139)

接前一篇文章:軟考 系統架構設計師系列知識點之雜項集萃(138) 第257題 系統工程利用計算機作為工具,對系統的結構、元素、()和反饋等進行分析,以達到最優()、最優設計、最優管理和最優控制的目的。霍爾(A.D.Hall)于1969年提出了系統方法的三維結構體系,通常稱為霍…

solidity地址、智能合約、交易概念

目錄地址address 的兩種子類型&#xff08;Solidity 0.5.0&#xff09;address分類address 的常用操作和屬性總結交易交易的基本結構&#xff08;由外部發起&#xff09;Gas交易生命周期函數調用與交易常見交易場景總結地址 在 Solidity 中&#xff0c;地址&#xff08;addres…

jwt原理及Java中實現

一、JWT 是什么&#xff1f;解決什么問題&#xff1f; 我們先來一張圖看一下這個過程&#xff1a;JWT&#xff08;JSON Web Token&#xff09;是一種把“認證信息&#xff08;Claims&#xff09; 完整性校驗”打包成 自包含 的字符串的規范。 它主要用于無狀態認證&#xff1a;…

大數據在UI前端的應用深化研究:用戶行為數據的跨平臺關聯分析

大數據在UI前端的應用深化研究&#xff1a;用戶行為數據的跨平臺關聯分析每天&#xff0c;你在手機 App 里點了一個按鈕、在網頁上滑了兩屏、又在小程序里停留了 3 秒&#xff0c;這些看似零散的動作&#xff0c;其實都在被悄悄記錄。過去&#xff0c;這些數據只能各自躺在自己…

C++11基礎——— 右值引用和移動語義

1. C11的發展歷史 C11是C的第?個主要版本&#xff0c;并且是從C98起的最重要更新。它引入了大量更改&#xff0c;標準化了既有實踐&#xff0c;并改進了對C程序員可用的抽象。在它最終由ISO在2011年8月12日采納前&#xff0c;人們曾使用名稱“C0x”&#xff0c;因為它曾被期待…

【一】Django框架版本介紹

【一】Django框架版本介紹 【一】Django框架版本 ● Django 是一個高級的Python Web框架&#xff0c;由荷蘭人Armin Ronacher創建。 ● 隨著版本的迭代和功能的不斷優化&#xff0c;Django在處理異步請求方面也有了顯著的進步。 【1】Django1.x ● 默認不支持異步 ● Django 1.…

git 大文件上傳不了的 問題

你 還是在 cmd 里執行&#xff0c;Select-String 是 PowerShell 的命令&#xff0c;cmd 不認識。 請務必按下面的步驟 切換到 PowerShell 再運行。? 1. 打開 PowerShell&#xff08;不要再用 cmd&#xff09;最簡單&#xff1a; 在資源管理器里進入 D:\linShiWenjian\my-react…

【FIX】go運行報錯“missing go.sum entry for module providing package”解決方案

&#x1f527; ?核心解決方案?**運行 go mod tidy**? ?作用?&#xff1a;自動同步 go.mod和 go.sum文件&#xff0c;添加缺失依賴并移除無用條目。 go mod tidy?適用場景?&#xff1a;90% 的校驗和缺失問題可通過此命令解決。 ?注意?&#xff1a;若項目含私有倉庫&…

【實操教學】ArcGIS 如何進行定義坐標系

一、坐標系定義的方式創建數據時可直接完成坐標系定義&#xff1b;針對已創建的數據集&#xff08;涵蓋要素類、要素數據集及柵格數據集&#xff09;&#xff0c;則可通過以下這種方式定義&#xff1a;工具箱工具調用&#xff1a;使用 ArcGIS 工具箱中的 “定義投影&#xff08…

如何使用Windows自帶的PnPUtil命令來禁用/停用和啟用硬件設備

我來詳細講解一下如何使用 Windows 自帶的 PnPUtil 命令來禁用&#xff08;停用&#xff09; 和啟用硬件設備。 PnPUtil (即插即用實用工具) 是一個功能強大的命令行工具&#xff0c;主要用于安裝、卸載、枚舉和修改驅動程序包。對于硬件的啟用和禁用&#xff0c;它通過操作設…

鴻蒙Next媒體展示組件實戰:Video與動態布局全解析

今天我們來深入探討HarmonyOS Next中幾種核心媒體展示組件的使用方法&#xff0c;通過實際代碼示例展示如何打造豐富的多媒體體驗。HarmonyOS Next為開發者提供了一套強大而靈活的媒體展示組件&#xff0c;使開發者能夠輕松實現視頻播放、動態布局適應、全屏切換等常見多媒體功…