IKEA buttons

This commit is contained in:
2026-08-20 23:40:14 +03:00
parent 2a1bca73d7
commit 4d689be6ea
4 changed files with 76 additions and 5 deletions
+8 -1
View File
@@ -17,6 +17,7 @@ module AFRP
, thenA , thenA
, (>>|) , (>>|)
, toEvent , toEvent
, lMerge
) where ) where
import Control.Category (Category(..), (>>>)) import Control.Category (Category(..), (>>>))
@@ -142,7 +143,13 @@ thenA f g = f >>> arr Left ||| g
(>>|) :: (ArrowChoice cat, Arrow cat) => cat a (Either b1 c) -> cat c (Either b1 b2) -> cat a (Either b1 b2) (>>|) :: (ArrowChoice cat, Arrow cat) => cat a (Either b1 c) -> cat c (Either b1 b2) -> cat a (Either b1 b2)
(>>|) = thenA (>>|) = thenA
infixl 1 >>| infixl 2 >>|
toEvent :: Mealy eff (Either () a) (Event a) toEvent :: Mealy eff (Either () a) (Event a)
toEvent = arr (either (const Tick) Event) toEvent = arr (either (const Tick) Event)
lMerge :: Event a -> Event a -> Event a
lMerge Tick Tick = Tick
lMerge (Event a) _ = Event a
lMerge Tick (Event a) = Event a
+7
View File
@@ -24,6 +24,7 @@ module HomeAssistant.Controller
, lightController , lightController
, Presence(..) , Presence(..)
, presence , presence
, debug
) where ) where
import AFRP (Mealy, eff, Event(..), hold, events, changes, filterA, (>>|), toEvent) import AFRP (Mealy, eff, Event(..), hold, events, changes, filterA, (>>|), toEvent)
@@ -46,12 +47,18 @@ data Service = Service
data HASSEff a where data HASSEff a where
CallService :: Service -> HASSEff () CallService :: Service -> HASSEff ()
Debug :: Show a => a -> HASSEff ()
type HASS a b = Mealy HASSEff a b type HASS a b = Mealy HASSEff a b
callService :: Service -> HASS a () callService :: Service -> HASS a ()
callService service = eff (\_ -> CallService service) callService service = eff (\_ -> CallService service)
debug :: Show a => HASS a a
debug = proc x -> do
eff Debug -< x
returnA -< x
ruuviTemperatures :: Mealy eff (Event Value) Double ruuviTemperatures :: Mealy eff (Event Value) Double
ruuviTemperatures = entityRead @Double "sensor.ruuvitag_b168_temperature" >>> hold 0 ruuviTemperatures = entityRead @Double "sensor.ruuvitag_b168_temperature" >>> hold 0
+56 -2
View File
@@ -4,9 +4,12 @@ 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 (..)) import AFRP (Event (..), (>>|), toEvent, lMerge)
import Data.Aeson (Value, object, (.=)) import Data.Aeson (Value, object, (.=))
import Control.Arrow ((>>>), returnA) import Control.Arrow ((>>>), returnA, arr, ArrowChoice (..), Arrow (..))
import Control.Lens ((^?), to, traversed)
import Data.Aeson.Lens (key, _String)
import qualified Data.Text.Lens as TL
@@ -45,3 +48,54 @@ bedroomPresenceController = proc x -> do
Event Occupied -> callService (activateScene "makuuhuone_lights_snapshot") -< () Event Occupied -> callService (activateScene "makuuhuone_lights_snapshot") -< ()
_ -> returnA -< () _ -> returnA -< ()
-- Ikea quick buttons are identified by different event types
data IkeaQuickButton
= ShortClickOn
| DoubleClickOn
| LongClickOn
| ShortClickOff
| DoubleClickOff
| LongClickOff
toIkeaQuickButton :: T.Text -> Maybe IkeaQuickButton
toIkeaQuickButton = \case
"1_initial_press" -> Just ShortClickOn
"1_double_press" -> Just DoubleClickOn
"1_long_press" -> Just LongClickOn
"2_initial_press" -> Just ShortClickOff
"2_double_press" -> Just DoubleClickOff
"2_long_press" -> Just LongClickOff
_ -> Nothing
ikeaQuickButton :: T.Text -> HASS (Event Value) (Event IkeaQuickButton)
ikeaQuickButton entityId =
entityChangeEvent' entityId
>>| arr (maybe (Left ()) Right . eventType)
>>> toEvent
where
eventType v = v ^? key "event" . key "data" . key "new_state" . key "state" . _String . to toIkeaQuickButton . traversed
data BedroomControls
= Masse IkeaQuickButton
| Enishen IkeaQuickButton
bedroomButton :: HASS (Event Value) (Event BedroomControls)
bedroomButton = (masseButton &&& enishenButton) >>> arr (uncurry lMerge)
where
masseButton = fmap Masse <$> ikeaQuickButton "event.bedroom_quick_remote_masse_action"
enishenButton = fmap Enishen <$> ikeaQuickButton "event.bedroom_quick_remote_jemina_action"
bedroomButtonController :: HASS (Event Value) ()
bedroomButtonController = proc x -> do
ev <- bedroomButton -< x
case ev of
Event (Masse ShortClickOn) -> callService (activateScene "scene.makuuhuone_masse") -< ()
Event (Masse DoubleClickOn) -> callService (activateScene "scene.makuuhuone_keski") -< ()
Event (Masse LongClickOn) -> callService (activateScene "scene.makuuhuone_kirkas") -< ()
Event (Enishen ShortClickOn) -> callService (activateScene "scene.makuuhuone_jemina") -< ()
Event (Enishen DoubleClickOn) -> callService (activateScene "scene.makuuhuone_keski") -< ()
Event (Enishen LongClickOn) -> callService (activateScene "scene.makuuhuone_kirkas") -< ()
_ -> returnA -< ()
+5 -2
View File
@@ -26,7 +26,7 @@ import HomeAssistant.Runtime.Connection (readerAction, writerAction)
import HomeAssistant.Runtime.Supervisor (defaultBackoff, supervised) import HomeAssistant.Runtime.Supervisor (defaultBackoff, supervised)
import Network.Socket (withSocketsDo) import Network.Socket (withSocketsDo)
import System.Environment (getEnv) import System.Environment (getEnv)
import HomeAssistant.Controller.Bedroom (bedroomPresenceController) import HomeAssistant.Controller.Bedroom (bedroomPresenceController, bedroomButtonController)
step :: (forall x. eff x -> IO x) -> Mealy eff a b -> a -> IO (b, Mealy eff a b) step :: (forall x. eff x -> IO x) -> Mealy eff a b -> a -> IO (b, Mealy eff a b)
step nt (Mealy f) a = do step nt (Mealy f) a = do
@@ -36,7 +36,10 @@ step nt (Mealy f) a = do
data Controller = forall b. Controller T.Text (HASS (Event Value) b) data Controller = forall b. Controller T.Text (HASS (Event Value) b)
controllers :: [Controller] controllers :: [Controller]
controllers = [Controller "bedroom-presence" bedroomPresenceController] controllers =
[ Controller "bedroom-presence" bedroomPresenceController
, Controller "bedroom-button" bedroomButtonController
]
-- | 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