{-# LANGUAGE OverloadedStrings #-} -- :set -XOverloadedStrings -- | -- This module provides Haskell representation of messages exchanged on the sakura.io platform. -- -- https://sakura.io/docs/pages/spec/platform/message.html module Network.SakuraIO.Platform.Message ( ModuleID ,OutGoingMessage (..) ,ChannelID ,OGChannel (..) ,Latitude, Longitude, RangeM ,Coordinate (..) ,InComingMessage (..) ,ICChannel (..) ,ChannelValue (..) ) where import Data.Time.Clock(UTCTime(..)) import Data.Int(Int32(..),Int64(..)) import Data.Word(Word8(..),Word32(..),Word64(..)) import qualified Data.ByteString as BS import Control.Applicative(empty) import Data.HashMap.Strict ((!)) import Data.Aeson.Types (Value(..)) import Data.Aeson.Types (FromJSON,parseJSON,(.:),Parser(..)) import Data.Aeson.Types (ToJSON,toJSON,object,(.=)) import Data.Monoid ((<>)) -- | Module ID starting with \'u\'. type ModuleID = String -- | Messages sent from sakura.io to external services. -- -- It supports `FromJSON` to parse from json using @aeson@. data OutGoingMessage = OGChannels ModuleID UTCTime [OGChannel] | OGConnection ModuleID UTCTime Bool | OGLocation ModuleID UTCTime (Maybe Coordinate) | OGKeepalive UTCTime deriving (Eq,Show) -- | 7bit channel number. type ChannelID = Word8 -- | Channel payload sent from sakura.io to external services as part of `OGChannels`. -- -- It supports `FromJSON` to parse from json using @aeson@. data OGChannel = OGChannel ChannelID UTCTime ChannelValue deriving (Eq,Show) -- | Latitude. type Latitude = Float -- | Longitude. type Longitude = Float -- | Range in meters. type RangeM = Int32 -- | Coordinate payload sent from sakura.io to external services as part of `OGLocation`. -- -- It supports `FromJSON` to get json representation using @aeson@. data Coordinate = Coordinate Latitude Longitude RangeM deriving (Eq,Show) -- | Messages sent from external services to sakura.io. -- -- It supports `ToJSON` to get json representation using @aeson@. data InComingMessage = ICChannels ModuleID [ICChannel] deriving (Eq,Show) -- | Channel payload sent from external services to sakura.io as part of `ICChannels`. -- -- It supports `ToJSON` to get json representation using @aeson@. data ICChannel = ICChannel ChannelID ChannelValue deriving (Eq,Show) -- | Data exchanged between sakura.io and external services as part of `OGChannel` and `ICChannel`. -- data ChannelValue = CVInt32 Int32 | CVWord32 Word32 | CVInt64 Int64 | CVWord64 Word64 | CVFloat Float | CVDouble Double | CVBytes BS.ByteString deriving (Eq,Show) instance FromJSON OutGoingMessage where parseJSON (Object v ) = do dt <- v .: "datetime" t <- (v .: "type" :: Parser String) case t of "channels" -> case (v ! "payload") of Object pl -> do md <- v .: "module" cs <- pl .: "channels" return $ OGChannels md dt cs _ -> fail "playload must be Object" "connection" -> case (v ! "payload") of Object pl -> do md <- v .: "module" ol <- pl .: "is_online" return $ OGConnection md dt ol _ -> fail "playload must be Object" "location" -> case (v ! "payload") of Object pl -> do md <- v .: "module" cd <- pl .: "coordinate" return $ OGLocation md dt cd _ -> fail "playload must be Object" "keepalive"-> return $ OGKeepalive dt parseJSON _ = empty instance FromJSON OGChannel where parseJSON (Object v) = do ch <- v .: "channel" dt <- v .: "datetime" t <- v .: "type" v <- case t of 'i' -> CVInt32 <$> v .: "value" 'I' -> CVWord32 <$> v .: "value" 'l' -> CVInt64 <$> v .: "value" 'L' -> CVWord64 <$> v .: "value" 'f' -> CVFloat <$> v .: "value" 'd' -> CVDouble <$> v .: "value" 'b' -> CVBytes . parseHex <$> v .: "value" return $ OGChannel ch dt v parseJSON _ = empty instance FromJSON Coordinate where parseJSON (Object v) = Coordinate <$> v .: "latitude" <*> v .: "longitude" <*> v .: "range_m" parseJSON _ = empty instance ToJSON InComingMessage where toJSON (ICChannels md xs) = object ["type" .= ("channels"::String), "module" .= md, "payload" .= object ["channels" .= xs] ] instance ToJSON ICChannel where toJSON (ICChannel ch (CVInt32 i)) = object ["channel" .= ch, "type" .= ("i"::String), "value" .= i] toJSON (ICChannel ch (CVWord32 i)) = object ["channel" .= ch, "type" .= ("I"::String), "value" .= i] toJSON (ICChannel ch (CVInt64 l)) = object ["channel" .= ch, "type" .= ("l"::String), "value" .= l] toJSON (ICChannel ch (CVWord64 l)) = object ["channel" .= ch, "type" .= ("L"::String), "value" .= l] toJSON (ICChannel ch (CVFloat f)) = object ["channel" .= ch, "type" .= ("f"::String), "value" .= f] toJSON (ICChannel ch (CVDouble d)) = object ["channel" .= ch, "type" .= ("d"::String), "value" .= d] toJSON (ICChannel ch (CVBytes b)) = object ["channel" .= ch, "type" .= ("b"::String), "value" .= toHex b] parseHex :: [Char] -> BS.ByteString parseHex = BS.pack . f . splitAt 2 where f :: ([Char],[Char]) -> [Word8] f ([], _) = [] f (xs, ys) = (g xs) : (f $ splitAt 2 ys) g :: [Char] -> Word8 g (h:l:[]) = (d h) * 16 + (d l) where d :: Char -> Word8 d '0' = 0x0 d '1' = 0x1 d '2' = 0x2 d '3' = 0x3 d '4' = 0x4 d '5' = 0x5 d '6' = 0x6 d '7' = 0x7 d '8' = 0x8 d '9' = 0x9 d 'a' = 0xa d 'b' = 0xb d 'c' = 0xc d 'd' = 0xd d 'e' = 0xe d 'f' = 0xf d 'A' = 0xa d 'B' = 0xb d 'C' = 0xc d 'D' = 0xd d 'E' = 0xe d 'F' = 0xf toHex :: BS.ByteString -> [Char] toHex bs = foldr (\b->(<>)(g b)) [] $ BS.unpack bs where g :: Word8 -> [Char] g w = [d $ w `div` 16, d $ w `mod` 16] where d :: Word8 -> Char d 0x0 = '0' d 0x1 = '1' d 0x2 = '2' d 0x3 = '3' d 0x4 = '4' d 0x5 = '5' d 0x6 = '6' d 0x7 = '7' d 0x8 = '8' d 0x9 = '9' d 0xa = 'a' d 0xb = 'b' d 0xc = 'c' d 0xd = 'd' d 0xe = 'e' d 0xf = 'f'