c ++ stl: хороший способ разобрать ответ датчика - PullRequest
0 голосов
/ 30 января 2010

Внимание, пожалуйста:

Я уже реализовал этот материал, но ни в коем случае не универсальный и не элегантный. Этот вопрос мотивирован моим желанием узнать больше трюков с помощью stl, а не самой проблемой.

Я думаю, что это ясно из того, как я заявил, что уже решил проблему, но многие люди в своих лучших намерениях ответили решением проблемы , а не ответами на вопрос «Как решить этот путь». Мне очень жаль, если я сформулировал этот вопрос в замешательстве. Я ненавижу тратить время людей.

Хорошо, вот оно:

Я получаю строку, полную закодированных данных.

Он входит в: N >> 64 байта

  • каждые 3 байта декодируются в значение типа int
  • после того, как самое большее 64 байта (да, не делится на 3!) Приходит байт в качестве контрольной суммы
  • с последующим переводом строки.
  • и так продолжается.

Завершается, когда найдены 2 последовательных перевода строки.

Это выглядит как хороший или, по крайней мере, нормальный формат данных, но разбирает его элегантно stl - настоящий бит **.

Я сделал это "вручную".

Но мне было бы интересно, если бы существовал элегантный способ с использованием stl - или, возможно, буста - магии, который не включает в себя копирование вещи.

Разъяснение: Это становится действительно большим иногда. N >> 64 байта было больше похоже на N >>> 64 байта; -)

UPDATE Хорошо, N> 64 байта, кажется, сбивает с толку. Это не важно.

  • Датчик принимает M измерений в виде целых чисел. Кодирует каждый из них в 3 байта. и отправляет их один за другим
  • когда датчик отправил 64 байта данных, он вставляет контрольную сумму в 64 байта и LF. Это не волнует, если одно из закодированных целых чисел "разбито" этим. Это просто продолжается в следующей строке. (Это дает эффект только для того, чтобы сделать данные удобочитаемыми для человека, но довольно неприятными для элегантного анализа)
  • если он завершил отправку данных, он вставляет байт контрольной суммы и LFLF

Таким образом, один блок данных может выглядеть следующим образом, для N = 129 = 43x3:

|<--64byte-data-->|1byte checksum|LF 
|<--64byte-data-->|1byte checksum|LF 
|<--1byte-data-->|1byte checksum|LF
LF

Когда у меня M = 22 измерения, это означает, что у меня N = 66 байт данных. После 64 байт он вставляет контрольную сумму и LF и продолжает. Таким образом, это разбивает мое последнее измерение который закодирован в байтах 64, 65 и 66. Теперь это выглядит так: 64, контрольная сумма, LF, 65, 66. Поскольку кратное 3, деленное на 64, несет остаток 2 из 3 раз, и каждый раз еще один, паршиво разбирать. У меня было 2 решения:

  1. проверка контрольной суммы, объединение данных в одну строку, содержащую только байты данных, декодирование.
  2. пробежка с итераторами и одна неприятная конструкция if, чтобы избежать копирования.

Я просто подумал, что может быть лучше. Я размышлял о std :: transform, но он не сработает, потому что 3 байта - это одно целое.

Ответы [ 6 ]

2 голосов
/ 31 января 2010

Как бы мне ни нравился STL, я не думаю, что есть что-то неправильное в том, чтобы делать что-то вручную, особенно если проблема не относится к случаям, для которых был создан STL. Опять же, я не уверен, почему вы спрашиваете. Может быть, вам нужен итератор ввода STL, который (проверяет и) отбрасывает контрольные суммы и символы LF и выдает целые числа?

Я предполагаю, что кодировка такова, что LF может появляться только в тех местах, то есть в каком-то виде Base-64 или подобном?

1 голос
/ 01 февраля 2010

Как уже говорили другие, в stl / boost нет серебряной пули, чтобы элегантно решить вашу проблему. Если вы хотите проанализировать свой чанк напрямую с помощью арифметики указателей, возможно, вы можете черпать вдохновение из std :: iostream и скрыть беспорядочную арифметику указателей в пользовательском классе потока. Вот наполовину решение, которое я придумал:

#include <cctype>
#include <iostream>
#include <vector>
#include <boost/lexical_cast.hpp>

class Stream
{
public:
    enum StateFlags
    {
        goodbit = 0,
        eofbit  = 1 << 0,   // End of input packet
        failbit = 1 << 1    // Corrupt packet
    };

