Shelly: improve channel support/ standardise Pro EM readings (#20141)

This commit is contained in:
Markus Thierolf 2025-03-27 11:10:24 +01:00 • committed by GitHub
parent 782db8f9f1
commit 6f5c0b6478
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 43 additions and 39 deletions

View file

@ -16,7 +16,7 @@ func NewEnergyMeter(conn *Connection) *EnergyMeter {
// CurrentPower implements the api.Meter interface
func (sh *EnergyMeter) CurrentPower() (float64, error) {
var res Gen2EmStatusResponse
var res Gen2StatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, err
}
@ -38,7 +38,7 @@ var _ api.PhaseCurrents = (*EnergyMeter)(nil)
// Currents implements the api.PhaseCurrents interface
func (sh *EnergyMeter) Currents() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
var res Gen2StatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}
@ -50,7 +50,7 @@ var _ api.PhaseVoltages = (*EnergyMeter)(nil)
// Voltages implements the api.PhaseVoltages interface
func (sh *EnergyMeter) Voltages() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
var res Gen2StatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}
@ -62,7 +62,7 @@ var _ api.PhasePowers = (*EnergyMeter)(nil)
// Powers implements the api.PhasePowers interface
func (sh *EnergyMeter) Powers() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
var res Gen2StatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}

View file

@ -42,25 +42,18 @@ func (sh *Switch) CurrentPower() (float64, error) {
}
default:
var resem Gen2EmStatusResponse
var res Gen2StatusResponse
if d.app == "Pro3EM" && d.profile == "monophase" {
if err := d.execGen2Cmd("Shelly.GetStatus", false, &resem); err != nil {
return 0, err
}
} else {
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
power = res.Switch1.Apower + res.Pm1.Apower + resem.Em1.ActPower
power = res.Switch1.Apower + res.Pm1.Apower + res.Em1.ActPower
case 2:
power = res.Switch2.Apower + res.Pm2.Apower + resem.Em2.ActPower
power = res.Switch2.Apower + res.Pm2.Apower + res.Em2.ActPower
default:
power = res.Switch0.Apower + res.Pm0.Apower + resem.Em0.ActPower
power = res.Switch0.Apower + res.Pm0.Apower + res.Em0.ActPower
}
}
@ -130,25 +123,18 @@ func (sh *Switch) TotalEnergy() (float64, error) {
energy = gen1Energy(d.devicetype, energy)
default:
var resem Gen2EmStatusResponse
var res Gen2StatusResponse
if d.app == "Pro3EM" && d.profile == "monophase" {
if err := d.execGen2Cmd("Shelly.GetStatus", false, &resem); err != nil {
return 0, err
}
} else {
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
energy = res.Switch1.Aenergy.Total + res.Pm1.Aenergy.Total + resem.Em1Data.TotalActEnergy - resem.Em1Data.TotalActRetEnergy
energy = res.Switch1.Aenergy.Total + res.Pm1.Aenergy.Total + res.Em1Data.TotalActEnergy - res.Em1Data.TotalActRetEnergy
case 2:
energy = res.Switch2.Aenergy.Total + res.Pm2.Aenergy.Total + resem.Em2Data.TotalActEnergy - resem.Em2Data.TotalActRetEnergy
energy = res.Switch2.Aenergy.Total + res.Pm2.Aenergy.Total + res.Em2Data.TotalActEnergy - res.Em2Data.TotalActRetEnergy
default:
energy = res.Switch0.Aenergy.Total + res.Pm0.Aenergy.Total + resem.Em0Data.TotalActEnergy - resem.Em0Data.TotalActRetEnergy
energy = res.Switch0.Aenergy.Total + res.Pm0.Aenergy.Total + res.Em0Data.TotalActEnergy - res.Em0Data.TotalActRetEnergy
}
}

View file

