;; each sentence is introduced by $ and terminated by * ;; first two chars are "talker id" ;; 3-5 are the message type ;; fields are comma-delimited ;; we can parse the message based only on type (i.e. the same type is ;; always parsed the same way no matter who the talker) ;; outline: split message into lines, extract values between ;; $ and * as talker, type, fields ;; then call the parser for the type (import-macros { : define-tests : expect : expect= } :assert) (fn split-line [line] (let [(talker msgtype rest) (string.match line "$(..)(...)(.+)*") fields (icollect [k _ (string.gmatch rest ",([^,]*)")] k)] { : talker :message-type msgtype :fields fields })) (expect= (split-line "$GLGSV,2,1,07,78,47,295,34,77,35,208,19,88,36,267,30,87,67,025,27,1*74\n") { :talker "GL" :message-type "GSV" :fields ["2" "1" "07" "78" "47" "295" "34" "77" "35" "208" "19" "88" "36" "267" "30" "87" "67" "025" "27" "1" ] } ) (expect= (split-line "$GNGNS,111134.00,5131.348976,N,00005.551003,W,AAAANN,19,0.7,20.3,47.0,,,V*0C\n") { :talker "GN" :message-type "GNS" :fields ["111134.00" ; utc "5131.348976" "N" "00005.551003" "W" "AAAANN" "19" ; number of satellites "0.7" ; hdop "20.3" ; height "47.0" ; geoidal separation "" "" ; differential data age, reference station "V" ; not valid for navigation (?) ] } ) (fn try-number [s] (if (= s "") nil (tonumber s))) (fn parse-coordinate [value sign] (if (= value "") nil (let [(deg min) (string.match value "(..)(.+)")] (* (+ (tonumber deg) (/ (tonumber min) 60)) (case sign :N 1 :E 1 :S -1 :W -1))))) (local msg-types { :GGA (fn [fields] (let [[utc lat latsign lon lonsign fix-quality total-space-vehicles hdop altitude _ geoidal-separation _ _ _ ] fields ] { :lat (parse-coordinate lat latsign) :lon (parse-coordinate lon lonsign) : fix-quality :total-space-vehicles (try-number total-space-vehicles) :hdop (try-number hdop) :altitude (try-number altitude) :geoidal-separation (try-number geoidal-separation) })) :RMC (fn [[utc valid lat latsign lon lonsign knots bearing-true date magn-var magn-var-sign mode nav-status &as fields]] (let [magnetic-variation (if (= magn-var "") nil (* (tonumber magn-var) (case magn-var-sign :E 1 :W -1)))] { : utc : valid :lat (parse-coordinate lat latsign) :lon (parse-coordinate lon lonsign) :speed-knots (try-number knots) :bearing-true (try-number bearing-true) : date : magnetic-variation ;;; XXX probably wrong : mode })) :GNS (fn [fields] (let [[utc lat latsign lon lonsign mode total-space-vehicles hdop altitude geoidal-separation _ _ nav-status] fields] { :lat (parse-coordinate lat latsign) :lon (parse-coordinate lon lonsign) : mode :total-space-vehicles (try-number total-space-vehicles) :hdop (try-number hdop) :altitude (try-number altitude) :geoidal-separation (try-number geoidal-separation) : nav-status } )) :VTG (fn [[bearing-true _ bearing-mag _ knots _ kmh _ mode]] { :bearing-true (try-number bearing-true) :bearing-magnetic (try-number bearing-mag) :speed-knots (try-number knots) :speed-kmh (try-number kmh) : mode } ) }) (fn parse-line [line] (let [{ : message-type : talker : fields &as split} (split-line line) parser (. msg-types message-type)] (if parser (doto (parser fields) (tset :message-type message-type) (tset :talker talker)) split))) ;; XXX this is wrong. 5131.348976 is 51 degrees 31.348976 minutes (expect= (parse-line "$GNGNS,111134.00,5131.348976,N,00005.551003,W,AAAANN,19,0.7,20.3,47.0,,,V*0C\n") {:altitude 20.3 :geoidal-separation 47 :hdop 0.7 :lat 51.522482933333 :lon -0.092516716666667 :message-type "GNS" :mode "AAAANN" :nav-status "V" :talker "GN" :total-space-vehicles 19}) (expect= (parse-line "$GNVTG,263.3,T,266.5,M,1.6,N,2.9,K,A*32\n") { :talker :GN :message-type :VTG :bearing-true 263.3 :bearing-magnetic 266.5 :speed-knots 1.6 :speed-kmh 2.9 :mode :A }) { :parse parse-line }