Files
gisle f1ed6da6d7
continuous-integration/drone/push Build is passing
Squashed commit of the following:
commit 62e7cb88f4
Author: Gisle Aune <dev@gisle.me>
Date:   Mon Feb 28 22:52:06 2022 +0100

    refactor color system to be separate from models, also move errors out of models.

commit df865574bf
Author: Gisle Aune <dev@gisle.me>
Date:   Mon Feb 21 19:27:42 2022 +0100

    rework color system and make it work with drivers.
2022-02-28 22:52:25 +01:00

266 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}