2025-09-30
C#
00

目录

摘要
1. 读取功能
2. 写入功能
3. 断开连接
4. 连接服务器
正文

摘要


1. 读取功能

  • 同步读取
    • 单个地址读:允许用户读取指定地址上的数据。
    • 给定地址列表读:允许用户一次性读取多个指定地址上的数据。
    • 读全部:读取所有可用的数据项,通常用于初始化或状态监控。
  • 异步读取
    • 单个地址读:允许用户异步方式读取指定地址上的数据,以避免阻塞应用程序。
    • 给定地址列表读:通过异步操作,提高读取多个地址时的性能。
    • 读全部:同样以异步方式读取所有可用的数据项,用于高效的数据更新和监控。

2. 写入功能

  • 同步写
    • 单个写:将数据写入到指定的单个地址。
    • 多个写:将数据写入到多个指定地址,实现批量更新。
  • 异步写
    • 单个写:以异步方式写入数据至指定地址,提升响应速度。
    • 多个写:异步批量写入至多个地址,以提高写入性能并减小对应用程序主线程的影响。

3. 断开连接

  • 管理与 OPC DA 服务器的连接,确保能够安全断开,并进行必要的清理。

4. 连接服务器

  • 连接:负责与指定的 OPC DA 服务器建立连接。
  • 初始化属性:在建立连接后,初始化相关的通信属性和参数,以确保系统在使用时处于正常工作状态。
  • 绑定事件:将必要的事件处理程序绑定到 OPC DA 服务器事件,以处理数据变化、连接丢失等情况。

正文

image.png

