Quick buttons

This commit is contained in:
2026-09-17 11:48:22 +03:00
parent 036473ab81
commit e3bc25ce54
4 changed files with 79 additions and 58 deletions
+45 -1
View File
@@ -28,6 +28,10 @@ module HomeAssistant.Controller
, Target(..) , Target(..)
, brightness , brightness
, Light(..) , Light(..)
, ikeaQuickButton
, IkeaButton(..)
, IkeaGesture(..)
, activateScene
) where ) where
import AFRP (Mealy (..), eff, Event(..), events, filterA, (>>|), toEvent, Request, hold, lMerge, waitFor, tag, edge) import AFRP (Mealy (..), eff, Event(..), events, filterA, (>>|), toEvent, Request, hold, lMerge, waitFor, tag, edge)
@@ -36,7 +40,7 @@ import Control.Category ((>>>))
import Data.Aeson (Value, object, (.=)) import Data.Aeson (Value, object, (.=))
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Set as S import qualified Data.Set as S
import Control.Lens (has, only, (^?), to) import Control.Lens (has, only, (^?), to, traversed)
import Data.Aeson.Lens (key, _String, _Integral) import Data.Aeson.Lens (key, _String, _Integral)
import qualified Data.Text.Lens as TL import qualified Data.Text.Lens as TL
import Data.Bool (bool) import Data.Bool (bool)
@@ -191,3 +195,43 @@ brightness entityId =
>>> AFRP.toEvent >>> AFRP.toEvent
where where
eventBrightness v = v ^? key "event" . key "variables" . key "trigger" . key "to_state" . key "attributes" . key "brightness" . _Integral eventBrightness v = v ^? key "event" . key "variables" . key "trigger" . key "to_state" . key "attributes" . key "brightness" . _Integral
data IkeaGesture
= ShortClick
| ShortRelease
| DoubleClick
| LongClick
deriving Show
data IkeaButton a = OnButton !a | OffButton !a
deriving Show
toIkeaQuickButton :: T.Text -> Maybe (IkeaButton IkeaGesture)
toIkeaQuickButton = \case
-- 1 is on, 2 is off side of the button
"1_initial_press" -> Just $ OnButton ShortClick
"1_short_release" -> Just $ OnButton ShortRelease
"1_double_press" -> Just $ OnButton DoubleClick
"1_long_press" -> Just $ OnButton LongClick
"2_initial_press" -> Just $ OffButton ShortClick
"2_short_release" -> Just $ OffButton ShortRelease
"2_double_press" -> Just $ OffButton DoubleClick
"2_long_press" -> Just $ OffButton LongClick
_ -> Nothing
ikeaQuickButton :: T.Text -> HASS (Event Value) (Event (IkeaButton IkeaGesture))
ikeaQuickButton entityId =
entityChangeEvent' entityId
>>| arr (maybe (Left ()) Right . eventType)
>>> toEvent
where
eventType v = v ^? key "event" . key "variables" . key "trigger" . key "to_state" . key "attributes" . key "event_type" . _String . to toIkeaQuickButton . traversed
activateScene :: T.Text -> Service
activateScene sceneId = Service
{ serviceDomain="scene"
, serviceName= "turn_on"
, serviceTarget= [EntityId sceneId]
, serviceData = Nothing
}
+1 -40
View File
@@ -4,11 +4,9 @@ module HomeAssistant.Controller.Bedroom where
import HomeAssistant.Controller import HomeAssistant.Controller
import qualified Data.Text as T import qualified Data.Text as T
import AFRP (Event (..), (>>|), toEvent, lMerge, duration, edge) import AFRP (Event (..), lMerge, duration, edge)
import Data.Aeson (Value, object, (.=)) import Data.Aeson (Value, object, (.=))
import Control.Arrow ((>>>), returnA, arr, Arrow (..)) import Control.Arrow ((>>>), returnA, arr, Arrow (..))
import Control.Lens ((^?), to, traversed)
import Data.Aeson.Lens (key, _String)
import Data.Bool (bool) import Data.Bool (bool)
import Data.Time (NominalDiffTime) import Data.Time (NominalDiffTime)
import qualified AFRP import qualified AFRP
@@ -30,13 +28,6 @@ createBedroomScene = Service
} }
activateScene :: T.Text -> Service
activateScene sceneId = Service
{ serviceDomain="scene"
, serviceName= "turn_on"
, serviceTarget= [EntityId sceneId]
, serviceData = Nothing
}
-- Bedroom presence -- Bedroom presence
bedroomPresence :: HASS (Event Value) (Event Presence) bedroomPresence :: HASS (Event Value) (Event Presence)
@@ -50,36 +41,6 @@ bedroomPresenceController = proc x -> do
Event Occupied -> callService (activateScene "scene.makuuhuone_lights_snapshot") -< () Event Occupied -> callService (activateScene "scene.makuuhuone_lights_snapshot") -< ()
_ -> returnA -< () _ -> returnA -< ()
data IkeaGesture
= ShortClick
| ShortRelease
| DoubleClick
| LongClick
deriving Show
data IkeaButton a = OnButton !a | OffButton !a
deriving Show
toIkeaQuickButton :: T.Text -> Maybe (IkeaButton IkeaGesture)
toIkeaQuickButton = \case
-- 1 is on, 2 is off side of the button
"1_initial_press" -> Just $ OnButton ShortClick
"1_short_release" -> Just $ OnButton ShortRelease
"1_double_press" -> Just $ OnButton DoubleClick
"1_long_press" -> Just $ OnButton LongClick
"2_initial_press" -> Just $ OffButton ShortClick
"2_short_release" -> Just $ OffButton ShortRelease
"2_double_press" -> Just $ OffButton DoubleClick
"2_long_press" -> Just $ OffButton LongClick
_ -> Nothing
ikeaQuickButton :: T.Text -> HASS (Event Value) (Event (IkeaButton IkeaGesture))
ikeaQuickButton entityId =
entityChangeEvent' entityId
>>| arr (maybe (Left ()) Right . eventType)
>>> toEvent
where
eventType v = v ^? key "event" . key "variables" . key "trigger" . key "to_state" . key "attributes" . key "event_type" . _String . to toIkeaQuickButton . traversed
data BedroomControls data BedroomControls
+18 -3
View File
@@ -1,13 +1,14 @@
{-# LANGUAGE Arrows #-} {-# LANGUAGE Arrows #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module HomeAssistant.Controller.Children where module HomeAssistant.Controller.Children where
import HomeAssistant.Controller (HASS, callService, Target (AreaId), light, Light(..)) import HomeAssistant.Controller (HASS, callService, Target (AreaId), light, Light(..), ikeaQuickButton, IkeaGesture (..), IkeaButton (..), traceEvent, activateScene)
import AFRP (Event) import AFRP (Event (..))
import qualified AFRP import qualified AFRP
import Control.Arrow (Arrow(..), (>>>)) import Control.Arrow (Arrow(..), (>>>), returnA)
import Data.Time (Day, TimeOfDay (..), localDay, LocalTime (..)) import Data.Time (Day, TimeOfDay (..), localDay, LocalTime (..))
import Data.Time.Calendar.OrdinalDate (WeekOfYear, mondayStartWeek) import Data.Time.Calendar.OrdinalDate (WeekOfYear, mondayStartWeek)
import Data.Functor.Contravariant (Predicate (..), (>$<)) import Data.Functor.Contravariant (Predicate (..), (>$<))
import Data.Aeson (Value)
-- Let's see building some reasonable interface for utctime -- Let's see building some reasonable interface for utctime
dow :: Day -> (WeekOfYear, Int) dow :: Day -> (WeekOfYear, Int)
@@ -80,3 +81,17 @@ lightsOn = foldMap atTime timersOn
lightsOff :: HASS a () lightsOff :: HASS a ()
lightsOff = foldMap atTime timersOff lightsOff = foldMap atTime timersOff
>>> AFRP.onEvent (callService (light [AreaId "lasten_makuuhuone"] Off)) >>> AFRP.onEvent (callService (light [AreaId "lasten_makuuhuone"] Off))
bedroomButton :: HASS (Event Value) (Event (IkeaButton IkeaGesture))
bedroomButton = ikeaQuickButton "event.children_quick_remote_action"
childrenBedroomButtonController :: HASS (Event Value) ()
childrenBedroomButtonController = proc x -> do
ev <- bedroomButton >>> traceEvent -< x
case ev of
Event (OnButton ShortRelease) -> callService (activateScene "scene.lasten_nukkuvalmistelu") -< ()
Event (OnButton DoubleClick) -> callService (activateScene "scene.lasten_luku") -< ()
Event (OnButton LongClick) -> callService (activateScene "scene.lasten_kirkas") -< ()
Event (OffButton _) -> callService (light [AreaId "lasten_makuuhuone"] Off) -< ()
_ -> returnA -< ()
+15 -14
View File
@@ -29,7 +29,7 @@ import Data.UUID (UUID, toText)
import qualified Data.UUID.V4 as UUID.V4 import qualified Data.UUID.V4 as UUID.V4
import Katip (runKatipT, logF, sl, Severity (..), ls, Namespace (Namespace), runKatipContextT) import Katip (runKatipT, logF, sl, Severity (..), ls, Namespace (Namespace), runKatipContextT)
import Control.Monad.IO.Class (liftIO, MonadIO) import Control.Monad.IO.Class (liftIO, MonadIO)
import HomeAssistant.Controller.Children (schoolLightController) import HomeAssistant.Controller.Children (schoolLightController, childrenBedroomButtonController)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import qualified System.Metrics import qualified System.Metrics
import qualified HomeAssistant.Runtime.Metrics import qualified HomeAssistant.Runtime.Metrics
@@ -50,25 +50,26 @@ step path trace st a = do
let req = Request now tz trace let req = Request now tz trace
stepAutoSerializing path st req a stepAutoSerializing path st req a
data Controller = forall b. Controller T.Text (HASS (Event Value) b) Bool data Controller = forall b. Controller Bool T.Text (HASS (Event Value) b)
controllers :: [Controller] controllers :: [Controller]
controllers = controllers =
[ Controller "bedroom-presence" bedroomPresenceController True [ Controller True "bedroom-presence" bedroomPresenceController
, Controller "bedroom-button" bedroomButtonController True , Controller True "bedroom-button" bedroomButtonController
, Controller "bedroom-drawer" bedroomDrawerController True , Controller True "bedroom-drawer" bedroomDrawerController
, Controller "bedroom-humidifier" humidifierController True , Controller True "bedroom-humidifier" humidifierController
, Controller "school-light-controller" schoolLightController True , Controller True "school-light-controller" schoolLightController
, Controller "kitchen-motion-controller" kitchenMotionController True , Controller True "kitchen-motion-controller" kitchenMotionController
, Controller "livingroom-presence" livingroomPresenceController True , Controller True "livingroom-presence" livingroomPresenceController
, Controller "hallway-motion-controller" hallwayLightsController True , Controller True "hallway-motion-controller" hallwayLightsController
, Controller True "children-button" childrenBedroomButtonController
] ]
-- | Steps the machine for every inbound message; service calls go to the -- | Steps the machine for every inbound message; service calls go to the
-- bus. A restart re-dups the inbound channel and starts from the machine's -- bus. A restart re-dups the inbound channel and starts from the machine's
-- initial state; messages broadcast during the restart window are lost. -- initial state; messages broadcast during the restart window are lost.
runController :: MonadIO m => FilePath -> Bus -> Controller -> m Void runController :: MonadIO m => FilePath -> Bus -> Controller -> m Void
runController rootDir bus (Controller name machine _enabled) = liftIO $ do runController rootDir bus (Controller _enabled name machine ) = liftIO $ do
inbound <- atomically (dupTChan (busInbound bus)) inbound <- atomically (dupTChan (busInbound bus))
let ns = Namespace [name] let ns = Namespace [name]
let workerDefinition = runMealy machine (runKatipContextT (busLogEnv bus) () ns . channelHassEval bus) let workerDefinition = runMealy machine (runKatipContextT (busLogEnv bus) () ns . channelHassEval bus)
@@ -108,14 +109,14 @@ defaultMain = withSocketsDo $ do
rrdtool <- fromMaybe "rrdtool" <$> lookupEnv "HA_RRDTOOL" rrdtool <- fromMaybe "rrdtool" <$> lookupEnv "HA_RRDTOOL"
metricsPort <- lookupPort metricsPort <- lookupPort
writerLimiter <- slidingWindowLimiter rateLimitMetrics 10 20 writerLimiter <- slidingWindowLimiter rateLimitMetrics 10 20
let active = [c | c@(Controller _ _ True) <- controllers] let active = [c | c@(Controller True _ _) <- controllers]
ents = foldMap (\(Controller _ m _) -> entities m) active ents = foldMap (\(Controller _ _ m) -> entities m) active
workers = workers =
[ ("reader", readerAction host 8123 token ents bus) [ ("reader", readerAction host 8123 token ents bus)
, ("writer", writerAction writerLimiter bus) , ("writer", writerAction writerLimiter bus)
, ("metrics", HomeAssistant.Runtime.Metrics.metricsAction store rrdPath rrdtool) , ("metrics", HomeAssistant.Runtime.Metrics.metricsAction store rrdPath rrdtool)
, ("metrics-http", HomeAssistant.Runtime.Graphing.graphAction rrdPath rrdtool metricsPort) , ("metrics-http", HomeAssistant.Runtime.Graphing.graphAction rrdPath rrdtool metricsPort)
] ++ [ (name, runController rootPath bus c) | c@(Controller name _ True) <- controllers ] ] ++ [ (name, runController rootPath bus c) | c@(Controller True name _) <- active ]
runKatipContextT (busLogEnv bus) () mempty $ runKatipContextT (busLogEnv bus) () mempty $
mapConcurrently_ (uncurry supervised) workers mapConcurrently_ (uncurry supervised) workers