From 8c2ef2dac344bef50516428c1a957b63d49ceaed Mon Sep 17 00:00:00 2001 From: Stian Fredrik Aune Date: Wed, 12 Jan 2022 17:01:31 +0100 Subject: [PATCH] factory command --- app/services/handlerfactory/config.go | 42 ++++++ app/services/handlerfactory/factory.go | 169 +++++++++++++++++++++++++ cmd/lucy/factorycmd.go | 71 +++++++++++ cmd/lucy/help.go | 17 +++ cmd/lucy/main.go | 8 +- 5 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 app/services/handlerfactory/config.go create mode 100644 app/services/handlerfactory/factory.go create mode 100644 cmd/lucy/factorycmd.go diff --git a/app/services/handlerfactory/config.go b/app/services/handlerfactory/config.go new file mode 100644 index 0000000..62d3b9d --- /dev/null +++ b/app/services/handlerfactory/config.go @@ -0,0 +1,42 @@ +package handlerfactory + +import ( + "gopkg.in/yaml.v2" + "io" + "io/ioutil" + "strings" +) + +type FactoryConfig struct { + Mode FCMOde `yaml:"mode"` + Target string `yaml:"target"` + SceneNames []string `yaml:"scenes"` + TriggerDeviceID string `yaml:"trigger_device_id"` +} + +type FCMOde string + +var ( + SceneDimmer FCMOde = "SceneDimmer" +) + +func ParseConfigs(reader io.Reader) ([]FactoryConfig, error) { + readAll, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + + splits := strings.Split(string(readAll), "\n---\n") + configs := make([]FactoryConfig, 0, 8) + for _, split := range splits { + var factoryConfig FactoryConfig + err := yaml.NewDecoder(strings.NewReader(split)).Decode(&factoryConfig) + if err != nil { + return nil, err + } + + configs = append(configs, factoryConfig) + } + + return configs, nil +} diff --git a/app/services/handlerfactory/factory.go b/app/services/handlerfactory/factory.go new file mode 100644 index 0000000..3de5044 --- /dev/null +++ b/app/services/handlerfactory/factory.go @@ -0,0 +1,169 @@ +package handlerfactory + +import ( + "fmt" + "git.aiterp.net/lucifer/new-server/models" +) + +func CreateHandlers( + config FactoryConfig, + scenes []models.Scene, + existing []models.EventHandler, +) ([]models.EventHandler, []models.EventHandler) { + switch config.Mode { + case SceneDimmer: + return sceneDimmer(config, scenes, existing) + default: + return []models.EventHandler{}, []models.EventHandler{} + } +} + +func sceneDimmer( + config FactoryConfig, + scenes []models.Scene, + 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.Conditions["deviceId"].EQ == config.TriggerDeviceID { + toRemove = append(toRemove, old) + } + } + + resolvedScenes := make([]models.Scene, 0, len(config.SceneNames)) + for _, name := range config.SceneNames { + for _, scene := range scenes { + if name == scene.Name { + resolvedScenes = append(resolvedScenes, scene) + break + } + } + } + + if len(scenes) < 2 { + return []models.EventHandler{}, []models.EventHandler{} + } + + handlers := make([]models.EventHandler, 0, len(scenes)+4) + + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "On"}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetPower: &myTrue}, + From: -1, + To: -1, + }) + + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "Off"}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetPower: &myFalse}, + From: -1, + To: -1, + }) + + for i, scene := range resolvedScenes { + if i > 0 { + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "DimDown"}, + "any.scene": {EQ: fmt.Sprintf("%d", scene.ID)}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetScene: &models.DeviceSceneAssignment{ + SceneID: resolvedScenes[i-1].ID, + Group: refValue, + DurationMS: 0, + }}, + From: -1, + To: -1, + }) + } + + if i < len(resolvedScenes)-1 { + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "DimUp"}, + "any.scene": {EQ: fmt.Sprintf("%d", scene.ID)}, + }, + OneShot: false, + Priority: 100, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetScene: &models.DeviceSceneAssignment{ + SceneID: resolvedScenes[i+1].ID, + Group: refValue, + DurationMS: 0, + }}, + From: -1, + To: -1, + }) + } + } + + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "DimDown"}, + }, + OneShot: false, + Priority: 90, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetScene: &models.DeviceSceneAssignment{ + SceneID: scenes[len(scenes)-1].ID, + Group: refValue, + DurationMS: 0, + }}, + From: -1, + To: -1, + }) + + handlers = append(handlers, models.EventHandler{ + EventName: "ButtonPressed", + Conditions: map[string]models.EventCondition{ + "deviceId": {EQ: config.TriggerDeviceID}, + "buttonName": {EQ: "DimUp"}, + }, + OneShot: false, + Priority: 90, + TargetKind: kind, + TargetValue: refValue, + Actions: models.EventAction{SetScene: &models.DeviceSceneAssignment{ + SceneID: scenes[0].ID, + Group: refValue, + DurationMS: 0, + }}, + From: -1, + To: -1, + }) + + return handlers, toRemove +} + +var myTrue = true +var myFalse = false diff --git a/cmd/lucy/factorycmd.go b/cmd/lucy/factorycmd.go new file mode 100644 index 0000000..5987154 --- /dev/null +++ b/cmd/lucy/factorycmd.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "fmt" + "git.aiterp.net/lucifer/new-server/app/client" + "git.aiterp.net/lucifer/new-server/app/services/handlerfactory" + "log" + "os" +) + +func factoryCmd( + ctx context.Context, + c client.Client, +) { + cmd := parseCommand(os.Args[2:]) + + switch cmd.Name { + case "run": + fileName := cmd.Params.Get(0).String() + if fileName == nil { + log.Fatalln("Missing filename") + } + + scenes, err := c.GetScenes(ctx) + if err != nil { + log.Fatalln(err.Error()) + } + + handlers, err := c.GetHandlers(ctx) + if err != nil { + log.Fatalln(err.Error()) + } + + file, err := os.Open(*fileName) + if err != nil { + log.Fatalln("Failed to open file:", err) + } + defer file.Close() + + configs, err := handlerfactory.ParseConfigs(file) + for _, config := range configs { + toAdd, toRemove := handlerfactory.CreateHandlers(config, scenes, handlers) + + for _, removing := range toRemove { + _, err := c.DeleteHandler(ctx, removing.ID) + if err != nil { + log.Fatalln(err.Error()) + } + + log.Printf("Removed handler %d", removing.ID) + } + + for _, adding := range toAdd { + res, err := c.AddHandler(ctx, adding) + if err != nil { + log.Fatalln(err.Error()) + } + + log.Printf("Added handler %d", res.ID) + } + } + + default: + if cmd.Name != "help" { + log.Println("Unknown command:", cmd.Name) + } + + _, _ = fmt.Fprintln(os.Stderr, helpString[1:]) + } +} diff --git a/cmd/lucy/help.go b/cmd/lucy/help.go index df4da3a..5be09ff 100644 --- a/cmd/lucy/help.go +++ b/cmd/lucy/help.go @@ -32,6 +32,23 @@ EVENT HANDLER COMMANDS <-fire> <-fire.payload.*> handler delete +EVENT HANDLER FACTORY COMMANDS + + factory run + +File example (use '---' to delimit multiple configs in one file): +mode: SceneDimmer +target: tag:Soverom +scenes: + - kveldslys + - landskap + - vasking +trigger_device_id: 14 +--- +(more modes coming soon) + +WARNING: Similar event handlers will be removed! + EVENT COMMANDS run <*=S> diff --git a/cmd/lucy/main.go b/cmd/lucy/main.go index 0e74490..f9223b3 100644 --- a/cmd/lucy/main.go +++ b/cmd/lucy/main.go @@ -82,8 +82,8 @@ func main() { case "update": { devices, err := c.PutDevice(ctx, cmd.Params.Get(0).StringOr("all"), models.DeviceUpdate{ - Name: cmd.Params.Get("name").String(), - Icon: cmd.Params.Get("icon").String(), + Name: cmd.Params.Get("name").String(), + Icon: cmd.Params.Get("icon").String(), UserProperties: cmd.Params.Subset("prop.").StringPtrMap(), }) if err != nil { @@ -93,6 +93,9 @@ func main() { WriteDeviceInfoTable(os.Stdout, devices) } + case "factory": + factoryCmd(ctx, c) + case "handler": handlerCmd(ctx, c) @@ -126,4 +129,3 @@ func main() { _, _ = fmt.Fprintln(os.Stderr, helpString[1:]) } } -