packages feed

arduino-copilot-1.5.2: Examples/WaterHeater/Demo.hs

{- Controlling a hot water heater.
 -
 - This shows how additional types can be used to eg, keep track of units
 - of measurement, while using the Copilot DSL.
 -}

{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE EmptyDataDecls #-}

module Examples.WaterHeater.Demo where

import Copilot.Arduino.Uno
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as L

-- For use as phantom types in TypedBehavior
data PSI
data Celsius

maxSafePSI :: TypedBehavior PSI Float
maxSafePSI = TypedBehavior (constant 45)

maxWaterTemp :: TypedBehavior Celsius Float
maxWaterTemp = TypedBehavior (constant 35)

warmWaterTemp :: TypedBehavior Celsius Float
warmWaterTemp = TypedBehavior (constant 30)

isSafeTemp :: TypedBehavior Celsius Float -> Behavior Bool
isSafeTemp t = liftB2 (<) t maxWaterTemp

isSafePSI :: TypedBehavior PSI Float -> Behavior Bool
isSafePSI p = liftB2 (<) p maxSafePSI

-- Avoid explosions and scalding water.
isSafeToHeat :: TypedBehavior PSI Float -> NonEmpty (TypedBehavior Celsius Float) -> Behavior Bool
isSafeToHeat p cs = foldl (&&) (isSafePSI p) (L.map isSafeTemp cs)

-- Convert a raw value read from an ADC into a temperature in Celsius.
--
-- Assumes the ADC value is 0 at 0F, and 1024 at 200F.
adcToCelsius :: Behavior ADC -> TypedBehavior Celsius Float
adcToCelsius v = TypedBehavior $ v' * (constant 200 / constant 1024)
  where
	v' :: Behavior Float
	v' = unsafeCast v

-- Convert a raw value read from an ADC into a PSI.
--
-- Assumes the ADCI value is 0 at 0 PSI, and 1024 at 150 PSI.
adcToPSI :: Behavior ADC -> TypedBehavior PSI Float
adcToPSI v = TypedBehavior $ v' * (constant 150 / constant 1024)
  where
	v' :: Behavior Float
	v' = unsafeCast v

main :: IO ()
main = arduino $ do
	uppertemp <- adcToCelsius <$> input a1
	lowertemp <- adcToCelsius <$> input a2
	pressure <- adcToPSI <$> input a3
	let upperheatingelement = pin4
	let lowerheatingelement = pin5
	whenB (isSafeToHeat pressure (uppertemp :| [lowertemp])) $ do
		-- Run the upper heating element if the upper water temp is
		-- low, to provide hot water on demand. Run the lower
		-- heating element only when the upper is off.
		let runupper = liftB2 (<) uppertemp warmWaterTemp
		upperheatingelement =: runupper
		lowerheatingelement =: not runupper
	delay =: MilliSeconds 1000