基于HTML5 技术的开放自动化HMI

news2024/11/25 10:49:05

        人机交互接口(HMI)是自动化系统中不可或缺的一部分。传统的做法是提供一个HMI 显示屏,并且通过组态软件来配置显示屏的功能,通过modbus 或者以太网与PLC 连接。

       现在,事情变得复杂了许多,用户不仅需要通过专用的HMI 面板操控设备,而且需要使用标准化的设备与设备打交道,比如PC,iPAD 或者智能手机。与此同时,显示屏的性能提升和价格下降,在生产现场和公司管理部门也会安装更多的HMI设备。并且希望显示的内容传统的嵌入式HMI屏更加丰富。

   传统的HMI 屏幕的软件通常是使用windows, QT作为技术平台,使用C#或者C语言实现。嵌入式HMI屏提供的组态工具缺乏足够的灵活性,而且局限性比较大。支持开放性协议的HMI 屏并不多见,例如OPC UA ,MQTT 等协议。

     有一个发展趋势是使用HTML5 技术来构建开放自动化系统中的HMI 管理系统。本文探讨其中的一些技术细节。

HMI 的运行环境

      HMI 管理系统在下列几种平台上运行

  • WebServer

                基于NodeJS 的Web服务器

  • Windows PC HMI应用程序
  • 基于Electron 跨平台HMI

                能够在Android ,Linux ,Windows 平台上运行的HMI

  • 基于嵌入式QT 的HMI
  • 基于Go,C++ 等语言实现的web服务器。

                轻量级的web 可以使用Golang实现

系统架构

以web 服务器为例

         web服务器使用NodeJS 实现,前端UI组件能够使用多种技术实现,为了简单起见,我们使用Jquery /Bootstrap 构建UI组件,也可以使用vue 来构建UI组件。

        使用Web socket 协议的原因是因为Web socket 是一种双向协议,而Restful 是Client/Server协议。事件通信需要双向通信。另外,Web socket 能够实现加密。

 响应式布局

         传统 HMI 的页面设计大多数采用了固定定位的方式实现。但是这种设计方法无法适应多种显示屏。而且要开发一种基于图形化的方式来设计页面的布局并不是一种轻松的工作,需要组态的参数非常多。传统的HMI 通常基于了Windows图形系统实现。灵活性受到限制。而且HMI 页面比较丑陋。比较合适的方式是采用web 页面响应式布局。所谓响应式布局是页面能够根据屏幕的类型自动地调整页面布局。

   bootstrap 支持响应式布局。下面是我们构建的一个HMI 的例子。

 

bootstrap 的栅格技术

Bootstrap包含了一个强大的移动优先的网格系统,它是基于一个12列的布局、有5种响应尺寸(对应不同的屏幕)

  1. xs:超小屏幕 手机 (<768px)
  2. sm:小屏幕 平板 (≥768px)
  3. md:中等屏幕 桌面显示器 (≥992px)
  4. lg:大屏幕 大桌面显示器 (≥1200px)

bootstrap 将屏幕分成容器,行和列。并且将列分成为12等分。

一个页面包含若干个容器

容器内包含了若干行。

行包含若干列。

        与表格不同的是,行内的列并不一定是在一行中排列的。它们从左到右地排列,当超过屏幕时,会自动地转向下一行。因此,在大屏幕上三列的内容,到了中等屏幕可能变成2 列,到了智能手机中,可能便成为一列。这就是所谓的“响应式”设计。

bootstrap的许多技术细节可以参考bootstrap 网站的文档。

组件式HMI

     在我们的设计中,希望HMI 的设计与开放自动化系统中的其它信息模型的构建方式相似,这样能够降低系统的学习难度。具体地讲,就是要与OPC UA 信息系统,IEC61499 功能块,工业4.0 AAS 等模型的构建方式一致。在我们的实验系统中,采取了基于组件的设计方法(Component Based Engineering)。因此,我们需要设计一种web 页面组件化的架构。

   依据Bootstrap 的格栅方案,我们将HMI 按如下分层架构来构建:

  • HMI 的基本组件称为面板(Panel)和UI对象组成。
  • HMI 页面由若干面板组成
  • 面板是可以嵌套的。

