C#简单组态软件开发

发布于:2025-09-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

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" 表示保持寄存器地址1
        if (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)
        {
            // 在主线程更新UI
            Application.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/        # 公共工具类

这个简单的组态软件开发指南涵盖了核心功能和实现方法。


网站公告

今日签到

点亮在社区的每一天
去签到