您好!欢迎来到默认站点! 请登录 注册有礼
首页 新闻资讯 > 研发日志 > 物联网

物联网IO模块开发二:设备搜索

2020-4-29 19:09:00 人评论

康耐德IO模块设备的SDK搜索设备效率很低,要30秒左右,相比网源RFID设备的瞬间秒完成显得异常笨重,难道是逐个IP地址轮训?与康耐德研发人员沟通得知,还真不是轮训,与网源一样也是通过UDP向指定端口(21678)发送数据来检索在线设备,SDK包是调用win32的dll类库,不想关心内…

康耐德IO模块设备的SDK搜索设备效率很低,要30秒左右,相比网源RFID设备的瞬间秒完成显得异常笨重,难道是逐个IP地址轮训?与康耐德研发人员沟通得知,还真不是轮训,与网源一样也是通过UDP向指定端口(21678)发送数据来检索在线设备,SDK包是调用win32的dll类库,不想关心内部代码如果实现,经过几次协商,通过与康耐德签署保密协议,终于获取到了UDP搜索协议规则,居然要用到CRC字典表,幸好自己没有去深入研究,不然只能是浪费时间,CRC算法还真复杂。

先建立通用型的UDP搜索功能,也就是可以索搜任意网络主机,可以归入Mvcms的基础应用功能模块中。

SocketReceiveData类,用来保存接收数据和远程主机IP等

using System;
using System.Net;
using Newtonsoft.Json;

namespace Mvcms.Common.Net {
    /// <summary>
    /// 接收客户端基础信息类
    /// </summary>
    public class BaseSocketReceive {
        /// <summary>
        /// 构造函数
        /// </summary>
        public BaseSocketReceive() {
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="ipaddress">设备IP地址</param>
        /// <param name="receiveBytes">接收到的设备发送的数据</param>
        public BaseSocketReceive(string ipaddress, byte[] receiveBytes) {

        }

        /// <summary>
        /// 设备IP地址
        /// </summary>
        public string IPAddress { get; set; }

        /// <summary>
        /// 设备发送数据的端口号
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// MAC地址
        /// </summary>
        public string MAC { get; set; }

        /// <summary>
        /// 设备名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 设备标题
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 上报数据IP地址
        /// </summary>
        public string DestinationIP { get; set; }

        /// <summary>
        /// 上报数据数据的端口号
        /// </summary>
        public int DestinationPort { get; set; }

        /// <summary>
        /// 接收数据时间
        /// </summary>
        public DateTime ReceiveTime { get; set; }

        /// <summary>
        /// 完成接收数据所耗时间(毫秒)
        /// </summary>
        /// <returns></returns>
        public int ElapsedTime() {
            TimeSpan ts = DateTime.Now - ReceiveTime;
            decimal span = Math.Round((decimal)ts.TotalMilliseconds, MidpointRounding.AwayFromZero);
            return (int)span;
        }

        /// <summary>
        /// 接收到的字符串数据
        /// </summary>
        public string ReceiveText { get; set; }

        /// <summary>
        /// 接收到的字节数据
        /// </summary>
        [JsonIgnore]
        public byte[] ReceiveBytes { get; set; }

        /// <summary>
        /// 是否为有效数据,此状态值可在OnReceiveDataEvent事件中修改,如果设置为false,将抛弃此接收数据
        /// </summary>
        public bool IsValid { get; set; }

        /// <summary>
        /// 是否已经完成了设备注册(完成数据库信息建立)
        /// </summary>
        public bool IsRegister { get; set; }

        /// <summary>
        /// 自定义文本(一般用于保存卡号)
        /// </summary>
        public string[] CustomLineText { get; set; }

        /// <summary>
        /// 自定义字节数据(一般用于保存状态参数值)
        /// </summary>
        public byte[] CustomBytes { get; set; }

        /// <summary>
        /// 保存任意对象
        /// </summary>
        [JsonIgnore]
        public object Tag { get; set; }
    }
}

Mvcms.Common.Net.SocketHelper类的SendData方法代码


//广播端口和数据在SocketHelper构造函数中设置
BroadcastPort = 21678;
BroadcastContent = Utils.HexStringToByte("FA013433215623A57B29C55D3C3212FE01FF000000000000000000003A4B");

/// <summary>
/// 以UDP协议向指定端口广播数据,并获取回复信息
/// </summary>
/// <param name="port">广播的目标端口</param>
/// <param name="byteData">字节数据</param>
/// <returns></returns>
public static List<BaseSocketReceive> SendData(int port, byte[] byteData) {
    List<BaseSocketReceive> result = new List<BaseSocketReceive>();
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) {//初始化一个Socket实例,采用UDP传输
        IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Broadcast, port);//初始化一个发送广播和指定端口的网络端口实例
        EndPoint endPoint = (EndPoint)ipEndPoint;
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);//设置该Socket实例的发送形式
        socket.SendTo(byteData, ipEndPoint);
        if (WebSocketHelper.IsListen) {//向WebSocket客户端发送消息
            WebSocketHelper.SendJson(4, "UDP Send Broadcast to Port:"+ port + " in " + DateTime.Now.ToShortTimeString(), Common.Utils.ByteToHexString(byteData, " "));
        }
        try {
            for (int i = 0; i < 255; i++) {
                for (int j = 0; j < 3; i++) {
                    byte[] buffer = new byte[1024];
                    socket.ReceiveTimeout = 10;
                    int receiveLength = socket.ReceiveFrom(buffer, ref endPoint);
                    if (receiveLength > 0) {
                        BaseSocketReceive dev = new BaseSocketReceive();
                        string[] cs = endPoint.ToString().Split(':');
                        dev.IPAddress = cs[0];
                        dev.Port = int.Parse(cs[1]);
                        string[] ls = socket.LocalEndPoint.ToString().Split(':');
                        dev.DestinationIP = ls[0];
                        dev.DestinationPort = int.Parse(ls[1]);
                        dev.ReceiveBytes = new byte[receiveLength];
                        Array.Copy(buffer, dev.ReceiveBytes, receiveLength);
                        dev.ReceiveText = Utils.ByteToText(dev.ReceiveBytes);
                        result.Add(dev);
                    }
                    Thread.Sleep(10);
                }
            }
        }
        catch (SocketException e) {
            if (e.ErrorCode != 10060) {//忽略超时异常
                message = e.Message;
            }
        }
        socket.Shutdown(SocketShutdown.Both);
        socket.Close();
    }
    return result;
}

Mvcms.Common.Net.SocketServer.cs搜索设备相关代码

#region 搜索设备(均在派生类中复写实现搜索不同种类设备)
/// <summary>
/// UDP协议搜索设备广播端口
/// </summary>
public int BroadcastPort{ get; set; }

/// <summary>
/// UDP协议搜索设备广播报文
/// </summary>
public byte[] BroadcastContent { get; set; }

/// <summary>
/// 搜索在线设备
/// </summary>
/// <returns></returns>
public virtual List<BaseSocketReceive> SearchDevices() {
    List<BaseSocketReceive> result = new List<BaseSocketReceive>();
    List<BaseSocketReceive> list = SocketHelper.SendData(BroadcastPort, BroadcastContent);
    foreach (BaseSocketReceive item in list) {
        if (!result.Exists(p => p.IPAddress == item.IPAddress)) {//去除重复数据
            SetDeviceInfo(item);//设置设备信息(派生类中实现)
            result.Add(item);
        }
    }
    return result;
}

public virtual BaseSocketReceive SearchDevice(string ipaddress) {
    BaseSocketReceive result = SocketHelper.SendData(ipaddress, BroadcastPort, ProtocolType.Udp, BroadcastContent);
    SetDeviceInfo(result);
    return result;
}

/// <summary>
/// (派生类中重写)解析回复内容,设置设备信息
/// </summary>
/// <param name="socketReceive"></param>
public virtual void SetDeviceInfo(BaseSocketReceive socketReceive){
}
#endregion

Mvcms.Common.Net.SwitchDeviceServer.cs搜索设备相关代码