根据面板的嵌套关系对应不同的Bootstrap 类

层级

Bootstrap 对象

第一层面板

容器container

第二层面板

第三层面板

UI对象

        从此可见,同样的面板组件,可能是行,也可能是列。它们交替变化。而UIObject 确定是列。 这是一种web 页面组件化的一种简单和有效的方法。可以使用Panel 和UIObject两种主要的组件就能够构建HMI面板。

功能组件与HMI 组件的接口

HMI 是一种信息组件,它们能够与功能组件交互,为了简单起见,功能组件将HMI组件看作为IO资源就可以。而IO服务组件包括:

  • HMI_Configuration
  • HMI_Write
  • HMI_Read
  • HMI_Input 

HMI 服务器中有一个运行时,支持OPC UA ,接口和IEC61499 功能块应用和一些标准功能组件库。

HMI 模型的模板

HMI 模型设计完成后,可以导出HMI模型,它们能够使用XML,JSON 格式描述,下面是一个JSON 描述的文件:

page={
    "panels":[
      {   "id":"panel_1",
        "style":"height:280px;background-color: yellowgreen;color:white",
         "title":"参数",
         "panels":[
           { 
             "id":"panel_2", 
              "panels":[
                   {
                     "id":"panels_211",
                     "style":"height:60px;background-color: blueviolet;color:white",
                     "UIObject":{
                 "id":"UI_11",
                 "Name":"Temperature",
                 "Type":"Text",
                 "Value":"32"
               } 
                   },
                   {
                     "id":"panels_212",
                     "style":"height:60px;background-color: blue;color:white",
                     "UIObject":{
                 "id":"UI_11",
                 "Name":"Voltage",
                 "Type":"Text",
                 "Value":"43"
               } 
                   },
                   {
                     "id":"panels_213",
                     "style":"height:60px;background-color: orange;color:white",
                     "UIObject":{
                 "id":"UI_11",
                 "Name":"Description",
                 "Type":"Text",
                 "Value":"64"
               } 
                   }
                 ]
                    
           }
         ]
      },
      { "id":"panel_3",
        "style":"height:280px;background-color: red;color:white",
         "title":"Panel2",
         "panels":[
           { "id":"panel_4",
            
               "UIObject":{
                 "id":"UI_2",
                 "Name":"Description",
                 "Type":"Text",
                 "Value":"HTML2 description"
               }
            
           }
         ]
      },
      {  
        "id":"panel_5",
        "style":"height:280px;background-color: blue;",
         "title":"Panel3",
         "panels":[
           {
            "id":"panel_6",
                   
               "UIObject":{
                 "id":"UI_3",
                 "Name":"Description",
                 "Type":"Text",
                 "Value":"HTML3 description"
               }       
           }
         ]
      },
      { "id":"panel_7",
      "style":"height:280px;background-color: green;",
         "title":"Panel4",
         "panels":[
           {
              "id":"panel_8",                 
               "UIObject":{
                "id":"UI_4",
                 "Name":"温度阀值",
                 "Type":"Input",
                 "Value":"HTML4 description"
               }
              
           },
         
         ]
      },
      { "id":"panel_9",
      "style":"height:280px;background-color:gold;",
         "title":"Panel5",
         "panels":[
           { "id":"panel_10",
             
              
               "UIObject":{
                "id":"highchart_01",
                 "Name":"Description",
                 "Type":"guage",
                 "Value":"HTML5 description"
               }
             
           }
         ]
      },
      { "id":"panel_11",
      "style":"height:280px;background-color: blueviolet;",
         "title":"Panel6",
         "panels":[
           {
              "id":"panel_12",                     
               "UIObject":{
                "id":"UI_6",
                 "Name":"Description",
                 "Type":"Text",
                 "Value":"HTML6 description"
               }
             
           },
           {
              "id":"panel_12",            
               "UIObject":{
                "id":"UI_7",
                 "Name":"Description",
                 "Type":"Text",
                 "Value":"HTML7 description"
               }
             
           },
           {
              "id":"panel_12",            
               "UIObject":{
                "id":"UI_8",
                 "Name":"Setting",
                 "Type":"Text",
                 "Value":"HTML8 description"
               }
             
           }
         ]
      },
    ]
  }

