diff --git a/vehicle/audi.go b/vehicle/audi.go index f31ade8dd..571dfec0c 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -9,6 +9,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" + "github.com/evcc-io/evcc/vehicle/audi" "github.com/evcc-io/evcc/vehicle/vw" ) @@ -19,6 +20,7 @@ import ( type Audi struct { *embed *vw.Provider // provides the api implementations + audiProvider *audi.Provider } func init() { @@ -78,5 +80,14 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { } } + audiApi := audi.NewAPI(log, identity, "Audi", "DE") + v.audiProvider = audi.NewProvider(audiApi, cc.VIN, cc.Cache) + return v, err } + +var _ api.VehicleOdometer = (*Audi)(nil) + +func (v *Audi) Odometer() (float64, error) { + return v.audiProvider.Odometer() +} diff --git a/vehicle/audi/api.go b/vehicle/audi/api.go new file mode 100644 index 000000000..c86a44fda --- /dev/null +++ b/vehicle/audi/api.go @@ -0,0 +1,65 @@ +package audi + +import ( + "fmt" + "net/http" + + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" + "golang.org/x/oauth2" +) + +// DefaultBaseURI is the Audi api base URI +const DefaultBaseURI = "https://msg.audi.de/fs-car" + +const StatusOdometer = "0x0101010002" + +// API is the VW api client +type API struct { + *request.Helper + brand, country string + baseURI string +} + +// NewAPI creates a new api client +func NewAPI(log *util.Logger, identity oauth2.TokenSource, brand, country string) *API { + v := &API{ + Helper: request.NewHelper(log), + brand: brand, + country: country, + baseURI: DefaultBaseURI, + } + + v.Client.Transport = &oauth2.Transport{ + Source: identity, + Base: v.Client.Transport, + } + + return v +} + +func (v *API) getJSON(uri string, res interface{}) error { + req, err := request.New(http.MethodGet, uri, nil, map[string]string{ + "Accept": request.JSONContent, + "X-App-Name": "myAudi", + "X-Country-Id": "DE", + "X-Language-Id": "de", + "X-App-Version": "3.22.0", + }) + + if err == nil { + err = v.DoJSON(req, &res) + } + + return err +} + +// Status returns the /vehicles//status response +func (v *API) Status(vin string) (StatusResponse, error) { + var res StatusResponse + + uri := fmt.Sprintf("%s/bs/vsr/v1/Audi/DE/vehicles/%s/status", DefaultBaseURI, vin) + err := v.getJSON(uri, &res) + + return res, err +} diff --git a/vehicle/audi/provider.go b/vehicle/audi/provider.go new file mode 100644 index 000000000..f21692fe6 --- /dev/null +++ b/vehicle/audi/provider.go @@ -0,0 +1,39 @@ +package audi + +import ( + "strconv" + "time" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/provider" +) + +// Provider implements the evcc vehicle api +type Provider struct { + statusG func() (interface{}, error) +} + +// NewProvider provides the evcc vehicle api provider +func NewProvider(api *API, vin string, cache time.Duration) *Provider { + impl := &Provider{ + statusG: provider.NewCached(func() (interface{}, error) { + return api.Status(vin) + }, cache).InterfaceGetter(), + } + return impl +} + +var _ api.VehicleOdometer = (*Provider)(nil) + +// Odometer implements the api.VehicleOdometer interface +func (v *Provider) Odometer() (float64, error) { + res, err := v.statusG() + if res, ok := res.(StatusResponse); err == nil && ok { + fd := res.ServiceByID(StatusOdometer).FieldByID(StatusOdometer) + if fd != nil { + return strconv.ParseFloat(fd.Value, 64) + } + } + + return 0, err +} diff --git a/vehicle/audi/types.go b/vehicle/audi/types.go new file mode 100644 index 000000000..5af000169 --- /dev/null +++ b/vehicle/audi/types.go @@ -0,0 +1,50 @@ +package audi + +type StatusResponse struct { + StoredVehicleDataResponse struct { + VIN string + VehicleData struct { + Data []ServiceDefinition + } + } +} + +func (s *StatusResponse) ServiceByID(id string) *ServiceDefinition { + for _, d := range s.StoredVehicleDataResponse.VehicleData.Data { + if d.ID == id { + return &d + } + } + + return nil +} + +type ServiceDefinition struct { + ID string + Field []FieldDefinition +} + +func (s *ServiceDefinition) FieldByID(id string) *FieldDefinition { + if s == nil { + return nil + } + + for _, f := range s.Field { + if f.ID == id { + return &f + } + } + + return nil +} + +type FieldDefinition struct { + ID string // "0x0101010001", + TsCarSentUtc string // "2021-09-05T07:54:20Z", + TsCarSent string // "2021-09-05T07:54:19", + TsCarCaptured string // "2021-09-05T07:54:19", + TsTssReceivedUtc string // "2021-09-05T07:54:23Z", + MilCarCaptured int // 25009, + MilCarSent int // 25009, + Value string // "echo" +}