public override void SetDeviceInfo(BaseSocketReceive socketReceive) {
    byte[] bytes = socketReceive.ReceiveBytes;
    //回复数据示例:SDD-4040-BB3                                  |                                   内容                                      |
    //FA013433215623A57B29C55D3C3212FE 02FF0000 0009F6113BB0 2400 0009F6113BB0 0A640A19 0614 1403 0002 C4CFC3C5C8EBBFDA000000000000000000000000 EF22
    //      包头16字节(固定内容)     |   命令  |   MAC地址  |长度|   MAC地址  |  IP地址 |编号| 主V| 从V |    设备名称                            |CRC                  
    int index = 28;//有效数据索引位置
    //MAC地址
    List<string> ss = new List<string>();
    for (int i = index + 0; i < index + 6; i++) {
        ss.Add(BitConverter.ToString(bytes, i, 1));
    }
    socketReceive.MAC = string.Join(":", ss);
    ss.Clear();
    //设备型号
    index += 10;
    byte[] num = new byte[2];
    Array.Copy(bytes, index, num, 0, 2);
    short deviceNumber = BitConverter.ToInt16(bytes, index);

    //设备名称
    index += 6;
    int endindex = bytes.Length;
    for(int i= index ;i<bytes.Length; i++){
        if(bytes[i] == 0){
            endindex = i;
            break;
        }
    }
    byte[] content = new byte[endindex - index];
    Array.Copy(bytes, index, content, 0, content.Length);
    socketReceive.Title = Encoding.Default.GetString(content);
    Entities.IOT.device_type entity_dev_type = new BLL.IOT.device_type().GetFirstEntity("custom_data='" + deviceNumber.ToString() + "'");
    if (entity_dev_type != null) {
        socketReceive.Name = entity_dev_type.name;
        socketReceive.Tag = entity_dev_type;
    } else {
        socketReceive.Name = "[设备编号:"+ deviceNumber.ToString() +"]" + socketReceive.Title;
    }
}

控制器代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Text;
using Mvcms.Common.Net;
using Mvcms.Common;

namespace Mvcms.Web.Controllers.admin.IOT {
    public class device_searchController : UI.Controllers.BaseManageController {
        string type_name = string.Empty;
        List<BaseSocketReceive> list;

        public ActionResult Index() {
            IsPlugin = true;
            type_name = request.QueryString["type_name"].TryString("Mvcms.Common.Net.ReaderDeviceServer");
            Type type = TypeHelper.GetType(type_name);
            SocketServer socketServer = (SocketServer)Activator.CreateInstance(type);
            list = socketServer.SearchDevices();
            ViewBag.type_name = type_name;
            return View(GetViewName("/iot/DeviceSearch.cshtml"), list);
        }
    }
}

视图页面代码

@model List<Mvcms.Common.Net.BaseSocketReceive>
@using Mvcms.Common;
@{
    Layout = "~/Areas/admin/Views/Shared/_LayoutList.cshtml";
    ViewBag.Title = "设备搜索";
    Mvcms.Entities.sysconfig sysconfig = ViewData["sysConfig"] as Mvcms.Entities.sysconfig;
    Dictionary<string, string> type_dict = Mvcms.Common.TypeHelper.GetDerivedClassDict(typeof(Mvcms.Common.Net.SocketServer));
}
@section HeaderContent{
    <script>
        function help() {
            showHelp('list', 'devices', '@sysconfig.webpath');//获取并显示帮助信息,common_ext.js->showHelp(page_type,page_name,webpath = '/');
        }

        function submitAdd(sender) {
            var select_ip = '';
            var select_port = 0;
            var receive_text = '';
            $('#reader_table tr:gt(0)').each(function () {
                var $td = $(this).find('td:eq(0)');
                var ip = $td.find('label:eq(0)').text();
                var port = $td.find('label:eq(1)').text();
                var is_reg = $td.find('label:eq(2)').text();
                if (is_reg == '0' && $td.find('.checkall input').prop('checked')) {
                    select_ip = ip;
                    select_port = port;
                    return false;
                }
            });
            if (select_ip == '') {
                UI.dialog({ style: 'error', content: '请选择要操作的数据!' });
                return;
            }
            window.location.href = '../device_edit/index?action=@TEnums.ActionEnum.Add&type_name=@ViewBag.type_name&ip=' + select_ip + '&port=' + select_port;
        }
    </script>
}

<!--导航栏-->
<div class="location">
    <a href="javascript:history.back(-1);" class="back"><i class="iconfont icon-up"></i><span>返回上一页</span></a>
    <a href="../../center/index"><i class="iconfont icon-home"></i><span>首页</span></a>
    <i class="arrow iconfont icon-arrow-right"></i>
    <span>设备类别管理</span>
</div>
<div id="setPanel" class="set-right">
    <div class="info">
        <a href="javascript:help();"><i class="iconfont icon-help"></i></a>
    </div>
    <div class="option">
        <i class="iconfont icon-more"></i>
        <div class="drop-menu">
            <ul class="item">
                <li>
                    <a href="#fieldPanel" onclick="togglePanel(this);return false;">显示字段选择</a>
                </li>
                <li>
                    <a href="javascript:;" onclick="help();return false;">帮助</a>
                </li>
                <li>
                    <a href=".hidden-parme" onclick="togglePanel(this);return false;">显示页面变量</a>
                </li>
            </ul>
        </div>
    </div>
