利用Codesys+Node.js+Mysql构建CNC数字车间工业物联网

一、概述

本项目的应用场景是CNC加工车间。

1,通过Codesys定时主动连接并且以http-post的方式上传状态信息到web服务器(Node.js+Express),而用户通过PC、手机和平板的浏览器访问web服务器获取设备状态信息。

2,Codesys在有需要时候主动连接并且以Tcp-Mysql报文的方式操作数据库(Mysql),获取生产需要的资料(G加工代码)。

3,用户经过PC、手机和平板的浏览器访问web服务器来灵活管理保存于数据库的生产资料,实现生产调度。

通过本项目展示 工业物联网项目的设备上云的基本框架,实现 设备远程数字化监控、灵活有效的生产调度这两个工业物联网的关键能力。

二、代码展示

2.1Codesys代码

2.1.1架构

2.1.2代码

本项目的Codesys程序沿用了我之前博文的《WPF上位机+Codesys的CNC在Codesys利用Socket操作Mysql,因此Servo、CNC和Mysql的代码,就不再展示。本文只展示其中的 http-post部分和Mysql的应用部分。

2.1.2.1 WebUpInfo
//作者:AlongWU
TYPE WebUpInfo :
STRUCT
    
STR_EquipId              :STRING(30);              //设备ID
R_V_X                    :REAL;                    //X位置
R_V_Y                    :REAL;                    //Y位置
R_V_Z                    :REAL;                    //Z位置
I_LineNo                 :DINT;                    //G代码的当前执行行号
W_V_State                :WORD;                    //程序状态

END_STRUCT
END_TYPE
2.1.2.2 Http_PostPacket_FB
//作者:AlongWU
FUNCTION_BLOCK Http_PostPacket_FB
VAR CONSTANT
abyEmpty               :ARRAY [0..999] OF BYTE;                //复位数组
END_VAR

VAR_INPUT
STR_Url                :STRING(30);                            //post报文的url字段
STR_Host               :STRING(30);                            //post报文的Host字段
Data                   :ARRAY[0..999]OF BYTE;                  //post报文 数据字节数组
DataLen                :INT;                                   //post报文 数据字节数组的字节数

END_VAR

VAR_OUTPUT
Packet                  :ARRAY [0..999] OF BYTE;                //输出post报文
PacketSize              :UINT;                                  //post报文的字节数
END_VAR

VAR
HeaderLen                :INT;                                   //post报文header的字节数
STR_HttpHeader           :STRING(200);                           //post报文Header
END_VAR

//构建报文header,  $0a 是换行符 $0d 是回车符
STR_HttpHeader:=CONCAT('POST ',STR_Url); 
STR_HttpHeader:=CONCAT(STR_HttpHeader,' HTTP/1.1$0d$0aAccept: */*$0d$0aAccept-Language: zh-CN$0d$0ahost:'); 
STR_HttpHeader:=CONCAT(STR_HttpHeader,STR_Host);                     
STR_HttpHeader:=CONCAT(STR_HttpHeader,'$0d$0aContent-TYPE: application/x-www-form-urlencoded$0d$0aConnection: close$0d$0aContent-Length: ');     
STR_HttpHeader:=CONCAT(STR_HttpHeader,INT_TO_STRING(DataLen));
STR_HttpHeader:=CONCAT(STR_HttpHeader,'$0d$0a$0d$0a');                            

//Header的字节数                                
HeaderLen :=LEN(STR_HttpHeader);

//复位字节数组
Packet :=abyEmpty;        

//header复制到 Packet
MEM.MemMove(pSource:= ADR(STR_HttpHeader), pDestination:= ADR(Packet), uiNumberOfBytes:=INT_TO_UINT(HeaderLen));

//data复制到 Packet
MEM.MemMove(pSource:= ADR(Data), pDestination:= ADR(Packet)+INT_TO_UINT(HeaderLen), uiNumberOfBytes:=INT_TO_UINT(DataLen));

//packet的总字节数
PacketSize :=INT_TO_UINT(HeaderLen + DataLen);
2.1.2.3 TcpUpInfoClient
//作者:AlongWU
PROGRAM TcpUpInfoClient

VAR CONSTANT
abyEmpty                    :ARRAY [0..999] OF BYTE;                        //复位数组
END_VAR

VAR
TCP_ClientFB                :NBS.TCP_Client;                                //TCP客户端连接实例
TCP_ReadFB                  :NBS.TCP_Read;                                  //TCP接收实例
TCP_WriteFB                 :NBS.TCP_Write;                                 //TCP发送实例
ServerIp                    :NBS.IP_ADDR:=(sAddr:='192.168.0.132');         //Tcp服务器地址
ServerPort                  :UINT:=8100;                                    //Tcp服务器port
Enable_F_TRIG               :F_TRIG;                                        //通信使能下降沿
Enable_R_TRIG               :R_TRIG;                                        //通信使能上升沿
Recei_R_TRIG                :R_TRIG;                                        //接收上升沿
Close_R_TRIG                :R_TRIG;                                        //关闭连接上升沿
Send_R_TRIG                 :R_TRIG;                                        //发送上升沿
WaitCount                   :UINT:=0;                                       //空闲计时
TryConnect                  :UINT;                                          //尝试连接次数
STR_Url                     :STRING(30);                                  //post报文的url字段
STR_Host                    :STRING(30);                                  //post报文的host字段
ConnectActive_R_TRIG        :R_TRIG;                                        //TCP连接成功上升沿
W_state                     :WORD;                                          //程序状态值
UpInfo                      :WebUpInfo;                                    //web上传数据的结构体
Datalen                     :INT;                                         //post报文data的字节数
PostPacketFB                :Http_PostPacket_FB;                       //构建post报文功能块的实例
UpInfoFB                    :WebUpInfoData_FB;                //构建post报文的data的功能块的实例
UpInfoFB_Size               :INT;                             //构建post报文的data的功能块的字节数
UpInfoFB_Data               :ARRAY [0..999] OF BYTE;     //构建post报文的data的功能块的输出字节数组
END_VAR



