2010-01-30 4 views
0

Attention s'il vous plaît:C++ stl: une bonne façon d'analyser une réponse du capteur

Je l'ai déjà mis en œuvre ce genre de choses, mais pas d'une façon générique ou élégante. Cette question est motivée par mon désir d'apprendre plus de trucs avec le stl, pas le problème lui-même.

Ce que je pense est clair dans la façon dont je l'ai dit que je l'ai déjà résolu le problème, mais beaucoup de gens ont répondu dans leurs meilleures intentions avec des solutions au problème , pas de réponses à la question « comment résoudre ce la façon stl ". Je suis vraiment désolé si j'ai formulé cette question d'une manière confuse. Je déteste perdre le temps des gens.

Ok, le voici:

je reçois une chaîne complète de données codées.

Il est disponible en: N >> 64 octets

  • tous les 3 octets se décodées en une valeur int
  • après au plus 64 octets (! Oui, non divisible par 3) vient un octet comme Somme de contrôle
  • suivi d'un saut de ligne.
  • et ainsi de suite.

Il se termine lorsque 2 sauts de ligne successifs sont trouvés.

Il ressemble à un format de données agréable ou au moins ok, mais en l'analysant avec élégance la façon stl est un peu **.

J'ai fait la chose "manuellement".

Mais je serais intéressé s'il y a une manière élégante avec la stl- ou peut-être la magie de boost que n'incorpore pas la copie la chose.

Clarification: Il devient parfois très gros. Le N >> 64 octets était plus comme un N de 64 octets ;-)

MISE À JOUR Ok, N> 64 octets semble être source de confusion. Ce n'est pas important.

  • Le capteur prend des mesures M comme nombres entiers. Encode chacun d'eux en 3 octets. et les envoie l'un après l'autre
  • lorsque le capteur a envoyé 64 octets de données, il insère une somme de contrôle sur le 64 octet et un LF. Il ne se soucie pas si l'un des entiers codés est "cassé" par cela. Cela continue juste dans la ligne suivante. (Cela a seulement pour effet de rendre les données joliment lisibles par l'homme, mais plutôt désagréables à analyser élégamment.)
  • si elle a terminé l'envoi de données insère une somme de contrôle octets et LFLF

donc un bloc de données peut ressembler à ceci, N = 129 = 43x3:

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

Quand j'ai M = 22 mesures, cela signifie que j'ai N = 66 octets de données. Après 64 octets, il insère la somme de contrôle et LF et continue. De cette façon, il rompt ma dernière mesure qui est codée dans les octets 64, 65 et 66. Il ressemble maintenant à ceci: 64, somme de contrôle, LF, 65, 66. Puisqu'un multiple de 3 divisé par 64 porte un résidu 2 sur 3 fois, et à chaque fois, il est méchant d'analyser. Je avait 2 solutions:

  1. contrôle de vérification, les données concaténer une chaîne qui ne dispose que d'octets de données, decode.
  2. traversée par des itérateurs et une construction si mauvaise pour éviter la copie.

Je pensais juste qu'il pourrait y avoir quelque chose de mieux. Je pensais à propos de std :: transform, mais ça ne marcherait pas car le 3 octets est une chose int.

+0

Ceci n'est toujours pas clair. a) Quelle est la taille maximale de l'ensemble des données, en octets? b) est la taille maximale des données dans chaque enregistrement 64 octets comme vous le dites ici, ou vraiment grand, comme l'indique votre commentaire à ma réponse - si ce dernier, quelle taille? –

+0

Vos données de mesure et votre somme de contrôle sont-elles binaires ou ascii? Si les données de mesure sont binaires, quelle est leur endianness? –

+0

Comment recevez-vous vos données de capteur? Un port série? Une prise réseau? Un fichier? Êtes-vous obligé de lire tout le bloc de données du capteur en une fois? –

Répondre

1

Il me semble que quelque chose d'aussi simple que ce qui suit devrait résoudre le problème:

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 
} 

Dans une implémentation réelle que vous voulez ajouter une vérification d'erreur, mais la logique de base devrait rester la même.

