obd-0.2.0.0: src/System/Hardware/ELM327/Car.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RankNTypes #-}
-- | A generic car data type
module System.Hardware.ELM327.Car (
-- * Base car structure
Car
, defaultCar
-- * Monad transformer for cashing
, CarT(..)
, runCarT
, flushCache
, cache
-- * Lenses for 'Car'
, engineCoolantTemperature
, engineFuelRate
, engineRPM
, intakeAirTemperature
, intakeManifoldAbsolutePressure
, massAirFlowRate
, throttlePosition
, vehicleSpeed
-- * Internal structure
, CarState
, emptyState
) where
import Control.Concurrent.STM (TVar, atomically, newTVarIO, readTVar, writeTVar, modifyTVar)
import Control.Lens (Lens', lens, (^.), (.~))
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (ReaderT, MonadReader, runReaderT, ask)
import Control.Monad.Trans.Class (MonadTrans, lift)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Numeric.Units.Dimensional.Prelude
import System.Hardware.ELM327.Connection (ConT)
import qualified System.Hardware.ELM327.Connection.OBD as OBD
-- | A car that has some properties, see lenses documentation below.
data Car m = Car { _engineCoolantTemperature :: CarT m (ThermodynamicTemperature Double)
, _engineFuelRate :: CarT m (VolumeFlow Double)
, _engineRPM :: CarT m (Frequency Double)
, _intakeAirTemperature :: CarT m (ThermodynamicTemperature Double)
, _intakeManifoldAbsolutePressure :: CarT m (Pressure Double)
, _massAirFlowRate :: CarT m (MassFlow Double)
, _throttlePosition :: CarT m Double
, _vehicleSpeed :: CarT m (Velocity Double) }
-- | A monad transformer for 'Car' where requested data is cached until 'flushCache' is called.
newtype CarT m a = CarT { runCarT' :: ReaderT (TVar CarState) m a }
deriving (Functor, Applicative, Monad, MonadIO, MonadReader (TVar CarState), MonadTrans)
-- | Run a 'CarT' with an initial empty state
runCarT :: MonadIO m => CarT m a -> m a
runCarT action = liftIO (newTVarIO emptyState) >>= runReaderT (runCarT' action)
-- | Flush the cache of a 'CarT'
flushCache :: MonadIO m => CarT m ()
flushCache = ask >>= liftIO . atomically . flip writeTVar emptyState
-- | Make an action cachable in 'CarT'
cache :: MonadIO m => (forall n . Lens' (Car n) (CarT n a)) -> CarT m a -> CarT m a
cache property action = do
mv <- runCarT . (^. property) <$> (ask >>= liftIO . atomically . readTVar)
v <- liftIO $ runMaybeT mv
case v of
Just x -> return x
Nothing -> do
v' <- action
ask >>= liftIO . atomically . flip modifyTVar (property .~ lift (MaybeT . return $ Just v'))
return v'
-- | The default car, that uses straight forward OBD commands to get
-- most of the data.
defaultCar :: MonadIO m => Car (ConT m)
defaultCar = Car { _engineCoolantTemperature = cache engineCoolantTemperature (lift OBD.engineCoolantTemperature)
, _engineFuelRate = cache engineFuelRate (lift OBD.engineFuelRate)
, _engineRPM = cache engineRPM (lift OBD.engineRPM)
, _intakeAirTemperature = cache intakeAirTemperature (lift OBD.intakeAirTemperature)
, _intakeManifoldAbsolutePressure = cache intakeManifoldAbsolutePressure (lift OBD.intakeManifoldAbsolutePressure)
, _massAirFlowRate = cache massAirFlowRate (lift OBD.massAirFlowRate)
, _throttlePosition = cache throttlePosition (lift OBD.throttlePosition)
, _vehicleSpeed = cache vehicleSpeed (lift OBD.vehicleSpeed) }
-- | The engine coolant temperature of the car.
engineCoolantTemperature :: Lens' (Car m) (CarT m (ThermodynamicTemperature Double))
engineCoolantTemperature = lens _engineCoolantTemperature $ \c x -> c { _engineCoolantTemperature = x }
-- | The engine fuel rate of the car.
engineFuelRate :: Lens' (Car m) (CarT m (VolumeFlow Double))
engineFuelRate = lens _engineFuelRate $ \c x -> c { _engineFuelRate = x }
-- | The engine RPM of the car.
engineRPM :: Lens' (Car m) (CarT m (Frequency Double))
engineRPM = lens _engineRPM $ \c x -> c { _engineRPM = x }
-- | The intake air temperature of the car.
intakeAirTemperature :: Lens' (Car m) (CarT m (ThermodynamicTemperature Double))
intakeAirTemperature = lens _intakeAirTemperature $ \c x -> c { _intakeAirTemperature = x }
-- | The intake manifold absolute pressure of the car.
intakeManifoldAbsolutePressure :: Lens' (Car m) (CarT m (Pressure Double))
intakeManifoldAbsolutePressure = lens _intakeManifoldAbsolutePressure $ \c x -> c { _intakeManifoldAbsolutePressure = x }
-- | The mass air flow rate of the car.
massAirFlowRate :: Lens' (Car m) (CarT m (MassFlow Double))
massAirFlowRate = lens _massAirFlowRate $ \c x -> c { _massAirFlowRate = x }
-- | The throttle position of the car.
throttlePosition :: Lens' (Car m) (CarT m Double)
throttlePosition = lens _throttlePosition $ \c x -> c { _throttlePosition = x }
-- | The throttle position of the car.
vehicleSpeed :: Lens' (Car m) (CarT m (Velocity Double))
vehicleSpeed = lens _vehicleSpeed $ \c x -> c { _vehicleSpeed = x }
-- | The pure state of the car, with possibly missing values.
type CarState = Car (MaybeT IO)
-- | The empty 'CarState'
emptyState :: CarState
emptyState = Car { _engineCoolantTemperature = lift (MaybeT $ return Nothing)
, _engineFuelRate = lift (MaybeT $ return Nothing)
, _engineRPM = lift (MaybeT $ return Nothing)
, _intakeAirTemperature = lift (MaybeT $ return Nothing)
, _intakeManifoldAbsolutePressure = lift (MaybeT $ return Nothing)
, _massAirFlowRate = lift (MaybeT $ return Nothing)
, _throttlePosition = lift (MaybeT $ return Nothing)
, _vehicleSpeed = lift (MaybeT $ return Nothing) }