Enable_F_TRIG(CLK:=GVL.B_TcpUpInfo_Enable, Q=> );                                //使能下降沿
Enable_R_TRIG(CLK:=GVL.B_TcpUpInfo_Enable, Q=> );                                //使能上升沿

Recei_R_TRIG(CLK:=TCP_ReadFB.xReady, Q=> );                                      //接收上升沿

Send_R_TRIG(CLK:=(GVL.B_TcpUpInfo_Send), Q=> );                                  //发送上升沿
Close_R_TRIG(CLK:=GVL.B_TcpUpInfo_Close, Q=> );                                //关闭信号上升沿


IF Enable_F_TRIG.Q THEN
    //使能下降沿,复位状态
    GVL.B_TcpUpInfo_Send :=FALSE;
    GVL.TcpUpInfo_abyRx :=abyEmpty;
END_IF


IF Enable_R_TRIG.Q THEN
    //使能上升沿,复位状态
    GVL.B_TcpUpInfo_OK :=FALSE;
    GVL.B_TcpUpInfo_FF :=FALSE;
    TryConnect:=0;
    WaitCount :=0;
    
END_IF

IF Send_R_TRIG.Q THEN
    //发送上升沿,复位状态
    GVL.B_TcpUpInfo_OK :=FALSE;
    GVL.B_TcpUpInfo_FF :=FALSE;
    WaitCount :=0;
    
        (*

Http报文 例子
    
POST /PlcUpInfo  HTTP/1.1
Accept: */*
Accept-Language: zh-CN
host: localhost:8100
Content-Type: application/x-www-form-urlencoded
Content-Length: 54
Connection:close

EquipId=E00001&V_X=10&V_Y=20&V_Z=30&LineNo=1&V_State=1


    *)
    
    //结构体赋值
    UpInfo.STR_EquipId := GVL.STR_EquipID;
    
    UpInfo.R_V_X :=LREAL_TO_REAL(GVL.X_Pos);
    UpInfo.R_V_Y :=LREAL_TO_REAL(GVL.Y_Pos);
    UpInfo.R_V_Z :=LREAL_TO_REAL(GVL.Z_Pos);
    UpInfo.I_LineNo :=DINT_TO_INT(GVL.D_LineNo);
    
    W_state.0 :=GVL.Interpolator.bBusy;
    W_state.1 :=GVL.Interpolator.bDone;
    UpInfo.W_V_State :=W_state;
    
    
    //打包POST报文的 数据区
    UpInfoFB(UpInfo:=UpInfo , Data=>UpInfoFB_Data , DataSize=>UpInfoFB_Size );
    
    STR_Url:='/PlcUpInfo';
    STR_Host:=CONCAT(ServerIp.sAddr,':'); 
    STR_Host:=CONCAT(STR_Host,UINT_TO_STRING(ServerPort)); 
    
    //打包POST报文
    PostPacketFB(
    STR_Url:= STR_Url, 
    STR_Host:= STR_Host, 
    Data:= UpInfoFB_Data, 
    DataLen:= UpInfoFB_Size, 
    Packet=> GVL.TcpUpInfo_abyTx, 
    PacketSize=> GVL.TcpUpInfo_WriteSize);
    
END_IF




//TCP客户端连接功能块
TCP_ClientFB(
    xEnable:= GVL.B_TcpUpInfo_Enable, 
    xDone=> , 
    xBusy=> , 
    xError=> , 
    udiTimeOut:= 0, 
    ipAddr:= ServerIp, 
    uiPort:= ServerPort, 
    eError=> , 
    xActive=> , 
    hConnection=> );

    
//TCP客户端接收功能块
TCP_ReadFB(
    xEnable:= TCP_ClientFB.xActive, 
    xDone=> , 
    xBusy=> , 
    xError=> , 
    hConnection:= TCP_ClientFB.hConnection, 
    szSize:= 1000, 
    pData:= ADR(GVL.TcpUpInfo_abyRx), 
    eError=> , 
    xReady=> , 
    szCount=> );
    
//TCP客户端发送功能块
TCP_WriteFB(
    xExecute:= GVL.B_TcpUpInfo_Send, 
    udiTimeOut:= 100, 
    xDone=> , 
    xBusy=> , 
    xError=> , 
    hConnection:= TCP_ClientFB.hConnection,  
    szSize:= GVL.TcpUpInfo_WriteSize, 
    pData:= ADR(GVL.TcpUpInfo_abyTx), 
    eError=> );
    

//TCP连接成功上升沿
ConnectActive_R_TRIG(CLK:= TCP_ClientFB.xActive, Q=> );

    
IF GVL.B_TcpUpInfo_Enable THEN
    IF ConnectActive_R_TRIG.Q THEN    
        //TCP上传动作使能后,成功建立连接后,启动发送动作
        GVL.B_TcpUpInfo_Send := TRUE;            
    END_IF
END_IF    
    
    
    
//发送完成
IF TCP_WriteFB.xDone THEN
    GVL.B_TcpUpInfo_Send :=FALSE;    
END_IF

//发送后,web服务返回结果,断开连接
IF GVL.B_TcpUpInfo_Enable THEN
    WaitCount := WaitCount +1;
    IF (WaitCount >5 AND GVL.B_TcpUpInfo_Close =FALSE) OR TCP_ReadFB.xReady THEN
        GVL.B_TcpUpInfo_Close := TRUE;
    END_IF
END_IF

IF Close_R_TRIG.Q THEN
    //关闭信号上升沿,复位状态
    GVL.B_TcpUpInfo_Close := FALSE;
    GVL.B_TcpUpInfo_Enable:=FALSE;
    GVL.B_TcpUpInfo_Send :=FALSE;    
END_IF

    
2.1.2.4 WebUpInfoData_FB
//作者:AlongWU
FUNCTION_BLOCK WebUpInfoData_FB
VAR_INPUT
UpInfo                      :WebUpInfo;                   //输入上传数据结构体
END_VAR
VAR_OUTPUT
Data                        :ARRAY[0..999]OF BYTE;       //输出结果字节数组
DataSize                    :INT;                        //输出结果字节数组的字节数
END_VAR
VAR
STR_Data                    :STRING(255);
END_VAR


//构建post报文的data

STR_Data:=CONCAT('EquipId=',UpInfo.STR_EquipId); 
STR_Data:=CONCAT(STR_Data,'&V_X='); 
STR_Data:=CONCAT(STR_Data,Real2Str(UpInfo.R_V_X,3)); 
STR_Data:=CONCAT(STR_Data,'&V_Y='); 
STR_Data:=CONCAT(STR_Data,Real2Str(UpInfo.R_V_Y,3)); 
STR_Data:=CONCAT(STR_Data,'&V_Z='); 
STR_Data:=CONCAT(STR_Data,Real2Str(UpInfo.R_V_Z,3)); 
STR_Data:=CONCAT(STR_Data,'&LineNo='); 
STR_Data:=CONCAT(STR_Data,DINT_TO_STRING(UpInfo.I_LineNo));     
STR_Data:=CONCAT(STR_Data,'&V_State='); 
STR_Data:=CONCAT(STR_Data,WORD_TO_STRING(UpInfo.W_V_State)); 

//data的字节数量
DataSize :=LEN(STR_Data);

//字符串转字节数组
MEM.MemMove(pSource:= ADR(STR_Data), pDestination:= ADR(Data), uiNumberOfBytes:=INT_TO_UINT(DataSize));
2.1.2.5 MysqlGetNewGcode
//作者:AlongWU
PROGRAM MysqlGetNewGcode

VAR CONSTANT
EmptyResult                  :ARRAY[0..5119] OF BYTE;            //复位字节数组
END_VAR


VAR
QueryStr                      :STRING(100);                        //查询命令字符串
QueryBytes                    :ARRAY[0..5119] OF BYTE;             //查询命令buffer
MysqlCmdPack                  :MysqlQueryCmdPack_FB;               //Mysql指令实例
MysqlFirstRowInfo             :MysqlRowInfo;                       //Mysql首行数据结构体
MysqlFirstRowDecode           :MysqlFirstRowData_FB;        //Mysql解析回复表格信息并提取首行数据
GcodeBytes                    :ARRAY[0..5119] OF BYTE;             //查询命令buffer
tmpUint                       :UINT;
tmpAddr                       :UINT;
TxtWrite                      :TxtWrite_FB;                        //txt写入功能块实例
TxtTruncate                   :TxtTruncate_FB;                     //txt清空功能块实例
GcodeSize                     :UINT;                               //NextGcode字节数
END_VAR


CASE GVL.I_GetNextCodeStep OF 
        0:        
            QueryBytes := EmptyResult;                                   //指令buffer复位
            
            IF GVL.B_Mysql_Inited THEN 
                GVL.I_GetNextCodeStep := 10;                             //已连接数据库
            ELSE
                GVL.I_GetNextCodeStep := GVL.I_GetNextCodeStep +1;       //未连接数据库,下一步
            END_IF
        1:
            GVL.B_Mysql_Enable :=TRUE;
            GVL.I_GetNextCodeStep := GVL.I_GetNextCodeStep +1;           //下一步
        2:
            IF GVL.B_Mysql_Inited THEN         
                GVL.I_GetNextCodeStep := 10;                              //已连接数据库
            END_IF
            
            IF GVL.B_Mysql_AuthFalure THEN                                //连接失败,推出登录
                GVL.I_GetNextCodeStep := 99;
            END_IF
        10:
            GVL.B_Mysql_Result :=FALSE;                                    //复位接收解析标志
            
            // '要用$27来转义,特殊的符号,用$+16进制的acsii值
            QueryStr :='SELECT `NextGcode` FROM `equiptable` WHERE `STATE`=1 AND `EquipId`=$27';
            QueryStr := CONCAT(QueryStr,GVL.STR_EquipID);
            QueryStr := CONCAT(QueryStr,'$27 LIMIT 1');
            MEM.MemMove(pSource:= ADR(QueryStr), pDestination:= ADR(QueryBytes), uiNumberOfBytes:=INT_TO_UINT(LEN(QueryStr)));
            //构建cmd
            MysqlCmdPack(Cmd:=3 , InputBytes:= QueryBytes, Result=> GVL.Mysql_abyTx, ResultSize=>GVL.Mysql_WriteSize );        
            GVL.B_Mysql_Send :=TRUE;
            GVL.I_GetNextCodeStep := GVL.I_GetNextCodeStep +1;             //下一步
        11:
            IF GVL.B_Mysql_FF OR GVL.B_Mysql_FE THEN
                GVL.I_GetNextCodeStep:=99;                                 //数据库错误,退出程序
                RETURN;
            END_IF
        
            IF GVL.B_Mysql_Result THEN
                MysqlFirstRowDecode(InputRxBytes:=GVL.Mysql_abyRx , FirstRowInfo=>MysqlFirstRowInfo );        //解析row数据
                IF MysqlFirstRowInfo.IsNull THEN
                    //回复为空,即该EquipID没有数据        
                    //NOTHING
                    GVL.I_GetNextCodeStep:=99;
                ELSE
                    //回复为真,该EquipID有G代码资料
                    TxtTruncate();                                                                            //重置cnc文件
                    GVL.I_GetNextCodeStep:=GVL.I_GetNextCodeStep +1;                        
                END_IF                
            END_IF
        12:
            //读取mysql回复数据
            GcodeSize :=MysqlFirstRowInfo.RowData[0];
            MEM.MemMove(pSource:= ADR(MysqlFirstRowInfo.RowData)+1, pDestination:= ADR(GcodeBytes), uiNumberOfBytes:=GcodeSize);                
            //写入并更新txt文件
            TxtWrite(Buffer:=ADR(GcodeBytes), BufferLen:= GcodeSize);    
            GVL.I_GetNextCodeStep:=GVL.I_GetNextCodeStep +1;    
        13:
            // 更新ActiveGcode
            QueryStr :='UPDATE `equiptable` SET `ActiveGcode`=$27';
            tmpUint :=INT_TO_UINT(LEN(QueryStr));
            MEM.MemMove(pSource:= ADR(QueryStr), pDestination:= ADR(QueryBytes), uiNumberOfBytes:=tmpUint);
        
            tmpAddr := tmpUint;
            //Gcode复制到命令中
            MEM.MemMove(pSource:= ADR(GcodeBytes), pDestination:= ADR(QueryBytes)+tmpAddr, uiNumberOfBytes:=GcodeSize);
        
            tmpAddr := tmpAddr + GcodeSize;
            
            QueryStr :='$27 WHERE `EquipId`=$27';
            QueryStr := CONCAT(QueryStr,GVL.STR_EquipID);
            QueryStr := CONCAT(QueryStr,'$27');
            
            MEM.MemMove(pSource:= ADR(QueryStr), pDestination:= ADR(QueryBytes)+tmpAddr, uiNumberOfBytes:=INT_TO_UINT(LEN(QueryStr)));
            //构建cmd
            MysqlCmdPack(Cmd:=3 , InputBytes:= QueryBytes, Result=> GVL.Mysql_abyTx, ResultSize=>GVL.Mysql_WriteSize );        
            GVL.B_Mysql_Send :=TRUE;
            GVL.I_GetNextCodeStep := GVL.I_GetNextCodeStep +1;                                                //下一步
        14:
            IF GVL.B_Mysql_FF OR GVL.B_Mysql_FE OR  GVL.B_Mysql_OK THEN
                GVL.I_GetNextCodeStep:=99;                                            
            END_IF            
        99:            
            GVL.I_GetNextCodeStep:=100;
ELSE        
    GVL.B_GetNextCode:=FALSE;
    GVL.B_GetNextCodeTask:=FALSE;
END_CASE


//mysql错误处理
IF GVL.B_Mysql_AuthFalure THEN
    GVL.B_GetNextCode := FALSE;
    GVL.B_GetNextCodeTask:=FALSE;
END_IF
2.1.2.6 PLC_PRG
//作者:AlongWu
PROGRAM PLC_PRG
VAR
    
ReadFileTask_F_TRIG             :F_TRIG;                //读取cnc文件标志下降沿
Cnc_Start_R_TRIG                :R_TRIG;                //cnc执行标志上升沿
B_MysqlGetGCode_R_TRIG          :R_TRIG;                //Mysql更新G代码上升沿
UpBlink                         :INT:=0;                //http post的延时计数

END_VAR

//cnc执行标志上升沿
Cnc_Start_R_TRIG(CLK:= GVL.B_Cnc_Start, Q=> );

IF Cnc_Start_R_TRIG.Q THEN
    //读取cnc文件标志和读取文件Task的触发标志,置TRUE。
    GVL.B_ReadFile:=TRUE;
    GVL.B_ReadFileTask:=TRUE;
END_IF

//读取txt文件标志下降沿
ReadFileTask_F_TRIG(CLK:= GVL.B_ReadFileTask, Q=> );

IF ReadFileTask_F_TRIG.Q THEN
    //读取cnc并解析后,启动插补器
    GVL.B_Cnc_Ipo:= TRUE;
    GVL.B_Cnc_Start :=FALSE;
END_IF


//通过Mysql获取 NextGCode
B_MysqlGetGCode_R_TRIG(CLK:=GVL.B_GetNextCode , Q=> );                                                

IF B_MysqlGetGCode_R_TRIG.Q THEN
    //启动Mysql获取NextGCode
    GVL.I_GetNextCodeStep := 0;    
    GVL.B_GetNextCodeTask :=TRUE;
    GVL.B_Mysql_AuthFalure :=FALSE;
END_IF

(*http post 上传数据*)
IF UpBlink < 2 THEN
    UpBlink :=UpBlink+1;
ELSE
    //每2个周期上传一次
    GVL.B_TcpUpInfo_Enable:= TRUE;
    UpBlink:=0;
END_IF

2.2服务器端Node.js代码

//------------const end------------
 
const express = require('express');
const mysql = require('mysql');
const bodyParser = require('body-parser');

// 创建web服务器
const app = express();

//mysql connection
const connection = mysql.createConnection({
    host     : 'localhost',
    user     : 'testNode',
    password : 'testNode',
    database : 'testcnc'
  });

//------------const end--------------

//------------class start------------
 
class PLC {
    constructor(EquipId, Name,TouchCount,V_X,V_Y,V_Z,V_LineNo,V_State,ActGcode,NextGcode,sqlState) {
      this.EquipId = EquipId
      this.Name = Name
      this.V_X = V_X
      this.V_Y = V_Y
      this.V_Z = V_Z
      this.V_LineNo = V_LineNo
      this.V_State = V_State
      this.TouchCount = TouchCount
      this.ActGcode = ActGcode
      this.NextGcode = NextGcode
      this.sqlState = sqlState
    }
  
  }

 //------------class end--------------

//------------object start------------
 
//plc变量值 Json对象
var tmpPlcVars =
{
    EquipId:'',
    Name:'',
    X:0,
    Y:0,
    Z:0,
    LineNo:0,
    TouchCount:0,
    State:0
};

var tmpGcodeVars =
{
    EquipId:'',
    ActGcode:'',
    NextGcode:'',
    res:0
};

//------------object end------------

//------------var start-------------
   
  //PLC的class数组
  var PLC_ArrList = new Array();
 // 创建 application/x-www-form-urlencoded 编码解析
  var urlencodedParser = bodyParser.urlencoded({ extended: false })

//------------var end---------------
  
//------------ function start-------

//查询mysql设备信息表
 function  QueryEquipList()
{
   connection.query('SELECT * FROM equiptable WHERE STATE=1', function (error, results) {
       if(error){       
           console.log('[SELECT ERROR] - ',error.message);
           return;
         }
        let rowResult;
        let tmpPlc;
        let n,h,i,j;
        let TmpPlcList;
        h = results.length;
        n = PLC_ArrList.length;
       
       /*
         1,查询mysql数据库,获取当前能用的equiplist。
         2,查询结果跟当前的PLC_ArrList进行,数据更新并且 mysql数据库标注为可用的,更新PLC_ArrList项的sqlState;如果有新增,push添加。
         3,将PLC_ArrList项的sqlState为0 的剔除,并重新建立PLC_ArrList。
       */


        if(h > 0)
        {        
           //全部plc的sqlstate置0   
           for(i=0;i < n;i++ )
          {
            PLC_ArrList[i].sqlState = 0;
          }
         
           //如果PLC_ArrList的项在mysql仍然有记录,则sqlstate置1
           for ( i=0; i<h; i++) 
           {                  
               rowResult = results[i];             
               for( j=0;j < n;j++ )
               {
                   if(rowResult['EquipId'] == PLC_ArrList[j].EquipId)
                   {
                    //找到PLC_ArrList的位置,更新ActiveGcode和NextGcode。
                       PLC_ArrList[j].ActiveGcode = rowResult['ActiveGcode'];
                       PLC_ArrList[j].NextGcode = rowResult['NextGcode'];
                       PLC_ArrList[j].sqlState = 1;
                       break;
                   }

               }

               //PLC_ArrList没有记录,push新增
               if(j == n)
               {
                   tmpPlc = new PLC(rowResult['EquipId'],rowResult['Name'],0,0,0,0,0,0,rowResult['ActiveGcode'],rowResult['NextGcode'],1);                      
                   PLC_ArrList.push(tmpPlc);        
                      
               }
           }

           //更新 PLC_ArrList的数量
           n = PLC_ArrList.length;

           TmpPlcList = new Array();

           //把 PLC_ArrList数组 复制到 TmpPlcList
           for (i=0; i<n; i++) 
           {
               TmpPlcList.push(PLC_ArrList.pop());
           }
 
           //PLC_ArrList复位
           PLC_ArrList = new Array();

           for (i=0; i<n; i++) 
           {
               tmpPlc = TmpPlcList.pop();

               if(tmpPlc.sqlState == 1)
               {
                   //如果 sqlState=1,重新压回 PLC_ArrList
                   PLC_ArrList.push(tmpPlc);
               }
           }
       }        
   });
   
}

