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.
 
 
 
 

254 lines
6.0 KiB

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
}