2009-04-19 3 views
3

저는 실제 세계와 상호 작용하는 레거시 임베디드 장치를 유지 관리하고 있습니다. 일반적으로이 장치는 센서에서 데이터를 수집하고 내부 알고리즘을 사용하여 데이터를 처리하며 데이터가 특정 "불량"상태에 도달하면 경고를 표시합니다.표 형식의 데이터를 전달하는 바이너리 형식

디버깅을 위해이 장치가 수신 한 많은 데이터와 처리 된 데이터를 정기적으로 보내주기를 바랍니다.

우리는 대부분의 데이터가 표 형식으로 설명 할 수있는 결론을 우리는 분명히 테이블의 하나 이상의 양식을 지원해야

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를 보내야합니다.

나는 또한 내가 전달하고자하는 데이터의 대부분이 수치이므로주의하고 싶은, 그래서 텍스트 기반의 프로토콜은 너무 낭비입니다.

마지막으로 내가 본 구글의 프로토콜 버퍼, 그것은 충분히 가까이하지만 C를 지원하지 않습니다

[/ 편집]

내가 설명한 것 같은 알려진 프로토콜 또는 구현에 대한 어떤 생각 ? 이 데이터를 보내는 것이 더 좋은 생각입니까?

1) 핸드 셰이크가 : 당신이 입력하고자하는 모든 테이블의 헤더를 보내

은 내가 마음에 두 단계 프로토콜을했다,이 프로토콜을 설계하는 것은 매우 어려운 일이 아니다는 사실을 알고 있어요. 각 테이블 설명은 각 컬럼의 크기에 대한 정보를 포함한다.

2) 데이터 : 핸드 쉐이크에 따라 (테이블 인덱스를 전송)의 실제 데이터 하였다. 데이터에 체크섬이옵니다.

그러나 이러한 디자인의 작은 세부 사항을 피하고 기성 프로토콜을 사용하고 싶습니다. 또는 더 나은 방법은 사용 가능한 구현을 사용하는 것입니다.

답변

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)를 사용하여 다른 속성을 지정할 수 있습니다.

+0

데이터를 보낼 때마다 테이블 헤더를 보내시겠습니까? 나는 그것을 피하려고 노력했다. 필자는 임베디드 장치를 원했고 호스트 컴퓨터는 제한된 테이블 집합에 동의 할 것이고 구조체를 테이블 인덱스로 지정할 것입니다. –

+0

흠. 3 가지 유형의 테이블 데이터가 있다고 가정 해 보겠습니다. [0]을 입력하고 [1]을 입력하고 [2]를 입력하십시오. 장치가 실제로 테이블 데이터를 보내기 전에 연결 시작시 이러한 테이블 구조를 보낼 수 있습니다. 그런 다음 데이터와 함께 * 사용할 테이블 구조 (0, 1 또는 2)를 보낼 수 있습니다. 기본적으로 데이터 자체를 전송하기 전에 모든 설정을 수행하십시오 (설정 종류에 관계없이). – poundifdef

+0

그건 내가 염두에 두었던 것이지만 누군가가 이미 그 코드를 작성했다고 생각합니다. :) –

1

임베디드 작업에서는 일반적으로 임베디드 장치가 가능한 한 적은 작업을 수행하고 클라이언트 컴퓨터가 자체 속도의 도구의 가용성. 귀하의 예를 들어, 내가받은 데이터의 최대 크기 또는 열 머리글 (내 선택)의 최대 크기를보고에서 데이터를 수집 한 다음, 테이블을 포맷 수 있습니다. 그리고 정보를 디버깅하기 때문에 테이블 크기가 컬렉션간에 바뀌면 너무 중요하지 않습니다. 또는 장치가 머리글 레이블을 전송하여 열 크기를 "강제"할 수도 있고, 모든 데이터가 0이지만 원하는 형식 및 길이로 더미 데이터의 첫 번째 줄을 전송할 수도 있습니다.

2

프로토콜 버퍼를 사용해보십시오.

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

프로토콜 버퍼는 아직 효율적인 인코딩 구조화 된 데이터의 확장 방법 형식이다. Google은 프로토콜 버퍼를 거의 모든 내부 RPC 프로토콜과 형식의 파일에 사용합니다.

protobufs는 형식을 컴파일하여 송수신하는 것이 효율적입니다. 나중에 필드를 추가/제거하려는 경우에도 확장 가능합니다. 그리고 훌륭한 API가 있습니다 (예 : protobuf python).

+0

Google의 프로토콜 버퍼를 알고 있지만 불행히도 내 프로젝트의 필수 요소 인 C 백엔드가 없습니다. 그러나 프로토콜 버퍼는 참으로 (필자의) 필자의 필요를 채워주는 좋은 예입니다. –