//更新mysql设备的G代码数据项
function MysqlUpdateGCode(equipid,newcode)
{
    connection.query("UPDATE equiptable SET NextGcode='"+newcode+"' WHERE EquipId='"+equipid+"'", function (error, results) {
        if(error){       
            console.log('[SELECT ERROR] - ',error.message);
            return;
          }    
       });
}

//构建express(web服务器)
function InitExpress()
{

// 启动服务器
app.listen(8100, () => {
   console.log('express server running at http://127.0.0.1')
});

//use方法,静态路由
app.use( express.static('page'));

//State指令的处理
app.get('/State', (req, res) => {  
   let id = req.query.EquipId;
   n = PLC_ArrList.length;
   if( n >0)
   {
       for(i=0;i<n;i++)
       {
           if(PLC_ArrList[i].EquipId == id)
           {             
               break;
           }
       }

       if(n == i)
       {          
           //设备未登记
           tmpPlcVars.EquipId ='';
           tmpPlcVars.Name ='';
           tmpPlcVars.X = 0;
           tmpPlcVars.Y = 0;
           tmpPlcVars.Z = 0;
           tmpPlcVars.LineNo = 0;
           tmpPlcVars.State = 0;
           tmpPlcVars.TouchCount = 0;   
       }
       else
       {
           //设备已登记
           //更新Plc数组的信息
           tmpPlcVars.EquipId  = PLC_ArrList[i].EquipId;
           tmpPlcVars.Name  = PLC_ArrList[i].Name;    
           tmpPlcVars.X =  PLC_ArrList[i].V_X;
           tmpPlcVars.Y =  PLC_ArrList[i].V_Y;
           tmpPlcVars.Z =  PLC_ArrList[i].V_Z;
           tmpPlcVars.LineNo = PLC_ArrList[i].LineNo;
           tmpPlcVars.State = PLC_ArrList[i].V_State;
           tmpPlcVars.TouchCount = PLC_ArrList[i].TouchCount;          
       }
   }
 //回复用户浏览器
   res.send(JSON.stringify(tmpPlcVars));
});

//Fresh G Code指令的处理
app.get('/FreshGode', urlencodedParser, function (req, res) {
   
   let id = req.query.EquipId;

   let i,n;

   n = PLC_ArrList.length;

   for(i=0;i<n;i++)
    {
       if(PLC_ArrList[i].EquipId == id)
       {
           break;
       }
    }
    if(i<n)
    {
        //如果所查找的EquipId在PLC_ArrList数组内,则赋值tmpGcodeVars。
       tmpGcodeVars.EquipId = id;
       tmpGcodeVars.ActGcode = PLC_ArrList[i].ActGcode;
       tmpGcodeVars.NextGcode = PLC_ArrList[i].NextGcode;
       tmpGcodeVars.res = 1;
    }
    else
    {
       //如果所查找的EquipId不在PLC_ArrList数组内,则回复查询失败。
       tmpGcodeVars.EquipId = "";
       tmpGcodeVars.ActGcode = "";
       tmpGcodeVars.NextGcode ="";
       tmpGcodeVars.res = 0;
    }
   
    //回复用户浏览器
   res.send(JSON.stringify(tmpGcodeVars));
   
})

//Plc上传数据 PlcUpInfo指令的处理
app.post('/PlcUpInfo', urlencodedParser, function (req, res) {
   let id = req.body.EquipId;
   let i,n;
   n = PLC_ArrList.length;

   if( n >0)
   {
       for(i=0;i<n;i++)
       {
           if(PLC_ArrList[i].EquipId == id)
           {
               break;
           }
       }

       if(n == i)
       {
           //设备未登记
           //nothing
           console.log('no equip');
       }
       else
       {
        
           //设备已登记
           //更新Plc数组的信息
           PLC_ArrList[i].V_X = req.body.V_X;
           PLC_ArrList[i].V_Y = req.body.V_Y;
           PLC_ArrList[i].V_Z = req.body.V_Z;
           PLC_ArrList[i].LineNo = req.body.LineNo;
           PLC_ArrList[i].V_State = req.body.V_State;
           PLC_ArrList[i].TouchCount = PLC_ArrList[i].TouchCount + 1;          

            if(PLC_ArrList[i].TouchCount > 10000)
            {
              PLC_ArrList[i].TouchCount =0;
            }

       }
   }
   res.end();//结束进程
  
})

//web导入新Gcode
app.post('/Newgcode', urlencodedParser, function (req, res) {
    let id = req.body.EquipId;
    let i,n;
    n = PLC_ArrList.length;
    if( n >0)
    {
        for(i=0;i<n;i++)
        {
            if(PLC_ArrList[i].EquipId == id)
            {
                break;
            }
        }
 
        if(n == i)
        {
            //设备未登记           
            res.send('EquipId:'+id+" 不可用。");
        }
        else
        {
            //设备已登记
            MysqlUpdateGCode(id,req.body.gc);
            res.send('新G代码已更新'); 
        }
    }
 
 })
}

