Add range and charge status (#842)
This commit is contained in:
parent
0a825267d4
commit
099f37ee4f
3 changed files with 143 additions and 29 deletions
|
|
@ -20,11 +20,10 @@ import (
|
|||
type Niu struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
user, password string
|
||||
serial string
|
||||
tokens niu.Token
|
||||
accessTokenExpiry time.Time
|
||||
chargeStateG func() (float64, error)
|
||||
user, password string
|
||||
serial string
|
||||
token niu.Token
|
||||
apiG func() (interface{}, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -60,7 +59,7 @@ func NewNiuFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
serial: strings.ToUpper(cc.Serial),
|
||||
}
|
||||
|
||||
v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter()
|
||||
v.apiG = provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
@ -86,10 +85,9 @@ func (v *Niu) login() error {
|
|||
})
|
||||
|
||||
if err == nil {
|
||||
var tokens niu.Token
|
||||
if err = v.DoJSON(req, &tokens); err == nil {
|
||||
v.tokens = tokens
|
||||
v.accessTokenExpiry = time.Unix(v.tokens.Data.Token.TokenExpiresIn, 0)
|
||||
var token niu.Token
|
||||
if err = v.DoJSON(req, &token); err == nil {
|
||||
v.token = token
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,32 +103,70 @@ func md5Hash(text string) (string, error) {
|
|||
|
||||
// request implements the Niu web request
|
||||
func (v *Niu) request(uri string) (*http.Request, error) {
|
||||
if v.tokens.Data.Token.AccessToken == "" || v.accessTokenExpiry.Before(time.Now()) {
|
||||
if v.token.AccessToken == "" || time.Until(v.token.Expiry) < time.Minute {
|
||||
if err := v.login(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"token": v.tokens.Data.Token.AccessToken,
|
||||
"token": v.token.AccessToken,
|
||||
})
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
// chargeState implements the api.Vehicle interface
|
||||
func (v *Niu) chargeState() (float64, error) {
|
||||
var resp niu.SoC
|
||||
// batteryAPI provides battery api response
|
||||
func (v *Niu) batteryAPI() (interface{}, error) {
|
||||
var res niu.Response
|
||||
|
||||
req, err := v.request(niu.ApiURI + "/v3/motor_data/index_info?sn=" + v.serial)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &resp)
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return float64(resp.Data.Batteries.CompartmentA.BatteryCharging), err
|
||||
return res, err
|
||||
}
|
||||
|
||||
// SoC implements the api.Vehicle interface
|
||||
func (v *Niu) SoC() (float64, error) {
|
||||
return v.chargeStateG()
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(niu.Response); err == nil && ok {
|
||||
return float64(res.Data.Batteries.CompartmentA.BatteryCharging), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*Niu)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (v *Niu) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusA // disconnected
|
||||
|
||||
res, err := v.apiG()
|
||||
if res, ok := res.(niu.Response); err == nil && ok {
|
||||
if res.Data.IsConnected {
|
||||
status = api.StatusB
|
||||
}
|
||||
if res.Data.IsCharging > 0 {
|
||||
status = api.StatusC
|
||||
}
|
||||
}
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
var _ api.VehicleRange = (*Niu)(nil)
|
||||
|
||||
// Range implements the api.VehicleRange interface
|
||||
func (v *Niu) Range() (int64, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(niu.Response); err == nil && ok {
|
||||
return int64(res.Data.EstimatedMileage), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,57 @@
|
|||
package niu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthURI = "https://account-fk.niu.com"
|
||||
ApiURI = "https://app-api-fk.niu.com"
|
||||
)
|
||||
|
||||
// Token is the Niu oauth2 api response
|
||||
// https://account-fk.niu.com/v3/api/oauth2/token?account=<NiuUser>&app_id=niu_8xt1afu6&grant_type=password&password=<MD5PasswordHash>&scope=base
|
||||
type Token struct {
|
||||
Data struct {
|
||||
Token struct {
|
||||
AccessToken string `json:"access_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
RefreshTokenExpiresIn int64 `json:"refresh_token_expires_in,omitempty"`
|
||||
TokenExpiresIn int64 `json:"token_expires_in,omitempty"`
|
||||
type Token oauth2.Token
|
||||
|
||||
// UnmarshalJSON decodes the token api response
|
||||
func (t *Token) UnmarshalJSON(data []byte) error {
|
||||
var res struct {
|
||||
Data struct {
|
||||
Token struct {
|
||||
oauth2.Token
|
||||
RefreshTokenExpiresIn int64 `json:"refresh_token_expires_in,omitempty"`
|
||||
TokenExpiresIn int64 `json:"token_expires_in,omitempty"`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := json.Unmarshal(data, &res)
|
||||
if err == nil {
|
||||
(*t) = (Token)(res.Data.Token.Token)
|
||||
|
||||
if res.Data.Token.Expiry.IsZero() && res.Data.Token.TokenExpiresIn != 0 {
|
||||
t.Expiry = time.Unix(res.Data.Token.TokenExpiresIn, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SoC is the Niu motor_data api response
|
||||
// Response is the Niu motor_data api response
|
||||
// https://app-api-fk.niu.com/v3/motor_data/index_info?sn=<ScooterSerialNumber>
|
||||
type SoC struct {
|
||||
type Response struct {
|
||||
Data struct {
|
||||
Batteries struct {
|
||||
IsCharging int `json:"isCharging,omitempty"`
|
||||
IsConnected bool `json:"isConnected,omitempty"`
|
||||
Timestamp int64 `json:"time,omitempty"`
|
||||
Batteries struct {
|
||||
CompartmentA struct {
|
||||
BatteryCharging int64 `json:"batteryCharging,omitempty"`
|
||||
} `json:"compartmentA"`
|
||||
}
|
||||
LeftTime string `json:"leftTime,omitempty"`
|
||||
EstimatedMileage int64 `json:"estimatedMileage,omitempty"`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
internal/vehicle/niu/types_test.go
Normal file
52
internal/vehicle/niu/types_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package niu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalNiuToken(t *testing.T) {
|
||||
var tok Token
|
||||
str := `{"data":{"token":{"access_token":"access","refresh_token":"refresh","refresh_token_expires_in":1620139071,"token_expires_in":1620139071},"user":{"user_id":"user","mobile":"","email":"email","country_code":"49","nick_name":"nickname","real_name":"realname","last_name":"lastname","identification_code":"identification_code","birthdate":"","gender":1,"avatar":"","thumb_avatar":"","profession":0,"income":0,"car_owners":0,"purpose":"","sign":"A NIU WAY FORWARD","background":"https://download.niucache.com/static/upload/20200507/57f16705-48e9-4080-8246-b2fde8df30b5.jpg","height":0,"weight":0}},"desc":"ok","status":0}`
|
||||
|
||||
if err := json.Unmarshal([]byte(str), &tok); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if tok.AccessToken != "access" {
|
||||
t.Error("AccessToken")
|
||||
}
|
||||
|
||||
if tok.RefreshToken != "refresh" {
|
||||
t.Error("RefreshToken")
|
||||
}
|
||||
|
||||
if tok.Expiry.IsZero() {
|
||||
t.Error("TokenExpiresIn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalNiuResponse(t *testing.T) {
|
||||
var tok Response
|
||||
str := `{"data":{"isCharging":0,"lockStatus":0,"isAccOn":0,"isFortificationOn":0,"isConnected":true,"postion":{"lat":42.330353,"lng":11.450598},"hdop":1,"time":1617697890173,"batteries":{"compartmentA":{"bmsId":"bmsId","isConnected":true,"batteryCharging":59,"gradeBattery":"94.8"},"compartmentB":{"bmsId":"bmsId","isConnected":true,"batteryCharging":58,"gradeBattery":"95.9"}},"leftTime":"10.2","estimatedMileage":68,"gpsTimestamp":1617697890173,"infoTimestamp":1617697890173,"nowSpeed":0,"shakingValue":3,"locationType":1,"batteryDetail":true,"centreCtrlBattery":100,"ss_protocol_ver":3,"ss_online_sta":"1","gps":5,"gsm":14,"lastTrack":{"ridingTime":90,"distance":571,"time":1617269526360}},"desc":"成功","trace":"成功","status":0}`
|
||||
|
||||
if err := json.Unmarshal([]byte(str), &tok); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if tok.Data.Batteries.CompartmentA.BatteryCharging != 59 {
|
||||
t.Error("BatteryCharging")
|
||||
}
|
||||
|
||||
if tok.Data.EstimatedMileage != 68 {
|
||||
t.Error("EstimatedMileage")
|
||||
}
|
||||
|
||||
if tok.Data.IsCharging != 0 {
|
||||
t.Error("IsCharging")
|
||||
}
|
||||
|
||||
if tok.Data.LeftTime != "10.2" {
|
||||
t.Error("LeftTime")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue