Freitag, 28. Oktober 2011

Berechnen der NMEA Checksumme / Computing the NMEA checksum

Die National Marine Electronics Association (NMEA) zeichnet u.a. für den digitalen Datenaustausch bei GPS Geräten verantwortlich. Egal ob wir ein solches Gerät auf einem Schiff, im Auto, am Fahrrad oder beim Wandern benutzen. Die zum Einsatz kommenden Datensätze entspringen (mit kleinen individuellen Herstellervarianten) den NMEA Definitionen.

Da jedwede Art der Datenübertragung qualitativen Schwankungen des Übertragungsmediums unterliegt, beinhaltet die Definition der Datensätze eine Checksumme die zur Verifikation des korrekten Empfanges herangezogen wird.

Der u.a. Beispielcode zeigt, wie einfach die Überprüfung der Checksumme eines solchen Datensatzes in VFP realisiert werden kann.

INFO: Der als Basis dienende Datenbereich des jeweiligen Datensatzes beginnt grundsätzlich hinter dem $ Zeichen und endet am * dem sich dann die Checksumme des Datenbereichs anschliesst.

$GPXTE,A,A,0.67,L,N*6F

* // NMEA Checksumme berechnen
CLEAR 

* // Quelle der Musterstrings: http://www.gpsinformation.org/dale/nmea.htm
LOCAL laString( 1 , 19 )
laString(  1 ) = [$GPGGA,162045,5058.809,N,00647.103,E,1,03,2.5,63.1,M,47.2,M,,*79]
laString(  2 ) = [$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39]
laString(  3 ) = [$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75]
laString(  4 ) = [$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A]
laString(  5 ) = [$GPGLL,4916.45,N,12311.12,W,225444,A,*1D]
laString(  6 ) = [$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48]
laString(  7 ) = [$GPWPL,4807.038,N,01131.000,E,WPTNME*5C]
laString(  8 ) = [$GPAAM,A,A,0.10,N,WPTNME*32]
laString(  9 ) = [$GPAPB,A,A,0.10,R,N,V,V,011,M,DEST,011,M,011,M*3C]
laString( 10 ) = [$GPBOD,045.,T,023.,M,DEST,START*01]
laString( 11 ) = [$GPBWC,225444,4917.24,N,12309.57,W,051.9,T,031.6,M,001.3,N,004*29]
laString( 12 ) = [$GPRMB,A,0.66,L,003,004,4917.24,N,12309.57,W,001.3,052.5,000.5,V*20]
laString( 13 ) = [$GPRTE,2,1,c,0,W3IWI,DRIVWY,32CEDR,32-29,32BKLD,32-I95,32-US1,BW-32,BW-198*69]
laString( 14 ) = [$GPXTE,A,A,0.67,L,N*6F]
laString( 15 ) = [$HCHDG,101.1,,,7.1,W*3C]
laString( 16 ) = [$GPZDA,201530.00,04,07,2002,00,00*60]
laString( 17 ) = [$GPMSK,318.0,A,100,M,2*45]
laString( 18 ) = [$GPMSS,55,27,318.0,100,*66]
laString( 19 ) = [$PGRME,15.0,M,45.0,M,25.0,M*1C]
FOR liflag = 1 TO ALEN( laString )
    ? laString( liFlag )
    ? GetNMEACheckSum( laString( liFlag ) )
ENDFOR

FUNCTION GetNMEACheckSum as String
LPARAMETERS vNMEAString as String

    LOCAL lcString as String, liCheck as Integer, i as Integer

    IF    AT( [$] , vNMEAString ) > 0 ;
    AND    AT( [*] , vNMEAString ) > 0
        lcString    = STREXTRACT( vNMEAString , [$] , [*] , 1 )
    ELSE 
        lcString = vNMEAString
    ENDIF 
    STORE 0 TO liCheck , i

    FOR i = 1 TO LEN( lcString )

        liCheck = BITXOR( liCheck , ASC( SUBSTR( lcString , i , 1 ) ) )

    ENDFOR 

    RETURN RIGHT(TRANSFORM( liCheck , [@0] ) , 2 )
    