//定时循环任务函数
function CircleTask1()
{
    QueryEquipList();   //查询mysql,获取最新equiplist
}

//------------ function end----------

//------------Main function start----

function MainFunction (){

    InitExpress();                      //建立Web服务器 express
    console.log('已建立Express');
          
    //建立mysql长连接
    connection.connect(function(err,data){
        if(err)
        { 
            throw err           
            return;                      //mysql不可用,退出程序
        }else
        {
            //连接成功            
            console.log('已连接mysql');   
        }});
                                        
    QueryEquipList();                   //第一次查询mysql,获取最新equiplist

    setInterval(CircleTask1,2000);      //启动定时任务1,周期2s
    console.log('已启动定时循环任务');
     
};
 
MainFunction();                         //启动MainFunction

//------------Main function end----

2.3客户端Html+原生js

<html>
<head>
<style type="text/css">
.mbutton {
    height: 45px;
    width: 100px;
}

.mtable{
  border:1px;
  border-style: solid;
  width: 850px;
  height: 350px;
  border-collapse: collapse;
}

.mtextarea{
  width: 250px;
  height: 400px;
  overflow-y: auto;
}

.codeSp
{
  background-color: lightgray;
}

.mtr{
  height: 50px;
}
</style>



<script type="text/javascript">

//------------var start------------

