TwinCAT XML服务器提供了一个PLC库,可以实现对XML数据的写/读访问。XML服务器的特点是易于处理。它特别适合于加载初始化数据,因此通常需要它来启动机器。XML服务器通过ADS与TwinCAT PLC通信。
- 在写入过程中,变量将被转换成文本,并通过MSXML DOM解析器存储在XML文件中。XML文件不包含任何关于数据类型的信息,只包含变量名及其值:<name_of_variable> value </name_of_variable>。
- 在读取过程中,将通过ADS确定每个变量的数据类型,相应的XML文本将被正确地转换。
1、需要安装的组件
- TwinCAT XML Server:随TwinCAT一起启动,用于TwinCAT和XML文件之间的连接。
- PLC 库:提供了读写操作的四个功能块。允许在XML文件中格式化保存PLC变量,并使用XML文件中的数据初始化TwinCAT变量。
注:TwinCAT XML Server可以在官网下载安装(TF6421-XML-Server);PLC 库需要在项目中手动添加。如图所示,已经在系统中安装好了TF6421,以及在项目中的references添加了Tc2_XmlDataSrv,这时就可以使用xml功能了。
2、Tc2_TcXmlDataSrv中的四个功能块
Tc2_TcXmlDataSrv库中有四个读/写xml的功能块,包括两个读(FB_XMLSrvRead, FB_XMLSrvReadByName)和两个写(FB_XMLSrvWrite, FB_XMLSrvWriteByName)。
(FB_XMLSrvRead, FB_XMLSrvWrite)使用PLC变量的地址和大小来指定变量。(FB_XMLSrvReadByName, FB_XMLSrvWriteByName)使用符号名来指定变量。前者具有更高的性能。此外,XML文件的路径和XML文档中变量的位置必须作为输入参数传递给函数块。
2.1 FB_XmlSrvRead
2.2 FB_XmlSrvReadByName
2.3 FB_XmlSrvWrite
2.4 FB_XmlSrvWriteByName
- sNetId:包含TwinCAT 3 XML Server的网络地址的字符串。对于本地计算机(默认),可以指定一个空字符串。
- epath:用于在目标设备上选择打开文件的TwinCAT系统路径。
- nMode:用于控制XML文件的计算方式。XmlSrvRead (同 XmlSrvReadByName)命令只支持 XMLSRV_SKIPMISSING 模式。XmlSrvWrite (同 XmlSrvWriteByName)可以使用 XMLSRV_SKIPMISSING 和 XMLSRV_ADDMISSING 模式。在 XMLSRV_SKIPMISSING 模式下,只有那些已经存在于XML文件中的PLC符号的子元素被写入XML文件。在XMLSRV_ADDMISSING模式中,将缺失的子元素添加到XML文件中。
- pSymAddr:将XML文件中的数据写入的PLC变量的地址。
- cbSymSize:将XML文件中的数据写入的PLC变量的大小。
- sFilePath:要打开的文件的路径和文件名。该路径只能指向本地计算机的文件系统。
- sXPath:要从XML文档中写入数据的的标记地址。地址必须是有效的XPath指令。标记的名称不能与符号的名称相同。
- bExecute:激活功能块的标志位,true=激活。
- tTimeout:功能块执行的最大时间。
3、支持的数据类型
4、四个功能块使用demo
定义两个结构体:
TYPE ST_MYSTRUCT:
STRUCT
fReal : REAL;
bBool : ARRAY [0..2] OF BOOL;
stInner : ST_INNTERSTRUCT;
END_STRUCT
END_TYPE
TYPE ST_INNTERSTRUCT:
STRUCT
nInteger : INT;
sString : STRING;
END_STRUCT
END_TYPE
4.1 Sample 1 (FB_XmlSrvWrite)
PROGRAM MAIN
VAR
value1 : ST_MyStruct;
fbXmlSrvWrite : FB_XmlSrvWrite;
bExecute : BOOL;
sFilePath : T_MaxString := D:\demo\Test.xml'; (* CE: '\Hard Disk\Test.xml' *)
sXPath : T_MaxString := '/dataentry/MAIN.value1';
END_VAR
fbXmlSrvWrite(
nMode := XMLSRV_ADDMISSING,
pSymAddr := ADR(value1),
cbSymSize := SIZEOF(value1),
sFilePath := sFilePath,
sXPath := sXPath,
bExecute := bExecute
);
bExecute := TRUE;
4.2 Sample 2 (FB_XmlSrvWriteByName)
PROGRAM MAIN
VAR
value1 : ST_MyStruct;
fbXmlSrvWrite : FB_XmlSrvWriteByName;
bExecute : BOOL;
sSymName : T_MaxString := 'MAIN.value1';
sFilePath : T_MaxString := 'D:\demo\Test.xml'; (* CE: '\Hard Disk\Test.xml' *)
sXPath : T_MaxString := '/dataentry/MAIN.value1';
END_VAR
fbXmlSrvWrite(
nMode := XMLSRV_ADDMISSING,
sSymName := sSymName,
sFilePath := sFilePath,
sXPath := sXPath,
bExecute := bExecute
);
bExecute:= TRUE;
4.3 Sample 3 (FB_XmlSrvRead)
PROGRAM MAIN
VAR
value1 : ST_MyStruct;
fbXmlSrvRead : FB_XmlSrvRead;
bExecute : BOOL;
sFilePath : T_MaxString := 'D:\demo\Test.xml'; (* CE: '\Hard Disk\Test.xml' *)
sXPath : T_MaxString := '/dataentry/MAIN.value1';
END_VAR
fbXmlSrvRead(
pSymAddr := ADR(value1),
cbSymSize := SIZEOF(value1),
sFilePath := sFilePath,
sXPath := sXPath,
bExecute := bExecute
);
bExecute:= TRUE;
4.4 Sample 4 (FB_XmlSrvReadByName)
PROGRAM MAIN
VAR
value1 : ST_MyStruct;
fbXmlSrvRead : FB_XmlSrvReadByName;
bExecute : BOOL;
sSymName : T_MaxString := 'MAIN.value1';
sFilePath : T_MaxString := 'D:\demo\Test.xml'; (* CE: '\Hard Disk\Test.xml' *)
sXPath : T_MaxString := '/dataentry/MAIN.value1';
END_VAR
fbXmlSrvRead(
sSymName := sSymName,
sFilePath := sFilePath,
sXPath := sXPath,
bExecute := bExecute
);
bExecute:= TRUE;
5、 实际使用demo
5.1 程序启动时的初始化
PROGRAM MAIN
VAR
value1 : ST_MyStruct;
fbXmlSrvRead : FB_XmlSrvRead;
bExecute : BOOL;
sFilePath : T_MaxString := 'D:\demo\Test.xml'; (* CE: '\Hard Disk\Test.xml' *)
sXPath : T_MaxString := '/dataentry/MAIN.value1';
nState : INT := 0;
END_VAR
CASE nState OF
0: (* initialize *)
fbXmlSrvRead(
pSymAddr := ADR(value1),
cbSymSize := SIZEOF(value1),
sFilePath := sFilePath,
sXPath := sXPath,
bExecute := bExecute
);
fbXmlSrvRead(bExecute:= TRUE);
nState:= 1;
1: (* wait for read operation *)
fbXmlSrvRead(bExecute:= FALSE);
IF NOT fbXmlSrvRead.bBusy AND NOT fbXmlSrvRead.bError THEN
nState:= 2;
ELSIF fbXmlSrvRead.bError THEN
nState:= 100;
END_IF
2: (* operations *)
;
100:(* errorState *)
;
END_CASE
5.2 循环和事件驱动的打印过程
PROGRAM Sample6
VAR
value1 : ST_MyStruct;
fbXmlSrvWrite : FB_XmlSrvWrite;
sFileFolder : T_MaxString :='C:\'; (* CE: '\Hard Disk\' *)
sFileName : T_MaxString:= '_test.xml';
sFilePathWrite : T_MaxString
(*sFilePathWrite = sFileFolder + time + sFileName*)
sXPathWrite : T_MaxString :='/dataentry/MAIN.value1';
ntGetTime : NT_GetTime;
stMyTimestruct : TIMESTRUCT;
iState : INT := 1;
bTwentySec : BOOL:= FALSE;
bButton : BOOL:= FALSE;
bTwentySecOver : BOOL;
triggerWrite : R_TRIG;
triggerButton : R_TRIG;
END_VAR
triggerButton(CLK:= bButton);
CASE iState OF
0: (* idle state *)
;
1: (* initialize *)
fbXmlSrvWrite(
nMode:=XMLSRV_ADDMISSING,
pSymAddr:= ADR(value1),
cbSymSize:= SIZEOF(value1)
);
ntGetTime(START:= TRUE, TIMESTR=>stMyTimestruct); (* get Windows time *)
IF NOT ntGetTime.BUSY AND NOT ntGetTime.ERR THEN
iState:= 2;
ELSIF ntGetTime.ERR THEN
iState:= 100;
END_IF
2: (* working state *)
(* change some values - replace with production-process *)
value1.stInner.nInteger:= value1.stInner.nInteger + 1;
IF value1.stInner.nInteger = 32767 THEN
value1.stInner.nInteger:= 0;
END_IF(* get Windows time *)
ntGetTime(START:= FALSE);
IF NOT ntGetTime.BUSY AND NOT ntGetTime.ERR THEN
ntGetTime(START:= TRUE, TIMESTR=>stMyTimestruct);
ELSIF ntGetTime.ERR THEN
iState:= 100;
END_IF
(* check if 20s have passed*)
IF stMyTimestruct.wSecond = 0 OR stMyTimestruct.wSecond = 20 OR stMyTimeStruct.wSecond = 40 THEN
bTwentySecOver:= TRUE;
ELSE
bTwentySecOver:= FALSE;
END_IF
(* if 20s have passed => trigger writing-process *)
triggerWrite(CLK:=bTwentySecOver);
IF (triggerWrite.Q OR triggerButton.Q) AND NOT fbXmlSrvWrite.bBusy AND NOT fbXmlSrvWrite.bError THEN
(* create filename *)
sFilePathWrite:= CONCAT(sFileFolder, SYSTEMTIME_TO_STRING(stMyTimestruct)); (* set folder + ti
me *)
sFilePathWrite:= DELETE(STR:= sFilePathWrite, LEN:= 4 , POS:= LEN(STR:=sFilePathWrite)-3); (*
delete milliseconds *)
sFilePathWrite:= REPLACE(STR1:= sFilePathWrite , STR2:= '.' , L:= 1,
P:= LEN(STR:=sFilePathWrite)-2); (* replace colon with point *)
sFilePathWrite:= REPLACE(STR1:= sFilePathWrite , STR2:= '.' , L:= 1,
P:= LEN(STR:=sFilePathWrite)-5); (* replace colon with point *)
sFilePathWrite:= CONCAT(sFilePathWrite, sFileName); (* add filename (default: test) *)
(*change value 1*)
value1.stInner.sString := sFilePathWrite;
value1.stInner.nInteger := stMyTimestruct.wSecond;
(* write *)
fbXmlSrvWrite(sFilePath:=sFilePathWrite, sXPath:=sXPathWrite, bExecute:= TRUE);
ELSIF fbXmlSrvWrite.bError THEN
iState:= 100;
END_IF
(* reset fbXmlSrvWrite *)
IF fbXmlSrvWrite.bBusy AND NOT tGetTime.ERR THEN
fbXmlSrvWrite(bExecute:= FALSE);
ELSIF ntGetTime.ERR THEN
iState:= 100;
END_IF
100: (* error state*)
;
END_CASE