package models import ( "context" "regexp" "strconv" "strings" ) 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"` } 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"` } 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.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 (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 default: return false, true } } var numRegex = regexp.MustCompile("^{-[0-9].}+$") func (c *EventCondition) matches(value string) bool { if numRegex.MatchString(value) { numValue, _ := strconv.ParseFloat(c.LT, 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"` AddIntensity *float64 `json:"addIntensity"` FireEvent *Event `json:"fireEvent"` } 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 } } 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, ";") }