ENDFUNC 

Die Generierung der Prüfziffer beruht auf einer XOR Verknüpfung des aktuellen XOR Ergebnisses mit dem nächsten Byte des Datenbereichs. Die erste Verknüpfung erfolgt auf Basis des Wertes 0 mit dem 1. Byte.
Das Endergebnis wird ins Hexadezimalformat gewandelt wodurch eine Wertigkeit von 0-255 (00-FF) gewährleistet ist.

Was ist nun dieses 'XOR'?

XOR Vergleiche sind die einfachste Methode, auf Bits basierende Prüfungen zu realisieren. Mit Hilfe eines Prüfbits kann der Status eines als Grundlage herangezogenen Bits jederzeit korrigiert werden.
Die Idee dahinter ist, wenn zwei Werte (Bits) miteinander verglichen werden, dann sind sie entweder gleich (Prüfbit = 0) oder ungleich (Prüfbit = 1). Aufgrund dieser Logik kann nun eines der beiden Bits auf Basis des anderen sowie des Prüfbits wieder hergestellt werden.

Die entsprechende Funktion innerhalb von Visual Foxpro lautet BITXOR()

Die verwendeten Musterdatensätze stammen von hier:
http://www.gpsinformation.org/dale/nmea.htm

Montag, 17. Oktober 2011

Versionskontrolle kann Spaß machen / Version Control can be fun

Nein, das ist kein Witz, Versionskontrolle kann wirklich Spaß machen, zumindest wenn man weiß wofür sie gut ist und ... wenn man das neue Buch von Erik Sink gelesen hat ... :-)

Anmerkung:
Für diejenigen denen dieser Name nichts sagt sei ein Besuch der SourceGear Homepage angeraten.

Ich habe mir mittlerweile diverse Videos von Erik Sink im Web angesehen und ohne Zweifel hat er eine Menge Humor und weiß diesen auch geschickt bei seinen Präsentationen einzusetzen. Um so angenehmer war es, dass er dies auch in seinem Buch 'Version Control by Example' schafft.

Sein Buch kann sowohl bei den bekannten Onlinestores gekauft als auch kostenlos online gelesen werden. Besitzer von eBooks können es auch direkt im epub Format herunterladen.

Viel Spaß beim schmökern! :-)


Freitag, 7. Oktober 2011

Stringinvertierung - andersherum / inverting strings - the other way around

Beim durchstöbern meiner Codeschnipsel stolperte ich eben über eine andere Sichtweise der Stringinvertierung. Bei dieser geht es um einen einfachen Positionstausch innerhalb eines übergebenen Strings.

Mit anderen Worten: aus
 MeineInvertierteZeichenkette
wird
etteknehcieZetreitrevnIenieM
Das folgende Codemuster zeigt drei mögliche Wege auf, dies zu erreichen...

* // Funktionstest 
CLEAR 
liS=SECONDS()
?RevertString1( [etteknehcieZetreitrevnIenieM] )
?SECONDS() - liS

liS=SECONDS()
?RevertString2( [etteknehcieZetreitrevnIenieM] )
?SECONDS() - liS

liS=SECONDS()
?RevertString3( [etteknehcieZetreitrevnIenieM] )
?SECONDS() - liS

* Invertieren mit VFP Bordmitteln                        
FUNCTION RevertString1
LPARAMETERS vString AS String
    LOCAL liZaehler AS Integer, lcReturn AS String
    liZaehler    = 0
    lcReturn    = []
    FOR liZaehler = LEN ( vString ) TO 1 STEP -1
        lcReturn = lcReturn + SUBSTR ( vString , liZaehler , 1 )
    ENDFOR
    RETURN lcReturn
ENDFUNC

