6

Bluetooth からバイト シーケンスを受信する長さ 256 のバッファーがあります。抽出する必要がある実際のパケットは、 byteで始まり、 byteで終わります126。LINQ を使用して、バッファー内の最新のパケットを抽出したいと考えています。

私が今行っているのは、 の最後のインデックスをチェックしてから126、別の に到達するまで逆方向にカウントすること126です。いくつかの落とし穴もあります。たとえば、2 つの隣接するパケットが 2 バイト隣り合っ126ている可能性があります。

バッファのサンプルは次のとおりです。

126 6 0 5 232 125 93 126 126 69 0
0 1 0 2 2 34 6 0 5 232 125
93 126 126 69 0 0 1 0 2 2 34
6 0 5 232 125 93 126 126 69 0 0
1 0 2 2 34 6 0 5 232 125 93
126 126 69 0 0

だから私が持っている情報は次のとおりです。

  • パケットはバイト値 126 で開始および終了します
  • 開始インデックスの後の次のバイトの値は 69 です
  • 126 の最後のバイトの直前の最後の 3 バイトは、計算方法を知っているパケット全体の CRC であるため、パケットを抽出した後、この CRC をチェックして、正しいパケットがあるかどうかを確認できます。

最後に、正しいパケットを含む配列またはリストが必要です。例えば:

126 69  0  0   1   0   2   2   34  6   0   5   232 125 93 126

このパケットをバッファから抽出する簡単な解決方法を教えてください。

これは私がこれまでに試したことです....探している正しいパケットを実際に返すことができないため、失敗します:

var data = ((byte[])msg.Obj).ToList(); //data is the buffer 

byte del = 126; //delimeter or start/end byte
var lastIndex = data.LastIndexOf(del);
var startIndex = 0;
List<byte> tos = new List<byte>(); //a new list to store the result (packet)    

//try to figure out start index                            
if(data[lastIndex - 1] != del)
{
    for(int i = lastIndex; i > 0; i--)
    {
        if(data[i] == del)
        {
            startIndex = i;
        }
    }

    //add the result in another list
    for(int i = 0; i <= lastIndex - startIndex; i++)
    {
        tos.Add(data[i]);
    }

    string shit = string.Empty;

    foreach (var b in tos)
        shit += (int)b + ", ";

   //print result in  a textbox
    AddTextToLogTextView(shit + "\r\n");
}
4

5 に答える 5

3

次の 2 つのルールをバッファーに適用できる場合、LINQ を使用してこれを 1 行のコードで実行できます。

  • バッファーには、指定された区切り文字で囲まれた完全なパッケージが少なくとも 1 つ含まれています。
  • 各パケットには、少なくとも 1 バイトのデータが含まれます。

コードは次のとおりです。

var data = (byte[])msg.Obj;
byte delimiter = 126;

var packet = data.Reverse()
                 .SkipWhile(b => b != delimiter)
                 .SkipWhile(b => b == delimiter)
                 .TakeWhile(b => b != delimiter)
                 .Reverse();

(わかりました、読みやすくするために複数行に分割したため、これは複数行でした。)

編集:常に空のシーケンスを返すため、Take(1) の呼び出しを削除しました。ただし、結果にはこのように区切り文字が含まれていません。


そして、これがどのように機能するかです:

最後のパケットを見つけたいので、データを逆にすることができます:

var reversed = data.Reverse();

バッファは、まだ完了していないパケットで終了する可能性があります。だからそれをスキップしましょう:

reversed = reversed.SkipWhile(b => b != delimiter);

reversedは空であるか、 で始まりdelimiterます。バッファーには常に少なくとも 1 つの完全なパケットが含まれていると想定しているため、次のバイトが区切り文字であることがわかっているため、結果として次のバイトを取得できます。

var packet = reversed.Take(1);

シーケンスで、1 バイトをスキップできるようになりました。見つかった区切り文字が実際に新しいパケットの始まりであった場合、残りのシーケンスは別の区切り文字で始まるため、それもスキップする必要があります。

reversed = reversed.Skip(1);
if (reversed.First() == delimiter)
{
    reversed.Skip(1);
}

パケットには 3 バイトの CRC が含まれているため、パケットを空にすることはできないことがわかっているため、次のように記述できます。

reversed = reversed.SkipWhile(b => b == delimiter);

実際のデータは次のとおりです。

packet = packet.Concat(reversed.TakeWhile(b => b != delimiter));
reversed = reversed.SkipWhile(b => b != delimiter);

次のバイトは、パケットの開始を示す区切り文字です。

packet = packet.Concat(reversed.Take(1));

最後に行うことは、結果を再び逆にすることです。

packet = packet.Reverse();

これをメソッドに入れたいと思うかもしれません:

public IEnumerable<byte> GetPacket(byte[] data, byte delimiter)
{
    yield return delimiter;

    foreach (byte value in data.Reverse()
                               .SkipWhile(b => b != delimiter)
                               .SkipWhile(b => b == delimiter)
                               .TakeWhile(b => b != delimiter))
    {
        yield return value;
    }

    yield return delimiter;
}

このメソッドの戻り値に対して Reverse を呼び出す必要があります。


パフォーマンスが重要な場合は、基になる配列で同じアルゴリズムを使用できます。この方法では、約 20 倍高速になります。

int end = data.Length - 1;
while (data[end] != delimiter)
    end--;

while (data[end] == delimiter)
    end--;

int start = end;
while (data[start] != delimiter)
    start--;

byte[] result = new byte[end - start + 2];  // +2 to include delimiters
Array.Copy(data, start, result, 0, result.Length);
于 2013-04-12T16:31:00.030 に答える
1

