4

私は、現実の世界と相互作用するレガシー組み込みデバイスを維持しています。一般的に、このデバイスはセンサーからデータを収集し、内部アルゴリズムを使用してデータを処理し、データが特定の「不良」状態に達すると警告を表示します。

デバッグの目的で、このデバイスが受信したデータの多くと、処理後のデータを定期的に送信してくれることを願っています。

ほとんどのデータは表形式で記述できるという結論に達しました。

sensor|time|temprature|moisture
------+----+----------+--------
1     |3012|20        |0.5
2     |3024|22        |0.9

明らかに、複数の形式のテーブルをサポートする必要があります。

したがって、基本的には、特定のテーブルの説明のセットを受け入れ、その説明に従ってテーブルデータを配信できるプロトコルが必要です。

データを送信するための擬似コードの例は次のとおりです。

table_t table = select_table(SENSORS_TABLE);
sensors_table_data_t data[] = {
    {1,3012,20,0.5},
    {1,3024,22,0.9}
    };
send_data(table,data);

データを受信するための擬似コードの例は次のとおりです。

data_t *data = recieve();
switch (data->table) {
    case SENSORS_TABLE:
         puts("sensor|time|temprature|moisture");
         for (int i=0;i<data->length;i++) printf(
             "%5s|%4s|%9s|%9s\n",
              data->cell[i]->sensor,
              data->cell[i]->time,
              data->cell[i]->temprature,
              data->cell[i]->moisture);
         break;
    case USER_INPUT_TABLE:
         ...
}

テーブルの定義は、デバイスとそれと通信するクライアントコンピューターの両方でオフラインで行うことも、オンラインで行うこともできます。単純なハンドシェイクプロトコルを追加して、デバイスの起動時にテーブルの形式に同意することができます。

これはレガシーデバイスであるため、RS232通信のみをサポートし、CPUはかなり遅い(486に相当)ため、XMLのようなデータ転送方法を使用する余裕はありません。それらは高すぎます(計算時間または帯域幅のいずれか)。生のSQLコマンドの送信も考慮され、帯域幅を考慮して拒否されました。

[編集]

明確にするために、毎回テーブルヘッダーを送信するオーバーヘッドも削減します。データを送信するたびに、テーブルヘッダーを送信しないようにしています。そのため、テーブル行を送信するたびに、テーブルIDを送信する必要があります。

また、渡したいデータのほとんどは数値であるため、テキストベースのプロトコルは無駄が多すぎることにも注意してください。

最後に、Googleのプロトコルバッファを見ました。十分に近いですが、Cをサポートしていません。

[/編集]

私が説明したような既知のプロトコルまたは実装についてのアイデアはありますか?このデータを送信するためのより良いアイデアはありますか?

このプロトコルの設計はそれほど難しくないという事実を認識しています。私は2フェーズプロトコルを念頭に置いていました。

1)ハンドシェイク:入力するすべてのテーブルのヘッダーを送信します。各テーブルの説明には、各列のサイズに関する情報が含まれます。

2)データ:テーブルインデックス(ハンドシェイクによる)に続いて実際のデータを送信します。データの後にチェックサムが続きます。

しかし、私はそのような設計の細部を避け、いくつかの既製のプロトコルを使用したいと思います。またはさらに良いことに、利用可能な実装を使用します。

4

8 に答える 8

2

私はこれを行うプロトコルを知りません(あるかもしれませんが、私はそれを知りません)。

あなたはこれについて考えたことがあると思います:フォーマットをバイナリデータストリームとしても渡してみませんか?

擬似コード:

struct table_format_header {
  int number_of_fields; /* number of fields that will be defined in table */
                        /* sent before the field descriptions themselves  */
};

struct table_format {
   char column_name[8];   /* name of column ("sensor");  */
   char fmt_specifier[5]; /* format specifier for column */

   ... (etc)
}

次に、フィールド/列を(何らかの方法で)計算headerし、受信者がバッファーを割り当てることができるように構造体を送信table_formatしてから、それらのフィールドごとに構造体を繰り返し送信できます。構造体には、そのヘッダーに関連する必要なすべての情報(名前、フィールドのバイト数など)が含まれます。スペースが実際に制限されている場合は、ビットフィールド(int precision:3)を使用してさまざまな属性を指定できます

于 2009-04-19T06:05:12.140 に答える
2

すべてのデータが一定の長さである場合、それらの間に区切り文字は必要ありません。したがって、バイナリコンテンツを直接送信できます。たとえば、次の行です。

sensor|time|temprature|moisture
------+----+----------+--------
1     |3012|20        |0.5

次のように送信されます:

0x01 0x0B 0xC4 0x14 [4 bytes for float 0.5]

センサーと温度を1バイト、時間を2バイト、水分を4バイト(フロート)と想定しています。ヘッダーを送信する必要はありません。

これで、各行は一定の長さになり、受信者は変換ジョブを実行する必要があります。組み込みデバイスは、この形式でデータを簡単に送信できます。

ここで、メッセージにデータをカプセル化して、メッセージがいつ開始されるかを受信者が認識できるようにするという問題もあります。通常、これを行うには、ヘッダーとフッターを追加します。

[STX] message [ETX]

通常、ASCII文字のSTXとETXが使用されます(0x02と0x03だと思います)。問題は、これらの値がメッセージ本文にも表示される可能性があることです。したがって、トランスミッションに別のレイヤーを追加する必要があります。バイト0x02または0x03を送信する場合は、2回送信します。受信者では、単一の0x02バイトがメッセージの開始を示します。メッセージ本文内の追加の0x02および0x03バイトを削除する必要があります。

最後に、通信リンクの信頼性が低い場合は、チェックサムも追加する必要があります。

これらの手法は通常、PPPなどのシリアルプロトコルで使用されます。

于 2009-04-21T08:47:33.170 に答える
2

プロトコル バッファを試してみてください。

http://code.google.com/p/protobuf/

プロトコル バッファは、構造化されたデータを効率的かつ拡張可能な形式でエンコードする方法です。Google では、ほぼすべての内部 RPC プロトコルとファイル形式にプロトコル バッファを使用しています。

rascher のコメントに基づいて構築された protobufs は、フォーマットをコンパイルするため、送受信が非常に効率的です。後でフィールドを追加/削除したい場合にも拡張可能です。また、優れた API (protobuf python など) もあります。

于 2009-04-19T08:25:41.587 に答える
1

テキストは使いたくないとおっしゃっていましたが、B64の使用を検討する必要があります。これにより、バイナリからテキストへの変換とバイナリへの変換を簡単かつ比較的効率的に行うことができます。オーバーヘッドは1/3です。バイナリの3バイトごとに、4バイトのテキスト値に変換されます。テキストに変換した後、単純なデータスタイルプロトコルを使用できます。送信デバイスでは、エンコーダを実装するだけで済みます。以下の完全なコードを参照してください。

/********************************************************************/
/*                                                                  */
/* Functions:                                                       */
/* ----------                                                       */
/* TBase64Encode()                                                  */
/* TBase64Decode()                                                  */
/* TBase64EncodeBlock()                                             */
/* TBase64DecodeBlock()                                             */
/*                                                                  */
/********************************************************************/

#include "yourstuff.h"


// This table is used to encode 6 bit binary to Base64 ASCII.
static char Base64Map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
                          "ghijklmnopqrstuvwxyz0123456789+/";

// This table is used to decode Base64 ASCII back to 6 bit binary.
static char Base64Decode[]=
{
    62,                                         // '+'
    99, 99, 99,                                 // **** UNUSED ****
    63,                                         // '/'
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,     // '0123456789'
    99, 99, 99, 99, 99, 99, 99,                 // **** UNUSED ****
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,               // 'ABCDEFGHIJ'
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,     // 'KLMNOPQRST'
    20, 21, 22, 23, 24, 25,                     // 'UVWXYZ'
    99, 99, 99, 99, 99, 99,                     // **** UNUSED ****
    26, 27, 28, 29, 30, 31, 32, 33, 34, 35,     // 'abcdefghij'
    36, 37, 38, 39, 40, 41, 42, 43, 44, 45,     // 'klmnopqrst'
    46, 47, 48, 49, 50, 51                      // 'uvwxyz'
};




/** Convert binary data to Base64 data.
 *
 * @return  Size of output buffer if ok, -1 if problem (invalid paramaters).
 *
 * @param   input  - Pointer to input data.
 * @param   size   - Number of bytes to encode.
 * @param   output - Pointer to output buffer.
 *
 * @note    Up to caller to ensure output buffer is big enough. As a rough
 *          guide your output buffer should be (((size/3)+1)*4) bytes.
 */
