github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/automation/trigger_manager.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package automation 20 21 import ( 22 "context" 23 "fmt" 24 "sync" 25 26 "github.com/e154/smart-home/adaptors" 27 "github.com/e154/smart-home/common/apperr" 28 "github.com/e154/smart-home/common/events" 29 m "github.com/e154/smart-home/models" 30 "github.com/e154/smart-home/plugins/triggers" 31 "github.com/e154/smart-home/system/bus" 32 "github.com/e154/smart-home/system/scripts" 33 "github.com/e154/smart-home/system/supervisor" 34 "github.com/pkg/errors" 35 "go.uber.org/atomic" 36 ) 37 38 type triggerManager struct { 39 eventBus bus.Bus 40 scriptService scripts.ScriptService 41 supervisor supervisor.Supervisor 42 adaptors *adaptors.Adaptors 43 isStarted *atomic.Bool 44 rawPlugin triggers.IGetTrigger 45 triggerCounter *atomic.Uint64 46 sync.Mutex 47 triggers map[int64]*Trigger 48 } 49 50 func NewTriggerManager(eventBus bus.Bus, 51 scriptService scripts.ScriptService, 52 sup supervisor.Supervisor, 53 adaptors *adaptors.Adaptors) (manager *triggerManager) { 54 manager = &triggerManager{ 55 eventBus: eventBus, 56 scriptService: scriptService, 57 supervisor: sup, 58 adaptors: adaptors, 59 isStarted: atomic.NewBool(false), 60 triggers: make(map[int64]*Trigger), 61 triggerCounter: atomic.NewUint64(0), 62 } 63 return 64 } 65 66 // Start ... 67 func (a *triggerManager) Start() { 68 69 a.load() 70 _ = a.eventBus.Subscribe("system/automation/triggers/+", a.eventHandler, false) 71 _ = a.eventBus.Subscribe("system/models/triggers/+", a.eventHandler, false) 72 a.isStarted.Store(true) 73 74 log.Info("Started") 75 } 76 77 // Shutdown ... 78 func (a *triggerManager) Shutdown() { 79 80 a.unload() 81 _ = a.eventBus.Unsubscribe("system/automation/triggers/+", a.eventHandler) 82 _ = a.eventBus.Unsubscribe("system/models/triggers/+", a.eventHandler) 83 84 log.Info("Shutdown") 85 } 86 87 func (a *triggerManager) eventHandler(_ string, msg interface{}) { 88 89 switch v := msg.(type) { 90 case events.CommandEnableTrigger: 91 go a.updateTrigger(v.Id) 92 case events.CommandDisableTrigger: 93 go a.removeTrigger(v.Id) 94 95 case events.EventUpdatedTriggerModel: 96 go a.updateTrigger(v.Id) 97 case events.EventCreatedTriggerModel: 98 go a.updateTrigger(v.Id) 99 case events.EventRemovedTriggerModel: 100 go a.removeTrigger(v.Id) 101 } 102 } 103 104 func (a *triggerManager) load() { 105 if a.isStarted.Load() { 106 return 107 } 108 109 // load triggers plugin 110 plugin, err := a.supervisor.GetPlugin(triggers.Name) 111 if err != nil { 112 log.Error(err.Error()) 113 return 114 } 115 116 if rawPlugin, ok := plugin.(triggers.IGetTrigger); ok { 117 a.rawPlugin = rawPlugin 118 } else { 119 log.Fatal("bad static cast triggers.IGetTrigger") 120 } 121 122 const perPage int64 = 500 123 var page int64 = 0 124 LOOP: 125 triggers, _, err := a.adaptors.Trigger.List(context.Background(), perPage, page*perPage, "", "", true) 126 if err != nil { 127 log.Error(err.Error()) 128 return 129 } 130 for _, trigger := range triggers { 131 if err = a.addTrigger(trigger); err != nil { 132 log.Warn(err.Error()) 133 } 134 } 135 if len(triggers) != 0 { 136 page++ 137 goto LOOP 138 } 139 140 log.Info("Loaded ...") 141 } 142 143 func (a *triggerManager) unload() { 144 if !a.isStarted.Load() { 145 return 146 } 147 148 for id := range a.triggers { 149 a.removeTrigger(id) 150 } 151 a.isStarted.Store(false) 152 153 log.Info("Unloaded ...") 154 } 155 156 // addTrigger ... 157 func (a *triggerManager) addTrigger(model *m.Trigger) (err error) { 158 159 defer func() { 160 if err == nil { 161 a.triggerCounter.Inc() 162 } 163 }() 164 165 if _, ok := a.triggers[model.Id]; ok { 166 err = errors.Wrap(apperr.ErrInternal, fmt.Sprintf("trigger %s exist", model.Name)) 167 return 168 } 169 170 if !model.Enabled { 171 return 172 } 173 174 var trigger *Trigger 175 if trigger, err = NewTrigger(a.eventBus, a.scriptService, model, a.rawPlugin); err != nil { 176 log.Error(err.Error()) 177 return 178 } 179 180 a.Lock() 181 a.triggers[model.Id] = trigger 182 a.Unlock() 183 184 trigger.Start() 185 186 return 187 } 188 189 // removeTrigger ... 190 func (a *triggerManager) removeTrigger(id int64) { 191 a.Lock() 192 defer a.Unlock() 193 //log.Infof("remove trigger id:%d", id) 194 195 trigger, ok := a.triggers[id] 196 if !ok { 197 return 198 } 199 trigger.Stop() 200 delete(a.triggers, id) 201 202 a.triggerCounter.Dec() 203 } 204 205 // updateTrigger ... 206 func (a *triggerManager) updateTrigger(id int64) { 207 //log.Infof("reload trigger id:%d", id) 208 a.removeTrigger(id) 209 210 trigger, err := a.adaptors.Trigger.GetById(context.Background(), id) 211 if err != nil { 212 return 213 } 214 215 a.addTrigger(trigger) 216 } 217 218 func (a *triggerManager) IsLoaded(id int64) (loaded bool) { 219 a.Lock() 220 _, loaded = a.triggers[id] 221 a.Unlock() 222 return 223 }