-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathWebUIREST.hs
184 lines (163 loc) · 7.14 KB
/
WebUIREST.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
{-# LANGUAGE OverloadedStrings, OverloadedLists #-}
module WebUIREST ( lightsSetState
, lightsSetScene
, lightsSwitchOnOff
, lightsBreatheCycle
, lightsColorLoop
, lightsChangeBrightness
, lightsSetColorXY
, lightsSetColorTemperature
, recallScene
, switchAllLights
) where
import Data.Monoid
import Data.Aeson
import Data.Vector ()
import qualified Data.HashMap.Strict as HM
import Control.Concurrent.STM
import Control.Concurrent.Async
import Control.Lens hiding ((<.>))
import Control.Monad
import Control.Monad.Reader
import Util
import PersistConfig
import HueJSON
import HueREST
import LightColor
-- Make a REST- API call in another thread to change the state on the bridge. The calls
-- are fire & forget, we don't retry in case of an error
-- http://www.developers.meethue.com/documentation/lights-api#16_set_light_state
lightsSetState :: (MonadIO m, ToJSON body, Show body)
=> IPAddress
-> BridgeUserID
-> [LightID]
-> body
-> m ()
lightsSetState bridgeIP userID lightIDs body =
void . liftIO . async $
forM_ lightIDs $ \lightID ->
bridgeRequestTrace
MethodPUT
bridgeIP
(Just body)
userID
("lights/" <> fromLightID lightID <> "/state")
lightsSetScene :: MonadIO m
=> IPAddress
-> BridgeUserID
-> Scene
-> m ()
lightsSetScene bridgeIP userID scene =
void . liftIO . async $
-- TODO: Maybe use the actual scene API instead of setting all light states one by one?
forM_ scene $ \(lightID, lightState) ->
bridgeRequestTrace
MethodPUT
bridgeIP
(Just lightState)
userID
("lights/" <> fromLightID lightID <> "/state")
lightsSwitchOnOff :: MonadIO m => IPAddress -> BridgeUserID -> [LightID] -> Bool -> m ()
lightsSwitchOnOff bridgeIP userID lightIDs onOff =
lightsSetState bridgeIP userID lightIDs $ HM.fromList [("on" :: String, onOff)]
lightsBreatheCycle :: MonadIO m => IPAddress -> BridgeUserID -> [LightID] -> m ()
lightsBreatheCycle bridgeIP userID lightIDs =
lightsSetState bridgeIP userID lightIDs $ HM.fromList [("alert" :: String, "select" :: String)]
filterLights :: MonadIO m => TVar Lights -> [LightID] -> (Light -> Bool) -> m [LightID]
filterLights lights' lightIDs p = do
lights <- liftIO . atomically $ readTVar lights'
let onAndColIDs = filter (maybe False p . flip HM.lookup lights) lightIDs
return onAndColIDs
-- Turn on the color loop effect for the specified lights
lightsColorLoop :: MonadIO m => IPAddress -> BridgeUserID -> TVar Lights -> [LightID] -> m ()
lightsColorLoop bridgeIP userID lights lightIDs = do
-- Can only change the color of lights which are turned on and support this feature
onAndColIDs <- filterLights
lights
lightIDs
(\l -> (l ^. lgtState . lsOn) && (l ^. lgtType . to isColorLT))
lightsSetState bridgeIP userID onAndColIDs $
HM.fromList [ ("effect" :: String, String "colorloop")
-- The effect uses the current saturation, make sure it
-- is at maximum or we might not see much color change
, ("sat" :: String, Number $ fromIntegral (254 :: Int))
]
lightsChangeBrightness :: MonadIO m
=> IPAddress
-> BridgeUserID
-> TVar Lights
-> [LightID]
-> Int
-> m ()
lightsChangeBrightness bridgeIP userID lights lightIDs change = do
-- Can only change the brightness of lights which are turned on and support this feature
onAndDimmableIDs <- filterLights
lights
lightIDs
(\l -> (l ^. lgtState . lsOn) && (l ^. lgtType . to isDimmableLT))
lightsSetState bridgeIP userID onAndDimmableIDs $ HM.fromList [("bri_inc" :: String, change)]
lightsSetColorXY :: MonadIO m
=> IPAddress
-> BridgeUserID
-> TVar Lights
-> [LightID]
-> Float
-> Float
-> m ()
lightsSetColorXY bridgeIP userID lights lightIDs xyX xyY = do
-- Can only change the color of lights which are turned on and support this feature
onAndColIDs <- filterLights
lights
lightIDs
(\l -> (l ^. lgtState . lsOn) && (l ^. lgtType . to isColorLT))
lightsSetState bridgeIP userID onAndColIDs $
HM.fromList [ -- Make sure 'colorloop' is disabled
--
-- TODO: This doesn't always seem to work, maybe we need to first
-- disable the color loop, then send the new color command?
--
("effect", String "none")
, ("xy" :: String, Array [Number $ realToFrac xyX, Number $ realToFrac xyY])
]
lightsSetColorTemperature :: MonadIO m
=> IPAddress
-> BridgeUserID
-> TVar Lights
-> [LightID]
-> Float
-> m ()
lightsSetColorTemperature bridgeIP userID lights lightIDs ctKelvin = do
-- Note that we only change pure color temperature lights here, not extended color lights
onAndCTIDs <- filterLights
lights
lightIDs
(\l -> (l ^. lgtState . lsOn) && (l ^. lgtType . to isCTOnlyLight))
lightsSetState bridgeIP userID onAndCTIDs $
HM.fromList
[ ("ct" :: String, Number $ fromIntegral (round $ kelvinToMirec ctKelvin :: Int)) ]
-- http://www.developers.meethue.com/documentation/groups-api#253_body_example
-- TODO: Version 1 scenes take a long time to have the state changes reported back from
-- the bridge, see
--
-- http://www.developers.meethue.com/content/
-- bug-delay-reporting-changed-light-state-when-recalling-scenes
recallScene :: MonadIO m => IPAddress -> BridgeUserID -> BridgeSceneID -> m ()
recallScene bridgeIP userID sceneID =
void . liftIO . async $
bridgeRequestTrace
MethodPUT
bridgeIP
(Just $ HM.fromList [("scene" :: String, sceneID)])
userID
("groups/0/action")
-- http://www.developers.meethue.com/documentation/groups-api#25_set_group_state
switchAllLights :: MonadIO m => IPAddress -> BridgeUserID -> Bool -> m ()
switchAllLights bridgeIP userID onOff =
let body = HM.fromList[("on" :: String, onOff)]
in void . liftIO . async $
bridgeRequestTrace
MethodPUT
bridgeIP
(Just body)
userID
"groups/0/action" -- Special group 0, all lights