Browse Source

add sensor scenes for Motion and Temperature.

feature-colorvalue2
Gisle Aune 3 years ago
parent
commit
147231e4fd
  1. 26
      app/services/events.go
  2. 15
      app/services/publisher/publisher.go
  3. 43
      app/services/publisher/scene.go
  4. 2
      internal/drivers/hue/state.go
  5. 20
      models/device.go
  6. 1
      models/event.go
  7. 81
      models/scene.go

26
app/services/events.go

@ -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
} }
}
}

15
app/services/publisher/publisher.go

@ -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 {

43
app/services/publisher/scene.go

@ -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,
Length: len(list),
CurrentTime: currentTime,
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)

2
internal/drivers/hue/state.go

@ -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),
}, },
} }
} }

20
models/device.go

@ -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
} }

1
models/event.go

@ -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"
) )

81
models/scene.go

@ -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"`
PowerMode ScenePowerMode `json:"powerMode"`
TargetKind ReferenceKind `json:"targetKind"`
TargetValue string `json:"targetValue"`
Interpolate bool `json:"interpolate"`
Relative bool `json:"relative"`
Order string `json:"order"`
States []NewDeviceState `json:"states"`
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"`
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

Loading…
Cancel
Save