Skip to content

Commit

Permalink
Allow zero as a value for color temperature and brightness. This will…
Browse files Browse the repository at this point in the history
… tell Kelvin to ignore these values (fixes #8).
  • Loading branch information
stefanwichmann committed Apr 2, 2017
1 parent 3f02060 commit 32f1469
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 63 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Imagine your lights shine in an energetic but not to bright blue color to get yo
# Features
- Adjust the color temperature and brightness of your lights based on the local sunrise and sunset times
- Define a fine grained daily schedule to fit your personal needs throughout the day
- Define a default startup color for your lights
- Define a default startup color and brightness for your lights
- Gradual light transitions you won't even notice
- Works with smart switches as well as conventional switches
- Respects manual light changes until a light is switched off and on again
Expand Down Expand Up @@ -117,8 +117,8 @@ The configuration contains the following fields:
| ---- | ----------- |
| bridge | This element contains the IP and username of your Philips Hue bridge. Both values are usually obtained automatically. If the lookup fails you can fill in this details by hand. |
| location | This element contains the latitude and longitude of your location on earth. Both values are determined by your public IP. If this fails, is inaccurate or you want to change it manually just fill in your own coordinates. |
| defaultColorTemperature | This default color temperature will be used between sunrise and sunset. Valid values are between 2000K and 6500K. See [Wikipedia](https://en.wikipedia.org/wiki/Color_temperature) for reference values. |
| defaultBrightness | This default brightness value will be used between sunrise and sunset. Valid values are between 0% and 100%. |
| defaultColorTemperature | This default color temperature will be used between sunrise and sunset. Valid values are between 2000K and 6500K. See [Wikipedia](https://en.wikipedia.org/wiki/Color_temperature) for reference values. If you set this value to 0 Kelvin will ignore the color temperature and you can change it manually.|
| defaultBrightness | This default brightness value will be used between sunrise and sunset. Valid values are between 0% and 100%. If you set this value to 0 Kelvin will ignore the brightness and you can change it manually.|
| beforeSunrise | This element contains a list of timestamps and their configuration you want to set between midnight and sunrise of any given day. The *time* value must follow the `XX:XXAM/PM` format. *colorTemperature* and *brightness* must follow the same rules as the default values. |
| afterSunset | This element contains a list of timestamps and their configuration you want to set between sunset and midnight of any given day. The *time* value must follow the `XX:XXAM/PM` format. *colorTemperature* and *brightness* must follow the same rules as the default values. |
| ignoredDeviceIDs | This element contains a list of device IDs which will be excluded from Kelvin's automatic adjustments. Kelvin will print all device IDs found on your bridge during startup. Just add them (separated by comma) to this list if you want to exclude them.|
Expand Down
5 changes: 5 additions & 0 deletions colorspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ package main
import "math"

func colorTemperatureToXYColor(t int) (float32, float32) {
// zero indicates values to ignore. Map these to {0,0}
if t == 0 {
return 0, 0
}

// http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html
var x float64
if t < 2000 {
Expand Down
29 changes: 15 additions & 14 deletions light.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (light *Light) updateCurrentLightState() error {
light.reachable = attr.State.Reachable
light.on = attr.State.On
light.currentLightState = lightStateFromHueValues(attr.State.Ct, attr.State.Xy, attr.State.Bri)

return nil
}

Expand All @@ -128,7 +129,8 @@ func (light *Light) update() error {
if !light.tracking {
log.Printf("💡 Light %s just appeared. Initializing state to %vK at %v%s\n", light.name, light.targetLightState.colorTemperature, light.targetLightState.brightness, "%")

// For initialization we set the state again and again for 10 seconds because during startup the zigbee communication is unstable
// For initialization we set the state again and again for 10 seconds
// because during startup the zigbee communication is unstable
for index := 0; index < 9; index++ {
light.setLightState(light.targetLightState)
}
Expand All @@ -149,14 +151,14 @@ func (light *Light) update() error {
}

// did the user manually change the light state?
if !light.currentLightState.equals(light.savedLightState) {
if !light.savedLightState.equals(light.currentLightState) {
log.Printf("💡 Light %s was manually changed - current: %+v - saved: %+v\n", light.name, light.currentLightState, light.savedLightState)
light.manually = true
return nil
}

// Update needed?
if light.currentLightState.equals(light.targetLightState) {
if light.targetLightState.equals(light.currentLightState) {
return nil
}

Expand All @@ -180,32 +182,31 @@ func (light *Light) update() error {

func (light *Light) setLightState(state LightState) (LightState, error) {
// Don't send repeated "On" as this slows the bridge down (see https://developers.meethue.com/faq-page #Performance)
var newLightState hue.SetLightState
var hueLightState hue.SetLightState
colorTemperature, color, brightness := state.convertValuesToHue()
if light.supportsXYColor {
newLightState.Xy = []float32{color[0], color[1]}
newLightState.Ct = strconv.Itoa(colorTemperature)
} else if light.supportsColorTemperature {
newLightState.Ct = strconv.Itoa(colorTemperature)
if light.supportsXYColor && state.color[0] != 0 && state.color[1] != 0 {
hueLightState.Xy = []float32{color[0], color[1]}
} else if light.supportsColorTemperature && state.colorTemperature != 0 {
hueLightState.Ct = strconv.Itoa(colorTemperature)
}
if light.dimmable {
newLightState.Bri = strconv.Itoa(brightness)
if light.dimmable && state.brightness != 0 {
hueLightState.Bri = strconv.Itoa(brightness)
}
newLightState.TransitionTime = strconv.Itoa(lightTransitionIntervalInSeconds * 10) // conversion to 100ms-value
hueLightState.TransitionTime = strconv.Itoa(lightTransitionIntervalInSeconds * 10) // conversion to 100ms-value

results, err := light.hueLight.SetState(newLightState)
results, err := light.hueLight.SetState(hueLightState)
if err != nil {
return LightState{0, []float32{0, 0}, 0}, err
}

// iterate over result to acquire set values
color = []float32{} // clear old color values
for _, result := range results {
for key, value := range result.Success {
path := strings.Split(key, "/")

switch path[len(path)-1] {
case "xy":
color = []float32{} // clear old color values
for _, elem := range value.([]interface{}) {
color = append(color, float32(elem.(float64)))
}
Expand Down
132 changes: 86 additions & 46 deletions lightstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,77 +32,117 @@ type LightState struct {
}

func (lightstate *LightState) equals(state LightState) bool {
// if comparing light states always prefer color comparison
if (lightstate.color[0] != 0 && lightstate.color[1] != 0) || (state.color[0] != 0 && state.color[1] != 0) {
diffx := math.Abs(float64(lightstate.color[0] - state.color[0]))
diffy := math.Abs(float64(lightstate.color[1] - state.color[1]))
diffbri := math.Abs(float64(lightstate.brightness - state.brightness))
var sameColor = false
var sameColorTemperature = false
var sameBrightness = false

if diffx < 0.001 && diffy < 0.001 && diffbri < 3 {
return true
// compare color values
currentX := lightstate.color[0]
currentY := lightstate.color[1]
if currentX == 0 && currentY == 0 {
// zero value implies ignore color
sameColor = true
} else {
diffx := math.Abs(float64(currentX - state.color[0]))
diffy := math.Abs(float64(currentY - state.color[1]))
if diffx < 0.001 && diffy < 0.001 {
sameColor = true
}
return false
}

if lightstate.colorTemperature != state.colorTemperature || lightstate.brightness != state.brightness {
return false
// compare color temperature
if lightstate.colorTemperature == 0 {
// zero value implies ignore color temperature
sameColorTemperature = true
} else {
diffTemperature := math.Abs(float64(lightstate.colorTemperature - state.colorTemperature))
if diffTemperature < 3 {
sameColorTemperature = true
}
}
return true

// compare brightness
if lightstate.brightness == 0 {
// zero value implies ignore brightness
sameBrightness = true
} else {
diffBrightness := math.Abs(float64(lightstate.brightness - state.brightness))
if diffBrightness < 3 {
sameBrightness = true
}
}

// check if equal and prefer same color over same color temperature
if sameColor && sameBrightness {
return true
}
if sameColorTemperature && sameBrightness {
return true
}
return false
}

func (lightstate *LightState) convertValuesToHue() (int, []float32, int) {
var hueColorTemperature = 0
var hueBrightness = 0

// color temperature
if lightstate.colorTemperature > 6500 {
lightstate.colorTemperature = 6500
} else if lightstate.colorTemperature < 2000 {
lightstate.colorTemperature = 2000
if lightstate.colorTemperature != 0 {
if lightstate.colorTemperature > 6500 {
lightstate.colorTemperature = 6500
} else if lightstate.colorTemperature < 2000 {
lightstate.colorTemperature = 2000
}
hueColorTemperature = int((float64(1) / float64(lightstate.colorTemperature)) * float64(1000000))
}
hueColor := (float64(1) / float64(lightstate.colorTemperature)) * float64(1000000)

// brightness
if lightstate.brightness > 100 {
lightstate.brightness = 100
} else if lightstate.brightness < 0 {
lightstate.brightness = 0
}
hueBrightness := (float64(lightstate.brightness) / float64(100)) * float64(254)

// map temperature to xy if not set
x := lightstate.color[0]
y := lightstate.color[1]
if x == 0 || y == 0 {
x, y = colorTemperatureToXYColor(lightstate.colorTemperature)
if lightstate.brightness != 0 {
if lightstate.brightness > 100 {
lightstate.brightness = 100
} else if lightstate.brightness < 0 {
lightstate.brightness = 0
}
hueBrightness = int((float64(lightstate.brightness) / float64(100)) * float64(254))
}

return int(hueColor), []float32{float32(x), float32(y)}, int(hueBrightness)
// xy color should not need a mapping
return hueColorTemperature, lightstate.color, hueBrightness
}

func lightStateFromHueValues(colorTemperature int, color []float32, brightness int) LightState {
// color temperature
newColorTemperature := int(float64(1000000) / float64(colorTemperature))
var stateColorTemperature = 0
var stateColor = []float32{0, 0}
var stateBrightness = 0

if newColorTemperature > 6500 {
newColorTemperature = 6500
} else if newColorTemperature < 2000 {
newColorTemperature = 2000
// color temperature
if colorTemperature != 0 {
stateColorTemperature = int(float64(1000000) / float64(colorTemperature))
if stateColorTemperature > 6500 {
stateColorTemperature = 6500
} else if stateColorTemperature < 2000 {
stateColorTemperature = 2000
}
}

// color
if len(color) != 2 || color[0] == 0 || color[1] == 0 {
if len(color) != 2 {
// color is not properly initialized. Since we need it
// for state comparison we need to provide a valid state
x, y := colorTemperatureToXYColor(newColorTemperature)
color = []float32{float32(x), float32(y)}
x, y := colorTemperatureToXYColor(stateColorTemperature)
stateColor = []float32{float32(x), float32(y)}
} else {
stateColor = color
}

// brightness
newBrightness := (float64(brightness) / float64(254)) * float64(100)

if newBrightness > 100 {
newBrightness = 100
} else if newBrightness < 0 {
newBrightness = 0
if brightness != 0 {
stateBrightness = int((float64(brightness) / float64(254)) * float64(100))
if stateBrightness > 100 {
stateBrightness = 100
} else if stateBrightness < 0 {
stateBrightness = 0
}
}

return LightState{int(newColorTemperature), color, int(newBrightness)}
return LightState{stateColorTemperature, stateColor, stateBrightness}
}

0 comments on commit 32f1469

Please sign in to comment.