var GcodeTxt ;
var TmpActGcode='';
var T_CircleQuery,T_ReplyCheck;
var LostConnect =0;
var EquipId = 'E00001';
var TouchCount = 0,LastTouchCount = 0;
var SendLock=0;
var PlcOnline=0;
var codeArr;

//delay函数
const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))


//------------var end------------


//主函数
function MainFuncion()
{
  console.log("启动循环查询");
  var t=0;

  //构建定时循环任务
  var CircleAsyncTask = async() =>{ 
    while(LostConnect == 0)
    {
      
      if(t<10)
      {
        //每0.2s查询一次状态
        QueryFunc();
        t++;
      }
      else
      {
        //每2s查询一次g代码
        GetGodeFunc();
        t=0;

        if(LastTouchCount != TouchCount)
        {
          document.getElementById("onLine").textContent ='在线中';
          PlcOnline=1;
        }
        else
        {
          document.getElementById("onLine").textContent ='未连接';
          PlcOnline=0;
        }

        //更新TouchCount
        LastTouchCount = TouchCount;

      }
      //异步循环
      await delay(200)
    }
  console.log("结束循环查询");
  };

  //启动定时循环任务
  CircleAsyncTask();  

}

//G代码上传函数
function TxtUpload(input) {  
            //支持chrome IE10  
            if (window.FileReader) {                 
                var reader = new FileReader();  
                var file = input.files[0];  
                //构建reader
                reader.onload = function() {  
                    GcodeTxt = this.result;  
                    //把Txt文件的G代码发送到服务器
                    SendGodeFunc();
                }  
                reader.readAsText(file);  
            }   
          
        }  

//发送G代码至服务器的函数
function SendGodeFunc()
{
  let xmlhttp;
  let postCode;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        //复位Ajax响应计时
      clearTimeout(T_ReplyCheck);
      SendLock=0;
    }
  }


xmlhttp.open("post","/Newgcode",true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");   
postCode ="EquipId="+EquipId+"&gc="+GcodeTxt;

while(SendLock==1)
{
  //wait
}
xmlhttp.send(postCode);
SendLock=1;
//启动Ajax响应计时
T_ReplyCheck =setTimeout("AjaxRelyFalure()", 100);  

}

//查询当前设备状态的函数
function QueryFunc()
{
let xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
      //复位Ajax响应计时
      clearTimeout(T_ReplyCheck);
      SendLock =0;
      let PlcObj = JSON.parse(xmlhttp.responseText);

      document.getElementById("Plc_X").textContent = PlcObj.X;
      document.getElementById("Plc_Y").textContent = PlcObj.Y;
      document.getElementById("Plc_Z").textContent = PlcObj.Z;
      
      if(parseInt(PlcObj.State) == 0)
      {
        document.getElementById("Plc_State").textContent = "待机";
     
      }
      else
      {
        document.getElementById("Plc_State").textContent = "加工中";
      }

      TouchCount = parseInt(PlcObj.TouchCount);

      let lineNo = parseInt(PlcObj.LineNo);
      
      if(lineNo ==-1 || PlcOnline == 0)
      {
        document.getElementById("ActGcode").innerHTML =TmpActGcode;
      }
      else
      {
          
          let n = codeArr.length;
          let i;
          let code='';
          if(n>0)
          {
            for(i=0;i<n;i++)
            {
                if(i == lineNo)
                {
                  code = code + "<span class='codeSp'>"+codeArr[i]+'</span><br>';
                }
                else
                {
                  code = code + codeArr[i]+'<br>';
                }
            }
            document.getElementById("ActGcode").innerHTML =code;
            
          }

      }


    }
  }