支持的页面

 前端JavaScript 程序

 function LoadUIObject(ParentNodeId,UIObject){
   if (UIObject.Type=="Text"){
    $("#"+ParentNodeId).append("<div "+"id="+UIObject.id+">"+UIObject.Value+"</div>");
   }else if (UIObject.Type=="guage"){
    $("#"+ParentNodeId).append("<div "+"id="+UIObject.id+" style='height:240px'></div>");
    guageInit(UIObject.id);
   } else if (UIObject.Type=="Input"){
    $("#"+ParentNodeId).append('<div class="input-group mb-3">');
      $(".input-group").append('<div style="margin:5px">'+UIObject.Name+'</div>');
    $(".input-group").append('<input type="text" class="form-control" id="name" placeholder="Recipient username" aria-label="Recipient  username" aria-describedby="basic-addon2">') ;                               
    $("input").append('<input type="text" class="form-control" id="name" placeholder="Recipient username" aria-label="Recipient username" aria-describedby="basic-addon2">')  ;
    $(".input-group").append('<div class="input-group-append">');
    $(".input-group-append").append('<button class="btn btn-info" type="button">确认</button>');                                                                  
   }

  }
  function Load(parentNodeId,panels,level){
    console.log("level:"+level);
    for (var panel in panels){
   
      if(level%2==0)
    { 
        if (level==0)
        $("#"+parentNodeId).append("<div "+"id="+panels[panel].id+" class='col-xs-6 col-lg-4 col-md-6 ' style='"+panels[panel].style+"'></div>");
        else
        $("#"+parentNodeId).append("<div "+"id="+panels[panel].id+" class='col' style='"+panels[panel].style+"'></div>");
       
      }
      else { 
      $("#"+parentNodeId).append("<div "+"id="+panels[panel].id+" class='row' style='"+panels[panel].style+"'></div>");
      }
      if (panels[panel].title)
      $("#"+panels[panel].id).append("<div class='row' >"+panels[panel].title+" </div>");
     if (panels[panel].panels!=null){
      Load(panels[panel].id,panels[panel].panels,level+1);
      }
      else if (panels[panel].UIObject!=null) {
        LoadUIObject(panels[panel].id,panels[panel].UIObject);

      } 
    }
  }
  function guageInit(id){
    console.log("id="+id);
     $( '#' +id).highcharts({
        chart: {
        type: 'gauge',
        alignTicks: false,
        plotBackgroundColor: null,
        plotBackgroundImage: null,
        plotBorderWidth: 0,
        plotShadow: false
    },

    title: {
        text: 'Temperature'
    },

    pane: {
        startAngle: -150,
        endAngle: 150
    },

    yAxis: [{
        min: 0,
        max: 200,
        lineColor: '#339',
        tickColor: '#339',
        minorTickColor: '#339',
        offset: -25,
        lineWidth: 2,
        labels: {
            distance: -20,
            rotation: 'auto'
        },
        tickLength: 5,
        minorTickLength: 5,
        endOnTick: false
    }, {
        min: 0,
        max: 124,
        tickPosition: 'outside',
        lineColor: '#933',
        lineWidth: 2,
        minorTickPosition: 'outside',
        tickColor: '#933',
        minorTickColor: '#933',
        tickLength: 5,
        minorTickLength: 5,
        labels: {
            distance: 12,
            rotation: 'auto'
        },
        offset: -20,
        endOnTick: false
    }],

    series: [{
        name: 'Speed',
        data: [80],
        dataLabels: {
            formatter: function () {
                var kmh = this.y,
                    mph = Math.round(kmh * 0.621);
                return '<span style="color:#339">' + kmh + ' km/h</span><br/>' +
                    '<span style="color:#933">' + mph + ' mph</span>';
            },
            backgroundColor: {
                linearGradient: {
                    x1: 0,
                    y1: 0,
                    x2: 0,
                    y2: 1
                },
                stops: [
                    [0, '#DDD'],
                    [1, '#FFF']
                ]
            }
        },
        tooltip: {
            valueSuffix: ' km/h'
        }
    }]

    }); 
    }
 
  console.log("start");
  console.log(page.panels);
   Load("top",page.panels,0);