+0

euh, Neil, vous copiez la chaîne. chaîne de données = line.substr .... Je ne veux pas être nuls, mais c'est vraiment une grosse chaîne dans certains cas. – AndreasT

+0

@ AndreasT Quelle est la taille? Et combien de mémoire avez-vous? Si vous analysez, vous devez généralement faire des copies. Ou vous pouvez utiliser des itérateurs. Vous devez rendre votre question plus explicite - par exemple, que veut voir le consommateur des données analysées? –

+0

@ AndreasT Et dans votre question initiale, vous avez dit "après au plus 64 octets", donc la copie à l'endroit que vous avez pointé dans votre commentaire ne sera pas significative. –

0

Pourquoi êtes-vous concerné par la copie? Est-ce le temps de travail ou les frais généraux de l'espace? Il semble que vous lisez toutes les données non analysées dans un grand tampon, et maintenant vous voulez écrire du code qui fait que le gros tampon de données non analysées ressemble à un tampon légèrement plus petit de données analysées (les données moins les totaux de contrôle) et linefeeds), pour éviter le surcoût de l'espace impliqué dans la copie dans le tampon légèrement plus petit. L'ajout d'une couche d'abstraction compliquée ne va pas aider avec le temps supplémentaire à moins que vous n'ayez besoin que d'une petite partie des données.

Et si c'est le cas, peut-être pourriez-vous trouver la petite portion dont vous avez besoin et la copier. (Encore une fois, la plus grande partie de la couche d'abstraction peut déjà être écrite pour vous, par exemple le Boost iterator library.)

Un moyen plus simple de réduire l'espace disponible est de lire les données en plus petits morceaux (ou une ligne à la fois), et l'analyser comme vous allez. Ensuite, vous avez seulement besoin de stocker la version analysée dans un grand tampon. (Cela suppose que vous le lisez à partir d'un fichier/socket/port, plutôt que de passer un tampon important qui n'est pas sous votre contrôle.)

Une autre façon de réduire l'espace disque est d'écraser les données en place vous l'analysez. Vous encourrez le coût de la copie, mais vous n'aurez besoin que d'un tampon. (Cela suppose que les données de 64 octets ne grossissent pas lorsque vous l'analysez, c'est-à-dire qu'elles ne sont pas compressées.)

+0

Je suis préoccupé par la copie parce que c'est la seule chose qui aura un impact sur les performances. Cela fonctionne sur un robot et les ressources sont généralement rares. Le reste est juste un calcul simple qui ne peut être évité dans tous les cas. Les données viennent en gros morceaux comme je l'ai dit dans la question. Merci pour vos réflexions, mais vraiment, la question concerne une solution stl, aussi générique et rapide que possible. Rien d'autre. J'ai déjà implémenté ce genre de choses, pas générique ni élégant. Cette question est motivée par mon désir d'apprendre plus de trucs avec le stl, pas le problème lui-même. – AndreasT

+1

@AndreasT, merci de clarifier cela. Ce n'est pas purement STL, mais avez-vous regardé dans la bibliothèque Boost itérateur? Il dispose d'adaptateurs itératifs pour filtrer et transformer les données au niveau de l'itérateur: http://www.boost.org/doc/libs/1_41_0/libs/iterator/doc/index.html – bk1e

2

Autant que j'aime STL, je ne pense pas qu'il y ait quelque chose de mal à faire les choses manuellement, surtout si le problème ne tombe pas vraiment dans les cas, le STL a été fait pour. Là encore, je ne sais pas pourquoi vous demandez. Peut-être avez-vous besoin d'un itérateur d'entrée STL qui vérifie et supprime les sommes de contrôle et les caractères LF et qui émet les entiers?

Je suppose que le codage est tel que LF peut seulement apparaître à ces endroits, c'est-à-dire, une sorte de Base-64 ou similaire?

+0

