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.
221 lines
5.5 KiB
221 lines
5.5 KiB
package models
|
|
|
|
import (
|
|
"context"
|
|
"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"`
|
|
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 ColorValue `json:"color,omitempty"`
|
|
Intensity float64 `json:"intensity,omitempty"`
|
|
Temperature float64 `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 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) 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 = "ColorHS"
|
|
DCColorHSK DeviceCapability = "ColorHSK"
|
|
DCColorKelvin DeviceCapability = "ColorKelvin"
|
|
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 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) 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 := ParseColorValue(*newState.Color)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if (parsed.IsKelvin() && d.HasCapability(DCColorKelvin, DCColorHSK)) || (parsed.IsHueSat() && d.HasCapability(DCColorHS)) {
|
|
d.State.Color = parsed
|
|
}
|
|
}
|
|
|
|
if newState.Intensity != nil && d.HasCapability(DCIntensity) {
|
|
d.State.Intensity = *newState.Intensity
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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 := ParseColorValue(*s.Color)
|
|
oc, err2 := ParseColorValue(*other.Color)
|
|
if err == nil && err2 == nil {
|
|
rc := ColorValue{}
|
|
rc.Hue = interpolateFloat(sc.Hue, oc.Hue, fac)
|
|
rc.Saturation = interpolateFloat(sc.Saturation, oc.Saturation, fac)
|
|
rc.Kelvin = interpolateInt(sc.Kelvin, oc.Kelvin, 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))
|
|
}
|