xmlhttp.open("get","/State?EquipId="+EquipId,true);
xmlhttp.setRequestHeader("Content-Type", "text/html");   

while(SendLock==1)
{
  //wait
}

xmlhttp.send();
SendLock = 1;
//启动Ajax响应计时
T_ReplyCheck =setTimeout("AjaxRelyFalure()", 100);  
}

//查询当前设备G代码的函数
function GetGodeFunc()
{
  let xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        //复位Ajax响应计时
      clearTimeout(T_ReplyCheck);
      SendLock = 0;
      let GcodeObj = JSON.parse(xmlhttp.responseText);
     
      if(GcodeObj.res == 1)
      {
        codeArr = (GcodeObj.ActGcode).split('\r\n');       
        TmpActGcode = (GcodeObj.ActGcode).replace(/\r\n/g,'<br>');        
        document.getElementById("NextGcode").innerHTML = (GcodeObj.NextGcode).replace(/\r\n/g,'<br>');     
      }
      else
      {
        document.getElementById("ActGcode").innerHTML ='';
        document.getElementById("NextGcode").innerHTML= '';
      }
    }
  }
xmlhttp.open("get","/FreshGode?EquipId="+EquipId,true);
xmlhttp.setRequestHeader("Content-Type", "text/html");   

while(SendLock==1)
{
  //wait
}
xmlhttp.send();
SendLock = 1;
//启动Ajax响应计时
T_ReplyCheck =setTimeout("AjaxRelyFalure()", 100);  

}

//ajax接收超时报警函数
function AjaxRelyFalure()
{
  SendLock=0;
  LostConnect = 1 ;
  alert("服务器连接失败,请刷新页面");
}


//页面加载完成后,启动主函数
window.onload = function(){
            //页面加载即执行函数
            MainFuncion();
        }


</script>
</head>
<body>

<h2>NodeJs+Codesys</h2>
<table class="mtable" cellspacing="0">
<tr>
  <td width="250">
    <table cellspacing="0">
      <tr>
        <td><h3>设备执行G代码</h3></td>
      </tr>
      <tr>
        <td><div id="ActGcode" class="mtextarea"></div></td>
      </tr>
      
    </table>
  </td>
  <td width="250">
    <table cellspacing="0">
      <tr>
        <td><h3>设备下一个G代码</h3></td>
      </tr>
      <tr>
        <td><div id="NextGcode" class="mtextarea"></div></td>
      </tr>
      
    </table>
  </td>
  <td width="350">
    <table>
      <tr class="mtr">
        <td width="90px">
          <h3>X</h3>
        </td>
        <td>
          <h2 id="Plc_X"></h2>
        </td>
      </tr>
      <tr class="mtr">
        <td >
          <h3>Y</h3>
        </td>
        <td>
          <h2 id="Plc_Y"></h2>
        </td>
      </tr>
      <tr class="mtr">
        <td >
          <h3>Z</h3>
        </td>
        <td>
          <h2 id="Plc_Z"></h2>
        </td>
      </tr>
      <tr class="mtr">
        <td >
          设备状态
        </td>
        <td>
          <span id="Plc_State"></span>
        </td>
      </tr>
      <tr class="mtr">
        <td >
          在线状态
        </td>
        <td>
          <span id="onLine"></span>
        </td>
      </tr>

      <tr class="mtr">
        <td valign="middle">
          导入
        </td>
        <td >
          <input type="file"  onchange="TxtUpload(this)"/>
        </td>
      </tr>
    </table>
  </td>
