|
|
package hue
import ( "encoding/json" "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) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Index int `json:"index"` UniqueID string `json:"uniqueId"` ExternalID int `json:"externalId"` Info LightData `json:"info"` Input LightStateInput `json:"input"` Stale bool `json:"stale"` }{ Index: s.index, UniqueID: s.uniqueID, ExternalID: s.externalID, Info: s.info, Input: s.input, Stale: s.stale, }) }
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 *input.CT < s.info.Capabilities.Control.CT.Min { *input.CT = s.info.Capabilities.Control.CT.Min } if *input.CT > s.info.Capabilities.Control.CT.Max { *input.CT = s.info.Capabilities.Control.CT.Max }
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.Saturation * 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 s.input.On == nil || state.On != *s.input.On { s.stale = true } if !state.On { return }
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 int }
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": { // Ignore old events.
if time.Since(stateTime) > time.Second*3 { return nil } if state.prevData == nil { 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": { if state.prevData != nil && state.prevData.State.Presence != newData.State.Presence { if newData.State.Presence { state.presenceCooldown = -1
return &models.Event{ Name: models.ENSensorPresenceStarted, Payload: map[string]string{ "deviceId": strconv.Itoa(state.externalID), "deviceInternalId": newData.UniqueID, }, } } else { state.presenceCooldown = 0 } }
if state.presenceCooldown == -2 { state.presenceCooldown = int(time.Since(stateTime) / time.Minute) }
nextEventWait := time.Minute * time.Duration(state.presenceCooldown) if state.presenceCooldown != -1 && !newData.State.Presence && time.Since(stateTime) > nextEventWait { state.presenceCooldown += 1
return &models.Event{ Name: models.ENSensorPresenceEnded, Payload: map[string]string{ "deviceId": strconv.Itoa(state.externalID), "deviceInternalId": newData.UniqueID, "minutesElapsed": strconv.Itoa(state.presenceCooldown - 1), "secondsElapsed": strconv.Itoa((state.presenceCooldown - 1) * 60), "lastUpdated": strconv.FormatInt(stateTime.Unix(), 10), }, } } } case "ZLLTemperature": if time.Since(stateTime) > (time.Minute * 15) { return nil }
if !state.prevTime.Equal(stateTime) { return &models.Event{ Name: models.ENSensorTemperature, Payload: map[string]string{ "temperature": strconv.FormatFloat(float64(newData.State.Temperature)/100, 'f', 2, 64), "deviceId": strconv.Itoa(state.externalID), "deviceInternalId": newData.UniqueID, "lastUpdated": strconv.FormatInt(stateTime.Unix(), 10), }, } } }
return nil }
func ptrBool(v bool) *bool { return &v }
func ptrInt(v int) *int { return &v }
|