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.
 
 
 
 

281 lines
7.1 KiB

package models
import (
"context"
"regexp"
"strconv"
"strings"
"time"
)
type EventHandler struct {
ID int `json:"id"`
EventName string `json:"eventName"`
Conditions map[string]EventCondition `json:"conditions"`
OneShot bool `json:"oneShot"`
Priority int `json:"priority"`
TargetKind ReferenceKind `json:"targetKind"`
TargetValue string `json:"targetValue"`
Actions EventAction `json:"actions"`
From TimeOfDay `json:"from"`
To TimeOfDay `json:"to"`
}
type EventHandlerRepository interface {
FindByID(ctx context.Context, id int) (*EventHandler, error)
FetchAll(ctx context.Context) ([]EventHandler, error)
Save(ctx context.Context, handler *EventHandler) error
Delete(ctx context.Context, handler *EventHandler) error
}
type EventCondition struct {
EQ string `json:"eq,omitempty"`
GT string `json:"gt,omitempty"`
GTE string `json:"gte,omitempty"`
LT string `json:"lt,omitempty"`
LTE string `json:"lte,omitempty"`
}
type EventHandlerUpdate struct {
SetEventName *string `json:"setEventName"`
SetPriority *int `json:"setPriority"`
SetTargetKind *ReferenceKind `json:"setTargetKind"`
SetTargetValue *string `json:"setTargetValue"`
SetOneShot *bool `json:"setOneShot"`
SetConditions map[string]EventCondition `json:"setConditions"`
PatchConditions map[string]*EventCondition `json:"patchConditions"`
SetActions *EventAction `json:"setActions"`
SetFrom *TimeOfDay `json:"setFrom"`
SetTo *TimeOfDay `json:"setTo"`
}
func (h *EventHandler) ApplyUpdate(update EventHandlerUpdate) {
if update.SetEventName != nil {
h.EventName = *update.SetEventName
}
if update.SetPriority != nil {
h.Priority = *update.SetPriority
}
if update.SetTargetKind != nil {
h.TargetKind = *update.SetTargetKind
}
if update.SetTargetValue != nil {
h.TargetValue = *update.SetTargetValue
}
if update.SetActions != nil {
h.Actions = *update.SetActions
}
if update.SetOneShot != nil {
h.OneShot = *update.SetOneShot
}
if update.SetFrom != nil {
h.From = *update.SetFrom
}
if update.SetFrom != nil {
h.To = *update.SetFrom
}
if update.SetConditions != nil {
h.Conditions = update.SetConditions
}
if update.PatchConditions != nil {
if h.Conditions == nil {
h.Conditions = make(map[string]EventCondition)
}
for key, value := range update.PatchConditions {
if value == nil {
delete(h.Conditions, key)
} else {
h.Conditions[key] = *value
}
}
}
}
func (h *EventHandler) MatchesEvent(event Event, targets []Device) bool {
if event.Name != h.EventName {
return false
}
for key, condition := range h.Conditions {
if !strings.ContainsRune(key, '.') && !event.HasPayload(key) {
return false
}
if !condition.check(key, event.Payload[key], targets) {
return false
}
}
return true
}
func (h *EventHandler) IsWithinTimeRange() bool {
if h.From.IsNever() || h.To.IsNever() {
return true
}
return CurrentTimeOfDay().IsBetween(h.From, h.To)
}
func (c *EventCondition) check(key, value string, targets []Device) bool {
any := strings.HasPrefix(key, "any.")
all := strings.HasPrefix(key, "all.")
if any || all {
count := 0
total := 0
for _, target := range targets {
matches, skip := c.checkDevice(key[4:], target)
if skip {
continue
}
if matches {
count++
}
total++
}
return (any && count > 0) || (all && count == total)
}
return c.matches(value)
}
func (c *EventCondition) checkDevice(key string, device Device) (matches bool, skip bool) {
switch key {
case "power":
if !device.HasCapability(DCPower) {
return false, true
}
return c.matches(strconv.FormatBool(device.State.Power)), false
case "color":
if !device.HasCapability(DCColorKelvin, DCColorHS, DCColorHSK) {
return false, true
}
return c.matches(device.State.Color.String()), false
case "intensity":
if !device.HasCapability(DCIntensity) {
return false, true
}
return c.matches(strconv.FormatFloat(device.State.Intensity, 'f', -1, 64)), false
case "temperature":
if !device.HasCapability(DCTemperatureControl, DCTemperatureSensor) {
return false, true
}
return c.matches(strconv.FormatFloat(device.State.Temperature, 'f', -1, 64)), false
case "scene":
sceneId := -1
for _, assignment := range device.SceneAssignments {
duration := time.Duration(assignment.DurationMS) * time.Millisecond
if duration <= 0 || time.Now().Before(assignment.StartTime.Add(duration)) {
sceneId = assignment.SceneID
}
}
return c.matches(strconv.Itoa(sceneId)), false
default:
return false, true
}
}
var numRegex = regexp.MustCompile("^-*[0-9]+(.[0-9]+)*$")
func (c *EventCondition) matches(value string) bool {
if numRegex.MatchString(value) {
numValue, _ := strconv.ParseFloat(value, 64)
stillAlive := true
if c.LT != "" {
lt, _ := strconv.ParseFloat(c.LT, 64)
stillAlive = numValue < lt
}
if stillAlive && c.LTE != "" {
lte, _ := strconv.ParseFloat(c.LTE, 64)
stillAlive = numValue <= lte
}
if stillAlive && c.EQ != "" {
eq, _ := strconv.ParseFloat(c.EQ, 64)
stillAlive = numValue == eq
}
if stillAlive && c.GTE != "" {
gte, _ := strconv.ParseFloat(c.GTE, 64)
stillAlive = numValue >= gte
}
if stillAlive && c.GT != "" {
gt, _ := strconv.ParseFloat(c.GT, 64)
stillAlive = numValue > gt
}
return stillAlive
} else if c.EQ != "" {
return strings.ToLower(c.EQ) == strings.ToLower(value)
} else {
return false
}
}
type EventAction struct {
SetPower *bool `json:"setPower"`
SetColor *string `json:"setColor"`
SetIntensity *float64 `json:"setIntensity"`
SetTemperature *int `json:"setTemperature"`
AddIntensity *float64 `json:"addIntensity"`
FireEvent *Event `json:"fireEvent"`
SetScene *DeviceSceneAssignment `json:"setScene"`
PushScene *DeviceSceneAssignment `json:"pushScene"`
}
func (action *EventAction) Apply(other EventAction) {
if action.SetPower == nil {
action.SetPower = other.SetPower
}
if action.SetColor == nil {
action.SetColor = other.SetColor
}
if action.SetIntensity == nil && other.SetIntensity != nil {
action.SetIntensity = other.SetIntensity
}
if action.AddIntensity == nil && action.SetIntensity == nil {
action.AddIntensity = other.AddIntensity
}
if action.FireEvent == nil {
action.FireEvent = other.FireEvent
}
if action.SetScene == nil {
action.SetScene = other.SetScene
}
if action.PushScene == nil {
action.PushScene = other.PushScene
}
}
func (c EventCondition) String() string {
str := make([]string, 0, 5)
if len(c.LT) > 0 {
str = append(str, "lt:"+c.LT)
}
if len(c.LTE) > 0 {
str = append(str, "lte:"+c.LTE)
}
if len(c.EQ) > 0 {
str = append(str, c.EQ)
}
if len(c.GTE) > 0 {
str = append(str, "gte:"+c.GTE)
}
if len(c.GT) > 0 {
str = append(str, "gte:"+c.GT)
}
return strings.Join(str, ";")
}