@ -33,15 +33,6 @@ type Gen2Switch struct {
}
}
type Gen2StatusResponse struct {
Switch0 Gen2Switch `json:"switch:0"`
Switch1 Gen2Switch `json:"switch:1"`
Switch2 Gen2Switch `json:"switch:2"`
Pm0 Gen2Switch `json:"pm1:0"`
Pm1 Gen2Switch `json:"pm2:1"`
Pm2 Gen2Switch `json:"pm3:2"`
}
type Gen2Em struct {
Current float64 `json:"current"`
Voltage float64 `json:"voltage"`
@ -53,7 +44,14 @@ type Gen2EmData struct {
TotalActRetEnergy float64 `json:"total_act_ret_energy"`
}
type Gen2EmStatusResponse struct {
type Gen2StatusResponse struct {
Switch0 Gen2Switch `json:"switch:0"`
Switch1 Gen2Switch `json:"switch:1"`
Switch2 Gen2Switch `json:"switch:2"`
Pm0 Gen2Switch `json:"pm1:0"`
Pm1 Gen2Switch `json:"pm2:1"`
Pm2 Gen2Switch `json:"pm3:2"`
// additional shelly Pro EM meter JSON response
TotalPower float64 `json:"total_act_power"`
CurrentA float64 `json:"a_current"`
CurrentB float64 `json:"b_current"`

View file

@ -70,7 +70,7 @@ func TestUnmarshalGen2StatusResponse(t *testing.T) {
{
// Shelly Pro 3EM
var res Gen2EmStatusResponse
var res Gen2StatusResponse
jsonstr := `{"ble":{},"bthome":{"errors":["bluetooth_disabled"]},"cloud":{"connected":true},"em1:0":{"id":0,"current":3.705,"voltage":242.8,"act_power":598.9,"aprt_power":900.6,"pf":0.66,"freq":50.0,"calibration":"factory"},"em1:1":{"id":1,"current":0.194,"voltage":242.8,"act_power":0.0,"aprt_power":47.2,"pf":0.00,"freq":50.0,"calibration":"factory"},"em1:2":{"id":2,"current":0.027,"voltage":242.8,"act_power":0.0,"aprt_power":6.6,"pf":0.00,"freq":50.0,"calibration":"factory"},"em1data:0":{"id":0,"total_act_energy":3458.24,"total_act_ret_energy":1605.24},"em1data:1":{"id":1,"total_act_energy":2768.67,"total_act_ret_energy":25.49},"em1data:2":{"id":2,"total_act_energy":3.09,"total_act_ret_energy":0.71},"eth":{"ip":null},"modbus":{},"mqtt":{"connected":false},"sys":{"mac":"FCE8C0DBA850","restart_required":false,"time":"19:46","unixtime":1731404780,"uptime":563,"ram_size":247148,"ram_free":110596,"fs_size":524288,"fs_free":176128,"cfg_rev":21,"kvs_rev":0,"schedule_rev":3,"webhook_rev":1,"available_updates":{},"reset_reason":3},"temperature:0":{"id": 0,"tC":39.0, "tF":102.2},"wifi":{"sta_ip":"192.168.40.174","status":"got ip","ssid":"IoT","rssi":-67},"ws":{"connected":false}}`
require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
@ -93,6 +93,26 @@ func TestUnmarshalGen2StatusResponse(t *testing.T) {
assert.Equal(t, 3.09, res.Em2Data.TotalActEnergy)
assert.Equal(t, 0.71, res.Em2Data.TotalActRetEnergy)
}
{
// Shelly Pro EM-50 channel 0 + 1
var res Gen2StatusResponse
jsonstr := `{"ble":{},"bthome":{"errors":["bluetooth_disabled"]},"cloud":{"connected":true},"em1:0":{"id":0,"current":1.473,"voltage":226.9,"act_power":-332.2,"aprt_power":335.0,"pf":0.99,"freq":50.0,"calibration":"factory"},"em1:1":{"id":1,"current":0.428,"voltage":227.0,"act_power":-38.5,"aprt_power":97.4,"pf":0.38,"freq":50.0,"calibration":"factory"},"em1data:0":{"id":0,"total_act_energy":1264.15,"total_act_ret_energy":144792.28},"em1data:1":{"id":1,"total_act_energy":48002.83,"total_act_ret_energy":33241.59},"eth":{"ip":null},"modbus":{},"mqtt":{"connected":false},"switch:0":{"id":0, "source":"HTTP_in", "output":false,"temperature":{"tC":46.4, "tF":115.5}},"sys":{"mac":"08F9E0E8AF2C","restart_required":false,"time":"10:42","unixtime":1742809323,"uptime":3671372,"ram_size":249680,"ram_free":107492,"fs_size":524288,"fs_free":188416,"cfg_rev":12,"kvs_rev":0,"schedule_rev":1,"webhook_rev":0,"available_updates":{"beta":{"version":"1.5.1-beta2"}},"reset_reason":3},"wifi":{"sta_ip":"192.168.1.120","status":"got ip","ssid":"Spaetzlewerk","rssi":-61},"ws":{"connected":false}}`
require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
// Channel 0 (1)
assert.Equal(t, -332.2, res.Em0.ActPower)
assert.Equal(t, 1.473, res.Em0.Current)
assert.Equal(t, 226.9, res.Em0.Voltage)
assert.Equal(t, 1264.15, res.Em0Data.TotalActEnergy)
assert.Equal(t, 144792.28, res.Em0Data.TotalActRetEnergy)
// Channel 1 (2)
assert.Equal(t, -38.5, res.Em1.ActPower)
assert.Equal(t, 0.428, res.Em1.Current)
assert.Equal(t, 227.0, res.Em1.Voltage)
assert.Equal(t, 48002.83, res.Em1Data.TotalActEnergy)
assert.Equal(t, 33241.59, res.Em1Data.TotalActRetEnergy)
}
}
// Test Shelly device info