int TBase64Encode( const BYTE *input, int size, PSTR output)
{
    int i, rc=0, block_size;

    while (size>0)
    {
        if (size>=3)
            block_size = 3;
        else
            block_size = size;

        i = TBase64EncodeBlock( input, block_size, output);

        if (i==-1)
            return -1;

        input += 3;
        output += 4;
        rc += 4;
        size -= 3;
    }

    return rc;
}




/** Convert Base64 data to binary data.
 *
 * @return  Number of bytes in output buffer, negative number if problem
 *          as follows:
 *           -1 : Invalid paramaters (bad pointers or bad size).
 *           -2 : Outside of range value for Base64.
 *           -3 : Invalid base 64 character.
 *
 * @param   input  - Pointer to input buffer.
 * @param   size   - Size of input buffer (in bytes).
 * @param   output - Pointer to output buffer.
 *
 * @note    Up to caller to ensure output buffer is big enough. As a rough
 *          guide your output buffer should be (((size/4)+1)*3) bytes.
 *          NOTE : The input size paramater must be multiple of 4 !!!!
 *          Note that error codes -2 and -3 essentiallty mean the same
 *          thing, just for debugging it means something slight different
 *          to me :-). Calling function can just check for any negative
 *          response.
 */
int TBase64Decode( CPSTR input, int size, BYTE *output)
{
    int output_size=0, i;

    // Validate size paramater only.
    if (size<=0 || size & 3)
        return -1;

    while (size>0)
    {   
        i = TBase64DecodeBlock( input, output);
        if (i<0)
            return i;

        output_size += i;
        output += i;
        input += 4;
        size -= 4;
    }

    return output_size;
}




/** Convert up to 3 bytes of binary data to 4 bytes of Base64 data.
 *
 * @return  0 if ok, -1 if problem (invalid paramaters).
 *
 * @param   input  - Pointer to input data.
 * @param   size   - Number of bytes to encode(1 to 3).
 * @param   output - Pointer to output buffer.
 *
 * @note    Up to caller to ensure output buffer is big enough (4 bytes).
 */
int TBase64EncodeBlock( const BYTE *input, int size, PSTR output)
{
    int i;
    BYTE mask;
    BYTE input_buffer[3];

    // Validate paramaters (rudementary).
    if (!input || !output)
        return -1;
    if (size<1 || size>3)
        return -1;

    memset( input_buffer, 0, 3);
    memcpy( input_buffer, input, size);

    // Convert three 8bit values to four 6bit values.
    mask = input_buffer[2];
    output[3] = mask & 0x3f;            // Fourth byte done...

    output[2] = mask >> 6;
    mask = input_buffer[1] << 2;
    output[2] |= (mask & 0x3f);         // Third byte done...

    output[1] = input_buffer[1] >> 4;
    mask = input_buffer[0] << 4;
    output[1] |= (mask & 0x3f);         // Second byte done...

    output[0] = input_buffer[0]>>2;     // First byte done...

    // TEST
//  printf("[%02x,%02x,%02x,%02x]", output[0], output[1], output[2], output[3]);

    // Convert 6 bit indices to base64 characters.
    for (i=0; i<4; i++)
        output[i] = Base64Map[output[i]];

    // Handle special padding.
    switch (size)
    {
        case 1:
            output[2] = '=';
        case 2:
            output[3] = '=';
        default:
            break;
    }


    return 0;
}




/** Convert 4 bytes of Base64 data to 3 bytes of binary data.
 *
 * @return  Number of bytes (1 to 3) if ok, negative number if problem
 *          as follows:
 *           -1 : Invalid paramaters (bad pointers).
 *           -2 : Outside of range value for Base64.
 *           -3 : Invalid base 64 character.
 *
 * @param   input  - Pointer to input buffer (4 bytes).
 * @param   output - Pointer to output buufer (3 bytes).
 *
 * @comm    While there may be 1, 2 or 3 output bytes the output
 *          buffer must be 3 bytes. Note that error codes -2 and -3
 *          essentiallty mean the same thing, just for debugging it
 *          means something slight different to me :-). Calling function
 *          can just check for any negative response.
 */
