github.com/amimof/huego@v1.2.1/light.go (about)

     1  package huego
     2  
     3  import (
     4  	"context"
     5  	"image/color"
     6  	"math"
     7  )
     8  
     9  // Light represents a bridge light https://developers.meethue.com/documentation/lights-api
    10  type Light struct {
    11  	State            *State `json:"state,omitempty"`
    12  	Type             string `json:"type,omitempty"`
    13  	Name             string `json:"name,omitempty"`
    14  	ModelID          string `json:"modelid,omitempty"`
    15  	ManufacturerName string `json:"manufacturername,omitempty"`
    16  	UniqueID         string `json:"uniqueid,omitempty"`
    17  	SwVersion        string `json:"swversion,omitempty"`
    18  	SwConfigID       string `json:"swconfigid,omitempty"`
    19  	ProductName      string `json:"productname,omitempty"`
    20  	ID               int    `json:"-"`
    21  	bridge           *Bridge
    22  }
    23  
    24  // State defines the attributes and properties of a light
    25  type State struct {
    26  	On             bool      `json:"on"`
    27  	Bri            uint8     `json:"bri,omitempty"`
    28  	Hue            uint16    `json:"hue,omitempty"`
    29  	Sat            uint8     `json:"sat,omitempty"`
    30  	Xy             []float32 `json:"xy,omitempty"`
    31  	Ct             uint16    `json:"ct,omitempty"`
    32  	Alert          string    `json:"alert,omitempty"`
    33  	Effect         string    `json:"effect,omitempty"`
    34  	TransitionTime uint16    `json:"transitiontime,omitempty"`
    35  	BriInc         int       `json:"bri_inc,omitempty"`
    36  	SatInc         int       `json:"sat_inc,omitempty"`
    37  	HueInc         int       `json:"hue_inc,omitempty"`
    38  	CtInc          int       `json:"ct_inc,omitempty"`
    39  	XyInc          int       `json:"xy_inc,omitempty"`
    40  	ColorMode      string    `json:"colormode,omitempty"`
    41  	Reachable      bool      `json:"reachable,omitempty"`
    42  	Scene          string    `json:"scene,omitempty"`
    43  }
    44  
    45  // NewLight defines a list of lights discovered the last time the bridge performed a light discovery.
    46  // Also stores the timestamp the last time a discovery was performed.
    47  type NewLight struct {
    48  	Lights   []string
    49  	LastScan string `json:"lastscan"`
    50  }
    51  
    52  // SetState sets the state of the light to s.
    53  func (l *Light) SetState(s State) error {
    54  	return l.SetStateContext(context.Background(), s)
    55  }
    56  
    57  // SetStateContext sets the state of the light to s.
    58  func (l *Light) SetStateContext(ctx context.Context, s State) error {
    59  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, s)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	l.State = &s
    64  	return nil
    65  }
    66  
    67  // Off sets the On state of one light to false, turning it off
    68  func (l *Light) Off() error {
    69  	return l.OffContext(context.Background())
    70  }
    71  
    72  // OffContext sets the On state of one light to false, turning it off
    73  func (l *Light) OffContext(ctx context.Context) error {
    74  	state := State{On: false}
    75  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, state)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	l.State.On = false
    80  	return nil
    81  }
    82  
    83  // On sets the On state of one light to true, turning it on
    84  func (l *Light) On() error {
    85  	return l.OnContext(context.Background())
    86  }
    87  
    88  // OnContext sets the On state of one light to true, turning it on
    89  func (l *Light) OnContext(ctx context.Context) error {
    90  	state := State{On: true}
    91  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, state)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	l.State.On = true
    96  	return nil
    97  }
    98  
    99  // IsOn returns true if light state On property is true
   100  func (l *Light) IsOn() bool {
   101  	return l.State.On
   102  }
   103  
   104  // Rename sets the name property of the light
   105  func (l *Light) Rename(new string) error {
   106  	return l.RenameContext(context.Background(), new)
   107  }
   108  
   109  // RenameContext sets the name property of the light
   110  func (l *Light) RenameContext(ctx context.Context, new string) error {
   111  	update := Light{Name: new}
   112  	_, err := l.bridge.UpdateLightContext(ctx, l.ID, update)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	l.Name = new
   117  	return nil
   118  }
   119  
   120  // Bri sets the light brightness state property
   121  func (l *Light) Bri(new uint8) error {
   122  	return l.BriContext(context.Background(), new)
   123  }
   124  
   125  // BriContext sets the light brightness state property
   126  func (l *Light) BriContext(ctx context.Context, new uint8) error {
   127  	update := State{On: true, Bri: new}
   128  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	l.State.Bri = new
   133  	l.State.On = true
   134  	return nil
   135  }
   136  
   137  // Hue sets the light hue state property (0-65535)
   138  func (l *Light) Hue(new uint16) error {
   139  	return l.HueContext(context.Background(), new)
   140  }
   141  
   142  // HueContext sets the light hue state property (0-65535)
   143  func (l *Light) HueContext(ctx context.Context, new uint16) error {
   144  	update := State{On: true, Hue: new}
   145  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	l.State.Hue = new
   150  	l.State.On = true
   151  	return nil
   152  }
   153  
   154  // Sat sets the light saturation state property (0-254)
   155  func (l *Light) Sat(new uint8) error {
   156  	return l.SatContext(context.Background(), new)
   157  }
   158  
   159  // SatContext sets the light saturation state property (0-254)
   160  func (l *Light) SatContext(ctx context.Context, new uint8) error {
   161  	update := State{On: true, Sat: new}
   162  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	l.State.Sat = new
   167  	l.State.On = true
   168  	return nil
   169  }
   170  
   171  // Xy sets the x and y coordinates of a color in CIE color space. (0-1 per value)
   172  func (l *Light) Xy(new []float32) error {
   173  	return l.XyContext(context.Background(), new)
   174  }
   175  
   176  // XyContext sets the x and y coordinates of a color in CIE color space. (0-1 per value)
   177  func (l *Light) XyContext(ctx context.Context, new []float32) error {
   178  	update := State{On: true, Xy: new}
   179  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	l.State.Xy = new
   184  	l.State.On = true
   185  	return nil
   186  }
   187  
   188  // Ct sets the light color temperature state property
   189  func (l *Light) Ct(new uint16) error {
   190  	return l.CtContext(context.Background(), new)
   191  }
   192  
   193  // CtContext sets the light color temperature state property
   194  func (l *Light) CtContext(ctx context.Context, new uint16) error {
   195  	update := State{On: true, Ct: new}
   196  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	l.State.Ct = new
   201  	l.State.On = true
   202  	return nil
   203  }
   204  
   205  // Col sets the light color as RGB (will be converted to xy)
   206  func (l *Light) Col(new color.Color) error {
   207  	return l.ColContext(context.Background(), new)
   208  }
   209  
   210  // ColContext sets the light color as RGB (will be converted to xy)
   211  func (l *Light) ColContext(ctx context.Context, new color.Color) error {
   212  	xy, bri := ConvertRGBToXy(new)
   213  
   214  	update := State{On: true, Xy: xy, Bri: bri}
   215  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	l.State.Xy = xy
   220  	l.State.Bri = bri
   221  	l.State.On = true
   222  	return nil
   223  }
   224  
   225  // TransitionTime sets the duration of the transition from the light’s current state to the new state
   226  func (l *Light) TransitionTime(new uint16) error {
   227  	return l.TransitionTimeContext(context.Background(), new)
   228  }
   229  
   230  // TransitionTimeContext sets the duration of the transition from the light’s current state to the new state
   231  func (l *Light) TransitionTimeContext(ctx context.Context, new uint16) error {
   232  	update := State{On: l.State.On, TransitionTime: new}
   233  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	l.State.TransitionTime = new
   238  	return nil
   239  }
   240  
   241  // Effect the dynamic effect of the light, currently “none” and “colorloop” are supported
   242  func (l *Light) Effect(new string) error {
   243  	return l.EffectContext(context.Background(), new)
   244  }
   245  
   246  // EffectContext the dynamic effect of the light, currently “none” and “colorloop” are supported
   247  func (l *Light) EffectContext(ctx context.Context, new string) error {
   248  	update := State{On: true, Effect: new}
   249  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   250  	if err != nil {
   251  		return err
   252  	}
   253  	l.State.Effect = new
   254  	l.State.On = true
   255  	return nil
   256  }
   257  
   258  // Alert makes the light blink in its current color. Supported values are:
   259  // “none” – The light is not performing an alert effect.
   260  // “select” – The light is performing one breathe cycle.
   261  // “lselect” – The light is performing breathe cycles for 15 seconds or until alert is set to "none".
   262  func (l *Light) Alert(new string) error {
   263  	return l.AlertContext(context.Background(), new)
   264  }
   265  
   266  // AlertContext makes the light blink in its current color. Supported values are:
   267  // “none” – The light is not performing an alert effect.
   268  // “select” – The light is performing one breathe cycle.
   269  // “lselect” – The light is performing breathe cycles for 15 seconds or until alert is set to "none".
   270  func (l *Light) AlertContext(ctx context.Context, new string) error {
   271  	update := State{On: true, Alert: new}
   272  	_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	l.State.Effect = new
   277  	l.State.On = true
   278  	return nil
   279  }
   280  
   281  // ConvertRGBToXy converts a given RGB color to the xy color of the ligth.
   282  // implemented as in https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
   283  func ConvertRGBToXy(newcolor color.Color) ([]float32, uint8) {
   284  	r, g, b, _ := newcolor.RGBA()
   285  	rf := float64(r) / 65536.0
   286  	gf := float64(g) / 65536.0
   287  	bf := float64(b) / 65536.0
   288  
   289  	rf = gammaCorrect(rf)
   290  	gf = gammaCorrect(gf)
   291  	bf = gammaCorrect(bf)
   292  
   293  	X := float32(rf*0.649926 + gf*0.103455 + bf*0.197109)
   294  	Y := float32(rf*0.234327 + gf*0.743075 + bf*0.022598)
   295  	Z := float32(rf*0.0000000 + gf*0.053077 + bf*1.035763)
   296  
   297  	x := X / (X + Y + Z)
   298  	y := Y / (X + Y + Z)
   299  
   300  	xy := []float32{x, y}
   301  	return xy, uint8(Y * 254)
   302  }
   303  
   304  func gammaCorrect(value float64) float64 {
   305  	if value > 0.04045 {
   306  		return math.Pow((value+0.055)/(1.0+0.055), 2.4)
   307  	}
   308  	return (value / 12.92)
   309  }