    Stream() : state_(failbit), csum_(0), pos_(0), end_(0) {}
    Stream(char* begin, char* end) {open(begin, end);}
    void open(char* begin, char* end)
        {state_=goodbit; csum_=0; pos_=begin, end_=end;}
    StateFlags rdstate() const {return static_cast<StateFlags>(state_);}
    bool good() const {return state_ == goodbit;}
    bool fail() const {return (state_ & failbit) != 0;}
    bool eof() const {return (state_ & eofbit) != 0;}
    Stream& read(int& measurement)
    {
        measurement = readDigit() * 100;
        measurement += readDigit() * 10;
        measurement += readDigit();
        return *this;
    }

private:
    int readDigit()
    {
        int digit = 0;

        // Check if we are at end of packet
        if (pos_ == end_) {state_ |= eofbit; return 0;}

        /* We should be at least csum|lf|lf away from end, and we are
            not expecting csum or lf here. */
        if (pos_+3 >= end_ || pos_[0] == '\n' || pos_[1] == '\n')
        {
            state_ |= failbit;
            return 0;
        }

        if (!getDigit(digit)) {return 0;}
        csum_ = (csum_ + digit) % 10;
        ++pos_;

        // If we are at checksum, check and consume it, along with linefeed
        if (pos_[1] == '\n')
        {
            int checksum = 0;
            if (!getDigit(checksum) || (checksum != csum_)) {state_ |= failbit;}
            csum_ = 0;
            pos_ += 2;

            // If there is a second linefeed, we are at end of packet
            if (*pos_ == '\n') {pos_ = end_;}
        }
        return digit;
    }

    bool getDigit(int& digit)
    {
        bool success = std::isdigit(*pos_);
        if (success)
            digit = boost::lexical_cast<int>(*pos_);
        else
            state_ |= failbit;
        return success;
    }

    int csum_;
    unsigned int state_;
    char* pos_;
    char* end_;
};


int main()
{
    // Use (8-byte + csum + LF) fragments for this example
    char data[] = "\
001002003\n\
300400502\n\
060070081\n\n";

    std::vector<int> measurements;
    Stream s(data, data + sizeof(data));
    int meas = 0;

    while (s.read(meas).good())
    {
        measurements.push_back(meas);
        std::cout << meas << " ";
    }

    return 0;
}

Возможно, вы захотите добавить дополнительные StateFlags, чтобы определить, произошел ли сбой из-за ошибки контрольной суммы или ошибки кадрирования. Надеюсь, это поможет.

1 голос
/ 30 января 2010

Мне кажется, что что-то простое, как следующее, должно решить проблему:

string line;
while( getline( input, line ) && line != "" ) {    
  int val = atoi( line.substr(0, 3 ).c_str() );
  string data = line.substr( 3, line.size() - 4 );
  char csum = line[ line.size() - 1 ];
  // process val, data and csum
}

В реальной реализации вы хотели бы добавить проверку ошибок, но базовая логика должна оставаться прежней.

0 голосов
/ 31 января 2010

Это не элегантное решение. Это было бы лучше, используя «матрицу переходов» и читая только один символ за раз. Не мой стиль. Тем не менее, этот код имеет минимум избыточного перемещения данных, и, похоже, он выполняет свою работу. Минимально C ++, это действительно просто C-программа. Добавление итераторов оставлено в качестве упражнения для читателя. Поток данных не был полностью определен, и не было определенного места назначения для преобразованных данных. Предположения отмечены в комментариях. Много печати должно показать функциональность.

// convert series of 3 ASCII decimal digits to binary
// there is a checksum byte at least once every 64 bytes - it can split a digit series
// if the interval is less than 64 bytes, it must be followd by LF (to identify it)
// if the interval is a full 64 bytes, the checksum may or may not be followed by LF
// checksum restricted to a simple sum modulo 10 to keep ASCII format
// checksum computations are only printed to allowed continuation of demo, and so results can be
// inserted back in data for testing
// there is no verification of the 3 byte sets of digits
// results are just printed, non-zero return indicates error


int readData(void) {
    int binValue =  0, digitNdx = 0,  sensorCnt = 0, lineCnt = 0;
    char oneDigit;
    string sensorTxt;

    while( getline( cin, sensorTxt ) ) {
        int i, restart = 0, checkSum = 0, size = sensorTxt.size()-1;
        if(size < 0)
            break;
        lineCnt++;
        if(sensorTxt[0] == '#')
            continue;
        printf("INPUT: %s\n", &sensorTxt[0]);           // gag

        while(restart<size) {
            for(i=0; i<min(64, size); i++) {
                oneDigit = sensorTxt[i+restart] & 0xF;
                checkSum += oneDigit;
                binValue = binValue*10 + oneDigit;
                //printf("%3d-%X ", binValue, sensorTxt[i+restart]);
                digitNdx++;
                if(digitNdx == 3) {
                    sensorCnt++;
                    printf("READING# %d (LINE %d) = %d  CKSUM %d\n",
                           sensorCnt, lineCnt, binValue, checkSum);
                    digitNdx = 0;
                    binValue = 0;
                }
            }
            oneDigit = sensorTxt[i+restart] & 0x0F;
            char compCheckDigit = (10-(checkSum%10)) % 10;
            printf("    CKSUM at sensorCnt %d  ", sensorCnt);
            if((checkSum+oneDigit) % 10)
                printf("ERR got %c exp %c\n", oneDigit|0x30, compCheckDigit|0x30);
            else
                printf("OK\n");
            i++;
            restart += i;
        }
    }
    if(digitNdx)
        return -2;
    else
        return 0;
}

Определение данных было дополнено комментариями, вы можете использовать следующее как

