当MES遇上PLC——SOAP篇(上)(含调试程序)

2022-11-14 18:59:08 浏览数 (1)

前 言:

前段时间,有客户在网上看到了我们边缘计算模块产品,找到了我们,跟我们描述了他们目前遇到的问题:

某汽车零部件制造厂在进行智能工厂的升级改造,工单派发和生产顺序指定由MES系统完成,西门子1200 PLC负责生产控制系统。但是,MES系统只能提供SOAP协议给PLC。看到这里,大多数PLC工程师可能就有点懵了,这啥玩意啊?这玩意不是咱干的活呀!

确实,这不属于工控行业的协议,这是IT界的。

那么,何为SOAP呢?简单来说,SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。或者更简单地说:SOAP 是用于访问网络服务的协议,它独立于平台、独立于语言,用于在因特网传输消息的格式。你瞅瞅,这玩意就是一种能够跨平台发送消息的东西。

了解了这个协议,我们就有办法去搞定它,将它和PLC建立连接,让MES系统的数据,流畅地传输到PLC中。此时北京伟联科技有限公司发布的边缘计算模块(WL-320E-M)产品便担负起了这个重任。

在后期的沟通了解中,按照用户的设计要求,需要先将MES系统排序好的工单数据通过SOAP协议请求回来后记录到数据库中,然后由PLC按照生产的节奏从数据库中获取工单数据。同时,需要利用边缘计算模块,将MES系统正在排序的工单实时传输给PLC,供触摸屏和上位机实时显示。

针对用户的需求,设计的网络拓扑图如下:

在MySQL数据库内创建表

设计思路:

定时请求SOAP数据流程图

PLC请求数据库工单数据程序流程图

1. 边缘计算模块内配置

在本项目中,需要用到连接PLC和连接数据库节点,该节点配置具有全局属性,在整个边缘计算模块环境内都可以调用。该节点无需单独从节点区域拖拽,双击任意s7-in或者s7-out节点,点击右边编辑按钮即可编辑该节点。

S7-endpoint节点属性内

Transport:连接到PLC的通讯协议类型,选择Ethernet(ISO-ON-TCP)即可。

Address:PLC的IP地址及通讯协议端口号

Mode:选择通讯的模式,S7-200型号的PLC选择TSAP,除此之外其余的都选择RACK/SLOT

Rack:PLC的CPU位置,可在博图或者Step7软件里面硬件组态内看到

Name:自定义名称

Variable内为连接PLC的变量地址及在边缘计算模块内使用的变量名。

需要注意的是,边缘计算模块使用的是s7协议与西门子PLC建立连接,因此,需要将DB块属性内将“优化的块访问”选项去掉。

以及,在PLC属性内,将防护与安全选项里面的 允许PUTGET访问。

Variable选项内变量可以导出为CSV文件使用Excel编辑后再导入。

PLC地址编写方式参考节点帮助内容或WL-320E-M使用手册。

数据库连接节点mysql

其中,Host为运行MySQL数据库的计算机IP地址

Port端口默认为3306

User用户名为提前设置好的MESUser

Password:为提前设置好的密码

Database:为要连接MySQL内数据库名称

其余默认。

请求触发时间控制

本项目中,对SOAP接口采用定时触发的方式控制边缘计算模块对soap接口的访问频率。默认以5分钟为周期。

5分周期设定

整个流程初始触发条件为1秒周期,但是在定时请求任务号函数内,规定,在每小时的0分30秒,5分30秒,10分30秒,15分30秒等这样的时刻下触发后面的请求动作。在程序中,使用当前分钟数除以5取余数的方法判断当前时刻是否为计划的时刻。

判断当前时间分钟数除以5取余数为0 并且 当前秒为30时,触发后面动作。

代码语言:javascript复制
var sta = msg.payload
var CurMin,CurSec
if( sta == "OK" || sta == "connected") //判断数据库连接是否正常
{
    CurMin = Number(getCurrentDate(1))
    CurSec = Number(getCurrentDate(2))
    
    if((CurMin % 5 == 0) && (CurSec == 30)  )
    {
        msg.payload = getCurrentDate(3)
        return msg;
    }
}
else
{
   //只有判断到数据库连接正常后才输出,否则无输出
}
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){    //参数为1时返回分钟数
        time = minu;
      }
      //精确到分
      else if(format==2){  //参数为2时返回秒数
        time = sec;
      }
      else if(format==3){   //参数为3时返回完整时刻
        time = year    month    date   hour   minu    sec;
      }
      return time;
}

