AI摘要FreeSCADA 2 是一款开源 SCADA 系统,基于微软技术栈构建,采用 .NET/C#/WPF/XAML 技术。它支持纯 XAML 语法定义矢量图形,并可将 XAML 属性直接绑定到标签值,实现动态数据可视化展示,内置 OPC、ModBus 和 SNMP 等多种通信协议驱动程序,在工业自动化领域具有广泛应用潜力。系统架构主要由 Designer 和 Run Time 两个核心模块以及其他辅助模块和插件组成。Designer 是用于创建和配置项目的开发工具,提供可视化界面,允许用户定义与数据源的链接、设置归档规则等;Run Time 负责项目的实际运行和监控,实现数据的实时采集、处理、存储、归档等功能。
项目简介

FreeSCADA 2 是一款基于微软技术栈的开源 SCADA(监控与数据采集)系统,采用 .NET/C#/WPF/XAML 等技术构建。它支持使用纯 XAML 语法定义矢量图形,并可将 XAML 属性直接绑定到标签值,实现动态数据的可视化展示。此外,还内置了 OPC、ModBus 和 SNMP 等多种通信协议的驱动程序,在工业自动化领域具有广泛应用潜力。
克隆项目
打开命令行工具,执行以下命令以克隆FreeSCADA项目到本地:
|
git clone https://github.com/AlexDovgan/FreeSCADA.git |
项目架构
- 整体架构 :FreeSCADA 2 的架构主要由 Designer 和 Run Time 两个核心模块以及其他辅助模块和插件等组成。
- Designer :是用于创建和配置项目的开发工具,提供了可视化界面,允许用户定义与数据源的链接、设置归档规则、声明报警及其预期用户反应、创建可视化方案和报告模板、设置报告生成计划等功能。例如,用户可以在 Designer 中通过简单的拖拽操作和配置,建立起与 PLC 等设备的连接,并设计出相应的监控画面和数据展示布局。
- Run Time :负责项目的实际运行和监控,实现数据的实时采集、处理、存储、归档,以及报警生成、数据可视化、报告生成等功能。它能够将 Designer 中配置好的项目部署到实际运行环境中,并按照设定的规则进行数据采集和处理,同时以图形化的方式将数据展示给用户。例如,在工业生产过程中,Run Time 可以实时采集生产数据,并在界面上动态显示生产状态和相关参数,当出现异常情况时及时发出报警。
- 数据流架构 :数据采集模块从各种设备和数据源中获取数据,然后通过数据处理模块进行清洗、转换和计算等操作,再将处理后的数据存储到数据库中。数据存储模块为数据查询和分析模块提供支持,以便用户能够快速检索和分析历史数据。同时,系统还会根据配置的规则生成相应的报警信息,并通过报警管理模块通知用户。在数据展示方面,系统利用 WPF 和 XAML 的强大功能,将实时数据和历史数据以图表、趋势图、仪表盘等多种形式直观地展示给用户。
- 通信架构 :FreeSCADA 2 内置了多种通信协议的驱动程序,如 OPC、ModBus 和 SNMP 等,通过这些协议,系统能够与各种工业设备和系统进行通信,实现数据的采集和交互。此外,它还提供了通信插件机制,允许用户根据实际需求扩展和定制通信协议,以满足不同设备和场景的通信要求。
主要代码分析