# normal 64 byte lines with 3 digit value split across lines
00100200300400500600700800901001101201301401501601701801902002105
22023024025026027028029030031032033034035036037038039040041042046
# short lines, partial values - remove checksum digit to combine short lines
30449
0451
0460479
0480490500510520530540550560570580590600610620630641
# long line with embedded checksums every 64 bytes
001002003004005006007008009010011012013014015016017018019020021052202302402502602702802903003103203303403503603703803904004104204630440450460470480490500510520530540550560570580590600610620630640
# dangling digit at end of file (with OK checksum)
37
0 голосов
/ 31 января 2010

Вы должны думать о своем протоколе связи как о многоуровневом. Лечить

|<--64byte-data-->|1byte checksum|LF

как фрагменты для повторной сборки в более крупные пакеты непрерывных данных. Как только пакет большего размера будет восстановлен, легче будет анализировать его данные непрерывно (вам не нужно иметь дело с измерениями, разбитыми по фрагментам). Многие существующие сетевые протоколы (такие как UDP / IP) выполняют подобную сборку фрагментов в пакеты.

Возможно считывать фрагменты непосредственно в их соответствующие "слоты" в буфере пакетов. Поскольку у ваших фрагментов вместо нижних колонтитулов есть нижние колонтитулы, а прибытие ваших фрагментов не в порядке очереди, это должно быть довольно легко закодировано (по сравнению с алгоритмами повторной сборки IP без копирования). Как только вы получаете «пустой» фрагмент (дубликат LF), это означает конец пакета.

Вот пример кода для иллюстрации идеи:

#include <vector>
#include <cassert>

class Reassembler
{
public:
    // Constructs reassembler with given packet buffer capacity
    Reassembler(int capacity) : buf_(capacity) {reset();}

    // Returns bytes remaining in packet buffer
    int remaining() const {return buf_.end() - pos_;}

    // Returns a pointer to where the next fragment should be read
    char* back() {return &*pos_;}

    // Advances the packet's position cursor for the next fragment
    void push(int size) {pos_ += size; if (size == 0) complete_ = true;}

    // Returns true if an empty fragment was pushed to indicate end of packet
    bool isComplete() const {return complete_;}

    // Resets the reassembler so it can process a new packet
    void reset() {pos_ = buf_.begin(); complete_ = false;}

    // Returns a pointer to the accumulated packet data
    char* data() {return &buf_[0];}

    // Returns the size in bytes of the accumulated packet data
    int size() const {return pos_ - buf_.begin();}

private:
    std::vector<char> buf_;
    std::vector<char>::iterator pos_;
    bool complete_;
};


int readFragment(char* dest, int maxBytes, char delimiter)
{
    // Read next fragment from source and save to dest pointer
    // Return number of bytes in fragment, except delimiter character
}

bool verifyChecksum(char* fragPtr, int size)
{
    // Returns true if fragment checksum is valid
}

void processPacket(char* data, int size)
{
    // Extract measurements which are now stored contiguously in packet
}

int main()
{
    const int kChecksumSize = 1;
    Reassembler reasm(1000); // Use realistic capacity here
    while (true)
    {
        while (!reasm.isComplete())
        {
            char* fragDest = reasm.back();
            int fragSize = readFragment(fragDest, reasm.remaining(), '\n');
            if (fragSize > 1)
                assert(verifyChecksum(fragDest, fragSize));
            reasm.push(fragSize - kChecksumSize);
        }
        processPacket(reasm.data(), reasm.size());
        reasm.reset();
    }
}

Хитрость заключается в создании эффективной функции readFragment, которая останавливается на каждом разделителе новой строки и сохраняет входящие данные в указанном указателе буфера назначения. Если вы расскажете мне, как вы собираете данные с вашего датчика, то я, возможно, смогу дать вам больше идей.

0 голосов
/ 30 января 2010

Почему вы занимаетесь копированием? Это время или пространство?

Похоже, что вы читаете все неразобранные данные в большой буфер, и теперь вы хотите написать код, который делает большой буфер неразобранных данных похожим на немного меньший буфер разобранных данных (данные за вычетом контрольных сумм и linefeeds), чтобы избежать нехватки места при копировании в немного меньший буфер.

Добавление сложного уровня абстракции не поможет с временными затратами, если вам не нужна только небольшая часть данных. И если это так, возможно, вы могли бы просто выяснить, какая небольшая часть вам нужна, и скопировать это. (Опять же, большая часть уровня абстракции может быть уже написана для вас, например, Boost iterator library .)

Более простой способ уменьшить затраты пространства - это читать данные небольшими порциями (или строкой за раз) и анализировать их по мере необходимости. Тогда вам нужно только сохранить разобранную версию в большом буфере. (Предполагается, что вы читаете его из файла / сокета / порта, а не передаете большой буфер, который не находится под вашим контролем.)

Еще один способ уменьшить затраты пространства - перезаписать данные на месте при их разборе. Вы будете нести расходы на копирование, но вам понадобится только один буфер. (Это предполагает, что 64-байтовые данные не растут при разборе, т. Е. Они не сжаты.)

...