结束语

这是为系统方案做的预研工作,还有许多问题有待研究,例如智能化响应式页面设计。让布局更加符合自动化系统的要求。德国有一家公司Helio 的HMI 做的不错,可惜还没有上市。

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

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

相关文章

干货 | 关于PCB中的“平衡铜”,一文全部说明白

平衡铜是PCB设计的一个重要环节&#xff0c;对PCB上闲置的空间用铜箔进行填充&#xff0c;一般将其设置为地平面。 平衡铜的意义在于&#xff1a; 对信号来说&#xff0c;提供更好的返回路径&#xff0c;提高抗干扰能力&#xff1b;对电源来说&#xff0c;降低阻抗&#xff0c;…

Android 13 源码获取与构建

文章目录1. 环境准备1.1 基本信息1.2 系统初始化1.2.1 更新 Ubuntu 软件包1.2.2 安装 git 工具1.2.3 安装依赖包(Ubuntu 18.04)1.2.4 修改默认python版本1.2.5 安装 repo 工具2. 源码下载完成2.1 创建源码目录2.2 初始化源码仓库2.3 开始下载源码2.4 Android 13 源码目录3. 构建…

m可见光通信的空间调制(sm)误码率matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 可见光通信技术&#xff08;Visible Light Communication&#xff0c;VLC&#xff09;是指利用可见光波段的光作为信息载体&#xff0c;在空气中直接传输光信号的通信方式。可见光通信技术绿色低…

virtio vring原理

vring原理 在 virtio 设备上进行批量数据传输的机制被称为 virtqueue 。每个设备可以拥有零个或多个 virtqueue &#xff0c;当 Driver 想要向设备发送数据时&#xff0c;它会填充 Descriptor Table 中的一项&#xff08;或将几项链接在一起&#xff09;&#xff0c;并将描述符…

圣诞树拼图游戏unity制作

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 一、前言 提示&#xff1a;使用unity来制作一个拼图游戏&#xff0c;图片便是圣诞树。 二、创意名 圣诞树拼图游戏 三、效果展示 圣诞树拼图游戏最终效果。 游戏中效果如图&#xff1a; 游戏拼图完成后效果如图&am…

vue实现随机生成分享海报(内容动态)

大家好&#xff0c;我是雄雄。 前言 昨天写了篇文章&#xff1a;自己整理的vue实现生成分享海报&#xff08;含二维码&#xff09;&#xff0c;看着网上的没实现 主要是介绍了如何使用vue实现&#xff0c;动态分享内容为海报&#xff0c;且附带二维码&#xff0c;扫描二维码能…

shell脚本四剑客之awk详解

文章目录awk的介绍awk能够干什么awk的格式工作原理&#xff1a;记录和域内建变量的用法1. FS2. OFS3.RS4. ORS5. NF6. NRBEGIN 和END语句块常见案例1. 使用NR行号提取ip2. 打印UID小于10的账号名称和UID信息3. 数学运算4. AWK打印硬盘设备名称&#xff0c;默认以空格为分割&…

UDP用户数据报协议(计算机网络-运输层)

目录 UDP 概述 UDP 的主要特点 UDP 的问题 UDP的多路分用模型 UDP 的首部格式 UDP 概述 用户数据报协议&#xff08;User Datagram Protocol&#xff0c;UDP&#xff09; UDP 只在 IP 的数据报服务之上增加了很少一点的功能&#xff0c;即端口的功能和差错检测的功能 虽…

计算机网络——网络层功能概述

网络层 网络层的主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层的传输单位成为数据报。 数据报是一组比较长的数据&#xff0c;分组则是将数据报划分为不同的片段 网络层的第一个功能&#xff1a;路由的选择和分组的转发。 网络层…

python词云图词频统计

目录 一&#xff1a;安装必要的库 二&#xff1a;数据分析 条形图可视化 三&#xff1a;数据分析 词频统计 词云图可视化 一&#xff1a;安装必要的库 导入必要的库 import collections # 词频统计库 import os import re # 正则表达式库 import urllib.error # 指定url&…

