当MES遇上PLC——SOAP篇(下)

前 言:

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

某汽车零部件制造厂在进行智能工厂的升级改造,工单派发和生产顺序指定由MES系统完成,西门子1200 PLC负责生产控制系统。但是,MES系统只能提供SOAP协议给PLC

在第一部分介绍中(当MES遇上PLC——SOAP篇(上)(含调试程序)),我们详细讲解利用伟联科技的边缘计算模块(WL-320E-M)建立起它和PLC之间的连接,今天让我们继续,让MES系统的数据,流畅地传输到PLC中

3. PLC请求订单数据

该部分功能为实现PLC从数据库获取未生产订单,当PLC发出数据库请求信号时,边缘计算模块先将正在生产表内订单信息转移到生产完成表内,清空正在生产表。然后,边缘计算模块会从数据库内获取当前未生产订单表内存在的订单信息,判断是否已经生产完毕。

如果已经从未生产表内查不到任何订单信息,表示所有订单生产完毕,此时向PLC内变量发送一个信息,通知PLC。

如果未生产表内还有信息,会将排列在前面的一组订单信息(相同任务号的一组,包含≤10条信息)读取回来,将该信息传输给PLC相应的变量,同时,将该组信息转移到正在生产表中。并且将该信息从未生产表内删除。

图片

使用到的节点如下

S7-in西门子PLC变量读取(FBDBTrig):用于读取PLC内FB部分数据库触发变量值。对于RBEXE部分,读取的就是RBEXE的数据库触发变量值。 

图片

Function函数(检测PLC信号):用于判断PLC值,相当于让PLC值变为一个上升沿信号。 

图片

Change设定(获取全局):获取当前数据库连接状态,将状态变量值设定给下一节点的输入属性。

图片

 Function函数(判断连接状态):判断当前数据库连接状态是否正常。 

图片

 var sta = msg.payload
if( sta == "OK" || sta == "connected") //判断数据库连接是否正常
{
        msg.payload = 1
        return msg;
}
else
{
   //只有判断到数据库连接正常后才输出,否则无输出
}

Function函数(获取FB正在生产表数据):编写SQL语句,获取FB正在生产表内数据。对于RBEXE,只需将其中表名称更换即可。

图片

var SQLStr1,SQLStr2  //定义SQL语句
 
SQLStr1 = "select taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime from FBExecutingOrder order by ID limit 1"
//查询未生产订单表里面是否还有数据
msg.payload = SQLStr1
return msg;

change设定(设定SQL语句):设定SQL语句到下一节点的topic属性。 

图片

Mysql节点,用于连接MYSQL数据库和执行SQL语句,返回执行结果。

Function函数(判断是否有数据):判断SQL语句执行完毕后返回值内是否有数据。 

图片

var GetData
GetData = msg.payload
 
if(GetData.length >=1)  //数组长度为大于等于1,表示有数据返回,否则判定为返回空,不执行后面步骤
{
     msg.payload = GetData
     return [msg,null];
}
else
{
    msg.payload = "NoOrder"
    return [null,msg];
}

Function函数(写入FB已完成生产表):用于将读取回来的正在生产表中数据写入到已完成生产表中。RBEXE的更换数据库表名称即可。 

图片

var SQLStr1,SQLStr2  //定义SQL语句
var SQLData   //定义数组获取接受到的数据
var taskIDStr
taskIDStr= msg.payload[0].taskID;
 
SQLStr1 = "select '"+ getCurrentDate(2) + "',taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime, 'Finished' from FBExecutingOrder where taskID = '" + taskIDStr + "'"
SQLStr2 = "insert into FBExecutedOrder(RecordTime,taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime,OrderStatus) " + SQLStr1
 
//调用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

change设定(设定SQL语句):设定SQL语句到下一节点的topic属性。 

图片

Mysql节点,用于连接MYSQL数据库和执行SQL语句

Delay延时(延迟1秒):从正在生产表中读取数据1秒后,将该数据从正在生产表中删除。

Function(清空FB正在生产表):组合SQL语句,清空正在生产表。 

