Add Aiways (#5810)

Initial support
This commit is contained in:
andig 2023-01-19 21:23:32 +01:00 • committed by GitHub
parent dcaadb783f
commit f38353bf0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 454 additions and 0 deletions

View file

@ -0,0 +1,12 @@
template: aiways
products:
- brand: Aiways
params:
- preset: vehiclebase
- preset: vehicleidentify
- name: vin
required: true
render: |
type: aiways
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}

View file

@ -0,0 +1,28 @@
product:
brand: Aiways
render:
- default: |
type: template
template: aiways
title: # Wird in der Benutzeroberfläche angezeigt # Optional
user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.)
password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen)
vin: W... # Erforderlich, wenn mehrere Fahrzeuge des Herstellers vorhanden sind
capacity: 50 # Akkukapazität in kWh # Optional
advanced: |
type: template
template: aiways
title: # Wird in der Benutzeroberfläche angezeigt # Optional
user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.)
password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen)
vin: W... # Erforderlich, wenn mehrere Fahrzeuge des Herstellers vorhanden sind
capacity: 50 # Akkukapazität in kWh # Optional
phases: 3 # Die maximale Anzahl der Phasen welche genutzt werden können # Optional
icon: car # Darstellung in der UI. Mögliche Werte: bike, bus, car, heater, heatercooler, heatpump, moped, motorcycle, plug, scooter, tractor, van, waterheater # Optional
cache: 15m # Zeitintervall nach dem Daten erneut vom Fahrzeug abgefragt werden # Optional
mode: # Möglich sind Off, Now, MinPV und PV, oder leer wenn keiner definiert werden soll # Optional
minSoc: 25 # Lade sofort mit maximaler Geschwindigkeit bis zu dem angegeben Ladestand, wenn der Lademodus nicht auf 'Aus' steht # Optional
targetSoc: 80 # Bis zu welchem Ladestand (Soc) soll das Fahrzeug geladen werden # Optional
minCurrent: 6 # Definiert die minimale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll # Optional
maxCurrent: 16 # Definiert die maximale Stromstärke pro angeschlossener Phase mit welcher das Fahrzeug geladen werden soll # Optional
identifiers: # Kann meist erst später eingetragen werden, siehe: https://docs.evcc.io/docs/guides/vehicles/#erkennung-des-fahrzeugs-an-der-wallbox # Optional

View file

@ -131,6 +131,7 @@
"Victron"
],
"Vehicles": [
"Aiways",
"Audi",
"BMW",
"Citroën",

67
vehicle/aiways.go Normal file
View file

@ -0,0 +1,67 @@
package vehicle
import (
"strconv"
"time"
"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/aiways"
)
// https://github.com/davidgiga1993/AiwaysAPI
// https://github.com/TA2k/ioBroker.vw-connect
// Aiways is an api.Vehicle implementation for Aiways cars
type Aiways struct {
*embed
*aiways.Provider // provides the api implementations
}
func init() {
registry.Add("aiways", NewAiwaysFromConfig)
}
// NewAiwaysFromConfig creates a new vehicle
func NewAiwaysFromConfig(other map[string]interface{}) (api.Vehicle, error) {
cc := struct {
embed `mapstructure:",squash"`
User, Password, VIN string
Cache time.Duration
Timeout time.Duration
}{
Cache: interval,
Timeout: request.Timeout,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
v := &Aiways{
embed: &cc.embed,
}
log := util.NewLogger("aiways").Redact(cc.User, cc.Password, cc.VIN)
identity := aiways.NewIdentity(log)
user, err := identity.Login(cc.User, cc.Password)
if err != nil {
return nil, err
}
log.Redact(strconv.FormatInt(user, 10))
api := aiways.NewAPI(log, identity)
api.Client.Timeout = cc.Timeout
// _, err := api.Vehicles()
// cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles)
if err == nil {
v.Provider = aiways.NewProvider(api, user, cc.VIN, cc.Cache)
}
return v, err
}

60
vehicle/aiways/api.go Normal file
View file

@ -0,0 +1,60 @@
package aiways
import (
"errors"
"fmt"
"net/http"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
)
// https://github.com/snaptec/openWB/blob/master/modules/soc_aiways/aiways_get_soc.py
const URI = "https://coiapp-api-eu.ai-ways.com:10443"
// API implements the Aiways api
type API struct {
*request.Helper
identity TokenProvider
}
// New creates a new Aiways API
func NewAPI(log *util.Logger, identity TokenProvider) *API {
v := &API{
Helper: request.NewHelper(log),
identity: identity,
}
return v
}
// func (v *API) Vehicles() ([]Vehicle, error) {
// }
func (v *API) Status(user int64, vin string) (StatusResponse, error) {
var res StatusResponse
data2 := struct {
UserId int64 `json:"userId"`
VIN string `json:"vin"`
}{
UserId: user,
VIN: vin,
}
uri := fmt.Sprintf("%s/app/vc/getCondition", URI)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data2), map[string]string{
"Content-Type": request.JSONContent,
"Accept": request.JSONContent,
"Token": v.identity.Token(),
})
if err == nil {
if err = v.DoJSON(req, &res); err == nil && res.Data == nil {
err = errors.New(res.Message)
}
}
return res, err
}

View file

@ -0,0 +1,68 @@
package aiways
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"net/http"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
)
type Identity struct {
*request.Helper
user, hash, token string
}
type TokenProvider interface {
Token() string
}
// NewIdentity creates BMW identity
func NewIdentity(log *util.Logger) *Identity {
v := &Identity{
Helper: request.NewHelper(log),
}
return v
}
func (v *Identity) Login(user, password string) (int64, error) {
hash := md5.New()
hash.Write([]byte(password))
v.user = user
v.hash = hex.EncodeToString(hash.Sum(nil))
var res User
data := struct {
Account string `json:"account"`
Password string `json:"password"`
}{
Account: v.user,
Password: v.hash,
}
uri := fmt.Sprintf("%s/aiways-passport-service/passport/login/password", URI)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
if err = v.DoJSON(req, &res); err == nil && res.Data == nil {
err = errors.New(res.Message)
}
if err == nil {
v.token = res.Data.Token
return res.Data.UserID, nil
}
}
return 0, err
}
func (v *Identity) Token() string {
return v.token
}

View file

@ -0,0 +1,89 @@
package aiways
import (
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
)
// Provider implements the vehicle api
type Provider struct {
statusG func() (StatusResponse, error)
}
// NewProvider creates a vehicle api provider
func NewProvider(api *API, user int64, vin string, cache time.Duration) *Provider {
impl := &Provider{
statusG: provider.Cached(func() (StatusResponse, error) {
return api.Status(user, vin)
}, cache),
}
return impl
}
var _ api.Battery = (*Provider)(nil)
// Soc implements the api.Vehicle interface
func (v *Provider) Soc() (float64, error) {
res, err := v.statusG()
if err == nil {
return float64(res.Data.Vc.Soc), nil
}
return 0, err
}
// var _ api.ChargeState = (*Provider)(nil)
// // Status implements the api.ChargeState interface
// func (v *Provider) Status() (api.ChargeStatus, error) {
// status := api.StatusA // disconnected
// res, err := v.statusG()
// if err == nil {
// if res.Charger.Status.PlugStatusData.PlugState.Content == "connected" {
// status = api.StatusB
// }
// if res.Charger.Status.ChargingStatusData.ChargingState.Content == "charging" {
// status = api.StatusC
// }
// }
// return status, err
// }
var _ api.VehicleRange = (*Provider)(nil)
// Range implements the api.VehicleRange interface
func (v *Provider) Range() (int64, error) {
res, err := v.statusG()
if err == nil {
return int64(res.Data.Vc.DrivingRange), nil
}
return 0, err
}
var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) {
res, err := v.statusG()
if err == nil {
return res.Data.Vc.VehicleMileage, nil
}
return 0, err
}
// var _ api.VehiclePosition = (*Provider)(nil)
// // Position implements the api.VehiclePosition interface
// func (v *Provider) Position() (float64, float64, error) {
// res, err := v.statusG()
// if err == nil {
// return res.Data.Vc.Lat, res.Data.Vc.Lon, nil
// }
// return 0, 0, err
// }

129
vehicle/aiways/types.go Normal file
View file