あなたの質問を解決するには実際にはさまざまな方法があります。最も簡単なアイデアは、 double 126(0x7e)を検出することであり、CRC などの他のことは問題ではありません。

この概念の基本的な実装は次のようになります

  • シンプルなコード

    var list=new List<byte[]>();
    int i=0, j=0;
    for(; i<data.Length; ++i)
        if(i>0&&0x7e==data[i]&&0x7e==data[i-1]) {
            list.Add(data.Skip(j).Take(i-j).ToArray());
            j=i;
        }
    list.Add(data.Skip(j).Take(i-j).ToArray());
    

C# でのコナミ コードの古い回答に基づいており、この質問を解決するために使用されていました: c# でキーストロークをログに記録しているときに特殊文字を入力すると、二重文字が表示されます

  • シーケンス検出器を使用したコード

    public partial class TestClass {
        public static void TestMethod() {
            var data=(
                new[] { 
                        126, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0, 1, 0, 2, 2, 34, 6, 0, 5, 232, 125, 93, 126, 
                        126, 69, 0, 0 
                    }).Select(x => (byte)x).ToArray();
    
            var list=new List<List<byte>>();
    
            foreach(var x in data) {
                if(list.Count<1||SequenceCapturer.Captured((int)x))
                    list.Add(new List<byte>());
    
                list.Last().Add(x);
            }
    
            foreach(var byteList in list)
                Debug.Print("{0}", byteList.Select(x => x.ToString("x2")).Aggregate((a, b) => a+"\x20"+b));
        }
    }
    
    public class SequenceCapturer {
        public int Count {
            private set;
            get;
        }
    
        public int[] Sequence {
            set;
            get;
        }
    
        public bool Captures(int value) {
            for(var i=Sequence.Length; i-->0; ) {
                if(Sequence[i]!=value) {
                    if(0==i)
                        Count=0;
    
                    continue;
                }
    
                if(Count!=i)
                    continue;
    
                ++Count;
                break;
            }
    
            var x=Sequence.Length==Count;
            Count=x?0:Count;
            return x;
        }
    
        public SequenceCapturer(int[] newSequence) {
            Sequence=newSequence;
        }
    
        public SequenceCapturer()
            : this(new[] { 0x7e, 0x7e }) {
        }
    
        public static bool Captured(int value) {
            return Instance.Captures(value);
        }
    
        public static SequenceCapturer Instance=new SequenceCapturer();
    }
    

または、Linq で完全に記述したい場合は、次のことを試してください。を使用する必要さえありません。バイト配列の配列を直接提供しますListpacketArray

s は、コードを行に分割することを目的としています。letそうしないと、1 行で非常に長いステートメントになります。1 行が最適であると考えられる場合は、そうします。

  • コードのpacketArray

    var packetArray=(
        from sig in new[] { new byte[] { 0x7e, 0x7e } }
        let find=new Func<byte[], int, IEnumerable<byte>>((x, i) => x.Skip(i).Take(sig.Length))
        let isMatch=new Func<IEnumerable<byte>, bool>(sig.SequenceEqual)
        let filtered=data.Select((x, i) => 0==i||isMatch(find(data, i-1))?i:~0)
        let indices=filtered.Where(i => ~0!=i).Concat(new[] { data.Length }).ToArray()
        from index in Enumerable.Range(1, indices.Length-1)
        let skipped=indices[index-1]
        select data.Skip(skipped).Take(indices[index]-skipped).ToArray()).ToArray();
    
  • 出力用コード

    foreach(var byteArray in packetArray)
        Debug.Print("{0}", byteArray.Select(x => x.ToString("x2")).Aggregate((a, b) => a+"\x20"+b));
    

ただ、同じ解法であっても、先に述べたように様々な方法があります。CRC に関するような追加の条件を含めないことを強くお勧めします。これにより、事態がより複雑になる可能性があります。

于 2013-04-05T13:11:39.150 に答える
0

最後のパケットを探しているので、byte[] を逆にして最初のパケットを探す方がはるかに簡単です。2 つのパケット区切り文字は 126 だけではありません。パケットの最後が受信された最後のバイトでない限り、最初は 126, 69 で、最後は 126, 126 であり、終了区切り文字は 126 になります。

これに似た方法を使用することをお勧めします。

public static byte[] GetMessage(byte[] msg)
    {
        //Set delimiters
        byte delimit = 126;
        byte startDelimit = 69;

        //Reverse the msg so we can find the last packet
        List<byte> buf = msg.Reverse().ToList();

        //set indices to impossible values to check for failures
        int startIndex = -1;
        int endIndex = -1;
        //loop through the message
        for (int i = 0; i < buf.Count - 1; i++)
        {
            //find either a double 126, or 126 as the last byte (message just ended)
            if (buf[i] == delimit && (buf[i + 1] == delimit || i == 0))
            {
                if (i == 0)
                {
                    startIndex = i;
                    i++;
                }
                else
                {
                    startIndex = i + 1;
                    i += 2;
                }
                continue;
            }
            //Only process if we've found the start index
            if (startIndex != -1)
            {
                //check if the byte is 69 followed by 126
                if (buf[i] == startDelimit && buf[i + 1] == delimit)
                {
                    endIndex = i + 1;
                    break;
                }
            }
        }
        //make sure we've found a message
        if (!(startIndex == -1 || endIndex==-1))
        {
            //get the message and reverse it to be the original packet
            byte[] revRet = new byte[endIndex - startIndex];
            Array.Copy(buf.ToArray(), startIndex, revRet, 0, endIndex - startIndex);

            return revRet.Reverse().ToArray();
        }
        return new byte[1];
    }

コピーのインデックスが完全に正しいかどうかは完全にはわかりませんが、これがその要旨です。

于 2013-04-17T15:34:02.630 に答える