Add Zero Motorcycles (#14823)
This commit is contained in:
parent
b0ea0582e2
commit
dc9f02b676
5 changed files with 288 additions and 0 deletions
10
templates/definition/vehicle/zero.yaml
Normal file
10
templates/definition/vehicle/zero.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
template: zero
|
||||
products:
|
||||
- brand: Zero Motorcycles
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- preset: vehicle-identify
|
||||
render: |
|
||||
type: zero
|
||||
{{ include "vehicle-base" . }}
|
||||
{{ include "vehicle-identify" . }}
|
||||
71
vehicle/zero/api.go
Normal file
71
vehicle/zero/api.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package zero
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
)
|
||||
|
||||
const BaseUrl = "https://mongol.brono.com/mongol/api.php"
|
||||
|
||||
// See https://www.electricmotorcycleforum.com/boards/index.php?topic=9520.0
|
||||
// API is quite simple
|
||||
// 1 Acquire unit id(s) by calling
|
||||
// https://mongol.brono.com/mongol/api.php?commandname=get_units&format=json&user=yourusername&pass=yourpass
|
||||
// 2 Query last dataset
|
||||
// https://mongol.brono.com/mongol/api.php?commandname=get_last_transmit&format=json&user=yourusername&pass=yourpass&unitnumber=0000000
|
||||
|
||||
// API is an api.Vehicle implementation for Zero Motorcycles
|
||||
type API struct {
|
||||
*request.Helper
|
||||
user string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewAPI creates a new vehicle
|
||||
func NewAPI(log *util.Logger, user, password string) (*API, error) {
|
||||
var err error
|
||||
v := &API{
|
||||
Helper: request.NewHelper(log),
|
||||
user: user,
|
||||
password: password,
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (v *API) Vehicles() ([]Unit, error) {
|
||||
var res []Unit
|
||||
|
||||
params := url.Values{
|
||||
"user": {v.user},
|
||||
"pass": {v.password},
|
||||
"commandname": {"get_units"},
|
||||
"format": {"json"},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s?%s", BaseUrl, params.Encode())
|
||||
err := v.GetJSON(uri, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Status implements the /user/vehicles/<vin>/status api
|
||||
func (v *API) Status(unitId string) (State, error) {
|
||||
var res []State
|
||||
|
||||
params := url.Values{
|
||||
"user": {v.user},
|
||||
"pass": {v.password},
|
||||
"unitnumber": {unitId},
|
||||
"commandname": {"get_last_transmit"},
|
||||
"format": {"json"},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s?%s", BaseUrl, params.Encode())
|
||||
if err := v.GetJSON(uri, &res); err != nil {
|
||||
return State{}, err
|
||||
}
|
||||
|
||||
return res[0], nil
|
||||
}
|
||||
85
vehicle/zero/provider.go
Normal file
85
vehicle/zero/provider.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package zero
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/provider"
|
||||
)
|
||||
|
||||
// Provider implements the vehicle api
|
||||
type Provider struct {
|
||||
status provider.Cacheable[State]
|
||||
}
|
||||
|
||||
// NewProvider creates a vehicle api provider
|
||||
func NewProvider(api *API, unitId string, cache time.Duration) *Provider {
|
||||
impl := &Provider{
|
||||
status: provider.ResettableCached(func() (State, error) {
|
||||
return api.Status(unitId)
|
||||
}, cache),
|
||||
}
|
||||
|
||||
return impl
|
||||
}
|
||||
|
||||
var _ api.Battery = (*Provider)(nil)
|
||||
|
||||
// Soc implements the api.Vehicle interface
|
||||
func (v *Provider) Soc() (float64, error) {
|
||||
res, err := v.status.Get()
|
||||
return float64(res.Soc), err
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*Provider)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (v *Provider) Status() (api.ChargeStatus, error) {
|
||||
res, err := v.status.Get()
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
status := api.StatusA
|
||||
if res.Pluggedin != 0 {
|
||||
status = api.StatusB
|
||||
}
|
||||
if res.Charging != 0 {
|
||||
status = api.StatusC
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
var _ api.VehicleFinishTimer = (*Provider)(nil)
|
||||
|
||||
// FinishTime implements the api.VehicleFinishTimer interface
|
||||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
res, err := v.status.Get()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
t, err := time.Parse("20060102150405", res.DatetimeActual)
|
||||
if err != nil {
|
||||
t = time.Now()
|
||||
}
|
||||
|
||||
return t.Add(time.Duration(res.Chargingtimeleft) * time.Minute), nil
|
||||
}
|
||||
|
||||
var _ api.VehicleOdometer = (*Provider)(nil)
|
||||
|
||||
// Odometer implements the api.VehicleOdometer interface
|
||||
func (v *Provider) Odometer() (float64, error) {
|
||||
res, err := v.status.Get()
|
||||
return res.Mileage, err
|
||||
}
|
||||
|
||||
var _ api.VehiclePosition = (*Provider)(nil)
|
||||
|
||||
// Position implements the api.VehiclePosition interface
|
||||
func (v *Provider) Position() (float64, float64, error) {
|
||||
res, err := v.status.Get()
|
||||
return res.Latitude, res.Longitude, err
|
||||
}
|
||||
59
vehicle/zero/types.go
Normal file
59
vehicle/zero/types.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package zero
|
||||
|
||||
type Unit struct {
|
||||
UnitNumber string //"123456",
|
||||
Name string
|
||||
}
|
||||
|
||||
type ErrorAnswer struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Unitnumber string //"123456",
|
||||
Name string //"538ZFAZ76LCK00000",
|
||||
Unittype string //"5",
|
||||
Unitmodel string //"6",
|
||||
Mileage float64 `json:",string"` //"4382.46",
|
||||
Software_version string //"190430",
|
||||
Logic_state string //"2"
|
||||
Reason string //"2",
|
||||
Response string //"0",
|
||||
Driver string //"0",
|
||||
Latitude float64 // 51.5000,
|
||||
Longitude float64 // 4.5000,
|
||||
Altitude string //:"0",
|
||||
Gps_valid string //:"0",
|
||||
Gps_connected string //:"1",
|
||||
Satellites string //"0",
|
||||
Velocity string //"1",
|
||||
Heading string //"344",
|
||||
Emergency string //:"0",
|
||||
Shock string //:"",
|
||||
Ignition string //:"0",
|
||||
Door string //:"0",
|
||||
Hood string //:"0",
|
||||
Volume string //:"0",
|
||||
Water_temp string //:"",
|
||||
Oil_pressure string //:"0",
|
||||
Main_voltage float64 //:13.08,
|
||||
Analog1 float64 //":"0.09",
|
||||
Analog2 float64 //":"0.09",
|
||||
Analog3 float64 //":"0.09",
|
||||
Siren string //:"0",
|
||||
Lock string //:"0",
|
||||
Int_lights string //:"0",
|
||||
DatetimeUtc string `json:"datetime_utc"` //:"20191030162309",
|
||||
DatetimeActual string `json:"datetime_actual"` //:"20191102113548"
|
||||
Address string //:"YourCity, YourStreet",
|
||||
Perimeter string //:"",
|
||||
Color int //:2,
|
||||
Soc int //:91,
|
||||
Tipover int //:0,
|
||||
Charging int //:1,
|
||||
Chargecomplete int // 0,
|
||||
Pluggedin int //:1,
|
||||
Chargingtimeleft int //:0
|
||||
Storage int
|
||||
Battery int
|
||||
}
|
||||
63
vehicle/zeromotorcycles.go
Normal file
63
vehicle/zeromotorcycles.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/vehicle/zero"
|
||||
)
|
||||
|
||||
// Zero is an api.Vehicle implementation for probably all ZERO Motorcycles
|
||||
type ZeroMotorcycle struct {
|
||||
*embed
|
||||
*zero.Provider // provides the api implementations
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("zero", NewZeroFromConfig)
|
||||
}
|
||||
|
||||
// NewZeroFromConfig creates a new vehicle
|
||||
func NewZeroFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
var res *zero.API
|
||||
var err error
|
||||
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
User, Password, VIN string
|
||||
Cache time.Duration
|
||||
}{
|
||||
Cache: interval,
|
||||
}
|
||||
|
||||
if err = util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cc.User == "" || cc.Password == "" {
|
||||
return nil, api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
log := util.NewLogger("Zero").Redact(cc.User, cc.Password)
|
||||
|
||||
if res, err = zero.NewAPI(log, cc.User, cc.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vehicle, err := ensureVehicleEx(cc.VIN, res.Vehicles,
|
||||
func(v zero.Unit) (string, error) {
|
||||
return v.Name, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &ZeroMotorcycle{
|
||||
embed: &cc.embed,
|
||||
Provider: zero.NewProvider(res, vehicle.UnitNumber, cc.Cache),
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue