From 1634971032351984eff4198fb9d6aba227c07bec Mon Sep 17 00:00:00 2001 From: Arun Poudel Date: Tue, 30 Jul 2019 22:17:03 +0200 Subject: [PATCH] Added support for complex timers --- fimptype/primefimp/testdata/mode.json | 324 ++++++++++++++++++ .../testdata/timer_with_actions.json | 29 ++ .../primefimp/testdata/timer_with_mode.json | 23 ++ .../testdata/timer_with_shortcut.json | 23 ++ fimptype/primefimp/types.go | 70 +++- fimptype/primefimp/types_test.go | 169 +++++---- 6 files changed, 575 insertions(+), 63 deletions(-) create mode 100644 fimptype/primefimp/testdata/mode.json create mode 100644 fimptype/primefimp/testdata/timer_with_actions.json create mode 100644 fimptype/primefimp/testdata/timer_with_mode.json create mode 100644 fimptype/primefimp/testdata/timer_with_shortcut.json diff --git a/fimptype/primefimp/testdata/mode.json b/fimptype/primefimp/testdata/mode.json new file mode 100644 index 0000000..e6354a9 --- /dev/null +++ b/fimptype/primefimp/testdata/mode.json @@ -0,0 +1,324 @@ +[ + { + "action": { + "room": { + "1": { + "heating": { + "desired": 19 + } + }, + "10": { + "heating": { + "desired": 19 + } + }, + "11": { + "heating": { + "desired": 19 + } + }, + "2": { + "heating": { + "desired": 19 + } + }, + "3": { + "heating": { + "desired": 19 + } + }, + "4": { + "heating": { + "desired": 19 + } + }, + "5": { + "heating": { + "desired": 19 + } + }, + "6": { + "heating": { + "desired": 19 + } + }, + "7": { + "heating": { + "desired": 19 + } + }, + "8": { + "heating": { + "desired": 19 + } + }, + "9": { + "heating": { + "desired": 19 + } + } + } + }, + "id": "away" + }, + { + "action": { + "room": { + "1": { + "heating": { + "desired": 23 + } + }, + "10": { + "heating": { + "desired": 23 + } + }, + "11": { + "heating": { + "desired": 23 + } + }, + "2": { + "heating": { + "desired": 23 + } + }, + "3": { + "heating": { + "desired": 23 + } + }, + "4": { + "heating": { + "desired": 23 + } + }, + "5": { + "heating": { + "desired": 23 + } + }, + "6": { + "heating": { + "desired": 23 + } + }, + "7": { + "heating": { + "desired": 23 + } + }, + "8": { + "heating": { + "desired": 23 + } + }, + "9": { + "heating": { + "desired": 23 + } + } + } + }, + "id": "home" + }, + { + "action": { + "device": { + "1": { + "power": "off" + }, + "10": { + "power": "off" + }, + "13": { + "power": "off" + }, + "16": { + "power": "off" + }, + "19": { + "power": "off" + }, + "22": { + "power": "on" + }, + "25": { + "power": "on" + }, + "28": { + "power": "off" + }, + "31": { + "power": "off" + }, + "34": { + "power": "off" + }, + "37": { + "power": "off" + }, + "4": { + "power": "off" + }, + "40": { + "power": "off" + }, + "43": { + "power": "off" + }, + "46": { + "power": "off" + }, + "51": { + "power": "off" + }, + "58": { + "power": "off" + }, + "64": { + "power": "on" + }, + "67": { + "power": "off" + }, + "7": { + "power": "off" + }, + "70": { + "power": "on" + }, + "73": { + "power": "on" + }, + "76": { + "power": "off" + }, + "79": { + "power": "off" + } + }, + "room": { + "1": { + "heating": { + "desired": 19 + } + }, + "10": { + "heating": { + "desired": 19 + } + }, + "11": { + "heating": { + "desired": 19 + } + }, + "2": { + "heating": { + "desired": 19 + } + }, + "3": { + "heating": { + "desired": 19 + } + }, + "4": { + "heating": { + "desired": 19 + } + }, + "5": { + "heating": { + "desired": 19 + } + }, + "6": { + "heating": { + "desired": 19 + } + }, + "7": { + "heating": { + "desired": 19 + } + }, + "8": { + "heating": { + "desired": 19 + } + }, + "9": { + "heating": { + "desired": 19 + } + } + } + }, + "id": "sleep" + }, + { + "action": { + "room": { + "1": { + "heating": { + "desired": 8 + } + }, + "10": { + "heating": { + "desired": 8 + } + }, + "11": { + "heating": { + "desired": 8 + } + }, + "2": { + "heating": { + "desired": 8 + } + }, + "3": { + "heating": { + "desired": 8 + } + }, + "4": { + "heating": { + "desired": 8 + } + }, + "5": { + "heating": { + "desired": 8 + } + }, + "6": { + "heating": { + "desired": 8 + } + }, + "7": { + "heating": { + "desired": 8 + } + }, + "8": { + "heating": { + "desired": 8 + } + }, + "9": { + "heating": { + "desired": 8 + } + } + } + }, + "id": "vacation" + } +] \ No newline at end of file diff --git a/fimptype/primefimp/testdata/timer_with_actions.json b/fimptype/primefimp/testdata/timer_with_actions.json new file mode 100644 index 0000000..61259c2 --- /dev/null +++ b/fimptype/primefimp/testdata/timer_with_actions.json @@ -0,0 +1,29 @@ +{ + "action": { + "device": { + "37": { + "power": "on" + } + } + }, + "client": { + "name": "Test Timer" + }, + "enabled": true, + "id": 3, + "next": "2019-07-31T13:00:00Z", + "prev": null, + "time": { + "recurring": { + "days": [ + "mo", + "tu", + "we", + "th", + "fr" + ], + "skipHolidays": false + }, + "when": "15:00:00" + } +} \ No newline at end of file diff --git a/fimptype/primefimp/testdata/timer_with_mode.json b/fimptype/primefimp/testdata/timer_with_mode.json new file mode 100644 index 0000000..4e90907 --- /dev/null +++ b/fimptype/primefimp/testdata/timer_with_mode.json @@ -0,0 +1,23 @@ +{ + "action": "vacation", + "client": { + "name": "Test Vacation" + }, + "enabled": true, + "id": 3, + "next": "2019-07-31T13:00:00Z", + "prev": null, + "time": { + "recurring": { + "days": [ + "mo", + "tu", + "we", + "th", + "fr" + ], + "skipHolidays": false + }, + "when": "15:00:00" + } +} \ No newline at end of file diff --git a/fimptype/primefimp/testdata/timer_with_shortcut.json b/fimptype/primefimp/testdata/timer_with_shortcut.json new file mode 100644 index 0000000..1c5192e --- /dev/null +++ b/fimptype/primefimp/testdata/timer_with_shortcut.json @@ -0,0 +1,23 @@ +{ + "action": 1, + "client": { + "name": "Test Timer" + }, + "enabled": true, + "id": 3, + "next": "2019-07-31T13:00:00Z", + "prev": null, + "time": { + "recurring": { + "days": [ + "mo", + "tu", + "we", + "th", + "fr" + ], + "skipHolidays": false + }, + "when": "15:00:00" + } +} \ No newline at end of file diff --git a/fimptype/primefimp/types.go b/fimptype/primefimp/types.go index 6a5e24c..5222f1a 100644 --- a/fimptype/primefimp/types.go +++ b/fimptype/primefimp/types.go @@ -1,6 +1,8 @@ package primefimp import ( + "encoding/json" + "errors" "time" ) @@ -121,8 +123,8 @@ type ActionDevice map[string]interface{} type ActionRoom map[string]interface{} type ShortcutAction struct { - Device map[int]ActionDevice `json:"device"` - Room map[int]ActionRoom `json:"room"` + Device ActionDevice `json:"device"` + Room ActionRoom `json:"room"` } type Shortcut struct { @@ -141,8 +143,8 @@ type Hub struct { } type ModeAction struct { - Device map[int]ActionDevice `json:"device"` - Room map[int]ActionRoom `json:"room"` + Device ActionDevice `json:"device"` + Room ActionRoom `json:"room"` } type Mode struct { @@ -150,10 +152,68 @@ type Mode struct { Action ModeAction `json:"action"` } +type TimerAction struct { + Type string + Shortcut int + Mode string + Action ShortcutAction +} + type Timer struct { - Action int `json:"action"` + Action TimerAction Client Client `json:"client"` Enabled bool `json:"enabled"` Time map[string]interface{} `json:"time"` Id int `json:"id"` } + +func (t *Timer) UnmarshalJSON(b []byte) error { + temp := &struct { + Action interface{} + Client Client `json:"client"` + Enabled bool `json:"enabled"` + Time map[string]interface{} `json:"time"` + Id int `json:"id"` + }{} + + err := json.Unmarshal(b, temp) + if err != nil { + return err + } + t.Client = temp.Client + t.Enabled = temp.Enabled + t.Time = temp.Time + t.Id = temp.Id + + switch temp.Action.(type) { + case float64: + t.Action.Type = "shortcut" + t.Action.Shortcut = int(temp.Action.(float64)) + case float32: + // If we are running on a 32 bit machine + t.Action.Type = "shortcut" + t.Action.Shortcut = int(temp.Action.(float32)) + case string: + t.Action.Type = "mode" + t.Action.Mode = temp.Action.(string) + case map[string]interface{}: + t.Action.Type = "custom" + act := temp.Action.(map[string]interface{}) + if actRoom, ok := act["room"]; ok { + t.Action.Action.Room = make(map[string]interface{}) + for idRoom, act := range actRoom.(map[string]interface{}) { + t.Action.Action.Room[idRoom] = act + } + } + if actDevice, ok := act["device"]; ok { + t.Action.Action.Device = make(map[string]interface{}) + for idDevice, act := range actDevice.(map[string]interface{}) { + t.Action.Action.Device[idDevice] = act + } + } + default: + return errors.New("invalid timer structure") + } + + return nil +} diff --git a/fimptype/primefimp/types_test.go b/fimptype/primefimp/types_test.go index 3167d39..88d9f2e 100644 --- a/fimptype/primefimp/types_test.go +++ b/fimptype/primefimp/types_test.go @@ -1,130 +1,183 @@ package primefimp import ( + "encoding/json" "github.com/futurehomeno/fimpgo" log "github.com/sirupsen/logrus" + "io/ioutil" "testing" ) -func TestPrimeFimp_SendFimpWithTopicResponse(t *testing.T) { - log.SetLevel(log.DebugLevel) - mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883","fimpgotest","","",true,1,1) - err := mqtt.Start() - t.Log("Connected") - if err != nil { - t.Error("Error connecting to broker ",err) - } +func TestMode(t *testing.T) { + tb, _ := ioutil.ReadFile("testdata/mode.json") + + var mode []Mode + + err := json.Unmarshal(tb, &mode) + if err != nil { + t.Error(err.Error()) + } - // Actual test - syncClient := fimpgo.NewSyncClient(mqtt) + if mode[0].Id == "" { + t.Errorf("Error unmarshling mode") + } +} - reqAddr := fimpgo.Address{MsgType: fimpgo.MsgTypeCmd, ResourceType: fimpgo.ResourceTypeApp, ResourceName: "vinculum", ResourceAddress: "1"} - respAddr := fimpgo.Address{MsgType: fimpgo.MsgTypeRsp, ResourceType: fimpgo.ResourceTypeApp, ResourceName: "fimpgo-test", ResourceAddress: "1"} - syncClient.AddSubscription(respAddr.Serialize()) +func TestTimerWithActions(t *testing.T) { + tb, _ := ioutil.ReadFile("testdata/timer_with_actions.json") + var timer Timer - param := RequestParam{Components: []string{"device"}} - req := Request{Cmd:"get",Param:param } + json.Unmarshal(tb, &timer) - msg := fimpgo.NewMessage("cmd.pd7.request", "vinculum",fimpgo.VTypeObject, req, nil, nil, nil) - msg.ResponseToTopic = respAddr.Serialize() - msg.Source = "fimpgo-test" - response,err := syncClient.SendFimpWithTopicResponse(reqAddr.Serialize(),msg,respAddr.Serialize(),"temp_sensor","",5) - if err != nil { - t.Error("Error",err) - t.Fail() - } - resp := Response{} - err = response.GetObjectValue(&resp) + device37 := timer.Action.Action.Device["37"].(map[string]interface{}) - t.Log(resp.Success) - if err != nil { - t.Error("Error",err) - t.Fail() - } - syncClient.Stop() - if len(resp.GetDevices()) == 0 { - t.Error("No rooms") - t.Fail() - } + if device37["power"].(string) != "on" { + t.Errorf("Wrong power value for device 37. Expecting: on, Got: %s", device37["power"].(string)) + } +} + +func TestTimerWithMode(t *testing.T) { + tb, _ := ioutil.ReadFile("testdata/timer_with_mode.json") + + var timer Timer + + json.Unmarshal(tb, &timer) + + if timer.Action.Type != "mode" { + t.Errorf("Wrong action type. Expection: mode, Got: %s", timer.Action.Mode) + } +} + +func TestTimerWithShortcut(t *testing.T) { + tb, _ := ioutil.ReadFile("testdata/timer_with_shortcut.json") + + var timer Timer + + json.Unmarshal(tb, &timer) + + if timer.Action.Type != "shortcut" { + t.Errorf("Wrong action type. Expection: mode, Got: %s", timer.Action.Mode) + } +} + +func TestPrimeFimp_SendFimpWithTopicResponse(t *testing.T) { + log.SetLevel(log.DebugLevel) + mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883", "fimpgotest", "", "", true, 1, 1) + err := mqtt.Start() + t.Log("Connected") + if err != nil { + t.Error("Error connecting to broker ", err) + } + + // Actual test + syncClient := fimpgo.NewSyncClient(mqtt) + + reqAddr := fimpgo.Address{MsgType: fimpgo.MsgTypeCmd, ResourceType: fimpgo.ResourceTypeApp, ResourceName: "vinculum", ResourceAddress: "1"} + respAddr := fimpgo.Address{MsgType: fimpgo.MsgTypeRsp, ResourceType: fimpgo.ResourceTypeApp, ResourceName: "fimpgo-test", ResourceAddress: "1"} + syncClient.AddSubscription(respAddr.Serialize()) + + param := RequestParam{Components: []string{"device"}} + req := Request{Cmd: "get", Param: param} + + msg := fimpgo.NewMessage("cmd.pd7.request", "vinculum", fimpgo.VTypeObject, req, nil, nil, nil) + msg.ResponseToTopic = respAddr.Serialize() + msg.Source = "fimpgo-test" + response, err := syncClient.SendFimpWithTopicResponse(reqAddr.Serialize(), msg, respAddr.Serialize(), "temp_sensor", "", 5) + if err != nil { + t.Error("Error", err) + t.Fail() + } + resp := Response{} + err = response.GetObjectValue(&resp) + + t.Log(resp.Success) + if err != nil { + t.Error("Error", err) + t.Fail() + } + syncClient.Stop() + if len(resp.GetDevices()) == 0 { + t.Error("No rooms") + t.Fail() + } t.Log("Response test - OK , total number of devices = ", len(resp.GetDevices())) } func TestPrimeFimp_ClientApi_GetDevices(t *testing.T) { log.SetLevel(log.DebugLevel) - mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883","fimpgotest","","",true,1,1) + mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883", "fimpgotest", "", "", true, 1, 1) err := mqtt.Start() t.Log("Connected") if err != nil { - t.Error("Error connecting to broker ",err) + t.Error("Error connecting to broker ", err) } - client := NewApiClient("test-1",mqtt,false) - devices ,err := client.GetDevices(false) + client := NewApiClient("test-1", mqtt, false) + devices, err := client.GetDevices(false) if err != nil { - t.Error("Error",err) + t.Error("Error", err) t.Fail() } if len(devices) == 0 { t.Error("Site should have more then 0 devices ") } - log.Infof("SIte contains %d devices",len(devices) ) + log.Infof("SIte contains %d devices", len(devices)) client.Stop() } func TestPrimeFimp_ClientApi_GetSite(t *testing.T) { log.SetLevel(log.DebugLevel) - mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883","fimpgotest","","",true,1,1) + mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883", "fimpgotest", "", "", true, 1, 1) err := mqtt.Start() t.Log("Connected") if err != nil { - t.Error("Error connecting to broker ",err) + t.Error("Error connecting to broker ", err) } - client := NewApiClient("test-1",mqtt,false) - site ,err := client.GetSite(false) + client := NewApiClient("test-1", mqtt, false) + site, err := client.GetSite(false) if err != nil { - t.Error("Error",err) + t.Error("Error", err) t.Fail() } if len(site.Devices) == 0 { t.Error("Site should have more then 0 devices ") } - log.Infof("SIte contains %d devices",len(site.Devices) ) + log.Infof("SIte contains %d devices", len(site.Devices)) client.Stop() } - func TestPrimeFimp_ClientApi_Notify(t *testing.T) { log.SetLevel(log.DebugLevel) - mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883","fimpgotest","","",true,1,1) + mqtt := fimpgo.NewMqttTransport("tcp://cube.local:1883", "fimpgotest", "", "", true, 1, 1) err := mqtt.Start() t.Log("Connected") if err != nil { - t.Error("Error connecting to broker ",err) + t.Error("Error connecting to broker ", err) } // Actual test - notifyCh := make(chan Notify,10) + notifyCh := make(chan Notify, 10) - client := NewApiClient("test-1",mqtt,false) - client.RegisterChannel("test-1-ch",notifyCh) + client := NewApiClient("test-1", mqtt, false) + client.RegisterChannel("test-1-ch", notifyCh) client.StartNotifyRouter() - i :=0 + i := 0 for msg := range notifyCh { if msg.Component == ComponentDevice { - log.Infof("New notify from device %s",msg.GetDevice().Client.Name) + log.Infof("New notify from device %s", msg.GetDevice().Client.Name) } - log.Infof("New notify message of cmd = %s,comp = %s",msg.Cmd,msg.Component) + log.Infof("New notify message of cmd = %s,comp = %s", msg.Cmd, msg.Component) i++ - if i >3 { + if i > 3 { break } } client.Stop() -} \ No newline at end of file +}