基于SVG的HMI组件

news2025/1/31 7:59:39

        人机界面是自动化领域不可或缺重要组成部分。人机界面系统的设计看上去并没有太大的技术门槛,但是设计一个HMI系统的工作量是巨大的,如果你没有足够的耐心和精力是难以完成一个通用HMI系统的。构建UI控件库就是一个似乎永远完不成的事情,用户永远觉得不够用。

        另一方面用户使用HMI的组态工具构建HMI应用也是一个十分繁琐又容易出错的地方,你需要将大量的数据点与HMI上的UI对应起来。

人们期望自动生成HMI界面, 本文讨论如何解决这些问题。

基于模型的设计

        自动控制领域正走向开放性系统,并且采用基于模型的设计理念,OPC UA ,MTP,工业4.0 AAS等技术都是基于信息模型的工业标准。

       当系统采用了信息模块之后,现场传送的数据已经不是简单的整型数和浮点数,而是一个结构化的信息模型。

          如果 HMI 系统构建与信息模型相对应的UI 模型,那么数据模型的数据就能够直接呈现到UI模型上,不再需要组态工具去建立对应关系。这将减少组态时许多的工作量。

转变UI组件的开发方式

        传统的HMI 中的UI组件都是由HMI软件开发商实现的,即便开放了用户开发专用UI组件的能力,通常也是在HMI 组态工具中实现的。在这种相对封闭的UI组件构建方法带来了HMI系统的开发难度。

        由HMI软件开发者开发UI组件往往很难与应用完美的结合,与具体应用相结合时难免看上去牵强附会。

        笔者看来,应该将UI组件库集中方式,转向用户自主开发。具体地讲,将UI组件的开发交给底层设备的开发者,在设备的信息模型中包含了UI 组件。当设备与HMI 软件连接起来后,将UI组件上传到HMI系统中去。例如,一台空气压缩机的信息模型中包含了它的UI组件。

        设备开发者是最熟悉其设备的图形化方式,我们将这种由设备制造商提高UI 组件的方式称为“民主化开发”方法。

        在流程控制行业的NAMUR 的MTP 模型中,就采取了这样一种方法,号称能够根据P&ID 图(Piping and instrumentation diagram)自动生成HMI的画面。

UI 组件的实现技术

HMI 系统实现的方式主要有如下几种:

  1. 基于Web 的HTML5技术
  2. 基于Window 的.NET/C# 技术
  3. 基于Android的技术

        尽管.NET/C# 技术仅限于Windows 平台实现(目前.NET 的用户界面不支持Linux),C#仍然是开发工业控制上位机软件效率最高的设计工具。相比之下,HTML5 Web技术采用了开放性技术(HTML,CSS和javascript),带来了兼容性和灵活性,但是Web 技术过于碎片化,开发效率并不高。

UI组件的组成

        所谓UI组件,就是将HMI的页面分解成多个小型的模块。

        基于MVC 模型,现代界面采取了页面视图与控制逻辑的分离。

        在.NET/C# 技术中,也已经从传统的Form 转向页面与逻辑分离的WPF技术。wpf 窗口包括了描述界面的XAML和后端的代码C#。后端实现XAML 的响应。

        在QT技术中,大致也是如此。

UI 组件技术

  • 单一文件

        由于UI 组件是从底层信息模型中上传到HMI 系统的,为了避免多个文件的上传,最好是单一文件。 比如在Web技术中,我们可以采用VUE 组件。VUE 组件是将HTML,CSS 和JavaScript 合并在一个vue文件中。也可以采用SVG矢量图形

  • 与平台无关

显然WPF 是依赖Windows 平台的。

  • 简单设计工具