2. 从MES SOAP接口请求数据

在边缘计算模块中,需要使用 Simple SOAP节点来实现 SOAP XML方式得数据访问,再配合其他XML/JSON/JS对象/Function等数据处理节点,共同实现客户需要得功能。

定时请求SOAP数据

此处主要实现功能有:

(1) 按照5分钟的时间周期,输出触发SOAP的连接信号,触发该连接去获取MES系统对应接口的数据。

(2) 将从MES接口获取到的数据进行分类判断,正常值、空值、连接异常值。对应写入到数据库表内作为记录。

(3) 将连接异常信号发送给对应的PLC变量。

(4) 每次请求连接SOAP之前都需要判断边缘计算模块与数据库机器的连接状态,如果正常,则继续请求,如果异常,则不发出请求。

需要使用到的节点有

Inject插入(1秒周期触发):用于产生1秒周期脉冲信号 。

Change设定消息(获取全局):用于获取当前数据库连接状态 。

Function函数(定时请求任务号):

代码语言:javascript复制
var sta = msg.payload
var CurMin,CurSec
if( sta == "OK" || sta == "connected") //判断数据库连接是否正常
{
    CurMin = Number(getCurrentDate(1))
    CurSec = Number(getCurrentDate(2))
    if((CurMin % 5 == 0) && (CurSec == 30)  )
    {
        msg.payload = getCurrentDate(3)
        return msg;
    }
}
else
{
   //只有判断到数据库连接正常后才输出,否则无输出
}
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){    //参数为1时返回分钟数
        time = minu;
      }
      //精确到分
      else if(format==2){  //参数为2时返回秒数
        time = sec;
      }
      else if(format==3){   //参数为3时返回完整时刻
        time = year    month    date   hour   minu    sec;
      }
      
      return time;
}

Template模板(请求XML方法):用于编写SOAP接口连接主体参数 。

XML(XML与JS对象格式互转):格式转换,用于将XML与JS对象格式互相转换。

Simple-SOAP SOAP请求:用于设定连接参数,接口信息和连接SOAP接口 。

Base URL:

http://xxxxxxxx/MESService/BaseService.svc

Action: http://tempuri.org/IServiceBase/xxxxxxx

Function函数(判断是否为空):用于判断SOAP请求返回值是否为空值、故障值、正常值。

代码语言:javascript复制
var str0,str1,str2
str2 = msg.payload["envelope"]["body"]["0"]
 
if(str2.getseqorderresponse)  //判断请求返回数据是否为正常数据
{
    str0 = msg.payload["envelope"]["body"]["0"]["getseqorderresponse"]["0"]["getseqorderresult"]["0"]
    if(str0.length >10)
    {
        str1 = msg.payload["envelope"]["body"]["0"]["getseqorderresponse"]["0"]["getseqorderresult"]["0"]
        msg.payload = str1
        return [msg,null,null];
    }
    else
    {
       msg.payload = "EmptyData"
         return [null,msg,null];
        
    }
}
else if(str2.fault)
{
    msg.payload = str2
    return [null,null,msg];
}
 else  //除此之外,啥都不干
 {
 }

JSON节点(JSON):用于将返回的JSON格式转换为JS对象格式。

Function函数(写入总表):用于将从MES获取回来的数据按照数据库表结构写入到对应的数据库表中。

function函数(写入未生产订单表):用于将获取到的数据在写入总表的同时,写入到数据库未生产订单表内。

