Add Octopus Energy Italy tariff (#31505)

This commit is contained in:
andig 2026-07-07 12:06:08 +02:00 • committed by GitHub
parent 8a771d5fb3
commit de976b7aa2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 360 additions and 67 deletions

View file

@ -7,17 +7,17 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/vehicle/octopusde"
"github.com/evcc-io/evcc/vehicle/octopuskraken"
)
// OctopusDe is an api.Vehicle implementation for the Octopus Energy Germany Kraken API
type OctopusDe struct {
*embed
*octopusde.API
*octopuskraken.API
account string
device string
deviceID string
dataG func() (octopusde.Device, error)
dataG func() (octopuskraken.Device, error)
}
func init() {
@ -47,7 +47,7 @@ func NewOctopusDeFromConfig(other map[string]any) (api.Vehicle, error) {
log := util.NewLogger("octopus-de").Redact(cc.Email, cc.Password)
api, err := octopusde.NewAPI(log, cc.Email, cc.Password)
api, err := octopuskraken.NewAPI(log, octopuskraken.BaseURI, cc.Email, cc.Password)
if err != nil {
return nil, err
}
@ -66,16 +66,16 @@ func NewOctopusDeFromConfig(other map[string]any) (api.Vehicle, error) {
// status fetches the live state of the configured device, resolving the account
// and matching device on first use.
func (v *OctopusDe) status() (octopusde.Device, error) {
func (v *OctopusDe) status() (octopuskraken.Device, error) {
account, err := v.Account(v.account)
if err != nil {
return octopusde.Device{}, err
return octopuskraken.Device{}, err
}
v.account = account
devices, err := v.Devices(account)
if err != nil {
return octopusde.Device{}, err
return octopuskraken.Device{}, err
}
for _, d := range devices {
@ -94,9 +94,9 @@ func (v *OctopusDe) status() (octopusde.Device, error) {
}
if v.device == "" {
return octopusde.Device{}, api.ErrNotAvailable
return octopuskraken.Device{}, api.ErrNotAvailable
}
return octopusde.Device{}, fmt.Errorf("device not found: %s", v.device)
return octopuskraken.Device{}, fmt.Errorf("device not found: %s", v.device)
}
// Soc implements the api.Vehicle interface

View file

@ -1,4 +1,4 @@
package octopusde
package octopuskraken
import (
"context"
@ -6,25 +6,31 @@ import (
"time"
"github.com/evcc-io/evcc/api"
octoDeGql "github.com/evcc-io/evcc/tariff/octopusde/graphql"
krakengql "github.com/evcc-io/evcc/tariff/octopuskraken/graphql"
"github.com/evcc-io/evcc/util"
)
// API is the Octopus Energy Germany Kraken client for vehicle data. It reuses the
// authenticated Kraken GraphQL client from the tariff implementation so the JWT
// token source and auth transport are not duplicated.
// BaseURI re-exports the tariff graphql package's Kraken API root.
const BaseURI = krakengql.BaseURI
// ItBaseURI re-exports the tariff graphql package's Italian Kraken API root.
const ItBaseURI = krakengql.ItBaseURI
// API is the Octopus Energy Kraken client for vehicle data, reusing the
// authenticated Kraken GraphQL client from the tariff implementation.
type API struct {
*octoDeGql.OctopusDeGraphQLClient
*krakengql.Client
}
// NewAPI creates a Kraken API client authenticated via the given credentials.
func NewAPI(log *util.Logger, email, password string) (*API, error) {
// NewAPI creates a Kraken API client for the given Kraken instance (other
// regional Octopus companies run the same platform under their own baseURI).
func NewAPI(log *util.Logger, baseURI, email, password string) (*API, error) {
// the account number is discovered on demand and not needed for the shared client
client, err := octoDeGql.NewClient(log, email, password, "")
client, err := krakengql.NewClient(log, baseURI, email, password, "")
if err != nil {
return nil, err
}
return &API{OctopusDeGraphQLClient: client}, nil
return &API{Client: client}, nil
}
// krakenAccounts lists the accounts accessible to the authenticated user.

View file

@ -1,4 +1,4 @@
package octopusde
package octopuskraken
import (
"testing"

View file

@ -1,4 +1,4 @@
package octopusde
package octopuskraken
import (
"encoding/json"
@ -10,15 +10,23 @@ import (
)
func init() {
mux := http.NewServeMux()
mux.HandleFunc("GET /devices", getDevices)
register("octopus-de", BaseURI)
register("octopus-it", ItBaseURI)
}
service.Register("octopus-de", mux)
// register wires the device-listing endpoint for a Kraken instance under the given service name.
func register(name, baseURI string) {
mux := http.NewServeMux()
mux.HandleFunc("GET /devices", func(w http.ResponseWriter, req *http.Request) {
getDevices(w, req, name, baseURI)
})
service.Register(name, mux)
}
// getDevices lists the ids of the SmartFlex devices in the account, driving
// device selection in the template.
func getDevices(w http.ResponseWriter, req *http.Request) {
func getDevices(w http.ResponseWriter, req *http.Request, name, baseURI string) {
w.Header().Set("Content-Type", "application/json")
q := req.URL.Query()
@ -31,8 +39,8 @@ func getDevices(w http.ResponseWriter, req *http.Request) {
return
}
log := util.NewLogger("octopus-de").Redact(email, password)
api, err := NewAPI(log, email, password)
log := util.NewLogger(name).Redact(email, password)
api, err := NewAPI(log, baseURI, email, password)
if err != nil {
log.ERROR.Println(err)
return