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" "time" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api/implement" goe "github.com/evcc-io/evcc/charger/go-e" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/sponsor" ) // https://go-e.co/app/api.pdf // https://github.com/goecharger/go-eCharger-API-v1/ // https://github.com/goecharger/go-eCharger-API-v2/ // GoE charger implementation type GoE struct { implement.Caps api goe.API } func init() { registry.Add("go-e", func(other map[string]any) (api.Charger, error) { return newGoEFromConfig(true, other) }) registry.Add("go-e-v3", func(other map[string]any) (api.Charger, error) { return newGoEFromConfig(false, other) }) } // newGoEFromConfig creates a go-e charger from generic config func newGoEFromConfig(v2 bool, other map[string]any) (api.Charger, error) { cc := struct { Token string URI string Cache time.Duration }{ Cache: time.Second, } if err := util.DecodeOther(other, &cc); err != nil { return nil, err } if (cc.URI != "") == (cc.Token != "") { return nil, errors.New("must have either uri or token") } c, err := NewGoE(cc.URI, cc.Token, cc.Cache) if err != nil { return nil, err } if v2 { implement.Has(c, implement.ChargeRater(c.chargedEnergy)) } if c.api.IsV2() { implement.Has(c, implement.PhaseSwitcher(c.phases1p3p)) } return c, nil } // NewGoE creates GoE charger func NewGoE(uri, token string, cache time.Duration) (*GoE, error) { c := &GoE{ Caps: implement.New(), } log := util.NewLogger("go-e").Redact(token) if token != "" { c.api = goe.NewCloud(log, token, cache) } else { c.api = goe.NewLocal(log, util.DefaultScheme(uri, "http"), cache) } if !sponsor.IsAuthorized() { return nil, api.ErrSponsorRequired } return c, nil } // Status implements the api.Charger interface func (c *GoE) Status() (api.ChargeStatus, error) { resp, err := c.api.Status() if err != nil { return api.StatusNone, err } switch car := resp.Status(); car { case 1: return api.StatusA, nil case 2: return api.StatusC, nil case 3, 4: return api.StatusB, nil default: return api.StatusNone, fmt.Errorf("car unknown result: %d", car) } } // Enabled implements the api.Charger interface func (c *GoE) Enabled() (bool, error) { resp, err := c.api.Status() if err != nil { return false, err } return resp.Enabled(), nil } // Enable implements the api.Charger interface func (c *GoE) Enable(enable bool) error { var b int if enable { b = 1 } param := map[bool]string{false: "alw", true: "frc"}[c.api.IsV2()] if c.api.IsV2() { b += 1 } return c.api.Update(fmt.Sprintf("%s=%d", param, b)) } // MaxCurrent implements the api.Charger interface func (c *GoE) MaxCurrent(current int64) error { param := map[bool]string{false: "amx", true: "amp"}[c.api.IsV2()] return c.api.Update(fmt.Sprintf("%s=%d", param, current)) } var _ api.Meter = (*GoE)(nil) // CurrentPower implements the api.Meter interface func (c *GoE) CurrentPower() (float64, error) { resp, err := c.api.Status() if err != nil { return 0, err } return resp.CurrentPower(), err } // removed: https://github.com/evcc-io/evcc/issues/13726 // var _ api.ChargeRater = (*GoE)(nil) var _ api.PhaseCurrents = (*GoE)(nil) // Currents implements the api.PhaseCurrents interface func (c *GoE) Currents() (float64, float64, float64, error) { resp, err := c.api.Status() if err != nil { return 0, 0, 0, err } i1, i2, i3 := resp.Currents() return i1, i2, i3, err } var _ api.PhaseVoltages = (*GoE)(nil) // Voltages implements the api.PhaseVoltages interface func (c *GoE) Voltages() (float64, float64, float64, error) { resp, err := c.api.Status() if err != nil { return 0, 0, 0, err } u1, u2, u3 := resp.Voltages() return u1, u2, u3, err } var _ api.Identifier = (*GoE)(nil) // Identify implements the api.Identifier interface func (c *GoE) Identify() ([]string, error) { resp, err := c.api.Status() if err != nil { return nil, err } return []string{resp.Identify()}, nil } var _ api.MeterEnergy = (*GoE)(nil) // totalEnergy implements the api.MeterEnergy interface - v2 only func (c *GoE) TotalEnergy() (float64, error) { resp, err := c.api.Status() if err != nil { return 0, err } return resp.TotalEnergy(), err } // chargedEnergy implements the api.ChargeRater interface - v2 only // https://github.com/evcc-io/evcc/issues/13726 func (c *GoE) chargedEnergy() (float64, error) { resp, err := c.api.Status() if err != nil { return 0, err } return resp.ChargedEnergy(), err } // phases1p3p implements the api.PhaseSwitcher interface - v2 only func (c *GoE) phases1p3p(phases int) error { if phases == 3 { phases = 2 } return c.api.Update(fmt.Sprintf("psm=%d", phases)) }