代码语言:javascript复制
var SQLStr1,SQLStr2  //定义SQL语句
var OrderData   //定义数组获取接受到的数据
var OrderNum //定义变量,记录获取到数组元素个数
var SQLValueData1,SQLValueData2,SQLValueData3  //定义SQL语句内Value个数,即插入多行数据
OrderData= msg.payload
OrderNum = OrderData.length
SQLValueData3 = " "
SQLStr1 = "insert into Getallorder(RecordTime,taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime,OrderStatus) Values"
for(i=0;i<orderdata.length;i  )   ="" 循环信息,将获取到的订单组合成多行插入语句<="" span="">
{
    SQLValueData1 = "('"  getCurrentDate(2)   "','"  OrderData[i].taskID   "','"   OrderData[i].SerialNo   "','"   OrderData[i].Category   "','"   OrderData[i].PartNo  "','"   OrderData[i].SeqOrderNo   "','"
    SQLValueData2 = OrderData[i].SeqOrderSn   "','"   OrderData[i].AssemblyLine   "','"   OrderData[i].CustPartNo   "','"   OrderData[i].PublishTime   "','"   OrderData[i].ExpectedArrivalTime  "','"   OrderData[i].RackNo   "','"  OrderData[i].FlexTime   "','"   "No')"
    SQLValueData3 =SQLValueData3   SQLValueData1   SQLValueData2   ","
}
SQLStr2 = SQLStr1   SQLValueData3
SQLStr2 = SQLStr2.slice(0,SQLStr2.length-1)  //去掉最后一个字符
//调用getCurrentDate(1) 会返回当前日期  格式 2020-01-01
//调用getCurrentDate(2) 会返回当前日期时间 格式 2020-01-01 01:01:01
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){
        time = year   "-"   month   "-"   date;
      }
      //精确到分
      else if(format==2){
        time = year   "-"   month   "-"   date  " "   hour   ":"   minu   ":"   sec;
      }
      return time;
}
msg.payload = SQLStr2
return msg;

function函数(空值记录):用于编写SQL语句,记录SOAP返回的空值和故障值写入到数据内。

代码语言:javascript复制
var SQLStr //定义SQL语句
var GetData
GetData= msg.payload
 
if(GetData == "EmptyData")
{
    SQLStr = "update GetallOrderEvent set ResponseTime = '"   getCurrentDate(2)   "',ResponseStatus = 'ConnGood,EmptyData' where ID = (select  ID from (select  ID from GetSeqOrderEvent   order by ID desc limit 1 ) as a )"
    msg.payload = SQLStr
    return msg;
}
else if(GetData.fault)
{
    SQLStr = "update GetallOrderEvent set ResponseTime = '"   getCurrentDate(2)   "',ResponseStatus = '"  GetData.fault[0].faultcode[0]._   "' where ID = (select  ID from (select  ID from GetallOrderEvent   order by ID desc limit 1 ) as a )"  
    msg.payload = SQLStr
    return msg;
}
else  //除此之外,啥都不输出
{
    
}
//调用getCurrentDate(1) 会返回当前日期  格式 2020-01-01
//调用getCurrentDate(2) 会返回当前日期时间 格式 2020-01-01 01:01:01
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){
        time = year   "-"   month   "-"   date;
      }
      //精确到分
      else if(format==2){
        time = year   "-"   month   "-"   date  " "   hour   ":"   minu   ":"   sec;
      }
      return time;
}
msg.payload = SQLStr
return msg;

Delay延时节点(限制1msg/s):用于限制数据流,此处设置为1秒1条信息流通过,为了避免信息流拥挤。

Change设定节点(设定SQL语句):用于配合后面的MySQL数据库连接节点使用,设定上一节点的信息传输到下一节点的topic属性内。

Mysql节点(MySQL):用于连接MYSQL数据库,执行前面编写的SQL语句。

点击上面Databas后面的小铅笔(编辑)按钮后,设置MySQL数据库的连接参数。

以下为辅助节点,用于报警,状态获取,手动触发等功能。

Status(状态)请求状态:用于获取SOAP连接节点的状态,将其连接状态信息通过后面节点记录到数据库内。

Function函数(响应事件记录):用于将SOAP连接节点的状态信息编写为SQL语句,记录到数据库内。

代码语言:javascript复制
var SQLStr //定义SQL语句
var objStatus = new Object()  //定义对象存储节点状态
if("text" in msg.status) //如果上一节点状态对象内包含text属性,表示有故障存在
{
    objStatus.text = msg.status["text"]  //获取故障信息
    objStatus.name = msg.status["source"]["name"]
    SQLStr = "update GetallOrderEvent set ResponseTime = '"   getCurrentDate(2)   "',ResponseStatus = '"   objStatus.text   "',ResponseNode ='"   objStatus.name   "' where ID = (select  ID from (select  ID from GetallOrderEvent   order by ID desc limit 1 ) as a )"
    msg.payload = SQLStr
    return [msg,null];  //第一个出口输出故障信息
}
else  //如果上一节点状态对象内不包含text属性,表示无故障存在,响应正常
{
    objStatus.text = "ConnectGood"
    objStatus.name = msg.status["source"]["name"]
    SQLStr = "update GetallOrderEvent set ResponseTime = '"   getCurrentDate(2)   "',ResponseStatus = '"   objStatus.text   "',ResponseNode ='"   objStatus.name   "' where ID = (select  ID from (select  ID from GetSeqOrderEvent   order by ID desc limit 1 ) as a )"
    msg.payload = SQLStr
    return [null,msg];  //第二个出口输出正常响应
}
//调用getCurrentDate(1) 会返回当前日期  格式 2020-01-01
//调用getCurrentDate(2) 会返回当前日期时间 格式 2020-01-01 01:01:01
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){
        time = year   "-"   month   "-"   date;
      }
      //精确到分
      else if(format==2){
        time = year   "-"   month   "-"   date  " "   hour   ":"   minu   ":"   sec;
      }
      return time;
}