int TBase64DecodeBlock( CPSTR input, BYTE *output)
{
    int i, j;
    int size=3;
    BYTE mask;
    BYTE input_buffer[4];

    // Validate paramaters (rudementary).
    if (!input || !output)
        return -1;

    memcpy( input_buffer, input, 4);

    // Calculate size of output data.
    if (input_buffer[3]=='=')
    {
        input_buffer[3] = 43;
        size--;
    }
    if (input_buffer[2]=='=')
    {
        input_buffer[2] = 43;
        size--;
    }

    // Convert Base64 ASCII to 6 bit data.
    for (i=0; i<4; i++)
    {
        j = (int) (input_buffer[i]-43);
        if (j<0 || j>79)
            return -2;          // Invalid char in Base64 data.
        j = Base64Decode[j];
        if (j==99)      
            return -3;          // Invalid char in Base64 data.

        input_buffer[i] = (char) j;
    }

    // TEST
//  printf("[%02x,%02x,%02x,%02x]", input_buffer[0], input_buffer[1], input_buffer[2], input_buffer[3]);

    // Convert four 6bit values to three 8bit values.
    mask = input_buffer[1] >> 4;
    output[0] = (input_buffer[0]<<2) | mask;    // First byte done.

    if (size>1)
    {
        mask = input_buffer[1] << 4;
        output[1] = input_buffer[2] >> 2;
        output[1] |= mask;              // Second byte done.

        if (size==3)
        {
            mask = input_buffer[2] << 6;
            output[2] = input_buffer[3] | mask;     // Third byte done.
        }
    }

    return size;
}
于 2009-05-05T12:19:46.913 に答える
1

組み込み作業では、一般に、組み込みデバイスが実行する作業をできるだけ少なくし、クライアント コンピューターに独自の速度とツールの可用性を利用させることが推奨されます。あなたの例を考えると、受け取ったデータの最大サイズ、または列ヘッダーの最大サイズ(私の選択)を見るだけで、データを収集してからテーブルをフォーマットできます。また、これはデバッグ情報であるため、コレクションごとにテーブルのサイズが変わってもあまり問題にはなりません。または、デバイスはヘッダー ラベルを送信するだけで列のサイズを「強制」するか、すべてのデータがゼロであるダミー データの最初の行を目的の形式と長さで送信することもできます。

于 2009-04-19T07:31:44.523 に答える
1

誰かが言ったように:

[header][data][checksum]

ただし、それを拡張したい場合は、次を使用できます。

[header][table_id][elements][data][checksum]

[header]   : start of frame
[table_id] : table
[elements] : payload size
[data]     : raw data
[checksum] : checksum/crc, just to be on the safe side

「要素」は、固定サイズのデータ​​の数として、または「データ」セグメント内のバイト数としても使用できます。

ヘッダーとチェックサムを使用すると、画面上で何千もの 16 進文字を見るときに作業が楽になります。

編集:

ヘッダーは、メッセージが開始/終了したことをホストのプログラムに伝える良い方法です。それについて考えたことはありますか?

一方、統計的な方法でヘッダーの使用について考える必要があります。10 バイトごとに 4 バイトは 40% ですが、256 バイトではわずか 1.6% です。したがって、それに応じてサイズを指定します。

于 2009-04-22T17:10:19.273 に答える
1

CSV は最も単純な形式であるため (CSV の最適な説明についてはRFC 4180を参照)、CSV に投票します (gbarry の回答を参照)。

RFC (セクション 2、項目 3) で説明されているように、列名を含むオプションのヘッダーが必要になります。

CSV 送信者で注意すべき主な点は、「特殊」文字のエスケープだけです。

于 2009-04-19T09:23:57.030 に答える
0

シリアル計算の基礎..。

[header] [data] [check-sum]

[データ]は最も重要な部分ですが、[ヘッダー]と[チェックサム]は実際に奇妙な実際の単語の問題を解決するのに役立ちます。ただし、小さい場合は常に[ヘッダー]と[チェックサム]を使用してください。

データの大規模なチェーンを作成することで、[ヘッダー]、[チェックサム]の過負荷を減らすことができます。

データを読み取った後、ホストPC(デバッグPCになります)から何かを実行して、任意の形式でデータを読み取って表示します。

于 2009-04-20T05:09:59.537 に答える