Fritz!: refactor session caching (#29574)

This commit is contained in:
andig 2026-05-02 14:51:08 +02:00 • committed by GitHub
parent ba29520170
commit c96dbed78e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 42 deletions

View file

@ -6,7 +6,6 @@ import (
"net/url"
"strconv"
"strings"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/fritz"
@ -23,8 +22,6 @@ import (
type Connection struct {
*request.Helper
*fritz.Settings
SID string
updated time.Time
}
// NewConnection creates FritzDECT connection
@ -58,19 +55,13 @@ func NewConnection(uri, ain, user, password string) (*Connection, error) {
// ExecCmd execautes an FritzDECT AHA-HTTP-Interface command
func (c *Connection) ExecCmd(function string) (string, error) {
// refresh Fritzbox session id
if time.Since(c.updated) >= fritz.SessionTimeout {
sid, err := c.GetSessionID(c.Helper)
if err != nil {
return "", err
}
// update session timestamp
c.SID = sid
c.updated = time.Now()
sid, err := c.GetSessionID(c.Helper)
if err != nil {
return "", err
}
parameters := url.Values{
"sid": {c.SID},
"sid": {sid},
"ain": {c.AIN},
"switchcmd": {function},
}

View file

@ -21,10 +21,8 @@ import (
type Connection struct {
*request.Helper
*fritz.Settings
SID string
UID string // device UID (AIN with space)
updated time.Time
unitG util.Cacheable[Unit]
UID string // device UID (AIN with space)
unitG util.Cacheable[Unit]
}
// NewConnection creates a new REST API connection
@ -75,7 +73,8 @@ func ainToUID(ain string) string {
// getUnit fetches unit data from REST API
func (c *Connection) getUnit() (Unit, error) {
if err := c.refreshSession(); err != nil {
sid, err := c.GetSessionID(c.Helper)
if err != nil {
return Unit{}, err
}
@ -83,7 +82,7 @@ func (c *Connection) getUnit() (Unit, error) {
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{
"Authorization": "AVM-SID " + c.SID,
"Authorization": "AVM-SID " + sid,
}, request.AcceptJSON)
var unit Unit
@ -101,10 +100,15 @@ func (c *Connection) getUnit() (Unit, error) {
// 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 " + c.SID,
"Authorization": "AVM-SID " + sid,
}, request.AcceptJSON)
if err != nil {
return Unit{}, err
@ -201,7 +205,8 @@ func (c *Connection) SwitchOff() error {
// setSwitch sets the switch state via REST API
func (c *Connection) setSwitch(on bool) error {
if err := c.refreshSession(); err != nil {
sid, err := c.GetSessionID(c.Helper)
if err != nil {
return err
}
@ -219,7 +224,7 @@ func (c *Connection) setSwitch(on bool) error {
}
req, _ := request.New("PUT", uri, request.MarshalJSON(data), map[string]string{
"Authorization": "AVM-SID " + c.SID,
"Authorization": "AVM-SID " + sid,
}, request.JSONEncoding)
var unit Unit
@ -240,19 +245,3 @@ func (c *Connection) setSwitch(on bool) error {
return nil
}
// refreshSession ensures we have a valid session ID
func (c *Connection) refreshSession() error {
// refresh Fritzbox session id
if time.Since(c.updated) >= fritz.SessionTimeout {
sid, err := c.GetSessionID(c.Helper)
if err != nil {
return err
}
// update session timestamp
c.SID = sid
c.updated = time.Now()
}
return nil
}

View file

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"net/url"
"sync"
"time"
"github.com/evcc-io/evcc/util/request"
@ -20,12 +21,24 @@ const SessionTimeout = 15 * time.Minute
type Settings struct {
URI, AIN, User, Password string
Firmware82 bool // use new REST API (FritzOS 8.2+)
mu sync.Mutex
sid string
updated time.Time
}
// Fritzbox helpers (credits to https://github.com/rsdk/ahago)
// getSessionID fetches a session-id based on the username and password in the connection struct
func (s Settings) GetSessionID(c *request.Helper) (string, error) {
// GetSessionID returns a valid Fritzbox session ID, refreshing it when the
// previously fetched session has timed out.
func (s *Settings) GetSessionID(c *request.Helper) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
if time.Since(s.updated) < SessionTimeout {
return s.sid, nil
}
uri := fmt.Sprintf("%s/login_sid.lua", s.URI)
body, err := c.GetBody(uri)
if err != nil {
@ -53,11 +66,17 @@ func (s Settings) GetSessionID(c *request.Helper) (string, error) {
}
}
return v.SID, err
if err != nil {
return "", err
}
s.sid = v.SID
s.updated = time.Now()
return v.SID, nil
}
// createChallengeResponse creates the Fritzbox challenge response string
func (s Settings) createChallengeResponse(challenge string) (string, error) {
func (s *Settings) createChallengeResponse(challenge string) (string, error) {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
utf16le, err := encoder.String(challenge + "-" + s.Password)
if err != nil {