188 lines
4.7 KiB
Go
188 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|