</tr>
</table>

</body>
</html>

三、总结

本项目通过CNC的G代码的联网数据库调用和管理,展示了Codesys+Node.js+Mysql的工业物联网应用的基本构架和实现。项目实现的功能虽然比较简单,但基本体现了工业物联网的特性和应用。

Codesys通过http报文与web系统交互,意味着web服务器软件不限于Node.js+express,还可以PHP+apache 、 Asp.net+iis 等等全部web服务器系统。前端不限于原生js,还可以Vue、React等等的全部SPA的框架。

Codesys通过Tcp报文与数据库系统交互,意味着数据库不限于Mysql,还可以在codesys开发相应数据库的操作子程序来使用Oracle、SQLServer等等。

基于Codesys的socket能力,可以十分灵活和便捷地对接服务器软件(Web, ERP,MES等等),实现设备的联网,进而实现设备数字化管理和使用。

  • 22
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: CODESYS中文教程.pdf是一份介绍使用CODESYS软件进行编程的教程,旨在帮助初学者快速入门。CODESYS是一款广泛应用于工业自动化领域的编程软件,通过这份教程,读者可以了解到CODESYS的基本概念、界面和操作方法,以及如何使用该软件进行PLC编程。教程逐步介绍了编程语言、控制台、变量类型、数据类型、指令集、函数块和程序组织等内容,通过实际案例和练习来深入讲解相关概念和技术,帮助读者更好地掌握CODESYS编程技术。 CODESYS中文教程.pdf适合有一定编程基础的读者学习,如机械、电气、自动化等相关专业的学生和从事PLC编程的工程师等。教程内容清晰详尽,且配有丰富的配图和案例,讲解生动易懂。阅读者只需要按照教程学习并跟随练习,就可以逐渐掌握CODESYS编程技术,为未来从事工业自动化领域的工作打下坚实的基础。总之,CODESYS中文教程.pdf是一份很好的学习资料,有助于初学者快速掌握CODESYS编程技术。 ### 回答2: 《CODESYS中文教程.pdf》是一份关于CODESYS编程语言的中文教程文件。CODESYS是一种流行的集成开发环境(IDE)和编程工具,用于开发和编写针对工业自动化和控制系统的程序。该教程文件将帮助读者了解CODESYS的基本概念和功能,并提供逐步的指导和示例来帮助初学者入门。 教程的内容主要包括以下几个方面: 1. CODESYS的介绍:教程首先会简单介绍CODESYS的历史和特点,帮助读者理解它的用途和优势。 2. 环境设置:教程将指导读者如何在自己的电脑上安装和配置CODESYS开发环境,以便开始编写代码。 3. 语言基础:教程将详细介绍CODESYS的编程语言特性,包括变量定义、数据类型、运算符、控制流等。 4. 编程实例:教程会提供一些实际的编程示例,帮助读者理解如何应用CODESYS来解决实际问题,例如开关控制、传感器数据读取等。 5. 进阶主题:教程还将介绍一些高级的主题,如面向对象编程、库函数的使用等,帮助读者进一步提升编程技能。 通过学习《CODESYS中文教程.pdf》,读者可以获得使用CODESYS开发工具的基本知识,掌握CODESYS编程语言的基本技能,并能够应用这些知识来开发和维护工业自动化系统。这对于在工业自动化领域工作的人员和对CODESYS感兴趣的学习者来说,都是一份有价值的学习资料。 ### 回答3: “codesys中文教程.pdf”是一本关于CODESYS编程软件的中文教程手册。CODESYS是一款先进的工业自动化和嵌入式系统开发工具,被广泛应用于世界各地的工业控制系统中。 CODESYS中文教程.pdf为用户提供了一份详细而全面的学习指南,帮助用户快速上手使用该软件。该教程包括以下内容: 1. CODESYS软件介绍:介绍了CODESYS的基本概念、功能和特点,以及其在工业自动化领域的应用。 2. 安装与配置:详细讲解了CODESYS的安装过程和配置方法,帮助用户正确设置开发环境。 3. 编程基础:从基本的编程概念入手,介绍了CODESYS的编程语言、数据类型、变量和运算符等内容。 4. 基本功能:介绍了CODESYS中常用的功能模块,如I/O配置、通讯设置、定时器和计数器等。 5. 高级功能:深入探讨了CODESYS中的高级编程技术,如函数块、任务、委托和异常处理等。 6. 应用实例:提供了一些实际应用的示例,通过具体案例展示了CODESYS的实际应用场景。 通过CODESYS中文教程.pdf,用户可以系统地学习和掌握CODESYS的使用,从而能够独立开发和维护工业控制系统。教程以简洁明了的语言、配图和示例为辅助,使用户能够更加轻松地理解和运用所学知识。 总之,CODESYS中文教程.pdf是一本优质的学习资料,适用于对CODESYS软件感兴趣的工业自动化从业人员和学习者。它不仅提供了必要的理论知识,还以实际案例和操作指南的形式帮助用户加深理解,具有很高的实用性和教育价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值