SVG

        矢量图形SVG 也是一种单一UI 组件的方法。SVG的全称是可缩放矢量图形(Scalable Vector Graphics,SVG)基于 XML 标记语言。但是SVG不仅仅描述矢量图形,还能够包含JavaScript 程序。 SVG是一种理想的HMI UI组件工具

        SVG 是W3C 的开放型标准。与具体的架构无关。有许多工具能够实现SVG 的绘制。添加Javascript代码只需要文本编辑器就能够完成。

        所以SVG是一种理想的HMI UI组件工具。

XAML/C#

        C# 的XAML 能够描述精美的图形界面,而且支持3D 模型的显示。WPF 的UI 组件是用户组件(UserControl )。每个UserControl 包含了一个XAML和一个CS文件。构建用户组件库的方法是:

  1. 新建用户控件库项目,在该库中可以包含多个用户控件。
  2. 编译成为DLL 文件。
  3. 在其它应用程序中导入该DLL 或者动态导入DLL。

        在一些传统的HMI系统中,仍然使用XAML 开发用户界面,例如艾默生公司的MOVICON SCADA 软件就是采用XAML 。

        XAML/C# 的缺点是依赖Windows 平台技术·,要使用微软的VS开发UI控件。另外,组件以DLL 形式呈现,不可阅读和修改。动态载入HMI系统也比较麻烦。

        在笔者看来SVG是比较好的UI组件技术。

基于SVG 的UI组件

下面我们以svidget 开源架构为例介绍基于SVG 的UI组件技术。

:也许有人会疑惑,为什么SVG在能够包含javascript 代码呢?这是由于SVG 是使用XML 描述的矢量化图形。在浏览器中是将SVG的XML 直接嵌入HTML 文本中处理的,所以SVG中能够嵌入HTML5 的内容。

项目Github:https://github.com/joeax/svidget

 svidget  主要构建基于SVG 技术的UI组件,它被成为Widget(小部件),其特点是

可交互

        您可以通过页面的参数、操作和事件从页面操作小部件。 也就是说,您可以设置参数值、调用操作以及处理小部件中的事件。

使用方便

可以使用<object>标签在网页中嵌入SVG Widget。并且使用<param> 标签将数据传递到小组件。

下面是一个简单的例子

假设我想创建一个圆环仪表来显示操作的进度。看起来像这样的东西:

 这是它的原始静态SVG代码:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svidget="http://www.svidget.org/svidget"
		 width="200" height="200" viewBox="0 0 200 200" style="background:transparent">
	<title>Donut Gauge</title>
	<desc>Visualizes a donut-style gauge with text value in the middle.</desc>

	<style>
		#textLabel { font-size: 16px; font-family: Helvetica; alignment-baseline: text-top; }
		#spanContent { font-size: 28px; font-weight: bold; margin: 0px; }
	</style>

	<defs>
		<mask id="donutMask">
			<rect x="0" y="0" width="100%" height="100%" fill="white" />
			<circle r="48" cx="100" cy="100" fill="black" />
		</mask>
	</defs>

	<g>
		<circle id="backCircle" r="98" cx="100" cy="100" fill="#ddd" mask="url(#donutMask)" />
		<path id="foreArc" fill="none" stroke="#7f7fdf" stroke-width="50" d="M 100,27 A 73,73 0 1,1 27,100" />
		<text id="textLabel" x="100" y="110" text-anchor="middle" fill="#3f3f3f">
			<tspan id="spanContent">75</tspan> %
		</text>
	</g>

</svg>

我们可以通过将其转换为 SVG 小部件来使其有用。 要对此进行小部件化,我们首先需要考虑的是它的属性是什么。在 svidget 中,我们将这些称为参数。 对于我们的甜甜圈仪表盘

  • 数据 - 这是主要数据。让它成为 0 到 1 之间的值,表示百分比。
  • 颜色 - 圆圈上前景的颜色。
  • 背景颜色 - 圆圈上背景的颜色。
  • 文本颜色 - 中间文本的颜色。
  • showText - 是否显示文本的布尔标志。
  • 宽度 - 甜甜圈的宽度。可以是介于 1 和 98 之间的值。

 Svidget 使用声明性语法向小部件添加参数。由于SVG是结构化XML,我们可以用svidget命名空间来扩展它。

