Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add charger V2C Trydan #17878

Merged
merged 8 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions charger/trydan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package charger

// https://v2charge.com/trydan/

import (
"errors"
"fmt"
"strings"
"time"

"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/sponsor"
)

type RealTimeData struct {
ID string `json:"ID"`
ChargeState int `json:"ChargeState"`
ReadyState int `json:"ReadyState"`
ChargePower float64 `json:"ChargePower"`
ChargeEnergy float64 `json:"ChargeEnergy"`
SlaveError int `json:"SlaveError"`
ChargeTime int `json:"ChargeTime"`
HousePower float64 `json:"HousePower"`
FVPower float64 `json:"FVPower"`
BatteryPower float64 `json:"BatteryPower"`
Paused int `json:"Paused"`
Locked int `json:"Locked"`
Timer int `json:"Timer"`
Intensity int `json:"Intensity"`
Dynamic int `json:"Dynamic"`
MinIntensity int `json:"MinIntensity"`
MaxIntensity int `json:"MaxIntensity"`
PauseDynamic int `json:"PauseDynamic"`
FirmwareVersion string `json:"FirmwareVersion"`
DynamicPowerMode int `json:"DynamicPowerMode"`
ContractedPower int `json:"ContractedPower"`
}

// Trydan charger implementation
type Trydan struct {
*request.Helper
uri string
statusG provider.Cacheable[RealTimeData]
current int
enabled bool
}

func init() {
registry.Add("trydan", NewTrydanFromConfig)
}

// NewTrydanFromConfig creates a Trydan charger from generic config
func NewTrydanFromConfig(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
}

if cc.URI == "" {
return nil, errors.New("missing uri")
}

return NewTrydan(cc.URI, cc.Cache)
}

// NewTrydan creates Trydan charger
func NewTrydan(uri string, cache time.Duration) (api.Charger, error) {
if !sponsor.IsAuthorized() {
return nil, api.ErrSponsorRequired
}

c := &Trydan{
Helper: request.NewHelper(util.NewLogger("trydan")),
uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "http"),
}

c.statusG = provider.ResettableCached(func() (RealTimeData, error) {
var res RealTimeData
uri := fmt.Sprintf("%s/RealTimeData", c.uri)
err := c.GetJSON(uri, &res)
return res, err
}, cache)

return c, nil
}

// Status implements the api.Charger interface
func (t Trydan) Status() (api.ChargeStatus, error) {
data, err := t.statusG.Get()
if err != nil {
return api.StatusNone, err
}
switch state := data.ChargeState; state {
case 0:
return api.StatusA, nil
case 1:
return api.StatusB, nil
case 2:
return api.StatusC, nil
default:
return api.StatusNone, fmt.Errorf("unknown status: %d", state)
}
}

// Enabled implements the api.Charger interface
func (c Trydan) Enabled() (bool, error) {
data, err := c.statusG.Get()
return data.Paused == 0 && data.Locked == 0, err
}

func (c *Trydan) setValue(param string, value int) error {
uri := fmt.Sprintf("%s/write/%s=%d", c.uri, param, value)
res, err := c.GetBody(uri)
if str := string(res); err == nil && str != "OK" {
err = fmt.Errorf("command failed: %s", res)
}
return err
}

// Enable implements the api.Charger interface
func (c Trydan) Enable(enable bool) error {
var pause int
if !enable {
pause = 1
}

if err := c.setValue("Paused", pause); err != nil {
return err
}
if err := c.setValue("Locked", pause); err != nil {
return err
}
c.enabled = enable

return nil
}

// MaxCurrent implements the api.Charger interface
func (c Trydan) MaxCurrent(current int64) error {
err := c.setValue("Intensity", int(current))
if err == nil {
c.current = int(current)
}
return err
}

var _ api.ChargeRater = (*Trydan)(nil)

// ChargedEnergy implements the api.ChargeRater interface
func (c Trydan) ChargedEnergy() (float64, error) {
data, err := c.statusG.Get()
return data.ChargeEnergy, err
}

var _ api.ChargeTimer = (*Trydan)(nil)

// ChargeDuration implements the api.ChargeTimer interface
func (c Trydan) ChargeDuration() (time.Duration, error) {
data, err := c.statusG.Get()
return time.Duration(data.ChargeTime) * time.Second, err
}

var _ api.Meter = (*Trydan)(nil)

// CurrentPower implements the api.Meter interface
func (c Trydan) CurrentPower() (float64, error) {
data, err := c.statusG.Get()
return data.ChargePower, err
}

var _ api.Diagnosis = (*Trydan)(nil)

// Diagnose implements the api.Diagnosis interface
func (c *Trydan) Diagnose() {
data, err := c.statusG.Get()
if err != nil {
fmt.Printf("%#v", data)
}
}
11 changes: 11 additions & 0 deletions templates/definition/charger/trydan.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
template: trydan
products:
- brand: V2C Trydan
requirements:
description:
en: V2C trydan
params:
- name: host
render: |
type: trydan
uri: http://{{ .host }}
Loading