You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
3.5 KiB
175 lines
3.5 KiB
package scene
|
|
|
|
import (
|
|
"context"
|
|
"git.aiterp.net/lucifer/new-server/app/config"
|
|
"git.aiterp.net/lucifer/new-server/models"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
type Scene struct {
|
|
startTime time.Time
|
|
duration time.Duration
|
|
lastRun int64
|
|
due bool
|
|
group string
|
|
data models.Scene
|
|
roleList [][]models.Device
|
|
deviceMap map[int]*models.Device
|
|
roleMap map[int]int
|
|
changes map[int]models.NewDeviceState
|
|
}
|
|
|
|
func (s *Scene) UpdateScene(scene models.Scene) {
|
|
s.data = scene
|
|
s.roleMap = make(map[int]int)
|
|
|
|
for _, device := range s.deviceMap {
|
|
s.moveDevice(*device, false)
|
|
}
|
|
|
|
for i, role := range s.data.Roles {
|
|
role.ApplyOrder(s.roleList[i])
|
|
}
|
|
|
|
s.Run()
|
|
}
|
|
|
|
func (s *Scene) UpsertDevice(device models.Device) {
|
|
s.deviceMap[device.ID] = &device
|
|
s.moveDevice(device, true)
|
|
}
|
|
|
|
func (s *Scene) RemoveDevice(device models.Device) {
|
|
roleIndex, hasRole := s.roleMap[device.ID]
|
|
|
|
delete(s.deviceMap, device.ID)
|
|
delete(s.roleMap, device.ID)
|
|
|
|
if hasRole {
|
|
s.runRole(roleIndex)
|
|
}
|
|
}
|
|
|
|
func (s *Scene) Due() bool {
|
|
return s.due || s.intervalNumber() != s.lastRun
|
|
}
|
|
|
|
func (s *Scene) Flush() []models.Device {
|
|
res := make([]models.Device, 0, 8)
|
|
for key, change := range s.changes {
|
|
if s.deviceMap[key] != nil {
|
|
device := *s.deviceMap[key]
|
|
err := device.SetState(change)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
res = append(res, device)
|
|
}
|
|
|
|
delete(s.changes, key)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (s *Scene) Run() {
|
|
s.lastRun = s.intervalNumber()
|
|
s.due = false
|
|
|
|
for index := range s.roleList {
|
|
s.runRole(index)
|
|
}
|
|
}
|
|
|
|
func (s *Scene) runRole(index int) {
|
|
role := s.data.Roles[index]
|
|
devices := s.roleList[index]
|
|
|
|
intervalNumber := s.intervalNumber()
|
|
intervalMax := s.intervalMax()
|
|
|
|
for i, device := range devices {
|
|
newState, changed := role.ApplyEffect(&device, models.SceneRunContext{
|
|
Index: i,
|
|
Length: len(devices),
|
|
IntervalNumber: intervalNumber,
|
|
IntervalMax: intervalMax,
|
|
})
|
|
|
|
if !changed {
|
|
go func() {
|
|
_ = config.DeviceRepository().Save(context.Background(), &device, models.SMState)
|
|
}()
|
|
}
|
|
|
|
s.changes[device.ID] = newState
|
|
}
|
|
}
|
|
|
|
func (s *Scene) moveDevice(device models.Device, run bool) {
|
|
oldRole, hasOldRole := s.roleMap[device.ID]
|
|
newRole := s.data.RoleIndex(&device)
|
|
|
|
// If it doesn't move between roles, just update and reorder it.
|
|
if hasOldRole && oldRole == newRole {
|
|
for i, device2 := range s.roleList[newRole] {
|
|
if device2.ID == device.ID {
|
|
s.roleList[newRole][i] = device
|
|
}
|
|
}
|
|
|
|
if run {
|
|
s.data.Roles[newRole].ApplyOrder(s.roleList[newRole])
|
|
s.runRole(newRole)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Take it out of the old role.
|
|
if hasOldRole {
|
|
for i, device2 := range s.roleList[oldRole] {
|
|
if device2.ID == device.ID {
|
|
s.roleList[oldRole] = append(s.roleList[oldRole][:i], s.roleList[oldRole][i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
if run {
|
|
s.runRole(oldRole)
|
|
}
|
|
}
|
|
|
|
if newRole != -1 {
|
|
s.roleMap[device.ID] = newRole
|
|
s.roleList[newRole] = append(s.roleList[newRole], device)
|
|
|
|
if run {
|
|
s.data.Roles[newRole].ApplyOrder(s.roleList[newRole])
|
|
s.runRole(newRole)
|
|
}
|
|
}
|
|
|
|
s.due = true
|
|
}
|
|
|
|
func (s *Scene) intervalNumber() int64 {
|
|
intervalDuration := time.Duration(s.data.IntervalMS) * time.Millisecond
|
|
if intervalDuration == 0 {
|
|
return 0
|
|
}
|
|
|
|
return int64(math.Round(float64(time.Since(s.startTime)) / float64(intervalDuration)))
|
|
}
|
|
|
|
func (s *Scene) intervalMax() int64 {
|
|
intervalDuration := time.Duration(s.data.IntervalMS) * time.Millisecond
|
|
if intervalDuration == 0 {
|
|
return 1
|
|
}
|
|
|
|
return int64(s.duration / intervalDuration)
|
|
}
|