Shelly: use methods instead of models (#20802)

This commit is contained in:
andig 2025-04-23 14:58:01 +02:00 • committed by GitHub
parent 3c131e4783
commit bacf49fd12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 71 deletions

View file

@ -53,11 +53,6 @@ func NewConnection(uri, user, password string, channel int, cache time.Duration)
return nil, fmt.Errorf("%s (%s) missing user/password", resp.Model, resp.Mac)
}
// Set default profile to "monophase" if not provided
if resp.Profile == "" {
resp.Profile = "monophase"
}
model := strings.Split(resp.Type+resp.Model, "-")[0]
client.Transport = request.NewTripper(log, transport.Insecure())
@ -73,7 +68,12 @@ func NewConnection(uri, user, password string, channel int, cache time.Duration)
} else {
// Shelly GEN 2+ API
// https://shelly-api-docs.shelly.cloud/gen2/
gen = newGen2(client, uri, model, resp.Profile, channel, user, password, cache)
var err error
gen, err = newGen2(client, uri, model, channel, user, password, cache)
if err != nil {
return nil, err
}
}
conn := &Connection{gen}

View file

@ -21,6 +21,10 @@ type Gen2RpcPost struct {
Method string `json:"method"`
}
type Gen2Methods struct {
Methods []string
}
type Gen2SwitchStatus struct {
Output bool
Apower float64
@ -70,7 +74,7 @@ type gen2 struct {
uri string
channel int
model string
profile string
methods []string
switchstatus util.Cacheable[Gen2SwitchStatus]
em1status func() (Gen2EM1Status, error)
em1data func() (Gen2EM1Data, error)
@ -89,18 +93,18 @@ func apiCall[T any](c *gen2, api string) func() (T, error) {
}
// gen2InitApi initializes the connection to the shelly gen2+ api and sets up the cached gen2SwitchStatus, gen2EM1Status and gen2EMStatus
func newGen2(helper *request.Helper, uri, model, profile string, channel int, user, password string, cache time.Duration) *gen2 {
c := &gen2{
Helper: helper,
uri: uri,
channel: channel,
model: model,
profile: profile,
}
func newGen2(helper *request.Helper, uri, model string, channel int, user, password string, cache time.Duration) (*gen2, error) {
// Shelly GEN 2+ API
// https://shelly-api-docs.shelly.cloud/gen2/
c.uri = fmt.Sprintf("%s/rpc", util.DefaultScheme(uri, "http"))
c := &gen2{
Helper: helper,
uri: fmt.Sprintf("%s/rpc", util.DefaultScheme(uri, "http")),
channel: channel,
model: model,
}
// Shelly gen 2 rfc7616 authentication
// https://shelly-api-docs.shelly.cloud/gen2/General/Authentication
if user != "" {
c.Client.Transport = digest.NewTransport(user, password, c.Client.Transport)
}
@ -111,15 +115,18 @@ func newGen2(helper *request.Helper, uri, model, profile string, channel int, us
c.emstatus = util.Cached(apiCall[Gen2EMStatus](c, "EM.GetStatus"), cache)
c.emdata = util.Cached(apiCall[Gen2EMData](c, "EMData.GetStatus"), cache)
return c
var res Gen2Methods
if err := c.execCmd("Shelly.ListMethods", false, &res); err != nil {
return nil, err
}
c.methods = res.Methods
return c, nil
}
// execCmd executes a shelly api gen2+ command and provides the response
func (c *gen2) execCmd(method string, enable bool, res any) error {
// Shelly gen 2 rfc7616 authentication
// https://shelly-api-docs.shelly.cloud/gen2/Overview/CommonDeviceTraits#authentication
// https://datatracker.ietf.org/doc/html/rfc7616
data := &Gen2RpcPost{
Id: c.channel,
On: enable,
@ -138,18 +145,15 @@ func (c *gen2) execCmd(method string, enable bool, res any) error {
// CurrentPower implements the api.Meter interface
func (c *gen2) CurrentPower() (float64, error) {
switch {
// Endpoint Switch.GetStatus
case c.hasSwitchEndpoint():
res, err := c.switchstatus.Get()
return res.Apower, err
// Endpoint EM1.GetStatus
case c.hasEMEndpoint() && c.profile == "monophase":
case c.hasEM1Endpoint():
res, err := c.em1status()
return res.ActPower, err
// Endpoint EM.GetStatus
case c.hasEMEndpoint() && c.profile == "triphase":
case c.hasEMEndpoint():
res, err := c.emstatus()
return res.TotalActPower, err
@ -182,11 +186,11 @@ func (c *gen2) TotalEnergy() (float64, error) {
res, err := c.switchstatus.Get()
return res.Aenergy.Total / 1000, err
case c.hasEMEndpoint() && c.profile == "monophase":
case c.hasEM1Endpoint():
res, err := c.em1data()
return res.TotalActEnergy / 1000, err
case c.hasEMEndpoint() && c.profile == "triphase":
case c.hasEMEndpoint():
res, err := c.emdata()
return res.TotalAct / 1000, err
@ -202,11 +206,11 @@ func (c *gen2) Currents() (float64, float64, float64, error) {
res, err := c.switchstatus.Get()
return res.Current, 0, 0, err
case c.hasEMEndpoint() && c.profile == "monophase":
case c.hasEM1Endpoint():
res, err := c.em1status()
return res.Current, 0, 0, err
case c.hasEMEndpoint() && c.profile == "triphase":
case c.hasEMEndpoint():
res, err := c.emstatus()
return res.ACurrent, res.BCurrent, res.CCurrent, err
@ -222,11 +226,11 @@ func (c *gen2) Voltages() (float64, float64, float64, error) {
res, err := c.switchstatus.Get()
return res.Voltage, 0, 0, err
case c.hasEMEndpoint() && c.profile == "monophase":
case c.hasEM1Endpoint():
res, err := c.em1status()
return res.Voltage, 0, 0, err
case c.hasEMEndpoint() && c.profile == "triphase":
case c.hasEMEndpoint():
res, err := c.emstatus()
return res.AVoltage, res.BVoltage, res.CVoltage, err
@ -242,11 +246,11 @@ func (c *gen2) Powers() (float64, float64, float64, error) {
res, err := c.switchstatus.Get()
return res.Apower, 0, 0, err
case c.hasEMEndpoint() && c.profile == "monophase":
case c.hasEM1Endpoint():
res, err := c.em1status()
return res.ActPower, 0, 0, err
case c.hasEMEndpoint() && c.profile == "triphase":
case c.hasEMEndpoint():
res, err := c.emstatus()
return res.AActPower, res.BActPower, res.CActPower, err
@ -257,45 +261,20 @@ func (c *gen2) Powers() (float64, float64, float64, error) {
// Gen2+ models using Switch.GetStatus endpoint https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Switch#switchgetstatus-example
func (c *gen2) hasSwitchEndpoint() bool {
// Generation 2 Devices (Plus Series):
// - SNSW-001X16EU: Shelly Plus 1 with 1x relay
// - SNSW-001P16EU: Shelly Plus 1PM with 1x relay + power meter
// - SNSW-002P16EU: Shelly Plus 2PM with 2x relay + power meter
// - SNPL-00112EU: Shelly Plus Plug S (EU)
// - SNPL-00110IT: Shelly Plus Plug S (Italy)
// - SNPL-00112UK: Shelly Plus Plug S (UK)
// - SNPL-00116US: Shelly Plus Plug S (US)
// Generation 2 Devices (Pro Series - Hutschiene):
// - SPSW-001XE16EU: Shelly Pro 1 with 1x relay
// - SPSW-001PE16EU: Shelly Pro 1 PM with 1x relay + power meter
// - SPSW-002XE16EU: Shelly Pro 2 with 2x relay
// - SPSW-002PE16EU: Shelly Pro 2 PM with 2x relay + power meter
// - SPSW-003XE16EU: Shelly Pro 3 with 3x relay
// - SPSW-004PE16EU: Shelly Pro 4 PM with 4x relay + power meter
// Generation 3 Devices:
// - S3SW-001P16EU: Shelly 1PM Gen3 with 1x relay + power meter
// Generation 4 Devices:
// - S4SW-001X8EU: Shelly 1 Mini Gen4 with 1x relay
// - S4SW-001P8EU: Shelly 1PM Mini Gen4 with 1x relay + power meter
// - S4SW-001P16EU: Shelly 1PM Gen4 with 1x relay + power meter
switchModels := []string{"SNSW", "SNPL", "SPSW", "S3SW", "S4SW"}
return slices.Contains(switchModels, c.model)
return c.hasMethod("Switch.GetStatus")
}
func (c *gen2) hasEM1Endpoint() bool {
return c.hasMethod("EM1.GetStatus")
}
func (c *gen2) hasEMEndpoint() bool {
return c.hasMethod("EM.GetStatus")
}
// Gen2+ models using EM1.GetStatus endpoint for power and EM1Data.GetStatus for energy
// https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1#em1getstatus-example
// https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1Data#em1datagetstatus-example
func (c *gen2) hasEMEndpoint() bool {
// Generation 2 Devices (Pro Series - Hutschiene):
// - SPEM-002CEBEU50: Shelly Pro EM 50 with 1x relay + 2x energy meter
// - SPEM-003CEBEU120: Shelly Pro 3EM with 3x energy meter
// - SPEM-003CEBEU63: Shelly Pro 3EM-3CT63 with 3x energy meter
// Generation 3 Devices:
// - S3EM-002CXCEU: Shelly EM Gen 3 with 1x relay + 2x energy meter
// - S3EM-001XCEU: Shelly 3EM Gen 3 with 3x energy meter
// - S3EM-003CXCEU63: Shelly 3EM Gen 3 with 3x energy meter
// Generation 4 Devices:
// - S4EM-001PXCEU16: Shelly EM Mini Gen4 with power meter
em1Models := []string{"SPEM", "S3EM", "S4EM"}
return slices.Contains(em1Models, c.model)
func (c *gen2) hasMethod(method string) bool {
return slices.Contains(c.methods, method)
}