packages feed

network-can-0.1.0.0: src/Network/SLCAN/Builder.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Network.SLCAN.Builder
  ( buildSLCANMessage
  ) where

import Data.ByteString (ByteString)
import Data.ByteString.Builder (Builder)
import Data.Set (Set)
import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))
import Network.SLCAN.Types
  ( SLCANMessage(..)
  , SLCANControl(..)
  , SLCANState(..)
  , SLCANCounters(..)
  , SLCANError(..)
  )
import qualified Data.Bits
import qualified Data.Set
import qualified Data.ByteString.Lazy
import qualified Data.ByteString.Builder

slCANBuilder
  :: SLCANMessage
  -> Builder
slCANBuilder slcanMsg =
  case slcanMsg of
    SLCANMessage_Control ctrlMsg -> slCANControlBuilder ctrlMsg
    SLCANMessage_Data canMsg -> slCANDataBuilder canMsg
    SLCANMessage_State state counters  -> slCANStateBuilder state counters
    SLCANMessage_Error errs -> slCANErrorBuilder errs
  <> Data.ByteString.Builder.char7 '\r'

slCANControlBuilder
  :: SLCANControl
  -> Builder
slCANControlBuilder SLCANControl_Open =
  Data.ByteString.Builder.char7 'O'
slCANControlBuilder SLCANControl_Close =
  Data.ByteString.Builder.char7 'C'
slCANControlBuilder (SLCANControl_Bitrate bitrate) =
     Data.ByteString.Builder.char7 'S'
  <> Data.ByteString.Builder.intDec
      (fromEnum bitrate)
slCANControlBuilder SLCANControl_ResetErrors =
  Data.ByteString.Builder.char7 'F'
slCANControlBuilder SLCANControl_ListenOnly =
  Data.ByteString.Builder.char7 'L'

slCANDataBuilder
  :: CANMessage
  -> Builder
slCANDataBuilder CANMessage{..} =
     arbitrationId canMessageArbitrationField
  <> Data.ByteString.Builder.word8Hex
      (fromIntegral $ length canMessageData)
  <> mconcat
      (map
         Data.ByteString.Builder.word8HexFixed
         canMessageData
      )

arbitrationId
  :: CANArbitrationField
  -> Builder
arbitrationId CANArbitrationField{..} =
     Data.ByteString.Builder.char7
       (case ( canArbitrationFieldExtended
             , canArbitrationFieldRTR
             )
        of
         (False, False) -> 't'
         (False, True)  -> 'r'
         (True, False)  -> 'T'
         (True, True)   -> 'R'
       )
  <> (if canArbitrationFieldExtended
      then Data.ByteString.Builder.word32HexFixed
      else
        (\word11 ->
           Data.ByteString.Builder.word8Hex
            (fromIntegral (word11 `Data.Bits.shiftR` 8))
        <> Data.ByteString.Builder.word8HexFixed
            (fromIntegral word11)
        )
     )
     canArbitrationFieldID

slCANStateBuilder
  :: SLCANState
  -> SLCANCounters
  -> Builder
slCANStateBuilder state SLCANCounters{..} =
     Data.ByteString.Builder.char7 's'
  <> Data.ByteString.Builder.char7
      (case state of
         SLCANState_Active -> 'a'
         SLCANState_Warning -> 'w'
         SLCANState_Passive -> 'p'
         SLCANState_BusOff -> 'b'
      )
  <> word16Dec3 slCANCountersTxErrors
  <> word16Dec3 slCANCountersRxErrors
  where
    -- encode as 3 bytes (maximum of 999 and zero padded)
    word16Dec3 x =
           (case x of
              _ | x < 10 -> Data.ByteString.Builder.string7 "00"
              _ | x < 100 -> Data.ByteString.Builder.char7 '0'
              _ | otherwise -> mempty
           )
        <> Data.ByteString.Builder.word16Dec
            (min 999 x)

slCANErrorBuilder
  :: Set SLCANError
  -> Builder
slCANErrorBuilder errs =
     Data.ByteString.Builder.char7 'e'
  <> Data.ByteString.Builder.word8Hex
      (fromIntegral $ Data.Set.size errs)
  <> mconcat
      (map
         ( Data.ByteString.Builder.char7
         . \case
             SLCANError_Ack -> 'a'
             SLCANError_Bit0 -> 'b'
             SLCANError_Bit1 -> 'B'
             SLCANError_CRC -> 'c'
             SLCANError_Form -> 'f'
             SLCANError_RxOverrun -> 'o'
             SLCANError_TxOverrun -> 'O'
             SLCANError_Stuff -> 's'
         )
         $ Data.Set.toList
             errs
      )

buildSLCANMessage
  :: SLCANMessage
  -> ByteString
buildSLCANMessage =
   Data.ByteString.Lazy.toStrict
 . Data.ByteString.Builder.toLazyByteString
 . slCANBuilder