Unify per-room Lights enums into shared LightingMode

This commit is contained in:
2026-09-29 23:38:43 +03:00
parent ad023a1ba2
commit c2426dec3d
6 changed files with 49 additions and 58 deletions
+1
View File
@@ -64,6 +64,7 @@ library
, HomeAssistant.Controller.Bedroom , HomeAssistant.Controller.Bedroom
, HomeAssistant.Controller.Hallway , HomeAssistant.Controller.Hallway
, HomeAssistant.Controller.Kitchen , HomeAssistant.Controller.Kitchen
, HomeAssistant.Controller.Light.Mode
, HomeAssistant.Controller.Children , HomeAssistant.Controller.Children
, HomeAssistant.Controller.Livingroom , HomeAssistant.Controller.Livingroom
, HomeAssistant.Controller.Ruuvi , HomeAssistant.Controller.Ruuvi
+9 -14
View File
@@ -3,6 +3,7 @@
module HomeAssistant.Controller.Bedroom where module HomeAssistant.Controller.Bedroom where
import HomeAssistant.Controller import HomeAssistant.Controller
import HomeAssistant.Controller.Light.Mode (LightingMode (..))
import qualified Data.Text as T import qualified Data.Text as T
import AFRP (Event (..), lMerge, duration, edge) import AFRP (Event (..), lMerge, duration, edge)
import Data.Aeson (Value, object, (.=)) import Data.Aeson (Value, object, (.=))
@@ -32,17 +33,11 @@ createBedroomScene = Service
, serviceTarget= [] , serviceTarget= []
} }
data PresenceLights
= LightsOn -- Restore lights
| LightsOff -- Timer expired, darkness
| LightsSleep -- No presence detected, sleep mode
deriving (Eq, Show)
-- Bedroom presence -- Bedroom presence
bedroomPresence :: HASS (Event Value) (Event Presence) bedroomPresence :: HASS (Event Value) (Event Presence)
bedroomPresence = presence "binary_sensor.presence_sensor_bedroom_occupancy" bedroomPresence = presence "binary_sensor.presence_sensor_bedroom_occupancy"
presenceLightEvents :: HASS (Event Value) (Event PresenceLights) presenceLightEvents :: HASS (Event Value) (Event LightingMode)
presenceLightEvents = presenceLightEvents =
bedroomPresence >>> AFRP.hold Unoccupied bedroomPresence >>> AFRP.hold Unoccupied
>>> arr lightWanted >>> arr lightWanted
@@ -50,9 +45,9 @@ presenceLightEvents =
>>> arr (\(s,(o,n)) -> s `AFRP.lMerge` o `AFRP.lMerge` n) >>> arr (\(s,(o,n)) -> s `AFRP.lMerge` o `AFRP.lMerge` n)
where where
lightWanted o = o == Occupied lightWanted o = o == Occupied
sleepEvent = arr not >>> AFRP.edge >>> arr (AFRP.tag LightsSleep) sleepEvent = arr not >>> AFRP.edge >>> arr (AFRP.tag LightingSleep)
onEvent = AFRP.edge >>> arr (AFRP.tag LightsOn) onEvent = AFRP.edge >>> arr (AFRP.tag LightingOn)
offEvent = arr not >>> AFRP.waitFor 300 >>> arr (AFRP.tag LightsOff) offEvent = arr not >>> AFRP.waitFor 300 >>> arr (AFRP.tag LightingOff)
bedroomLightsSignal :: HASS (Event Value) (Map T.Text LightSnapshot) bedroomLightsSignal :: HASS (Event Value) (Map T.Text LightSnapshot)
@@ -63,13 +58,13 @@ bedroomPresenceController = proc x -> do
p <- presenceLightEvents >>> traceEvent -< x p <- presenceLightEvents >>> traceEvent -< x
snap <- (doSnapshot &&& bedroomLightsSignal) >>> AFRP.snapshot M.empty >>> traceValue -< x snap <- (doSnapshot &&& bedroomLightsSignal) >>> AFRP.snapshot M.empty >>> traceValue -< x
case p of case p of
Event LightsOff -> do Event LightingOff -> do
callService (light bedroomLights Off) -< () callService (light bedroomLights Off) -< ()
Event LightsOn -> do Event LightingOn -> do
-- callService (activateSceneWith "scene.makuuhuone_lights_snapshot" transition) -< () -- callService (activateSceneWith "scene.makuuhuone_lights_snapshot" transition) -< ()
callServicesDyn (concatMap (uncurry toLight)) -< M.toList snap callServicesDyn (concatMap (uncurry toLight)) -< M.toList snap
returnA -< () returnA -< ()
Event LightsSleep -> do Event LightingSleep -> do
callService createBedroomScene -< () callService createBedroomScene -< ()
callService (activateSceneWith "scene.makuuhuone_lepotila" transition) -< () callService (activateSceneWith "scene.makuuhuone_lepotila" transition) -< ()
_ -> returnA -< () _ -> returnA -< ()
@@ -83,7 +78,7 @@ bedroomPresenceController = proc x -> do
, light [EntityId entityId] (On def{lightTransition=Just 5, lightBrightness=Just (BrightnessAbsolute b)}) , light [EntityId entityId] (On def{lightTransition=Just 5, lightBrightness=Just (BrightnessAbsolute b)})
] ]
doSnapshot = presenceLightEvents doSnapshot = presenceLightEvents
>>> arr (\ev -> if ev == Event LightsSleep then Event () else Tick) >>> arr (\ev -> if ev == Event LightingSleep then Event () else Tick)
transition = Just $ object ["transition" .= (1:: Int)] transition = Just $ object ["transition" .= (1:: Int)]
+5 -13
View File
@@ -2,7 +2,8 @@
{-# LANGUAGE Arrows #-} {-# LANGUAGE Arrows #-}
module HomeAssistant.Controller.Hallway where module HomeAssistant.Controller.Hallway where
import HomeAssistant.Controller (Presence (..), HASS, motion, motionToPresence, traceEvent, callServiceDyn, light, Target (..), LightCommand (..)) import HomeAssistant.Controller (Presence, HASS, motion, motionToPresence, traceEvent, callServiceDyn, light, Target (..), LightCommand (..))
import HomeAssistant.Controller.Light.Mode (LightingMode (..), lightingModeEvents)
import AFRP (Event (..)) import AFRP (Event (..))
import qualified AFRP import qualified AFRP
import Data.Aeson (Value) import Data.Aeson (Value)
@@ -15,25 +16,16 @@ import Data.Default (def)
hallwayPresence :: HASS (Event Value) Presence hallwayPresence :: HASS (Event Value) Presence
hallwayPresence = motion "binary_sensor.hallway_movement_occupancy" >>> motionToPresence 500 hallwayPresence = motion "binary_sensor.hallway_movement_occupancy" >>> motionToPresence 500
data Lights = LightsOn | LightsOff
deriving (Show)
eventLights :: HASS Presence (Event Lights)
eventLights = AFRP.changes >>> arr (fmap presenceLights)
where
presenceLights Occupied = LightsOn
presenceLights Unoccupied = LightsOff
hallwayLightsController :: HASS (Event Value) () hallwayLightsController :: HASS (Event Value) ()
hallwayLightsController = proc x -> do hallwayLightsController = proc x -> do
now <- AFRP.currentTime -< () now <- AFRP.currentTime -< ()
p <- hallwayPresence -< x p <- hallwayPresence -< x
ev <- eventLights -< p ev <- lightingModeEvents -< p
traceEvent -< ev traceEvent -< ev
case ev of case ev of
-- The lights entity is a "group of lights" in hass -- The lights entity is a "group of lights" in hass
Event LightsOn | lightsAllowed now -> callServiceDyn (light [EntityId "light.hallway_lights"]) -< On def Event LightingOn | lightsAllowed now -> callServiceDyn (light [EntityId "light.hallway_lights"]) -< On def
Event LightsOff -> callServiceDyn (light [EntityId "light.hallway_lights"]) -< Off Event LightingOff -> callServiceDyn (light [EntityId "light.hallway_lights"]) -< Off
_ -> returnA -< () _ -> returnA -< ()
where where
lightsAllowed (LocalTime _ tod) = not (tod > TimeOfDay 1 45 0 && tod < TimeOfDay 5 0 0) lightsAllowed (LocalTime _ tod) = not (tod > TimeOfDay 1 45 0 && tod < TimeOfDay 5 0 0)
+11 -19
View File
@@ -2,6 +2,7 @@
{-# LANGUAGE Arrows #-} {-# LANGUAGE Arrows #-}
module HomeAssistant.Controller.Kitchen where module HomeAssistant.Controller.Kitchen where
import HomeAssistant.Controller import HomeAssistant.Controller
import HomeAssistant.Controller.Light.Mode (LightingMode (..), lightingModeEvents)
import AFRP (Event (..)) import AFRP (Event (..))
import qualified AFRP import qualified AFRP
import Data.Aeson (Value) import Data.Aeson (Value)
@@ -13,27 +14,18 @@ import Data.Default (def)
-- Kitchen has two "presence" sensors. One IKEA motion sensor and one SwitchBot presence sensor -- Kitchen has two "presence" sensors. One IKEA motion sensor and one SwitchBot presence sensor
data Lights = LightsOn | LightsOff | LightsSleep
deriving (Show)
kitchenPresence :: HASS (Event Value) Presence kitchenPresence :: HASS (Event Value) Presence
kitchenPresence = motion "binary_sensor.kitchen_movement_occupancy" >>> motionToPresence 500 kitchenPresence = motion "binary_sensor.kitchen_movement_occupancy" >>> motionToPresence 500
eventLights :: HASS Presence (Event Lights)
eventLights = AFRP.changes >>> arr (fmap presenceLights)
where
presenceLights Occupied = LightsOn
presenceLights Unoccupied = LightsOff
kitchenCeilingController :: HASS (Event Value) () kitchenCeilingController :: HASS (Event Value) ()
kitchenCeilingController = proc x -> do kitchenCeilingController = proc x -> do
now <- AFRP.currentTime -< () now <- AFRP.currentTime -< ()
p <- kitchenPresence -< x p <- kitchenPresence -< x
ev <- eventLights -< p ev <- lightingModeEvents -< p
traceEvent -< ev traceEvent -< ev
case ev of case ev of
Event LightsOn | lightsAllowed now -> callServiceDyn (light [EntityId "light.kitchen_ceiling"]) -< On def Event LightingOn | lightsAllowed now -> callServiceDyn (light [EntityId "light.kitchen_ceiling"]) -< On def
Event LightsOff -> callServiceDyn (light [EntityId "light.kitchen_ceiling"]) -< Off Event LightingOff -> callServiceDyn (light [EntityId "light.kitchen_ceiling"]) -< Off
_ -> returnA -< () _ -> returnA -< ()
where where
lightsAllowed (LocalTime _ tod) = not (tod > TimeOfDay 1 45 0 && tod < TimeOfDay 5 0 0) lightsAllowed (LocalTime _ tod) = not (tod > TimeOfDay 1 45 0 && tod < TimeOfDay 5 0 0)
@@ -45,7 +37,7 @@ dinnerLightLevel = entityRead "sensor.sijainti_keittio_light_level" >>> AFRP.hol
dinnerPresence :: HASS (Event Value) Presence dinnerPresence :: HASS (Event Value) Presence
dinnerPresence = presence "binary_sensor.sijainti_keittio_occupancy" >>> AFRP.hold Unoccupied dinnerPresence = presence "binary_sensor.sijainti_keittio_occupancy" >>> AFRP.hold Unoccupied
dinnerLightEvents :: HASS (Event Value) (Event Lights) dinnerLightEvents :: HASS (Event Value) (Event LightingMode)
dinnerLightEvents = dinnerLightEvents =
((,) <$> dinnerLightLevel <*> dinnerPresence) ((,) <$> dinnerLightLevel <*> dinnerPresence)
>>> arr lightWanted >>> arr lightWanted
@@ -56,29 +48,29 @@ dinnerLightEvents =
onEvent = onEvent =
AFRP.edge AFRP.edge
>>> arr (fmap (const LightsOn)) >>> arr (fmap (const LightingOn))
sleepEvent = sleepEvent =
arr not arr not
>>> AFRP.edge >>> AFRP.edge
>>> arr (fmap (const LightsSleep)) >>> arr (fmap (const LightingSleep))
offEvent = offEvent =
arr not arr not
>>> AFRP.waitFor 300 >>> AFRP.waitFor 300
>>> arr (fmap (const LightsOff)) >>> arr (fmap (const LightingOff))
dinnerTableController :: HASS (Event Value) () dinnerTableController :: HASS (Event Value) ()
dinnerTableController = proc x -> do dinnerTableController = proc x -> do
ev <- dinnerLightEvents -< x ev <- dinnerLightEvents -< x
case ev of case ev of
Event LightsOn -> do Event LightingOn -> do
sleep -< False sleep -< False
callServiceDyn (light [EntityId "light.kitchen_dining_large"]) -< On def callServiceDyn (light [EntityId "light.kitchen_dining_large"]) -< On def
Event LightsOff -> do Event LightingOff -> do
sleep -< False sleep -< False
callServiceDyn (light [EntityId "light.kitchen_dining_large"]) -< Off callServiceDyn (light [EntityId "light.kitchen_dining_large"]) -< Off
Event LightsSleep -> do Event LightingSleep -> do
sleep -< True sleep -< True
_ -> returnA -< () _ -> returnA -< ()
where where
@@ -0,0 +1,19 @@
module HomeAssistant.Controller.Light.Mode
( LightingMode(..)
, toLightingMode
, lightingModeEvents
) where
import HomeAssistant.Controller (Presence(..), HASS)
import qualified AFRP
import Control.Arrow (Arrow(..), (>>>))
data LightingMode = LightingOn | LightingOff | LightingSleep
deriving (Eq, Show)
toLightingMode :: Presence -> LightingMode
toLightingMode Occupied = LightingOn
toLightingMode Unoccupied = LightingOff
lightingModeEvents :: HASS Presence (AFRP.Event LightingMode)
lightingModeEvents = AFRP.changes >>> arr (fmap toLightingMode)
+4 -12
View File
@@ -3,6 +3,7 @@
module HomeAssistant.Controller.Livingroom where module HomeAssistant.Controller.Livingroom where
import HomeAssistant.Controller (HASS, Presence (..), Target (..), presence, callServiceDyn, light, LightCommand (..), traceEvent, callService, activateScene) import HomeAssistant.Controller (HASS, Presence (..), Target (..), presence, callServiceDyn, light, LightCommand (..), traceEvent, callService, activateScene)
import HomeAssistant.Controller.Light.Mode (LightingMode (..), lightingModeEvents)
import qualified AFRP import qualified AFRP
import Control.Arrow ((>>>), Arrow (..), returnA) import Control.Arrow ((>>>), Arrow (..), returnA)
import Data.Aeson (Value) import Data.Aeson (Value)
@@ -11,18 +12,9 @@ import Control.Category ((.))
import Data.Time (LocalTime(..), toGregorian, TimeOfDay (..)) import Data.Time (LocalTime(..), toGregorian, TimeOfDay (..))
import Data.Default (def) import Data.Default (def)
data Lights = LightsOn | LightsOff
deriving (Show)
livingroomPresence :: HASS (AFRP.Event Value) Presence livingroomPresence :: HASS (AFRP.Event Value) Presence
livingroomPresence = presence "binary_sensor.occupancy_olohuone_derived" >>> AFRP.hold Unoccupied livingroomPresence = presence "binary_sensor.occupancy_olohuone_derived" >>> AFRP.hold Unoccupied
eventLights :: HASS Presence (AFRP.Event Lights)
eventLights = AFRP.changes >>> arr (fmap presenceLights)
where
presenceLights Occupied = LightsOn
presenceLights Unoccupied = LightsOff
-- There's a small race condition / corner case bug in the og implementation -- There's a small race condition / corner case bug in the og implementation
-- I have a couple of lights in the Livingroom -- I have a couple of lights in the Livingroom
@@ -44,11 +36,11 @@ lights = [EntityId "light.livingroom_ceiling", EntityId "light.livingroom_sofa"]
livingroomPresenceController :: HASS (AFRP.Event Value) () livingroomPresenceController :: HASS (AFRP.Event Value) ()
livingroomPresenceController = proc x -> do livingroomPresenceController = proc x -> do
ev <- eventLights . livingroomPresence -< x ev <- lightingModeEvents . livingroomPresence -< x
traceEvent -< ev traceEvent -< ev
case ev of case ev of
AFRP.Event LightsOn -> callServiceDyn (light lights) -< On def AFRP.Event LightingOn -> callServiceDyn (light lights) -< On def
AFRP.Event LightsOff -> callServiceDyn (light lights) -< Off AFRP.Event LightingOff -> callServiceDyn (light lights) -< Off
_ -> returnA -< () _ -> returnA -< ()
returnA -< () returnA -< ()