@ -0,0 +1,129 @@
package aiways
type User struct {
Code int64 `json:"code"`
Message string `json:"message"`
Data *struct {
Email string `json:"email"`
HeadURL string `json:"headUrl"`
IsFirstLogin int64 `json:"isFirstLogin"`
Mobile interface{} `json:"mobile"`
Nickname string `json:"nickname"`
Sex interface{} `json:"sex"`
Token string `json:"token"`
UserID int64 `json:"userId"`
UserType int64 `json:"userType"`
Username string `json:"username"`
} `json:"data"`
Timestamp int64 `json:"timestamp"`
}
type StatusResponse struct {
Code string `json:"code"`
Message string `json:"message"`
Data *struct {
Vc struct {
DataTime string `json:"dataTime"`
DataTimeTS int64 `json:"dataTimeTS"`
// NorthLat string `json:"northLat"`
// EastLon string `json:"eastLon"`
Lat float64 `json:"lat,string"`
Lon float64 `json:"lon,string"`
Address string `json:"address"`
ChargeSts int `json:"chargeSts,string"`
// SocLow string `json:"socLow"`
// LastFlameoutTime string `json:"lastFlameoutTime"`
// VehicleSts string `json:"vehicleSts"`
// LowSpdMuteSts string `json:"lowSpdMuteSts"`
// DoorLockSts string `json:"doorLockSts"`
// TrunkLockSts string `json:"trunkLockSts"`
// DoorStsReserved string `json:"doorStsReserved"`
// DoorOpenStsLf string `json:"doorOpenStsLf"`
// DoorOpenStsRf string `json:"doorOpenStsRf"`
// DoorOpenStsLb string `json:"doorOpenStsLb"`
// DoorOpenStsRb string `json:"doorOpenStsRb"`
// FrontHatchCoverOpenSts string `json:"frontHatchCoverOpenSts"`
// TrunkDoorOpenSts string `json:"trunkDoorOpenSts"`
// ChargingCoverOpenSts string `json:"chargingCoverOpenSts"`
// WindowOpenStsLf string `json:"windowOpenStsLf"`
// WindowOpenStsRf string `json:"windowOpenStsRf"`
// WindowOpenStsLb string `json:"windowOpenStsLb"`
// WindowOpenStsRb string `json:"windowOpenStsRb"`
// LeftTurnLightOpenSts string `json:"leftTurnLightOpenSts"`
// RightTurnLightOpenSts string `json:"rightTurnLightOpenSts"`
// BackFrogLightOpenSts string `json:"backFrogLightOpenSts"`
// HighLightOpenSts string `json:"highLightOpenSts"`
// LowLightOpenSts string `json:"lowLightOpenSts"`
// WidthLightOpenSts string `json:"widthLightOpenSts"`
// BrakeLightOpenSts string `json:"brakeLightOpenSts"`
// ReversingLightOpenSts string `json:"reversingLightOpenSts"`
// DayRunningLightOpenSts string `json:"dayRunningLightOpenSts"`
// AutoLightOpenSts string `json:"autoLightOpenSts"`
// SunroofStsSts string `json:"sunroofStsSts"`
// SunroofHorizontalOpen string `json:"sunroofHorizontalOpen"`
// SunroofSunshadeOpen string `json:"sunroofSunshadeOpen"`
// TirePressureStsLf string `json:"tirePressureStsLf"`
// TirePressureStsRf string `json:"tirePressureStsRf"`
// TirePressureStsLb string `json:"tirePressureStsLb"`
// TirePressureStsRb string `json:"tirePressureStsRb"`
// TirePressureLf string `json:"tirePressureLf"`
// TirePressureRf string `json:"tirePressureRf"`
// TirePressureLb string `json:"tirePressureLb"`
// TirePressureRb string `json:"tirePressureRb"`
// TireTempHighStsLf string `json:"tireTempHighStsLf"`
// TireTempHighStsRf string `json:"tireTempHighStsRf"`
// TireTempHighStsLb string `json:"tireTempHighStsLb"`
// TireTempHighStsRb string `json:"tireTempHighStsRb"`
// TireTempLf string `json:"tireTempLf"`
// TireTempRf string `json:"tireTempRf"`
// TireTempLb string `json:"tireTempLb"`
// TireTempRb string `json:"tireTempRb"`
// AirconAcSts string `json:"airconAcSts"`
// AirconAutoSts string `json:"airconAutoSts"`
// AirconWindMode string `json:"airconWindMode"`
// AirconWindVolume string `json:"airconWindVolume"`
// AirconCycleSts string `json:"airconCycleSts"`
// AirconTempDisLf string `json:"airconTempDisLf"`
// AirconTempDisRf string `json:"airconTempDisRf"`
// AirconSyncSts string `json:"airconSyncSts"`
// AirconRunSts string `json:"airconRunSts"`
// AirconOutsideTemp string `json:"airconOutsideTemp"`
// AirconInsideTemp string `json:"airconInsideTemp"`
// SafetyBeltStsDrv string `json:"safetyBeltStsDrv"`
// SafetyBeltStsPass string `json:"safetyBeltStsPass"`
// SeatBeltStsBl string `json:"seatBeltStsBl"`
// SeatBeltStsBm string `json:"seatBeltStsBm"`
// SeatBeltStsBr string `json:"seatBeltStsBr"`
// SeatHeatStsDrv string `json:"seatHeatStsDrv"`
// SeatHeatStsPass string `json:"seatHeatStsPass"`
// SeatHeatStsSys string `json:"seatHeatStsSys"`
// ChgConnStsDc string `json:"chgConnStsDc"`
// ChgConnStsAc string `json:"chgConnStsAc"`
// KeyLowPowerWarn string `json:"keyLowPowerWarn"`
DrivingRange float64 `json:"drivingRange,string"`
VehicleMileage float64 `json:"vehicleMileage,string"`
// Speed string `json:"speed"`
// CrashSts string `json:"crashSts"`
// ElecModeFlg string `json:"elecModeFlg"`
// PowerMode string `json:"powerMode"`
// SteeringAngle string `json:"steeringAngle"`
// EpbSts string `json:"epbSts"`
Soc int `json:"soc,string"`
// AgpsSts string `json:"agpsSts"`
// BtConnMac string `json:"btConnMac"`
// BtConnUserID string `json:"btConnUserId"`
// WifiConnCount string `json:"wifiConnCount"`
// HasSkylight string `json:"hasSkylight"`
// Iccid string `json:"iccid"`
// DataFlow string `json:"dataFlow"`
// AuthStatus string `json:"authStatus"`
// SenceSeason int `json:"senceSeason"`
// SenceSeasonText string `json:"senceSeasonText"`
// CarSenceSeasonText string `json:"carSenceSeasonText"`
// BmsChgRemTime string `json:"bmsChgRemTime"`
// BmsPreThemalMode string `json:"bmsPreThemalMode"`
// BcmRemoteControlSt string `json:"bcmRemoteControlSt"`
Active bool `json:"active"`
} `json:"vc"`
} `json:"data"`
}