|
|
package models
import ( "context" "git.aiterp.net/lucifer/new-server/internal/color" "git.aiterp.net/lucifer/new-server/internal/lerrors" "strings" "time" )
type Device struct { ID int `json:"id"` BridgeID int `json:"bridgeID"` InternalID string `json:"internalId"` Icon string `json:"icon"` Name string `json:"name"` Capabilities []DeviceCapability `json:"capabilities"` ButtonNames []string `json:"buttonNames"` DriverProperties map[string]interface{} `json:"driverProperties"` UserProperties map[string]string `json:"userProperties"` SceneAssignments []DeviceSceneAssignment `json:"sceneAssignments"` SceneState *DeviceState `json:"sceneState"` State DeviceState `json:"state"` Tags []string `json:"tags"` }
type DeviceUpdate struct { Icon *string `json:"icon"` Name *string `json:"name"` UserProperties map[string]*string `json:"userProperties"` }
// DeviceState contains optional state values that
// - Power: Whether the device is powered on
// - Color: Color value, if a color setting can be set on the device
// - Intensity: e.g. brightness, range 0..=1
// - Temperature: e.g. for thermostats
type DeviceState struct { Power bool `json:"power"` Color color.Color `json:"color,omitempty"` Intensity float64 `json:"intensity,omitempty"` Temperature int `json:"temperature"` }
type DeviceScene struct { SceneID int `json:"sceneId"` Time time.Time `json:"time"` DurationMS int64 `json:"duration"` Tag string `json:"tag"` }
type NewDeviceState struct { Power *bool `json:"power"` Color *string `json:"color"` Intensity *float64 `json:"intensity"` Temperature *int `json:"temperature"` }
type DeviceCapability string
type SaveMode int
const ( SMState SaveMode = 1 SMProperties SaveMode = 2 SMTags SaveMode = 4 )
type DeviceRepository interface { Find(ctx context.Context, id int) (*Device, error) FetchByReference(ctx context.Context, kind ReferenceKind, value string) ([]Device, error) Save(ctx context.Context, device *Device, mode SaveMode) error SaveMany(ctx context.Context, mode SaveMode, devices []Device) error Delete(ctx context.Context, device *Device) error }
func DeviceCapabilitiesToStrings(caps []DeviceCapability) []string { res := make([]string, 0, len(caps)) for _, cap := range caps { res = append(res, string(cap)) }
return res }
var ( DCPower DeviceCapability = "Power" DCColorHS DeviceCapability = "HueSat" DCColorHSK DeviceCapability = "ColorHSK" DCColorKelvin DeviceCapability = "ColorKelvin" DCColorXY DeviceCapability = "XY" DCColorRGB DeviceCapability = "RGB" DCButtons DeviceCapability = "Buttons" DCPresence DeviceCapability = "Presence" DCIntensity DeviceCapability = "Intensity" DCTemperatureControl DeviceCapability = "TemperatureControl" DCTemperatureSensor DeviceCapability = "TemperatureSensor" )
var Capabilities = []DeviceCapability{ DCPower, DCColorHS, DCColorKelvin, DCButtons, DCPresence, DCIntensity, DCTemperatureControl, DCTemperatureSensor, }
func (d *Device) ApplyUpdate(update DeviceUpdate) { if update.Name != nil { d.Name = *update.Name } if update.Icon != nil { d.Icon = *update.Icon } if d.UserProperties == nil { d.UserProperties = make(map[string]string) } for key, value := range update.UserProperties { if value != nil { d.UserProperties[key] = *value } else { delete(d.UserProperties, key) } } }
func (d *Device) Validate() error { d.Name = strings.Trim(d.Name, " \t\n ") if d.Name == "" { return lerrors.ErrInvalidName }
newCaps := make([]DeviceCapability, 0, len(d.Capabilities)) for _, currCap := range d.Capabilities { for _, validCap := range Capabilities { if currCap == validCap { newCaps = append(newCaps, currCap) break } } } d.Capabilities = newCaps
return nil }
func (d *Device) HasTag(tags ...string) bool { for _, c := range d.Tags { for _, c2 := range tags { if c == c2 { return true } } }
return false }
func (d *Device) IsOnlySensor() bool { return !d.HasCapability( DCPower, DCColorHS, DCColorHSK, DCColorKelvin, DCIntensity, DCTemperatureControl, ) }
func (d *Device) HasCapability(capabilities ...DeviceCapability) bool { for _, c := range d.Capabilities { for _, c2 := range capabilities { if c == c2 { return true } } }
return false }
func (d *Device) SetState(newState NewDeviceState) error { if newState.Power != nil && d.HasCapability(DCPower) { d.State.Power = *newState.Power }
if newState.Color != nil { parsed, err := color.Parse(*newState.Color) if err != nil { return err }
switch { case (parsed.RGB != nil || parsed.XY != nil || parsed.HS != nil) || d.HasCapability(DCColorHS, DCColorXY, DCColorRGB): d.State.Color = parsed case parsed.K != nil && d.HasCapability(DCColorKelvin, DCColorHSK): if !d.HasCapability(DCColorKelvin) { d.State.Color.HS = &color.HueSat{Hue: 0, Sat: 0} }
d.State.Color = parsed } }
if newState.Intensity != nil && d.HasCapability(DCIntensity) { d.State.Intensity = *newState.Intensity }
if newState.Temperature != nil && d.HasCapability(DCTemperatureControl) { d.State.Temperature = *newState.Temperature }
return nil }
func (s *NewDeviceState) RelativeTo(device Device) NewDeviceState { n := NewDeviceState{}
if s.Intensity != nil { intensity := device.State.Intensity * *s.Intensity n.Intensity = &intensity }
return n }
func (s *NewDeviceState) Interpolate(other NewDeviceState, fac float64) NewDeviceState { n := NewDeviceState{}
if s.Power != nil && other.Power != nil { if fac >= 0.5 { n.Power = other.Power } else { n.Power = s.Power } }
if s.Color != nil && other.Color != nil { sc, err := color.Parse(*s.Color) oc, err2 := color.Parse(*other.Color) if err == nil && err2 == nil { rc := sc.Interpolate(oc, fac) rcStr := rc.String() n.Color = &rcStr } }
if s.Intensity != nil && other.Intensity != nil { n.Intensity = new(float64) *n.Intensity = interpolateFloat(*s.Intensity, *other.Intensity, fac) }
return n }
func interpolateFloat(a, b, fac float64) float64 { return (a * (1 - fac)) + (b * fac) }
func interpolateInt(a, b int, fac float64) int { return int((float64(a) * (1 - fac)) + (float64(b) * fac)) }
|