diff --git a/charger/echarge/types.go b/charger/echarge/types.go new file mode 100644 index 000000000..d08dfad70 --- /dev/null +++ b/charger/echarge/types.go @@ -0,0 +1,36 @@ +package echarge + +const ( + ModeEco = "eco" + ModeManual = "manual" +) + +type Meter struct { + ID int + Name string + Data map[string]float64 +} + +type ChargeControl struct { + ID int + Name string + State string + Mode string + ManualModeAmp float64 +} + +type Rfid struct { + ID int + Name string + Data struct { + Tag string + } +} + +type All struct { + Network struct{} + System struct{} + Meters []Meter + ChargeControls []ChargeControl + Rfid []Rfid +} diff --git a/charger/hardybarth.go b/charger/hardybarth.go new file mode 100644 index 000000000..d89dd10cf --- /dev/null +++ b/charger/hardybarth.go @@ -0,0 +1,211 @@ +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" + "net/url" + "strings" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/charger/echarge" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" +) + +// http://apidoc.ecb1.de +// https://github.com/evcc-io/evcc/discussions/778 + +// HardyBarth charger implementation +type HardyBarth struct { + *request.Helper + uri string + chargecontrol int + meter int + current int64 +} + +func init() { + registry.Add("hardybarth", NewHardyBarthFromConfig) +} + +// NewHardyBarthFromConfig creates a HardyBarth cPH1 charger from generic config +func NewHardyBarthFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := struct { + URI string + ChargeControl int + Meter int + }{ + ChargeControl: 1, + Meter: 1, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return NewHardyBarth(cc.URI, cc.ChargeControl, cc.Meter) +} + +// NewHardyBarth creates HardyBarth charger +func NewHardyBarth(uri string, chargecontrol, meter int) (api.Charger, error) { + log := util.NewLogger("hardybarth") + + wb := &HardyBarth{ + Helper: request.NewHelper(log), + uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"), + chargecontrol: chargecontrol, + meter: meter, + current: 6, + } + + // if !sponsor.IsAuthorized() { + // return nil, api.ErrSponsorRequired + // } + + uri = fmt.Sprintf("%s/api/v1/chargecontrols/%d/mode", wb.uri, wb.chargecontrol) + data := url.Values{"mode": {echarge.ModeManual}} + err := wb.post(uri, data) + + return wb, err +} + +func (wb *HardyBarth) getChargeControl() (echarge.ChargeControl, error) { + uri := fmt.Sprintf("%s/api/v1/chargecontrols/%d", wb.uri, wb.chargecontrol) + + res := struct { + ChargeControl struct { + echarge.ChargeControl + } + }{} + + err := wb.GetJSON(uri, &res) + + return res.ChargeControl.ChargeControl, err +} + +// Status implements the api.Charger interface +func (wb *HardyBarth) Status() (api.ChargeStatus, error) { + res, err := wb.getChargeControl() + if err != nil { + return api.StatusNone, err + } + + switch s := res.State[:1]; s { + case "A", "B", "C": + return api.ChargeStatus(s), nil + default: + return api.StatusNone, fmt.Errorf("invalid state: %s", s) + } +} + +// 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.ManualModeAmp > 0, err +} + +// Enable implements the api.Charger interface +func (wb *HardyBarth) Enable(enable bool) error { + var current int64 + if enable { + current = wb.current + } + + return wb.setCurrent(current) +} + +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 +} + +func (wb *HardyBarth) setCurrent(current int64) error { + uri := fmt.Sprintf("%s/api/v1/chargecontrols/%d/mode/manual/ampere", wb.uri, wb.chargecontrol) + data := url.Values{"manualmodeamp": {fmt.Sprintf("%d", current)}} + return wb.post(uri, data) +} + +// MaxCurrent implements the api.Charger interface +func (wb *HardyBarth) MaxCurrent(current int64) error { + err := wb.setCurrent(current) + if err == nil { + wb.current = current + } + return err +} + +var _ api.Meter = (*HardyBarth)(nil) + +// CurrentPower implements the api.Meter interface +func (wb *HardyBarth) CurrentPower() (float64, error) { + uri := fmt.Sprintf("%s/api/v1/meters/%d", wb.uri, wb.meter) + + res := struct { + Meter struct { + echarge.Meter + } + }{} + + err := wb.GetJSON(uri, &res) + if err != nil { + return 0, err + } + + return res.Meter.Meter.Data["1-0:1.4.0"], nil +} + +// var _ api.ChargeRater = (*HardyBarth)(nil) + +// // ChargedEnergy implements the api.ChargeRater interface +// func (wb *HardyBarth) ChargedEnergy() (float64, error) { +// return 0, api.ErrNotAvailable +// } + +// var _ api.MeterCurrent = (*HardyBarth)(nil) + +// // Currents implements the api.MeterCurrent interface +// func (wb *HardyBarth) Currents() (float64, float64, float64, error) { +// return 0, 0, 0, api.ErrNotAvailable +// } + +// var _ api.MeterEnergy = (*HardyBarth)(nil) + +// // TotalEnergy implements the api.MeterEnergy interface - v2 only +// func (wb *HardyBarth) TotalEnergy() (float64, error) { +// return 0, api.ErrNotAvailable +// } + +// var _ api.Identifier = (*HardyBarth)(nil) + +// // Identify implements the api.Identifier interface +// func (wb *HardyBarth) Identify() (string, error) { +// return "", api.ErrNotAvailable +// } diff --git a/cmd/charger.go b/cmd/charger.go index 9d85df8fd..76596a7a7 100644 --- a/cmd/charger.go +++ b/cmd/charger.go @@ -18,10 +18,12 @@ var chargerCmd = &cobra.Command{ Run: runCharger, } +const noCurrent = -1 + func init() { rootCmd.AddCommand(chargerCmd) chargerCmd.PersistentFlags().StringP(flagName, "n", "", "select charger by name") - chargerCmd.PersistentFlags().IntP(flagCurrent, "I", -1, "set current") + chargerCmd.PersistentFlags().IntP(flagCurrent, "I", noCurrent, "set current") chargerCmd.PersistentFlags().BoolP(flagEnable, "e", false, flagEnable) chargerCmd.PersistentFlags().BoolP(flagDisable, "d", false, flagDisable) chargerCmd.PersistentFlags().BoolP(flagWakeup, "w", false, flagWakeup) @@ -65,7 +67,7 @@ func runCharger(cmd *cobra.Command, args []string) { chargers = map[string]api.Charger{arg: cp.Charger(arg)} } - var current int64 + current := int64(noCurrent) if flag := cmd.PersistentFlags().Lookup(flagCurrent); flag.Changed { var err error current, err = strconv.ParseInt(flag.Value.String(), 10, 64)