NRG: fix connect json encoding (#5899)
This commit is contained in:
parent
72121dbcfe
commit
a0be402251
5 changed files with 181 additions and 121 deletions
|
|
@ -1,4 +1,4 @@
|
|||
package nrgble
|
||||
package ble
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package nrgble
|
||||
package ble
|
||||
|
||||
const (
|
||||
InfoService = "8f75bba0-c903-11e4-9fe8-0002a5d6b15d"
|
||||
51
charger/nrg/connect/types.go
Normal file
51
charger/nrg/connect/types.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package connect
|
||||
|
||||
const (
|
||||
SettingsPath = "settings"
|
||||
MeasurementsPath = "measurements"
|
||||
)
|
||||
|
||||
// Measurements is the /api/measurements response
|
||||
type Measurements struct {
|
||||
Message string `json:"omitempty"` // api message if not ok
|
||||
ChargingEnergy float64
|
||||
ChargingEnergyOverAll float64
|
||||
ChargingPower float64
|
||||
ChargingPowerPhase [3]float64
|
||||
ChargingCurrentPhase [3]float64
|
||||
Frequency float64
|
||||
}
|
||||
|
||||
// Settings is the /api/settings request/response
|
||||
type Settings struct {
|
||||
Message string `json:",omitempty"` // api message if not ok
|
||||
Info Info `json:",omitempty"`
|
||||
Values Values
|
||||
}
|
||||
|
||||
// Info is Settings.Info
|
||||
type Info struct {
|
||||
Connected bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Values is Settings.Values
|
||||
type Values struct {
|
||||
ChargingStatus *ChargingStatus `json:",omitempty"`
|
||||
ChargingCurrent *ChargingCurrent `json:",omitempty"`
|
||||
DeviceMetadata DeviceMetadata
|
||||
}
|
||||
|
||||
// ChargingStatus is Settings.Values.ChargingStatus
|
||||
type ChargingStatus struct {
|
||||
Charging bool
|
||||
}
|
||||
|
||||
// ChargingCurrent is Settings.Values.ChargingCurrent
|
||||
type ChargingCurrent struct {
|
||||
Value float64
|
||||
}
|
||||
|
||||
// DeviceMetadata is Settings.Values.DeviceMetadata
|
||||
type DeviceMetadata struct {
|
||||
Password string
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/nrgble"
|
||||
"github.com/evcc-io/evcc/charger/nrg/ble"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/lunixbochs/struc"
|
||||
|
|
@ -121,12 +121,12 @@ func NewNRGKickBLE(device, mac string, pin int) (*NRGKickBLE, error) {
|
|||
}
|
||||
|
||||
func (nrg *NRGKickBLE) connect() (*device.Device1, error) {
|
||||
dev, err := nrgble.FindDevice(nrg.adapter, nrg.mac, nrgTimeout)
|
||||
dev, err := ble.FindDevice(nrg.adapter, nrg.mac, nrgTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find device: %s", err)
|
||||
}
|
||||
|
||||
err = nrgble.Connect(dev, nrg.agent, nrg.device)
|
||||
err = ble.Connect(dev, nrg.agent, nrg.device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -199,8 +199,8 @@ func (nrg *NRGKickBLE) write(service string, val interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (nrg *NRGKickBLE) mergeSettings(info nrgble.Info) nrgble.Settings {
|
||||
return nrgble.Settings{
|
||||
func (nrg *NRGKickBLE) mergeSettings(info ble.Info) ble.Settings {
|
||||
return ble.Settings{
|
||||
PIN: nrg.pin,
|
||||
ChargingEnergyLimit: 19997, // magic const for "disable"
|
||||
KWhPer100: info.KWhPer100,
|
||||
|
|
@ -214,8 +214,8 @@ func (nrg *NRGKickBLE) mergeSettings(info nrgble.Info) nrgble.Settings {
|
|||
|
||||
// Status implements the api.Charger interface
|
||||
func (nrg *NRGKickBLE) Status() (api.ChargeStatus, error) {
|
||||
res := nrgble.Power{}
|
||||
if err := nrg.read(nrgble.PowerService, &res); err != nil {
|
||||
res := ble.Power{}
|
||||
if err := nrg.read(ble.PowerService, &res); err != nil {
|
||||
return api.StatusF, err
|
||||
}
|
||||
|
||||
|
|
@ -235,8 +235,8 @@ func (nrg *NRGKickBLE) Status() (api.ChargeStatus, error) {
|
|||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (nrg *NRGKickBLE) Enabled() (bool, error) {
|
||||
res := nrgble.Info{}
|
||||
if err := nrg.read(nrgble.InfoService, &res); err != nil {
|
||||
res := ble.Info{}
|
||||
if err := nrg.read(ble.InfoService, &res); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -249,8 +249,8 @@ func (nrg *NRGKickBLE) Enabled() (bool, error) {
|
|||
|
||||
// Enable implements the api.Charger interface
|
||||
func (nrg *NRGKickBLE) Enable(enable bool) error {
|
||||
res := nrgble.Info{}
|
||||
if err := nrg.read(nrgble.InfoService, &res); err != nil {
|
||||
res := ble.Info{}
|
||||
if err := nrg.read(ble.InfoService, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ func (nrg *NRGKickBLE) Enable(enable bool) error {
|
|||
settings := nrg.mergeSettings(res)
|
||||
|
||||
nrg.log.TRACE.Printf("write settings (workaround): %+v", settings)
|
||||
if err := nrg.write(nrgble.SettingsService, &settings); err != nil {
|
||||
if err := nrg.write(ble.SettingsService, &settings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -271,13 +271,13 @@ func (nrg *NRGKickBLE) Enable(enable bool) error {
|
|||
|
||||
nrg.log.TRACE.Printf("write settings: %+v", settings)
|
||||
|
||||
return nrg.write(nrgble.SettingsService, &settings)
|
||||
return nrg.write(ble.SettingsService, &settings)
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (nrg *NRGKickBLE) MaxCurrent(current int64) error {
|
||||
res := nrgble.Info{}
|
||||
if err := nrg.read(nrgble.InfoService, &res); err != nil {
|
||||
res := ble.Info{}
|
||||
if err := nrg.read(ble.InfoService, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -286,15 +286,15 @@ func (nrg *NRGKickBLE) MaxCurrent(current int64) error {
|
|||
|
||||
nrg.log.TRACE.Printf("write settings: %+v", settings)
|
||||
|
||||
return nrg.write(nrgble.SettingsService, &settings)
|
||||
return nrg.write(ble.SettingsService, &settings)
|
||||
}
|
||||
|
||||
var _ api.Meter = (*NRGKickBLE)(nil)
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
func (nrg *NRGKickBLE) CurrentPower() (float64, error) {
|
||||
res := nrgble.Power{}
|
||||
if err := nrg.read(nrgble.PowerService, &res); err != nil {
|
||||
res := ble.Power{}
|
||||
if err := nrg.read(ble.PowerService, &res); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
|
@ -307,8 +307,8 @@ var _ api.MeterEnergy = (*NRGKickBLE)(nil)
|
|||
|
||||
// TotalEnergy implements the api.MeterEnergy interface
|
||||
func (nrg *NRGKickBLE) TotalEnergy() (float64, error) {
|
||||
res := nrgble.Energy{}
|
||||
if err := nrg.read(nrgble.EnergyService, &res); err != nil {
|
||||
res := ble.Energy{}
|
||||
if err := nrg.read(ble.EnergyService, &res); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
|
@ -321,8 +321,8 @@ var _ api.PhaseCurrents = (*NRGKickBLE)(nil)
|
|||
|
||||
// Currents implements the api.PhaseCurrents interface
|
||||
func (nrg *NRGKickBLE) Currents() (float64, float64, float64, error) {
|
||||
res := nrgble.VoltageCurrent{}
|
||||
if err := nrg.read(nrgble.VoltageCurrentService, &res); err != nil {
|
||||
res := ble.VoltageCurrent{}
|
||||
if err := nrg.read(ble.VoltageCurrentService, &res); err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
|
|
@ -337,8 +337,8 @@ func (nrg *NRGKickBLE) Currents() (float64, float64, float64, error) {
|
|||
// ChargedEnergy implements the ChargeRater interface
|
||||
// NOTE: apparently shows energy of a stopped charging session, hence substituted by TotalEnergy
|
||||
// func (nrg *NRGKickBLE) ChargedEnergy() (float64, error) {
|
||||
// res := nrgble.Energy{}
|
||||
// if err := nrg.read(nrgble.EnergyService, &res); err != nil {
|
||||
// res := ble.Energy{}
|
||||
// if err := nrg.read(ble.EnergyService, &res); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
// nrg.log.TRACE.Printf("energy: %+v", res)
|
||||
|
|
|
|||
|
|
@ -3,71 +3,28 @@ package charger
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/nrg/connect"
|
||||
"github.com/evcc-io/evcc/provider"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
)
|
||||
|
||||
// https://www.nrgkick.com/wp-content/uploads/2019/08/20190814_API-Dokumentation_04.pdf
|
||||
|
||||
const (
|
||||
nrgSettings = "settings"
|
||||
nrgMeasurements = "measurements"
|
||||
)
|
||||
|
||||
// NRGMeasurements is the /api/measurements response
|
||||
type NRGMeasurements struct {
|
||||
Message string `json:"omitempty"` // api message if not ok
|
||||
ChargingEnergy float64
|
||||
ChargingEnergyOverAll float64
|
||||
ChargingPower float64
|
||||
ChargingPowerPhase [3]float64
|
||||
ChargingCurrentPhase [3]float64
|
||||
Frequency float64
|
||||
}
|
||||
|
||||
// NRGSettings is the /api/settings request/response
|
||||
type NRGSettings struct {
|
||||
Message string `json:",omitempty"` // api message if not ok
|
||||
Info NRGInfo `json:",omitempty"`
|
||||
Values NRGValues
|
||||
}
|
||||
|
||||
// NRGInfo is NRGSettings.Info
|
||||
type NRGInfo struct {
|
||||
Connected bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// NRGValues is NRGSettings.Values
|
||||
type NRGValues struct {
|
||||
ChargingStatus NRGChargingStatus
|
||||
ChargingCurrent NRGChargingCurrent
|
||||
DeviceMetadata NRGDeviceMetadata
|
||||
}
|
||||
|
||||
// NRGChargingStatus is NRGSettings.Values.ChargingStatus
|
||||
type NRGChargingStatus struct {
|
||||
Charging *bool `json:",omitempty"` // use pointer to allow omitting false
|
||||
}
|
||||
|
||||
// NRGChargingCurrent is NRGSettings.Values.ChargingCurrent
|
||||
type NRGChargingCurrent struct {
|
||||
Value float64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// NRGDeviceMetadata is NRGSettings.Values.DeviceMetadata
|
||||
type NRGDeviceMetadata struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
// NRGKickConnect charger implementation
|
||||
type NRGKickConnect struct {
|
||||
*request.Helper
|
||||
uri string
|
||||
mac string
|
||||
password string
|
||||
uri string
|
||||
mac string
|
||||
password string
|
||||
enabled bool
|
||||
settingsG provider.Cacheable[connect.Settings]
|
||||
measurementsG provider.Cacheable[connect.Measurements]
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -76,18 +33,22 @@ func init() {
|
|||
|
||||
// NewNRGKickConnectFromConfig creates a NRGKickConnect charger from generic config
|
||||
func NewNRGKickConnectFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
var cc struct {
|
||||
cc := struct {
|
||||
URI, Mac, Password string
|
||||
Cache time.Duration
|
||||
}{
|
||||
Cache: time.Second,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewNRGKickConnect(cc.URI, cc.Mac, cc.Password)
|
||||
return NewNRGKickConnect(cc.URI, cc.Mac, cc.Password, cc.Cache)
|
||||
}
|
||||
|
||||
// NewNRGKickConnect creates NRGKickConnect charger
|
||||
func NewNRGKickConnect(uri, mac, password string) (*NRGKickConnect, error) {
|
||||
func NewNRGKickConnect(uri, mac, password string, cache time.Duration) (*NRGKickConnect, error) {
|
||||
nrg := &NRGKickConnect{
|
||||
Helper: request.NewHelper(util.NewLogger("nrgconn")),
|
||||
uri: util.DefaultScheme(uri, "http"),
|
||||
|
|
@ -95,6 +56,28 @@ func NewNRGKickConnect(uri, mac, password string) (*NRGKickConnect, error) {
|
|||
password: password,
|
||||
}
|
||||
|
||||
nrg.settingsG = provider.ResettableCached(func() (connect.Settings, error) {
|
||||
var res connect.Settings
|
||||
|
||||
err := nrg.GetJSON(nrg.apiURL(connect.SettingsPath), &res)
|
||||
if err != nil && res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}, cache)
|
||||
|
||||
nrg.measurementsG = provider.ResettableCached(func() (connect.Measurements, error) {
|
||||
var res connect.Measurements
|
||||
|
||||
err := nrg.GetJSON(nrg.apiURL(connect.MeasurementsPath), &res)
|
||||
if err != nil && res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}, cache)
|
||||
|
||||
return nrg, nil
|
||||
}
|
||||
|
||||
|
|
@ -106,15 +89,22 @@ func (nrg *NRGKickConnect) putJSON(url string, data interface{}) error {
|
|||
req, err := request.New(http.MethodPut, url, request.MarshalJSON(data), request.JSONEncoding)
|
||||
|
||||
if err == nil {
|
||||
var resp struct {
|
||||
var res struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
if err = nrg.DoJSON(req, &resp); err != nil {
|
||||
if resp.Message != "" {
|
||||
return fmt.Errorf("response: %s", resp.Message)
|
||||
if err = nrg.DoJSON(req, &res); err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
} else if res.Message != "" {
|
||||
return errors.New(res.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
nrg.settingsG.Reset()
|
||||
nrg.measurementsG.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
@ -122,63 +112,83 @@ func (nrg *NRGKickConnect) putJSON(url string, data interface{}) error {
|
|||
|
||||
// Status implements the api.Charger interface
|
||||
func (nrg *NRGKickConnect) Status() (api.ChargeStatus, error) {
|
||||
return api.StatusC, nil
|
||||
res, err := nrg.settingsG.Get()
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
if res.Values.ChargingStatus == nil {
|
||||
return api.StatusNone, errors.New("unknown status")
|
||||
}
|
||||
|
||||
if res.Values.ChargingStatus.Charging {
|
||||
return api.StatusC, nil
|
||||
}
|
||||
|
||||
return api.StatusB, nil
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (nrg *NRGKickConnect) Enabled() (bool, error) {
|
||||
var res NRGSettings
|
||||
err := nrg.GetJSON(nrg.apiURL(nrgSettings), &res)
|
||||
if err != nil {
|
||||
if res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return *res.Values.ChargingStatus.Charging, nil
|
||||
return nrg.enabled, nil
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (nrg *NRGKickConnect) Enable(enable bool) error {
|
||||
settings := NRGSettings{}
|
||||
settings.Values.DeviceMetadata.Password = nrg.password
|
||||
settings.Values.ChargingStatus.Charging = &enable
|
||||
data := connect.Settings{
|
||||
Values: connect.Values{
|
||||
ChargingStatus: &connect.ChargingStatus{
|
||||
Charging: enable,
|
||||
},
|
||||
DeviceMetadata: connect.DeviceMetadata{
|
||||
Password: nrg.password,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return nrg.putJSON(nrg.apiURL(nrgSettings), settings)
|
||||
err := nrg.putJSON(nrg.apiURL(connect.SettingsPath), data)
|
||||
if err == nil {
|
||||
nrg.enabled = enable
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (nrg *NRGKickConnect) MaxCurrent(current int64) error {
|
||||
settings := NRGSettings{}
|
||||
settings.Values.DeviceMetadata.Password = nrg.password
|
||||
settings.Values.ChargingCurrent.Value = float64(current)
|
||||
data := connect.Settings{
|
||||
Values: connect.Values{
|
||||
ChargingCurrent: &connect.ChargingCurrent{
|
||||
Value: float64(current),
|
||||
},
|
||||
DeviceMetadata: connect.DeviceMetadata{
|
||||
Password: nrg.password,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return nrg.putJSON(nrg.apiURL(nrgSettings), settings)
|
||||
return nrg.putJSON(nrg.apiURL(connect.SettingsPath), data)
|
||||
}
|
||||
|
||||
var _ api.Meter = (*NRGKickConnect)(nil)
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
func (nrg *NRGKickConnect) CurrentPower() (float64, error) {
|
||||
var res NRGMeasurements
|
||||
err := nrg.GetJSON(nrg.apiURL(nrgMeasurements), &res)
|
||||
if err != nil && res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
res, err := nrg.measurementsG.Get()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return 1000 * res.ChargingPower, err
|
||||
return res.ChargingPower * 1e3, err
|
||||
}
|
||||
|
||||
var _ api.MeterEnergy = (*NRGKickConnect)(nil)
|
||||
|
||||
// TotalEnergy implements the api.MeterEnergy interface
|
||||
func (nrg *NRGKickConnect) TotalEnergy() (float64, error) {
|
||||
var res NRGMeasurements
|
||||
err := nrg.GetJSON(nrg.apiURL(nrgMeasurements), &res)
|
||||
if err != nil && res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
res, err := nrg.measurementsG.Get()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.ChargingEnergyOverAll, err
|
||||
|
|
@ -188,10 +198,9 @@ var _ api.PhaseCurrents = (*NRGKickConnect)(nil)
|
|||
|
||||
// Currents implements the api.PhaseCurrents interface
|
||||
func (nrg *NRGKickConnect) Currents() (float64, float64, float64, error) {
|
||||
var res NRGMeasurements
|
||||
err := nrg.GetJSON(nrg.apiURL(nrgMeasurements), &res)
|
||||
if err != nil && res.Message != "" {
|
||||
err = errors.New(res.Message)
|
||||
res, err := nrg.measurementsG.Get()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
if len(res.ChargingCurrentPhase) != 3 {
|
||||
|
|
@ -207,7 +216,7 @@ func (nrg *NRGKickConnect) Currents() (float64, float64, float64, error) {
|
|||
// ChargedEnergy implements the ChargeRater interface
|
||||
// NOTE: apparently shows energy of a stopped charging session, hence substituted by TotalEnergy
|
||||
// func (nrg *NRGKickConnect) ChargedEnergy() (float64, error) {
|
||||
// var res NRGMeasurements
|
||||
// err := nrg.GetJSON(nrg.apiURL(nrgMeasurements), &res)
|
||||
// var res connect.Measurements
|
||||
// err := nrg.GetJSON(nrg.apiURL(connect.MeasurementsPath), &res)
|
||||
// return res.ChargingEnergy, err
|
||||
// }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue