引子

通信方式有很多种,常见的网络通信,如TCP/UDP、串口通信和IO通信;如电脑跟PLC之间常见的是网络通信,TCP/UDP是基础架构,各个厂商在此基础之上定义了各种自家的通信协议;作为最基础的,我们在gclx框架下实现了TCP/UDP的数据通信,主要包含以下几个方面:1、通信设备管理&通信协议编写;2、结点全局数据暴漏;3、数据接收端解析(主要在拍照模块解析:如条形码、触发等);4、发送结点数据绑定。

通信设备&协议管理

通信设备和协议作为一个单独的模块进行管理,主要包含链接参数设置、通信协议管理等,如下图所示,以下以UDP为例说明;

UDP通信不同于TCP,不需要Connect即可通信,是一种只管发不管结果的通信方式,主要用于流媒体对稳定性要求不是很高的场景,在初始化时需要本地需要绑定到一个端口上,发送数据时,根据服务端的ip及port号进行发送;
数据格式:支持固定长度及分隔符模式,固定长度每次发送接收数据长度一致,每个数据位置及长度需要配置,分隔符模式(如按照','进行分割),只需要位置信息,长度不需要,分隔符模式下目前仅支持纯字符串通信;
场景:暂时没用,新的模式已经兼容,后续删除
全局变量:暂时没用,后续删除;

步骤1:添加设备

设置设备名称、通信方式、ip地址端口、数据格式等,点击添加完成添加,此时在协议列表中显示刚刚添加的通信设备;

步骤2:配置协议

数据

名称:支持的关键词字段,后续可扩展,没有的字段不选;
数据类型:字符串、uchar8(固定长度为1),uint32(固定长度为4);float类型数据需要乘以固定的倍率取整进行发送
索引:当前数据在整个字段中的位置;
长度:字符串时定义字符串长度,整形时固定为1/4
值:主要用于发送端,如果需要发送固定的值,在此处配置,如果需要绑定结点输出值,在发送页面单独配置;
放大倍率:如果要发送和接收float类型数据,需要放大之后取整再发送;

发送

名称:当前协议名称,发送端会已【协议名称-名称】进行设置,响应那一条指令
关键词:接收的字段关键词跟此处设置一致,
设置数据端配置,点击添加,即可添加一条协议;

接收

名称:同发送
关键词:收到的指令关键词字段必须跟此设置一致,才会响应;
添加方式:同发送;

步骤3:结点暴漏全局变量

在发送模块需要绑定数据到协议中,此时只需要将可用的信息暴漏到全局Buffer中即可;
1
2
3
4
5
6
7
8
9
// 有全局变量的,需要在此处添加
gtf_buffer->SetGlobal(QString::fromWCharArray(L"%1%2:Station").arg(name()).arg(id),
Data_GlobalInMem(Enum_RundataType::RT_Base_Int, QVariant::fromValue(0)));
gtf_buffer->SetGlobal(QString::fromWCharArray(L"%1%2:Barcode").arg(name()).arg(id),
Data_GlobalInMem(Enum_RundataType::RT_Base_String, QVariant::fromValue(QString(""))));

// 测试
gtf_buffer->SetGlobal(QString::fromWCharArray(L"%1%2:Position").arg(name()).arg(id),
Data_GlobalInMem(Enum_RundataType::RT_Point, QVariant::fromValue(gclPoint(1,2,1))));

步骤4: 接收端解析

接收端接收到来至服务器的数据,按照通信协议配置页面的索引及长度对指令进行解析转译成结构体数据;发送给响应的模块,如拍照模块,拍照模块就会更新当前的二维码、工站或开始触发拍照等操作;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    // 固定长度:可以包含字符串及数字;
for (int i = 0; i < Enum_Communication_KeyWorld::enum_num; i++)
{
if (!receive.dataStr[i].enable)
{
cmdParts << "";
continue;
}
int index = receive.dataStr[i].index;
int len = receive.dataStr[i].len;
if (index < 0 || index >= cmd.length() || index + len > cmd.length())
{
onaction_logmsg(QString::fromWCharArray(
L"通信设备[%1]:接收到指令[%2],长度=%3,协议指令[%4]数据段[%5][索引%6,长度%7]不合法").
arg(device).arg(cmd).arg(cmd.length()).arg(cmdName).arg(i + 1).arg(index).arg(len), 1);
continue;
}
QString tmp = cmd.mid(receive.dataStr[i].index, receive.dataStr[i].len);
cmdParts << tmp;
//arrHex.append(tmp.toUtf8());
auto dt = receive.dataStr[i].type;

// int 固定长度4,分地位在前和高位在前;u8固定长度为1
if (i == Enum_Communication_KeyWorld::enum_station)
{
result.station = dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8());
}
else if (i == Enum_Communication_KeyWorld::enum_barcode)
{
result.barcode = tmp;
}
else if (i == Enum_Communication_KeyWorld::enum_positionX)
{
result.position[0] = (dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8())) / 1000.0f;
}
else if (i == Enum_Communication_KeyWorld::enum_positionY)
{
result.position[1] = (dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8())) / 1000.0f;
}
else if (i == Enum_Communication_KeyWorld::enum_positionZ)
{
result.position[2] = (dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8())) / 1000.0f;
}
else if (i == Enum_Communication_KeyWorld::enum_positionA)
{
result.position[3] = (dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8())) / 1000.0f;
}
else if (i == Enum_Communication_KeyWorld::enum_trigger)
{
result.isTrigger = (dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8())) > 0;
}
else if (i == Enum_Communication_KeyWorld::enum_triggerComplete)
{
// 不会进入:发送端才会有此字段
}
else if (i >= Enum_Communication_KeyWorld::enum_data0 &&
i < Enum_Communication_KeyWorld::enum_num)
{
// 数据字段
result.data[i - Enum_Communication_KeyWorld::enum_data0] =
dt == Enum_Commnication_DataType::enum_string ? tmp.toInt() :
dt == Enum_Commnication_DataType::enum_u8 ? QByte2Uchar(tmp.toUtf8()) :
QByte2Int(tmp.toUtf8());
}
}

步骤5:发送数据给服务器

发送模块暂时有两种方式,一是可绑定到各个模块中进行响应(主要针对拍照模块,如触发完成回执等),其他数据通过独立的发送模块进行发送

设置协议:选择要响应的通信;
长度:不用管,由通信模块管理
固定值:如果发送的是固定值,需要设置,如果要保定全局变量,不设置
放大倍率:数值数据放大倍率
绑定参数:结点开放出来的全局变量都会显示出来;
绑定-key:绑定的参数可能不是简单的数据类型,是结构体,此时会自定显示内部数据类型,如绑定的数据是点gclpoint,key会显示[x,y,z]等,选择要绑定的数据即可,并保存;

测试

发送:54 54 54 54 54 53 4E 30 30 31 31;包含关键字:‘TTTTT’,条码:‘SN001’,触发:‘1’
接收:54 54 54 54 54 53 4E 30 30 31 64 00 00 00 00 C8 00 00 00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
关键字:TTTTT,条码:SN001,数据0:100,数据1:200,数据2:100