odid-0.1.0.0: lib/Data/ODID.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Open Drone ID
module Data.ODID
( Msg(..), MsgHdr(..), MsgType(..), MsgBdy(..)
, IDType(..), UASID, UAType(..)
, SysMsg(..), ClassType(..), ClassCat(..), OpLocSrc(..)
, AuthMsg(..), AuthType(..)
, LocMsg(..), OpStatus(..), HorizAcc(..), VertAcc(..), SpeedAcc(..)
, HeightType(..)
) where
import Control.Monad
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import Data.Bits
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy.Char8 as BSC
import Data.Function
import Data.Int
import Foreign
import Numeric
import Prettyprinter
data Msg = Msg{msgHdr :: MsgHdr, msgBdy :: MsgBdy}
deriving (Eq, Read, Show)
instance Binary Msg where
get = get >>= \hdr -> Msg hdr <$> case msgType hdr of
BasicIDTy -> get >>= \b -> BasicIDBdy (getIDType $ b `shiftR` 4)
(toEnum $ fromIntegral $ b .&. 0xF) <$> getLazyByteString 20 <*> getLazyByteString 3
Location -> LocBdy <$> get
Auth -> AuthBdy <$> get
SelfIDTy -> SelfIDBdy <$> get <*> getLazyByteString 23
System -> SysBdy <$> get
OperatorID -> OpIDBdy <$> get <*> getLazyByteString 20 <*> getLazyByteString 3
Pack -> get >>= \sz -> get >>= \nm ->
PackBdy sz nm <$> replicateM (fromIntegral nm) get
RsvdTy _ -> RsvdBdy <$> getLazyByteString 24
where
getIDType t = case t of
0 -> IDTypeNone
1 -> SerialNum
2 -> CAARegID
3 -> UTMUUID
4 -> SpecificSessionID
n -> IDTypeRsvd n
put (Msg hdr bdy) = put hdr <> case bdy of
BasicIDBdy t ua uasid rsvd -> do
let ty = case t of
IDTypeNone -> 0
SerialNum -> 1
CAARegID -> 2
UTMUUID -> 3
SpecificSessionID -> 4
IDTypeRsvd r -> r
putWord8 $ ty `shiftL` 4 .|. fromIntegral (fromEnum ua)
putLazyByteString $ uasid <> rsvd
LocBdy l -> put l
AuthBdy a -> put a
SelfIDBdy ty desc -> put ty <> putLazyByteString desc
SysBdy s -> put s
OpIDBdy t i r -> put t <> putLazyByteString (i <> r)
PackBdy sz nm ms -> put sz <> put nm <> foldMap put ms
RsvdBdy bs -> putLazyByteString bs
instance Pretty Msg where
pretty (Msg hdr bdy) = vsep [pretty hdr, pretty bdy]
-- | Unmanned aircraft
data UAType = None | Aeroplane | Heli | Gyro | Hybrid | Ornith | Glider | Kite
| FreeBalloon | CaptiveBalloon | Airship | Parachute | Rocket
| TetheredPwrAircraft | GroundObstacle | Other
deriving (Bounded, Eq, Enum, Read, Show)
instance Pretty UAType where
pretty ua = case ua of
None -> "None"
Aeroplane -> "Aeroplane"
Heli -> "Helicopter"
Gyro -> "Gyroplane"
Hybrid -> "Hybrid Lift"
Ornith -> "Ornithopter"
Glider -> "Glider"
Kite -> "Kite"
FreeBalloon -> "Free Balloon"
CaptiveBalloon -> "Captive Balloon"
Airship -> "Airship"
Parachute -> "Parachute"
Rocket -> "Rocket"
TetheredPwrAircraft -> "Tethered Powered Aircraft"
GroundObstacle -> "Ground Obstacle"
Other -> "Other"
-- | Operational status
data OpStatus = Undeclared | Ground | Airborne | Emergency
| RemoteIDSystemFailure | OpStatusRsvd Word8
deriving (Eq, Read, Show)
instance Pretty OpStatus where
pretty s = case s of
RemoteIDSystemFailure -> "Remote ID System Failure"
OpStatusRsvd r -> "Reserved" <+> pretty r
_ -> viaShow s
data MsgType = BasicIDTy | Location | Auth | SelfIDTy | System | OperatorID
| Pack | RsvdTy Word8
deriving (Eq, Read, Show)
instance Pretty MsgType where
pretty t = case t of
BasicIDTy -> "Basic ID"
SelfIDTy -> "Self ID"
OperatorID -> "Operator ID"
RsvdTy r -> "Reserved" <+> pretty r
_ -> viaShow t
data MsgHdr = MsgHdr{msgVer :: Word8, msgType :: MsgType}
deriving (Eq, Read, Show)
instance Binary MsgHdr where
get = readHdr <$> get
where
readHdr b = MsgHdr (b .&. 0xF) $ case b `shiftR` 4 of
0x0 -> BasicIDTy
0x1 -> Location
0x2 -> Auth
0x3 -> SelfIDTy
0x4 -> System
0x5 -> OperatorID
0xF -> Pack
n -> RsvdTy n
put (MsgHdr v t) = putWord8 $ tNyb `shiftL` 4 .|. v
where
tNyb = case t of
BasicIDTy -> 0x0
Location -> 0x1
Auth -> 0x2
SelfIDTy -> 0x3
System -> 0x4
OperatorID -> 0x5
Pack -> 0xF
RsvdTy r -> r
instance Pretty MsgHdr where
pretty (MsgHdr v t) = "v" <> pretty v <+> pretty t
data MsgBdy = BasicIDBdy IDType UAType UASID ByteString | LocBdy LocMsg | AuthBdy AuthMsg
| SelfIDBdy Word8 ByteString | SysBdy SysMsg | OpIDBdy Word8 ByteString ByteString
| PackBdy Word8 Word8 [Msg] | RsvdBdy ByteString
deriving (Eq, Read, Show)
instance Pretty MsgBdy where
pretty m = case m of
BasicIDBdy idTy uaTy uasid rsvd -> vsep ["ID:" <+> pretty idTy
, "UA:" <+> pretty uaTy, "UASID:" <+> pretty (BSC.unpack uasid)
, "RSVD:" <+> prettyBytes rsvd]
LocBdy l -> pretty l
AuthBdy a -> pretty a
SelfIDBdy ty desc -> vsep ["Type:" <+> pretty ty, "Desc:" <+> pretty (BSC.unpack desc)]
SysBdy s -> pretty s
OpIDBdy t i r -> vsep ["Type:" <+> pretty t, "ID:" <+> pretty (BSC.unpack i)
, "Rsvd:" <+> pretty (BSC.unpack r)]
PackBdy sz nm ms -> vsep ["Size=" <> pretty sz <+> "Cnt=" <> pretty nm
, indent 2 $ vsep $ pretty <$> ms]
RsvdBdy bs -> "Reserved:" <+> prettyBytes bs
type UASID = ByteString
data IDType = IDTypeNone | SerialNum | CAARegID | UTMUUID | SpecificSessionID
| IDTypeRsvd Word8
deriving (Eq, Read, Show)
instance Pretty IDType where
pretty t = case t of
IDTypeNone -> "None"
SerialNum -> "Serial Number (ANSI/CTA-2063-A)"
CAARegID -> "CAA Assigned Registration ID"
UTMUUID -> "UTM Assigned UUID"
SpecificSessionID -> "Specific Session ID"
IDTypeRsvd r -> "Reserved" <+> pretty r
data HorizAcc
= GT10NM -- ^ >=18.52 km (10 NM) or Unknown
| LT10NM -- ^ <18.52 km (10 NM)
| LT4NM -- ^ <7.408 km (4 NM)
| LT2NM -- ^ <3.704 km (2 NM)
| LT1NM -- ^ <1852 m (1 NM)
| LT05NM -- ^ <926 m (0.5 NM)
| LT03NM -- ^ <555.6 m (0.3 NM)
| LT01NM -- ^ <185.2 m (0.1 NM)
| LT005NM -- ^ <92.6 m (0.05 NM)
| LT30M -- ^ <30 m
| LT10M -- ^ <10 m
| LT3M -- ^ <3 m
| LT1M -- ^ <1 m
| HorizAccRsvd Word8 -- ^ Reserved
deriving (Eq, Read, Show)
instance Pretty HorizAcc where
pretty a = case a of
GT10NM -> ">=18.52 km (10 NM) or Unknown"
LT10NM -> "<18.52 km (10 NM)"
LT4NM -> "<7.408 km (4 NM)"
LT2NM -> "<3.704 km (2 NM)"
LT1NM -> "<1852 m (1 NM)"
LT05NM -> "<926 m (0.5 NM)"
LT03NM -> "<555.6 m (0.3 NM)"
LT01NM -> "<185.2 m (0.1 NM)"
LT005NM -> "<92.6 m (0.05 NM)"
LT30M -> "<30 m"
LT10M -> "<10 m"
LT3M -> "<3 m"
LT1M -> "<1 m"
HorizAccRsvd n -> "Reserved" <+> pretty n
data ClassCat = Undefined | Open | Specific | Certified | ClassCatRsvd Word8
deriving (Eq, Read, Show)
instance Pretty ClassCat where
pretty (ClassCatRsvd n) = "Reserved" <+> pretty n
pretty c = viaShow c
data VertAcc
= VertAccGTE150M -- ^ >=150 m or Unknown
| VertAccLT150M-- ^ <150 m
| VertAccLT45M -- ^ <45 m
| VertAccLT25M -- ^ <25 m
| VertAccLT10M -- ^ <10 m
| VertAccLT3M -- ^ <3 m
| VertAccLT1M -- ^ <1 m
| VertAccRsvd Word8
deriving (Eq, Read, Show)
instance Pretty VertAcc where
pretty a = case a of
VertAccGTE150M -> ">=150 m or Unknown"
VertAccLT150M -> "<150 m"
VertAccLT45M -> "<45 m"
VertAccLT25M -> "<25 m"
VertAccLT10M -> "<10 m"
VertAccLT3M -> "<3 m"
VertAccLT1M -> "<1 m"
VertAccRsvd n -> "Reserved" <+> pretty n
readVertAcc :: Word8 -> VertAcc
readVertAcc n = case n of
0 -> VertAccGTE150M
1 -> VertAccLT150M
2 -> VertAccLT45M
3 -> VertAccLT25M
4 -> VertAccLT10M
5 -> VertAccLT3M
6 -> VertAccLT1M
_ -> VertAccRsvd n
writeVertAcc :: VertAcc -> Word8
writeVertAcc vacc = case vacc of
VertAccGTE150M -> 0
VertAccLT150M -> 1
VertAccLT45M -> 2
VertAccLT25M -> 3
VertAccLT10M -> 4
VertAccLT3M -> 5
VertAccLT1M -> 6
VertAccRsvd r -> r
data SpeedAcc = GTE10MS | LT10MS | LT3MS | LT1MS | LT03MS | SpeedAccRsvd Word8
deriving (Eq, Read, Show)
instance Pretty SpeedAcc where
pretty a = case a of
GTE10MS -> ">=10 m/s or Unknown"
LT10MS -> "<10 m/s"
LT3MS -> "<3 m/s"
LT1MS -> "<1 m/s"
LT03MS -> "<0.3 m/s"
SpeedAccRsvd n -> "Reserved" <+> pretty n
data HeightType = AboveTakeoff | AGL
deriving (Bounded, Enum, Eq, Read, Show)
instance Pretty HeightType where
pretty AboveTakeoff = "Above Takeoff"
pretty AGL = "AGL"
-- | Location message
data LocMsg = LocMsg
{ locOpStatus :: OpStatus, locFlagsRsvd :: Bool, locHeightType :: HeightType
, locFlagsDir :: Bool, locFlagsMult :: Bool, locTrackDir :: Integer, locSpeed :: Double
, locVertSpeed :: Double, locLat :: Double, locLon :: Double
, locPresAlt :: Double, locGeoAlt :: Double, locHeight :: Double
, locVertAcc :: VertAcc, locHorzAcc :: HorizAcc
, locBaroAltAcc :: VertAcc, locSpeedAcc :: SpeedAcc
, locTimestamp :: Double -- ^ seconds after the hour
, locTStampAccRsvd :: Word8, locTStampAcc :: Double -- ^ timestamp accuracy seconds, 0.1 s res
, locRsvd :: Word8
}
deriving (Eq, Read, Show)
instance Binary LocMsg where
get = getWord8 >>= \b -> do
let opStatus = case b `shiftR` 4 of
0 -> Undeclared
1 -> Ground
2 -> Airborne
3 -> Emergency
4 -> RemoteIDSystemFailure
n -> OpStatusRsvd n
flgsRsvd = testBit b 3
ht = if testBit b 2 then AGL else AboveTakeoff
dir = testBit b 1
mul = testBit b 0
trackDir <- applyWhen dir (+ 180) . fromIntegral <$> getWord8
speed <- decSpeed mul . fromIntegral <$> getWord8
vertSpeed <- (* 0.5) . fromIntegral <$> getInt8
lat <- decLatLon <$> getInt32le
lon <- decLatLon <$> getInt32le
palt <- decodeAlt <$> getWord16le
galt <- decodeAlt <$> getWord16le
hgt <- decodeAlt <$> getWord16le
vhacc <- getWord8
let vacc = readVertAcc $ vhacc `shiftR` 4
hacc = case vhacc .&. 0xF of
0 -> GT10NM
1 -> LT10NM
2 -> LT4NM
3 -> LT2NM
4 -> LT1NM
5 -> LT05NM
6 -> LT03NM
7 -> LT01NM
8 -> LT005NM
9 -> LT30M
10 -> LT10M
11 -> LT3M
12 -> LT1M
n -> HorizAccRsvd n
bacc <- getWord8
let baroAcc = readVertAcc $ bacc `shiftR` 4
speedAcc = case bacc .&. 0xF of
0 -> GTE10MS
1 -> LT10MS
2 -> LT3MS
3 -> LT1MS
4 -> LT03MS
n -> SpeedAccRsvd n
tstmp <- (* 0.1) . fromIntegral <$> getWord16le
tstmprsvdacc <- getWord8
let tstmpacc = fromIntegral (tstmprsvdacc .&. 0xF) * 0.1
LocMsg opStatus flgsRsvd ht dir mul trackDir speed vertSpeed lat
lon palt galt hgt vacc hacc baroAcc speedAcc tstmp
(tstmprsvdacc `shiftR` 4) tstmpacc <$> getWord8
where
decSpeed mul s = if mul then s * 0.75 + 255 * 0.25 else s * 0.25
put l = do
putWord8 $ opStatus `shiftL` 4 .|. rsvdFlag `shiftL` 3 .|. htTy `shiftL` 2 .|.
ewDir `shiftL` 1 .|. spMult
putWord8 $ fromIntegral $ applyWhen (locTrackDir l >= 180) (subtract 180) $ locTrackDir l
putWord8 $ round $ encSpeed $ locSpeed l
putInt8 $ round $ locVertSpeed l * 2
putInt32le $ encLatLon $ locLat l
putInt32le $ encLatLon $ locLon l
putWord16le $ encodeAlt $ locPresAlt l
putWord16le $ encodeAlt $ locGeoAlt l
putWord16le $ encodeAlt $ locHeight l
putWord8 $ writeVertAcc (locVertAcc l) `shiftL` 4 .|. hAcc
putWord8 $ writeVertAcc (locBaroAltAcc l) `shiftL` 4 .|. spAcc
putWord16le $ round $ locTimestamp l / 0.1
putWord8 $ locTStampAccRsvd l `shiftL` 4 .|. round (locTStampAcc l / 0.1)
putWord8 $ locRsvd l
where
opStatus = case locOpStatus l of
Undeclared -> 0
Ground -> 1
Airborne -> 2
Emergency -> 3
RemoteIDSystemFailure -> 4
OpStatusRsvd n -> n
rsvdFlag = fromBool $ locFlagsRsvd l
htTy = fromIntegral $ fromEnum $ locHeightType l
ewDir = fromBool $ locFlagsDir l
spMult = fromBool $ locFlagsMult l
hAcc = case locHorzAcc l of
GT10NM -> 0
LT10NM -> 1
LT4NM -> 2
LT2NM -> 3
LT1NM -> 4
LT05NM -> 5
LT03NM -> 6
LT01NM -> 7
LT005NM -> 8
LT30M -> 9
LT10M -> 10
LT3M -> 11
LT1M -> 12
HorizAccRsvd r -> r
spAcc = case locSpeedAcc l of
GTE10MS -> 0
LT10MS -> 1
LT3MS -> 2
LT1MS -> 3
LT03MS -> 4
SpeedAccRsvd r -> r
instance Pretty LocMsg where
pretty l = vsep
["Operational Status:" <+> pretty (locOpStatus l)
, "Flags", indent 2 $ vsep
[ "Reserved:" <+> if locFlagsRsvd l then "1" else "0"
, "Height type:" <+> pretty (locHeightType l)
, "E/W Dir Seg:" <+> if locFlagsDir l then ">=180" else "<180"
, "Speed mult:" <+> if locFlagsMult l then "x0.75" else "x0.25"
]
, "Track dir:" <+> pretty (locTrackDir l) <+> "deg"
, "Speed:" <+> pretty (locSpeed l) <+> "m/s"
, "Vert Speed:" <+> pretty (locVertSpeed l) <+> "m/s"
, "Latitude:" <+> pretty (locLat l) <+> "deg"
, "Longitude:" <+> pretty (locLon l) <+> "deg"
, "Pressure Alt:" <+> pretty (locPresAlt l) <+> "m"
, "Geodetic Alt:" <+> pretty (locGeoAlt l) <+> "m"
, "Height:" <+> pretty (locHeight l) <+> "m"
, "Vert Acc:" <+> pretty (locVertAcc l)
, "Horz Acc:" <+> pretty (locHorzAcc l)
, "Baro Alt Acc:" <+> pretty (locBaroAltAcc l)
, "Speed Acc:" <+> pretty (locSpeedAcc l)
, "Timestamp:" <+> pretty (locTimestamp l)
, "Reserved:" <+> pretty (locTStampAccRsvd l)
, "TStamp Acc:" <+> pretty (locTStampAcc l) <+> "s"
, "Reserved:" <+> pretty (locRsvd l)
]
data ClassType = ClassTypeUndeclared | EuroUnion | ClassTypeRsvd Word8
deriving (Eq, Read, Show)
instance Pretty ClassType where
pretty ClassTypeUndeclared = "Undeclared"
pretty EuroUnion = "European Union"
pretty (ClassTypeRsvd n) = "Reserved" <+> pretty n
-- | Operator location source type
data OpLocSrc = Takeoff | Dynamic | Fixed
deriving (Bounded, Enum, Eq, Read, Show)
instance Pretty OpLocSrc where
pretty = viaShow
data SysMsg = SysMsg
{ sysClassType :: ClassType, sysOpSrcType :: OpLocSrc, sysOpLat :: Double
, sysOpLon :: Double, sysArCnt :: Word16, sysArRad :: Integer, sysArCeil :: Double
, sysArFloor :: Double, sysClassCat :: ClassCat, sysClassClass :: Word8
, sysOpAlt :: Double, sysTimestamp :: Word32, sysRsvd :: Word8
}
deriving (Eq, Read, Show)
instance Binary SysMsg where
get = getWord8 >>= \flags -> do
let classType = case 0x7 .&. flags `shiftR` 2 of
0 -> ClassTypeUndeclared
1 -> EuroUnion
n -> ClassTypeRsvd n
srcType = if testBit flags 1 then Fixed else toEnum $ fromBool $ testBit flags 0
opLat <- decLatLon <$> getInt32le
opLon <- decLatLon <$> getInt32le
arCnt <- getWord16le
arRad <- (* 10) . fromIntegral <$> getWord8
arCeil <- decodeAlt <$> getWord16le
arFlor <- decodeAlt <$> getWord16le
uaClass <- getWord8
let classCat = case uaClass `shiftR` 4 of
0 -> Undefined
1 -> Open
2 -> Specific
3 -> Certified
r -> ClassCatRsvd r
SysMsg classType srcType opLat opLon arCnt arRad arCeil arFlor classCat
(uaClass .&. 0xF) <$> fmap decodeAlt getWord16le <*> getWord32le <*> getWord8
put m = do
putWord8 $ classTy `shiftL` 2 .|. fromIntegral (fromEnum $ sysOpSrcType m)
putInt32le $ encLatLon $ sysOpLat m
putInt32le $ encLatLon $ sysOpLon m
putWord16le $ sysArCnt m
putWord8 $ fromIntegral $ sysArRad m `div` 10
putWord16le $ encodeAlt $ sysArCeil m
putWord16le $ encodeAlt $ sysArFloor m
putWord8 $ classCat `shiftL` 4 .|. sysClassClass m .&. 0xF
putWord16le $ encodeAlt $ sysOpAlt m
putWord32le $ sysTimestamp m
putWord8 $ sysRsvd m
where
classTy = case sysClassType m of
ClassTypeUndeclared -> 0
EuroUnion -> 1
ClassTypeRsvd r -> r
classCat = case sysClassCat m of
Undefined -> 0
Open -> 1
Specific -> 2
Certified -> 3
ClassCatRsvd r -> r
instance Pretty SysMsg where
pretty s = vsep
[ "Class:" <+> pretty (sysClassType s)
, "Op Src:" <+> pretty (sysOpSrcType s)
, "Op Lat:" <+> pretty (sysOpLat s) <+> "deg"
, "Op Lon:" <+> pretty (sysOpLon s) <+> "deg"
, "Area Cnt:" <+> pretty (sysArCnt s)
, "Area Rad:" <+> pretty (sysArRad s) <+> "m"
, "Area Ceil:" <+> pretty (sysArCeil s) <+> "m"
, "Area Floor:" <+> pretty (sysArFloor s) <+> "m"
, "UA Category:" <+> pretty (sysClassCat s)
, "UA Class:" <+> pretty (sysClassClass s)
, "Op Alt:" <+> pretty (sysOpAlt s) <+> "m"
, "Timestamp:" <+> pretty (sysTimestamp s)
, "Reserved:" <+> pretty (sysRsvd s)
]
data AuthMsg = AuthMsg AuthType
Word8 -- ^ Page number
(Maybe (Word8, Word8, Word32)) -- ^ PageN last page index, length, timestamp
ByteString -- ^ signature
deriving (Eq, Read, Show)
instance Binary AuthMsg where
get = getWord8 >>= \b -> do
let ty = case b `shiftR` 4 of
0 -> AuthNone
1 -> UASIDSig
2 -> OpIDSig
3 -> MsgSetSig
4 -> AuthNRID
5 -> SpecificAuth
n | n >= 6 && n <= 9 -> AuthRsvd n
| otherwise -> AuthPriv n
pge = b .&. 0xF
if pge == 0
then do
z <- (,,) <$> getWord8 <*> getWord8 <*> getWord32le
AuthMsg ty pge (Just z) <$> getLazyByteString 17
else AuthMsg ty pge Nothing <$> getLazyByteString 23
put (AuthMsg ty pge (Just (lpi, l, ts)) sig) = do
putWord8 $ writeAuthType ty `shiftL` 4 .|. pge .&. 0xF
putWord8 lpi <> putWord8 l <> putWord32le ts <> putLazyByteString sig
put (AuthMsg ty pge Nothing sig) = do
putWord8 $ writeAuthType ty `shiftL` 4 .|. pge .&. 0xF
putLazyByteString sig
instance Pretty AuthMsg where
pretty (AuthMsg ty pge (Just (lpi, l, ts)) sig) = vsep
["Type:" <+> pretty ty, "Page:" <+> pretty pge, "Last Page Index:" <+> pretty lpi
, "Length:" <+> pretty l, "Timestamp:" <+> pretty ts, "Signature:" <+> prettyBytes sig
]
pretty (AuthMsg ty pge Nothing sig) = vsep
["Type:" <+> pretty ty, "Page:" <+> pretty pge, "Signature:" <+> prettyBytes sig]
data AuthType
= AuthNone | UASIDSig | OpIDSig | MsgSetSig | AuthNRID | SpecificAuth
| AuthRsvd Word8 | AuthPriv Word8
deriving (Eq, Read, Show)
instance Pretty AuthType where
pretty t = case t of
AuthNone -> "None"
UASIDSig -> "UAS ID Signature"
OpIDSig -> "Operator ID Signature"
MsgSetSig -> "Message Set Signature"
AuthNRID -> "Auth by Network Remote ID"
SpecificAuth -> "Specific Auth"
AuthRsvd r -> "Reserved" <+> pretty r
AuthPriv p -> "Private" <+> pretty p
writeAuthType :: AuthType -> Word8
writeAuthType ty = case ty of
AuthNone -> 0
UASIDSig -> 1
OpIDSig -> 2
MsgSetSig -> 3
AuthNRID -> 4
SpecificAuth -> 5
AuthRsvd r -> r
AuthPriv p -> p
encodeAlt :: Double -> Word16
encodeAlt x = round $ (x + 1000) * 2
decodeAlt :: Word16 -> Double
decodeAlt x = fromIntegral x * 0.5 - 1000
encLatLon :: Double -> Int32
encLatLon = round . (* latLonMult)
decLatLon :: Int32 -> Double
decLatLon = (/ latLonMult) . fromIntegral
latLonMult :: Double
latLonMult = 10 ^ (7 :: Int)
encSpeed :: Double -> Double
encSpeed s | s <= 255 * 0.25 = s / 0.25
| s > 255 * 0.25 && s < 254.25 = (s - (255 * 0.25)) / 0.75
| otherwise = 254
prettyBytes :: ByteString -> Doc ann
prettyBytes bs = "0x" <> foldMap prettyByte (BS.unpack bs)
where
prettyByte b = pretty $ showHex (b `shiftR` 4) "" <> showHex (b .&. 0xF) ""