图片

以下部分为PLC请求数据功能节点:

Delay延迟(延迟3秒):从获取到PLC请求信号,到判断当前数据库连接正常后延迟3秒,再将数据库内数据写入到PLC中(为了避免此过程中与正在生产表的操作发生冲突)。 

图片

Function函数(获取未生产表FB数据):组合SQL语句,用于从未生产表中查询是否还有未生产的FB数据。 

图片

var SQLStr1,SQLStr2  //定义SQL语句
SQLStr1 = "select taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime from unExecutedOrder where category = 'FB' order by ID limit 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 = SQLStr1
return msg;

 change设定(设定SQL语句):设定SQL语句到下一节点的topic属性。 

图片

Mysql节点,用于连接MYSQL数据库和执行SQL语句

Function函数(判断是否为空):判断读取回来的未生产表中FB数据是否为空,如果为空,就给PLC写一个信息。如果不为空,就执行下一步。 

图片

var GetData
GetData = msg.payload
 
if(GetData.length >=1)  //数组长度为大于等于1,表示有数据返回,否则判定为返回空,不执行后面步骤
{
     msg.payload = GetData
     return [msg,null];
}
else
{
    msg.payload = "NoOrder"
    return [null,msg];
}

Function函数(按顺序获取未生产的订单):组合SQL语句,按顺序读取未生产表中FB订单数据。RBEXE同理。 

图片

var SQLStr1,SQLStr2  //定义SQL语句
var taskIDData   //定义数组获取接受到的数据
var OrderNum //定义变量,记录获取到数组元素个数
var SQLValueData1,SQLValueData2,SQLValueData3;  //定义SQL语句内Value个数,即插入多行数据
taskIDData= msg.payload[0].taskID;
 
SQLStr1 = "select taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime from UnexecutedOrder where taskID = '" + taskIDData + "'"
 
msg.payload = SQLStr1
return msg;

 change设定(设定SQL语句):设定SQL语句到下一节点的topic属性。

图片

Mysql节点,用于连接MYSQL数据库和执行SQL语句。

Function函数(写入正在生产表):组合SQL语句,将从未生产表中读取到的FB数据写入到正在生产表。

图片

var SQLStr1,SQLStr2  //定义SQL语句
var SQLData   //定义数组获取接受到的数据
var taskIDStr
taskIDStr= msg.payload[0].taskID;
   
SQLStr1 = "select '"+ getCurrentDate(2) + "',taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime, 'executing' from UnExecutedOrder where taskID = '" + taskIDStr + "'"
SQLStr2 = "insert into FBExecutingOrder(RecordTime,taskID,SerialNo,Category,PartNo,SeqOrderNo,SeqOrderSn,AssemblyLine,CustPartNo,PublishTime,ExpectedArrivalTime,RackNo,FlexTime,OrderStatus) " + SQLStr1
msg.payload = SQLStr2
return 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函数(从原表删除):组合SQL语句,用于将已经读取到的FB数据从未生产表中删除。该节点中的SQL语句延时5秒后传输给mysql执行。

图片

var SQLStr1,SQLStr2  //定义SQL语句
var taskIDData   //定义数组获取接受到的数据
var OrderNum //定义变量,记录获取到数组元素个数
var SQLValueData1,SQLValueData2,SQLValueData3;  //定义SQL语句内Value个数,即插入多行数据
taskIDData= msg.payload[0].taskID;
SQLStr1 = "where taskID = '" + taskIDData + "'"
SQLStr2 = "delete from UnexecutedOrder " + SQLStr1
msg.payload = SQLStr2
return msg;

 function函数(FB写入到PLC):将从未生产表中读取回来的FB数据与PLC变量中相应变量地址组合起来,以两个数组方式传输给下一节点。 

图片

变量地址和变量数据对应关系为

图片

var GetData
var k
var ObjWrite = new Object() //创建新对象
var arryAddr = new Array()  //创建新数组—存放地址
var arryData = new Array()  //创建新数组—存放数据
 
