This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.aiterp.net/lucifer/new-server/app/config"
|
||||
"git.aiterp.net/lucifer/new-server/app/services/publisher"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
"log"
|
||||
"math"
|
||||
@@ -220,6 +221,29 @@ func handleSpecial(event models.Event) error {
|
||||
}
|
||||
|
||||
return driver.Publish(ctx, bridge, devices)
|
||||
case models.ENSensorPresenceStarted, models.ENSensorPresenceEnded, models.ENSensorTemperature:
|
||||
id, _ := strconv.Atoi(event.Payload["deviceId"])
|
||||
updateTime, _ := strconv.ParseInt(event.Payload["lastUpdated"], 10, 64)
|
||||
state := models.SceneSensor{
|
||||
ID: id,
|
||||
UpdateTime: time.Unix(updateTime, 0),
|
||||
}
|
||||
|
||||
switch event.Name {
|
||||
case models.ENSensorPresenceStarted:
|
||||
presence := true
|
||||
state.Presence = &presence
|
||||
case models.ENSensorPresenceEnded:
|
||||
presence := false
|
||||
state.Presence = &presence
|
||||
case models.ENSensorTemperature:
|
||||
temperature, _ := strconv.ParseFloat(event.Payload["temperature"], 64)
|
||||
state.Temperature = &temperature
|
||||
}
|
||||
|
||||
publisher.Global().UpdateSensor(state)
|
||||
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Publisher struct {
|
||||
started map[int]bool
|
||||
pending map[int][]models.Device
|
||||
waiting map[int]chan struct{}
|
||||
sensorCache map[int]models.SceneSensor
|
||||
}
|
||||
|
||||
func (p *Publisher) SceneState(deviceID int) *models.DeviceState {
|
||||
@@ -42,6 +43,15 @@ func (p *Publisher) UpdateScene(data models.Scene) {
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *Publisher) UpdateSensor(data models.SceneSensor) {
|
||||
p.mu.Lock()
|
||||
if assignment := p.sceneAssignment[data.ID]; assignment != nil {
|
||||
assignment.UpdateSensor(data)
|
||||
}
|
||||
p.sensorCache[data.ID] = data
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *Publisher) ReloadScenes(ctx context.Context) error {
|
||||
scenes, err := config.SceneRepository().FetchAll(ctx)
|
||||
if err != nil {
|
||||
@@ -90,6 +100,10 @@ func (p *Publisher) Publish(devices ...models.Device) {
|
||||
|
||||
if p.sceneAssignment[device.ID] != nil {
|
||||
p.sceneAssignment[device.ID].UpsertDevice(device)
|
||||
|
||||
if cache, ok := p.sensorCache[device.ID]; ok {
|
||||
p.sceneAssignment[device.ID].UpdateSensor(cache)
|
||||
}
|
||||
} else {
|
||||
p.pending[device.BridgeID] = append(p.pending[device.BridgeID], device)
|
||||
if p.waiting[device.BridgeID] != nil {
|
||||
@@ -291,6 +305,7 @@ var publisher = Publisher{
|
||||
started: make(map[int]bool),
|
||||
pending: make(map[int][]models.Device),
|
||||
waiting: make(map[int]chan struct{}),
|
||||
sensorCache: make(map[int]models.SceneSensor),
|
||||
}
|
||||
|
||||
func Global() *Publisher {
|
||||
|
||||
@@ -13,11 +13,25 @@ type Scene struct {
|
||||
roleMap map[int]int
|
||||
roleList map[int][]models.Device
|
||||
lastStates map[int]models.DeviceState
|
||||
sensors []models.SceneSensor
|
||||
|
||||
due bool
|
||||
lastInterval int64
|
||||
}
|
||||
|
||||
func (s *Scene) UpdateSensor(data models.SceneSensor) {
|
||||
s.due = true
|
||||
|
||||
for i, sensor := range s.sensors {
|
||||
if sensor.ID == data.ID {
|
||||
s.sensors[i] = data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.sensors = append(s.sensors, data)
|
||||
}
|
||||
|
||||
// UpdateScene updates the scene data and re-seats all devices.
|
||||
func (s *Scene) UpdateScene(data models.Scene) {
|
||||
devices := make([]models.Device, 0, 16)
|
||||
@@ -86,6 +100,13 @@ func (s *Scene) RemoveDevice(device models.Device) {
|
||||
|
||||
delete(s.roleMap, device.ID)
|
||||
delete(s.lastStates, device.ID)
|
||||
|
||||
for i, sensor := range s.sensors {
|
||||
if sensor.ID == device.ID {
|
||||
s.sensors = append(s.sensors[:i], s.sensors[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scene) Empty() bool {
|
||||
@@ -130,6 +151,7 @@ func (s *Scene) Run() []models.Device {
|
||||
return []models.Device{}
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
intervalNumber := int64(0)
|
||||
intervalMax := int64(1)
|
||||
if s.data.IntervalMS > 0 {
|
||||
@@ -149,14 +171,31 @@ func (s *Scene) Run() []models.Device {
|
||||
continue
|
||||
}
|
||||
|
||||
sensorCount := 0
|
||||
for _, device := range list {
|
||||
if device.IsOnlySensor() {
|
||||
sensorCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
role := s.data.Roles[i]
|
||||
|
||||
jSkip := 0
|
||||
|
||||
for j, device := range list {
|
||||
if device.IsOnlySensor() {
|
||||
updatedDevices = append(updatedDevices, device)
|
||||
jSkip += 1
|
||||
continue
|
||||
}
|
||||
|
||||
newState := role.ApplyEffect(&device, models.SceneRunContext{
|
||||
Index: j,
|
||||
Length: len(list),
|
||||
CurrentTime: currentTime,
|
||||
Index: j - jSkip,
|
||||
Length: len(list) - sensorCount,
|
||||
IntervalNumber: intervalNumber,
|
||||
IntervalMax: intervalMax,
|
||||
Sensors: s.sensors,
|
||||
})
|
||||
|
||||
err := device.SetState(newState)
|
||||
|
||||
@@ -219,6 +219,7 @@ func (state *hueSensorState) Update(newData SensorData) *models.Event {
|
||||
"deviceInternalId": newData.UniqueID,
|
||||
"minutesElapsed": strconv.Itoa(state.presenceCooldown - 1),
|
||||
"secondsElapsed": strconv.Itoa((state.presenceCooldown - 1) * 60),
|
||||
"lastUpdated": strconv.FormatInt(stateTime.Unix(), 10),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -235,6 +236,7 @@ func (state *hueSensorState) Update(newData SensorData) *models.Event {
|
||||
"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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+11
-9
@@ -2,7 +2,6 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -156,6 +155,17 @@ func (d *Device) HasTag(tags ...string) bool {
|
||||
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 {
|
||||
@@ -198,14 +208,6 @@ func (s *NewDeviceState) RelativeTo(device Device) NewDeviceState {
|
||||
intensity := device.State.Intensity * *s.Intensity
|
||||
n.Intensity = &intensity
|
||||
}
|
||||
if s.Color != nil {
|
||||
c, err := ParseColorValue(*s.Color)
|
||||
if err == nil {
|
||||
c.Hue = math.Mod(device.State.Color.Hue+c.Hue, 360)
|
||||
c.Saturation *= device.State.Color.Saturation
|
||||
c.Kelvin += device.State.Color.Kelvin
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ var (
|
||||
ENBridgeDisconnected = "BridgeDisconnected"
|
||||
ENButtonPressed = "ButtonPressed"
|
||||
ENSensorPresenceStarted = "SensorPresenceStarted"
|
||||
ENSensorPresenceEnding = "SensorPresenceEnding"
|
||||
ENSensorPresenceEnded = "SensorPresenceEnded"
|
||||
ENSensorTemperature = "SensorTemperature"
|
||||
)
|
||||
|
||||
+62
-1
@@ -61,10 +61,15 @@ const (
|
||||
SEGradient SceneEffect = "Gradient"
|
||||
SEWalkingGradient SceneEffect = "WalkingGradient"
|
||||
SETransition SceneEffect = "Transition"
|
||||
SEMotion SceneEffect = "Motion"
|
||||
SETemperature SceneEffect = "Temperature"
|
||||
)
|
||||
|
||||
type SceneRole struct {
|
||||
Effect SceneEffect `json:"effect"`
|
||||
MotionSeconds float64 `json:"motionSeconds"`
|
||||
MinTemperature int `json:"minTemperature"`
|
||||
MaxTemperature int `json:"maxTemperature"`
|
||||
PowerMode ScenePowerMode `json:"powerMode"`
|
||||
TargetKind ReferenceKind `json:"targetKind"`
|
||||
TargetValue string `json:"targetValue"`
|
||||
@@ -75,10 +80,19 @@ type SceneRole struct {
|
||||
}
|
||||
|
||||
type SceneRunContext struct {
|
||||
CurrentTime time.Time
|
||||
Index int
|
||||
Length int
|
||||
IntervalNumber int64
|
||||
IntervalMax int64
|
||||
Sensors []SceneSensor
|
||||
}
|
||||
|
||||
type SceneSensor struct {
|
||||
ID int
|
||||
UpdateTime time.Time
|
||||
Temperature *float64
|
||||
Presence *bool
|
||||
}
|
||||
|
||||
func (d *SceneRunContext) PositionFacShifted() float64 {
|
||||
@@ -120,7 +134,7 @@ func (r *SceneRole) Validate() error {
|
||||
}
|
||||
|
||||
switch r.Effect {
|
||||
case SEStatic, SERandom, SEGradient, SEWalkingGradient, SETransition:
|
||||
case SEStatic, SERandom, SEGradient, SEWalkingGradient, SETransition, SEMotion, SETemperature:
|
||||
default:
|
||||
return ErrSceneRoleUnknownEffect
|
||||
}
|
||||
@@ -178,6 +192,53 @@ func (r *SceneRole) ApplyEffect(device *Device, c SceneRunContext) (newState New
|
||||
newState = r.State(c.PositionFacShifted())
|
||||
case SETransition:
|
||||
newState = r.State(c.IntervalFac())
|
||||
case SEMotion:
|
||||
presence := false
|
||||
absenceTime := time.Time{}
|
||||
|
||||
for _, sensors := range c.Sensors {
|
||||
if sensors.Presence == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if *sensors.Presence {
|
||||
presence = true
|
||||
} else {
|
||||
if sensors.UpdateTime.After(absenceTime) {
|
||||
absenceTime = sensors.UpdateTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if presence {
|
||||
newState = r.State(0.0)
|
||||
} else {
|
||||
fac := c.CurrentTime.Sub(absenceTime).Seconds() / r.MotionSeconds
|
||||
if fac < 0 {
|
||||
fac = 0
|
||||
} else if fac > 1 {
|
||||
fac = 1
|
||||
}
|
||||
|
||||
newState = r.State(fac)
|
||||
}
|
||||
case SETemperature:
|
||||
avg := 0.0
|
||||
count := 0
|
||||
|
||||
for _, sensor := range c.Sensors {
|
||||
if sensor.Temperature == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
avg = *sensor.Temperature
|
||||
count = 1
|
||||
} else {
|
||||
avg = ((avg * float64(count)) + *sensor.Temperature) / float64(count+1)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Relative {
|
||||
|
||||
Reference in New Issue
Block a user