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  }