github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/automation/task.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-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 "go.uber.org/atomic" 27 28 "github.com/e154/smart-home/common/events" 29 "github.com/e154/smart-home/common/telemetry" 30 m "github.com/e154/smart-home/models" 31 "github.com/e154/smart-home/system/bus" 32 "github.com/e154/smart-home/system/scripts" 33 ) 34 35 // Task ... 36 type Task struct { 37 model *m.Task 38 eventBus bus.Bus 39 conditionGroup *ConditionGroup 40 script *scripts.Engine 41 enabled *atomic.Bool 42 scriptService scripts.ScriptService 43 actionsMx sync.Mutex 44 actions map[int64]*Action 45 sync.Mutex 46 telemetry telemetry.Telemetry 47 } 48 49 // NewTask ... 50 func NewTask(eventBus bus.Bus, 51 scriptService scripts.ScriptService, 52 model *m.Task) *Task { 53 return &Task{ 54 model: model, 55 eventBus: eventBus, 56 enabled: atomic.NewBool(false), 57 scriptService: scriptService, 58 actions: make(map[int64]*Action), 59 } 60 } 61 62 // Id ... 63 func (t *Task) Id() int64 { 64 return t.model.Id 65 } 66 67 // Name ... 68 func (t *Task) Name() string { 69 return t.model.Name 70 } 71 72 func (t *Task) eventHandler(_ string, msg interface{}) { 73 74 if t == nil || !t.enabled.Load() { 75 return 76 } 77 78 switch v := msg.(type) { 79 case events.EventUpdatedActionModel: 80 t.removeAction(v.Id) 81 t.addAction(v.Action) 82 case events.EventRemovedTaskModel: 83 t.removeAction(v.Id) 84 case events.EventTriggerCompleted: 85 t.prepareEventFromTrigger(v) 86 } 87 } 88 89 // Start ... 90 func (t *Task) Start() { 91 if t.enabled.Load() { 92 return 93 } 94 t.enabled.Store(true) 95 96 //log.Infof("task %d start", t.Id()) 97 98 // add actions 99 for _, model := range t.model.Actions { 100 t.addAction(model) 101 } 102 103 // add condition group 104 t.conditionGroup = NewConditionGroup(t.model.Condition) 105 106 // add conditions 107 for _, model := range t.model.Conditions { 108 condition, err := NewCondition(t.scriptService, model) 109 if err != nil { 110 log.Error(err.Error()) 111 continue 112 } 113 t.conditionGroup.AddCondition(condition) 114 } 115 116 // add triggers 117 for _, model := range t.model.Triggers { 118 t.eventBus.Subscribe(fmt.Sprintf("system/automation/triggers/%d", model.Id), t.eventHandler, false) 119 } 120 121 t.eventBus.Publish(fmt.Sprintf("system/automation/tasks/%d", t.model.Id), events.EventTaskLoaded{ 122 Id: t.model.Id, 123 }) 124 } 125 126 // Stop ... 127 func (t *Task) Stop() { 128 if !t.enabled.Load() { 129 return 130 } 131 t.enabled.Store(false) 132 //log.Infof("task %d stopped", t.Id()) 133 for _, model := range t.model.Triggers { 134 t.eventBus.Unsubscribe(fmt.Sprintf("system/automation/triggers/%d", model.Id), t.eventHandler) 135 } 136 for _, action := range t.actions { 137 action.Remove() 138 t.eventBus.Unsubscribe(fmt.Sprintf("system/models/actions/%d", action.model.Id), t.eventHandler) 139 } 140 t.actions = make(map[int64]*Action) 141 142 t.conditionGroup.Stop() 143 t.conditionGroup = nil 144 145 t.eventBus.Publish(fmt.Sprintf("system/automation/tasks/%d", t.model.Id), events.EventTaskUnloaded{ 146 Id: t.model.Id, 147 }) 148 } 149 150 func (t *Task) Telemetry() telemetry.Telemetry { 151 t.Lock() 152 defer t.Unlock() 153 return t.telemetry 154 } 155 156 func (t *Task) addAction(model *m.Action) { 157 t.actionsMx.Lock() 158 defer t.actionsMx.Unlock() 159 160 action, err := NewAction(t.scriptService, t.eventBus, model) 161 if err != nil { 162 log.Error(err.Error()) 163 } 164 t.actions[model.Id] = action 165 166 t.eventBus.Subscribe(fmt.Sprintf("system/models/actions/%d", model.Id), t.eventHandler, false) 167 } 168 169 func (t *Task) removeAction(id int64) { 170 t.actionsMx.Lock() 171 defer t.actionsMx.Unlock() 172 173 if a, ok := t.actions[id]; ok { 174 a.Remove() 175 } 176 delete(t.actions, id) 177 178 _ = t.eventBus.Unsubscribe(fmt.Sprintf("system/models/actions/%d", id), t.eventHandler) 179 } 180 181 func (t *Task) prepareEventFromTrigger(v events.EventTriggerCompleted) { 182 taskCtx, taskSpan := telemetry.Start(v.Ctx, "task") 183 taskSpan.SetAttributes("id", t.model.Id) 184 185 var actionCtx context.Context 186 defer func() { 187 taskSpan.End() 188 189 t.Lock() 190 t.telemetry = telemetry.Unpack(actionCtx) 191 t.Unlock() 192 }() 193 194 conditionsCtx, span := telemetry.Start(taskCtx, "conditions") 195 result, err := t.conditionGroup.Check(v.EntityId) 196 if err != nil || !result { 197 if err != nil { 198 span.SetStatus(telemetry.Error, err.Error()) 199 } 200 span.End() 201 return 202 } 203 span.End() 204 205 t.actionsMx.Lock() 206 for _, action := range t.actions { 207 if actionCtx != nil { 208 conditionsCtx = actionCtx 209 } 210 actionCtx, span = telemetry.Start(conditionsCtx, "actions") 211 span.SetAttributes("id", action.model.Id) 212 if _, err = action.Run(v.EntityId); err != nil { 213 log.Error(err.Error()) 214 span.SetStatus(telemetry.Error, err.Error()) 215 } 216 span.End() 217 } 218 t.actionsMx.Unlock() 219 220 //fmt.Println("time spent", timeSpent.Microseconds()) 221 t.eventBus.Publish(fmt.Sprintf("system/automation/tasks/%d", t.model.Id), events.EventTaskCompleted{ 222 Id: t.model.Id, 223 Ctx: actionCtx, 224 }) 225 }