Shelly: use methods instead of models (#20802)
This commit is contained in:
parent
3c131e4783
commit
bacf49fd12
2 changed files with 50 additions and 71 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue