Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added get status directly from device if status url is provided #5

Merged
merged 1 commit into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
36 changes: 15 additions & 21 deletions cmd/terrarium-bot-v2/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions cmd/terrarium-bot-v2/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 := `
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
}
6 changes: 6 additions & 0 deletions cmd/terrarium-bot-v2/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 0 additions & 7 deletions cmd/terrarium-bot-v2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
40 changes: 35 additions & 5 deletions cmd/terrarium-bot-v2/switch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"encoding/json"
"fmt"
"log"
"os"
"strings"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
60 changes: 41 additions & 19 deletions cmd/terrarium-bot-v2/switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package main

import (
"bytes"
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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("", "")
Expand Down