WRF进阶:antro_emiss工具处理全球大气人为排放(EDGRA_HTTPs)/人为排放清单前处理

本内容视频版讲解&#xff1a;全球人为排放处理 介绍 一般人为数据的排放前处理使用pre_chen_src工具&#xff0c;然而pre_chen_src处理后的文件并不是WRF所能读取的文件格式&#xff0c;需要使用onvert_emiss.exe&#xff0c;生成WRF需要的人为排放的nc数据。 在WRF-chem3.6…

煤矿视频监控分析检测 yolo

煤矿视频监控分析检测利用python基于yolo深度学习架构&#xff0c;对现场画面进行实时分析检测。我们使用YOLO(你只看一次)算法进行对象检测。YOLO是一个聪明的卷积神经网络(CNN)&#xff0c;用于实时进行目标检测。该算法将单个神经网络应用于完整的图像&#xff0c;然后将图像…

单片机——LED点阵

1. 基本介绍 LED点阵 LED点阵是由发光二极管排列组成的显示器件&#xff0c;通常应用较多的是88点阵&#xff0c;然后通过多个88点阵组成不同分辨率的LED点阵显示屏&#xff0c;如4个88组成的1616点阵 8*8点阵由64个LED组成&#xff0c;每个LED是放置在行线和列线的交叉点上…

LVGL学习笔记3 - 样式Style

目录 1. 初始化样式 2. 设置样式 3. 添加和移除样式 4. 验证 5. 状态&#xff08;State&#xff09; 6. 部分&#xff08;Parts&#xff09; 样式用于设置对象的外观&#xff0c;比如颜色等属性&#xff0c;存储在 lv_style_t 变量中&#xff0c;这个变量应该是static…

不写一行代码(二):实现安卓基于PWM的LED设备驱动

文章目录一、前言二、系列文章三、准备工作3.1 查找PWM引脚3.2 原理图&#xff1a;确认引脚位置3.3 PWM Controller四、查阅PWM bindings五、编写设备树节点5.1 实现节点&#xff1a;pwm-leds5.2 测试命令六、后语一、前言 在完成了基于GPIO的LED设备驱动的文章后&#xff0c;…

3天学会撰写软件发明专利——3.生命周期

“无意中发现了一个巨牛的人工智能教程&#xff0c;忍不住分享一下给大家。教程不仅是零基础&#xff0c;通俗易懂&#xff0c;而且非常风趣幽默&#xff0c;像看小说一样&#xff01;觉得太牛了&#xff0c;所以分享给大家。点这里可以跳转到教程。”。 一、专利授权生命周期…

4.1 协程:协程基础

1.协程 协程&#xff0c;又称微线程。协程是python个中另外一种实现多任务的方式&#xff0c;只不过比线程更小占用更小执行单元&#xff08;理解为需要的资源&#xff09;。 为啥说它是一个执行单元&#xff0c;因为它自带CPU上下文。这样只要在合适的时机&#xff0c; 我们可…

C++类和对象概念及实现详解(上篇)

文章目录 一、什么是类和对象呢&#xff1f; 1、类的引入 2、类的定义 3、类的访问限定符 4、类对象的储存方式 5、this指针的特性 二、类的六个默认成员函数详解 1、构造函数 2、析构函数 3、未完待续…… 标题&#xff1a;类和对象概念及实现详解&#xff08;上篇&#xff0…

vue3 antd table表格——自定义单元格样式(二)利用rowClassName给table添加行样式

vue3 antd项目实战——修改ant design vue组件中table表格的默认样式&#xff08;二&#xff09;知识调用场景复现修改table表格的行样式一、rowClassName添加行样式二、表格的不可控操作写在最后知识调用 文章中可能会用到的知识链接vue3ant design vuets实战【ant-design-vu…

从头开始用树莓派做一个NAS【最新超详细教程】

一、概述 众所周知在办公的时候两台电脑之间经常倒数据资料非常麻烦&#xff0c;而NAS可以很好的解决这个问题。树莓派搭建NAS方法有很多&#xff0c;我们之前也拍过直接用Samba、FTP这些来实现NAS功能&#xff0c;但是这些需要你会在命令行进行配置&#xff0c;而且对于新手用…