diff --git a/meter/fritz/aha/aha.go b/meter/fritz/aha/aha.go index e98542f47..8341585b4 100644 --- a/meter/fritz/aha/aha.go +++ b/meter/fritz/aha/aha.go @@ -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}, } diff --git a/meter/fritz/smarthome/smarthome.go b/meter/fritz/smarthome/smarthome.go index 624af3daf..e65647192 100644 --- a/meter/fritz/smarthome/smarthome.go +++ b/meter/fritz/smarthome/smarthome.go @@ -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 -} diff --git a/meter/fritz/types.go b/meter/fritz/types.go index 7b5599517..219970fde 100644 --- a/meter/fritz/types.go +++ b/meter/fritz/types.go @@ -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 {