for(let j1=1;j1<11;j1++)  //for循环,控制创建地址数组内元素
{
    arryAddr[j1-1] = String("FBSerialNo" + j1); //string为强制转换为字符串函数
}
 
for(let j3=11;j3<21;j3++)  //for循环,控制创建地址数组内元素
{
    arryAddr[j3-1] = String("FBSeqOrderNo" + Number(j3-10));//number函数将字符串转换为数字
}
for(let j4=21;j4<31;j4++)  //for循环,控制创建地址数组内元素
{
    arryAddr[j4-1] = String("FBCustPartNo" + Number(j4-20));
}
for(let j5=31;j5<41;j5++)  //for循环,控制创建地址数组内元素
{
    arryAddr[j5-1] = String("FBRackNo" + Number(j5-30));
}
for(let j6=41;j6<51;j6++)  //for循环,控制创建地址数组内元素
{
    arryAddr[j6-1] = String("FBFlexTime" + Number(j6-40));
}
GetData = msg.payload
for(let m=0;m<50;m++)  //for循环,数据数组默认清空后在写数据
{
    arryData[m] = String(" ");
}
for(let k=0;k<getdata.length;k++)< span="">
{
        arryData[k]    = String(GetData[k].SerialNo);  //此处为举例
        arryData[k+10] = String(GetData[k].SeqOrderNo)
        arryData[k+20] = String(GetData[k].CustPartNo)
        arryData[k+30] = String(GetData[k].RackNo)
        arryData[k+40] = String(GetData[k].FlexTime)
}
ObjWrite.variable = arryAddr;//给地址对象赋值
ObjWrite.payload =arryData; //给数据对象赋值
msg.payload = ObjWrite  //将对象传递给函数节点输出
return msg;

change设定(设定地址):用于将上一节点传输过来的PLC地址数据和数据数组分别设定到协议节点的variable(地址)属性和payload(数据)属性上。实现多个变量值同时写入。

图片

S7-out(PLCWrite):西门子PLC变量写入节点,此处实现多个变量值批量写入功能。

图片

以下为辅助节点,未生产表无数据输出和数据库触发变量复位。

Function函数(订单数据库无数据):当从未生产表中查询返回的数据中没有FB数据时,前面节点会返回NoOrder字符串,此时输出一个信号给PLC内变量。该变量在数据库有数据时会被复位。 

图片

Function函数(订单数据库有数据):将复位信号发送给PLC。 

图片

以下为复位数据库触发变量

Function函数(复位动作标签):给PLC对应变量发送一个复位信号。 

图片

图片

PLC请求事件记录 

图片

此处功能为记录PLC对数据库的请求记录,将PLC何时发送触发标签到边缘计算模块,模块何时将数据返回给PLC,做记录以备后面查询。所用达到的节点为函数节点和PLC连接节点,RBEXE和FB请求类似,此处不再赘述。

Function函数(PLC请求记录) 

图片

var SQLStr //定义SQL语句
 
//记录请求事件时间
SQLStr = "insert into plcrequestevent(RequestTime,Category) Values ('"  + getCurrentDate(2) + "' , 'RBEXE')"
 
//调用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;

 PLC响应事件记录

var SQLStr //定义SQL语句
var GetData = msg.payload
if(GetData == 1)  //表示返回空数据,已经没有数据了
{
    SQLStr = "update plcrequestEvent set ResponseTime = '" + getCurrentDate(2) + "',ResponseStatus = '" + "Nodata" + "'  where ID = (select  ID from (select  ID from plcrequestevent  where Category = 'FB' order by ID desc limit 1 ) as a )"
}
else
{
   SQLStr = "update plcrequestEvent set ResponseTime = '" + getCurrentDate(2) + "',ResponseStatus = '" + "Good" + "'  where ID = (select  ID from (select  ID from plcrequestevent  where Category = 'FB' order by ID desc limit 1 ) as a )"
}
 
//调用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;

 至此,用户需要的功能实现完毕。当然,除此之外,北京伟联科技有限公司发布的边缘计算模块功能还有以下这些:

图片

图片

 

李大拿家的王小拿

2022年8月