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.
 
 
 
 

238 lines
4.9 KiB

package scene
import (
"context"
"git.aiterp.net/lucifer/new-server/app/config"
"git.aiterp.net/lucifer/new-server/models"
"log"
"sync"
"time"
)
type Manager struct {
mu sync.Mutex
allocations map[int]*Scene
devices map[int]*models.Device
scenes []*Scene
notifyCh chan struct{}
}
func (mgr *Manager) UpdateDevice(ctx context.Context, device *models.Device, data *models.Scene) error {
mgr.mu.Lock()
defer mgr.mu.Unlock()
scene, err := mgr.findScene(ctx, device.SceneAssignments, data)
if err != nil {
return err
}
if mgr.allocations[device.ID] != nil && mgr.allocations[device.ID] != scene {
mgr.allocations[device.ID].RemoveDevice(*device)
}
if mgr.allocations[device.ID] != scene {
if mgr.allocations == nil {
mgr.allocations = make(map[int]*Scene, 8)
}
mgr.allocations[device.ID] = scene
if scene != nil {
scene.due = true
}
}
if scene != nil {
scene.UpsertDevice(*device)
if mgr.devices == nil {
mgr.devices = make(map[int]*models.Device)
}
mgr.devices[device.ID] = device
} else {
config.PublishChannel <- []models.Device{*device}
}
if mgr.notifyCh != nil {
close(mgr.notifyCh)
mgr.notifyCh = nil
}
return nil
}
func (mgr *Manager) UpdateScene(data *models.Scene) {
mgr.mu.Lock()
for _, scene := range mgr.scenes {
if scene.data.ID == data.ID {
scene.UpdateScene(scene.data)
}
}
mgr.mu.Unlock()
}
func (mgr *Manager) FilterUnassigned(devices []models.Device) []models.Device {
mgr.mu.Lock()
defer mgr.mu.Unlock()
res := make([]models.Device, 0, len(devices))
for _, device := range devices {
if mgr.allocations[device.ID] == nil {
res = append(res, device)
}
}
return res
}
func (mgr *Manager) Run(ctx context.Context) {
devices, err := config.DeviceRepository().FetchByReference(ctx, models.RKAll, "")
if err == nil {
for _, device := range devices {
_ = mgr.UpdateDevice(ctx, &device, nil)
}
}
ticker := time.NewTicker(time.Millisecond * 101)
defer ticker.Stop()
mgr.mu.Lock()
mgr.notifyCh = make(chan struct{})
mgr.mu.Unlock()
for {
mgr.mu.Lock()
if mgr.notifyCh == nil {
mgr.notifyCh = make(chan struct{})
}
notifyCh := mgr.notifyCh
mgr.mu.Unlock()
select {
case <-ticker.C:
case <-notifyCh:
case <-ctx.Done():
return
}
timeout, cancel := context.WithTimeout(ctx, time.Millisecond*100)
var updates []models.Device
mgr.mu.Lock()
for _, device := range mgr.devices {
if device == nil {
continue
}
if mgr.reScene(timeout, device) {
updates = append(updates, *device)
}
}
for _, scene := range mgr.scenes {
if scene.Due() {
scene.Run()
}
updates = append(updates, scene.Flush()...)
}
mgr.mu.Unlock()
cancel()
if len(updates) > 0 {
log.Println("Updating", len(updates), "devices")
config.PublishChannel <- updates
}
}
}
func (mgr *Manager) reScene(ctx context.Context, device *models.Device) bool {
scene, err := mgr.findScene(ctx, device.SceneAssignments, nil)
if err != nil {
return false
}
if mgr.allocations[device.ID] != nil && mgr.allocations[device.ID] != scene {
mgr.allocations[device.ID].RemoveDevice(*device)
}
if mgr.allocations[device.ID] != scene {
if mgr.allocations == nil {
mgr.allocations = make(map[int]*Scene, 8)
}
mgr.allocations[device.ID] = scene
if scene == nil {
return true
}
mgr.allocations[device.ID].UpsertDevice(*device)
}
return false
}
func (mgr *Manager) findScene(ctx context.Context, assignments []models.DeviceSceneAssignment, data *models.Scene) (*Scene, error) {
if len(assignments) == 0 {
return nil, nil
}
var selected *models.DeviceSceneAssignment
for _, assignment := range assignments {
pos := int64(time.Since(assignment.StartTime) / time.Millisecond)
if assignment.DurationMS == 0 || (pos >= -1 && pos < assignment.DurationMS) {
selected = &assignment
}
}
if selected == nil {
return nil, nil
}
for _, scene := range mgr.scenes {
if scene.group == selected.Group && scene.startTime.Equal(selected.StartTime) && selected.DurationMS == scene.duration.Milliseconds() {
return scene, nil
}
}
if data == nil {
for i := range mgr.scenes {
if mgr.scenes[i].data.ID == selected.SceneID {
data = &mgr.scenes[i].data
break
}
}
}
if data == nil {
var err error
data, err = config.SceneRepository().Find(ctx, selected.SceneID)
if err != nil {
return nil, err
}
}
scene := &Scene{
startTime: selected.StartTime,
duration: time.Duration(selected.DurationMS) * time.Millisecond,
lastRun: 0,
due: true,
group: selected.Group,
data: *data,
roleList: make([][]models.Device, len(data.Roles)),
deviceMap: make(map[int]*models.Device, 8),
roleMap: make(map[int]int, 8),
changes: make(map[int]models.NewDeviceState, 8),
}
mgr.scenes = append(mgr.scenes, scene)
return scene, nil
}
var globalManager = &Manager{}
func GlobalManager() *Manager {
return globalManager
}