diff --git a/README.md b/README.md index b97edac..051f2a0 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,3 @@ Call an action from a URL endpoint, I use my gpio-to-api app to monitor a gpio b ## The End Hopefully the configuration should be pretty self explanitory, if you get stuck or there are any features you think might be missing then feel free to create an issue :slightly_smiling_face:. - -## Known Issues -- My crappy sonoff devices don't support getting on/off status over LAN (might replace them at some point) so rather than checking if something is already on/off, I just set on/off anyway. E.g. A switch might be on and I turn it 'on' again. I have added a quick workaround which you can enable if you want `USE_IN_MEMORY_STATUS=true` which will store the status in memory after it has been changed. However, if you turn on or off a switch outside the application, it will not know you have done so. diff --git a/cmd/terrarium-bot-v2/config.go b/cmd/terrarium-bot-v2/config.go index 156ccfa..7febe14 100644 --- a/cmd/terrarium-bot-v2/config.go +++ b/cmd/terrarium-bot-v2/config.go @@ -11,19 +11,18 @@ import ( ) type Config struct { - Debug bool - DryRun bool - File string - Day StartAction `yaml:"day"` - Night StartAction `yaml:"night"` - Sunrise StartAction `yaml:"sunrise"` - Sunset StartAction `yaml:"sunset"` - Trigger []*Trigger `yaml:"trigger"` - Switch []*Switch `yaml:"switch"` - Sensor []*Sensor `yaml:"sensor"` - Notification []*Notification `yaml:"notification"` - Alert []*Alert `yaml:"alert"` - UseInMemoryStatus bool + Debug bool + DryRun bool + File string + Day StartAction `yaml:"day"` + Night StartAction `yaml:"night"` + Sunrise StartAction `yaml:"sunrise"` + Sunset StartAction `yaml:"sunset"` + Trigger []*Trigger `yaml:"trigger"` + Switch []*Switch `yaml:"switch"` + Sensor []*Sensor `yaml:"sensor"` + Notification []*Notification `yaml:"notification"` + Alert []*Alert `yaml:"alert"` } type StartAction struct { @@ -48,8 +47,10 @@ type Switch struct { Id string `yaml:"id"` On string `yaml:"on"` Off string `yaml:"off"` + StatusUrl string `yaml:"status"` + JsonPath string `yaml:"jsonPath"` Insecure bool `yaml:"insecure"` - Status string // on/off + State string // on/off Disabled time.Duration DisabledAt time.Time LastAction time.Time @@ -145,13 +146,6 @@ func (config Config) Load() Config { config.Sunrise.StartTime, _ = time.Parse("15:04", config.Sunrise.Start) config.Sunset.StartTime, _ = time.Parse("15:04", config.Sunset.Start) - // special parameter - if strings.ToLower(os.Getenv("USE_IN_MEMORY_STATUS")) == "true" { - config.UseInMemoryStatus = true - } else { - config.UseInMemoryStatus = false - } - log.Println("Configuration loaded...") return config diff --git a/cmd/terrarium-bot-v2/config_test.go b/cmd/terrarium-bot-v2/config_test.go index f777740..3725ff4 100644 --- a/cmd/terrarium-bot-v2/config_test.go +++ b/cmd/terrarium-bot-v2/config_test.go @@ -15,7 +15,6 @@ func TestLoadConfig(t *testing.T) { os.Setenv("CONFIG_FILE", "test.yaml") os.Setenv("NOTIFICATION_USER_TOKEN", "user123") os.Setenv("NOTIFICATION_API_TOKEN", "api123") - os.Setenv("USE_IN_MEMORY_STATUS", "true") // create the test yaml file configFileYaml := ` @@ -55,6 +54,8 @@ switch: - id: "switch1" on: "http://localhost:8081/on" off: "http://localhost:8081/off" + status: "http://localhost:8081/status" + jsonPath: "me.status" insecure: true - id: "switch2" on: "http://localhost:8081/on" @@ -86,7 +87,7 @@ alert: below: 5 after: "20m" notification: ["notification1"] -useInMemoryStatus: true` +` err := os.WriteFile("test.yaml", []byte(configFileYaml), 0644) if err != nil { t.Errorf("Error creating test.yaml: %v", err) @@ -137,6 +138,8 @@ useInMemoryStatus: true` assert.Equal(t, "switch1", loadedConfig.Switch[0].Id) assert.Equal(t, "http://localhost:8081/on", loadedConfig.Switch[0].On) assert.Equal(t, "http://localhost:8081/off", loadedConfig.Switch[0].Off) + assert.Equal(t, "http://localhost:8081/status", loadedConfig.Switch[0].StatusUrl) + assert.Equal(t, "me.status", loadedConfig.Switch[0].JsonPath) assert.True(t, loadedConfig.Switch[0].Insecure) assert.Len(t, loadedConfig.Sensor, 2) @@ -164,8 +167,6 @@ useInMemoryStatus: true` assert.Equal(t, 20*time.Minute, loadedConfig.Alert[0].After) assert.Equal(t, []string{"notification1"}, loadedConfig.Alert[0].Notification) - assert.True(t, loadedConfig.UseInMemoryStatus) - // clean up os.Remove("test.yaml") } diff --git a/cmd/terrarium-bot-v2/configuration.yaml b/cmd/terrarium-bot-v2/configuration.yaml index ca894fc..98bece8 100644 --- a/cmd/terrarium-bot-v2/configuration.yaml +++ b/cmd/terrarium-bot-v2/configuration.yaml @@ -101,15 +101,21 @@ switch: - id: big_led on: http://meross-lan-api:8080/turnOn/big-led off: http://meross-lan-api:8080/turnOff/big-led + status: http://meross-lan-api:8080/status/big-led + jsonPath: Status - id: uvb on: http://sonoff-lan-api:8080/turnOn/uvb off: http://sonoff-lan-api:8080/turnOff/uvb - id: heater on: http://meross-lan-api:8080/turnOn/heat off: http://meross-lan-api:8080/turnOff/heat + status: http://meross-lan-api:8080/status/heat + jsonPath: Status - id: mister on: http://meross-lan-api:8080/turnOn/mister off: http://meross-lan-api:8080/turnOff/mister + status: http://meross-lan-api:8080/status/mister + jsonPath: Status - id: fan on: http://terrarium-fan-control:8080/turnOn off: http://terrarium-fan-control:8080/turnOff diff --git a/cmd/terrarium-bot-v2/main.go b/cmd/terrarium-bot-v2/main.go index d6a2e79..c17f48b 100644 --- a/cmd/terrarium-bot-v2/main.go +++ b/cmd/terrarium-bot-v2/main.go @@ -28,13 +28,6 @@ func main() { log.Println("****************************************") } - if config.UseInMemoryStatus { - log.Println("*****************************************************") - log.Println("**** 'USE_IN_MEMORY_STATUS' is active ****") - log.Println("**** Please do not switch any switches manually ****") - log.Println("*****************************************************") - } - InitSensors() InitSwitches() InitTime() diff --git a/cmd/terrarium-bot-v2/switch.go b/cmd/terrarium-bot-v2/switch.go index ac8d850..903fd85 100644 --- a/cmd/terrarium-bot-v2/switch.go +++ b/cmd/terrarium-bot-v2/switch.go @@ -1,6 +1,8 @@ package main import ( + "encoding/json" + "fmt" "log" "os" "strings" @@ -30,11 +32,39 @@ func (s *Switch) SetLastAction() { } func (s *Switch) getStatus() string { - return s.Status + if s.StatusUrl == "" { + return s.State + } + + r, respCode, err := SendRequest(s.StatusUrl, s.Insecure) + if err != nil || respCode != 200 { + log.Printf("Switch Offline: %s", s.Id) + for _, n := range config.Notification { + n.SendNotification("Currently unable to get status for switch '%s'. Please check the logs.", s.Id) + } + } else { + // use resp to get status + b, err := json.MarshalIndent(r, "", " ") + if err != nil { + log.Println(err) + return s.State + } + state := strings.ToLower(fmt.Sprintf("%s", getJsonValue(string(b), s.JsonPath))) + if state == "on" || state == "off" { + s.State = state + } else { + log.Printf("Unknown status '%s' received for switch '%s'", state, s.Id) + return s.State + } + } + + return s.State } -func (s *Switch) setStatus(status string) { - s.Status = status +func (s *Switch) setStatus(state string) { + if s.StatusUrl == "" { + s.State = state + } } func (s *Switch) Enable(reason string) { @@ -80,7 +110,7 @@ func (s *Switch) fixURLs() { } func (s *Switch) TurnOn(For string, Reason string) { - if config.UseInMemoryStatus && s.getStatus() == "on" { + if s.getStatus() == "on" { return } // check for disable parameter @@ -111,7 +141,7 @@ func (s *Switch) TurnOn(For string, Reason string) { } func (s *Switch) TurnOff(reason string) { - if config.UseInMemoryStatus && s.getStatus() == "off" { + if s.getStatus() == "off" { return } s.SetLastAction() diff --git a/cmd/terrarium-bot-v2/switch_test.go b/cmd/terrarium-bot-v2/switch_test.go index 2e6edcb..6b51398 100644 --- a/cmd/terrarium-bot-v2/switch_test.go +++ b/cmd/terrarium-bot-v2/switch_test.go @@ -2,7 +2,10 @@ package main import ( "bytes" + "fmt" "log" + "net/http" + "net/http/httptest" "os" "strings" "testing" @@ -35,19 +38,47 @@ func TestSwitchSetLastAction(t *testing.T) { // Switch get status and set status function tests func TestSwitchGetSetStatus(t *testing.T) { - s := &Switch{} + s1 := &Switch{ + Id: "test-switch-1", + On: "switch1.com/on", + Off: "switch1.com/off", + } // Set status to on and ensure that it's set correctly - s.setStatus("on") - if s.Status != "on" { + s1.setStatus("on") + if s1.State != "on" { t.Errorf("Status was not set correctly") } // Get status and ensure that it's returned correctly - status := s.getStatus() - if status != s.Status { + state := s1.getStatus() + if state != s1.State { t.Errorf("getStatus did not return the correct status") } + + // configure mock http response + mockResponse := `{"name": "test-switch-2", "status": "on"}` + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, mockResponse) + }) + server := httptest.NewServer(mux) + defer server.Close() + + // Setup a switch status response + s2 := &Switch{ + Id: "test-switch-2", + On: "switch2.com/on", + Off: "switch2.com/off", + StatusUrl: server.URL, + JsonPath: "status", + } + state = s2.getStatus() + if state != "on" { + t.Errorf("getStatus did not return the correct status: got %v, want %v", state, "on") + } } // Switch set disable function tests @@ -125,25 +156,16 @@ func TestSwitchTurnOn(t *testing.T) { t.Errorf("Switch did not turn on as expected") } - // Test case 2: Switch doesn't turn on again when already on with UseInMemoryStatus enabled + // Test case 2: Switch doesn't turn on again when already on var buf bytes.Buffer log.SetOutput(&buf) - config.UseInMemoryStatus = true s.TurnOn("", "") - if strings.Contains(buf.String(), "Switch On 'on-test'") { - t.Errorf("Switch turned on again while 'USE_IN_MEMORY_STATUS' was set") - } - - // Test case 3: Switch turns on when already on with UseInMemoryStatus disabled - config.UseInMemoryStatus = false - buf.Reset() - s.TurnOn("", "") - if !strings.Contains(buf.String(), "Switch On: 'on-test'") { - t.Errorf("Switch did not turn on again while 'USE_IN_MEMORY_STATUS' was unset, got %v", buf.String()) + if strings.Contains(buf.String(), "Switch On: 'on-test'") { + t.Errorf("Switch turned on again while already on") } - // Test case 4: Switch turns off after 'for' duration + // Test case 3: Switch turns off after 'for' duration s.TurnOff("") // ensure switch is off go s.TurnOn("2s", "") // turn on for 2 seconds time.Sleep(1 * time.Second) @@ -155,7 +177,7 @@ func TestSwitchTurnOn(t *testing.T) { t.Errorf("Switch did not turn off after 2 seconds as expected") } - // Test case 5: Switch does not turn on if recently turned off and Disable is specified + // Test case 4: Switch does not turn on if recently turned off and Disable is specified s.TurnOff("") s.Disable("10m", "") s.TurnOn("", "")