packages feed

obd-0.1.0.0: src/System/Hardware/ELM327/Commands.hs

-- | A list of supported ELM327 commands (incomplete).
module System.Hardware.ELM327.Commands where

import Control.Lens (Prism', prism', re, (^.), (^?))
import Data.Bits (shiftR, (.&.), (.|.))
import Data.Word (Word8, Word16)
import Numeric (readHex)
import Text.Printf (printf)

-- | A generic command, that can be sent to an ELM327.
data Command = AT AT
             | OBD OBD
             deriving (Eq, Show)

-- | A prism between 'String' and 'Command'
command :: Prism' String Command
command = prism' conv mConv
  where
    conv (AT x) = "AT" ++ x ^. re at
    conv (OBD x) = x ^. re obd

    mConv ('A':'T':xs) = AT <$> xs ^? at
    mConv xs = OBD <$> xs ^? obd

-- | An AT command, to configure the ELM327.
data AT = ATDescribeProtocolNumber
        | ATEchoOff
        | ATReadVoltage
        | ATResetAll
        | ATSelectProtocol Protocol
        | ATVersionID
        deriving (Eq, Show)

-- | A prism between 'String' and 'AT'.
at :: Prism' String AT
at = prism' conv mConv
  where
    conv ATDescribeProtocolNumber = "DPN"
    conv ATEchoOff = "E0"
    conv ATReadVoltage = "RV"
    conv ATResetAll = "Z"
    conv (ATSelectProtocol p) = "SP" ++ p ^. re protocol
    conv ATVersionID = "I"

    mConv "DPN" = Just ATDescribeProtocolNumber
    mConv "E0" = Just ATEchoOff
    mConv "RV" = Just ATReadVoltage
    mConv "Z" = Just ATResetAll
    mConv ('S':'P':p) = ATSelectProtocol <$> p ^? protocol
    mConv "I" = Just ATVersionID
    mConv _ = Nothing

-- | An OBD command, to talk to the vehicle.
--
-- Every OBD command can be one of several modes.
data OBD = CurrentData CurrentData
         deriving (Eq, Show)

-- | Return the mode of an 'OBD' command.
obdMode :: OBD -> Word8
obdMode cmd = word16Mode $ cmd ^. re obdWord16

-- | Return the PID of an 'OBD' command.
obdPID :: OBD -> Word8
obdPID cmd = word16PID $ cmd ^. re obdWord16

-- | Return the mode of an 'OBD' command represented as a 'Word16'.
word16Mode :: Word16 -> Word8
word16Mode x = fromIntegral $ shiftR x 8 .&. 0xFF

-- | Return the PID of an 'OBD' command represented as a 'Word16'.
word16PID :: Word16 -> Word8
word16PID x = fromIntegral $ x .&. 0xFF

-- | A prism between 'OBD' and 'Word16'
obdWord16 :: Prism' Word16 OBD
obdWord16 = prism' conv mConv
  where
    conv (CurrentData x) = 0x0100 .|. fromIntegral (x ^. re currentData)

    mConv x | word16Mode x == 0x01 = CurrentData <$> word16PID x ^? currentData
            | otherwise = Nothing

-- | A prism between 'OBD' and 'String'
obd :: Prism' String OBD
obd = prism' conv mConv
  where
    conv x = printf "%04X" (x ^. re obdWord16)
    mConv s | [(x, "")] <- readHex s = x ^? obdWord16
            | otherwise = Nothing

-- | An OBD command requesting current vehicle data (mode 0x01).
data CurrentData = OBDEngineCoolantTemperature
                 | OBDEngineFuelRate
                 | OBDEngineRPM
                 | OBDIntakeAirTemperature
                 | OBDIntakeManifoldAbsolutePressure
                 | OBDMassAirFlowRate
                 | OBDSupported01PIDs
                 | OBDThrottlePosition
                 | OBDVehicleSpeed
                 deriving (Eq, Show)

-- | A prism between 'Word8' and 'CurrentData'.
currentData :: Prism' Word8 CurrentData
currentData = prism' conv mConv
  where
    conv OBDEngineCoolantTemperature = 0x05
    conv OBDEngineFuelRate = 0x5E
    conv OBDEngineRPM = 0x0C
    conv OBDIntakeAirTemperature = 0x0F
    conv OBDIntakeManifoldAbsolutePressure = 0x0B
    conv OBDMassAirFlowRate = 0x10
    conv OBDSupported01PIDs = 0x00
    conv OBDThrottlePosition = 0x11
    conv OBDVehicleSpeed = 0x0D

    mConv 0x05 = Just OBDEngineCoolantTemperature
    mConv 0x5E = Just OBDEngineFuelRate
    mConv 0x0C = Just OBDEngineRPM
    mConv 0x0F = Just OBDIntakeAirTemperature
    mConv 0x0B = Just OBDIntakeManifoldAbsolutePressure
    mConv 0x10 = Just OBDMassAirFlowRate
    mConv 0x00 = Just OBDSupported01PIDs
    mConv 0x11 = Just OBDThrottlePosition
    mConv 0x0D = Just OBDVehicleSpeed
    mConv _ = Nothing

-- | The protocol used by the ELM327 to connect to your car.
data Protocol = AutomaticProtocol
              | SAE_J1850_PWM
              | SAE_J1850_VPW
              | ISO_9141_2
              | ISO_14230_4_KWP
              | ISO_14230_4_KWP_Fast
              | ISO_15765_4_CAN_11_Bit_ID_500_KBaud
              | ISO_15765_4_CAN_29_Bit_ID_500_KBaud
              | ISO_15765_4_CAN_11_Bit_ID_250_KBaud
              | ISO_15765_4_CAN_29_Bit_ID_250_KBaud
              | SAE_J1939_CAN
              | USER1_CAN
              | USER2_CAN
              deriving (Eq, Show)

-- | A prism between 'String' and 'Protocol'.
protocol :: Prism' String Protocol
protocol = prism' conv mConv
  where
    conv AutomaticProtocol = "0"
    conv SAE_J1850_PWM = "1"
    conv SAE_J1850_VPW = "2"
    conv ISO_9141_2 = "3"
    conv ISO_14230_4_KWP = "4"
    conv ISO_14230_4_KWP_Fast = "5"
    conv ISO_15765_4_CAN_11_Bit_ID_500_KBaud = "6"
    conv ISO_15765_4_CAN_29_Bit_ID_500_KBaud = "7"
    conv ISO_15765_4_CAN_11_Bit_ID_250_KBaud = "8"
    conv ISO_15765_4_CAN_29_Bit_ID_250_KBaud = "9"
    conv SAE_J1939_CAN = "A"
    conv USER1_CAN = "B"
    conv USER2_CAN = "C"

    mConv "0" = Just AutomaticProtocol
    mConv "1" = Just SAE_J1850_PWM
    mConv "2" = Just SAE_J1850_VPW
    mConv "3" = Just ISO_9141_2
    mConv "4" = Just ISO_14230_4_KWP
    mConv "5" = Just ISO_14230_4_KWP_Fast
    mConv "6" = Just ISO_15765_4_CAN_11_Bit_ID_500_KBaud
    mConv "7" = Just ISO_15765_4_CAN_29_Bit_ID_500_KBaud
    mConv "8" = Just ISO_15765_4_CAN_11_Bit_ID_250_KBaud
    mConv "9" = Just ISO_15765_4_CAN_29_Bit_ID_250_KBaud
    mConv "A" = Just SAE_J1939_CAN
    mConv "B" = Just USER1_CAN
    mConv "C" = Just USER2_CAN
    mConv _ = Nothing