核心模块代码
Designer 模块
位于项目的 Designer
目录下,其核心代码主要包括 MainForm.cs
等文件。MainForm.cs
是 Designer 的主窗口代码,实现了界面的初始化、菜单和工具栏事件处理、项目加载和保存等功能。例如,在项目加载过程中,它会读取项目的配置文件,解析项目的结构和各个组件的配置信息,并在界面上进行相应的显示和初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
// 定义一个静态的WindowManager实例,用于管理窗口 static WindowManager windowManager; // 定义一个私有成员变量,用于存储启动画面 private SplashScreen _splashScreen; /// <summary> /// 构造器 /// </summary> public MainForm() { // 初始化组件,包括窗体和控件的创建 InitializeComponent(); // 设置自动缩放模式为DPI,以适应不同分辨率的屏幕 AutoScaleMode = AutoScaleMode.Dpi; // 设置启动画面 SetSplashScreen(); // 初始化环境,传入当前窗体、主菜单、主工具栏和环境模式 Env.Initialize(this, mainMenu, mainToolbar, FreeSCADA.Interfaces.EnvironmentMode.Designer); // 初始化归档系统 ArchiverMain.Initialize(); // 初始化文件操作的命令上下文 CommandManager.fileContext = new BaseCommandContext(fileToolStripMenuItem.DropDown, mainToolbar); // 初始化视图操作的命令上下文 CommandManager.viewContext = new BaseCommandContext(viewSubMenu.DropDown, mainToolbar); // 初始化文档编辑的命令上下文 CommandManager.documentContext = new BaseCommandContext(editSubMenu.DropDown, mainToolbar); // 添加帮助按钮到主菜单 ToolStripMenuItem newItem = new ToolStripMenuItem(StringResources.CommandContextHelp); mainMenu.Items.Add(newItem); // 初始化帮助操作的命令上下文 CommandManager.helpContext = new BaseCommandContext(newItem.DropDown, null); // 添加检查更新的命令到帮助上下文 CommandManager.helpContext.AddCommand(new CheckForUpdatesCommand()); // 添加最近使用的文件列表到菜单栏 MRUManager mruManager = new MRUManager(mRU1ToolStripMenuItem, toolStripSeparator2); // 创建并初始化窗口管理器,传入停靠面板和最近使用的文件管理器 windowManager = new WindowManager(dockPanel, mruManager); // 注册项目加载完成的事件处理程序 Env.Current.Project.ProjectLoaded += new EventHandler(OnProjectLoaded); // 更新窗体标题和命令状态 UpdateCaptionAndCommands(); } |
RunTime 模块
主要代码集中在 Runtime
目录中,MainForm
.cs
是其核心文件之一,负责 RunTime 的启动、初始化和运行时管理。它会加载项目的配置,启动数据采集和处理线程,初始化通信协议驱动等,并在运行过程中实时监控系统的状态和数据变化,确保系统的稳定运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
// 定义一个WindowManager类型的变量windowManager,用于管理窗口 WindowManager windowManager; // MainForm的构造函数,用于初始化窗体 public MainForm() { // 调用InitializeComponent方法,初始化窗体组件 InitializeComponent(); // 初始化Env环境,传入当前窗体、主菜单、主工具栏和运行模式 Env.Initialize(this, mainMenu, mainToolbar, FreeSCADA.Interfaces.EnvironmentMode.Runtime); // 初始化ArchiverMain,可能是用于数据归档或日志记录 ArchiverMain.Initialize(); // 设置CommandManager的viewContext,传入视图子菜单的DropDown和主工具栏 CommandManager.viewContext = new BaseCommandContext(viewSubMenu.DropDown, mainToolbar); // 创建一个MRUManager对象,用于管理最近使用的文件,传入最近使用文件菜单项和分隔符 MRUManager mruManager = new MRUManager(mRUstartToolStripMenuItem, toolStripSeparator3); // 创建一个WindowManager对象,传入停靠面板和MRUManager,用于管理窗口 windowManager = new WindowManager(dockPanel, mruManager); // 更新窗体的标题 UpdateCaption(); } // MainForm的另一个构造函数,用于初始化窗体并加载指定的文件 public MainForm(string fileToLoad) { // 调用InitializeComponent方法,初始化窗体组件 InitializeComponent(); // 初始化Env环境,传入当前窗体、主菜单、主工具栏和运行模式 Env.Initialize(this, mainMenu, mainToolbar, FreeSCADA.Interfaces.EnvironmentMode.Runtime); // 初始化ArchiverMain,可能是用于数据归档或日志记录 ArchiverMain.Initialize(); // 设置CommandManager的viewContext,传入视图子菜单的DropDown和主工具栏 CommandManager.viewContext = new BaseCommandContext(viewSubMenu.DropDown, mainToolbar); // 创建一个MRUManager对象,用于管理最近使用的文件,传入最近使用文件菜单项和分隔符 MRUManager mruManager = new MRUManager(mRUstartToolStripMenuItem, toolStripSeparator3); // 创建一个WindowManager对象,传入停靠面板和MRUManager,用于管理窗口 windowManager = new WindowManager(dockPanel, mruManager); // 如果传入的文件路径不为空,则加载该文件 if (fileToLoad != "") windowManager.LoadProject(fileToLoad); // 更新窗体的标题 UpdateCaption(); } |
Common模块
Common 模块是 FreeSCADA 2 的核心基础模块,它为整个系统提供了通用的接口、数据模型、工具类和配置管理等功能,是其他模块(如 Designer、Run Time 等)正常运行所依赖的基石。它就像是一个公共工具箱,里面包含了各种各样的工具和资源,供系统的不同部分在需要时调用和使用。
主要功能与代码细节
- 接口定义
- 数据接口 :Common 模块定义了一系列与数据相关的接口,比如
IDataSource
接口。这个接口规定了数据源的基本操作规范,包括数据的读取、写入、连接和断开等方法。例如,Read
方法用于从数据源读取数据,Write
方法用于向数据源写入数据。这些接口使得不同类型的设备和协议驱动(如 OPC、ModBus 等)能够以统一的方式进行数据交互。当开发者需要添加一个新的数据源驱动时,只需实现 IDataSource
接口,就可以确保它能够与系统的其他部分无缝集成。
- 通信接口 :
ICommunicationDriver
是通信相关的接口,它规定了通信驱动的基本行为,例如建立通信连接、发送和接收数据等。这使得系统能够轻松地扩展对不同通信协议的支持。例如,如果要添加一个新的无线通信协议驱动,开发者可以基于 ICommunicationDriver
接口进行实现,而无需修改系统的其他核心逻辑。
- 数据模型
- 标签数据模型(Tag) :在 Common 模块中,标签(Tag)是数据的基本单位。
Tag
类定义了标签的属性,如名称、数据类型、描述、当前值、质量(表示数据的可靠性,如好、坏、可疑等)、时间戳等。例如,一个温度传感器的标签可能有名称为 “TemperatureSensor1”,数据类型为浮点数,当前值为 25.5℃,质量为 “Good”,时间戳为当前系统时间。这些标签数据模型为系统中的数据采集、存储、处理和可视化提供了统一的数据结构。
- 项目配置数据模型(ProjectConfig) :
ProjectConfig
类用于存储整个项目的配置信息,包括项目的名称、描述、数据源配置、通信协议配置、可视化界面布局配置等。它就像是一个项目的全局配置中心。例如,项目配置中可以定义与哪些 OPC 服务器进行连接,每个连接的参数是什么;定义哪些标签需要进行归档,归档的时间间隔等。这些配置信息在项目的运行过程中会被其他模块频繁地读取和使用。
- 工具类
- 数据处理工具类(DataProcessorUtils) :这个工具类提供了各种数据处理的通用方法,如数据类型转换、数据过滤、数据计算(如计算平均值、最大值、最小值等)等。例如,它有一个
ConvertDataType
方法,可以将一个数据从一种类型(如字符串)转换为另一种类型(如整数)。在数据采集过程中,采集到的数据可能有各种类型,为了统一处理,可以利用这个工具类进行类型转换。
- 日志工具类(Logger) :日志记录对于系统的调试、监控和维护至关重要。
Logger
类提供了方便的日志记录功能,包括记录不同级别的日志(如信息、警告、错误等)。例如,当系统成功连接到一个设备时,可以记录一条信息级别的日志;当数据采集出现异常时,记录一条错误级别的日志。这有助于开发者快速定位问题所在,了解系统的运行状态。
- 配置管理
- 配置文件读取与写入(ConfigManager) :
ConfigManager
类负责读取和写入系统的配置文件。它能够解析配置文件的格式(如 XML 或 JSON),并将配置信息加载到内存中,供系统使用。同时,当配置信息发生变化时,它能够将新的配置写回到配置文件中。例如,在 Designer 模块中修改了项目的数据源配置后,ConfigManager
会将这些修改保存到配置文件中,以便在 Run Time 模块运行时能够读取到最新的配置。
- 配置验证(ConfigValidator) :为了确保系统的稳定运行,
ConfigValidator
类对配置信息进行验证。它会检查配置文件中的参数是否符合规定的范围和格式。例如,检查数据采集周期是否为正数,检查通信端口号是否在有效范围内等。如果配置信息有误,会在系统启动或配置加载时发出警告,提示用户进行修改。
在系统中的作用与与其他模块的交互
- 在系统中的作用 :Common 模块是整个 FreeSCADA 2 系统的 “粘合剂”。它为其他模块提供了统一的接口规范、数据模型和工具,确保了系统的各个部分能够以协调一致的方式工作。没有 Common 模块,其他模块之间就难以进行有效的通信和数据共享。
- 与其他模块的交互 :Designer 模块在创建和配置项目时,会频繁地调用 Common 模块中的接口和工具类。例如,在定义标签时,会使用
Tag
类来设置标签的属性;在配置通信协议时,会参考 ICommunicationDriver
接口的规范。Run Time 模块在运行项目时,会根据 ProjectConfig
类中的配置信息来初始化数据源和通信连接,并利用 DataProcessorUtils
类对采集到的数据进行处理。同时,Run Time 模块也会通过 Logger
类记录运行过程中的各种事件和状态信息。
数据采集
在 Communication
目录下,包含了各种通信协议驱动的实现代码。比如在 FreeSCADA 2 的 Communication.OPCPlug 模块中,ICommunicationDriver 接口发挥着关键作用,它定义了通信驱动的基本规范,使得 OPCPlug 模块能够与系统其他部分高效协同,实现数据的精准传递。
OPCPlug 模块概述
OPCPlug 模块专注于实现与 OPC(OLE for Process Control)服务器的通信,它充当 FreeSCADA 2 系统与 OPC 服务器之间的桥梁,负责从 OPC 服务器获取数据并传递给系统,同时也可将系统数据写回 OPC 服务器。OPC 服务器在工业自动化领域广泛应用于设备数据的采集与整合,因此 OPCPlug 模块对于 FreeSCADA 2 连接各类工业设备、获取实时生产数据至关重要。
ICommunicationDriver 接口在 OPCPlug 中的实现
接口方法实现
连接与断开
Connect
方法:OPCPlug 模块通过 Connect
方法与 OPC 服务器建立连接。它利用 OPC 客户端库(如 OPC .NET API)初始化 OPC 客户端对象,并指定 OPC 服务器的 URL 或地址。在连接过程中,会进行身份验证(如果需要),并检查 OPC 服务器的可用性。例如,当连接到一个工厂车间的 OPC 服务器时,Connect
方法会使用服务器的 IP 地址和端口号等信息,尝试与之建立通信链路。一旦连接成功,模块会记录连接状态,为后续的数据读写操作做好准备。
Disconnect
方法:用于优雅地断开与 OPC 服务器的连接。它会清理 OPC 客户端对象所占用的资源,释放通信通道,并更新连接状态。这有助于避免资源泄漏和通信冲突,确保系统的稳定运行。
数据读取
Read
方法:这是实现从 OPC 服务器读取数据的核心方法。它接收一个数据项标识符(通常符合 OPC 规范的项路径)作为参数。OPCPlug 模块通过 OPC 客户端调用 OPC 服务器的读取接口,获取对应数据项的值、质量(如好、坏、可疑等)和时间戳等信息。例如,对于一个表示电机转速的 OPC 数据项 “Motor.Speed”,Read
方法会从 OPC 服务器获取其当前转速值(如 1450rpm)、数据质量(假设为 “Good”)以及数据更新时间戳。读取到的数据随后会按照 FreeSCADA 2 系统的数据模型进行封装,以便传递给系统的其他模块,如 Run Time 模块进行数据处理和可视化展示。
批量读取:为了提高数据读取效率,OPCPlug 模块还实现了批量读取功能。通过一次性向 OPC 服务器发送多个数据项的读取请求,减少通信开销。例如,在一个复杂的生产线上,需要同时读取多个传感器的数据,批量读取能够显著降低读取延迟,确保数据的实时性。
数据写入
Write
方法:用于将数据写入 OPC 服务器。它接收数据项标识符和要写入的值作为参数。在工业自动化场景中,这可用于向设备发送控制指令或参数设置。例如,向一个阀门的开度控制数据项 “Valve.Position” 写入值 50%,以调整阀门的开度。OPCPlug 模块会通过 OPC 客户端调用 OPC 服务器的写入接口,确保数据正确写入,并处理可能出现的写入错误(如权限不足、数据类型不匹配等),将写入结果反馈给系统。
数据封装与传递
数据封装
读取到的数据或要写入的数据都会按照 FreeSCADA 2 的数据模型(如定义在 Common 模块中的 Tag
类)进行封装。OPCPlug 模块将 OPC 数据项的值、质量、时间戳等信息映射到 Tag
对象的相应属性。例如,OPC 数据项 “Temperature.Sensor1” 的值 85℃ 会被封装到一个 Tag
对象的 Value 属性中,质量 “Good” 对应 Quality 属性,当前系统时间作为 TimeStamp 属性值。这种封装确保了数据在整个 FreeSCADA 2 系统中具有一致的格式和语义,方便其他模块进行处理和使用。
数据传递流程
当 Run Time 模块需要从 OPC 服务器获取数据时,它会通过 OPCPlug 模块的 Read
方法发起请求。OPCPlug 模块利用 ICommunicationDriver 的 Read
实现与 OPC 服务器通信,读取数据并封装为 Tag
对象后,将数据传递回 Run Time 模块。Run Time 模块接收到数据后,会根据配置的规则进行数据处理(如归档、计算等)和可视化展示(在监控界面上更新温度值显示等)。
对于数据写入操作,Run Time 模块会构造包含目标 OPC 数据项和写入值的请求,通过 OPCPlug 模块的 Write
方法发送给 OPC 服务器。OPCPlug 模块借助 ICommunicationDriver 的 Write
实现完成写入操作,并将写入结果(成功或失败及错误信息)反馈给 Run Time 模块,以便系统进行相应的处理(如记录写入失败日志、重新尝试写入等)。
OPCPlug 模块在系统中的位置与交互
与 Run Time 模块交互
OPCPlug 模块紧密集成在 Run Time 模块的数据采集和控制流程中。Run Time 模块根据项目配置中定义的 OPC 通信设置(如 OPC 服务器地址、数据项列表等),调用 OPCPlug 模块初始化通信连接。在运行过程中,按照设定的采集周期或事件触发机制,Run Time 模块通过 OPCPlug 模块与 OPC 服务器交互,实现数据的实时采集和设备控制。例如,在一个化工生产过程监控系统中,Run Time 模块通过 OPCPlug 模块每秒从 OPC 服务器读取反应釜的温度、压力等数据,并根据预设的控制策略,通过 OPCPlug 模块向搅拌电机发送转速调整指令。
与其他模块协同
OPCPlug 模块还会与系统的其他模块(如报警模块、报表模块等)间接交互。例如,当 OPCPlug 模块读取到的数据超出正常范围(如温度过高),会触发报警模块生成报警信息。同时,采集到的历史数据可通过 OPCPlug 模块传递给报表模块,用于生成生产统计报表等。
自定义控件
VisualControls.FS2EasyControls 模块是 FreeSCADA 2 系统中用于提供可视化控件的核心组件库,旨在为用户提供更丰富、便捷的图形化界面元素,用于直观地展示和操作工业自动化过程中的各类数据。这些控件涵盖了从基本的数据显示(如数值、文本)、状态指示(如指示灯、报警器),到复杂的交互式图表(如趋势图、棒图)等多种类型,是实现系统人机交互界面(HMI)的关键。
IVisualControlsPlug 接口在 FS2EasyControls 中的实现
接口方法实现
数据源绑定
BindDataSource
方法:这是实现控件与数据源关联的核心方法。它接收数据源标识符(如标签名称)和数据绑定规则(如数据类型转换、数据更新频率等)作为参数。FS2EasyControls 模块中的每个可视化控件都通过这个方法与 FreeSCADA 2 系统中的特定数据(如从 OPC 服务器、ModBus 设备等采集的数据)建立联系。
例如,对于一个用于显示温度的数字显示屏控件,通过 BindDataSource
方法将其与表示温度传感器数据的标签 “Temperature.Sensor1” 绑定。在绑定过程中,可以指定数据类型为浮点数,更新频率为每秒一次等规则。这样,控件就能够实时获取该温度数据的变化,并在界面上进行相应更新。
数据更新与刷新
UpdateData
方法:用于将最新的数据从数据源拉取到控件,并触发控件的刷新操作。当系统中的数据(如通过 Communication 模块从设备采集到的新数据)发生变化时,FS2EasyControls 模块会调用控件的 UpdateData
方法。该方法内部会根据控件的类型和绑定规则,对数据进行适当的处理(如数值修约、单位转换等),然后更新控件的显示内容。
比如,在一个模拟压力表控件中,当 UpdateData
方法被调用并接收到新的压力值数据时,它会根据压力值计算指针的旋转角度,重新绘制压力表的图形界面,使指针指向对应的压力刻度位置,从而直观地反映当前压力的变化情况。
用户交互响应
OnUserInteraction
方法:用于处理用户与控件之间的交互操作,如点击按钮、调整滑块、输入文本等。当用户对可视化控件进行操作时,FS2EasyControls 模块会捕捉到这些操作事件,并通过 OnUserInteraction
方法进行相应处理。
例如,在一个用于控制电机启停的按钮控件中,当用户点击启动按钮时,OnUserInteraction
方法会识别这个点击事件,然后根据预先定义的控制逻辑(如向 PLC 发送电机启动命令),通过 FreeSCADA 2 系统的通信模块将控制指令发送到相应的设备。同时,它还可以更新按钮本身的显示状态(如改变按钮颜色为绿色表示电机已启动)。
数据封装与传递
数据封装
在 FS2EasyControls 模块中,数据从数据源传递到控件时,会按照 FreeSCADA 2 系统统一的数据模型(如 Common 模块中的 Tag
类)进行封装。控件接收到的数据包含数据值、质量(如好、坏、可疑等)、时间戳等关键信息。例如,从 OPC 服务器获取的流量数据 “FlowMeter1” 会封装为一个 Tag
对象,其 Value 属性为当前流量值(如 120m³/h),Quality 属性为数据质量(假设为 “Good”),TimeStamp 属性为数据更新时间。
数据传递流程
当系统中的数据源(如通过 OPCPlug 模块连接的 OPC 服务器)有数据更新时,数据首先会按照 ICommunicationDriver 的规范传递到 FreeSCADA 2 的数据处理层。然后,数据处理层根据可视化界面的配置(如哪个控件绑定了该数据源),调用 FS2EasyControls 模块中相应控件的 UpdateData
方法。控件接收到数据后,根据自身类型和绑定规则进行处理,并更新显示内容。同时,控件在处理数据过程中,也可以将一些状态信息(如数据是否在正常范围内、是否触发报警等)反馈给系统,以便系统进行进一步的处理(如生成报警信息推送等)。