commit 13c33e1ff16b1e55302fe934231f74c7ed67bdb9 Author: Giovanni Harting <539@idlegandalf.com> Date: Thu Mar 27 13:18:04 2025 +0100 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cd52232 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module ShellyCalibrationHelper + +go 1.24 + +require github.com/sirupsen/logrus v1.9.3 + +require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..21f9bfb --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f100ea2 --- /dev/null +++ b/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + log "github.com/sirupsen/logrus" + "io" + "net/http" + "strings" + "time" +) + +var ( + shellyFlag = flag.String("s", "", "list shelly ips, comma seperated") + debugFlag = flag.Bool("d", false, "enable debug mode") +) + +const ( + SleepMinutes = 5 +) + +type ShellyStatus struct { + WifiSta struct { + Connected bool `json:"connected"` + Ssid string `json:"ssid"` + Ip string `json:"ip"` + Rssi int `json:"rssi"` + } `json:"wifi_sta"` + Cloud struct { + Enabled bool `json:"enabled"` + Connected bool `json:"connected"` + } `json:"cloud"` + Mqtt struct { + Connected bool `json:"connected"` + } `json:"mqtt"` + Time string `json:"time"` + UnixTime int `json:"unixtime"` + Serial int `json:"serial"` + HasUpdate bool `json:"has_update"` + Mac string `json:"mac"` + CfgChangedCnt int `json:"cfg_changed_cnt"` + ActionsStats struct { + Skipped int `json:"skipped"` + } `json:"actions_stats"` + Thermostats []struct { + Pos float64 `json:"pos"` + TargetT struct { + Enabled bool `json:"enabled"` + Value float64 `json:"value"` + ValueOp float64 `json:"value_op"` + Units string `json:"units"` + } `json:"target_t"` + Tmp struct { + Value float64 `json:"value"` + Units string `json:"units"` + IsValid bool `json:"is_valid"` + } `json:"tmp"` + Schedule bool `json:"schedule"` + ScheduleProfile int `json:"schedule_profile"` + BoostMinutes int `json:"boost_minutes"` + WindowOpen bool `json:"window_open"` + } `json:"thermostats"` + Calibrated bool `json:"calibrated"` + Bat struct { + Value int `json:"value"` + Voltage float64 `json:"voltage"` + } `json:"bat"` + Charger bool `json:"charger"` + Update struct { + Status string `json:"status"` + HasUpdate bool `json:"has_update"` + NewVersion string `json:"new_version"` + OldVersion string `json:"old_version"` + BetaVersion interface{} `json:"beta_version"` + } `json:"update"` + RamTotal int `json:"ram_total"` + RamFree int `json:"ram_free"` + FsSize int `json:"fs_size"` + FsFree int `json:"fs_free"` + Uptime int `json:"uptime"` + FwInfo struct { + Device string `json:"device"` + Fw string `json:"fw"` + } `json:"fw_info"` + PsMode int `json:"ps_mode"` + DbgFlags int `json:"dbg_flags"` +} + +type ShellyRebootResponse struct { + Ok bool `json:"ok"` +} + +func main() { + flag.Parse() + + if *debugFlag { + log.SetLevel(log.DebugLevel) + } + + if *shellyFlag == "" { + log.Fatal("no shelly ips specified") + } + + for _, ip := range strings.Split(*shellyFlag, ",") { + go func() { + err := checkShelly(ip) + if err != nil { + log.Fatalf("shelly worker %s failed: %v", ip, err) + } + }() + } +} + +func checkShelly(ip string) error { + log.Infof("shelly worker %s started", ip) + + for { + resp, err := http.Get(fmt.Sprintf("http://%s/status", ip)) + if err != nil { + log.Warningf("shelly %s status check failed: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + if resp.StatusCode != 200 { + log.Warningf("shelly %s status check failed: %v", ip, resp.Status) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + respBin, err := io.ReadAll(resp.Body) + if err != nil { + log.Warningf("shelly %s response cannot be read: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + shellyStatus := new(ShellyStatus) + if err := json.Unmarshal(respBin, shellyStatus); err != nil { + log.Warningf("shelly %s response cannot be parsed: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + if !shellyStatus.Calibrated { + log.Infof("shelly %s calibrated: %v", ip, shellyStatus.Calibrated) + rebootResp, err := http.Get(fmt.Sprintf("http://%s/reboot", ip)) + if err != nil { + log.Warningf("shelly %s reboot request failed: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + if rebootResp.StatusCode != 200 { + log.Warningf("shelly %s reboot request failed: %v", ip, resp.Status) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + rebootBin, err := io.ReadAll(rebootResp.Body) + if err != nil { + log.Warningf("shelly %s reboot response cannot be read: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + + shellyReboot := new(ShellyRebootResponse) + if err := json.Unmarshal(rebootBin, shellyReboot); err != nil { + log.Warningf("shelly %s reboot response cannot be parsed: %v", ip, err) + time.Sleep(time.Duration(SleepMinutes) * time.Minute) + continue + } + log.Infof("shelly %s reboot response: %v", ip, shellyReboot.Ok) + } + + time.Sleep(time.Minute * 5) + } +}