github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/automation/trigger.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 "go.uber.org/atomic" 25 26 "github.com/e154/smart-home/common" 27 "github.com/e154/smart-home/common/events" 28 "github.com/e154/smart-home/common/telemetry" 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 ) 34 35 // Trigger ... 36 type Trigger struct { 37 scriptService scripts.ScriptService 38 lastStatus *atomic.Bool 39 model *m.Trigger 40 name string 41 triggerPlugin triggers.ITrigger 42 taskName string 43 triggerSubscribers []*TriggerSubscriber 44 eventBus bus.Bus 45 } 46 47 // NewTrigger ... 48 func NewTrigger( 49 eventBus bus.Bus, 50 scriptService scripts.ScriptService, 51 model *m.Trigger, 52 rawPlugin triggers.IGetTrigger) (tr *Trigger, err error) { 53 54 pluginName := model.PluginName 55 if pluginName == "" { 56 pluginName = triggers.StateChangeName 57 } 58 59 var triggerPlugin triggers.ITrigger 60 if triggerPlugin, err = rawPlugin.GetTrigger(pluginName); err != nil { 61 log.Error(err.Error()) 62 return 63 } 64 65 tr = &Trigger{ 66 model: model, 67 name: model.Name, 68 scriptService: scriptService, 69 triggerPlugin: triggerPlugin, 70 eventBus: eventBus, 71 lastStatus: atomic.NewBool(false), 72 triggerSubscribers: make([]*TriggerSubscriber, 0), 73 } 74 75 return 76 } 77 78 // Start ... 79 func (tr *Trigger) Start() { 80 log.Infof("start trigger '%s'", tr.name) 81 82 if len(tr.model.Entities) > 0 { 83 for _, entity := range tr.model.Entities { 84 engine := tr.genScriptEngine(&entity.Id) 85 tr.triggerSubscribers = append(tr.triggerSubscribers, &TriggerSubscriber{ 86 Engine: engine, 87 Subscriber: tr.genSubscriber(&entity.Id, tr.genCheckFunc(engine)), 88 }) 89 } 90 91 } else { 92 engine := tr.genScriptEngine(nil) 93 tr.triggerSubscribers = append(tr.triggerSubscribers, &TriggerSubscriber{ 94 Engine: engine, 95 Subscriber: tr.genSubscriber(nil, tr.genCheckFunc(engine)), 96 }) 97 } 98 99 for _, sub := range tr.triggerSubscribers { 100 _ = tr.triggerPlugin.Subscribe(sub.Subscriber) 101 } 102 _ = tr.eventBus.Subscribe(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), tr.eventHandler, false) 103 tr.eventBus.Publish(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), events.EventTriggerLoaded{ 104 Id: tr.model.Id, 105 }) 106 } 107 108 // Stop ... 109 func (tr *Trigger) Stop() { 110 log.Infof("stop trigger '%s'", tr.name) 111 for _, sub := range tr.triggerSubscribers { 112 if sub.Engine != nil { 113 sub.Engine.Stop() 114 } 115 } 116 _ = tr.eventBus.Unsubscribe(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), tr.eventHandler) 117 for _, sub := range tr.triggerSubscribers { 118 _ = tr.triggerPlugin.Unsubscribe(sub.Subscriber) 119 } 120 tr.eventBus.Publish(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), events.EventTriggerUnloaded{ 121 Id: tr.model.Id, 122 }) 123 } 124 125 func (tr *Trigger) genCheckFunc(scriptEngine *scripts.EngineWatcher) func(msg interface{}) (state bool, err error) { 126 return func(msg interface{}) (state bool, err error) { 127 128 if scriptEngine != nil && scriptEngine.Engine() != nil { 129 var result string 130 if result, err = scriptEngine.Engine().AssertFunction(tr.triggerPlugin.FunctionName(), msg); err != nil { 131 log.Error(err.Error()) 132 } 133 134 state = result == "true" 135 tr.lastStatus.Store(state) 136 return 137 } 138 139 state = true 140 141 tr.lastStatus.Store(state) 142 143 return 144 } 145 } 146 147 func (tr *Trigger) genSubscriber(entityId *common.EntityId, check func(msg interface{}) (state bool, err error)) triggers.Subscriber { 148 149 return triggers.Subscriber{ 150 EntityId: entityId, 151 Payload: tr.model.Payload, 152 Handler: func(_ string, msg interface{}) { 153 triggerCtx, span := telemetry.Start(context.Background(), "trigger") 154 span.SetAttributes("id", tr.model.Id) 155 args := &events.TriggerMessage{ 156 Payload: msg, 157 TriggerName: tr.model.Name, 158 EntityId: entityId, 159 } 160 result, err := check(args) 161 span.End() 162 if err != nil || !result { 163 return 164 } 165 //fmt.Println("call trigger", tr.model.Name, tr.triggerPlugin.Name()) 166 tr.eventBus.Publish(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), events.EventTriggerCompleted{ 167 Id: tr.model.Id, 168 Args: args, 169 EntityId: entityId, 170 Ctx: triggerCtx, 171 }) 172 }, 173 } 174 } 175 176 func (tr *Trigger) genScriptEngine(entityId *common.EntityId) (scriptEngine *scripts.EngineWatcher) { 177 if tr.model.Script != nil { 178 179 var err error 180 if scriptEngine, err = tr.scriptService.NewEngineWatcher(tr.model.Script); err != nil { 181 return 182 } 183 scriptEngine.PushStruct("Trigger", NewTriggerBind(tr)) 184 scriptEngine.BeforeSpawn(func(engine *scripts.Engine) { 185 if entityId != nil { 186 if _, err = engine.EvalString(fmt.Sprintf("const ENTITY_ID = \"%s\";", entityId.String())); err != nil { 187 log.Error(err.Error()) 188 } 189 } 190 }) 191 scriptEngine.Spawn(func(engine *scripts.Engine) { 192 //if _, err = engine.Do(); err != nil { 193 // log.Error(err.Error()) 194 // return 195 //} 196 }) 197 } 198 return 199 } 200 201 func (tr *Trigger) eventHandler(_ string, msg interface{}) { 202 203 var entityId *common.EntityId 204 205 if len(tr.model.Entities) == 1 { 206 entityId = &tr.model.Entities[0].Id 207 } 208 209 switch v := msg.(type) { 210 case events.EventCallTrigger: 211 triggerCtx, span := telemetry.Start(v.Ctx, "trigger") 212 span.SetAttributes("id", tr.model.Id) 213 span.End() 214 tr.eventBus.Publish(fmt.Sprintf("system/automation/triggers/%d", tr.model.Id), events.EventTriggerCompleted{ 215 Id: tr.model.Id, 216 Args: nil, 217 EntityId: entityId, 218 Ctx: triggerCtx, 219 }) 220 } 221 }