NRG: fix connect json encoding (#5899)

This commit is contained in:
andig 2023-01-24 22:14:17 +01:00 • committed by GitHub
parent 72121dbcfe
commit a0be402251
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 121 deletions

View file

@ -1,4 +1,4 @@
package nrgble
package ble
import (
"errors"

View file

@ -1,4 +1,4 @@
package nrgble
package ble
const (
InfoService = "8f75bba0-c903-11e4-9fe8-0002a5d6b15d"

View 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
}

View file

@ -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)

View file

@ -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
// }