You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.2 KiB
191 lines
4.2 KiB
package hue
|
|
|
|
import (
|
|
"git.aiterp.net/lucifer/new-server/models"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type hueLightState struct {
|
|
index int
|
|
uniqueID string
|
|
externalID int
|
|
info LightData
|
|
input LightStateInput
|
|
|
|
stale bool
|
|
}
|
|
|
|
func (s *hueLightState) Update(state models.DeviceState) {
|
|
input := LightStateInput{}
|
|
if state.Power {
|
|
input.On = ptrBool(true)
|
|
if state.Color.IsKelvin() {
|
|
input.CT = ptrInt(1000000 / state.Color.Kelvin)
|
|
if s.input.CT == nil || *s.input.CT != *input.CT {
|
|
s.stale = true
|
|
}
|
|
} else {
|
|
input.Hue = ptrInt(int(state.Color.Hue*(65536/360)) % 65536)
|
|
if s.input.Hue == nil || *s.input.Hue != *input.Hue {
|
|
s.stale = true
|
|
}
|
|
|
|
input.Sat = ptrInt(int(state.Color.Hue * 255))
|
|
if *input.Sat > 254 {
|
|
*input.Sat = 254
|
|
}
|
|
if *input.Sat < 0 {
|
|
*input.Sat = 0
|
|
}
|
|
if s.input.Sat == nil || *s.input.Sat != *input.Sat {
|
|
s.stale = true
|
|
}
|
|
}
|
|
|
|
input.Bri = ptrInt(int(state.Intensity * 255))
|
|
if *input.Bri > 254 {
|
|
*input.Bri = 254
|
|
} else if *input.Bri < 0 {
|
|
*input.Bri = 0
|
|
}
|
|
|
|
if s.input.Bri == nil || *s.input.Bri != *input.Bri {
|
|
s.stale = true
|
|
}
|
|
} else {
|
|
input.On = ptrBool(false)
|
|
}
|
|
|
|
if s.input.On == nil || *s.input.On != *input.On {
|
|
s.stale = true
|
|
}
|
|
|
|
input.TransitionTime = ptrInt(1)
|
|
|
|
s.input = input
|
|
}
|
|
|
|
func (s *hueLightState) CheckStaleness(state LightState) {
|
|
if state.ColorMode == "xy" {
|
|
s.stale = true
|
|
if s.input.CT == nil && s.input.Hue == nil {
|
|
s.input.Hue = ptrInt(state.Hue)
|
|
s.input.Sat = ptrInt(state.Sat)
|
|
s.input.Bri = ptrInt(state.Bri)
|
|
}
|
|
return
|
|
} else if state.ColorMode == "ct" {
|
|
if s.input.CT == nil || state.CT != *s.input.CT {
|
|
s.stale = true
|
|
}
|
|
} else {
|
|
if s.input.Hue == nil || state.Hue != *s.input.Hue || s.input.Sat == nil || state.Sat == *s.input.Sat {
|
|
s.stale = true
|
|
}
|
|
}
|
|
}
|
|
|
|
type hueSensorState struct {
|
|
index int
|
|
externalID int
|
|
uniqueID string
|
|
prevData *SensorData
|
|
prevTime time.Time
|
|
presenceCooldown bool
|
|
}
|
|
|
|
func (state *hueSensorState) Update(newData SensorData) *models.Event {
|
|
stateTime, err := time.ParseInLocation("2006-01-02T15:04:05", newData.State.LastUpdated, time.UTC)
|
|
if err != nil {
|
|
// Invalid time is probably "none".
|
|
return nil
|
|
}
|
|
|
|
defer func() {
|
|
state.prevData = &newData
|
|
state.prevTime = stateTime
|
|
}()
|
|
|
|
if state.uniqueID != newData.UniqueID {
|
|
state.uniqueID = newData.UniqueID
|
|
}
|
|
|
|
if state.prevData != nil && newData.Type != state.prevData.Type {
|
|
return nil
|
|
}
|
|
|
|
switch newData.Type {
|
|
case "ZLLSwitch":
|
|
{
|
|
if time.Since(stateTime) > time.Second*3 {
|
|
return nil
|
|
}
|
|
|
|
pe := state.prevData.State.ButtonEvent
|
|
ce := newData.State.ButtonEvent
|
|
|
|
td := stateTime.Sub(state.prevTime) >= time.Second
|
|
pIdx := (pe / 1000) - 1
|
|
cIdx := (ce / 1000) - 1
|
|
pBtn := pe % 1000 / 2 // 0 = pressed, 1 = released
|
|
cBtn := ce % 1000 / 2 // 0 = pressed, 1 = released
|
|
|
|
if pIdx == cIdx && cBtn == 1 && pBtn == 0 {
|
|
// Do not allow 4002 after 4000.
|
|
// 4000 after 4002 is fine, though.
|
|
break
|
|
}
|
|
|
|
if cBtn != pBtn || cIdx != pIdx || td {
|
|
return &models.Event{
|
|
Name: models.ENButtonPressed,
|
|
Payload: map[string]string{
|
|
"buttonIndex": strconv.Itoa(cIdx),
|
|
"buttonName": buttonNames[cIdx],
|
|
"deviceId": strconv.Itoa(state.externalID),
|
|
"hueButtonEvent": strconv.Itoa(ce),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
case "ZLLPresence":
|
|
{
|
|
// TODO: Test this!
|
|
if state.prevData != nil && state.prevData.State.Presence != newData.State.Presence {
|
|
name := models.ENSensorPresenceStarted
|
|
if !newData.State.Presence {
|
|
name = models.ENSensorPresenceEnding
|
|
state.presenceCooldown = true
|
|
}
|
|
|
|
return &models.Event{
|
|
Name: name,
|
|
Payload: map[string]string{
|
|
"deviceId": strconv.Itoa(state.externalID),
|
|
"deviceInternalId": newData.UniqueID,
|
|
},
|
|
}
|
|
} else if state.presenceCooldown && !newData.State.Presence && time.Since(stateTime) > time.Minute {
|
|
state.presenceCooldown = false
|
|
return &models.Event{
|
|
Name: models.ENSensorPresenceEnded,
|
|
Payload: map[string]string{
|
|
"deviceId": strconv.Itoa(state.externalID),
|
|
"deviceInternalId": newData.UniqueID,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ptrBool(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func ptrInt(v int) *int {
|
|
return &v
|
|
}
|