Pourquoi je demande: je suis venu à aimer le la simplicité et la généralité des solutions stl une fois que vous avez dépassé la hausse initiale de la courbe d'apprentissage. J'espérais simplement qu'il existait une combinaison algorithme/itérateur/foncteur capable de faire face à ce format de données. Mais comme vous le dites à juste titre, cela semble être une situation pour laquelle le stl n'a pas été créé. – AndreasT

+0

À part ça, je pense que l'utilisation d'un itérateur personnalisé pourrait bien être la réponse qui correspond le mieux à la question. (+1) Cela ajoute plutôt plus de complexité qu'elle n'en résout, puisque la logique dans l'itérateur est presque la même que celle que j'utilise pour la solution "manuelle" + la surcharge de la déclaration de l'itérateur. Il ne fournit pas vraiment d'avantages «diviser pour mieux régner», mais il répond à la plupart des exigences. Je vous remercie. – AndreasT

0

Vous devriez considérer votre protocole de communication comme étant en couches. Traiter

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

comme des fragments à réassembler en paquets plus grands de données contiguës. Une fois le plus grand paquet reconstitué, il est plus facile d'analyser ses données de manière contiguë (vous n'avez pas à vous occuper de la division des mesures entre les fragments). Beaucoup de protocoles réseau existants (tels que UDP/IP) font ce genre de réassemblage de fragments en paquets.

Il est possible de lire les fragments directement dans leur "emplacement" dans le tampon de paquets. Puisque vos fragments ont des pieds de page au lieu d'en-têtes, et qu'il n'y a pas d'arrivée de vos fragments dans le désordre, cela devrait être assez facile à coder (par rapport aux algorithmes de réassemblage IP sans copulants). Une fois que vous recevez un fragment "vide" (la doublon LF), cela marque la fin du paquet.

Voici quelques exemples de code pour illustrer l'idée:

#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(); 
    } 
} 

L'astuce fera une fonction readFragment efficace qui arrête à chaque delimiter nouvelle ligne et stocke les données entrantes dans le pointeur de la mémoire tampon de destination donnée. Si vous me dites comment vous acquérez vos données de capteur, alors je peux peut-être vous donner plus d'idées.

+0

J'avais aussi pensé à ça. C'est essentiellement ce que je voulais dire avec la solution un. – AndreasT

+0

Ah, désolé pour ce Andreas. Je vais laisser ma réponse ici pour que les autres puissent rire ... regarder pour trouver l'inspiration. –

0

Une solution élégante ce n'est pas. Ce serait plus en utilisant une «matrice de transition», et en lisant seulement un caractère à la fois. Pas mon style. Pourtant, ce code a un minimum de mouvements de données redondants, et il semble faire le travail. Au minimum C++, c'est vraiment juste un programme en C. L'ajout d'itérateurs est laissé comme un exercice pour le lecteur. Le flux de données n'était pas complètement défini et il n'y avait pas de destination définie pour les données converties. Hypothèses notées dans les commentaires. Beaucoup d'impression devrait montrer la fonctionnalité.

// 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; 
} 

La définition des données a été étendue avec des commentaires, vous vous pouvez utiliser ce qui suit comme est:

# 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

<: | Je suis vraiment désolé pour l'effort que vous avez fait. Mais cette solution n'est ni particulièrement stable ni évite la copie. J'ai mentionné que le problème en général est résolu. Je voulais juste savoir s'il y a une combinaison intelligente d'algorithmes, d'itérateurs, de flux et d'un foncteur pour le faire avec élégance. – AndreasT

1

Comme d'autres l'ont dit, il n'y a pas de solution miracle dans stl/coup de pouce pour résoudre élégamment votre problème . Si vous voulez analyser votre morceau directement via l'arithmétique du pointeur, vous pouvez peut-être vous inspirer de std :: iostream et masquer l'arithmétique du pointeur désordonné dans une classe de flux personnalisée. Voici une demi-arsed solution je suis venu avec:

#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; 
} 

Peut-être que vous aurez envie d'ajouter StateFlags supplémentaires pour déterminer si l'échec est dû à une erreur somme de contrôle ou d'une erreur de cadrage. J'espère que cela t'aides.

Questions connexes