<svidget:params>
<svidget:param name="data" shortname="d" type="number" description="The percentage to fill. Value should be between 0 and 1." />
<svidget:param name="color" shortname="color" type="string" subtype="color" binding="#foreArc@stroke" description="The circle foreground color." />
<svidget:param name="backColor" shortname="bcolor" type="string" subtype="color" binding="#backCircle@fill" description="The circle background color." />
<svidget:param name="textColor" shortname="tcolor" type="string" subtype="color" binding="#textLabel@fill" description="The text color." />
<svidget:param name="showText" shortname="st" type="bool" description="Whether to show the text in the middle." />
<svidget:param name="width" shortname="w" type="number" description="The width of the donut portion of the circle." />
</svidget:params>

完整的代码

<?xml version="1.0" encoding="utf-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svidget="http://www.svidget.org/svidget"
		 width="200" height="200" viewBox="0 0 200 200" style="background:transparent">
	<title>Donut Gauge</title>
	<desc>Visualizes a donut-style gauge with text value in the middle.</desc>

	<style>
		<![CDATA[		#textLabel { font-size: 16px; font-family: Helvetica; alignment-baseline: text-top; }		#spanContent { font-size: 28px; font-weight: bold; margin: 0px; }		]]>
	</style>
	
	<svidget:params>
		<svidget:param name="data" shortname="d" type="number" description="The percentage to fill. Value should be between 0 and 1." />
		<svidget:param name="color" shortname="color" type="string" subtype="color" binding="#foreArc@stroke" description="The circle foreground color." />
		<svidget:param name="backColor" shortname="bcolor" type="string" subtype="color" binding="#backCircle@fill" description="The circle background color." />
		<svidget:param name="textColor" shortname="tcolor" type="string" subtype="color" binding="#textLabel@fill" description="The text color." />
		<svidget:param name="showText" shortname="st" type="bool" description="Whether to show the text in the middle." />
		<svidget:param name="width" shortname="w" type="number" description="The width of the donut portion of the circle." />
	</svidget:params>

	<defs>
		<mask id="donutMask">
			<rect x="0" y="0" width="100%" height="100%" fill="white" />
			<circle r="48" cx="100" cy="100" fill="black" />
		</mask>
	</defs>

	<g>
		<circle id="backCircle" r="98" cx="100" cy="100" fill="#ddd" mask="url(#donutMask)" />
		<path id="foreArc" fill="none" stroke="#7f7fdf" stroke-width="50" d="M 100,27 A 73,73 0 1,1 27,100" />
		<text id="textLabel" x="100" y="110" text-anchor="middle" fill="#3f3f3f" visibility="visible">
			<tspan id="spanContent">75</tspan> %
		</text>
	</g>

	<script type="application/javascript" xlink:href="../scripts/svidget.js"></script>
	<script type="application/javascript">
		<![CDATA[
	
		var _data = 0;
		var _width = 50;
		var FULL_RADIUS = 100;
		var DONUT_RADIUS = FULL_RADIUS - 2; // 98
		
		function init() {
			//setArcPath(0.75);
			//debugger;
			var widget = svidget.current();
			widget.param("data").on("set", onParamDataSet);
			widget.param("width").on("set", onParamWidthSet);
			widget.param("showText").on("set", onParamShowTextSet);
			setParamData(widget.param("data").value());
			setParamWidth(widget.param("width").value());
			setParamWidth(widget.param("showText").value());
		}
		
		function onParamDataSet(e) {
			//alert('onParamDataSet');
			var val = parseFloat(e.value.value); // { value: val }
			setParamData(val);
		}
				
		function onParamWidthSet(e) {
			var w = parseInt(e.value.value); // { value: val }
			setParamWidth(w);
		}
		
		function onParamShowTextSet(e) {
			var show = e.value.value;
			setParamShowText(show);
		}
		
		// range: 0 to 1
		function setParamData(val) {
			val = rangify(val, 0, 1);
			// set donut arc
			setArcPath(val);
			// set text
			setContentLabel(val);
			// set global data
			_data = val;
		}

		// range: 1 to 98
		function setParamWidth(w) {
			w = rangify(w, 1, DONUT_RADIUS);
			// set donut mask - i.e. empty hole in middle aka the donut hole
			var rad = DONUT_RADIUS - w;
			setMaskRadius(rad);
			setArcWidth(w);
			// set global width
			_width = w;
			// re-set arc path
			setArcPath(_data);
		}
		
		function setParamShowText(show) {
			//debugger;
			var textLabel = document.getElementById("textLabel");
			var v = !show ? "hidden" : "visible";
			textLabel.setAttribute("visibility", v);
		}

		function setMaskRadius(rad) {
			var maskCir = document.querySelector("#donutMask > circle");
			maskCir.setAttribute("r", rad);
		}

		function setContentLabel(pct) {
			var val = parseInt(pct * 100);
			var spanContent = document.getElementById("spanContent");
			spanContent.textContent = val + "";
		}

		function setArcPath(pct) {
			var path = generateArcPath(pct);
			var foreArc = document.getElementById("foreArc");
			foreArc.setAttribute("d", path);
		}
		
		function setArcWidth(w) {
			var foreArc = document.getElementById("foreArc");
			foreArc.setAttribute("stroke-width", w);
		}
	
		// pct == 0 to 1
		function generateArcPath(pct) {
			var baseY = 2;
			var halfWidth = _width / 2.0;
			var startY = baseY + halfWidth;
			var arcRadius = FULL_RADIUS - startY;
			var largeArc = pct > 0.5 ? 1 : 0; // if greater than 50 we need to use large arc in path
			var pctRadians = (Math.PI * 2) * pct;
			var endX = round3((Math.sin(pctRadians) * arcRadius) + FULL_RADIUS);
			var endY = round3((-Math.cos(pctRadians) * arcRadius) + FULL_RADIUS);
			if (endX == 100 && pct > 0) endX = 99.999; // this corrects issue with path if start and end are same it won't draw an arc
			//debugger;
			
			// M 100,27 A 73,73 0 1,1 27,100
			var path = "M 100," + startY + " "; // move to start
			path += "A " + arcRadius + "," + arcRadius + " 0 " + largeArc + ",1 " + endX + "," + endY;
			
			return path;
		}
		
		function rangify(val, min, max) {
			if (isNaN(val) || val < min) val = min;
			if (val > max) val = max;
			return val;
		}
		
		function round3(val) {
			return parseInt(val * 1000) / 1000.0;
		}
	
		window.addEventListener('load', init, false);
	
	  ]]>
	</script>
</svg>

在网页上嵌入小部件的主要方法是object> 标记。 框架使用 role=“svidget” 查找所有 <object> 标签,并实例化小部件。 

<object id="myDonutGauge" role="svidget" data="widgets/donut.svg" type="image/svg+xml" width="200" height="200">
<param name="data" value="0.55" />
<param name="color" value="#da3333" />
<param name="backColor" value="#ffac33" />
<param name="textColor" value="#da3333" />
<param name="showText" value="true" />
<param name="width" value="40" />
</object>

您还可以使用 svidget.load() 以编程方式加载小部件:

svidget.load("#widgetContainer", "widgets/donut.svg", { 
data: 0.55, 
color: "#da3333",
backColor: "#ffac33",
textColor: "#da3333",
showText: true,
width: 40,
});

 在Github 中包含了一个demo 网页

很酷吧?

HMI组件实现的细节 

在具体地实现中,还有如下细节要考虑:

  •  UI组件只是提供了组件,网页布局仍然需要组态软件来完成。尽管简单了许多。
  •      网页中包含一个JavaScript 的运行时(runtime),它是与信息模型无关的。他只完成信息的交换。运行时将信息模型的NodeID 传递给UI 组件。UI组件订阅信息对象的特性(Property)并且支持事件和方法调用。

结束语

本文讨论的一种灵活的HMI组件方法,具体细节可以交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

12.基于蒙特卡洛抽样的电动汽车充电负荷计算

说明书 MATLAB代码&#xff1a;基于蒙特卡洛抽样的电动汽车充电负荷计算 关键词&#xff1a;电动汽车 蒙特卡洛模拟 抽样 充放电负荷 参考文档&#xff1a;《主动配电网多源协同运行优化研究_乔珊》第3.2节&#xff0c;完全复现 仿真平台&#xff1a;MATLAB 优势&#xf…

JavaWeb——IO、存储、硬盘、文件系统相关常识

目录 一、IO 1、定义 二、存储和硬盘 1、存储 2、硬盘 三、文件系统 1、文件 &#xff08;1&#xff09;、定义 &#xff08;2&#xff09;、分类 &#xff08;3&#xff09;、操作 2、树形结构和目录 3、路径 &#xff08;1&#xff09;、定义 &#xff08;2&…

elementui是怎么做表单验证的?

文章目录 前言elementui是怎么做表单验证&#xff1f;步骤 一、 表单验证校验代码&#xff1f;二、el-button提交验证代码2.validate方法深入了解1. 有参数2. 无参数 总结 前言 在项目开发中&#xff0c;我们经常会遇到表单保存的功能&#xff0c;在表单保存前&#xff0c;常常…

Salesforce如何防止黑客攻击和数据泄露?了解他们的安全措施!

安全性一直是Salesforce密切关注的问题。Google的安全浏览报告指出&#xff0c;2022年网络钓鱼网站的数量增加了80&#xff05;。面对着黑客攻击、安全漏洞、数据泄露等不安全事件频发&#xff0c;实施更强大的安全措施比以往更加重要。 调查显示&#xff0c;电子邮件目前是网…

阿里巴巴图标,font-family字体样式

阿里巴巴官网&#xff1a;https://www.iconfont.cn/ 如有需要&#xff0c;请注意到文章最后的问题。 一&#xff0c;图标 1&#xff0c;搜索关键词&#xff0c;点击图标 -> 加入购物车 -> 添加到项目 2&#xff0c;两种方式 &#xff08;1&#xff09;选择 Font cla…

推荐一个好工具,可以替代 swagger 生成文档

Swagger 是一个规范且完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口&#xff0c;可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 S…

SpringBoot扫包排除指定包(配置文件形式)

1、背景​ 在有些时候&#xff0c;不同环境可能需要用代码不同模块的能力&#xff0c;但是不同环境的项目包是一致的。这块我们想到的最简单的办法是在maven打包的时候打不同的模块&#xff0c;但是这样往往需要出多个包&#xff0c;虽然这种方式是正路但运维同学嫌麻烦。第二种…

Java面试题--MySQL索引

一. 索引介绍 MySQL的索引是一种数据结构&#xff0c;它可以帮助MySQL快速定位需要访问的记录。索引可类比于一本书的目录&#xff0c;通过它可以快速找到某个特定的记录。 MySQL支持多种类型的索引&#xff0c;每种索引都有其优势和局限性&#xff0c;常用的包括&#xff1a…

python类型转换

我们就想那我们目前接触到的三种类型做一下转换 分别是 字符串 浮点数 正整数 可能会有刚接触编程的小伙伴在想 没事类型转换什么呢&#xff1f; 其实在实际开发中 这东西用的不要太多 根据我们目前认识的类型 先用到三个方法 我们编写代码 String_int str(111); print(St…

初阶数据结构——时间复杂度和空间复杂度

目录 算法效率算法的复杂度 时间复杂度时间复杂度的概念习题 空间复杂度空间复杂度的概念习题 什么是数据结构&#xff1f; 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的数据元素的集合。 什么是算法&#xff1f; 算法…

java+ssm603企业人事档案管理系统带前端springboot

人事管理系统的设计与实现采用Spring、SpringMVC和MyBatis作为主体框架,系统设计遵循界面层、业务逻辑层和数据访问层的Web开发三层架构。采用B/S结构,使得系统更加容易维护。人事管理系统的设计与实现主要实现角色有管理员和用户,管理员在后台管理资料文件模块、员工模块、文件…

深度相机初体验:Hello World

当我的组长给了我一个深度相机&#xff0c;倒霉的事情就开始了&#xff0c;在使用的过程中遇到的某些问题搜不到&#xff0c;头秃啊呜呜呜呜呜呜呜呜 配置&#xff1a; ubuntu20.04&#xff08;我实在是懒得去升级了&#xff0c;一旦升级就可能会出现找不到教程的可能性&#x…

运行时内存数据区之执行引擎(一)

执行引擎概述 执行引擎是Java虚拟机核心的组成部分之一。 “虚拟机”是一个相对于“物理机”的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行引擎则是由软…

【MATLAB基础绘图第8棒】绘制局部放大图

MATLAB绘制局部放大图 1 工具准备 MATLAB官网-ZoomPlot(Kepeng Qiu. Matlab Central, 2022) 初始数据图绘制完成后&#xff0c;调用以下代码&#xff1a; %% 添加局部放大 zp BaseZoom(); zp.plot;1.1 具体绘制步骤 具体绘制步骤如下&#xff1a; 通过鼠标左键框选作图区…

JavaScript中如何删除对象/数组中null、undefined、空对象及空数组实例代码

如下&#xff0c;对于一个多层嵌套的数据结构&#xff1a;例如 要做的就是删除所有value为空&#xff0c;数组为空&#xff0c;对象为空的字段 const querys {name: 测试,httpMethod: ,httpHeaders: [{key: Accept,value: test,},],restParams: [{key: ,value: ,},],body: {b…

GZIPOutputStream GZIPInputStream 数据压缩解压

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 数据压缩&解压 前言一、最终走上了压缩的路 GZIPOutputStream二、收到数据进行解压 GZIPInputStream三、测试Demo四、验证结果总结 前言 最近接手了一批性能优化… 遇到…

全注解下的SpringIoc

Spring架构中的核心理念就是IOC和AOP&#xff0c;可以说&#xff0c;Spring的强大离不开这两大特性。 因为spring boot推荐采用注解开发&#xff0c;所以文中主要介绍基于注解的Spring Ioc。 IoC容器简介 Spring IoC 容器是个管理 Bean&#xff08;在Spring 中把每个需要管理…

VMware vCenter Server 8.0U1 发布 - 集中式管理 vSphere 环境

请访问原文链接&#xff1a;VMware vCenter Server 8.0U1 - 集中式管理 vSphere 环境&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 2023-04-18, VMware vSphere 8.0U1 发布。 详见&#xff1a;VMware vSphere 8 Update 1 新…

单片机的实例——28BYJ48步进电机

整体过程 准备资料1&#xff0c;步进电机内部结构示意图2&#xff0c;步进电机命名3&#xff0c;实际结构的转速比4&#xff0c;数值的含义5&#xff0c;实际内部主动轮结构分析实际内部机构及接线定子部分转子部分 定子和转子磁极的工作分析 6&#xff0c;工作时序7&#xff0…

机器学习 day06(向量化应用于多元线性回归的梯度下降算法,正规方程)

向量化多元线性回归&#xff0c;及梯度下降算法 将W₁&#xff0c;…&#xff0c;Wn写成向量W&#xff0c;将X₁&#xff0c;…&#xff0c;Xn写成向量X&#xff0c;而b还是之前的b注意&#xff1a;在多元线性回归的梯度下降算法中&#xff0c;Wj是指从W₁到Wn中的某一项&…