package script import ( "fmt" lucifer3 "git.aiterp.net/lucifer3/server" "git.aiterp.net/lucifer3/server/commands" "git.aiterp.net/lucifer3/server/device" "git.aiterp.net/lucifer3/server/internal/formattools" "git.aiterp.net/lucifer3/server/internal/gentools" "github.com/google/uuid" "sort" ) type service struct { resolver device.Resolver variables *Variables scripts map[string]*Script triggers map[uuid.UUID]*Trigger } func (s *service) Active() bool { return true } func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) { for _, trig := range s.triggers { if trig.Check(event, s.resolver) { lines := append(make([]Line, 0, 64), trig.ScriptPre...) script := s.scripts[trig.ScriptName] if script != nil { lines = append(lines, script.Lines...) } lines = append(lines, trig.ScriptPost...) variables := s.variables.Get() devices := s.resolver.Resolve(trig.ScriptTarget) sort.Slice(devices, func(i, j int) bool { return devices[i].ID < devices[j].ID }) fmt.Println("[TRIGGER] Running", trig.ID, "for", len(devices), "devices:", event.EventDescription()) s.runScript(bus, lines, trig.ScriptTarget, map[string]bool{}, devices, variables) } } } func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) { switch command := command.(type) { case Update: s.scripts[command.Name] = &Script{ Name: command.Name, Lines: command.Lines, } case Execute: script := s.scripts[command.Name] if script == nil { break } variables := s.variables.Get() devices := s.resolver.Resolve(command.Match) sort.Slice(devices, func(i, j int) bool { return devices[i].ID < devices[j].ID }) s.runScript(bus, script.Lines, command.Match, map[string]bool{}, devices, variables) case UpdateTrigger: s.triggers[command.ID] = gentools.Ptr(Trigger(command)) case DeleteTrigger: delete(s.triggers, command.ID) } } func NewService(resolver device.Resolver, variables *Variables) lucifer3.Service { return &service{ resolver: resolver, variables: variables, triggers: make(map[uuid.UUID]*Trigger, 8), scripts: make(map[string]*Script, 8), } } func (s *service) runScript(bus *lucifer3.EventBus, lines []Line, match string, matchedDevices map[string]bool, devices []device.Pointer, variables VariableSet) { for _, line := range lines { if line.If != nil { devicesIDs := gentools.Map(devices, func(d device.Pointer) string { return d.ID }) if line.If.Condition.Check(variables, match, devicesIDs) { s.runScript(bus, line.If.Then, match, matchedDevices, devices, variables) } else { s.runScript(bus, line.If.Else, match, matchedDevices, devices, variables) } } else if line.Assign != nil { matcher := s.resolver.CompileMatcher(line.Assign.Match) matched := make([]string, 0) for _, dev := range devices { if matchedDevices[dev.ID] { continue } if line.Assign.Match == "*" || dev.Matches(matcher) { matchedDevices[dev.ID] = true matched = append(matched, dev.ID) } } if len(matched) > 0 { bus.RunCommand(commands.Assign{ Match: formattools.CompactMatch(matched), Effect: line.Assign.Effect.Effect, }) } } else if line.Set != nil { var sv *SetVariable switch line.Set.Scope { case "devices": sv = &SetVariable{ Devices: gentools.Map(devices, func(d device.Pointer) string { return d.ID }), Key: line.Set.Key, Value: line.Set.Value, } bus.RunCommand(sv) case "match": sv = &SetVariable{ Match: gentools.Ptr(match), Key: line.Set.Key, Value: line.Set.Value, } case "global": sv = &SetVariable{ Match: gentools.Ptr(match), Key: line.Set.Key, Value: line.Set.Value, } } if sv != nil { variables = variables.With(*sv) bus.RunCommand(*sv) } } else if line.Select != nil { matcher := s.resolver.CompileMatcher(line.Select.Match) matched := make([]device.Pointer, 0) for _, dev := range devices { if dev.Matches(matcher) { matched = append(matched, dev) } } if len(matched) > 0 { s.runScript(bus, line.Select.Then, match, matchedDevices, matched, variables) } } } }