概述
一、了解通信方式
搭建4G DTU设备对应的云平台过程中第一个问题就是,如何在自己的服务器上面与设备进行通信。
刚开始的时候,我看到说明书上写着TCP协议,还以为是用HTTP协议,用RequestHeaders和Request Params访问设备,然后会返回想要的数据,但是多方求证无果,最后发现仅仅是TCP协议,也就是单纯的Socket通信。
第二个问题是端口的问题。
以前公司里用过一次以太网转串口的服务器,在哪台设备上,特定的串口都是绑定到特定的端口的,比如说:
刚开始我也以为这个DTU设备的串口透传是要通过特定的端口来访问的,但是问了厂家的技术人员,但是对方好像不是很了解,问了两天结果问了个寂寞????(不知道是我的表达问题还是啥)。
然后自己写了个控制台程序,把端口都遍历了一遍:
但是全被拒绝了。
分析问题,应该是因为设备上设置了服务器的端口,所以这个设备只会跟服务器上面的特定端口进行通信,其他的端口即使尝试建立连接也会被拒绝。而我遍历设备端口的时候正是实时生成随机端口来访问设备的。
后来尝试在TCP连接(三次握手)建立之后,在建立连接的套接字上面向设备不停的发送数据,这一次成功了,从而知道,透传的端口不是特定的,而是在建立TCP连接的端口就可以透传数据。
二、对云平台数据采集部分进行编码
同样,采集数据还是直接用控制台程序,因为没有什么参数好设置的。
这一部分,又可以拆分出来三个小部分:数据通信,数据校验,数据存储。
1、数据校验
这一部分主要是针对CRC16 modbus校验来编写的,首先根据CRC16的定义得到以下的校验算法(这个算法是网上找的,出处忘了):
public static byte[] CRC16(byte[] bytes)
{
//计算并填写CRC校验码
int crc = 0xffff;
int len = bytes.Length;
for (int n = 0; n < len; n++)
{
byte i;
crc = crc ^ bytes[n];
for (i = 0; i < 8; i++)
{
int TT;
TT = crc & 1;
crc = crc >> 1;
crc = crc & 0x7fff;
if (TT == 1)
{
crc = crc ^ 0xa001;
}
crc = crc & 0xffff;
}
}
var nl = bytes.Length + 2;
//生成的两位校验码
byte[] redata = new byte[2];
redata[0] = (byte)((crc & 0xff));
redata[1] = (byte)((crc >> 8) & 0xff);
//重新组织字节数组
var newByte = new byte[nl];
for (int i = 0; i < bytes.Length; i++)
{
newByte[i] = bytes[i];
}
newByte[nl - 2] = (byte)redata[0];
newByte[nl - 1] = redata[1];
return newByte;
}
因为不只是发送的指令需要校验,从设备返回的数据同样是需要校验的,所以需要一个函数来对返回数据进行校验其正确性:
public static bool isDataCorrect(byte[] data)
{
var crcData = CRC16(data.Take(data.Length - 2).ToArray());
return crcData[crcData.Length - 1] == data[data.Length - 1] && crcData[crcData.Length - 2] == data[data.Length - 2];
}
另外一个跟数据处理相关的地方就是把byte转换成十六位无符号整数、十六位有符号整数、三十二位无符号整数、三十二位有符号整数等,一开始我是自己写的:
public static int getCombinedInt(List<byte> list, int startIndex)
{
return (short)((list[startIndex] << 8) + list[startIndex + 1]);
}
public static long getCombinedLong(List<byte> list, int startIndex)
{
return ((int)(list[startIndex] << 24)) | ((int)list[startIndex + 1] << 16) | (list[startIndex + 2] << 8) | list[startIndex + 3];
}
挺抽象的,也挺麻烦的,后来才知道.net 平台已经提供了此类数据转换的封装,直接调用就行:
public static int getSignedInt(byte[] data, int startIndex)
{
return BitConverter.ToInt32(data, startIndex);
}
public static uint getInt(byte[] data, int startIndex)
{
return BitConverter.ToUInt32(data, startIndex);
}
public static short getSignedShort(byte[] data,int startIndex)
{
return BitConverter.ToInt16(data, startIndex);
}
public static ushort getShort(byte[] data,int startIndex)
{
return BitConverter.ToUInt16(data, startIndex);
}
2、数据存储
存储数据使用的是MySQL,首先要在nuget里面安装MySQL的扩展包。
MySQL使用方式和SQL server差不多,都要有一个ConnetionString,所以需要有这样一个数据库连接字符串,然后提供打开连接和关闭连接:
private readonly MySqlConnection connection = new MySqlConnection();
private MySqlCommand command;
public DataBaseUtils(string connectionString,string remoteAddress)
{
connection.ConnectionString = connectionString;
}
public bool openConnection()
{
try
{
connection.Open();
return true;
}
catch (Exception e)
{
Console.WriteLine(connection.ConnectionString);
Console.WriteLine(e.StackTrace);
Console.WriteLine(e.Message);
return false;
}
}
public bool closeConnection()
{
try
{
connection.Close();
return true;
}
catch (Exception e)
{
Console.WriteLine(connection.ConnectionString);
Console.WriteLine(e.StackTrace);
Console.WriteLine(e.Message);
return false;
}
}
然后用MySqlCommand,MySqlConnection向数据库插入数据:
public bool insertMysql(MysqlTables table, string createTime, List<long> data)
{
try
{
var tableName = getTableName(table);
var tableData = getDataString(data);
var insertSql = $"insert into {tableName} values(null,'{createTime}',{tableData})";
command = new MySqlCommand(insertSql, connection);
var result = command.ExecuteNonQuery();
if (result == 0)
{
return false;
}
return true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
return false;
}
}
3、数据通信
这一部分是直接写在Main函数里面的,因为代码比较少,就不再抽象成其他的库来二次调用了。
思路是:在主线程中不断地监听端口,每当有一个TCP连接建立,就新创建一个新的线程,在新的线程里面用套接字进行通信。
首先实例化一个TcpListener,在循环中不断尝试接受TCP连接请求:
bool listening = true;
var listener = new TcpListener(IPAddress.Parse("0.0.0.0"), 8081);
listener.Start();
Console.WriteLine("正在监听8081端口rn");
while (listening)
{
var socket = listener.AcceptSocket();
}
刚开始我把IPAddress.Parse的参数写成了127.0.0.1,但是怎么都收不到外网传进来的TCP连接,只能在内网自己连着玩????。经过开发者群大佬的指点,得知这个地方要写0.0.0.0:
TCP连接之后,创建子线程,进行指令的发送和数据的采集和存储:
Task.Run(() =>
{
try
{
string remoteSocket = socket.RemoteEndPoint.ToString();
var buffer = new byte[128];
int i = socket.Receive(buffer);//接受连接请求的字节流
string msg = "<" + remoteSocket + ">" + System.Text.Encoding.UTF8.GetString(buffer);
Console.WriteLine(DateTime.Now);
Console.WriteLine(msg);//在控制台显示字符串
while (socket.Connected)
{
socket.Send(CRC16Utils.CRC16(Commands.DTU_COMMAND));
socket.Receive(buffer);
if (buffer[0] == 1 && buffer[1] == 3)
{
if (CRC16Utils.isDataCorrect(buffer.Take(67).ToArray()))
{
var model = DataDeSeirializeUtils.deserializeDTUAmmeterData(buffer);
model.time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
dataBase.insertData(model, ProjectCommonLibs.enums.MysqlTables.DTU_AMMETER);
}
}
var listHex = (from item in buffer select String.Format("{0:X2}", item)).ToArray();
Console.WriteLine(String.Join(" ", listHex));
Thread.Sleep(10 * 1000);
}
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
if (socket.Connected)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
});
数据采集部分结束,后面写服务器端和前端。
最后
以上就是友好香水为你收集整理的从零开始搭建4G DTU设备对应的云平台(一)一、了解通信方式二、对云平台数据采集部分进行编码的全部内容,希望文章能够帮你解决从零开始搭建4G DTU设备对应的云平台(一)一、了解通信方式二、对云平台数据采集部分进行编码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复