Add Tinkerforge WARP WebSocket API (BC) (#26970)
This commit is contained in:
parent
f3de6d63f2
commit
e707282a01
11 changed files with 1911 additions and 65 deletions
516
charger/warp-ws.go
Normal file
516
charger/warp-ws.go
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
package charger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/coder/websocket"
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/warp"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/jpfielding/go-http-digest/pkg/digest"
|
||||
)
|
||||
|
||||
type WarpWS struct {
|
||||
*request.Helper
|
||||
|
||||
// config
|
||||
emHelper *request.Helper
|
||||
log *util.Logger
|
||||
uri string
|
||||
emURI string
|
||||
meterIndex uint
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
// capabilities
|
||||
features []string
|
||||
|
||||
// evse
|
||||
evse warp.Evse
|
||||
maxCurrent int64 // input from evcc
|
||||
|
||||
// meter
|
||||
meter warp.MeterValues
|
||||
meterMap map[int]int
|
||||
metersValueIDsTopic string
|
||||
metersValuesTopic string
|
||||
|
||||
// nfc
|
||||
chargeTracker warp.ChargeTrackerCurrentCharge
|
||||
|
||||
// power manager
|
||||
pmState warp.PmState
|
||||
pmLowLevelState warp.PmLowLevelState
|
||||
}
|
||||
|
||||
type warpEvent struct {
|
||||
Topic string `json:"topic"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
var _ api.ChargerEx = (*WarpWS)(nil)
|
||||
|
||||
func init() {
|
||||
registry.AddCtx("warp-ws", NewWarpWSFromConfig)
|
||||
}
|
||||
|
||||
//go:generate go tool decorate -f decorateWarpWS -b *WarpWS -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.PhaseGetter,GetPhases,func() (int, error)"
|
||||
|
||||
func NewWarpWSFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
|
||||
var cc struct {
|
||||
URI string
|
||||
User string
|
||||
Password string
|
||||
EnergyManagerURI string
|
||||
EnergyManagerUser string
|
||||
EnergyManagerPassword string
|
||||
DisablePhaseAutoSwitch bool
|
||||
EnergyMeterIndex uint
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wb, err := NewWarpWS(ctx, cc.URI, cc.User, cc.Password, cc.EnergyMeterIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Feature: Meter -> Meter is legacy API, Meters is the new API
|
||||
var currentPower, totalEnergy func() (float64, error)
|
||||
if wb.hasFeature(warp.FeatureMeter) || wb.hasFeature(warp.FeatureMeters) {
|
||||
currentPower = wb.currentPower
|
||||
totalEnergy = wb.totalEnergy
|
||||
}
|
||||
|
||||
// Feature: Phases
|
||||
var currents, voltages func() (float64, float64, float64, error)
|
||||
if wb.hasFeature(warp.FeatureMeters) || wb.hasFeature(warp.FeatureMeterAllValues) {
|
||||
currents = wb.currents
|
||||
voltages = wb.voltages
|
||||
}
|
||||
|
||||
if wb.hasFeature(warp.FeaturePhaseSwitch) && wb.emURI == "" {
|
||||
wb.emURI = wb.uri
|
||||
wb.emHelper = wb.Helper
|
||||
} else if cc.EnergyManagerURI != "" { // fallback to Energy Manager
|
||||
wb.emURI, err = parseURI(cc.EnergyManagerURI, false)
|
||||
if wb.emURI == "" {
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
wb.log.DEBUG.Println(err)
|
||||
}
|
||||
wb.emHelper = request.NewHelper(wb.log)
|
||||
if cc.EnergyManagerUser != "" {
|
||||
wb.emHelper.Client.Transport = digest.NewTransport(cc.EnergyManagerUser, cc.EnergyManagerPassword, wb.emHelper.Client.Transport)
|
||||
}
|
||||
}
|
||||
|
||||
// Feature: NFC
|
||||
var identify func() (string, error)
|
||||
if wb.hasFeature(warp.FeatureNfc) {
|
||||
identify = wb.identify
|
||||
}
|
||||
|
||||
// Feature: EM
|
||||
var phases func(int) error
|
||||
var getPhases func() (int, error)
|
||||
if wb.pmState.ExternalControl != 1 {
|
||||
phases = wb.phases1p3p
|
||||
getPhases = wb.getPhases
|
||||
}
|
||||
|
||||
// Phase Auto Switching needs to be disabled for WARP3
|
||||
// Necessary if charging 1p only vehicles
|
||||
if cc.DisablePhaseAutoSwitch {
|
||||
// unfortunately no feature to check for, instead this is set in template
|
||||
if err := wb.disablePhaseAutoSwitch(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wb.log.TRACE.Println("disabled phase auto switching")
|
||||
}
|
||||
|
||||
return decorateWarpWS(wb, currentPower, totalEnergy, currents, voltages, identify, phases, getPhases), nil
|
||||
}
|
||||
|
||||
func NewWarpWS(ctx context.Context, uri, user, password string, meterIndex uint) (*WarpWS, error) {
|
||||
log := util.NewLogger("warp-ws")
|
||||
|
||||
client := request.NewHelper(log)
|
||||
if user != "" {
|
||||
client.Client.Transport = digest.NewTransport(user, password, client.Client.Transport)
|
||||
}
|
||||
|
||||
w := &WarpWS{
|
||||
Helper: client, log: log,
|
||||
uri: util.DefaultScheme(uri, "http"),
|
||||
meterIndex: meterIndex,
|
||||
meterMap: map[int]int{},
|
||||
metersValueIDsTopic: fmt.Sprintf("meters/%d/value_ids", meterIndex),
|
||||
metersValuesTopic: fmt.Sprintf("meters/%d/values", meterIndex),
|
||||
}
|
||||
|
||||
go w.run(ctx)
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) run(ctx context.Context) {
|
||||
uri, err := parseURI(w.uri, true)
|
||||
if err != nil {
|
||||
w.log.DEBUG.Println(err)
|
||||
if uri == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
w.log.TRACE.Printf("connecting to %s …", uri)
|
||||
|
||||
bo := backoff.NewExponentialBackOff(backoff.WithMaxElapsedTime(0))
|
||||
for ctx.Err() == nil {
|
||||
conn, _, err := websocket.Dial(ctx, uri, nil)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(bo.NextBackOff())
|
||||
continue
|
||||
}
|
||||
|
||||
bo.Reset()
|
||||
if err := w.handleConnection(ctx, conn); err != nil {
|
||||
w.log.ERROR.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseURI(uri string, toWS bool) (string, error) {
|
||||
u, err := url.Parse(util.DefaultScheme(strings.TrimRight(uri, "/"), "http"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Scheme == "https" || u.Scheme == "wss" {
|
||||
u.Scheme = "http"
|
||||
err = fmt.Errorf("https or wss are not supported, using http/ws instead")
|
||||
}
|
||||
if toWS {
|
||||
u.Scheme = "ws"
|
||||
u.Path = path.Join(u.Path, "/ws")
|
||||
}
|
||||
return u.String(), err
|
||||
}
|
||||
|
||||
func (w *WarpWS) handleConnection(ctx context.Context, conn *websocket.Conn) error {
|
||||
defer conn.Close(websocket.StatusInternalError, "reconnect")
|
||||
for {
|
||||
msgType, r, err := conn.Reader(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msgType != websocket.MessageText {
|
||||
continue // next frame
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(r)
|
||||
for {
|
||||
var event warpEvent
|
||||
if err := dec.Decode(&event); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break //next frame
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.handleEvent(event.Topic, event.Payload); err != nil {
|
||||
w.log.ERROR.Printf("bad payload for topic %s: %v", event.Topic, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WarpWS) handleEvent(topic string, payload json.RawMessage) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var err error
|
||||
switch topic {
|
||||
case "charge_tracker/current_charge":
|
||||
err = json.Unmarshal(payload, &w.chargeTracker)
|
||||
case "evse/external_current":
|
||||
err = json.Unmarshal(payload, &w.evse.ExternalCurrent)
|
||||
case "evse/user_current":
|
||||
err = json.Unmarshal(payload, &w.evse.UserCurrent)
|
||||
case "evse/user_enabled":
|
||||
err = json.Unmarshal(payload, &w.evse.UserEnabled)
|
||||
case "evse/state":
|
||||
err = json.Unmarshal(payload, &w.evse.State)
|
||||
case "meter/all_values":
|
||||
if !slices.Contains(w.features, warp.FeatureMeters) {
|
||||
return nil
|
||||
}
|
||||
err = json.Unmarshal(payload, &w.meter.TmpValues)
|
||||
if len(w.meter.TmpValues) > 5 {
|
||||
copy(w.meter.Voltages[:], w.meter.TmpValues[:3])
|
||||
copy(w.meter.Currents[:], w.meter.TmpValues[3:6])
|
||||
}
|
||||
case "meter/values":
|
||||
if !slices.Contains(w.features, warp.FeatureMeters) {
|
||||
return nil
|
||||
}
|
||||
err = json.Unmarshal(payload, &w.meter)
|
||||
case w.metersValueIDsTopic:
|
||||
var ids []int
|
||||
if err = json.Unmarshal(payload, &ids); err != nil {
|
||||
return err
|
||||
}
|
||||
w.meterMap = make(map[int]int, len(ids))
|
||||
for i, id := range ids {
|
||||
w.meterMap[id] = i
|
||||
}
|
||||
case w.metersValuesTopic:
|
||||
if err := json.Unmarshal(payload, &w.meter.TmpValues); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := func(id int) (float64, bool) {
|
||||
if idx, ok := w.meterMap[id]; ok && idx < len(w.meter.TmpValues) {
|
||||
return w.meter.TmpValues[idx], true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
s := warp.DefaultSchema
|
||||
if v, ok := get(s.PowerID); ok {
|
||||
w.meter.Power = v
|
||||
}
|
||||
if v, ok := get(s.EnergyAbsID); ok {
|
||||
w.meter.EnergyAbs = v
|
||||
}
|
||||
for p, ids := range s.Phases {
|
||||
if v, ok := get(ids.CurrentID); ok {
|
||||
w.meter.Currents[p] = v
|
||||
}
|
||||
if v, ok := get(ids.VoltageID); ok {
|
||||
w.meter.Voltages[p] = v
|
||||
}
|
||||
}
|
||||
case "power_manager/state":
|
||||
err = json.Unmarshal(payload, &w.pmState)
|
||||
case "power_manager/low_level_state":
|
||||
err = json.Unmarshal(payload, &w.pmLowLevelState)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *WarpWS) hasFeature(feature string) bool {
|
||||
w.mu.RLock()
|
||||
if w.features != nil {
|
||||
w.mu.RUnlock()
|
||||
return slices.Contains(w.features, feature)
|
||||
}
|
||||
uri := fmt.Sprintf("%s/info/features", w.uri)
|
||||
w.mu.RUnlock()
|
||||
|
||||
var f []string
|
||||
if err := w.GetJSON(uri, &f); err == nil {
|
||||
w.mu.Lock()
|
||||
w.features = f
|
||||
w.mu.Unlock()
|
||||
return slices.Contains(f, feature)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WarpWS) Enable(enable bool) error {
|
||||
var curr int64
|
||||
if enable {
|
||||
w.mu.RLock()
|
||||
curr = w.maxCurrent
|
||||
w.mu.RUnlock()
|
||||
}
|
||||
return w.setCurrent(curr)
|
||||
}
|
||||
|
||||
func (w *WarpWS) Enabled() (bool, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.evse.ExternalCurrent.Current > 0, nil
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (w *WarpWS) MaxCurrent(current int64) error {
|
||||
return w.MaxCurrentMillis(float64(current))
|
||||
}
|
||||
|
||||
// MaxCurrentMillis implements the api.ChargerEx interface
|
||||
func (w *WarpWS) MaxCurrentMillis(current float64) error {
|
||||
curr := int64(current * 1e3)
|
||||
err := w.setCurrent(curr)
|
||||
if err == nil {
|
||||
w.mu.Lock()
|
||||
w.maxCurrent = curr
|
||||
w.mu.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *WarpWS) statusFromEvseStatus(state int) (api.ChargeStatus, error) {
|
||||
if state < 3 {
|
||||
return []api.ChargeStatus{api.StatusA, api.StatusB, api.StatusC}[state], nil
|
||||
}
|
||||
return api.StatusNone, fmt.Errorf("unknown evse status: %d", state)
|
||||
}
|
||||
|
||||
func (w *WarpWS) Status() (api.ChargeStatus, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.statusFromEvseStatus(w.evse.State.Iec61851State)
|
||||
}
|
||||
|
||||
func (w *WarpWS) StatusReason() (api.Reason, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
if status, err := w.statusFromEvseStatus(w.evse.State.Iec61851State); err != nil {
|
||||
return api.ReasonUnknown, err
|
||||
} else if status == api.StatusB && w.evse.UserEnabled.Enabled && w.evse.UserCurrent.Current == 0 {
|
||||
return api.ReasonWaitingForAuthorization, nil
|
||||
}
|
||||
return api.ReasonUnknown, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) currentPower() (float64, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.meter.Power, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) totalEnergy() (float64, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.meter.EnergyAbs, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) currents() (float64, float64, float64, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.meter.Currents[0], w.meter.Currents[1], w.meter.Currents[2], nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) voltages() (float64, float64, float64, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.meter.Voltages[0], w.meter.Voltages[1], w.meter.Voltages[2], nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) identify() (string, error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.chargeTracker.AuthorizationInfo.TagId, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) setCurrent(curr int64) error {
|
||||
uri := fmt.Sprintf("%s/evse/external_current", w.uri)
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(map[string]int64{"current": curr}), request.JSONEncoding)
|
||||
_, err := w.Do(req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *WarpWS) disablePhaseAutoSwitch() error {
|
||||
uri := fmt.Sprintf("%s/evse/phase_auto_switch", w.uri)
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(map[string]bool{"enabled": false}), request.JSONEncoding)
|
||||
_, err := w.Do(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// phases1p3p implements the api.PhaseSwitcher interface
|
||||
func (w *WarpWS) phases1p3p(phases int) error {
|
||||
if ec, err := w.ensurePmState(); err != nil || ec.ExternalControl > warp.ExternalControlAvailable {
|
||||
return fmt.Errorf("external control not available: %d", ec.ExternalControl)
|
||||
}
|
||||
w.mu.RLock()
|
||||
em := w.emHelper
|
||||
uri := fmt.Sprintf("%s/power_manager/external_control", w.emURI)
|
||||
w.mu.RUnlock()
|
||||
|
||||
req, _ := request.New(http.MethodPost, uri, request.MarshalJSON(map[string]int{"phases_wanted": phases}), request.JSONEncoding)
|
||||
|
||||
if em != nil {
|
||||
_, err := em.Do(req)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := w.Do(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// getPhases implements the api.PhaseGetter interface
|
||||
func (w *WarpWS) getPhases() (int, error) {
|
||||
s, err := w.ensurePmLowLevelState()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if s.Is3phase {
|
||||
return 3, nil
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) ensurePmLowLevelState() (warp.PmLowLevelState, error) {
|
||||
w.mu.RLock()
|
||||
s := w.pmLowLevelState
|
||||
em := w.emHelper
|
||||
uri := w.emURI
|
||||
w.mu.RUnlock()
|
||||
if em == nil || uri == "" {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var ns warp.PmLowLevelState
|
||||
if err := em.GetJSON(fmt.Sprintf("%s/power_manager/low_level_state", uri), &ns); err != nil {
|
||||
return warp.PmLowLevelState{}, err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.pmLowLevelState = ns
|
||||
w.mu.Unlock()
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (w *WarpWS) ensurePmState() (warp.PmState, error) {
|
||||
w.mu.RLock()
|
||||
s := w.pmState
|
||||
em := w.emHelper
|
||||
uri := w.emURI
|
||||
w.mu.RUnlock()
|
||||
|
||||
if em == nil || uri == "" || s.ExternalControl != 0 {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var ns warp.PmState
|
||||
if err := em.GetJSON(fmt.Sprintf("%s/power_manager/state", uri), &ns); err != nil {
|
||||
return warp.PmState{}, err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.pmState = ns
|
||||
w.mu.Unlock()
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
1247
charger/warp-ws_decorators.go
Normal file
1247
charger/warp-ws_decorators.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,91 +2,71 @@ package warp
|
|||
|
||||
const (
|
||||
FeatureMeter = "meter"
|
||||
FeatureMeters = "meters"
|
||||
FeatureMeterAllValues = "meter_all_values"
|
||||
FeatureMeterPhases = "meter_phases"
|
||||
FeatureNfc = "nfc"
|
||||
FeaturePhaseSwitch = "phase_switch"
|
||||
)
|
||||
|
||||
// https://www.warp-charger.com/api.html#evse_state
|
||||
type EvseState struct {
|
||||
Iec61851State int `json:"iec61851_state"`
|
||||
ChargerState int `json:"charger_state"`
|
||||
ContactorState int `json:"contactor_state"`
|
||||
ContactorError int `json:"contactor_error"`
|
||||
AllowedChargingCurrent int64 `json:"allowed_charging_current"`
|
||||
ErrorState int `json:"error_state"`
|
||||
LockState int `json:"lock_state"`
|
||||
Iec61851State int `json:"iec61851_state"`
|
||||
}
|
||||
|
||||
type EvseExternalCurrent struct {
|
||||
Current int `json:"current"`
|
||||
}
|
||||
|
||||
// https://www.warp-charger.com/api.html#evse_low_level_state
|
||||
type LowLevelState struct {
|
||||
TimeSinceStateChange int64 `json:"time_since_state_change"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
LedState int `json:"led_state"`
|
||||
CpPwmDutyCycle int `json:"cp_pwm_duty_cycle"`
|
||||
AdcValues []int `json:"adc_values"`
|
||||
Voltages []int
|
||||
Resistances []int
|
||||
Gpio []bool
|
||||
type EvseUserEnabled struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// https://www.warp-charger.com/api.html#meter_state
|
||||
type MeterState struct {
|
||||
State int `json:"state"` // Warp 1 only
|
||||
type Evse struct {
|
||||
State EvseState
|
||||
ExternalCurrent EvseExternalCurrent
|
||||
UserCurrent EvseExternalCurrent
|
||||
UserEnabled EvseUserEnabled
|
||||
}
|
||||
|
||||
// https://www.warp-charger.com/api.html#meter_values
|
||||
type MeterValues struct {
|
||||
Power float64 `json:"power"`
|
||||
EnergyRel float64 `json:"energy_rel"`
|
||||
EnergyAbs float64 `json:"energy_abs"`
|
||||
PhasesActive []bool `json:"phases_active"`
|
||||
PhasesConnected []bool `json:"phases_connected"`
|
||||
Power float64 `json:"power"`
|
||||
EnergyRel float64 `json:"energy_rel"`
|
||||
EnergyAbs float64 `json:"energy_abs"`
|
||||
Currents [3]float64
|
||||
Voltages [3]float64
|
||||
TmpValues []float64
|
||||
}
|
||||
|
||||
// https://www.warp-charger.com/api.html#meter_all_values
|
||||
type MeterAllValues struct {
|
||||
PhasesActive []bool `json:"phases_active"`
|
||||
PhasesConnected []bool `json:"phases_connected"`
|
||||
type PhasePair struct {
|
||||
CurrentID int
|
||||
VoltageID int
|
||||
}
|
||||
|
||||
type UsersConfig struct {
|
||||
Users []User `json:"users"`
|
||||
type MeterSchema struct {
|
||||
PowerID int
|
||||
EnergyAbsID int
|
||||
Phases [3]PhasePair
|
||||
}
|
||||
|
||||
// Value IDs based on Tinkerforge's meter_value_id.csv
|
||||
var DefaultSchema = MeterSchema{
|
||||
PowerID: 74, // Power Im-Ex Sum L1 L2 L3
|
||||
EnergyAbsID: 209, // Energy Im Sum L1 L2 L3
|
||||
Phases: [3]PhasePair{
|
||||
{CurrentID: 13, VoltageID: 1}, // Current L1 Im-Ex Sum, Voltage L1-N
|
||||
{CurrentID: 17, VoltageID: 2}, // Current L2 Im-Ex Sum, Voltage L2-N
|
||||
{CurrentID: 21, VoltageID: 3}, // Current L3 Im-Ex Sum, Voltage L3-N
|
||||
},
|
||||
}
|
||||
|
||||
type ChargeTrackerCurrentCharge struct {
|
||||
UserID int `json:"user_id"`
|
||||
MeterStart float64 `json:"meter_start"`
|
||||
AuthorizationType int `json:"authorization_type"`
|
||||
AuthorizationInfo struct {
|
||||
TagType int `json:"tag_type"`
|
||||
TagId string `json:"tag_id"`
|
||||
} `json:"authorization_info"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Roles int `json:"roles"`
|
||||
Current int `json:"current"`
|
||||
DisplayName string `json:"display_name"`
|
||||
UserName string `json:"username"`
|
||||
}
|
||||
|
||||
type LastNfcTag struct {
|
||||
UserID int `json:"user_id"`
|
||||
Type int `json:"tag_type"`
|
||||
ID string `json:"tag_id"`
|
||||
}
|
||||
|
||||
type EmConfig struct {
|
||||
ContactorInstalled bool `json:"contactor_installed"`
|
||||
PhaseSwitchingMode int `json:"phase_switching_mode"`
|
||||
}
|
||||
|
||||
//go:generate go tool enumer -type ExternalControl -trimprefix ExternalControl -transform whitespace
|
||||
type ExternalControl int
|
||||
|
||||
|
|
@ -97,15 +77,10 @@ const (
|
|||
ExternalControlCurrentlySwitching
|
||||
)
|
||||
|
||||
type EmState struct {
|
||||
type PmState struct {
|
||||
ExternalControl ExternalControl `json:"external_control"`
|
||||
PhasesSwitched int `json:"phases_switched"`
|
||||
Input3State bool `json:"input3_state"`
|
||||
Input4State bool `json:"input4_state"`
|
||||
RelayState bool `json:"relay_state"`
|
||||
ErrorFlags int `json:"error_flags"`
|
||||
}
|
||||
|
||||
type EmLowLevelState struct {
|
||||
type PmLowLevelState struct {
|
||||
Is3phase bool `json:"is_3phase"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,14 +290,14 @@ func (wb *Warp2) identify() (string, error) {
|
|||
return res.AuthorizationInfo.TagId, err
|
||||
}
|
||||
|
||||
func (wb *Warp2) emState() (warp.EmState, error) {
|
||||
var res warp.EmState
|
||||
func (wb *Warp2) emState() (warp.PmState, error) {
|
||||
var res warp.PmState
|
||||
err := wb.emStateG(&res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (wb *Warp2) emLowLevelState() (warp.EmLowLevelState, error) {
|
||||
var res warp.EmLowLevelState
|
||||
func (wb *Warp2) emLowLevelState() (warp.PmLowLevelState, error) {
|
||||
var res warp.PmLowLevelState
|
||||
err := wb.emLowLevelG(&res)
|
||||
return res, err
|
||||
}
|
||||
32
templates/definition/charger/tinkerforge-warp-ws.yaml
Normal file
32
templates/definition/charger/tinkerforge-warp-ws.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
template: tinkerforge-warp3-ws
|
||||
products:
|
||||
- { brand: TinkerForge, description: { generic: WARP2 Smart } }
|
||||
- { brand: TinkerForge, description: { generic: WARP2 Pro } }
|
||||
- { brand: TinkerForge, description: { generic: WARP3 Smart } }
|
||||
- { brand: TinkerForge, description: { generic: WARP3 Pro } }
|
||||
capabilities: ["mA", "1p3p", "rfid"]
|
||||
requirements:
|
||||
description:
|
||||
de: Die automatische Phasenumschaltung bei 1p Fahrzeugen muss deaktiviert sein. Siehe [docs.warp-charger.com](https://docs.warp-charger.com/docs/mqtt_http/api_reference/evse#evse_phase_auto_switch_warp3).
|
||||
en: The automatic phase switching for 1p vehicles must be deactivated. See [docs.warp-charger.com](https://docs.warp-charger.com/docs/mqtt_http/api_reference/evse#evse_phase_auto_switch_warp3).
|
||||
evcc: ["skiptest"]
|
||||
params:
|
||||
- name: uri
|
||||
required: true
|
||||
- name: user
|
||||
- name: password
|
||||
- name: energyMeterIndex
|
||||
advanced: true
|
||||
default: 0
|
||||
description:
|
||||
generic: Energy Meter Index
|
||||
help:
|
||||
de: Index des in der Warp konfigurierten Meters dessen Werte genutzt werden sollen.
|
||||
en: Index of the meter configured in the WARP charger whose values should be used.
|
||||
render: |
|
||||
type: warp-ws
|
||||
uri: {{ .uri }}
|
||||
user: {{ .user }}
|
||||
password: {{ .password }}
|
||||
energyMeterIndex: {{ .energyMeterIndex }}
|
||||
disablePhaseAutoSwitch: true
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
template: tinkerforge-warp
|
||||
deprecated: true
|
||||
covers:
|
||||
- tinkerforge-warp-pro
|
||||
products:
|
||||
|
|
|
|||
29
templates/definition/charger/tinkerforge-warp1-ws.yaml
Normal file
29
templates/definition/charger/tinkerforge-warp1-ws.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
template: tinkerforge-warp-ws
|
||||
products:
|
||||
- { brand: TinkerForge, description: { generic: WARP1 Smart } }
|
||||
- { brand: TinkerForge, description: { generic: WARP1 Pro } }
|
||||
capabilities: ["mA", "1p3p", "rfid"]
|
||||
requirements:
|
||||
description:
|
||||
de: Die automatische Phasenumschaltung bei 1p Fahrzeugen muss deaktiviert sein. Siehe [docs.warp-charger.com](https://docs.warp-charger.com/docs/mqtt_http/api_reference/evse#evse_phase_auto_switch_warp3).
|
||||
en: The automatic phase switching for 1p vehicles must be deactivated. See [docs.warp-charger.com](https://docs.warp-charger.com/docs/mqtt_http/api_reference/evse#evse_phase_auto_switch_warp3).
|
||||
evcc: ["skiptest"]
|
||||
params:
|
||||
- name: uri
|
||||
required: true
|
||||
- name: user
|
||||
- name: password
|
||||
- name: energyMeterIndex
|
||||
advanced: true
|
||||
default: 0
|
||||
description:
|
||||
generic: Energy Meter Index
|
||||
help:
|
||||
de: Index des in der Warp konfigurierten Meters dessen Werte genutzt werden sollen.
|
||||
en: Index of the meter configured in the WARP charger whose values should be used.
|
||||
render: |
|
||||
type: warp-ws
|
||||
uri: {{ .uri }}
|
||||
user: {{ .user }}
|
||||
password: {{ .password }}
|
||||
energyMeterIndex: {{ .energyMeterIndex }}
|
||||
44
templates/definition/charger/tinkerforge-warp2-ws.yaml
Normal file
44
templates/definition/charger/tinkerforge-warp2-ws.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
template: tinkerforge-warp2-ws
|
||||
products:
|
||||
- { brand: TinkerForge, description: { generic: WARP2 Smart + Energy Manager } }
|
||||
- { brand: TinkerForge, description: { generic: WARP2 Pro + Energy Manager } }
|
||||
capabilities: ["mA", "1p3p", "rfid"]
|
||||
requirements:
|
||||
description:
|
||||
en: Firmware v2 required. Automatic phase switching requires the additional WARP Energy Manager.
|
||||
de: Firmware v2 erforderlich. Für automatische Phasenumschaltung wird zusätzlich der WARP Energy Manager benötigt.
|
||||
evcc: ["skiptest"]
|
||||
params:
|
||||
- name: uri
|
||||
required: true
|
||||
- name: user
|
||||
- name: password
|
||||
- name: energyManagerUri
|
||||
description:
|
||||
generic: Energy Manager URI
|
||||
help:
|
||||
de: HTTP(S) Adresse des WARP Energy Manager
|
||||
en: HTTP(S) address of the WARP Energy Manager
|
||||
- name: energyManagerUser
|
||||
description:
|
||||
de: Energy Manager Benutzerkonto
|
||||
en: Energy Manager Username
|
||||
help:
|
||||
de: bspw. E-Mail Adresse, User Id, etc.
|
||||
en: e.g. email address, user id, etc.
|
||||
- name: energyManagerPassword
|
||||
description:
|
||||
de: Energy Manager Passwort
|
||||
en: Energy Manager Password
|
||||
help:
|
||||
de: Bei führenden Nullen bitte in einfache Hochkommata setzen
|
||||
en: Use single quotes in case of leading zeros
|
||||
render: |
|
||||
type: warp-ws
|
||||
uri: {{ .uri }}
|
||||
user: {{ .user }}
|
||||
password: {{ .password }}
|
||||
energyManagerUri: {{ .energyManagerUri }}
|
||||
energyManagerUser: {{ .energyManagerUser }}
|
||||
energyManagerPassword: {{ .energyManagerPassword }}
|
||||
disablePhaseAutoSwitch: true
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
template: tinkerforge-warp3-smart
|
||||
deprecated: true
|
||||
products:
|
||||
- brand: TinkerForge
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
template: tinkerforge-warp3
|
||||
deprecated: true
|
||||
products:
|
||||
- brand: TinkerForge
|
||||
description:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue