From e16c52514d0933cd62bb863e056711304ec26fe6 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 11 Jun 2022 14:07:37 +0200 Subject: [PATCH] Add ConnectIQ charger (#3622) --- charger/_blueprint.go | 14 +-- charger/connectiq.go | 185 +++++++++++++++++++++++++++++++++++++ charger/connectiq/types.go | 24 +++++ 3 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 charger/connectiq.go create mode 100644 charger/connectiq/types.go diff --git a/charger/_blueprint.go b/charger/_blueprint.go index 53b758f14..d06ca3be1 100644 --- a/charger/_blueprint.go +++ b/charger/_blueprint.go @@ -95,6 +95,13 @@ func (wb *Blueprint) ChargedEnergy() (float64, error) { return 0, api.ErrNotAvailable } +var _ api.MeterEnergy = (*Blueprint)(nil) + +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Blueprint) TotalEnergy() (float64, error) { + return 0, api.ErrNotAvailable +} + var _ api.MeterCurrent = (*Blueprint)(nil) // Currents implements the api.MeterCurrent interface @@ -109,13 +116,6 @@ func (wb *Blueprint) Identify() (string, error) { return "", api.ErrNotAvailable } -var _ api.MeterEnergy = (*Blueprint)(nil) - -// TotalEnergy implements the api.MeterEnergy interface -func (wb *Blueprint) TotalEnergy() (float64, error) { - return 0, api.ErrNotAvailable -} - var _ api.ChargePhases = (*Blueprint)(nil) // Phases1p3p implements the api.ChargePhases interface diff --git a/charger/connectiq.go b/charger/connectiq.go new file mode 100644 index 000000000..c23f774ad --- /dev/null +++ b/charger/connectiq.go @@ -0,0 +1,185 @@ +package charger + +// LICENSE + +// Copyright (c) 2019-2022 andig + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/charger/connectiq" + "github.com/evcc-io/evcc/provider" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" +) + +// ConnectIq charger implementation +type ConnectIq struct { + *request.Helper + uri string + curr int64 + meterG func() (connectiq.MeterStatus, error) + cache time.Duration +} + +func init() { + registry.Add("connectiq", NewConnectIqFromConfig) +} + +// NewConnectIqFromConfig creates a ConnectIq charger from generic config +func NewConnectIqFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := struct { + URI string + Cache time.Duration + }{ + Cache: time.Second, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return NewConnectIq(cc.URI, cc.Cache) +} + +// NewConnectIq creates ConnectIq charger +func NewConnectIq(uri string, cache time.Duration) (api.Charger, error) { + log := util.NewLogger("connectiq") + + wb := &ConnectIq{ + Helper: request.NewHelper(log), + uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"), + curr: 6, + cache: cache, + } + + // cache meter readings + wb.meterG = provider.Cached(func() (connectiq.MeterStatus, error) { + var res connectiq.MeterStatus + uri := fmt.Sprintf("%s/meter/status", wb.uri) + err := wb.GetJSON(uri, &res) + return res, err + }, wb.cache) + + return wb, nil +} + +func (wb *ConnectIq) status() (connectiq.ChargeStatus, error) { + var res connectiq.ChargeStatus + uri := fmt.Sprintf("%s/charge/status", wb.uri) + err := wb.GetJSON(uri, &res) + return res, err +} + +// Status implements the api.Charger interface +func (wb *ConnectIq) Status() (api.ChargeStatus, error) { + resp, err := wb.status() + + res := api.StatusNone + switch resp.Status { + case "ready": + res = api.StatusA + case "ev": + res = api.StatusB + case "charging": + res = api.StatusC + default: + if err == nil { + err = fmt.Errorf("invalid status: %s", resp.Status) + } + } + + return res, err +} + +// Enabled implements the api.Charger interface +func (wb *ConnectIq) Enabled() (bool, error) { + var res connectiq.ChargeMaxAmps + uri := fmt.Sprintf("%s/charge/max_amps", wb.uri) + err := wb.GetJSON(uri, &res) + return res.Max > 0, err +} + +// Enable implements the api.Charger interface +func (wb *ConnectIq) Enable(enable bool) error { + var curr int64 + if enable { + curr = wb.curr + } + return wb.setCurrent(curr) +} + +func (wb *ConnectIq) setCurrent(current int64) error { + uri := fmt.Sprintf("%s/charge/max_amps", wb.uri) + + data := struct { + Max int64 `json:"max"` + }{ + Max: current, + } + + req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding) + if err == nil { + _, err = wb.DoBody(req) + } + + return err +} + +// MaxCurrent implements the api.Charger interface +func (wb *ConnectIq) MaxCurrent(current int64) error { + err := wb.setCurrent(current) + if err == nil { + wb.curr = current + } + return err +} + +var _ api.Meter = (*ConnectIq)(nil) + +// CurrentPower implements the api.Meter interface +func (wb *ConnectIq) CurrentPower() (float64, error) { + res, err := wb.meterG() + if err != nil { + return 0, err + } + return (res.Pow[0] + res.Pow[1] + res.Pow[2]) * 1e3, nil +} + +var _ api.MeterEnergy = (*ConnectIq)(nil) + +// TotalEnergy implements the api.MeterEnergy interface +func (wb *ConnectIq) TotalEnergy() (float64, error) { + var res connectiq.MeterRead + uri := fmt.Sprintf("%s/meter/read", wb.uri) + err := wb.GetJSON(uri, &res) + return res.Energy, err +} + +var _ api.MeterCurrent = (*ConnectIq)(nil) + +// Currents implements the api.MeterCurrent interface +func (wb *ConnectIq) Currents() (float64, float64, float64, error) { + res, err := wb.meterG() + if err != nil { + return 0, 0, 0, err + } + return res.Curr[0], res.Curr[1], res.Curr[2], nil +} diff --git a/charger/connectiq/types.go b/charger/connectiq/types.go new file mode 100644 index 000000000..fbc2b97ea --- /dev/null +++ b/charger/connectiq/types.go @@ -0,0 +1,24 @@ +package connectiq + +type ChargeStatus struct { + Amps int64 `json:"amps"` + Pp int64 `json:"pp"` + Status string `json:"status"` + Std int64 `json:"std"` +} + +type ChargeMaxAmps struct { + Max int64 `json:"max"` +} + +type MeterStatus struct { + App []float64 `json:"app"` + Curr []float64 `json:"curr"` + Fac []float64 `json:"fac"` + Pow []float64 `json:"pow"` + Volt []float64 `json:"volt"` +} + +type MeterRead struct { + Energy float64 `json:"energy"` +}