Function函数(请求事件记录):用于记录发起SOAP连接的请求时间,将其记录到数据库。

代码语言:javascript复制
var SQLStr //定义SQL语句
//记录请求事件时间
SQLStr = "insert into GetallOrderEvent(RequestTime) Values ('"    getCurrentDate(2)   "')"
 
//调用getCurrentDate(1) 会返回当前日期  格式 2020-01-01
//调用getCurrentDate(2) 会返回当前日期时间 格式 2020-01-01 01:01:01
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){
        time = year   "-"   month   "-"   date;
      }
      //精确到分
      else if(format==2){
        time = year   "-"   month   "-"   date  " "   hour   ":"   minu   ":"   sec;
      }
      return time;
}
msg.payload = SQLStr
return msg;

function函数(报警变量输出):用于将报警SOAP请求响应的报警信息转换为对应的数字信号传输给PLC。

function函数(报警变量复位):用于将报警SOAP请求响应正常后,将报警信息转换为对应的复位数字信号传输给PLC。

S7-out西门子PLC写入节点(MES请求异常报警):用于连接到西门子PLC并且执行变量值写入动作。

Status状态节点(MySQL连接状态):用于获取MySQL数据库连接状态。

Change设定(设定到全局):用于将获取到的MysQL状态值设定到一个全局的变量。

Function函数(数据库连接异常输出):用于判断当前数据库状态值,如果不是状态,都认为异常,写到PLC内对应报警变量。

代码语言:javascript复制
var sta = msg.status.text
if( sta == "OK" || sta == "connected") //如果状态正常,就复位报警变量
{
    msg.payload = 0  //函数返回0
    return msg;
}
else
{
     msg.payload = 1  //函数返回1   //如果状态不正常,就触发报警变量
    return msg;  
}

S7-out西门子PLC变量写入节点(数据库连接异常报警):用于将上一节点编写的异常报警信号写入到PLC内对应的变量上。

Function函数(检测PLC信号):用于判断PLC内变量值为1时才允许触发后面的程序(模拟一种上升沿信号)。

Change设定(获取全局):获取数据库连接异常信号,如果异常,则阻止手动请求信号继续执行。

Function函数(手动请求任务号):用于编写手动请求时,SOAP连接的任务号,规定以“999”结尾的任务号为手动请求回来的。

代码语言:javascript复制
 var sta = msg.payload
 
if( sta == "OK" || sta == "connected") //判断数据库连接是否正常
{
 
  msg.payload = getCurrentDate(2)   "999" //后缀为999的任务号为手动请求
  return msg;
}
function getCurrentDate(format) //获取当前日期时间函数
{
      var now = new Date();
      var year = now.getFullYear(); //得到年份
      var month = now.getMonth();//得到月份
      var date = now.getDate();//得到日期
      var day = now.getDay();//得到周几
      var hour = now.getHours();//得到小时
      var minu = now.getMinutes();//得到分钟
      var sec = now.getSeconds();//得到秒
      month = month   1;
      if (month < 10) month = "0"   month;
      if (date < 10) date = "0"   date;
      if (hour < 10) hour = "0"   hour;
      if (minu < 10) minu = "0"   minu;
      if (sec < 10) sec = "0"   sec;
      var time = "";
      //精确到天
      if(format==1){
        time = year   "-"   month   "-"   date;
      }
      //精确到分
      else if(format==2){
        time = year    month    date   hour   minu    sec;
      }
      return time;
}

Delay延时(延迟2秒):接受到手动请求信号2秒后,将该信号复位。

未完待续

李大拿家的王小拿

2022年8月

0 人点赞