From 15536c6078b619b49a133936cede191f71a5c959 Mon Sep 17 00:00:00 2001 From: Stian Fredrik Aune Date: Thu, 20 Jan 2022 22:07:04 +0100 Subject: [PATCH] lucy factory - sensor support --- app/services/handlerfactory/config.go | 22 +++++- app/services/handlerfactory/factory.go | 92 ++++++++++++++++++++++---- app/services/handlerfactory/utils.go | 72 ++++++++++++++++++++ cmd/lucy/factorycmd.go | 4 ++ cmd/lucy/help.go | 15 +++++ 5 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 app/services/handlerfactory/utils.go diff --git a/app/services/handlerfactory/config.go b/app/services/handlerfactory/config.go index 62d3b9d..d3739b9 100644 --- a/app/services/handlerfactory/config.go +++ b/app/services/handlerfactory/config.go @@ -1,6 +1,7 @@ package handlerfactory import ( + "git.aiterp.net/lucifer/new-server/models" "gopkg.in/yaml.v2" "io" "io/ioutil" @@ -12,14 +13,23 @@ type FactoryConfig struct { Target string `yaml:"target"` SceneNames []string `yaml:"scenes"` TriggerDeviceID string `yaml:"trigger_device_id"` + After []After `yaml:"after"` + From string `yaml:"from"` + To string `yaml:"to"` } type FCMOde string var ( - SceneDimmer FCMOde = "SceneDimmer" + SceneDimmer FCMOde = "SceneDimmer" + MotionSensor FCMOde = "MotionSensor" ) +type After struct { + Minutes int `yaml:"minutes"` + NewState models.NewDeviceState `yaml:"new_state"` +} + func ParseConfigs(reader io.Reader) ([]FactoryConfig, error) { readAll, err := ioutil.ReadAll(reader) if err != nil { @@ -40,3 +50,13 @@ func ParseConfigs(reader io.Reader) ([]FactoryConfig, error) { return configs, nil } + +func (f *FactoryConfig) FromTime() models.TimeOfDay { + tod, _ := models.ParseTimeOfDay(f.From) + return tod +} + +func (f *FactoryConfig) ToTime() models.TimeOfDay { + tod, _ := models.ParseTimeOfDay(f.To) + return tod +} diff --git a/app/services/handlerfactory/factory.go b/app/services/handlerfactory/factory.go index 997a091..453b25b 100644 --- a/app/services/handlerfactory/factory.go +++ b/app/services/handlerfactory/factory.go @@ -13,6 +13,8 @@ func CreateHandlers( switch config.Mode { case SceneDimmer: return sceneDimmer(config, scenes, existing) + case MotionSensor: + return motionSensor(config, existing) default: return []models.EventHandler{}, []models.EventHandler{} } @@ -59,8 +61,8 @@ func sceneDimmer( TargetKind: kind, TargetValue: refValue, Actions: models.EventAction{SetPower: &myTrue}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) handlers = append(handlers, models.EventHandler{ @@ -74,8 +76,8 @@ func sceneDimmer( TargetKind: kind, TargetValue: refValue, Actions: models.EventAction{SetPower: &myFalse}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) for i, scene := range resolvedScenes { @@ -96,8 +98,8 @@ func sceneDimmer( Group: refValue, DurationMS: 0, }}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) } @@ -118,8 +120,8 @@ func sceneDimmer( Group: refValue, DurationMS: 0, }}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) } } @@ -139,8 +141,8 @@ func sceneDimmer( Group: refValue, DurationMS: 0, }}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) handlers = append(handlers, models.EventHandler{ @@ -158,11 +160,75 @@ func sceneDimmer( Group: refValue, DurationMS: 0, }}, - From: -1, - To: -1, + From: config.FromTime(), + To: config.ToTime(), }) - return handlers, toRemove + return optimizeLists(handlers, toRemove) +} + +func motionSensor( + config FactoryConfig, + existing []models.EventHandler, +) ([]models.EventHandler, []models.EventHandler) { + kind, refValue := models.ParseFetchString(config.Target) + + toRemove := make([]models.EventHandler, 0, 8) + for _, old := range existing { + if old.From != config.FromTime() || old.To != config.ToTime() { + continue + } + + if old.Conditions["deviceId"].EQ == config.TriggerDeviceID { + toRemove = append(toRemove, old) + } + } + + handlers := make([]models.EventHandler, 0, len(config.After)) + for _, after := range config.After { + if after.Minutes == 0 { + handlers = append(handlers, models.EventHandler{ + EventName: "SensorPresenceStarted", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{ + SetPower: after.NewState.Power, + SetColor: after.NewState.Color, + SetIntensity: after.NewState.Intensity, + SetTemperature: after.NewState.Temperature, + }, + From: config.FromTime(), + To: config.ToTime(), + }) + } else { + handlers = append(handlers, models.EventHandler{ + EventName: "SensorPresenceEnded", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "minutesElapsed": {EQ: fmt.Sprintf("%d", after.Minutes)}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{ + SetPower: after.NewState.Power, + SetColor: after.NewState.Color, + SetIntensity: after.NewState.Intensity, + SetTemperature: after.NewState.Temperature, + }, + From: config.FromTime(), + To: config.ToTime(), + }) + } + } + + return optimizeLists(handlers, toRemove) } var myTrue = true diff --git a/app/services/handlerfactory/utils.go b/app/services/handlerfactory/utils.go new file mode 100644 index 0000000..6809135 --- /dev/null +++ b/app/services/handlerfactory/utils.go @@ -0,0 +1,72 @@ +package handlerfactory + +import ( + "git.aiterp.net/lucifer/new-server/models" +) + +func optimizeLists( + add []models.EventHandler, + del []models.EventHandler, +) ([]models.EventHandler, []models.EventHandler) { + newAdd := make([]models.EventHandler, 0, len(add)) + newDel := make([]models.EventHandler, 0, len(del)) + +OptimizeLoop1: + for _, addItem := range add { + for _, delItem := range del { + if checkDuplicate(&addItem, &delItem) { + continue OptimizeLoop1 + } + } + + newAdd = append(newAdd, addItem) + } + +OptimizeLoop2: + for _, delItem := range del { + for _, addItem := range add { + if checkDuplicate(&addItem, &delItem) { + continue OptimizeLoop2 + } + } + + newDel = append(newDel, delItem) + } + + return newAdd, newDel +} + +func checkDuplicate(a *models.EventHandler, b *models.EventHandler) bool { + check := true + check = check && a.EventName == b.EventName + check = check && len(a.Conditions) == len(b.Conditions) + for k := range a.Conditions { + check = check && a.Conditions[k] == b.Conditions[k] + } + check = check && a.OneShot == b.OneShot + check = check && a.Priority == b.Priority + check = check && a.TargetKind == b.TargetKind + check = check && a.TargetValue == b.TargetValue + check = check && a.From == b.From + check = check && a.To == b.To + + aPower := a.Actions.SetPower + bPower := b.Actions.SetPower + powerEq := (aPower == nil && bPower == nil) || (aPower != nil && bPower != nil && *aPower == *bPower) + + aColor := a.Actions.SetColor + bColor := b.Actions.SetColor + colorEq := (aColor == nil && bColor == nil) || (aColor != nil && bColor != nil && *aColor == *bColor) + + aInt := a.Actions.SetIntensity + bInt := b.Actions.SetIntensity + intEq := (aInt == nil && bInt == nil) || (aInt != nil && bInt != nil && *aInt == *bInt) + + aTemp := a.Actions.SetTemperature + bTemp := b.Actions.SetTemperature + tempEq := (aTemp == nil && bTemp == nil) || (aTemp != nil && bTemp != nil && *aTemp == *bTemp) + + check = check && powerEq && colorEq && intEq && tempEq + + return check +} diff --git a/cmd/lucy/factorycmd.go b/cmd/lucy/factorycmd.go index 5987154..8ad6645 100644 --- a/cmd/lucy/factorycmd.go +++ b/cmd/lucy/factorycmd.go @@ -39,6 +39,10 @@ func factoryCmd( defer file.Close() configs, err := handlerfactory.ParseConfigs(file) + if err != nil { + log.Fatalln("Failed to parse file:", err) + } + for _, config := range configs { toAdd, toRemove := handlerfactory.CreateHandlers(config, scenes, handlers) diff --git a/cmd/lucy/help.go b/cmd/lucy/help.go index 5be09ff..7ecf45a 100644 --- a/cmd/lucy/help.go +++ b/cmd/lucy/help.go @@ -45,6 +45,21 @@ scenes: - vasking trigger_device_id: 14 --- +mode: MotionSensor +target: tag:Bod +trigger_device_id: 36 +after: + - minutes: 0 + new_state: + power: true + color: hs:240,0.6 + intensity: 0.8 + - minutes: 1 + new_state: + power: false +from: 08:00 +to: 15:59:59 +--- (more modes coming soon) WARNING: Similar event handlers will be removed!