C#
public class OpcHelper { OPCServer MyOpcServer; OPCGroup MyOpcGroup; public string Ip { get; set; } public string ProgId { get; set; } = "Kepware.KEPServerEX.V6"; public bool IsActive { get; set; } = true; //控制对组的异步通知。已订阅的组从服务器接收数据更改。 public bool IsSubscribed { get; set; } = false; //当数据变化超过deadband设置时,就会触发datachange事件。或者是,只有当数据变化超过deadband设置时才会触发datachange事件 public int DeadBand { get; set; } = 0; //可能触发数据更改事件的最快速率。缓慢的进程可能会导致数据更改以低于此速率触发,但它们永远不会超过此速率。速率单位为毫秒。 public int UpdateRate { get; set; } = 100; //用于保存要访问的数据 private Dictionary<string, OPCItem> _dicOpcItem { get; set; } = new Dictionary<string, OPCItem>(); private event Action<Dictionary<string, object>> ReadFinishEvent; private List<string> _ReadAddress { get; set; } private event Action<bool> WriteFinishEvent; public event Action<Dictionary<string, object>> DataChangeEvent; public bool IsDataChangeEvent { get; set; } = false;//是否启用DataChange事件 public bool Connect() { try { MyOpcServer = new OPCServer(); MyOpcServer.Connect(ProgId, Ip); MyOpcGroup = MyOpcServer.OPCGroups.Add("DefaultGroup"); MyOpcGroup.IsActive = IsActive; MyOpcGroup.IsSubscribed = IsSubscribed; MyOpcGroup.DeadBand = DeadBand; MyOpcGroup.UpdateRate = UpdateRate; MyOpcGroup.AsyncReadComplete += MyOpcGroup_AsyncReadComplete; MyOpcGroup.AsyncWriteComplete += MyOpcGroup_AsyncWriteComplete; if (IsDataChangeEvent) { MyOpcGroup.DataChange += MyOpcGroup_DataChange; } return true; } catch (Exception ex) { //do log return false; } } /// <summary> /// 数据改变事件 /// </summary> /// <param name="TransactionID"></param> /// <param name="NumItems"></param> /// <param name="ClientHandles"></param> /// <param name="ItemValues"></param> /// <param name="Qualities"></param> /// <param name="TimeStamps"></param> private void MyOpcGroup_DataChange(int TransactionID, int NumItems , ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps) { if (DataChangeEvent != null) { Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (var item in _dicOpcItem) { dic.Add(item.Key, item.Value.Value); } DataChangeEvent(dic); } } //添加读写的节点信息 public bool AddOpcItems(string Address) { if (_dicOpcItem.ContainsKey(Address)) { return false; } OPCItem item = MyOpcGroup.OPCItems.AddItem(Address, 0); _dicOpcItem.Add(Address, item); return true; } //删除读写的节点 public bool RemoveOpcItems(string Address) { if (_dicOpcItem.ContainsKey(Address)) { _dicOpcItem[Address] = null; _dicOpcItem.Remove(Address); } return true; } //同步读取某一个节点数据 public object ReadValueSyn(string Address) { object ItemValues; object Qualities; object TimeStamps; OPCItem item = _dicOpcItem[Address]; item.Read(1, out ItemValues, out Qualities, out TimeStamps); return ItemValues; } //同步读取指定列表节点数据 public Dictionary<string, object> ReadAllSyn(Dictionary<string, object> dic) { object ItemValues; object Qualities; object TimeStamps; foreach (var item in dic) { OPCItem it = _dicOpcItem[item.Key].Value; it.Read(1, out ItemValues, out Qualities, out TimeStamps); dic[item.Key] = ItemValues; } return dic; } //同步读取所有节点数据 public Dictionary<string, object> ReadAllSyn() { object ItemValues; object Qualities; object TimeStamps; Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (var item in _dicOpcItem) { OPCItem it = item.Value; it.Read(1, out ItemValues, out Qualities, out TimeStamps); dic.Add(item.Key, ItemValues); } return dic; } //同步写 public void WriteValueSync(string Address, object Value) { int[] tmp = new int[] { 0, _dicOpcItem[Address].ServerHandle }; Array serverHandles = (Array)tmp; Array Errors; int cancelID; object[] v = new object[] { "", Value }; Array values = (Array)v; MyOpcGroup.SyncWrite(1, ref serverHandles, ref values, out Errors); } //同步按组写 public void WriteValueSync(Dictionary<string, object> dic) { int[] tmp = new int[dic.Count + 1]; int idx = 1; foreach (var item in dic) { tmp[idx] = _dicOpcItem[item.Key].ServerHandle; idx++; } object[] v = new object[dic.Count + 1]; idx = 1; foreach (var item in dic) { v[idx] = item.Value; idx++; } Array serverHandles = (Array)tmp; Array Errors; Array values = (Array)v; MyOpcGroup.SyncWrite(dic.Count, ref serverHandles, ref values, out Errors); } /// <summary> /// 异步读取信息 /// </summary> public void ReadValueAsync(List<string> Address, Action<Dictionary<string, object>> action) { _ReadAddress = Address; ReadFinishEvent = action; int[] tmp = new int[Address.Count + 1]; for (int i = 1; i < tmp.Length; i++) { tmp[i] = _dicOpcItem[Address[i - 1]].ServerHandle; } Array serverHandles = (Array)tmp; Array Errors; int cancelID; MyOpcGroup.AsyncRead(1, ref serverHandles, out Errors, 1, out cancelID); } /// <summary> /// 异步读的完成回调 /// </summary> /// <param name="TransactionID"></param> /// <param name="NumItems"></param> /// <param name="ClientHandles"></param> /// <param name="ItemValues"></param> /// <param name="Qualities"></param> /// <param name="TimeStamps"></param> /// <param name="Errors"></param> private void MyOpcGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles , ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors) { if (ReadFinishEvent != null) { Dictionary<string, object> dic = new Dictionary<string, object>(); for (int i = 0; i < _ReadAddress.Count; i++) { dic.Add(_ReadAddress[i], _dicOpcItem[_ReadAddress[i]].Value); } ReadFinishEvent(dic); } } public void WriteValueAsync(Dictionary<string, object> dic, Action<bool> action) { this.WriteFinishEvent = action; int[] tmp = new int[dic.Count + 1]; int idx = 1; foreach (var item in dic) { tmp[idx] = _dicOpcItem[item.Key].ServerHandle; idx++; } object[] v = new object[dic.Count + 1]; idx = 1; foreach (var item in dic) { v[idx] = item.Value; idx++; } Array serverHandles = (Array)tmp; Array Errors; Array values = (Array)v; MyOpcGroup.AsyncWrite(dic.Count, ref serverHandles, ref values, out Errors, 1, out var cancelId); } /// <summary> /// 写完成回调 /// </summary> /// <param name="TransactionID"></param> /// <param name="NumItems"></param> /// <param name="ClientHandles"></param> /// <param name="Errors"></param> private void MyOpcGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors) { if (WriteFinishEvent != null) { int[] tmp = new int[Errors.Length]; Errors.CopyTo(tmp, 0); int ret = Array.Find(tmp, x => x == 1); WriteFinishEvent(ret == 0); } } /// <summary> /// 断开连接 /// </summary> public void DisConnect() { MyOpcServer.Disconnect(); } }

在这个基础上可以考虑合并读取

给每个方法加上事件

界面开发

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace opcda01 { public partial class FrmOpc : Form { OpcHelper opc = new OpcHelper(); public FrmOpc() { InitializeComponent(); lsvMain.Columns.Add("B1"); lsvMain.Columns.Add("B2"); lsvMain.Columns.Add("B3"); lsvMain.Columns.Add("F1"); lsvMain.Columns.Add("I1"); lsvMain.Columns.Add("L1"); lsvMain.Columns.Add("S1"); lsvMain.Columns.Add("S2"); ListViewItem item = new ListViewItem(); item.Text = ""; item.SubItems.Add(""); item.SubItems.Add(""); item.SubItems.Add(""); item.SubItems.Add(""); item.SubItems.Add(""); item.SubItems.Add(""); item.SubItems.Add(""); lsvMain.Items.Add(item); } private void btnConnect_Click(object sender, EventArgs e) { opc.Ip = "127.0.0.1"; opc.IsSubscribed= true; opc.IsDataChangeEvent= true; opc.Connect(); opc.AddOpcItems("Chanel1.Device1.B1"); opc.AddOpcItems("Chanel1.Device1.B2"); opc.AddOpcItems("Chanel1.Device1.B3"); opc.AddOpcItems("Chanel1.Device1.F1"); opc.AddOpcItems("Chanel1.Device1.I1"); opc.AddOpcItems("Chanel1.Device1.L1"); opc.AddOpcItems("Chanel1.Device1.S1"); opc.AddOpcItems("Chanel1.Device1.S2"); } private void btnRead_Click(object sender, EventArgs e) { var ret = opc.ReadValueSyn("Chanel1.Device1.B1"); lsvMain.Items[0].SubItems[0].Text = ret.ToString(); } private void btnReadAll_Click(object sender, EventArgs e) { Dictionary<string, object> dic = opc.ReadAllSyn(); int idx = 0; foreach (var item in dic) { lsvMain.Items[0].SubItems[idx].Text = item.Value.ToString(); idx++; } } private void btnWrite_Click(object sender, EventArgs e) { opc.WriteValueSync("Chanel1.Device1.S2", "Hello"); } private void btnWriteBatch_Click(object sender, EventArgs e) { Dictionary<string, object> dic = new Dictionary<string, object>(); dic.Add("Chanel1.Device1.S1","A"); dic.Add("Chanel1.Device1.S2", "B"); opc.WriteValueSync(dic); } private void btnReadValueAsync_Click(object sender, EventArgs e) { List<string> Address = new List<string>() { "Chanel1.Device1.B1", "Chanel1.Device1.B2", "Chanel1.Device1.F1" }; opc.ReadValueAsync(Address, (obj)=> { var d = obj; }); } private void btnWriteAsync_Click(object sender, EventArgs e) { Dictionary<string, object> dic = new Dictionary<string, object>(); dic.Add("Chanel1.Device1.S1", "A"); dic.Add("Chanel1.Device1.S2", "B"); opc.WriteValueAsync(dic, (obj) => { var d = obj; }); } private void btnDataChange_Click(object sender, EventArgs e) { opc.DataChangeEvent -= Opc_DataChangeEvent; opc.DataChangeEvent += Opc_DataChangeEvent; } private void Opc_DataChangeEvent(Dictionary<string, object> dic) { int idx = 0; foreach (var item in dic) { this.Invoke(() => { lsvMain.Items[0].SubItems[idx].Text = item.Value.ToString(); }); idx++; } //这个是用来刷chart图的 int l = int.Parse(dic["Chanel1.Device1.L1"].ToString()); chart1.Invoke(Draw,new object[] {l}); } } }

考虑加一个chart图,跟踪L1

安装WinForms.DataVisualization

  • 申明Chart
  • 申明Title
  • 申明ChartArea
  • 申明 Series
  • 指定Series的类型
C#
private void InitChart() { Title title = new Title(); //标题内容 title.Text = "L1"; //标题的字体 title.Font = new System.Drawing.Font("Microsoft Sans Serif", 12, FontStyle.Bold); //标题字体颜色 title.ForeColor = Color.FromArgb(26, 59, 105); //标题阴影颜色 title.ShadowColor = Color.FromArgb(32, 0, 0, 0); //标题阴影偏移量 title.ShadowOffset = 3; chart1.Titles.Add(title); Series series = new Series(); series.ChartType = SeriesChartType.Spline; chart1.Series.Add(series); ChartArea chartArea = new ChartArea(); //背景色 chartArea.BackColor = Color.FromArgb(64, 165, 191, 228); //背景渐变方式 chartArea.BackGradientStyle = GradientStyle.TopBottom; //渐变和阴影的辅助背景色 chartArea.BackSecondaryColor = Color.White; //边框颜色 chartArea.BorderColor = Color.FromArgb(64, 64, 64, 64); //阴影颜色 chartArea.ShadowColor = Color.Transparent; //设置X轴和Y轴线条的颜色和宽度 chartArea.AxisX.LineColor = Color.FromArgb(64, 64, 64, 64); chartArea.AxisX.LineWidth = 1; chartArea.AxisY.LineColor = Color.FromArgb(64, 64, 64, 64); chartArea.AxisY.LineWidth = 1; chart1.ChartAreas.Add(chartArea); chart1.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm:ss"; chart1.ChartAreas[0].AxisX.ScaleView.Size = 5; chart1.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true; chart1.ChartAreas[0].AxisX.ScrollBar.Enabled = true; chart1.Series[0].IsXValueIndexed = true; chart1.Series[0].XValueType = System.Windows.Forms.DataVisualization .Charting.ChartValueType.Time; chart1.Dock=DockStyle.Fill; pnlChart.Controls.Add(chart1); } private void Draw(int value) { PointMessage point = new PointMessage() { Dt = DateTime.Now, Value =value }; if (chart1.Series[0].Points.Count >= 10) { chart1.Series[0].Points.RemoveAt(0); } chart1.Series[0].Points.AddXY(point.Dt, point.Value); chart1.ChartAreas[0].AxisX.ScaleView.Position = chart1.Series[0].Points.Count - 5; }

基础类

C#
public class PointMessage { public DateTime Dt { get; set; } public int Value { get; set; } }

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!