TEP是:TCP Extend Plugin ,中译:TCP 扩展 插件,用于对接非标或第三方的数据采集软件
底层采用SocketTCP来进行开发的,数据交互都是Byte[],所以说任何语言都可以对接并采集上传到(主端)
Master:主端,SocketTCP服务端,也就是接收数据,与DAQ函数一致,支持多个 从端 数据上传
Slave:从端,SocketTCP客户端,把非标数据通过网络传输到 主端
解释一下为什么这个设计
缺点简单介绍一下
[ Snet.TEP ] 接入规范 ( SocketTcp )
主端简称:S - 0x43
从端简称:C - 0x53
包体
名称 | 固定字节 | 长度 | 描述 |
---|---|---|---|
针头 | 0x7b 0x5b 0x28 0x3c | 4个字节 | 用于解包封包使用 |
命令 | 1个字节 | 请看下方接口描述 | |
方向 | C > S:0x43 0x53S > C:0x53 0x43 | 2个字节 | 客户端 往 服务端 发送数据服务端 往 客户端 发送数据 |
数据包长度 | 4个字节 | 也就是如下数据包的字节长度 | |
数据包 | N | 数据包为JSON数据,把JSON数据通过UTF8转换成字节 | |
CRC | 2个字节 | 用于判断如上数据包是否正常,最下面为CRC C#验证代码 | |
针尾 | 0x3e 0x29 0x5d 0x7d | 4个字节 | 用于解包封包使用 |
注意:收到数据进行针头、命令、方向、针尾判断与CRC校验+数据长度判断,来确保数据的完整性与正确性
命令
命令 | 名称 | 模式 | 描述 |
---|---|---|---|
0x30 | 握手 | 被动 | 发送到客户端的命令,表示客户端可以发数据给服务端了 |
0x31 | 身份验证 | 主动 | 握手后5秒内必须发送身份认证,不然会被服务端强制踢出 |
0x32 | 会话心跳包 | 主动 | 认证后5秒内必须主动请求一次,不然会被服务端强制踢出 |
0x33 | 获取客户端状态 | 被动 | 服务端通过设备名设备ID获取设备状态 |
0x34 | 数据上传 | 主动 | 客户端主动上传设备采集的数据 |
0x35 | 数据写入 | 被动 | 服务端下发至客户端写入数据,无法写入直接在Message中表现 |
数据包字段
名称 | 描述 |
---|---|
DevName | 设备名称 |
DevID | 设备ID,此字段属于标识符,必须唯一 |
Message | 消息 |
State | 状态;-1 身份尚未认证;0 未响应;1 成功/正常;2 异常; |
AddressValue | 地址键值集合,注意 Key 不要存在[ . ] 点,用别的符号代替 |
UserName | 用户名 |
Password | 密码 |
握手【被动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求:S>C | 0x30 | 0x53 0x43 | 握手 |
身份验证【主动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求:C>S | 0x31 | 0x43 0x53 | { "UserName": "test", "Password": "test", "DevName": "F100透气度仪", "DevID": "0001" } |
响应:S>C | 0x31 | 0x53 0x43 | 成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message ": "身份验证成功 ", "State ": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "身份验证失败,****", "State": 2 } |
心跳包 间隔 5秒一次【主动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求C>S | 0x32 | 0x43 0x53 | { "DevName": "F100透气度仪", "DevID": "0001" } |
发送S>C | 0x32 | 0x53 0x43 | 成功 { "DevName": "F100透气度仪", "DevID": "0001", "Message": "通信正常", "State": 1 }失败 { "DevName": "F100透气度仪", "DevID": "0001", "Message": "通信异常,******", "State": 2 }无响应:C 没有收到 S 的响应,S问题 ,C直接日志输出无响应:S 5秒内没有收到 C 的请求,C问题,强制关闭C的连接 |
获取客户端状态【被动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求:S>C | 0x33 | 0x53 0x43 | { "DevName": "F100透气度仪", "DevID": "0001" } |
响应:C>S | 0x33 | 0x43 0x53 | 正常 { "DevName": "F100透气度仪", "DevID": "0001", "State": 1, "Message": "状态正常", }异常 { "DevName": "F100透气度仪", "DevID": "0001", "State": 2, "Message": "状态异常,****", } |
数据上传【主动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求:C>S | 0x34 | 0x43 0x53 | { "DevName": "F100透气度仪", "DevID": "0001", " AddressValue": [{ "Key": "a", "Value": 6.6 }, { "Key": "b", "Value": "hello" } ] } |
响应:S>C | 0x34 | 0x53 0x43 | 成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据上传成功", "State": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据上传失败,****", "State": 2 } |
数据写入【被动】
类型 | 命令 | 方向 | 数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可 |
---|---|---|---|
请求:S>C | 0x35 | 0x53 0x43 | { "DevName": "F100透气度仪", "DevID": "0001", "AddressValue": [{ "Key": "a", "Value": 6.6 }, { "Key": "b", "Value": "hello" }] } |
响应:C>S | 0x35 | 0x43 0x53 | 成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据写入成功", "State": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001" "Message": "数据写入失败,****", "State": 2 } 注意:如果被服务端关闭,说明你返回的数据存在问题 |
[ Snet.TEP ] 接入规范 ( C#动态库 )
//先虚拟出 设备名称与ID
string DevName = "测试设备";
string DevID = new Random().Next(10000, 999999).ToString();
//第一步,先实例化对象,用户账号密码就是服务端内置的账号密码,做身份认证使用,其余参数为默认值即可
TepSlaveData.Basics basics = new TepSlaveData.Basics()
{
IpAddress = "127.0.0.1",
DevName = DevName,
DevID = DevID,
HalfTime = 1,
UserName = "samples",
Password = "samples"
ViolenceUpload = false
};
//第二步,设置方法
bool 服务端获取状态会进入到这里面()
{
return true;
}
OperateResult 服务端往客户端写入点位数据会进入到这里面(List<CoreBasicsData.KeyValue> keyValues)
{
//可以写入的返回
return new OperateResult(true, "写入成功", 1);
//不可以写入的返回
return new OperateResult(true, "写入失败,不支持写入功能", 1);
}
clientOperate.SetBaseStateFunc(服务端获取状态会进入到这里面);
clientOperate.SetBaseWriteFunc(服务端往客户端写入点位数据会进入到这里面);
//第三步,打开
OperateResult result = clientOperate.On();
//输出结果
Console.WriteLine(result.ToJson().JsonFormatting());
//状态判断
if (result.State)
{
//第四步,事件注册
clientOperate.OnEvent += ClientOperate_OnEvent;
}
//当你要上传数据时就执行数据上传方法,注意 Key 不要存在[ . ] 点,用别的符号代替
result = clientOperate.DataUpload(new List<KeyValue>() { new KeyValue { Key = "键", Value = "值" } });
//输出结果
Console.WriteLine(result.ToJson().JsonFormatting());
//事件接收
private static void ClientOperate_OnEvent(object? sender, Model.data.EventResult e)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(e.ToJson());
}
实例参数
/// <summary>
/// tep 从端数据
/// </summary>
public class TepSlaveData
{
/// <summary>
/// 基础数据
/// </summary>
public class Basics : TcpClientData.Basics
{
/// <summary>
/// 设备名称
/// </summary>
[Description("设备名称")]
public string? DevName { get; set; } = "samples";
/// <summary>
/// 设备ID
/// </summary>
[Description("设备ID")]
public string? DevID { get; set; } = "10001";
/// <summary>
/// 用户名
/// </summary>
[Description("用户名")]
public string? UserName { get; set; } = "samples";
/// <summary>
/// 密码
/// </summary>
[Description("密码")]
public string? Password { get; set; } = "samples";
/// <summary>
/// 休息时间;<br/>
/// 越小CPU占用越高,速度越快
/// </summary>
[Description("休息时间")]
public int HalfTime { get; set; } = 5;
/// <summary>
/// 暴力上传<br/>
/// 只管上传,不处理服务端的返回的结果
/// </summary>
[Description("暴力上传")]
public bool ViolenceUpload { get; set; } = false;
}
}
CRC校验
#region CRC16查表法
#region 高位表
/// <summary>
/// CRC高位校验码表
/// </summary>
readonly static byte[] cRCHighArray =
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
#endregion 高位表
#region 低位表
/// <summary>
/// CRC低位校验码表
/// </summary>
readonly static byte[] cRCLowArray =
{
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
#endregion 低位表
/// <summary>
/// 计算CRC16循环校验码
/// ① 支持按位异或校验(XOR)
/// ② 支持CRC16查表法校验
/// ③ 支持CRC16带多项式计算法校验
/// </summary>
/// <param name="cmd">字节数据</param>
/// <param name="isHighBefore">是否高位在前,默认false</param>
/// <returns></returns>
public static byte[] GetCRC16(byte[] cmd, bool isHighBefore = false)
{
int index;
int crc_Low = 0xFF;
int crc_High = 0xFF;
for (int i = 0; i < cmd.Length; i++)
{
index = crc_High ^ (char)cmd[i];
crc_High = crc_Low ^ cRCHighArray[index];
crc_Low = (byte)cRCLowArray[index];
}
if (isHighBefore == true)
{
return new byte[2] { (byte)crc_High, (byte)crc_Low };
}
else
{
return new byte[2] { (byte)crc_Low, (byte)crc_High };
}
}
#endregion CRC16查表法
关闭暴力上传的效率(单机测试 1s/150+)
开启暴力上传的效率(单机测试 1s/1500+)
TEP的主端从端示例代码已打包
https://shunnet.lanzouv.com/b0mbde3ih 密码:9air
感谢观看