238 lines
5.8 KiB
Go
238 lines
5.8 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 (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/charger/echarge"
|
|
"github.com/evcc-io/evcc/charger/echarge/ecb1"
|
|
"github.com/evcc-io/evcc/meter/obis"
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/evcc-io/evcc/util/request"
|
|
"github.com/evcc-io/evcc/util/sponsor"
|
|
)
|
|
|
|
// http://apidoc.ecb1.de
|
|
// https://github.com/evcc-io/evcc/discussions/778
|
|
// https://ee-toolkit.com/electric-car-automated-charging
|
|
|
|
// HardyBarth charger implementation
|
|
type HardyBarth struct {
|
|
*request.Helper
|
|
uri string
|
|
chargecontrol int
|
|
meterG func() (ecb1.Meter, error)
|
|
}
|
|
|
|
func init() {
|
|
registry.Add("hardybarth-ecb1", NewHardyBarthFromConfig)
|
|
}
|
|
|
|
// NewHardyBarthFromConfig creates a HardyBarth cPH1 charger from generic config
|
|
func NewHardyBarthFromConfig(other map[string]any) (api.Charger, error) {
|
|
cc := struct {
|
|
URI string
|
|
ChargeControl int
|
|
Meter int
|
|
Cache time.Duration
|
|
}{
|
|
ChargeControl: 1,
|
|
Meter: 1,
|
|
Cache: time.Second,
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewHardyBarth(cc.URI, cc.ChargeControl, cc.Meter, cc.Cache)
|
|
}
|
|
|
|
// NewHardyBarth creates HardyBarth charger
|
|
func NewHardyBarth(uri string, chargecontrol, meter int, cache time.Duration) (api.Charger, error) {
|
|
log := util.NewLogger("ecb1")
|
|
|
|
uri = strings.TrimSuffix(uri, "/") + "/api/v1"
|
|
|
|
wb := &HardyBarth{
|
|
Helper: request.NewHelper(log),
|
|
uri: util.DefaultScheme(uri, "http"),
|
|
chargecontrol: chargecontrol,
|
|
}
|
|
|
|
// cache meter readings
|
|
wb.meterG = util.Cached(func() (ecb1.Meter, error) {
|
|
var res struct {
|
|
Meter struct {
|
|
ecb1.Meter
|
|
}
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s/meters/%d", wb.uri, meter)
|
|
err := wb.GetJSON(uri, &res)
|
|
|
|
return res.Meter.Meter, err
|
|
}, cache)
|
|
|
|
if !sponsor.IsAuthorized() {
|
|
return nil, api.ErrSponsorRequired
|
|
}
|
|
|
|
uri = fmt.Sprintf("%s/chargecontrols/%d/mode", wb.uri, wb.chargecontrol)
|
|
data := url.Values{"mode": {echarge.ModeManual}}
|
|
if err := wb.post(uri, data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return wb, nil
|
|
}
|
|
|
|
func (wb *HardyBarth) getChargeControl() (ecb1.ChargeControl, error) {
|
|
uri := fmt.Sprintf("%s/chargecontrols/%d", wb.uri, wb.chargecontrol)
|
|
|
|
var res struct {
|
|
ChargeControl struct {
|
|
ecb1.ChargeControl
|
|
}
|
|
}
|
|
|
|
err := wb.GetJSON(uri, &res)
|
|
|
|
return res.ChargeControl.ChargeControl, err
|
|
}
|
|
|
|
// Status implements the api.Charger interface
|
|
func (wb *HardyBarth) Status() (api.ChargeStatus, error) {
|
|
resp, err := wb.getChargeControl()
|
|
if err != nil {
|
|
return api.StatusNone, err
|
|
}
|
|
|
|
if resp.State == "" {
|
|
return api.StatusNone, errors.New("invalid state- check controller type (eCB1 vs Salia)")
|
|
}
|
|
|
|
res := api.StatusA
|
|
|
|
if resp.Connected {
|
|
res = api.StatusB
|
|
|
|
if resp.StateID == 5 {
|
|
res = api.StatusC
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// Enabled implements the api.Charger interface
|
|
func (wb *HardyBarth) Enabled() (bool, error) {
|
|
res, err := wb.getChargeControl()
|
|
if err == nil && res.Mode != echarge.ModeManual {
|
|
err = fmt.Errorf("invalid mode: %s", res.Mode)
|
|
}
|
|
|
|
return res.StateID != 17, err
|
|
}
|
|
|
|
// Enable implements the api.Charger interface
|
|
func (wb *HardyBarth) Enable(enable bool) error {
|
|
action := "stop"
|
|
if enable {
|
|
action = "start"
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s/chargecontrols/%d/%s", wb.uri, wb.chargecontrol, action)
|
|
req, err := request.New(http.MethodPost, uri, nil, request.JSONEncoding)
|
|
if err == nil {
|
|
_, err = wb.DoBody(req)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (wb *HardyBarth) post(uri string, data url.Values) error {
|
|
resp, err := wb.PostForm(uri, data)
|
|
if err == nil {
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= http.StatusBadRequest {
|
|
err = fmt.Errorf("invalid status: %s", resp.Status)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// MaxCurrent implements the api.Charger interface
|
|
func (wb *HardyBarth) MaxCurrent(current int64) error {
|
|
uri := fmt.Sprintf("%s/chargecontrols/%d/mode/manual/ampere", wb.uri, wb.chargecontrol)
|
|
data := url.Values{"manualmodeamp": {strconv.FormatInt(current, 10)}}
|
|
return wb.post(uri, data)
|
|
}
|
|
|
|
var _ api.Meter = (*HardyBarth)(nil)
|
|
|
|
// CurrentPower implements the api.Meter interface
|
|
func (wb *HardyBarth) CurrentPower() (float64, error) {
|
|
res, err := wb.meterG()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return res.Data[obis.PowerConsumption], nil
|
|
}
|
|
|
|
var _ api.MeterEnergy = (*HardyBarth)(nil)
|
|
|
|
// TotalEnergy implements the api.MeterEnergy interface
|
|
func (wb *HardyBarth) TotalEnergy() (float64, error) {
|
|
res, err := wb.meterG()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return res.Data[obis.EnergyConsumption], nil
|
|
}
|
|
|
|
var _ api.PhaseCurrents = (*HardyBarth)(nil)
|
|
|
|
// Currents implements the api.PhaseCurrents interface
|
|
func (wb *HardyBarth) Currents() (float64, float64, float64, error) {
|
|
res, err := wb.meterG()
|
|
if err != nil {
|
|
return 0, 0, 0, err
|
|
}
|
|
|
|
return res.Data[obis.CurrentL1], res.Data[obis.CurrentL2], res.Data[obis.CurrentL3], nil
|
|
}
|
|
|
|
// var _ api.Identifier = (*HardyBarth)(nil)
|
|
|
|
// // Identify implements the api.Identifier interface
|
|
// func (wb *HardyBarth) Identify() (string, error) {
|
|
// return "", api.ErrNotAvailable
|
|
// }
|