biscuit/pkgs/maps/nmea.fnl

175 lines
4.5 KiB
Fennel

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