add gradient and random effect.
This commit is contained in:
+11
-20
@@ -55,27 +55,15 @@ func main() {
|
|||||||
|
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
|
for i, id := range []string{"40e5", "dead", "beef", "cafe", "1337"} {
|
||||||
bus.RunEvent(events.HardwareState{
|
bus.RunEvent(events.HardwareState{
|
||||||
ID: "nanoleaf:10.80.1.11:40e5",
|
ID: "nanoleaf:10.80.1.11:" + id,
|
||||||
InternalName: "Hexagon 6",
|
InternalName: fmt.Sprintf("Hexagon %d", 6+i),
|
||||||
SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity,
|
|
||||||
ColorFlags: device.CFlagRGB,
|
|
||||||
State: device.State{},
|
|
||||||
})
|
|
||||||
bus.RunEvent(events.HardwareState{
|
|
||||||
ID: "nanoleaf:10.80.1.11:dead",
|
|
||||||
InternalName: "Hexagon 7",
|
|
||||||
SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity,
|
|
||||||
ColorFlags: device.CFlagRGB,
|
|
||||||
State: device.State{},
|
|
||||||
})
|
|
||||||
bus.RunEvent(events.HardwareState{
|
|
||||||
ID: "nanoleaf:10.80.1.11:beef",
|
|
||||||
InternalName: "Hexagon 8",
|
|
||||||
SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity,
|
SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity,
|
||||||
ColorFlags: device.CFlagRGB,
|
ColorFlags: device.CFlagRGB,
|
||||||
State: device.State{},
|
State: device.State{},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
@@ -90,22 +78,25 @@ func main() {
|
|||||||
|
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
|
c1 := gentools.Ptr(color.MustParse("rgb:#ff0000"))
|
||||||
|
c2 := gentools.Ptr(color.MustParse("rgb:#00ff00"))
|
||||||
bus.RunCommand(commands.Assign{
|
bus.RunCommand(commands.Assign{
|
||||||
Match: "**:Hexagon *",
|
Match: "**:Hexagon *",
|
||||||
Effect: effects.Pattern{
|
Effect: effects.Gradient{
|
||||||
States: []device.State{
|
States: []device.State{
|
||||||
{
|
{
|
||||||
Power: gentools.Ptr(true),
|
Power: gentools.Ptr(true),
|
||||||
Color: gentools.Ptr(color.MustParse("rgb:#ff0000")),
|
Color: c1,
|
||||||
Intensity: gentools.Ptr(1.0),
|
Intensity: gentools.Ptr(1.0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Power: gentools.Ptr(true),
|
Power: gentools.Ptr(true),
|
||||||
Color: gentools.Ptr(color.MustParse("rgb:#00ff00")),
|
Color: c2,
|
||||||
Intensity: gentools.Ptr(0.7),
|
Intensity: gentools.Ptr(0.7),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ShiftMS: 1000,
|
AnimationMS: 1000,
|
||||||
|
Interpolate: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+51
-9
@@ -3,6 +3,7 @@ package device
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.aiterp.net/lucifer3/server/internal/color"
|
"git.aiterp.net/lucifer3/server/internal/color"
|
||||||
|
"git.aiterp.net/lucifer3/server/internal/gentools"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,24 +14,65 @@ type State struct {
|
|||||||
Color *color.Color `json:"color"`
|
Color *color.Color `json:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d State) String() string {
|
func (s State) String() string {
|
||||||
parts := make([]string, 0, 4)
|
parts := make([]string, 0, 4)
|
||||||
if d.Power != nil {
|
if s.Power != nil {
|
||||||
parts = append(parts, fmt.Sprintf("power:%t", *d.Power))
|
parts = append(parts, fmt.Sprintf("power:%t", *s.Power))
|
||||||
}
|
}
|
||||||
if d.Temperature != nil {
|
if s.Temperature != nil {
|
||||||
parts = append(parts, fmt.Sprintf("temperature:%f", *d.Temperature))
|
parts = append(parts, fmt.Sprintf("temperature:%f", *s.Temperature))
|
||||||
}
|
}
|
||||||
if d.Intensity != nil {
|
if s.Intensity != nil {
|
||||||
parts = append(parts, fmt.Sprintf("intensity:%.2f", *d.Intensity))
|
parts = append(parts, fmt.Sprintf("intensity:%.2f", *s.Intensity))
|
||||||
}
|
}
|
||||||
if d.Color != nil {
|
if s.Color != nil {
|
||||||
parts = append(parts, fmt.Sprintf("color:%s", d.Color.String()))
|
parts = append(parts, fmt.Sprintf("color:%s", s.Color.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprint("(", strings.Join(parts, ", "), ")")
|
return fmt.Sprint("(", strings.Join(parts, ", "), ")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s State) Interpolate(s2 State, f float64) State {
|
||||||
|
newState := State{}
|
||||||
|
if s.Color != nil && s2.Color != nil {
|
||||||
|
newState.Color = gentools.Ptr(s.Color.Interpolate(*s2.Color, f))
|
||||||
|
} else if s.Color != nil {
|
||||||
|
newState.Color = gentools.ShallowCopy(s.Color)
|
||||||
|
} else if s2.Color != nil {
|
||||||
|
newState.Color = gentools.ShallowCopy(s2.Color)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Intensity != nil && s2.Intensity != nil {
|
||||||
|
newState.Intensity = gentools.Ptr((*s.Intensity * f) + (*s2.Intensity * (1.0 - f)))
|
||||||
|
} else if s.Intensity != nil {
|
||||||
|
newState.Intensity = gentools.ShallowCopy(s.Intensity)
|
||||||
|
} else if s2.Intensity != nil {
|
||||||
|
newState.Intensity = gentools.ShallowCopy(s2.Intensity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Temperature != nil && s2.Temperature != nil {
|
||||||
|
newState.Temperature = gentools.Ptr((*s.Temperature * f) + (*s2.Temperature * (1.0 - f)))
|
||||||
|
} else if s.Temperature != nil {
|
||||||
|
newState.Temperature = gentools.ShallowCopy(s.Temperature)
|
||||||
|
} else if s2.Temperature != nil {
|
||||||
|
newState.Temperature = gentools.ShallowCopy(s2.Temperature)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Power != nil && s2.Power != nil {
|
||||||
|
if f < 0.5 {
|
||||||
|
newState.Power = gentools.ShallowCopy(s.Power)
|
||||||
|
} else {
|
||||||
|
newState.Power = gentools.ShallowCopy(s2.Power)
|
||||||
|
}
|
||||||
|
} else if s.Power != nil {
|
||||||
|
newState.Power = gentools.ShallowCopy(s.Power)
|
||||||
|
} else if s2.Power != nil {
|
||||||
|
newState.Power = gentools.ShallowCopy(s2.Power)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
type PresenceState struct {
|
type PresenceState struct {
|
||||||
Present bool `json:"present"`
|
Present bool `json:"present"`
|
||||||
AbsenceSeconds float64 `json:"absenceSeconds,omitempty"`
|
AbsenceSeconds float64 `json:"absenceSeconds,omitempty"`
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package effects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gradient struct {
|
||||||
|
States []device.State `json:"states,omitempty"`
|
||||||
|
AnimationMS int64 `json:"AnimationMs,omitempty"`
|
||||||
|
Reverse bool `json:"backward,omitempty"`
|
||||||
|
Interpolate bool `json:"interpolate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Gradient) State(index, length, round, _ int) device.State {
|
||||||
|
if len(e.States) == 0 {
|
||||||
|
return device.State{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Reverse {
|
||||||
|
index = length - (index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
walkedIndex := (index + round) % length
|
||||||
|
indexFactor := math.Min(float64(walkedIndex)/float64(length-1), 1)
|
||||||
|
stateIncrement := 1.0 / float64(len(e.States)-1)
|
||||||
|
|
||||||
|
for i := range e.States {
|
||||||
|
a := float64(i) * stateIncrement
|
||||||
|
if indexFactor >= a {
|
||||||
|
si := e.States[i]
|
||||||
|
sj := e.States[(i+1)%len(e.States)]
|
||||||
|
f := (indexFactor - a) / stateIncrement
|
||||||
|
|
||||||
|
if f < 0 || f > 1 {
|
||||||
|
panic(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Interpolate {
|
||||||
|
return si.Interpolate(sj, f)
|
||||||
|
} else if f < 0.5 {
|
||||||
|
return si
|
||||||
|
} else {
|
||||||
|
return sj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.States[len(e.States)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Gradient) Frequency() time.Duration {
|
||||||
|
return time.Duration(e.AnimationMS) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Gradient) EffectDescription() string {
|
||||||
|
return fmt.Sprintf("Gradient(states:%s, anim:%dms, int:%t)", statesDescription(e.States), e.AnimationMS, e.Interpolate)
|
||||||
|
}
|
||||||
+2
-2
@@ -15,14 +15,14 @@ type Manual struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e Manual) EffectDescription() string {
|
func (e Manual) EffectDescription() string {
|
||||||
return fmt.Sprintf("Manual%s", e.State(0, 0, 0).String())
|
return fmt.Sprintf("Manual%s", e.State(0, 0, 0, 0).String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Manual) Frequency() time.Duration {
|
func (e Manual) Frequency() time.Duration {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Manual) State(int, int, int) device.State {
|
func (e Manual) State(int, int, int, int) device.State {
|
||||||
return device.State{
|
return device.State{
|
||||||
Power: e.Power,
|
Power: e.Power,
|
||||||
Temperature: e.Temperature,
|
Temperature: e.Temperature,
|
||||||
|
|||||||
+11
-7
@@ -8,17 +8,21 @@ import (
|
|||||||
|
|
||||||
type Pattern struct {
|
type Pattern struct {
|
||||||
States []device.State `json:"states,omitempty"`
|
States []device.State `json:"states,omitempty"`
|
||||||
ShiftMS int64 `json:"shiftMs,omitempty"`
|
AnimationMS int64 `json:"animationMs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pattern) State(index, round, _ int) device.State {
|
func (e Pattern) State(index, _, round, _ int) device.State {
|
||||||
return p.States[(index+round)%len(p.States)]
|
if len(e.States) == 0 {
|
||||||
|
return device.State{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pattern) Frequency() time.Duration {
|
return e.States[(index+round)%len(e.States)]
|
||||||
return time.Duration(p.ShiftMS) * time.Millisecond
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pattern) EffectDescription() string {
|
func (e Pattern) Frequency() time.Duration {
|
||||||
return fmt.Sprintf("Pattern(len:%d, animation:%dms)", len(p.States), p.ShiftMS)
|
return time.Duration(e.AnimationMS) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Pattern) EffectDescription() string {
|
||||||
|
return fmt.Sprintf("Pattern(states:%s, anim:%dms)", statesDescription(e.States), e.AnimationMS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package effects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Random struct {
|
||||||
|
States []device.State `json:"states,omitempty"`
|
||||||
|
Interpolate bool `json:"interpolate,omitempty"`
|
||||||
|
AnimationMS int64 `json:"animationMs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Random) State(_, _, _, _ int) device.State {
|
||||||
|
if len(e.States) == 0 {
|
||||||
|
return device.State{}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexFactor := rand.Float64()
|
||||||
|
stateIncrement := 1.0 / float64(len(e.States)-1)
|
||||||
|
|
||||||
|
for i := range e.States {
|
||||||
|
a := float64(i) * stateIncrement
|
||||||
|
if indexFactor >= a {
|
||||||
|
si := e.States[i]
|
||||||
|
sj := e.States[(i+1)%len(e.States)]
|
||||||
|
f := (indexFactor - a) / stateIncrement
|
||||||
|
|
||||||
|
if f < 0 || f > 1 {
|
||||||
|
panic(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Interpolate {
|
||||||
|
return si.Interpolate(sj, f)
|
||||||
|
} else if f < 0.5 {
|
||||||
|
return si
|
||||||
|
} else {
|
||||||
|
return sj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.States[len(e.States)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Random) Frequency() time.Duration {
|
||||||
|
return time.Duration(e.AnimationMS) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Random) EffectDescription() string {
|
||||||
|
return fmt.Sprintf("Random(states:%s, anim:%dms, int:%t)", statesDescription(e.States), e.AnimationMS, e.Interpolate)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package effects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statesDescription(states []device.State) string {
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.Grow(128)
|
||||||
|
|
||||||
|
sb.WriteRune('[')
|
||||||
|
for i, state := range states {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(state.String())
|
||||||
|
}
|
||||||
|
sb.WriteRune(']')
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
+2
-1
@@ -8,9 +8,10 @@ import (
|
|||||||
type Effect interface {
|
type Effect interface {
|
||||||
// State uses three values:
|
// State uses three values:
|
||||||
// index: The position of this ID;
|
// index: The position of this ID;
|
||||||
|
// len: The total length of the effect's IDs;
|
||||||
// round: Increases by one for each run of the effect if Frequency > 0;
|
// round: Increases by one for each run of the effect if Frequency > 0;
|
||||||
// random: A random positive int in the full 31-bit range.
|
// random: A random positive int in the full 31-bit range.
|
||||||
State(index, round, random int) device.State
|
State(index, len, round, random int) device.State
|
||||||
Frequency() time.Duration
|
Frequency() time.Duration
|
||||||
EffectDescription() string
|
EffectDescription() string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,11 +188,17 @@ func (col *Color) Interpolate(other Color, fac float64) Color {
|
|||||||
return Color{K: &k3}
|
return Color{K: &k3}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fac < 0.000001 {
|
||||||
|
return *col
|
||||||
|
} else if fac > 0.999999 {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
// Get the colorful values.
|
// Get the colorful values.
|
||||||
cvCF := col.colorful()
|
cvCF := col.colorful()
|
||||||
otherCF := other.colorful()
|
otherCF := other.colorful()
|
||||||
|
|
||||||
// Blend and normalize
|
// Blend and normalize (clamping is hax to avoid issues with some colors)
|
||||||
blended := cvCF.BlendLuv(otherCF, fac)
|
blended := cvCF.BlendLuv(otherCF, fac)
|
||||||
blendedHue, blendedSat, _ := blended.Hsv()
|
blendedHue, blendedSat, _ := blended.Hsv()
|
||||||
blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
|
blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
|
||||||
@@ -253,7 +259,7 @@ func (col *Color) colorful() colorful.Color {
|
|||||||
case col.XY != nil:
|
case col.XY != nil:
|
||||||
return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
|
return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
|
||||||
default:
|
default:
|
||||||
return colorful.Color{R: 255, B: 255, G: 255}
|
return colorful.Color{R: 1, B: 1, G: 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package color
|
package color
|
||||||
|
|
||||||
import "github.com/lucasb-eyer/go-colorful"
|
import (
|
||||||
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
type HueSat struct {
|
type HueSat struct {
|
||||||
Hue float64 `json:"hue"`
|
Hue float64 `json:"hue"`
|
||||||
@@ -13,5 +16,5 @@ func (hs HueSat) ToXY() XY {
|
|||||||
|
|
||||||
func (hs HueSat) ToRGB() RGB {
|
func (hs HueSat) ToRGB() RGB {
|
||||||
c := colorful.Hsv(hs.Hue, hs.Sat, 1)
|
c := colorful.Hsv(hs.Hue, hs.Sat, 1)
|
||||||
return RGB{Red: c.R, Green: c.G, Blue: c.B}
|
return RGB{Red: math.Max(c.R, 0), Green: math.Max(c.G, 0), Blue: math.Max(c.B, 0)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ func (rgb RGB) AtIntensity(intensity float64) RGB {
|
|||||||
return RGB{Red: hsv2.R, Green: hsv2.G, Blue: hsv2.B}
|
return RGB{Red: hsv2.R, Green: hsv2.G, Blue: hsv2.B}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rgb RGB) Bytes() [3]uint8 {
|
||||||
|
return [3]uint8{
|
||||||
|
uint8(rgb.Red * 255.0),
|
||||||
|
uint8(rgb.Green * 255.0),
|
||||||
|
uint8(rgb.Blue * 255.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rgb RGB) ToHS() HueSat {
|
func (rgb RGB) ToHS() HueSat {
|
||||||
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv()
|
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv()
|
||||||
return HueSat{Hue: hue, Sat: sat}
|
return HueSat{Hue: hue, Sat: sat}
|
||||||
|
|||||||
@@ -3,3 +3,8 @@ package gentools
|
|||||||
func Ptr[T any](t T) *T {
|
func Ptr[T any](t T) *T {
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ShallowCopy[T any](t *T) *T {
|
||||||
|
tCopy := *t
|
||||||
|
return &tCopy
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
state := run.effect.State(j, run.round, r)
|
state := run.effect.State(j, len(run.ids), run.round, r)
|
||||||
commandQueue = append(commandQueue, commands.SetState{
|
commandQueue = append(commandQueue, commands.SetState{
|
||||||
ID: id,
|
ID: id,
|
||||||
State: state,
|
State: state,
|
||||||
|
|||||||
Reference in New Issue
Block a user