从零开始搭建4G DTU设备对应的云平台(一)

一、了解通信方式

搭建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端口\r\n");
            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();
                        }
                    }
                });

数据采集部分结束,后面写服务器端和前端。

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 要实现Java和4G DTU设备之间的通信,可以通过以下步骤: 1. 首先,确保Java程序中有相应的串口通信库,例如rxtx或JavaComm。这些库可以让Java程序通过串口与设备进行通信。 2. 确保4G DTU设备正确配置,配置包括设置串口参数(如波特率、数据位、校验位和停止位),并确保设备与计算机通过串口连接。 3. 在Java程序中,使用串口通信库打开与设备相连的串口,可以通过指定串口号或设备名称来打开串口。 4. 建立与设备的通信会话。可以通过向设备发送指令或数据来与设备进行通信,例如读取设备状态、发送控制命令等。 5. 在Java程序中,接收和解析来自设备的响应。可以通过读取串口缓冲区的数据来获取设备的响应,然后解析响应以获取所需的信息。 6. 根据需要,可以在Java程序中实现对设备进行控制的功能。这可以通过向设备发送控制命令或数据来实现。 7. 最后,在与设备通信完成后,记得关闭串口并释放相应的资源,以确保程序的正确执行。 综上所述,通过使用相应的串口通信库,可以在Java程序中实现与4G DTU设备的通信。通过打开串口,发送和接收数据,解析响应等步骤,可以实现与设备的通信,并根据需要进行相应的控制操作。 ### 回答2: 要实现 Java 和 4G DTU 设备之间的通信,我们可以采用以下步骤: 1. 确定设备接口:首先,我们需要了解 4G DTU 设备的通信接口类型,例如串口、以太网等。根据接口类型,我们选择适当的 Java 库和插件来实现通信。 2. 连接设备:使用 Java 提供的串口通信库或者网络通信库,我们可以建立 Java 程序与 4G DTU 设备之间的连接。如果是串口通信,我们需要指定正确的端口和波特率;如果是网络通信,我们需要指定正确的 IP 地址和端口号。 3. 通信协议:在建立连接后,我们需要确定设备所使用的通信协议。4G DTU 设备通常使用 Modbus、MQTT 或者自定义的协议进行通信。根据设备的协议,我们可以使用相应的 Java 库来解析和封装数据。 4. 数据读取和写入:通过 Java 程序与 4G DTU 设备之间的连接,我们可以实现对设备的数据读取和写入。根据设备的协议,我们可以发送合适的指令或者请求来获取设备的数据,同时也可以发送指令来向设备写入数据。 5. 异常处理:在通信过程中,可能会出现各种异常情况,例如通信中断、超时等。我们需要使用异常处理机制来捕获和处理这些异常,以保证程序的稳定运行。 6. 数据解析和处理:获取到设备的数据后,我们可以利用 Java 提供的数据处理和计算功能对数据进行解析和处理。根据具体的应用场景,我们可以把数据存储到数据库、进行数据分析或者其他操作。 需要注意的是,以上步骤仅提供了一个基本的通信框架,具体实现还需要根据具体的设备和应用场景进行调整和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值