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  }