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