evcc-io/charger/connectiq.go
2025-10-31 15:25:53 +00:00

184 lines
4.4 KiB
Go

package charger
// LICENSE
// Copyright (c) evcc.io (andig, naltatis, premultiply)
// 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/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]any) (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 = util.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.PhaseCurrents = (*ConnectIq)(nil)
// Currents implements the api.PhaseCurrents 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
}