A for-fun, cross-platform SCADA / HMI toolkit (revived from SharpSCADA)
This repository has been archived on 2026-10-09. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Find a file
todd c92b86fbc2 Scadetto: a cross-platform SCADA / HMI toolkit
A supervisory control and data acquisition application for industrial
monitoring. Avalonia on .NET 10, building and running on Windows, macOS
and Linux from one tree of projects.

It does three things. Acquisition: drivers read values from equipment
over FIP, Modbus or a simulator, loaded per-assembly so one bad driver
does not take the rest down. Display: pages authored as .axaml files on
disk bind to node values and refresh as the values change, alongside
built-in pages for alarms, equipment, logs, database history and PDF
reports. Configuration: a designer edits those pages as markup, with a
control catalogue to insert elements and a property panel that writes
changes back into the file in place.

A solution is a directory: an XML file listing equipment, alarm, page
and database configuration, with the pages themselves beside it. Nothing
in a solution is compiled into the application, so a solution can be
edited, version-controlled and shipped on its own.

Notable choices, each of which cost something and bought something else:

- No drag-and-drop design surface. It needs a design-time hosting
  library with no cross-platform equivalent. Editing the markup covers
  the same ground, because a page has to be written as .axaml whichever
  tool writes it.
- .axaml files are edited in place rather than round-tripped through an
  XML serialiser, which preserves the entity references, indentation,
  attribute order and layout a person chose. A page is the user's data.
- Dependencies come from NuGet or from source in this repository. There
  is no vendored binary anywhere, and a test fails the build if one comes
  back.
- Every project targets net10.0. A Windows-only reference would not break
  the macOS build — the hint path resolves and the assembly never loads —
  so nothing would say so until the app failed to start on a Mac. There
  is a test for that too.

732 tests, run with xunit and Avalonia.Headless. A real-window probe
under tools/ checks what headless cannot: headless does not run control
templates, so a control missing its styles measures correctly, reports
itself visible, and draws nothing.

MIT licensed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-29 14:54:28 +08:00
Alarm Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
bin Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Common Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Database Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
docs Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Driver Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Driver.FIP Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Driver.Modbus Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Driver.Simulator Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
FIPSimulator Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Modbus Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
ModbusSniffer Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.Avalonia Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.Core Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.Designer Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.RunTime Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.Widgets Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Tests Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
tools Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
.gitattributes Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
.gitignore Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
LICENSE Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
README.md Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00
Scadetto.slnx Scadetto: a cross-platform SCADA / HMI toolkit 2026-09-29 14:54:28 +08:00

Scadetto

跨平台的工业监控组态软件。Avalonia + .NET 10,可在 Windows、macOS、Linux 上构建和运行。

License: MIT

  • 许可:MIT
  • 目标框架:net10.0,17 个工程,零平台锁定依赖

Scadetto 做三件事:采集(通过驱动读设备数据)、展示(用画面呈现数据与告警)、 组态(在设计器里编辑画面与设备/告警/数据库配置)。

快速开始

git clone https://git.secevo.xyz/todd/Scadetto.git
cd Scadetto
dotnet build Scadetto.slnx

# 打开示例方案
dotnet run --project Scadetto.RunTime -- test      # 运行时
dotnet run --project Scadetto.Designer -- test    # 设计器

需要 .NET SDK 10.0.400 或更高。首次运行前请确保 bin/config/ 下的示例方案已随构建 复制到输出目录(dotnet build 会自动做这件事,见运行里的注意事项)。

已实现的功能

方案与画面

一个方案(solution)是一组配置文件的集合,放在 config/<方案名>/ 下, 由该目录下的 solution.xml 声明文件清单:

bin/config/test/                # 示例方案
├── solution.xml                # 文件清单:设备、告警、画面、数据库配置(每个方案目录下各一份)
├── Page/*.axaml                # 画面
├── Equipment/*.xml             # 设备与节点定义
├── Alarm/*.xml                 # 告警定义
└── Database/*.xml              # 数据库配置
  • 画面是磁盘上的 .axaml 文件,没有代码后置:根元素用 Grid,不写 x:Class。
  • 画面声明了 isPrimary="true" 的会在运行时启动时开成标签页。
  • 数据绑定写 {scadetto:Node 设备名.节点名},节点值变化时界面自动刷新。 节点名必须与 Equipment/ 下的设备配置一致——写错不会报错,只会解析不到、控件一直空着。
  • 画面加载失败不会静默:标签栏和底部日志栏都会说明哪个画面没能显示。

新建画面用设计器的「新建…」按钮:选模板(空白 Grid / 空白 Canvas / 模拟设备预览)、 填页面名,文件和 solution.xml 声明一起创建,创建完直接打开。

运行时(Scadetto.RunTime)

标签式画面宿主 + 方案选择器 + 底部日志栏。内置页面:

页面 能做什么
告警 列出方案里全部告警项,正在告警的标红;告警状态由设备轮询线程报上来,页面负责切回 UI 线程再改界面
设备 连接/断开,点进单台设备开节点页,可下发写命令
数据库浏览 真实 SQLite 历史查询,支持按时间范围过滤
实时日志 log4net appender 推送,200 条环形缓冲,错误级别过滤,可暂停
日志 按时间与级别筛选、查询、删除所选记录
报表生成 QuestPDF 输出 PDF,报表配置与模板化报表

画面本身作为标签页与这些内置页面并列。

设备通信:Driver 抽象 + 三个驱动——Driver.Simulator(随机数据)、 Driver.FIP、Driver.Modbus。驱动经 MEF 逐程序集加载,一个坏驱动不会拖垮其余。

告警:Alarm 的分层领域模型(Atom / Middle / Range / And),支持范围、 组合与多级融合。语音播报走跨平台 AlarmSpeaker——没有可用引擎时如实报告不可用,不抛异常。

数据存取:SQLite 单文件(Database 工程的 DAL/BLL)。 报表:Scadetto.Core/Reports/ReportDocumentBuilder.cs 用 QuestPDF 输出 PDF。

设计器(Scadetto.Designer)

  • 方案树 → 页面列表 → 源码编辑器(AvaloniaEdit + TextMate)→ 实时预览 + 诊断
  • 控件目录:左侧面板按分类列出可用控件,标记生成后可「添加到页面」, 文本进入当前页面编辑器的缓冲区(不自动保存)
  • 属性面板:列出画面元素、打开对应编辑器,把改动原位写回 .axaml 源码, 保留实体、缩进、属性顺序和作者的排版
  • 专用配置编辑器:设备(Modbus / FIP)、告警、数据库、报表五类都有真正能改能存的 专用编辑器,而不是退化成通用 XML 文本框
  • FIP 帧编辑器支持 fixed / node / verify 三种帧,含边界与重叠验证

预览走 PagePreviewCompiler(编译编辑器里的内存源码),运行时打开磁盘画面走 AvaloniaPageLoader。两者不是同一条路径:带 x:Class 的画面预览能过,运行时加载器会拒绝。

技术栈

用途 组件
UI 框架 Avalonia 12.1.3
图表 LiveChartsCore.SkiaSharpView.Avalonia 2.1.0-dev-798(预发布,见已知限制)
代码编辑器 Avalonia.AvaloniaEdit 12.0.0 + AvaloniaEdit.TextMate + TextMateSharp.Grammars 2.0.4
报表 QuestPDF 2026.9.1 + SkiaSharp 4.152.1
数据库 Microsoft.Data.Sqlite 10.0.12
日志 log4net 3.3.0
插件加载 MEF(System.ComponentModel.Composition 10.0.12)
串口 System.IO.Ports 10.0.12
测试 xunit 2.9.3 + Avalonia.Headless 12.1.3
目标框架 net10.0(全部工程)

项目结构

Scadetto/
├── Scadetto.Core/              # 无 UI:方案、页面元数据、绑定契约、属性编辑目录、AXAML 原位编辑、报表
├── Scadetto.Avalonia/          # 全部 Avalonia UI(程序化控件 + 磁盘画面 + 5 类专用编辑器)
├── Scadetto.RunTime/           # 可执行:运行时
├── Scadetto.Designer/          # 可执行:设计器
├── Scadetto.Widgets/           # 页面导航控件(OrderBox、PageButtons、PageCommandControl 等)
├── Alarm/                      # 告警领域模型(含 AlarmSpeaker)
├── Driver/                     # 驱动抽象 + MEF 逐程序集加载
├── Driver.FIP/                 # FIP 协议驱动
├── Driver.Modbus/              # Modbus 协议驱动
├── Driver.Simulator/           # 随机数据模拟驱动
├── Database/                   # SQLite DAL/BLL
├── Common/                     # 公共工具
├── Modbus/                     # Modbus 协议常量与工具
├── ModbusSniffer/              # Modbus 抓包
├── FIPSimulator/               # FIP 协议模拟器(独立可执行,零引用,按需手跑)
├── Tests/
│   ├── Scadetto.Tests/         # xunit 测试(732 项)
│   └── QuestPdfApiDump/        # QuestPDF API 导出工具(升级 QuestPDF 后核对 API 用)
├── bin/config/                 # 示例方案配置(git 跟踪)
├── bin/template/               # 新建文件模板(页面 4 个、配置 1 套)
├── tools/
│   ├── ThemeProbe/             # 主题检查探针(故意不在 slnx 里,见开发约定)
│   └── theme-check.sh
└── docs/
    ├── 开发约定.md             # 架构分层与代码约定
    ├── 路线图.md               # 下一步规划

架构硬规则:不得把 Avalonia 类型引入 Scadetto.Core、Alarm、Driver、 Database、Common。这些层只通过数据契约与 UI 层交互。

构建与测试

dotnet build Scadetto.slnx -v q
dotnet test Scadetto.slnx

当前基线:0 编译错误,732/732 测试通过(2026-09-29 实测,连跑三次一致)。

运行

dotnet run --project Scadetto.RunTime -- test     # 直接打开名为 test 的方案
dotnet run --project Scadetto.RunTime             # 不带参数:先出方案选择器

test 是方案名。不带参数时显示方案选择器,那条路径不可脚本化。

macOS 上做无人值守验证时前面加 caffeinate -i——屏幕休眠会让渲染直接失败。

会浪费时间的地方

  1. config 是 build 时拷贝的。 改了 bin/config/ 下的方案或画面, 必须重新 dotnet build,否则运行时读到的还是输出目录里的旧副本。

  2. 全量测试"绿"不等于通过——要比总数。 测试宿主中途 abort 会让未执行的测试 从总数里消失,报告看起来和成功一模一样。跑三次比数字。

  3. headless 测试不运行控件模板。 任何"控件画出来是什么样"的问题都不能用 dotnet test 回答,用 ./tools/theme-check.sh 开真实窗口验证。

已知限制

  • LiveCharts 用的是预发布包 2.1.0-dev-798。 最后一个正式版 2.0.5 在 nuspec 里 依赖 Avalonia 11.0.0,用它构造图表控件会真的抛 MissingFieldException: Field not found: 'Avalonia.Input.Gestures.PinchEvent' (只构造 series 不报错,所以验证图表库必须真的构造控件)。等发布匹配 Avalonia 12 的正式版后应换回。

  • 没有拖拽设计面。 设计器是源码编辑器 + 实时预览 + 控件目录 + 属性面板, 没有鼠标拖拽画布。原实现依赖一个没有跨平台后继的设计器框架,因此没有实现, 也不打算实现。控件目录与原位插入覆盖了它的核心用途。

  • 没有登录。 应用直接进入核心功能,运行时窗口标题是固定的 Scadetto 运行时。 Solution.ReadOnly(设备写权限开关)属性仍在,但当前没有写入方。

相关文档

文档 内容
docs/开发约定.md 架构分层、命名、测试纪律、几条踩过坑的规则
docs/路线图.md 已完成 / 下一步 / 不打算做的事