reactive-banana-automation-0.3.0: home (etc) automation using reactive-banana

Safe HaskellNone
LanguageHaskell98

Reactive.Banana.Automation.Examples

Description

Automation examples. View source for the code.

These examples are tested by doctest when building this library.

Patches adding examples welcomed!

Synopsis

Documentation

data Sensors Source #

We'll use a single Sensors type containing all the sensors used by the examples below.

data Actuators Source #

And a single Actuators type containing all the actuators used by the examples below.

mkSensors :: IO Sensors Source #

For running the examples, you'll need this, to construct a Sensors

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) -> (Sensors -> MomentAutomation (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 :: Sensors -> MomentAutomation (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 -> Sensors -> MomentAutomation (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]