This commit is contained in:
2026-08-21 09:12:10 +03:00
parent 5f0aad4e85
commit cd29899fe4
7 changed files with 43 additions and 20 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{ mkDerivation, aeson, annotated-exception, async, base, bytestring { mkDerivation, aeson, annotated-exception, async, base, bytestring
, hedgehog, hspec, hspec-hedgehog, lens, lens-aeson, lib, network , hedgehog, hspec, hspec-hedgehog, lens, lens-aeson, lib, network
, stm, text, time, websockets , stm, text, time, uuid, websockets
}: }:
mkDerivation { mkDerivation {
pname = "home-assistant-controller"; pname = "home-assistant-controller";
@@ -10,7 +10,7 @@ mkDerivation {
isExecutable = true; isExecutable = true;
libraryHaskellDepends = [ libraryHaskellDepends = [
aeson annotated-exception async base bytestring lens lens-aeson aeson annotated-exception async base bytestring lens lens-aeson
network stm text time websockets network stm text time uuid websockets
]; ];
executableHaskellDepends = [ base ]; executableHaskellDepends = [ base ];
testHaskellDepends = [ testHaskellDepends = [
+1
View File
@@ -86,6 +86,7 @@ library
, stm , stm
, async , async
, annotated-exception , annotated-exception
, uuid
-- Directories containing source files. -- Directories containing source files.
hs-source-dirs: src hs-source-dirs: src
+15 -7
View File
@@ -8,9 +8,9 @@ module AFRP
, events , events
, switch , switch
, preMapAccum , preMapAccum
, preMapAccumUTCTime , preMapAccumRequest
, mapAccum , mapAccum
, mapAccumUTCTime , mapAccumRequest
, changes , changes
, whenA , whenA
, filterA , filterA
@@ -18,6 +18,7 @@ module AFRP
, (>>|) , (>>|)
, toEvent , toEvent
, lMerge , lMerge
, Request(..)
) where ) where
import Control.Category (Category(..), (>>>)) import Control.Category (Category(..), (>>>))
@@ -27,9 +28,16 @@ import Data.Time (UTCTime)
import Control.Monad.Fix (MonadFix (mfix)) import Control.Monad.Fix (MonadFix (mfix))
import Data.Either (fromLeft) import Data.Either (fromLeft)
import Data.Bool (bool) import Data.Bool (bool)
import Data.UUID (UUID)
data Request = Request
{ requestTime :: !UTCTime
, requestTraceId :: !UUID
}
deriving Show
newtype Mealy eff a b = Mealy newtype Mealy eff a b = Mealy
{ runMealy :: forall m. MonadFix m => (forall x. eff x -> m x) -> UTCTime -> a -> m (b, Mealy eff a b) } { runMealy :: forall m. MonadFix m => (forall x. eff x -> m x) -> Request -> a -> m (b, Mealy eff a b) }
eff :: (a -> eff b) -> Mealy eff a b eff :: (a -> eff b) -> Mealy eff a b
eff f = Mealy $ \nt _ x -> eff f = Mealy $ \nt _ x ->
@@ -101,8 +109,8 @@ preMapAccum f x extract = go x
let next = f b a let next = f b a
in pure (extract b, go next) in pure (extract b, go next)
preMapAccumUTCTime :: (UTCTime -> x -> a -> x) -> x -> (x -> b) -> Mealy eff a b preMapAccumRequest :: (Request -> x -> a -> x) -> x -> (x -> b) -> Mealy eff a b
preMapAccumUTCTime f x extract = go x preMapAccumRequest f x extract = go x
where where
go b = Mealy $ \_ t a -> go b = Mealy $ \_ t a ->
let next = f t b a let next = f t b a
@@ -115,8 +123,8 @@ mapAccum f x extract = go x
let next = f b a let next = f b a
in pure (extract next, go next) in pure (extract next, go next)
mapAccumUTCTime :: (UTCTime -> x -> a -> x) -> x -> (x -> b) -> Mealy eff a b mapAccumRequest :: (Request -> x -> a -> x) -> x -> (x -> b) -> Mealy eff a b
mapAccumUTCTime f x extract = go x mapAccumRequest f x extract = go x
where where
go b = Mealy $ \_ t a -> go b = Mealy $ \_ t a ->
let next = f t b a let next = f t b a
+8 -1
View File
@@ -25,10 +25,11 @@ module HomeAssistant.Controller
, Presence(..) , Presence(..)
, presence , presence
, debug , debug
, traceEvent
, switch , switch
) where ) where
import AFRP (Mealy, eff, Event(..), hold, events, changes, filterA, (>>|), toEvent) import AFRP (Mealy (..), eff, Event(..), hold, events, changes, filterA, (>>|), toEvent, Request)
import Control.Arrow (Arrow(..), returnA) import Control.Arrow (Arrow(..), returnA)
import Control.Category ((>>>)) import Control.Category ((>>>))
import Data.Aeson (Value) import Data.Aeson (Value)
@@ -49,6 +50,7 @@ data Service = Service
data HASSEff a where data HASSEff a where
CallService :: Service -> HASSEff () CallService :: Service -> HASSEff ()
Debug :: Show a => a -> HASSEff () Debug :: Show a => a -> HASSEff ()
Trace :: Show a => Request -> a -> HASSEff ()
type HASS a b = Mealy HASSEff a b type HASS a b = Mealy HASSEff a b
@@ -60,6 +62,11 @@ debug = proc x -> do
eff Debug -< x eff Debug -< x
returnA -< x returnA -< x
traceEvent :: Show a => HASS (Event a) (Event a)
traceEvent = Mealy $ \nt req -> \case
Event a -> nt (Trace req a) >>= \() -> pure (Event a, traceEvent)
Tick -> pure (Tick, traceEvent)
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
+7 -5
View File
@@ -56,6 +56,7 @@ data IkeaQuickButton
| ShortClickOff | ShortClickOff
| DoubleClickOff | DoubleClickOff
| LongClickOff | LongClickOff
deriving Show
toIkeaQuickButton :: T.Text -> Maybe IkeaQuickButton toIkeaQuickButton :: T.Text -> Maybe IkeaQuickButton
@@ -74,25 +75,26 @@ ikeaQuickButton entityId =
>>| arr (maybe (Left ()) Right . eventType) >>| arr (maybe (Left ()) Right . eventType)
>>> toEvent >>> toEvent
where where
eventType v = v ^? key "event" . key "data" . key "new_state" . key "state" . _String . to toIkeaQuickButton . traversed eventType v = v ^? key "event" . key "data" . key "new_state" . key "attributes" . key "event_type" . _String . to toIkeaQuickButton . traversed
data BedroomControls data BedroomControls
= Masse IkeaQuickButton = Masse IkeaQuickButton
| Enishen IkeaQuickButton | Enishen IkeaQuickButton
deriving Show
bedroomButton :: HASS (Event Value) (Event BedroomControls) bedroomButton :: HASS (Event Value) (Event BedroomControls)
bedroomButton = (masseButton &&& enishenButton) >>> arr (uncurry lMerge) bedroomButton = (masseButton &&& enishenButton) >>> arr (uncurry lMerge)
where where
masseButton = fmap Masse <$> ikeaQuickButton "event.bedroom_quick_remote_masse_action" masseButton = fmap Masse <$> ikeaQuickButton "event.bedroom_quick_remote_masse_action"
enishenButton = fmap Enishen <$> ikeaQuickButton "event.bedroom_quick_remote_jemina_action" enishenButton = fmap Enishen <$> ikeaQuickButton "event.bedroom_quick_jemina_action"
bedroomButtonController :: HASS (Event Value) () bedroomButtonController :: HASS (Event Value) ()
bedroomButtonController = proc x -> do bedroomButtonController = proc x -> do
ev <- bedroomButton -< x ev <- bedroomButton >>> traceEvent -< x
case ev of case ev of
Event (Masse ShortClickOn) -> callService (activateScene "scene.makuuhuone_masse") -< () Event (Masse ShortClickOn) -> callService (activateScene "scene.makuuhuone_masse") -< () -- this should be on release
Event (Masse DoubleClickOn) -> callService (activateScene "scene.makuuhuone_keski") -< () Event (Masse DoubleClickOn) -> callService (activateScene "scene.makuuhuone_keski") -< ()
Event (Masse LongClickOn) -> callService (activateScene "scene.makuuhuone_kirkas") -< () Event (Masse LongClickOn) -> callService (activateScene "scene.makuuhuone_kirkas") -< ()
Event (Enishen ShortClickOn) -> callService (activateScene "scene.makuuhuone_jemina") -< () Event (Enishen ShortClickOn) -> callService (activateScene "scene.makuuhuone_jemina") -< ()
@@ -114,4 +116,4 @@ bedroomDrawerController = proc x -> do
Event Closed -> callService (switch [entity] False) -< () Event Closed -> callService (switch [entity] False) -< ()
_ -> returnA -< () _ -> returnA -< ()
where where
entity = "switch.bedroom_drawer_light_masse" entity = "bedroom_drawer_light_masse"
+9 -5
View File
@@ -13,7 +13,7 @@ module HomeAssistant.Runtime
, runController , runController
) where ) where
import AFRP (Event (..), Mealy (..)) import AFRP (Event (..), Mealy (..), Request (..))
import Control.Concurrent.Async (async, waitAny) import Control.Concurrent.Async (async, waitAny)
import Control.Concurrent.STM (atomically, dupTChan, readTChan) import Control.Concurrent.STM (atomically, dupTChan, readTChan)
import Data.Aeson (Value) import Data.Aeson (Value)
@@ -27,11 +27,13 @@ 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, bedroomButtonController, bedroomDrawerController) import HomeAssistant.Controller.Bedroom (bedroomPresenceController, bedroomButtonController, bedroomDrawerController)
import Data.UUID (UUID)
import qualified Data.UUID.V4 as UUID.V4
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) -> UUID -> Mealy eff a b -> a -> IO (b, Mealy eff a b)
step nt (Mealy f) a = do step nt trace (Mealy f) a = do
now <- getCurrentTime now <- getCurrentTime
f nt now a f nt (Request now trace) a
data Controller = forall b. Controller T.Text (HASS (Event Value) b) data Controller = forall b. Controller T.Text (HASS (Event Value) b)
@@ -52,8 +54,9 @@ runController bus (Controller _name machine) = do
where where
go inbound f = do go inbound f = do
msg <- atomically (readTChan inbound) msg <- atomically (readTChan inbound)
uuid <- UUID.V4.nextRandom
-- (_, f') <- step (channelHassEval bus) f (Event msg) -- (_, f') <- step (channelHassEval bus) f (Event msg)
(_, f') <- step (dryRunHassEval (busGen bus)) f (Event msg) (_, f') <- step (dryRunHassEval (busGen bus)) uuid f (Event msg)
go inbound f' go inbound f'
defaultMain :: IO () defaultMain :: IO ()
@@ -74,3 +77,4 @@ dryRunHassEval gen = \case
callId <- generateCallId gen callId <- generateCallId gen
print (callId, x) print (callId, x)
Debug x -> print x Debug x -> print x
Trace req x -> print (req, x)
+1
View File
@@ -43,6 +43,7 @@ channelHassEval :: Bus -> HASSEff a -> IO a
channelHassEval bus = \case channelHassEval bus = \case
CallService svc -> atomically $ writeTChan (busOutbound bus) svc CallService svc -> atomically $ writeTChan (busOutbound bus) svc
Debug x -> print x Debug x -> print x
Trace req x -> print (req, x)
newtype CallIdGen = CallIdGen { generateCallId :: IO Int } newtype CallIdGen = CallIdGen { generateCallId :: IO Int }