Fritz!: optional unit parameter for multi-unit smarthome devices (BC) (#29570)
This commit is contained in:
parent
c96dbed78e
commit
1fbe26a6ca
8 changed files with 80 additions and 59 deletions
|
|
@ -38,17 +38,17 @@ func NewFritzDECTFromConfig(other map[string]any) (api.Charger, error) {
|
|||
return nil, api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
return NewFritzDECT(cc.embed, cc.URI, cc.AIN, cc.User, cc.Password, cc.StandbyPower, cc.Firmware82)
|
||||
return NewFritzDECT(cc.embed, cc.URI, cc.AIN, cc.User, cc.Password, cc.StandbyPower, cc.Firmware82, cc.Unit)
|
||||
}
|
||||
|
||||
// NewFritzDECT creates a new connection with standbypower for charger
|
||||
func NewFritzDECT(embed embed, uri, ain, user, password string, standbypower float64, firmware82 bool) (*FritzDECT, error) {
|
||||
func NewFritzDECT(embed embed, uri, ain, user, password string, standbypower float64, firmware82 bool, unit int) (*FritzDECT, error) {
|
||||
var conn fritz.Switch
|
||||
var err error
|
||||
|
||||
// Use new REST API if firmware82 is set, otherwise use legacy LUA API
|
||||
if firmware82 {
|
||||
conn, err = smarthome.NewConnection(uri, ain, user, password)
|
||||
conn, err = smarthome.NewConnection(uri, ain, user, password, unit)
|
||||
} else {
|
||||
conn, err = aha.NewConnection(uri, ain, user, password)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ import (
|
|||
type Connection struct {
|
||||
*request.Helper
|
||||
*fritz.Settings
|
||||
UID string // device UID (AIN with space)
|
||||
UID string // unitUid resolved from /devices
|
||||
unitG util.Cacheable[Unit]
|
||||
}
|
||||
|
||||
// NewConnection creates a new REST API connection
|
||||
func NewConnection(uri, ain, user, password string) (*Connection, error) {
|
||||
func NewConnection(uri, ain, user, password string, unit int) (*Connection, error) {
|
||||
if uri == "" {
|
||||
uri = "https://fritz.box"
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ func NewConnection(uri, ain, user, password string) (*Connection, error) {
|
|||
AIN: ain,
|
||||
User: user,
|
||||
Password: password,
|
||||
Unit: unit,
|
||||
}
|
||||
|
||||
log := util.NewLogger("fritzsmarthome").Redact(password)
|
||||
|
|
@ -47,11 +48,16 @@ func NewConnection(uri, ain, user, password string) (*Connection, error) {
|
|||
conn := &Connection{
|
||||
Helper: request.NewHelper(log),
|
||||
Settings: settings,
|
||||
UID: ainToUID(ain),
|
||||
}
|
||||
|
||||
conn.Client.Transport = request.NewTripper(log, transport.Insecure())
|
||||
|
||||
uid, err := conn.resolveUnitUID(unit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.UID = uid
|
||||
|
||||
// cache unit data for 2 seconds to avoid excessive API calls
|
||||
conn.unitG = util.ResettableCached(func() (Unit, error) {
|
||||
return conn.getUnit()
|
||||
|
|
@ -60,15 +66,35 @@ func NewConnection(uri, ain, user, password string) (*Connection, error) {
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
// ainToUID converts AIN format to UID format by adding space
|
||||
// AIN: "116300015376" -> UID: "11630 0015376"
|
||||
func ainToUID(ain string) string {
|
||||
// Remove any existing spaces first
|
||||
ain = strings.ReplaceAll(ain, " ", "")
|
||||
if len(ain) >= 5 {
|
||||
return ain[:5] + " " + ain[5:]
|
||||
// resolveUnitUID looks up the device by AIN and returns its unitUid at the given index
|
||||
func (c *Connection) resolveUnitUID(unit int) (string, error) {
|
||||
sid, err := c.GetSessionID(c.Helper)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ain
|
||||
|
||||
uri := fmt.Sprintf("%s/api/v0/smarthome/overview/devices", c.URI)
|
||||
|
||||
req, _ := request.New("GET", uri, nil, map[string]string{
|
||||
"Authorization": "AVM-SID " + sid,
|
||||
}, request.AcceptJSON)
|
||||
|
||||
var devices []Device
|
||||
if err := c.DoJSON(req, &devices); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, d := range devices {
|
||||
if d.AIN != c.AIN {
|
||||
continue
|
||||
}
|
||||
if len(d.UnitUids) < unit+1 {
|
||||
return "", fmt.Errorf("invalid unit %d, got %v", unit, d.UnitUids)
|
||||
}
|
||||
return d.UnitUids[unit], nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("ain not found: %s", c.AIN)
|
||||
}
|
||||
|
||||
// getUnit fetches unit data from REST API
|
||||
|
|
@ -78,7 +104,6 @@ func (c *Connection) getUnit() (Unit, error) {
|
|||
return Unit{}, err
|
||||
}
|
||||
|
||||
// Try to get the specific unit first
|
||||
uri := fmt.Sprintf("%s/api/v0/smarthome/overview/units/%s", c.URI, url.PathEscape(c.UID))
|
||||
|
||||
req, _ := request.New("GET", uri, nil, map[string]string{
|
||||
|
|
@ -87,8 +112,7 @@ func (c *Connection) getUnit() (Unit, error) {
|
|||
|
||||
var unit Unit
|
||||
if err := c.DoJSON(req, &unit); err != nil {
|
||||
// Fall back to getting all units and finding ours
|
||||
return c.findUnit()
|
||||
return Unit{}, err
|
||||
}
|
||||
|
||||
if !unit.IsConnected {
|
||||
|
|
@ -98,41 +122,6 @@ func (c *Connection) getUnit() (Unit, error) {
|
|||
return unit, nil
|
||||
}
|
||||
|
||||
// findUnit searches for our unit in the list of all units
|
||||
func (c *Connection) findUnit() (Unit, error) {
|
||||
sid, err := c.GetSessionID(c.Helper)
|
||||
if err != nil {
|
||||
return Unit{}, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/api/v0/smarthome/overview/units", c.URI)
|
||||
|
||||
req, err := request.New("GET", uri, nil, map[string]string{
|
||||
"Authorization": "AVM-SID " + sid,
|
||||
}, request.AcceptJSON)
|
||||
if err != nil {
|
||||
return Unit{}, err
|
||||
}
|
||||
|
||||
var units []Unit
|
||||
if err := c.DoJSON(req, &units); err != nil {
|
||||
return Unit{}, err
|
||||
}
|
||||
|
||||
// Search for matching unit by UID or AIN
|
||||
for _, unit := range units {
|
||||
unitAIN := strings.ReplaceAll(unit.UID, " ", "")
|
||||
if unit.UID == c.UID || unitAIN == c.AIN {
|
||||
if !unit.IsConnected {
|
||||
return unit, api.ErrNotAvailable
|
||||
}
|
||||
return unit, nil
|
||||
}
|
||||
}
|
||||
|
||||
return Unit{}, fmt.Errorf("unit not found: %s", c.AIN)
|
||||
}
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
func (c *Connection) CurrentPower() (float64, error) {
|
||||
unit, err := c.unitG.Get()
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package smarthome
|
|||
|
||||
// Device represents a smarthome device from the /devices endpoint
|
||||
type Device struct {
|
||||
UID string `json:"UID"`
|
||||
AIN string `json:"ain"`
|
||||
Name string `json:"name"`
|
||||
ProductName string `json:"productName"`
|
||||
ProductCategory string `json:"productCategory"`
|
||||
IsConnected bool `json:"isConnected"`
|
||||
UID string `json:"UID"`
|
||||
AIN string `json:"ain"`
|
||||
Name string `json:"name"`
|
||||
ProductName string `json:"productName"`
|
||||
ProductCategory string `json:"productCategory"`
|
||||
IsConnected bool `json:"isConnected"`
|
||||
UnitUids []string `json:"unitUids"`
|
||||
}
|
||||
|
||||
// Unit represents a smarthome unit with its interfaces
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const SessionTimeout = 15 * time.Minute
|
|||
type Settings struct {
|
||||
URI, AIN, User, Password string
|
||||
Firmware82 bool // use new REST API (FritzOS 8.2+)
|
||||
Unit int // unit index for multi-unit devices (REST API only)
|
||||
|
||||
mu sync.Mutex
|
||||
sid string
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func NewFritzDECTFromConfig(other map[string]any) (api.Meter, error) {
|
|||
|
||||
// Use new REST API if firmware82 is set, otherwise use legacy LUA API
|
||||
if cc.Firmware82 {
|
||||
return smarthome.NewConnection(cc.URI, cc.AIN, cc.User, cc.Password)
|
||||
return smarthome.NewConnection(cc.URI, cc.AIN, cc.User, cc.Password, cc.Unit)
|
||||
}
|
||||
|
||||
return aha.NewConnection(cc.URI, cc.AIN, cc.User, cc.Password)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,15 @@ params:
|
|||
help:
|
||||
de: Verwende die neue REST-API für FritzOS ab Version 8.2
|
||||
en: Use the new REST API for FritzOS version 8.2 and later
|
||||
- name: unit
|
||||
advanced: true
|
||||
type: int
|
||||
description:
|
||||
de: Einheit
|
||||
en: Unit
|
||||
help:
|
||||
de: Index der Einheit für Geräte mit mehreren Einheiten (nur REST-API)
|
||||
en: Unit index for multi-unit devices (REST API only)
|
||||
- preset: switchsocket
|
||||
render: |
|
||||
type: fritzdect
|
||||
|
|
@ -43,4 +52,5 @@ render: |
|
|||
password: {{ .password }}
|
||||
ain: {{ .ain }} # switch actor identification number without blanks (see AIN number on switch sticker)
|
||||
firmware82: {{ .firmware82 }}
|
||||
unit: {{ .unit }}
|
||||
{{ include "switchsocket" . }}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@ params:
|
|||
help:
|
||||
de: Verwende die neue REST-API für FritzOS ab Version 8.2
|
||||
en: Use the new REST API for FritzOS version 8.2 and later
|
||||
- name: unit
|
||||
advanced: true
|
||||
type: int
|
||||
description:
|
||||
de: Einheit
|
||||
en: Unit
|
||||
help:
|
||||
de: Index der Einheit für Geräte mit mehreren Einheiten (nur REST-API)
|
||||
en: Unit index for multi-unit devices (REST API only)
|
||||
render: |
|
||||
type: fritzdect
|
||||
uri: {{ .uri }}
|
||||
|
|
@ -44,3 +53,4 @@ render: |
|
|||
password: {{ .password }}
|
||||
ain: {{ .ain }} # switch actor identification number without blanks (see AIN number on switch sticker)
|
||||
firmware82: {{ .firmware82 }}
|
||||
unit: {{ .unit }}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@ params:
|
|||
- name: ain
|
||||
required: true
|
||||
service: fritz/devices?uri={uri}&user={user}&password={password}
|
||||
- name: unit
|
||||
advanced: true
|
||||
type: int
|
||||
description:
|
||||
de: Einheit
|
||||
en: Unit
|
||||
help:
|
||||
de: Index der Einheit für Geräte mit mehreren Einheiten
|
||||
en: Unit index for multi-unit devices
|
||||
render: |
|
||||
type: fritzdect
|
||||
uri: {{ .uri }}
|
||||
|
|
@ -22,3 +31,4 @@ render: |
|
|||
password: {{ .password }}
|
||||
ain: {{ .ain }} # switch actor identification number without blanks (see AIN number on switch sticker)
|
||||
firmware82: true
|
||||
unit: {{ .unit }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue