Safe Haskell | None |
---|---|
Language | Haskell98 |
Automation
examples. View source for the code.
These examples are tested by doctest when building this library.
Patches adding examples welcomed!
- data Sensors = Sensors {
- fridgeTemperature :: EventSource (Sensed Double) ()
- motionSensor :: EventSource (Sensed (Timestamped POSIXTime Bool)) ()
- clock :: EventSource (ClockSignal LocalTime) ()
- rainGaugeTipSensor :: EventSource (Sensed ()) ()
- data Actuators
- mkSensors :: IO Sensors
- fridge :: Automation Sensors Actuators ()
- motionActivatedLight :: Automation Sensors Actuators ()
- nightLight :: Automation Sensors Actuators ()
- showBehaviorLCDDisplay :: (a -> String) -> Automation Sensors Actuators (Behavior a) -> Automation Sensors Actuators ()
- totalRainfall :: Automation Sensors Actuators (Behavior Integer)
- totalRainfallSince :: TimeOfDay -> Automation Sensors Actuators (Behavior (Timestamped (ClockSignal LocalTime) Integer))
- sprinklersStartingAt :: TimeOfDay -> Automation Sensors Actuators ()
- thisHouse :: Automation Sensors Actuators ()
Documentation
We'll use a single Sensors type containing all the sensors used by the examples below.
Sensors | |
|
And a single Actuators type containing all the actuators used by the examples below.
fridge :: Automation Sensors Actuators () Source #
A fridge, containing the fridgeTemperature
sensor and with
its power controlled by the FridgePower
actuator.
The fridge starts running when its temperature exceeds a maximum safe value. Once the temperature falls below a minimim value, the fridge stops running. Note that opening the door of this fridge for a minute typically won't cause it to run, unless it was already close to being too warm. This behavior was chosen to minimise starts of the compressor, but of course other fridge behaviors are also possible; this is only an example.
To give this example a try, import this module in ghci and run:
>>>
runner <- observeAutomation fridge mkSensors
>>>
runner $ \sensors -> fridgeTemperature sensors =: 6
[FridgePower PowerOn]>>>
runner $ \sensors -> fridgeTemperature sensors =: 3
[]>>>
runner $ \sensors -> fridgeTemperature sensors =: 0.5
[FridgePower PowerOff]
motionActivatedLight :: Automation Sensors Actuators () Source #
Turns on a light when the motionSensor
detects movement,
and leaves it on for 5 minutes after the last movement.
If this were run in real code, the motion sensor would trigger
calls to sensedNow
.
But, for testing, it's useful to specify the time that the sensor
is triggered, using sensedAt
. Import this module in ghci and run:
>>>
runner <- observeAutomation motionActivatedLight mkSensors
>>>
runner $ \sensors -> sensedAt 0 (motionSensor sensors) True
[LightSwitch PowerOn]>>>
runner $ \sensors -> sensedAt 30 (motionSensor sensors) False
[]>>>
runner $ \sensors -> sensedAt 60 (motionSensor sensors) True
[LightSwitch PowerOn]>>>
runner $ \sensors -> sensedAt 120 (motionSensor sensors) False
[]>>>
runner $ \sensors -> sensedAt 400 (motionSensor sensors) False
[LightSwitch PowerOff]
nightLight :: Automation Sensors Actuators () Source #
Turns on a light at night (after 6 pm), and off during the day (after 6 am).
If this were run in real code, the clock would be fed by running clockSignal every so often.
But, for testing, it's useful to specify the time, using
clockSignalAt
. Import this module in ghci and run:
>>>
let day = fromGregorian 2018 1 1
>>>
runner <- observeAutomation nightLight mkSensors
>>>
runner $ \sensors -> clockSignalAt (LocalTime day midnight) (clock sensors)
[LightSwitch PowerOn]>>>
runner $ \sensors -> clockSignalAt (LocalTime day midday) (clock sensors)
[LightSwitch PowerOff]
showBehaviorLCDDisplay :: (a -> String) -> Automation Sensors Actuators (Behavior a) -> Automation Sensors Actuators () Source #
Displays a Behavior on the LCD display actuator.
While it could be used to drive a real LCD, this is mostly useful for testing behaviors.
totalRainfall :: Automation Sensors Actuators (Behavior Integer) Source #
The rain gauge sensor is a tipping bucket type; the bucket collects 0.01
inches of rain and then tips, which triggers the rainGaugeTipSensor
.
This behavior sums up the total rainfall, in hundredths of an inch.
To test this behavior, we can use showBehaviorLCDDisplay
:
>>>
runner <- observeAutomation (showBehaviorLCDDisplay show totalRainfall) mkSensors
>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "1"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "2"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "3"]
totalRainfallSince :: TimeOfDay -> Automation Sensors Actuators (Behavior (Timestamped (ClockSignal LocalTime) Integer)) Source #
This behavior contains the total rainfall since a specified TimeOfDay
,
and is timestamped with the last clock signal.
To test this behavior, we can use showBehaviorLCDDisplay
,
providing both clock signals and rainGaugeTipSensor
events:
>>>
let day = fromGregorian 2018 1 1
>>>
runner <- observeAutomation (showBehaviorLCDDisplay (show . value) $ totalRainfallSince midnight) mkSensors
>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 13 0 0)) (clock sensors)
[LCDDisplay "0"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "1"]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 14 0 0)) (clock sensors)
[LCDDisplay "1"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "2"]>>>
runner $ \sensors -> clockSignalAt (LocalTime (succ day) (TimeOfDay 1 0 0)) (clock sensors)
[LCDDisplay "0"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "1"]>>>
runner $ \sensors -> clockSignalAt (LocalTime (succ day) (TimeOfDay 2 0 0)) (clock sensors)
[LCDDisplay "1"]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[LCDDisplay "2"]
sprinklersStartingAt :: TimeOfDay -> Automation Sensors Actuators () Source #
Turns on the sprinklers for an hour each day starting from
the specified TimeOfDay
, but only if the rain gauge collected
less than 0.03 inches of rain over the past day.
>>>
let day = fromGregorian 2018 1 1
>>>
runner <- observeAutomation (sprinklersStartingAt midnight) mkSensors
>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 13 0 0)) (clock sensors)
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 0 1 0)) (clock sensors)
[SprinklerSwitch PowerOn]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 0 2 0)) (clock sensors)
[SprinklerSwitch PowerOn]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 1 2 0)) (clock sensors)
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 1 3 0)) (clock sensors)
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> sensed (rainGaugeTipSensor sensors) ()
[SprinklerSwitch PowerOff]>>>
runner $ \sensors -> clockSignalAt (LocalTime day (TimeOfDay 0 1 0)) (clock sensors)
[SprinklerSwitch PowerOff]
thisHouse :: Automation Sensors Actuators () Source #
Automation
is a Monoid
, so it's easy to combine several
smaller automations like those above into a larger one.
>>>
let day = fromGregorian 2018 1 1
>>>
runner <- observeAutomation thisHouse mkSensors
>>>
runner $ \sensors -> clockSignalAt (LocalTime day midnight) (clock sensors)
[LightSwitch PowerOn,SprinklerSwitch PowerOn]>>>
runner $ \sensors -> fridgeTemperature sensors =: 6
[FridgePower PowerOn]>>>
runner $ \sensors -> sensedAt 0 (motionSensor sensors) True
[LightSwitch PowerOn]