package main import ( "encoding/json" "flag" "fmt" log "github.com/sirupsen/logrus" "io" "net/http" "strings" "sync" "time" ) var ( shellyFlag = flag.String("s", "", "list shelly ips, comma seperated") debugFlag = flag.Bool("d", false, "enable debug mode") wg sync.WaitGroup ) 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") } wg := new(sync.WaitGroup) for _, ip := range strings.Split(*shellyFlag, ",") { wg.Add(1) go func() { err := checkShelly(ip) if err != nil { log.Fatalf("shelly worker %s failed: %v", ip, err) } }() } wg.Wait() } func checkShelly(ip string) error { defer wg.Done() 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) } }