</div>
<!--/导航栏-->

<!--工具栏-->
<div class="toolbar-wrap">
    <div class="toolbar">
        <div class="box-wrap">
            <a class="menu-btn"><i class="iconfont icon-more"></i></a>
            <div class="l-list">
                <ul class="icon-list">
                    <li><a href="javascript:;" onclick="submitAdd(this);"><i class="iconfont icon-close"></i><span>登记设备</span></a></li>
                    <li>

                    </li>
                </ul>
                <div class="menu-list">
                </div>
            </div>
            <div class="r-list">
            </div>
        </div>
    </div>
</div>
<!--/工具栏-->

<!--标签页头-->
<div id="floatHead" class="content-tab-wrap">
    <div class="content-tab">
        <div class="content-tab-ul-wrap">
            <ul>
                @foreach(KeyValuePair<string, string> kv in type_dict){
                    <li><a class="@(ViewBag.type_name == kv.Key ? "selected" : "")" href="index?type_name=@kv.Key">@kv.Value</a></li>
                }
            </ul>
        </div>
    </div>
</div>
<!--/标签页头-->

<!--搜索在线设备列表-->
<div class="tab-content">
    <div class="line15"></div>
    <!--列表-->
    <div class="table-container">
        <table border="0" class="ltable" id="reader_table">
            <tr>
                <th><span class="item-mobile-hide">选择</span><i class="iconfont icon-check item-mobile-show"></i></th>
                <th>回复数据</th>
                <th>设备名称</th>
                <th>标题</th>
                <th>远程IP</th>
                <th>远程端口</th>
                <th>本地IP</th>
                <th>本地端口</th>
                <th>状态</th>
                <th>操作</th>
            </tr>
            @foreach (Mvcms.Common.Net.BaseSocketReceive item in Model) {
                int is_reg = new Mvcms.BLL.IOT.devices().GetCount("ipaddress='" + item.IPAddress + "'");
                <tr>
                    <td>
                        <span class="checkall">
                            <input type="checkbox" @(is_reg == 1 ? "disabled=\"disabled\"" : "") /></span>
                        <label style="display: none">@item.IPAddress</label>
                        <label style="display: none">@item.Port</label>
                        <label style="display: none">@is_reg</label>
                    </td>
                    <td><i title="@item.ReceiveText" class="iconfont icon-comment"></i></td>
                    <td>@item.Name</td>
                    <th>@item.Title</th>
                    <td>@item.IPAddress </td>
                    <td>@item.Port</td>
                    <td>@item.DestinationIP</td>
                    <td>@item.DestinationPort</td>
                    <td>
                        @if (is_reg == 0) {
                            <a href="../device_edit/index?action=@TEnums.ActionEnum.Add&type_name=@ViewBag.type_name&ip=@item.IPAddress&port=@item.Port" >未登记</a>
                        }
                        else {
                            @:已登记
                        }
                    </td>
                    <td class="item-mobile-hide">
                        <a title="id=@item.IPAddress" href="../DeviceSetting/index?action=@TEnums.ActionEnum.Edit&ip=@item.IPAddress&port=@item.Port&type_name=@ViewBag.type_name">修改配置</a>
                    </td>
                </tr>
            }
            @if (Model.Count == 0) {
                <tr>
                @if (string.IsNullOrEmpty(ViewBag.msg)) {
                        <td colspan="10">暂无记录</td>
                }
                else {
                        <td colspan="10">@ViewBag.msg</td>
                }
                </tr>
            }
        </table>
    </div>
    <!--/列表-->
</div>
<!--/搜索在线设备列表-->

运行界面

图片.png

所广播的内容字节数据 FA 01 34 33 21 56 23 A5 7B 29 C5 5D 3C 32 12 FE 01 FF 00 00 00 00 00 00 00 00 00 00 3A 4B ,即是经过CRC编码搜索数据,得到的回复数据经过SwitchDeviceServer类复写SetDeviceInfo方法完成数据解析任务

相关资讯

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?
    会员登陆
    18004549898
    QQ
    Mvcms网站管理系统客服QQ:
    返回顶部