1

가장 간단한 형식이므로 CSV (CSV에 대한 가장 좋은 설명은 RFC 4180 참조)에 투표 할 것입니다 ( gbarry의 답변 참조).

RFC (2 절, 항목 3)에서 설명한 것처럼 열 이름이있는 선택적인 헤더가 필요합니다.

주된 생각은 "특수"문자를 이스케이프 처리하는 것입니다. 일련의 계산

+0

저는 주로 번호를 보내고 있습니다. ASCII 숫자는 낭비입니다. 매번 테이블 헤더를 보내는 것에 대한 편집을 참조하십시오. –

+0

낭비 적이지만 간단하고 * 매우 * 강력합니다. 나는 아직도 그것을 투표. – bortzmeyer

0

기본 ...

[header] [data] [check-sum] 

에서 [데이터] 가장 중요한 부분이지만, [헤더] 및 [체크섬] 정말 이상한 실제 단어 문제를 해결하는 데 도움이됩니다. 어쨌든 작은 것은 항상 [머리글]과 [체크섬]으로 살아보십시오.

이제 [헤더]를 줄이면 [체크섬] 과부하가 큰 데이터 체인을 만들어 명확하게 도움이됩니다.

모든 데이터를 한 후, 일정한 길이의 경우 데이터를 읽고, 읽기 및 표시 호스트 PC에서 아무것도 수행하여 모든 형식을 사용하여 데이터 (디버깅 PC가 될 것이다 ..)

+0

헤더와 체크섬을 추가하는 것이 좋습니다. 그러나 나는 나의 질문과 관련이 없다. –

2

을 한 후 그들 사이에 분리 기호가 필요하지 않습니다. 따라서 바이너리 컨텐트를 직접 보낼 수 있습니다. I는 습기 센서 및 온도에 대한 1 개 바이트 표현 시간 두 바이트, 4 바이트 (플로트) 같은데

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

예를 들어 라인 : 같이

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

가 전송된다. 헤더를 보낼 필요가 없습니다.

각 행의 길이는 이제 일정하며 수신자는 변환 작업을 수행해야합니다. 임베디드 장치는이 형식으로 데이터를 쉽게 전송할 수 있습니다.

이제 메시지에 데이터를 캡슐화하여 수신자가 메시지가 언제 시작되는지 알 수 있도록하는 문제도 있습니다. 당신은 일반적으로 머리글과 바닥 글을 추가하여이 작업을 수행 할 :

(는 0x02 및 0x03으로 내가 생각하는) 일반적으로 ASCII 문자 STX와 ETX가 사용되는
[STX] message [ETX] 

. 문제는 이러한 값이 메시지 본문에 나타날 수도 있다는 것입니다. 따라서 전송에 다른 레이어를 추가해야합니다. 0x02 또는 0x03 바이트가 전송되면 두 번 전송하십시오. 수신기에서 단일 0x02 바이트는 메시지의 시작을 나타냅니다. 메시지 본문 내의 추가 0x02 및 0x03 바이트는 제거해야합니다.

마지막으로 통신 링크가 신뢰할 수없는 경우 체크섬을 추가해야합니다.

이러한 기술은 일반적으로 PPP와 같은 직렬 프로토콜에서 사용됩니다.

[header][data][checksum] 

을하지만 확장 할 경우 사용할 수 : 사람으로

+0

잊어 버린 작은 세부 사항이 있습니다. 나는 언제든지 헤더를 전송할 필요는 없지만 처음에는 테이블 번호를 보내야한다. (센서 테이블은 내가 할 수있는 유일한 것이 아니다.) 또한 헤더를 한 번 보내야한다. 클라이언트와 서버의 코드를 통해 "보내기"). 프로토콜은 테이블 레이아웃의 수정에 견고해야합니다. –

+0

그런 경우 "명령"과 "데이터"메시지가 모두 필요합니다. 메시지 유형을 나타 내기 위해 STX 뒤에 첫 x 째 Y이트를 할당하십시오. 그런 다음 메시지의 구조를 원하는대로 자유롭게 정의 할 수 있습니다. – kgiannakakis

+0

STX는 0x02이고 ETX는 0x04입니다. 아마 다를 수 있습니다. 직렬 통신은 최소 규제 형식이어야합니다. –

1

말했다

[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 바이트는 256 바이트에서 1.6 %이지만 40 바이트입니다. 그래서 크기가 적절합니다.

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" 
          "ghijklmnopqrstuvwxyz/"; 

// 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,  // '' 
    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; 
} 
관련 문제