go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/experiments/huectl/pkg/hue/group.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package hue 9 10 import ( 11 "context" 12 "errors" 13 "time" 14 ) 15 16 // Group represents a bridge group https://developers.meethue.com/documentation/groups-api 17 type Group struct { 18 bridge Bridge 19 20 ID int `json:"id,omitempty"` 21 22 Class string `json:"class,omitempty"` 23 GroupState GroupState `json:"state,omitempty"` 24 Lights []string `json:"lights,omitempty"` 25 Locations map[string][]float64 `json:"locations,omitempty"` 26 Name string `json:"name,omitempty"` 27 Recycle bool `json:"recycle,omitempty"` 28 State LightState `json:"action,omitempty"` 29 Stream Stream `json:"stream,omitempty"` 30 Type string `json:"type,omitempty"` 31 } 32 33 // GroupState defines the state on a group. 34 // Can be used to control the state of all lights in a group rather than controlling them individually 35 type GroupState struct { 36 AllOn bool `json:"all_on,omitempty"` 37 AnyOn bool `json:"any_on,omitempty"` 38 } 39 40 // Stream define the stream status of a group 41 type Stream struct { 42 ProxyMode string `json:"proxymode,omitempty"` 43 ProxyNode string `json:"proxynode,omitempty"` 44 ActiveRaw *bool `json:"active,omitempty"` 45 OwnerRaw *string `json:"owner,omitempty"` 46 } 47 48 // Active returns the stream active state, and will return false if ActiveRaw is nil 49 func (s *Stream) Active() bool { 50 if s.ActiveRaw == nil { 51 return false 52 } 53 54 return *s.ActiveRaw 55 } 56 57 // Owner returns the stream Owner, and will return an empty string if OwnerRaw is nil 58 func (s *Stream) Owner() string { 59 if s.OwnerRaw == nil { 60 return "" 61 } 62 63 return *s.OwnerRaw 64 } 65 66 // SetState sets the state of the group to s. 67 func (g *Group) SetState(ctx context.Context, s LightState) error { 68 _, err := g.bridge.SetGroupState(ctx, g.ID, s) 69 if err != nil { 70 return err 71 } 72 g.State = s 73 return nil 74 } 75 76 // Rename sets the name property of the group 77 func (g *Group) Rename(ctx context.Context, new string) error { 78 update := Group{Name: new} 79 _, err := g.bridge.UpdateGroup(ctx, g.ID, update) 80 if err != nil { 81 return err 82 } 83 g.Name = new 84 return nil 85 } 86 87 // Off sets the On state of one group to false, turning all lights in the group off 88 func (g *Group) Off(ctx context.Context) error { 89 state := LightState{On: false} 90 _, err := g.bridge.SetGroupState(ctx, g.ID, state) 91 if err != nil { 92 return err 93 } 94 g.State.On = false 95 return nil 96 } 97 98 // On sets the On state of one group to true, turning all lights in the group on 99 func (g *Group) On(ctx context.Context) error { 100 state := LightState{On: true} 101 _, err := g.bridge.SetGroupState(ctx, g.ID, state) 102 if err != nil { 103 return err 104 } 105 g.State.On = true 106 return nil 107 } 108 109 // IsOn returns true if light state On property is true 110 func (g *Group) IsOn() bool { 111 return g.State.On 112 } 113 114 // Brightness sets the light brightness state property 115 func (g *Group) Brightness(ctx context.Context, new uint8) error { 116 update := LightState{On: true, Bri: new} 117 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 118 if err != nil { 119 return err 120 } 121 g.State.Bri = new 122 g.State.On = true 123 return nil 124 } 125 126 // Hue sets the light hue state property (0-65535) 127 func (g *Group) Hue(ctx context.Context, new uint16) error { 128 update := LightState{On: true, Hue: new} 129 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 130 if err != nil { 131 return err 132 } 133 g.State.Hue = new 134 g.State.On = true 135 return nil 136 } 137 138 // Saturation sets the light saturation state property (0-254) 139 func (g *Group) Saturation(ctx context.Context, new uint8) error { 140 update := LightState{On: true, Sat: new} 141 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 142 if err != nil { 143 return err 144 } 145 g.State.Sat = new 146 g.State.On = true 147 return nil 148 } 149 150 // Xy sets the x and y coordinates of a color in CIE color space. (0-1 per value) 151 func (g *Group) Xy(ctx context.Context, new []float32) error { 152 update := LightState{On: true, Xy: new} 153 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 154 if err != nil { 155 return err 156 } 157 g.State.Xy = new 158 g.State.On = true 159 return nil 160 } 161 162 // ColorTemperature sets the light color temperature state property 163 func (g *Group) ColorTemperature(ctx context.Context, new uint16) error { 164 update := LightState{On: true, Ct: new} 165 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 166 if err != nil { 167 return err 168 } 169 g.State.Ct = new 170 g.State.On = true 171 return nil 172 } 173 174 // Color sets the light color as RGB (will be converted to xy) 175 func (g *Group) Color(ctx context.Context, c Color) error { 176 xy := c.XY(GamutC) 177 178 update := LightState{ 179 On: true, 180 Bri: g.State.Bri, 181 Xy: xy, 182 } 183 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 184 if err != nil { 185 return err 186 } 187 g.State.Xy = xy 188 g.State.On = true 189 return nil 190 } 191 192 // ColorTransition sets the light color as RGB (will be converted to xy) with a given transition time. 193 func (g *Group) ColorTransition(ctx context.Context, c Color, transition time.Duration) error { 194 xy := c.XY(GamutC) 195 196 update := LightState{ 197 On: true, 198 Bri: g.State.Bri, 199 Xy: xy, 200 TransitionTime: uint16(transition / (100 * time.Millisecond)), 201 } 202 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 203 if err != nil { 204 return err 205 } 206 g.State.Xy = xy 207 g.State.On = true 208 return nil 209 } 210 211 // Scene sets the scene by it's identifier of the scene you wish to recall 212 func (g *Group) Scene(ctx context.Context, scene string) error { 213 update := LightState{On: true, Scene: scene} 214 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 215 if err != nil { 216 return err 217 } 218 g.State.Scene = scene 219 g.State.On = true 220 return nil 221 } 222 223 // TransitionTimeContext sets the duration of the transition from the light’s current state to the new state 224 func (g *Group) TransitionTimeContext(ctx context.Context, new uint16) error { 225 update := LightState{On: g.State.On, TransitionTime: new} 226 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 227 if err != nil { 228 return err 229 } 230 g.State.TransitionTime = new 231 return nil 232 } 233 234 // EffectContext the dynamic effect of the lights in the group, currently “none” and “colorloop” are supported 235 func (g *Group) EffectContext(ctx context.Context, new string) error { 236 update := LightState{On: true, Effect: new} 237 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 238 if err != nil { 239 return err 240 } 241 g.State.Effect = new 242 g.State.On = true 243 return nil 244 } 245 246 // AlertContext makes the lights in the group blink in its current color. Supported values are: 247 // “none” – The light is not performing an alert effect. 248 // “select” – The light is performing one breathe cycle. 249 // “lselect” – The light is performing breathe cycles for 15 seconds or until alert is set to "none". 250 func (g *Group) AlertContext(ctx context.Context, new string) error { 251 update := LightState{On: true, Alert: new} 252 _, err := g.bridge.SetGroupState(ctx, g.ID, update) 253 if err != nil { 254 return err 255 } 256 g.State.Effect = new 257 g.State.On = true 258 return nil 259 } 260 261 // EnableStreamingContext enables streaming for the group by setting the Stream Active property to true 262 func (g *Group) EnableStreamingContext(ctx context.Context) error { 263 if g.Type != "Entertainment" { 264 return errors.New("must be an entertainment group to enable streaming") 265 } 266 267 active := true 268 update := Group{ 269 Stream: Stream{ 270 ActiveRaw: &active, 271 }, 272 } 273 _, err := g.bridge.UpdateGroup(ctx, g.ID, update) 274 if err != nil { 275 return err 276 } 277 278 g.Stream.ActiveRaw = &active 279 g.Stream.OwnerRaw = &g.bridge.Username 280 return nil 281 } 282 283 // DisableStreamingContext disabled streaming for the group by setting the Stream Active property to false 284 func (g *Group) DisableStreamingContext(ctx context.Context) error { 285 if g.Type != "Entertainment" { 286 return errors.New("must be an entertainment group to disable streaming") 287 } 288 289 active := false 290 update := Group{ 291 Stream: Stream{ 292 ActiveRaw: &active, 293 }, 294 } 295 _, err := g.bridge.UpdateGroup(ctx, g.ID, update) 296 if err != nil { 297 return err 298 } 299 300 g.Stream.ActiveRaw = &active 301 g.Stream.OwnerRaw = nil 302 303 return nil 304 }