diff --git a/proxy.go b/proxy.go index be5c65b..8586f02 100644 --- a/proxy.go +++ b/proxy.go @@ -17,72 +17,15 @@ import ( "github.com/robfig/cron/v3" ) -type ShellyStatus struct { - Wifi ShellyWifiStatus `json:"wifi_sta"` - Cloud ShellyCloudStatus `json:"cloud"` - Mqtt ShellyMqttStatus `json:"mqtt"` - Time string `json:"time"` - Serial int `json:"serial"` - HasUpdate bool `json:"has_update"` - MAC string `json:"mac"` - Relays []ShellyRelaysStatus `json:"relays"` - Meters []ShellyMetersStatus `json:"meters"` - Temperature float32 `json:"temperature"` - OverTemperature bool `json:"overtemperature"` - Update ShellyUpdateStatus `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"` -} - -type ShellyWifiStatus struct { - Connected bool `json:"connected"` - SSID string `json:"ssid"` - IP string `json:"ip"` - RSSI int `json:"rssi"` -} - -type ShellyCloudStatus struct { - Enabled bool `json:"enabled"` - Connected bool `json:"connected"` -} - -type ShellyMqttStatus struct { - Connected bool `json:"connected"` -} - -type ShellyRelaysStatus struct { - IsOn bool `json:"ison"` - HasTimer bool `json:"has_timer"` - Overpower bool `json:"overpower"` -} - -type ShellyMetersStatus struct { - Power float32 `json:"power"` - IsValid bool `json:"is_valid"` - Timestamp int `json:"timestamp"` - Counters []float32 `json:"counters"` - Total int `json:"total"` -} - -type ShellyUpdateStatus struct { - Status string `json:"status"` - HasUpdate bool `json:"has_update"` - NewVersion string `json:"new_version"` - OldVersion string `json:"old_version"` -} - var ( flagset = flag.NewFlagSet("shelly-proxy", flag.ContinueOnError) // config flags - shellyHost = flagset.String("shellyplug-host", "192.168.30.1", "Shellyplug address (also via SHELLYPLUG_HOST)") + shellyHost = flagset.String("shellyplug-host", "192.168.33.1", "Shellyplug address (also via SHELLYPLUG_HOST)") shellyPort = flagset.Int("shellyplug-port", 80, "Shellyplug port (also via SHELLYPLUG_HOST)") //shellyAuthUser = flagset.String("shellyplug-auth-username", "", "Shellyplug authentication username (also via SHELLYPLUG_AUTH_USERNAME)") //shellyAuthPass = flagset.String("shellyplug-auth-password", "", "Shellyplug authentication username (also via SHELLYPLUG_AUTH_PASSWORD)") - exporterPort = flagset.Int("exporter-port", 9000, "Exporter port (also via EXPORTER_PORT)") + exporterPort = flagset.Int("exporter-port", 5000, "Exporter port (also via EXPORTER_PORT)") // additional flags _ = flagset.String("config", "", "Path to config file (ini format)") @@ -92,6 +35,17 @@ var ( ss *ShellyStatus ) +type ShellyStatus struct { + Status1 *ShellyStatus1 + Status2 *ShellyStatus2 + Version int +} + +type ShellyVersion struct { + Gen int `json:"gen"` + Type string `json:"type"` +} + func main() { // use .env file if it exists @@ -134,24 +88,78 @@ func main() { } } -func (s *ShellyStatus) Fetch() { - resp, err := http.Get(fmt.Sprintf("http://%s:%d/status", *shellyHost, *shellyPort)) +func (s *ShellyStatus) GetVersion() int { + if s.Version != 0 { + return s.Version + } + resp, err := http.Get(fmt.Sprintf("http://%s:%d/shelly", *shellyHost, *shellyPort)) if err != nil { - log.Fatalf("ShellyStatus.Fetch : http.Get (%s)", err) + log.Fatalf("ShellyStatus.GetVersion : http.Get (%s)", err) } body, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("ShellyStatus.Fetch : io.ReadAll (%s)", err) + log.Fatalf("ShellyStatus.GetVersion : io.ReadAll (%s)", err) } - if err = json.Unmarshal(body, s); err != nil { - log.Fatalf("ShellyStatus.Fetch : json.Unmarshal (%s)", err) + v := ShellyVersion{} + if err = json.Unmarshal(body, &v); err != nil { + log.Fatalf("ShellyStatus.GetVersion : json.Unmarshal (%s)", err) + } + + if v.Type != "" { + s.Version = 1 + s.Status1 = &ShellyStatus1{} + } + if v.Gen != 0 { + s.Version = v.Gen + s.Status2 = &ShellyStatus2{} + } + + return s.Version +} + +func (s *ShellyStatus) Fetch() { + switch s.GetVersion() { + case 1: + resp, err := http.Get(fmt.Sprintf("http://%s:%d/status", *shellyHost, *shellyPort)) + if err != nil { + log.Fatalf("ShellyStatus.Fetch : http.Get (%s)", err) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("ShellyStatus.Fetch : io.ReadAll (%s)", err) + } + + if err = json.Unmarshal(body, s.Status1); err != nil { + log.Fatalf("ShellyStatus.Fetch : json.Unmarshal (%s)", err) + } + return + case 2: + resp, err := http.Get(fmt.Sprintf("http://%s:%d/rpc/Shelly.GetStatus", *shellyHost, *shellyPort)) + if err != nil { + log.Fatalf("ShellyStatus.Fetch : http.Get (%s)", err) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("ShellyStatus.Fetch : io.ReadAll (%s)", err) + } + + if err = json.Unmarshal(body, s.Status2); err != nil { + log.Fatalf("ShellyStatus.Fetch : json.Unmarshal (%s)", err) + } + return + default: + return } } func GetMetrics(c *gin.Context) { - c.String(http.StatusOK, `# HELP shellyplug_power Current power drawn in watts + switch ss.Version { + case 1: + c.String(http.StatusOK, `# HELP shellyplug_power Current power drawn in watts # TYPE shellyplug_power gauge shellyplug_power %.2f # HELP shellyplug_overpower Overpower drawn in watts/minute @@ -166,7 +174,43 @@ shellyplug_temperature %.2f # HELP shellyplug_uptime Plug uptime in seconds # TYPE shellyplug_uptime gauge shellyplug_uptime %d -`, ss.Meters[0].Counters[0], float32(0), ss.Meters[0].Total, ss.Temperature, ss.Uptime) +`, ss.Status1.Meters[0].Counters[0], float32(0), 0, ss.Status1.Temperature, ss.Status1.Uptime) + case 2: + c.String(http.StatusOK, `# HELP shellyplug_power Current power drawn in watts +# TYPE shellyplug_power gauge +shellyplug_power %.2f +# HELP shellyplug_overpower Overpower drawn in watts/minute +# TYPE shellyplug_overpower gauge +shellyplug_overpower %.2f +# HELP shellyplug_total_power Total power consumed in watts/minute +# TYPE shellyplug_total_power counter +shellyplug_total_power %d +# HELP shellyplug_temperature Plug temperature in celsius +# TYPE shellyplug_temperature gauge +shellyplug_temperature %.2f +# HELP shellyplug_uptime Plug uptime in seconds +# TYPE shellyplug_uptime gauge +shellyplug_uptime %d +`, ss.Status2.Switch.Power, float32(0), 0, ss.Status2.Switch.Temperature.Celsius, ss.Status2.Sys.Uptime) + default: + c.String(http.StatusOK, `# HELP shellyplug_power Current power drawn in watts +# TYPE shellyplug_power gauge +shellyplug_power %.2f +# HELP shellyplug_overpower Overpower drawn in watts/minute +# TYPE shellyplug_overpower gauge +shellyplug_overpower %.2f +# HELP shellyplug_total_power Total power consumed in watts/minute +# TYPE shellyplug_total_power counter +shellyplug_total_power %d +# HELP shellyplug_temperature Plug temperature in celsius +# TYPE shellyplug_temperature gauge +shellyplug_temperature %.2f +# HELP shellyplug_uptime Plug uptime in seconds +# TYPE shellyplug_uptime gauge +shellyplug_uptime %d +`, float32(0), float32(0), 0, float32(0), 0) + } + } // IniParser is a parser for config files in classic key/value style format. Each diff --git a/shelly1.go b/shelly1.go new file mode 100644 index 0000000..ee87957 --- /dev/null +++ b/shelly1.go @@ -0,0 +1,58 @@ +package main + +type ShellyStatus1 struct { + Wifi ShellyWifiStatus1 `json:"wifi_sta"` + Cloud ShellyCloudStatus1 `json:"cloud"` + Mqtt ShellyMqttStatus1 `json:"mqtt"` + Time string `json:"time"` + Serial int `json:"serial"` + HasUpdate bool `json:"has_update"` + MAC string `json:"mac"` + Relays []ShellyRelaysStatus1 `json:"relays"` + Meters []ShellyMetersStatus1 `json:"meters"` + Temperature float32 `json:"temperature"` + OverTemperature bool `json:"overtemperature"` + Update ShellyUpdateStatus1 `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"` +} + +type ShellyWifiStatus1 struct { + Connected bool `json:"connected"` + SSID string `json:"ssid"` + IP string `json:"ip"` + RSSI int `json:"rssi"` +} + +type ShellyCloudStatus1 struct { + Enabled bool `json:"enabled"` + Connected bool `json:"connected"` +} + +type ShellyMqttStatus1 struct { + Connected bool `json:"connected"` +} + +type ShellyRelaysStatus1 struct { + IsOn bool `json:"ison"` + HasTimer bool `json:"has_timer"` + Overpower bool `json:"overpower"` +} + +type ShellyMetersStatus1 struct { + Power float32 `json:"power"` + IsValid bool `json:"is_valid"` + Timestamp int `json:"timestamp"` + Counters []float32 `json:"counters"` + Total int `json:"total"` +} + +type ShellyUpdateStatus1 struct { + Status string `json:"status"` + HasUpdate bool `json:"has_update"` + NewVersion string `json:"new_version"` + OldVersion string `json:"old_version"` +} diff --git a/shelly2.go b/shelly2.go new file mode 100644 index 0000000..5b3ebeb --- /dev/null +++ b/shelly2.go @@ -0,0 +1,29 @@ +package main + +type ShellyStatus2 struct { + Switch ShellySwitch2 `json:"switch:0"` + Sys ShellySys2 `json:"sys"` +} + +type ShellySwitch2 struct { + Power float32 `json:"apower"` + Voltage float32 `json:"voltage"` + Current float32 `json:"current"` + Energy ShellyEnergy2 `json:"aenergy"` + Temperature ShellyTemperature2 `json:"temperature"` +} + +type ShellyEnergy2 struct { + Total float32 `json:"total"` + ByMinute []float32 `json:"by_minute"` + MinuteTS int `json:"minute_ts"` +} + +type ShellyTemperature2 struct { + Celsius float32 `json:"tC"` + Fahrenheit float32 `json:"tF"` +} + +type ShellySys2 struct { + Uptime int `json:"uptime"` +}