* Invertieren über die MS C++ Laufzeitbibliothek        
FUNCTION RevertString2
LPARAMETERS vString as String
    DECLARE STRING _strrev IN msvcrt20.dll STRING @
    m.vString = m.vString + CHR( 0 )
    RETURN _strrev( @m.vString )
ENDFUNC 

* Invertieren mit Bordmitteln und optimierter Schleife    
FUNCTION RevertString3
LPARAMETERS vString AS String
    LOCAL lcReturn as String
    lcReturn = SPACE( LEN( vString ) )
    FOR liZaehler = 1 TO LEN( vString ) / 2
        lcReturn = STUFF( lcReturn , liZaehler , 1 , SUBSTR( vString , LEN( vString ) + 1 - liZaehler , 1) )
        lcReturn = STUFF( lcReturn , LEN( vString ) + 1 - liZaehler , 1 , SUBSTR( vString , liZaehler , 1) )
    ENDFOR 
    RETURN lcReturn
ENDFUNC 

Bei kleinen Strings ist der zeitliche Unterschied marginal. Bei größeren Textmengen sieht es aber ganz anders aus und die Schere zu Variante 1 geht immer weiter auseinander.

Mittwoch, 5. Oktober 2011

Stringinvertierung - segmentiert und komplett / inverting strings - segmented and complete

Im Microsofts deutschem Foxpro Forum kam vor einigen Wochen die Frage wie ein String von A nach Z, B nach Y, C nach X usw. invertiert werden kann.

Im folgenden Codebeispiel steht wahlweise eine segmentierte Invertierung (nur für Buchstaben und Zahlen) sowie eine komplette Invertierung (sämtlich Zeichen der ASCII Tabelle = 0-255) zur Verfügung.

Vor der eigentlichen Funktion findet sich wie immer ein Funktionstest. Also einfach den Code markieren und über die Zwischenablage in ein PRG kopieren... ;-)


* // Stringinhalt unterscheidet sich von der Anzeige    
* // da nicht darstellbare Zeichen enthalten sein können
* // string content may differ from displayed value due 
* // to non-displayable chars                            

* // test - start --------------------------------
CLEAR 
lcTest = [Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz]
* // Original
?lcTest
* // segmentiert / segemented
?InvertString( lcTest )
* // komplett / complete
?InvertString( lcTest , 1 )
?
lcTest = [0 1 2 3 4 5 6 7 8 9]
* // Original
?lcTest
* // segmentiert / segemented
?InvertString( lcTest )
* // komplett / complete
?InvertString( lcTest , 1 )
* // test - ende ---------------------------------



FUNCTION InvertString as String
LPARAMETERS vString as String, vVersion as Integer
    * // Param #1:    string to invert            
    * // Param #2:    0|1 (optional)                
    * //            0 or ommit = sequenced ascii
    * //             1 = complete ascii range    
    LOCAL i as Integer, liAsc as Integer, lcReplace as String    
    vVersion = EVL( vVersion , 0 )
    FOR i = 1 TO LEN( vString )
        liAsc = ASC( SUBSTR( vString , i , 1 ) )
        DO CASE 
        * // complete ASCII    
        CASE vVersion = 1
            lcReplace = CHR( BITNOT( liAsc ) + 256 )
        * // A - Z            
        CASE BETWEEN( liAsc , 65 ,  90 )
            lcReplace = CHR( ABS( 25 - ( liAsc - 65 ) ) + 65 )
        * // a - z            
        CASE BETWEEN( liAsc , 97 , 122 )
            lcReplace = CHR( ABS( 25 - ( liAsc - 97 ) ) + 97 )
        * // 0 - 9            
        CASE BETWEEN( liAsc , 48 , 57 )
            lcReplace = CHR( ABS(  9 - ( liAsc - 48 ) ) + 48 )
        * // anything else    
        OTHERWISE 
            lcReplace = SUBSTR( vString , i , 1 )
        ENDCASE 
        vString = STUFF( vString , i , 1 , lcReplace )
    ENDFOR
    RETURN vString
ENDFUNC