{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} module TinyMesh where import Data.Char import GHC.Generics import Data.List (intercalate) import Control.Monad import Control.Applicative import System.Environment (getArgs) import System.Posix.Unistd import Debug.Trace --import System.IO -- From bytestring module: import qualified Data.ByteString.Char8 as BS -- From serialport package - portable serial port handling: import System.Hardware.Serialport as Serial -- From hex package: import Data.Hex -- From Attoparsec import Data.Attoparsec.ByteString.Char8 as Atto import Packet.Parse -- | Serial settings successfully used for communication. serialSettings :: SerialPortSettings serialSettings = defaultSerialSettings { commSpeed = CS19200 , timeout = 1 , flowControl = NoFlowControl --Software , parity = NoParity , stopb = One , bitsPerWord = 8 } --From datasheet: --http://tiny-mesh.com/mesh-network/pdf/RCxxxx%28HP%29-TM_Data_Sheet_1_42.pdf --Query message p. 22: queryCmd :: BS.ByteString Right queryCmd = unhex $ BS.concat [ --Checksum: "0A", --bcast address "FF", "FF", "FF", "FF", --Any random number below (hex) <80 "51", -- Command code: -- 11 - list devs | 12 - get directly connected | 16 - get paths "03", "11", -- packet fill: "00", "00" ] -- | Reading exactly N bytes from serial port, or reporting a failure. readN :: SerialPort -> Int -> IO (Maybe BS.ByteString) readN ser n = reader n [] where finalize = Just . BS.concat . reverse reader :: Int -> [BS.ByteString] -> IO (Maybe BS.ByteString) reader m _ | m < 0 = error "FIXME: Read too many bytes!!!" reader 0 acc = return $ finalize acc reader i acc = do rest <- Serial.recv ser i if BS.length rest == 0 then do print $ "Nothing in readN" ++ show (finalize acc) return Nothing -- not enough bytes: else reader (i-BS.length rest) (rest:acc) -- | Reading a packet: readPacket :: SerialPort -> IO (Maybe BS.ByteString) readPacket ser = do hdr <- Serial.recv ser 1 if BS.null hdr then return Nothing else let packetSize = ord $ BS.head hdr in do rest <- readN ser (packetSize - 1) case rest of Nothing -> return Nothing Just payload -> return $ Just $ hdr `BS.append` payload -- | Read all the packets currently in the buffer/network. readPackets :: SerialPort -> IO [BS.ByteString] readPackets ser = reverse <$> reader [] where reader acc = do result <- readPacket ser case result of Nothing -> return $ reverse acc Just pkt -> reader $ pkt:acc -- TODO: Use Generic? data Header = Header { len :: Int -- ^ length of the packet , systemId :: NetAddr -- ^ system address for mesh nodes , originId :: NetAddr -- ^ network address of originating node , originRSSI :: Int -- ^ origin RSSI , netLevel :: Int -- ^ number of vertical hops to gateway , hops :: Int -- ^ number of actual hops from router to gateway , origMsgCnt :: Int -- ^ origin message counter , latency :: Int -- ^ latency between message creation and delivery *10ms , packetType :: Int -- ^ integer packet type } deriving(Show, Generic) netaddr :: Parser NetAddr netaddr = parser instance Parse Header where parser = Header <$> byte -- length, byte <*> netaddr <*> netaddr <*> byte -- originRSSI <*> byte -- network level <*> byte -- hops <*> word -- origin message counter <*> word -- latency <*> byte -- packet type -- (BS.length <$> untilEOF anyChar) --endOfInput headerLen :: Int headerLen = 17 -- bytes data Payload = Event { } | Serial { blockCount :: Maybe Int , serData :: BS.ByteString } | Unknown BS.ByteString deriving Show data Packet = Packet { header :: Header , payload :: Payload } deriving(Show, Generic) newtype NetAddr = NetAddr { netAddrAsTuple :: (Int, Int, Int, Int) } deriving Generic instance Show NetAddr where show (NetAddr (d, c, b, a)) = "." `intercalate` map show [a, b, c, d] instance Parse NetAddr where parser = NetAddr <$> parser byte :: Parser Int byte = ord <$> anyChar word :: Parser Int word = compute <$> byte <*> byte where compute a b = a*256+b -- | TODO: Hex literals instance Parse Packet where parser = do hdr <- parser let bytesLeft = len hdr - headerLen Packet hdr <$> case packetType hdr of 2 -> parseEvent 16 -> parseSerial bytesLeft otherType -> trace ("Unknown packet type: " ++ show otherType) $ Unknown <$> bytestringParser bytesLeft parseEvent :: Parser Payload parseEvent = return Event {} parseSerial :: Int -> Parser Payload parseSerial bytesLeft = do blockCounter <- nothingIfZero <$> parser Serial blockCounter <$> bytestringParser (bytesLeft - 1) where nothingIfZero 0 = Nothing nothingIfZero i = Just i -- TODO: more efficient bytestring parsing bytestringParser :: Int -> Parser BS.ByteString bytestringParser i = BS.pack <$> count i anyChar parsePacket :: BS.ByteString -> Packet parsePacket = parseBS -- | Main test script: main :: IO () main = do serDevs <- getArgs --print queryCmd forM_ serDevs $ \serDev -> do ser <- openSerial serDev serialSettings flush ser putStrLn $ "Opened serial on " ++ serDev _ <- Serial.send ser queryCmd usleep $ 1*1000000 --Responses (p.24): -- 2 on -> 4 bytes with the address -- 18 on -> statuses, sensor readings result <- readPackets ser putStrLn $ unlines $ map show result mapM_ (print . parsePacket) result