[C#.NET][TCP Socket] 利用 Socket 控制 Advantech RU25 Reader-Passive Mode

  • 5080
  • 0
  • C#
  • 2013-08-16

[C#.NET][TCP Socket] 利用 Socket 控制 Advantech RU25 Reader-Passive Mode

日前,因為在控制這台 Reader 設備的時候短缺原廠的  command set / get 文件,於是利用原廠提供的 Demo software + Smart Sniff 來觀察 TCP Package,我也會用 Smart Sniff 來驗証資料傳遞之間是否有問題。

SNAGHTMLa620195

 

除了 Smart Sniff 之外,我也力推免費軟體 Wireshark,功能較為強大,相信大家都有聽過它,相當的實用,市面上也有相關的書籍,實戰封包分析-使用 Wireshark (Practical Packet Analysis: Using Wireshark to Solve Real-World Network Problems, 2/e)

SNAGHTMLb4cb7c4

 

工具準備好之後,就可以來開始處理它了。


Step1.Conect:

跟前面幾篇一樣建立一個新的 Socket Class,並且 調用Socket.Connect 方法。

var ipAddress = IPAddress.Parse(this.Config.IpAddress);
var iPEndPoint = new IPEndPoint(ipAddress, this.Config.Port);

//Create a TCP/IP  socket.
this._socketCliect = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
EndPoint endPoint = (EndPoint)iPEndPoint;
this._socketCliect.Connect(endPoint);


當連線成功的時候,Reader 不會拋訊息回來,不用寫 Receive,也就是不需要帳號/密碼,在網路的世界裡,欠缺安全性這方面的考量。
SNAGHTMLa6b8744

 

 

完整的 Connect 方法如下,因為設備的特性所以我必須要送出一些參數命令,比如連續兩次的 set readmode 0,才能在某些情況下取得正確的 Receive data(這是實驗得知的特性)

private Socket _socketCliect = null; private Encoding _encode = Encoding.ASCII; public bool Connect() { if (this.Config == null) { return false; } if (this.IsConnected) { return true; } var ipAddress = IPAddress.Parse(this.Config.IpAddress); var iPEndPoint = new IPEndPoint(ipAddress, this.Config.Port); //Create a TCP/IP socket. this._socketCliect = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); EndPoint endPoint = (EndPoint)iPEndPoint; this._socketCliect.Connect(endPoint); if (this._socketCliect.Connected) { SendAndReceive("set readmode 0"); var readmode = SendAndReceive("set readmode 0"); var inventorymode = this.SendAndReceive("set inventorymode 0"); if (readmode == MESSAGE_SUCCESS && inventorymode == MESSAGE_SUCCESS) { this.IsConnected = true; } else { this.IsConnected = false; } } return this.IsConnected; }



Setp2.Send Command Set:

跟設備搭上線後便可以對它送出 ASCII 命令,當我調用 Socket.Send 方法 送出資料 後,就要調用 Socket.Receive 方法去 拿回資料,然後驗證拿到的資料是否正確。

下圖為原廠提供設備回傳的資料格式:

image

 

 

根據文件,程式碼如下,在這裡我只檢查最後6個 byte[] { 49, 49, 48, 50, 13, 10 },也就是 1102\r\n ,若在時間內收到最後這 6 個 byte 則離開迴圈並回傳資料,若在時間內拿不到這最後的 byte[] ,表示網路接收逾時(ReceiveTimeout),則離開迴圈。

public static readonly string MESSAGE_SUCCESS = "1101\r\n701\r\n1102\r\n"; 
public string SendAndReceive(string Data)
{
    if (this.Config == null)
    {
        return null;
    }
    if (this._socketCliect == null || !this._socketCliect.Connected)
    {
        return null;
    }
    SocketError error = new SocketError();
    var command = string.Concat(Data, "\r");
    var sentCommand = this.Encode.GetBytes(command);
    this._socketCliect.Send(sentCommand, 0, sentCommand.Length, SocketFlags.None, out error);

    var tempTimeOut = this._socketCliect.ReceiveTimeout;
    this._socketCliect.ReceiveTimeout = 1000;

    var bufferArray = new byte[256];
    var socketError = new SocketError();

    MemoryStream memory = new MemoryStream();

    byte[] expect = new byte[] { 49, 49, 48, 50, 13, 10 };
    byte[] target = new byte[6];

    while (true)
    {
        var receiveCount = this._socketCliect.Receive(bufferArray, 0, bufferArray.Length, SocketFlags.None, out socketError);
        if (receiveCount == 0 || socketError != SocketError.Success)
        {
            break;
        }

        memory.Write(bufferArray, 0, receiveCount);
        if (memory.Length < expect.Length)
        {
            continue;
        }

        var startIndex = memory.Length - expect.Length;
        memory.Seek(startIndex, SeekOrigin.Begin);
        memory.Read(target, 0, expect.Length);

        var compare = expect.SequenceEqual(target);

        if (compare)
        {
            break;
        }
    }

    this._socketCliect.ReceiveTimeout = tempTimeOut;
    byte[] response = memory.ToArray();
    var result = this.Encode.GetString(response, 0, response.Length);

    if (memory != null)
    {
        memory.Close();
    }
    return result;
}

 

下圖為 set 命令的封包傳送結果

SNAGHTMLa8eabba

 


Setp3.Send Command Get:

設備除了有 set 之外,當然還會有 get。

下圖為 get command 所回傳的資料格式:

image

 

 

當我調用 SendAndReceive("get readmode") 方法,就能觀察到以下的資料

SNAGHTMLb1ee7cc

 

 

 

 

 

 

每一個功能的 get 回傳的資料格式以及 return code 都不太一樣,return code 是用來驗證資料格式是否正確的旗標,所以要 用力的 看文件,若沒文件就觀察 Smart Sniff。

依照文件規則 get command 產生了以下程式碼:

public string getValidData(string data, string returnCode)
{
    if (string.IsNullOrEmpty(data) | string.IsNullOrEmpty(returnCode))
    {
        return null;
    }

    var split = data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntri
    if (split.Length <= 3)
    {
        return null;
    }

    var commandIndex = 0;
    var endIndex = split.Length - 1;
    var returnIndex = split.Length - 2;

    var commandCode = split[commandIndex];
    var endCode = split[endIndex];

    if (commandCode != "1101" && endCode != "1102" && split[returnIndex] != returnCode)
    {
        return null;
    }
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < split.Length; i++)
    {
        if (i == commandIndex | i == endIndex | i == returnIndex)
        {
            continue;
        }
        builder.Append(split[i]);
    }

    return builder.ToString();
}


inventory command,是我手頭上所擁有的稀有文件資料,正好也算是一個最重要的命令,就以它來當例子,當我調用 SendAndReceive("inventory"),會得到以下的結果,它所回傳的結果是多筆的

SNAGHTMLb19e285

 

如下圖,inventory 所定義的資料格式

image

 

針對 inventory 的命令包裝成一個方法,然後調用 getValidData 方法,驗證是否為合法資料。

public IEnumerable<TagStatus> InventoryTag()
{
    if (!this.IsConnected)
    {
        return null;
    }
    var response = this.SendAndReceive("inventory");
    if (string.IsNullOrEmpty(response))
    {
        return null;
    }
    var data = getValidData(response, "101");

    List<TagStatus> tagStatusList = new List<TagStatus>();
    foreach (var d in data)
    {
        var tag = TagStatus.Parse(d);
        tagStatusList.Add(tag);
    }
    return tagStatusList;
}

 


用來裝載資料的 TagStatus 類別
public class TagStatus : INotifyPropertyChanged
{
    private string _tagEpc;
    private string _memory;
    private int _antennaNumber;
    private double _rssiAver;
    private double _rssiMax;
    private double _rssiMin;
    private string _pc;

    public string TagEpc
    {
        get { return _tagEpc; }
        set
        {
            _tagEpc = value;
            OnPropertyChanged("TagEpc");
        }
    }

    public int AntennaNumber
    {
        get { return _antennaNumber; }
        set
        {
            _antennaNumber = value;
            OnPropertyChanged("AntennaNumber");
        }
    }

    public double RSSI_Aver
    {
        get { return _rssiAver; }
        set
        {
            _rssiAver = value;
            OnPropertyChanged("RSSI_Aver");
        }
    }

    public double RSSI_Min
    {
        get { return _rssiMin; }
        set
        {
            _rssiMin = value;
            OnPropertyChanged("RSSI_Min");
        }
    }

    public double RSSI_Max
    {
        get { return _rssiMax; }
        set
        {
            _rssiMax = value;
            OnPropertyChanged("RSSI_Max");
        }
    }

    public string PC
    {
        get { return _pc; }
        set
        {
            _pc = value;
            OnPropertyChanged("PC");
        }
    }

    public string Memory
    {
        get { return _memory; }
        set
        {
            _memory = value;
            OnPropertyChanged("Memory");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler changedEventHandler = this.PropertyChanged;
        if (changedEventHandler == null)
            return;
        changedEventHandler((object)this, new PropertyChangedEventArgs(name));
    }

    public override string ToString()
    {
        return string.Format("TagEpc:{0}", this.TagEpc);
    }

    public static TagStatus Parse(string data)
    {
        // "3000E2003411B802011618152459,0002,4A,4A,4A";
        // "3000E2003411B802011618152459,000000000,0002,4A,4A,4A";
        if (string.IsNullOrEmpty(data))
        {
            return null;
        }

        var split = data.Split(',');
        var index = split.Length;
        if (index < 5)
        {
            return null;
        }
        TagStatus tagStatus = new TagStatus();
        tagStatus.TagEpc = split[0].Substring(4);
        tagStatus.PC = split[0].Substring(0, 4);
        if (index == 5)
        {
            // "3000E2003411B802011618152459,0002,4A,4A,4A";
            tagStatus.AntennaNumber = int.Parse(split[1]);
            tagStatus.RSSI_Min = Convert.ToInt32(split[2], 16);
            tagStatus.RSSI_Max = Convert.ToInt32(split[3], 16);
            tagStatus.RSSI_Aver = Convert.ToInt32(split[4], 16);
        }
        else if (index == 6)
        {
            // "3000E2003411B802011618152459,000000000,0002,4A,4A,4A";
            tagStatus.Memory = split[1];
            tagStatus.AntennaNumber = int.Parse(split[2]);
            tagStatus.RSSI_Min = Convert.ToInt32(split[3], 16);
            tagStatus.RSSI_Max = Convert.ToInt32(split[4], 16);
            tagStatus.RSSI_Aver = Convert.ToInt32(split[5], 16);
        }

        return tagStatus;
    }
}

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo