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.
250 lines
6.1 KiB
250 lines
6.1 KiB
package services
|
|
|
|
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"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
func StartEventHandler() {
|
|
config.EventChannel <- models.Event{Name: "LuciferStarted"}
|
|
|
|
go func() {
|
|
for event := range config.EventChannel {
|
|
responses := handleEvent(event)
|
|
|
|
for _, response := range responses {
|
|
handleEvent(response)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Generate TimeChanged event
|
|
go func() {
|
|
drift := time.Now().Add(time.Minute).Truncate(time.Minute).Sub(time.Now())
|
|
time.Sleep(drift + time.Millisecond*5)
|
|
|
|
for range time.NewTicker(time.Minute).C {
|
|
config.EventChannel <- models.Event{Name: "TimeChanged", Payload: map[string]string{
|
|
"weekdayName": time.Now().In(loc).Format("Monday"),
|
|
"month": time.Now().In(loc).Format("01"),
|
|
"year": time.Now().In(loc).Format("2006"),
|
|
}}
|
|
}
|
|
}()
|
|
}
|
|
|
|
var loc, _ = time.LoadLocation("Europe/Oslo")
|
|
var ctx = context.Background()
|
|
|
|
func handleEvent(event models.Event) (responses []models.Event) {
|
|
var deadHandlers []models.EventHandler
|
|
|
|
startTime := time.Now()
|
|
defer func() {
|
|
duration := time.Since(startTime)
|
|
if duration > time.Millisecond*250 {
|
|
log.Println(event.Name, "took long to handle:", duration)
|
|
}
|
|
}()
|
|
|
|
if !event.HasPayload("hour") {
|
|
event.AddPayload("hour", time.Now().In(loc).Format("15"))
|
|
}
|
|
if !event.HasPayload("minute") {
|
|
event.AddPayload("minute", time.Now().In(loc).Format("04"))
|
|
}
|
|
|
|
err := handleSpecial(event)
|
|
if err != nil {
|
|
log.Printf("Special event handler error (%s): %v", event.Name, err)
|
|
return
|
|
}
|
|
|
|
paramStrings := make([]string, 0, 8)
|
|
for key, value := range event.Payload {
|
|
paramStrings = append(paramStrings, fmt.Sprintf("%s=%s", key, value))
|
|
}
|
|
sort.Strings(paramStrings)
|
|
|
|
log.Printf("Event %s(%s)", event.Name, strings.Join(paramStrings, ", "))
|
|
|
|
handlers, err := config.EventHandlerRepository().FetchAll(ctx)
|
|
if err != nil {
|
|
log.Printf("Error fetchin' event halders: %s", err)
|
|
return
|
|
}
|
|
|
|
allDevices := make([]models.Device, 0, 16)
|
|
actions := make(map[int]models.EventAction, 16)
|
|
priorities := make(map[int]int, 16)
|
|
highestPriority := 0
|
|
var prioritizedEvent *models.Event
|
|
|
|
for _, handler := range handlers {
|
|
if handler.EventName != event.Name {
|
|
continue
|
|
}
|
|
if !handler.IsWithinTimeRange() {
|
|
continue
|
|
}
|
|
|
|
devices, err := config.DeviceRepository().FetchByReference(ctx, handler.TargetKind, handler.TargetValue)
|
|
if err != nil {
|
|
log.Printf("Error fetchin' event devices: %s", err)
|
|
return
|
|
}
|
|
|
|
if !handler.MatchesEvent(event, devices) {
|
|
continue
|
|
}
|
|
|
|
if handler.OneShot {
|
|
deadHandlers = append(deadHandlers, handler)
|
|
}
|
|
|
|
if handler.Priority > highestPriority {
|
|
highestPriority = handler.Priority
|
|
prioritizedEvent = handler.Actions.FireEvent
|
|
}
|
|
|
|
for _, device := range devices {
|
|
_, ok := actions[device.ID]
|
|
if !ok {
|
|
allDevices = append(allDevices, device)
|
|
actions[device.ID] = handler.Actions
|
|
priorities[device.ID] = handler.Priority
|
|
} else if handler.Priority > priorities[device.ID] {
|
|
actions[device.ID] = handler.Actions
|
|
priorities[device.ID] = handler.Priority
|
|
}
|
|
}
|
|
}
|
|
|
|
if prioritizedEvent != nil {
|
|
responses = append(responses, *prioritizedEvent)
|
|
}
|
|
|
|
for i, device := range allDevices {
|
|
action := actions[device.ID]
|
|
newState := models.NewDeviceState{}
|
|
|
|
newState.Color = action.SetColor
|
|
newState.Power = action.SetPower
|
|
|
|
if action.SetIntensity != nil {
|
|
newState.Intensity = action.SetIntensity
|
|
} else if action.AddIntensity != nil {
|
|
newIntensity := math.Max(0, math.Min(1, device.State.Intensity+*action.AddIntensity))
|
|
newState.Intensity = &newIntensity
|
|
}
|
|
|
|
if action.SetTemperature != nil {
|
|
newState.Temperature = action.SetTemperature
|
|
}
|
|
|
|
err = allDevices[i].SetState(newState)
|
|
if err != nil {
|
|
log.Println("Error updating state for device", device.ID, "err:", err)
|
|
}
|
|
|
|
if action.SetScene != nil {
|
|
action.SetScene.StartTime = time.Now()
|
|
allDevices[i].SceneAssignments = []models.DeviceSceneAssignment{*action.SetScene}
|
|
}
|
|
if action.PushScene != nil {
|
|
action.PushScene.StartTime = time.Now()
|
|
allDevices[i].SceneAssignments = append(allDevices[i].SceneAssignments, *action.PushScene)
|
|
}
|
|
}
|
|
|
|
config.PublishChannel <- allDevices
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
if len(allDevices) > 0 {
|
|
wg.Add(1)
|
|
go func() {
|
|
err := config.DeviceRepository().SaveMany(context.Background(), models.SMState, allDevices)
|
|
if err != nil {
|
|
log.Println("Failed to save devices' state:", err)
|
|
}
|
|
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
for _, handler := range deadHandlers {
|
|
wg.Add(1)
|
|
|
|
go func(handler models.EventHandler) {
|
|
err := config.EventHandlerRepository().Delete(context.Background(), &handler)
|
|
if err != nil {
|
|
log.Println("Failed to delete spent one-shot event handler:", err)
|
|
}
|
|
|
|
wg.Done()
|
|
}(handler)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return
|
|
}
|
|
|
|
func handleSpecial(event models.Event) error {
|
|
switch event.Name {
|
|
case models.ENBridgeConnected:
|
|
bridgeId, _ := strconv.Atoi(event.Payload["bridgeId"])
|
|
bridge, err := config.BridgeRepository().Find(ctx, bridgeId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
devices, err := config.DeviceRepository().FetchByReference(ctx, models.RKBridgeID, event.Payload["bridgeId"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
driver, err := config.DriverProvider().Provide(bridge.Driver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|