diff --git a/charger/bender.go b/charger/bender.go
index 0c1f24bccd..51a531fbef 100644
--- a/charger/bender.go
+++ b/charger/bender.go
@@ -43,25 +43,28 @@ type BenderCC struct {
const (
// all holding type registers
- bendRegChargePointState = 122 // Vehicle (Control Pilot) state
- bendRegPhaseEnergy = 200 // Phase energy from primary meter (Wh)
- bendRegCurrents = 212 // Currents from primary meter (mA)
- bendRegTotalEnergy = 218 // Total Energy from primary meter (Wh)
- bendRegActivePower = 220 // Active Power from primary meter (W)
- bendRegVoltages = 222 // Voltages of the ocpp meter (V)
- bendRegChargedEnergyLegacy = 705 // Sum of charged energy for the current session (Wh)
- bendRegChargingDurationLegacy = 709 // Duration since beginning of charge (Seconds)
- bendRegChargedEnergy = 716 // Sum of charged energy for the current session (Wh)
- bendRegChargingDuration = 718 // Duration since beginning of charge (Seconds)
- bendRegUserID = 720 // User ID (OCPP IdTag) from the current session. Bytes 0 to 19.
- bendRegEVCCID = 741 // ASCII representation of the Hex. Values corresponding to the EVCCID. Bytes 0 to 11.
- bendRegHemsCurrentLimit = 1000 // Current limit of the HEMS module (A)
+ bendRegChargePointState = 122 // Vehicle (Control Pilot) state
+ bendRegPhaseEnergy = 200 // Phase energy from primary meter (Wh)
+ bendRegCurrents = 212 // Currents from primary meter (mA)
+ bendRegTotalEnergy = 218 // Total Energy from primary meter (Wh)
+ bendRegActivePower = 220 // Active Power from primary meter (W)
+ bendRegVoltages = 222 // Voltages of the ocpp meter (V)
+ bendRegUserID = 720 // User ID (OCPP IdTag) from the current session. Bytes 0 to 19.
+ bendRegEVBatteryState = 730 // EV Battery State (% 0-100)
+ bendRegEVCCID = 741 // ASCII representation of the Hex. Values corresponding to the EVCCID. Bytes 0 to 11.
+ bendRegHemsCurrentLimit = 1000 // Current limit of the HEMS module (A)
bendRegFirmware = 100 // Application version number
bendRegOcppCpStatus = 104 // Charge Point status according to the OCPP spec. enumaration
bendRegProtocolVersion = 120 // Ebee Modbus TCP Server Protocol Version number
bendRegChargePointModel = 142 // ChargePoint Model. Bytes 0 to 19.
bendRegSmartVehicleDetected = 740 // Returns 1 if an EV currently connected is a smart vehicle, or 0 if no EV connected or it is not a smart vehicle
+
+ // unused
+ // bendRegChargedEnergyLegacy = 705 // Sum of charged energy for the current session (Wh)
+ // bendRegChargingDurationLegacy = 709 // Duration since beginning of charge (Seconds)
+ // bendRegChargedEnergy = 716 // Sum of charged energy for the current session (Wh)
+ // bendRegChargingDuration = 718 // Duration since beginning of charge (Seconds)
)
func init() {
@@ -81,7 +84,7 @@ func NewBenderCCFromConfig(other map[string]interface{}) (api.Charger, error) {
return NewBenderCC(cc.URI, cc.ID)
}
-//go:generate go run ../cmd/tools/decorate.go -f decorateBenderCC -b *BenderCC -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.ChargeRater,ChargedEnergy,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Identifier,Identify,func() (string, error)"
+//go:generate go run ../cmd/tools/decorate.go -f decorateBenderCC -b *BenderCC -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.Identifier,Identify,func() (string, error)"
// NewBenderCC creates BenderCC charger
func NewBenderCC(uri string, id uint8) (api.Charger, error) {
@@ -108,12 +111,12 @@ func NewBenderCC(uri string, id uint8) (api.Charger, error) {
}
var (
- currentPower func() (float64, error)
- currents func() (float64, float64, float64, error)
- voltages func() (float64, float64, float64, error)
- chargedEnergy func() (float64, error)
- totalEnergy func() (float64, error)
- identify func() (string, error)
+ currentPower func() (float64, error)
+ currents func() (float64, float64, float64, error)
+ voltages func() (float64, float64, float64, error)
+ totalEnergy func() (float64, error)
+ soc func() (float64, error)
+ identify func() (string, error)
)
// check presence of metering
@@ -125,13 +128,18 @@ func NewBenderCC(uri string, id uint8) (api.Charger, error) {
if b, err := wb.conn.ReadHoldingRegisters(reg, 2); err == nil && binary.BigEndian.Uint32(b) != math.MaxUint32 {
currentPower = wb.currentPower
currents = wb.currents
- chargedEnergy = wb.chargedEnergy
totalEnergy = wb.totalEnergy
// check presence of "ocpp meter"
if b, err := wb.conn.ReadHoldingRegisters(bendRegVoltages, 2); err == nil && binary.BigEndian.Uint32(b) > 0 {
voltages = wb.voltages
}
+
+ if !wb.legacy {
+ if _, err := wb.conn.ReadHoldingRegisters(bendRegEVBatteryState, 1); err == nil {
+ soc = wb.soc
+ }
+ }
}
// check rfid
@@ -139,7 +147,7 @@ func NewBenderCC(uri string, id uint8) (api.Charger, error) {
identify = wb.identify
}
- return decorateBenderCC(wb, currentPower, currents, voltages, chargedEnergy, totalEnergy, identify), nil
+ return decorateBenderCC(wb, currentPower, currents, voltages, totalEnergy, soc, identify), nil
}
// Status implements the api.Charger interface
@@ -218,24 +226,8 @@ func (wb *BenderCC) currentPower() (float64, error) {
return float64(binary.BigEndian.Uint32(b)), nil
}
-// ChargedEnergy implements the api.ChargeRater interface
-func (wb *BenderCC) chargedEnergy() (float64, error) {
- if wb.legacy {
- b, err := wb.conn.ReadHoldingRegisters(bendRegChargedEnergyLegacy, 1)
- if err != nil {
- return 0, err
- }
-
- return float64(binary.BigEndian.Uint16(b)) / 1e3, nil
- }
-
- b, err := wb.conn.ReadHoldingRegisters(bendRegChargedEnergy, 2)
- if err != nil {
- return 0, err
- }
-
- return float64(binary.BigEndian.Uint32(b)) / 1e3, nil
-}
+// removed: https://github.com/evcc-io/evcc/issues/13726
+// var _ api.ChargeRater = (*BenderCC)(nil)
// TotalEnergy implements the api.MeterEnergy interface
func (wb *BenderCC) totalEnergy() (float64, error) {
@@ -311,6 +303,26 @@ func (wb *BenderCC) identify() (string, error) {
return bytesAsString(b), nil
}
+// soc implements the api.Battery interface
+func (wb *BenderCC) soc() (float64, error) {
+ b, err := wb.conn.ReadHoldingRegisters(bendRegSmartVehicleDetected, 1)
+ if err != nil {
+ return 0, err
+ }
+
+ if binary.BigEndian.Uint16(b) == 1 {
+ b, err = wb.conn.ReadHoldingRegisters(bendRegEVBatteryState, 1)
+ if err != nil {
+ return 0, err
+ }
+ if soc := binary.BigEndian.Uint16(b); soc <= 100 {
+ return float64(soc), nil
+ }
+ }
+
+ return 0, api.ErrNotAvailable
+}
+
var _ api.Diagnosis = (*BenderCC)(nil)
// Diagnose implements the api.Diagnosis interface
diff --git a/charger/bender_decorators.go b/charger/bender_decorators.go
index c6f11d9365..86097d84fb 100644
--- a/charger/bender_decorators.go
+++ b/charger/bender_decorators.go
@@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
-func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), chargeRater func() (float64, error), meterEnergy func() (float64, error), identifier func() (string, error)) api.Charger {
+func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), meterEnergy func() (float64, error), battery func() (float64, error), identifier func() (string, error)) api.Charger {
switch {
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return base
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Meter
@@ -22,7 +22,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.PhaseCurrents
@@ -33,7 +33,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Meter
@@ -48,7 +48,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.PhaseVoltages
@@ -59,7 +59,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Meter
@@ -74,7 +74,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.PhaseCurrents
@@ -89,7 +89,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Meter
@@ -108,110 +108,110 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Meter
+ api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Meter
+ api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
},
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Meter
+ api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
@@ -221,21 +221,21 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Meter
+ api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -244,110 +244,110 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.MeterEnergy
+ api.Battery
}{
BenderCC: base,
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Meter
- api.MeterEnergy
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.MeterEnergy
+ api.Battery
api.PhaseCurrents
}{
BenderCC: base,
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Meter
- api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.MeterEnergy
+ api.Battery
api.PhaseVoltages
}{
BenderCC: base,
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Meter
- api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.MeterEnergy
+ api.Battery
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
@@ -357,21 +357,21 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Meter
- api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -380,31 +380,31 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
MeterEnergy: &decorateBenderCCMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Meter
api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
@@ -414,16 +414,16 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
MeterEnergy: &decorateBenderCCMeterEnergyImpl{
meterEnergy: meterEnergy,
@@ -433,17 +433,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
@@ -456,16 +456,16 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
MeterEnergy: &decorateBenderCCMeterEnergyImpl{
meterEnergy: meterEnergy,
@@ -475,17 +475,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Meter
api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
@@ -498,17 +498,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
MeterEnergy: &decorateBenderCCMeterEnergyImpl{
meterEnergy: meterEnergy,
@@ -521,18 +521,18 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
@@ -548,7 +548,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Identifier
@@ -559,7 +559,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Identifier
@@ -574,7 +574,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Identifier
@@ -589,7 +589,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
api.Identifier
@@ -608,7 +608,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Identifier
@@ -623,7 +623,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Identifier
@@ -642,7 +642,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Identifier
@@ -661,7 +661,7 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
api.Identifier
@@ -684,139 +684,139 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
+ api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
api.Meter
+ api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
+ api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
api.Meter
+ api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
+ api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
api.Meter
+ api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
+ api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -825,25 +825,25 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
api.Identifier
api.Meter
+ api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
- },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
+ MeterEnergy: &decorateBenderCCMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -852,139 +852,139 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
- api.MeterEnergy
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
api.Meter
- api.MeterEnergy
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
- api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
api.Meter
- api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
- api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
api.Meter
- api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseVoltages: &decorateBenderCCPhaseVoltagesImpl{
phaseVoltages: phaseVoltages,
},
}
- case chargeRater == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
- api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -993,25 +993,25 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
+ api.Battery
api.Identifier
api.Meter
- api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
+ },
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
},
Meter: &decorateBenderCCMeterImpl{
meter: meter,
},
- MeterEnergy: &decorateBenderCCMeterEnergyImpl{
- meterEnergy: meterEnergy,
- },
PhaseCurrents: &decorateBenderCCPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
@@ -1020,16 +1020,16 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1039,17 +1039,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.Meter
api.MeterEnergy
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1062,17 +1062,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1085,18 +1085,18 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages == nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1112,17 +1112,17 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1135,18 +1135,18 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1162,18 +1162,18 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.MeterEnergy
api.PhaseCurrents
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1189,10 +1189,10 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
},
}
- case chargeRater != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
+ case battery != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseVoltages != nil:
return &struct {
*BenderCC
- api.ChargeRater
+ api.Battery
api.Identifier
api.Meter
api.MeterEnergy
@@ -1200,8 +1200,8 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
api.PhaseVoltages
}{
BenderCC: base,
- ChargeRater: &decorateBenderCCChargeRaterImpl{
- chargeRater: chargeRater,
+ Battery: &decorateBenderCCBatteryImpl{
+ battery: battery,
},
Identifier: &decorateBenderCCIdentifierImpl{
identifier: identifier,
@@ -1224,12 +1224,12 @@ func decorateBenderCC(base *BenderCC, meter func() (float64, error), phaseCurren
return nil
}
-type decorateBenderCCChargeRaterImpl struct {
- chargeRater func() (float64, error)
+type decorateBenderCCBatteryImpl struct {
+ battery func() (float64, error)
}
-func (impl *decorateBenderCCChargeRaterImpl) ChargedEnergy() (float64, error) {
- return impl.chargeRater()
+func (impl *decorateBenderCCBatteryImpl) Soc() (float64, error) {
+ return impl.battery()
}
type decorateBenderCCIdentifierImpl struct {
diff --git a/charger/charger.go b/charger/charger.go
index 3196ea8d91..070630921e 100644
--- a/charger/charger.go
+++ b/charger/charger.go
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/evcc-io/evcc/api"
+ "github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util"
)
@@ -22,7 +23,7 @@ func init() {
registry.Add(api.Custom, NewConfigurableFromConfig)
}
-//go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)"
+//go:generate go run ../cmd/tools/decorate.go -f decorateCustom -b *Charger -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Resurrector,WakeUp,func() error" -t "api.Battery,Soc,func() (float64, error)" -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)"
// NewConfigurableFromConfig creates a new configurable charger
func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error) {
@@ -34,6 +35,13 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error
Wakeup *provider.Config
Soc *provider.Config
Tos bool
+
+ // optional measurements
+ Power *provider.Config
+ Energy *provider.Config
+
+ // phase values, currently not supported (https://github.com/evcc-io/evcc/pull/14546)
+ // Currents, Voltages, Powers []provider.Config
}
if err := util.DecodeOther(other, &cc); err != nil {
@@ -123,7 +131,13 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error
}
}
- return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc), nil
+ // decorate measurements
+ powerG, energyG, err := meter.BuildMeasurements(cc.Power, cc.Energy)
+ if err != nil {
+ return nil, err
+ }
+
+ return decorateCustom(c, maxcurrentmillis, identify, phases1p3p, wakeup, soc, powerG, energyG), nil
}
// NewConfigurable creates a new charger
diff --git a/charger/charger_decorators.go b/charger/charger_decorators.go
index faf30c44c8..a2ecb36444 100644
--- a/charger/charger_decorators.go
+++ b/charger/charger_decorators.go
@@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
-func decorateCustom(base *Charger, chargerEx func(float64) error, identifier func() (string, error), phaseSwitcher func(int) error, resurrector func() error, battery func() (float64, error)) api.Charger {
+func decorateCustom(base *Charger, chargerEx func(float64) error, identifier func() (string, error), phaseSwitcher func(int) error, resurrector func() error, battery func() (float64, error), meter func() (float64, error), meterEnergy func() (float64, error)) api.Charger {
switch {
- case battery == nil && chargerEx == nil && identifier == nil && phaseSwitcher == nil && resurrector == nil:
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return base
- case battery == nil && chargerEx != nil && identifier == nil && phaseSwitcher == nil && resurrector == nil:
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.ChargerEx
@@ -22,7 +22,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier != nil && phaseSwitcher == nil && resurrector == nil:
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.Identifier
@@ -33,7 +33,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier != nil && phaseSwitcher == nil && resurrector == nil:
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.ChargerEx
@@ -48,7 +48,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier == nil && phaseSwitcher != nil && resurrector == nil:
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.PhaseSwitcher
@@ -59,7 +59,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier == nil && phaseSwitcher != nil && resurrector == nil:
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.ChargerEx
@@ -74,7 +74,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier != nil && phaseSwitcher != nil && resurrector == nil:
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.Identifier
@@ -89,7 +89,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier != nil && phaseSwitcher != nil && resurrector == nil:
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.ChargerEx
@@ -108,7 +108,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier == nil && phaseSwitcher == nil && resurrector != nil:
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Resurrector
@@ -119,7 +119,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier == nil && phaseSwitcher == nil && resurrector != nil:
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.ChargerEx
@@ -134,7 +134,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier != nil && phaseSwitcher == nil && resurrector != nil:
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Identifier
@@ -149,7 +149,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier != nil && phaseSwitcher == nil && resurrector != nil:
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.ChargerEx
@@ -168,7 +168,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier == nil && phaseSwitcher != nil && resurrector != nil:
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.PhaseSwitcher
@@ -183,7 +183,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier == nil && phaseSwitcher != nil && resurrector != nil:
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.ChargerEx
@@ -202,7 +202,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx == nil && identifier != nil && phaseSwitcher != nil && resurrector != nil:
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.Identifier
@@ -221,7 +221,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery == nil && chargerEx != nil && identifier != nil && phaseSwitcher != nil && resurrector != nil:
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.ChargerEx
@@ -244,7 +244,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier == nil && phaseSwitcher == nil && resurrector == nil:
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -255,7 +255,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier == nil && phaseSwitcher == nil && resurrector == nil:
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -270,7 +270,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier != nil && phaseSwitcher == nil && resurrector == nil:
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -285,7 +285,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier != nil && phaseSwitcher == nil && resurrector == nil:
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -304,7 +304,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier == nil && phaseSwitcher != nil && resurrector == nil:
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -319,7 +319,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier == nil && phaseSwitcher != nil && resurrector == nil:
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -338,7 +338,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier != nil && phaseSwitcher != nil && resurrector == nil:
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -357,7 +357,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier != nil && phaseSwitcher != nil && resurrector == nil:
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
return &struct {
*Charger
api.Battery
@@ -380,7 +380,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier == nil && phaseSwitcher == nil && resurrector != nil:
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -395,7 +395,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier == nil && phaseSwitcher == nil && resurrector != nil:
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -414,7 +414,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier != nil && phaseSwitcher == nil && resurrector != nil:
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -433,7 +433,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier != nil && phaseSwitcher == nil && resurrector != nil:
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -456,7 +456,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier == nil && phaseSwitcher != nil && resurrector != nil:
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -475,7 +475,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier == nil && phaseSwitcher != nil && resurrector != nil:
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -498,7 +498,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx == nil && identifier != nil && phaseSwitcher != nil && resurrector != nil:
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -521,7 +521,7 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
},
}
- case battery != nil && chargerEx != nil && identifier != nil && phaseSwitcher != nil && resurrector != nil:
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
return &struct {
*Charger
api.Battery
@@ -547,6 +547,2150 @@ func decorateCustom(base *Charger, chargerEx func(float64) error, identifier fun
resurrector: resurrector,
},
}
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Meter
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy == nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.MeterEnergy
+ }{
+ Charger: base,
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.MeterEnergy
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter == nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery == nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector == nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher == nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier == nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx == nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
+
+ case battery != nil && chargerEx != nil && identifier != nil && meter != nil && meterEnergy != nil && phaseSwitcher != nil && resurrector != nil:
+ return &struct {
+ *Charger
+ api.Battery
+ api.ChargerEx
+ api.Identifier
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.Resurrector
+ }{
+ Charger: base,
+ Battery: &decorateCustomBatteryImpl{
+ battery: battery,
+ },
+ ChargerEx: &decorateCustomChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ Identifier: &decorateCustomIdentifierImpl{
+ identifier: identifier,
+ },
+ Meter: &decorateCustomMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateCustomMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateCustomPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ Resurrector: &decorateCustomResurrectorImpl{
+ resurrector: resurrector,
+ },
+ }
}
return nil
@@ -576,6 +2720,22 @@ func (impl *decorateCustomIdentifierImpl) Identify() (string, error) {
return impl.identifier()
}
+type decorateCustomMeterImpl struct {
+ meter func() (float64, error)
+}
+
+func (impl *decorateCustomMeterImpl) CurrentPower() (float64, error) {
+ return impl.meter()
+}
+
+type decorateCustomMeterEnergyImpl struct {
+ meterEnergy func() (float64, error)
+}
+
+func (impl *decorateCustomMeterEnergyImpl) TotalEnergy() (float64, error) {
+ return impl.meterEnergy()
+}
+
type decorateCustomPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
diff --git a/charger/delta.go b/charger/delta.go
index cb97151015..68005f96d6 100644
--- a/charger/delta.go
+++ b/charger/delta.go
@@ -28,34 +28,32 @@ type Delta struct {
const (
// EV Charger
// Read Input Registers (0x04)
- deltaRegState = 100 // Charger State - UINT16 0: not ready, 1: operational, 10: faulted, 255: not responding
- deltaRegVersion = 101 // Charger Version - UINT16
- deltaRegCount = 102 // Charger EVSE Count - UINT16
- deltaRegError = 103 // Charger Error - UINT16
- deltaRegSerial = 110 // Charger Serial - STRING20
- deltaRegModel = 130 // Charger Model - STRING20
+ deltaRegState = 100 // Charger State - UINT16 0: not ready, 1: operational, 10: faulted, 255: not responding
+ deltaRegCount = 102 // Charger EVSE Count - UINT16
+ deltaRegSerial = 110 // Charger Serial - STRING20
+ deltaRegModel = 130 // Charger Model - STRING20
// Write Multiple Registers (0x10)
- deltaRegCommunicationTimeoutEnabled = 201 // Communication Timeout Enabled 0/1
- deltaRegCommunicationTimeout = 202 // Communication Timeout [s]
- deltaRegFallbackPower = 203 // Fallback Power [W]
+ deltaRegCommunicationTimeoutEnabled = 201 // Communication Timeout Enabled - UINT16 0: false, 1: true
+ deltaRegCommunicationTimeout = 202 // Communication Timeout - UINT16 [s]
+ deltaRegFallbackPower = 203 // Fallback Power - UINT32 [W]
// EVSE - The following Register tables are defined as repeating blocks for each single EVSE
// Read Input Registers (0x04)
deltaRegEvseState = 0 // EVSE State - UINT16 0: Unavailable, 1: Available, 2: Occupied, 3: Preparing, 4: Charging, 5: Finishing, 6: Suspended EV, 7: Suspended EVSE, 8: Not ready, 9: Faulted
- deltaRegEvseChargerState = 1 // EVSE Charger State - UINT16 0: Charging process not started (no vehicle connected), 1: Connected, waiting for release (by RFID or local), 2: Charging process starts, 3: Charging, 4: Suspended (paused), 5: Charging process successfully completed (vehicle still plugged in), 6: Charging process completed by user (vehicle still plugged in), 7: Charging ended with error (vehicle still connected)
- deltaRegEvseActualOutputVoltage = 3 // EVSE Actual Output Voltage - FLOAT32 [V]
+ deltaRegEvseChargerState = 1 // EVSE Charger State* - UINT16 0: Charging process not started (no vehicle connected), 1: Connected, waiting for release (by RFID or local), 2: Charging process starts, 3: Charging, 4: Suspended (paused), 5: Charging process successfully completed (vehicle still plugged in), 6: Charging process completed by user (vehicle still plugged in), 7: Charging ended with error (vehicle still connected)
+ deltaRegEvseActualOutputVoltage = 3 // EVSE Actual Output Voltage* - FLOAT32 [V]
deltaRegEvseActualChargingPower = 5 // EVSE Actual Charging Power - UINT32 [W]
- deltaRegEvseActualChargingCurrent = 7 // EVSE Actual Charging Current - FLOAT32 [A]
- deltaRegEvseActualOutputPower = 9 // EVSE Actual Output Power - FLOAT32 [W]
- deltaRegEvseSoc = 11 // EVSE SOC [%/10]
- deltaRegEvseChargingTime = 17 // EVSE Charging Time [s]
- deltaRegEvseChargedEnergy = 19 // EVSE Charged Energy [Wh]
+ deltaRegEvseActualChargingCurrent = 7 // EVSE Actual Charging Current* - FLOAT32 [A]
+ deltaRegEvseActualOutputPower = 9 // EVSE Actual Output Power* - FLOAT32 [W]
+ deltaRegEvseSoc = 11 // EVSE SOC* [%/10]
+ deltaRegEvseChargingTime = 17 // EVSE Charging Time* [s]
+ deltaRegEvseChargedEnergy = 19 // EVSE Charged Energy* [Wh]
deltaRegEvseRfidUID = 100 // EVSE Used Authentication ID - STRING
// Write Multiple Registers (0x10)
deltaRegEvseChargingPowerLimit = 600 // EVSE Charging Power Limit - UINT32 [W]
- deltaRegEvseSuspendCharging = 602 // EVSE Suspend Charging - UINT16 - 0: no pause, 1 charging pause (lock on)
+ deltaRegEvseSuspendCharging = 602 // EVSE Suspend Charging - UINT16 0: no pause, 1 charging pause (lock on)
)
func init() {
@@ -103,16 +101,22 @@ func NewDelta(uri, device, comset string, baudrate int, proto modbus.Protocol, s
wb.base = connector * 1000
- // get failsafe timeout from charger
- b, err := wb.conn.ReadHoldingRegisters(deltaRegCommunicationTimeout, 1)
+ b, err := wb.conn.ReadHoldingRegisters(deltaRegCommunicationTimeoutEnabled, 1)
if err != nil {
- return nil, fmt.Errorf("failsafe timeout: %w", err)
+ return nil, fmt.Errorf("failsafe timeout enabled: %w", err)
}
- if u := encoding.Uint16(b); u > 0 {
- go wb.heartbeat(time.Duration(u) * time.Second / 2)
+
+ if encoding.Uint16(b) != 0 {
+ b, err := wb.conn.ReadHoldingRegisters(deltaRegCommunicationTimeout, 1)
+ if err != nil {
+ return nil, fmt.Errorf("failsafe timeout: %w", err)
+ }
+ if u := encoding.Uint16(b); u > 0 {
+ go wb.heartbeat(time.Duration(u) * time.Second / 2)
+ }
}
- return wb, err
+ return wb, nil
}
func (wb *Delta) heartbeat(timeout time.Duration) {
@@ -131,25 +135,27 @@ func (wb *Delta) heartbeat(timeout time.Duration) {
// Status implements the api.Charger interface
func (wb *Delta) Status() (api.ChargeStatus, error) {
- b, err := wb.conn.ReadInputRegisters(wb.base+deltaRegEvseChargerState, 1)
+ b, err := wb.conn.ReadInputRegisters(wb.base+deltaRegEvseState, 1)
if err != nil {
return api.StatusNone, err
}
- // 0: Charging process not started (no vehicle connected)
- // 1: Connected, waiting for release (by RFID or local)
- // 2: Charging process starts
- // 3: Charging
- // 4: Suspended (loading paused)
- // 5: Charging process successfully completed (vehicle still plugged in)
- // 6: Charging process completed by user (vehicle still plugged in)
- // 7: Charging ended with error (vehicle still connected)
+ // 0: Unavailable
+ // 1: Available
+ // 2: Occupied
+ // 3: Preparing
+ // 4: Charging
+ // 5: Finishing
+ // 6: Suspended EV
+ // 7: Suspended EVSE
+ // 8: Not ready
+ // 9: Faulted
switch s := encoding.Uint16(b); s {
- case 0:
+ case 0, 1, 2, 3:
return api.StatusA, nil
- case 1, 2, 4, 5, 6, 7:
+ case 5, 6, 7:
return api.StatusB, nil
- case 3:
+ case 4:
return api.StatusC, nil
default:
return api.StatusNone, fmt.Errorf("invalid status: %0x", s)
@@ -235,23 +241,11 @@ func (wb *Delta) CurrentPower() (float64, error) {
return float64(encoding.Uint32(b)), err
}
-var _ api.ChargeRater = (*Delta)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (wb *Delta) ChargedEnergy() (float64, error) {
- b, err := wb.conn.ReadInputRegisters(wb.base+deltaRegEvseChargedEnergy, 2)
- if err != nil {
- return 0, err
- }
-
- return float64(encoding.Uint32(b)) / 1e3, err
-}
-
var _ api.Identifier = (*Delta)(nil)
// Identify implements the api.Identifier interface
func (wb *Delta) Identify() (string, error) {
- b, err := wb.conn.ReadInputRegisters(wb.base+deltaRegEvseRfidUID, 6)
+ b, err := wb.conn.ReadInputRegisters(wb.base+deltaRegEvseRfidUID, 20)
if err != nil {
return "", err
}
@@ -266,21 +260,24 @@ func (wb *Delta) Diagnose() {
if b, err := wb.conn.ReadInputRegisters(deltaRegState, 1); err == nil {
fmt.Printf("\tState:\t%d\n", encoding.Uint16(b))
}
- if b, err := wb.conn.ReadInputRegisters(deltaRegVersion, 1); err == nil {
- fmt.Printf("\tVersion:\t%d\n", encoding.Uint16(b))
- }
if b, err := wb.conn.ReadInputRegisters(deltaRegCount, 1); err == nil {
fmt.Printf("\tEVSE Count:\t%d\n", encoding.Uint16(b))
}
- if b, err := wb.conn.ReadInputRegisters(deltaRegError, 1); err == nil {
- fmt.Printf("\tError:\t%d\n", encoding.Uint16(b))
- }
if b, err := wb.conn.ReadInputRegisters(deltaRegSerial, 20); err == nil {
fmt.Printf("\tSerial:\t%s\n", bytesAsString(b))
}
if b, err := wb.conn.ReadInputRegisters(deltaRegModel, 20); err == nil {
fmt.Printf("\tModel:\t%s\n", bytesAsString(b))
}
+ if b, err := wb.conn.ReadHoldingRegisters(deltaRegCommunicationTimeoutEnabled, 1); err == nil {
+ fmt.Printf("\tCommunication Timeout Enabled:\t%d\n", encoding.Uint16(b))
+ }
+ if b, err := wb.conn.ReadHoldingRegisters(deltaRegCommunicationTimeout, 1); err == nil {
+ fmt.Printf("\tCommunication Timeout:\t%d\n", encoding.Uint16(b))
+ }
+ if b, err := wb.conn.ReadHoldingRegisters(deltaRegFallbackPower, 2); err == nil {
+ fmt.Printf("\tFallback Power:\t%d\n", encoding.Uint32(b))
+ }
}
var _ loadpoint.Controller = (*Delta)(nil)
diff --git a/charger/eebus.go b/charger/eebus.go
index 7b855d7bc8..9b2476a2d3 100644
--- a/charger/eebus.go
+++ b/charger/eebus.go
@@ -3,21 +3,22 @@ package charger
import (
"errors"
"fmt"
- "os"
+ "slices"
"sync"
"time"
- "github.com/enbility/cemd/emobility"
- "github.com/enbility/eebus-go/features"
+ eebusapi "github.com/enbility/eebus-go/api"
+ ucapi "github.com/enbility/eebus-go/usecases/api"
+ "github.com/enbility/eebus-go/usecases/cem/evcc"
+ spineapi "github.com/enbility/spine-go/api"
+ "github.com/enbility/spine-go/model"
"github.com/evcc-io/evcc/api"
- "github.com/evcc-io/evcc/charger/eebus"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/provider"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util"
)
-//go:generate mockgen -package charger -destination eebus_test_mock.go github.com/enbility/cemd/emobility EmobilityI
-
const (
maxIdRequestTimespan = time.Second * 120
idleFactor = 0.6
@@ -29,30 +30,24 @@ type minMax struct {
}
type EEBus struct {
- ski string
- emobility emobility.EmobilityI
+ uc *eebus.UseCasesEVSE
+ ev spineapi.EntityRemoteInterface
+ mux sync.RWMutex
log *util.Logger
lp loadpoint.API
minMaxG func() (minMax, error)
- communicationStandard emobility.EVCommunicationStandardType
+ communicationStandard model.DeviceConfigurationKeyValueStringType
+ vasVW bool // wether the EVSE supports VW VAS with ISO15118-2
expectedEnableUnpluggedState bool
current float64
- // connection tracking for api.CurrentGetter
- evConnected bool
currentLimit float64
- lastIsChargingCheck time.Time
- lastIsChargingResult bool
-
- connected bool
- connectedC chan bool
+ *eebus.Connector
connectedTime time.Time
-
- mux sync.Mutex
}
func init() {
@@ -63,9 +58,10 @@ func init() {
func NewEEBusFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
Ski string
- Ip string
+ Ip_ string `mapstructure:"ip"` // deprecated
Meter bool
ChargedEnergy bool
+ VasVW bool
}{
ChargedEnergy: true,
}
@@ -74,32 +70,32 @@ func NewEEBusFromConfig(other map[string]interface{}) (api.Charger, error) {
return nil, err
}
- return NewEEBus(cc.Ski, cc.Ip, cc.Meter, cc.ChargedEnergy)
+ return NewEEBus(cc.Ski, cc.Meter, cc.ChargedEnergy, cc.VasVW)
}
//go:generate go run ../cmd/tools/decorate.go -f decorateEEBus -b *EEBus -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.ChargeRater,ChargedEnergy,func() (float64, error)"
// NewEEBus creates EEBus charger
-func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy bool) (api.Charger, error) {
- log := util.NewLogger("eebus")
-
+func NewEEBus(ski string, hasMeter, hasChargedEnergy, vasVW bool) (api.Charger, error) {
if eebus.Instance == nil {
return nil, errors.New("eebus not configured")
}
c := &EEBus{
- ski: ski,
- log: log,
- connectedC: make(chan bool, 1),
- communicationStandard: emobility.EVCommunicationStandardTypeUnknown,
- current: 6,
+ log: util.NewLogger("eebus"),
+ current: 6,
+ vasVW: vasVW,
+ uc: eebus.Instance.Evse(),
}
- c.emobility = eebus.Instance.RegisterEVSE(ski, ip, c.onConnect, c.onDisconnect, nil)
-
+ c.Connector = eebus.NewConnector(c.connectEvent)
c.minMaxG = provider.Cached(c.minMax, time.Second)
- if err := c.waitForConnection(); err != nil {
+ if err := eebus.Instance.RegisterDevice(ski, c); err != nil {
+ return nil, err
+ }
+
+ if err := c.Wait(90 * time.Second); err != nil {
return c, err
}
@@ -114,72 +110,63 @@ func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy bool) (api.Charger, err
return c, nil
}
-// waitForConnection wait for initial connection and returns an error on failure
-func (c *EEBus) waitForConnection() error {
- timeout := time.After(90 * time.Second)
- for {
- select {
- case <-timeout:
- return os.ErrDeadlineExceeded
- case connected := <-c.connectedC:
- if connected {
- return nil
- }
- }
- }
+func (c *EEBus) setEvEntity(entity spineapi.EntityRemoteInterface) {
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.ev = entity
}
-func (c *EEBus) onConnect(ski string) {
- c.log.TRACE.Println("connect ski:", ski)
+func (c *EEBus) evEntity() spineapi.EntityRemoteInterface {
+ c.mux.RLock()
+ defer c.mux.RUnlock()
- c.expectedEnableUnpluggedState = false
- c.setDefaultValues()
- c.setConnected(true)
+ return c.ev
}
-func (c *EEBus) onDisconnect(ski string) {
- c.log.TRACE.Println("disconnect ski:", ski)
+func (c *EEBus) connectEvent(connected bool) {
+ if connected && !c.Connected() {
+ c.mux.Lock()
+ c.connectedTime = time.Now()
+ c.mux.Unlock()
+ }
- c.expectedEnableUnpluggedState = false
- c.setConnected(false)
c.setDefaultValues()
}
-func (c *EEBus) setDefaultValues() {
- c.communicationStandard = emobility.EVCommunicationStandardTypeUnknown
- c.lastIsChargingCheck = time.Now().Add(-time.Hour * 1)
- c.lastIsChargingResult = false
-}
-
-func (c *EEBus) setConnected(connected bool) {
- c.mux.Lock()
- defer c.mux.Unlock()
+var _ eebus.Device = (*EEBus)(nil)
- if connected && !c.connected {
- c.connectedTime = time.Now()
- }
-
- select {
- case c.connectedC <- connected:
- default:
+// UseCaseEvent implements the eebus.Device interface
+func (c *EEBus) UseCaseEvent(device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
+ switch event {
+ // EV
+ case evcc.EvConnected:
+ c.log.TRACE.Println("EV Connected")
+ c.setEvEntity(entity)
+ c.currentLimit = -1
+ case evcc.EvDisconnected:
+ c.log.TRACE.Println("EV Disconnected")
+ c.setEvEntity(nil)
+ c.currentLimit = -1
}
-
- c.connected = connected
}
-func (c *EEBus) isConnected() bool {
- c.mux.Lock()
- defer c.mux.Unlock()
-
- return c.connected
+func (c *EEBus) setDefaultValues() {
+ c.communicationStandard = evcc.EVCCCommunicationStandardUnknown
+ c.expectedEnableUnpluggedState = false
}
var _ api.CurrentLimiter = (*EEBus)(nil)
func (c *EEBus) minMax() (minMax, error) {
- minLimits, maxLimits, _, err := c.emobility.EVCurrentLimits()
+ evEntity := c.evEntity()
+ if !c.uc.EvCC.EVConnected(evEntity) {
+ return minMax{}, errors.New("no ev connected")
+ }
+
+ minLimits, maxLimits, _, err := c.uc.OpEV.CurrentLimits(evEntity)
if err != nil {
- if err == features.ErrDataNotAvailable {
+ if err == eebusapi.ErrDataNotAvailable {
err = api.ErrNotAvailable
}
return minMax{}, err
@@ -198,31 +185,27 @@ func (c *EEBus) GetMinMaxCurrent() (float64, float64, error) {
}
// we assume that if any phase current value is > idleFactor * min Current, then charging is active and enabled is true
-func (c *EEBus) isCharging() bool { // d *communication.EVSEClientDataType
+func (c *EEBus) isCharging() bool {
+ evEntity := c.evEntity()
+ if !c.uc.EvCC.EVConnected(evEntity) {
+ return false
+ }
+
// check if an external physical meter is assigned
// we only want this for configured meters and not for internal meters!
// right now it works as expected
if c.lp != nil && c.lp.HasChargeMeter() {
- // we only check ever 10 seconds, maybe we can use the config interval duration
- if time.Since(c.lastIsChargingCheck) >= 10*time.Second {
- c.lastIsChargingCheck = time.Now()
- c.lastIsChargingResult = false
- // compare charge power for all phases to 0.6 * min. charge power of a single phase
- if c.lp.GetChargePower() > c.lp.EffectiveMinPower()*idleFactor {
- c.lastIsChargingResult = true
- return true
- }
- } else if c.lastIsChargingResult {
+ if c.lp.GetChargePower() > c.lp.EffectiveMinPower()*idleFactor {
return true
}
}
// The above doesn't (yet) work for built in meters, so check the EEBUS measurements also
- currents, err := c.emobility.EVCurrentsPerPhase()
+ currents, err := c.uc.EvCem.CurrentPerPhase(evEntity)
if err != nil {
return false
}
- limitsMin, _, _, err := c.emobility.EVCurrentLimits()
+ limitsMin, _, _, err := c.uc.OpEV.CurrentLimits(evEntity)
if err != nil || limitsMin == nil || len(limitsMin) == 0 {
return false
}
@@ -241,41 +224,36 @@ func (c *EEBus) isCharging() bool { // d *communication.EVSEClientDataType
// Status implements the api.Charger interface
func (c *EEBus) Status() (api.ChargeStatus, error) {
- if !c.isConnected() {
+ if !c.Connected() {
return api.StatusNone, api.ErrTimeout
}
- if !c.emobility.EVConnected() {
+ evEntity := c.evEntity()
+ if !c.uc.EvCC.EVConnected(evEntity) {
c.expectedEnableUnpluggedState = false
- c.evConnected = false
return api.StatusA, nil
}
- if !c.evConnected {
- c.evConnected = true
- c.currentLimit = -1
- }
-
- currentState, err := c.emobility.EVCurrentChargeState()
+ currentState, err := c.uc.EvCC.ChargeState(evEntity)
if err != nil {
return api.StatusNone, err
}
switch currentState {
- case emobility.EVChargeStateTypeUnknown, emobility.EVChargeStateTypeUnplugged: // Unplugged
+ case ucapi.EVChargeStateTypeUnknown, ucapi.EVChargeStateTypeUnplugged: // Unplugged
c.expectedEnableUnpluggedState = false
return api.StatusA, nil
- case emobility.EVChargeStateTypeFinished, emobility.EVChargeStateTypePaused: // Finished, Paused
+ case ucapi.EVChargeStateTypeFinished, ucapi.EVChargeStateTypePaused: // Finished, Paused
return api.StatusB, nil
- case emobility.EVChargeStateTypeActive: // Active
+ case ucapi.EVChargeStateTypeActive: // Active
if c.isCharging() {
return api.StatusC, nil
}
return api.StatusB, nil
- case emobility.EVChargeStateTypeError: // Error
+ case ucapi.EVChargeStateTypeError: // Error
return api.StatusF, nil
default:
- return api.StatusNone, fmt.Errorf("%s properties unknown result: %s", c.ski, currentState)
+ return api.StatusNone, fmt.Errorf("properties unknown result: %s", currentState)
}
}
@@ -283,8 +261,9 @@ func (c *EEBus) Status() (api.ChargeStatus, error) {
// should return true if the charger allows the EV to draw power
func (c *EEBus) Enabled() (bool, error) {
// when unplugged there is no overload limit data available
+ evEntity := c.evEntity()
state, err := c.Status()
- if err != nil || state == api.StatusA {
+ if err != nil || state == api.StatusA || evEntity == nil {
return c.expectedEnableUnpluggedState, nil
}
@@ -293,9 +272,27 @@ func (c *EEBus) Enabled() (bool, error) {
return true, nil
}
- limits, err := c.emobility.EVLoadControlObligationLimits()
+ // if the VW VAS PV mode is active, use PV limits
+ if c.hasActiveVASVW() {
+ limits, err := c.uc.OscEV.LoadControlLimits(evEntity)
+ if err != nil {
+ // there are no limits available, e.g. because the data was not received yet
+ return true, nil
+ }
+
+ for _, limit := range limits {
+ // check if there is an active limit set
+ if limit.IsActive && limit.Value >= 1 {
+ return true, nil
+ }
+ }
+
+ return false, nil
+ }
+
+ limits, err := c.uc.OpEV.LoadControlLimits(evEntity)
if err != nil {
- // there are no overload protection limits available, e.g. because the data was not received yet
+ // there are limits available, e.g. because the data was not received yet
return true, nil
}
@@ -303,7 +300,9 @@ func (c *EEBus) Enabled() (bool, error) {
// for IEC61851 the pause limit is 0A, for ISO15118-2 it is 0.1A
// instead of checking for the actual data, hardcode this, so we might run into less
// timing issues as the data might not be received yet
- if limit >= 1 {
+ // if the limit is not active, then the maximum possible current is permitted
+ if (limit.IsActive && limit.Value >= 1) ||
+ !limit.IsActive {
return true, nil
}
}
@@ -322,8 +321,8 @@ func (c *EEBus) Enable(enable bool) error {
// if we disable charging with a potential but not yet known communication standard ISO15118
// this would set allowed A value to be 0. And this would trigger ISO connections to switch to IEC!
if !enable {
- comStandard, err := c.emobility.EVCommunicationStandard()
- if err != nil || comStandard == emobility.EVCommunicationStandardTypeUnknown {
+ comStandard, err := c.uc.EvCC.CommunicationStandard(c.evEntity())
+ if err != nil || comStandard == evcc.EVCCCommunicationStandardUnknown {
return api.ErrMustRetry
}
}
@@ -339,37 +338,182 @@ func (c *EEBus) Enable(enable bool) error {
// send current charging power limits to the EV
func (c *EEBus) writeCurrentLimitData(currents []float64) error {
- comStandard, err := c.emobility.EVCommunicationStandard()
+ evEntity := c.evEntity()
+ if !c.uc.EvCC.EVConnected(evEntity) {
+ return errors.New("no ev connected")
+ }
+
+ // check if the EVSE supports overload protection limits
+ if !c.uc.OpEV.IsScenarioAvailableAtEntity(evEntity, 1) {
+ return api.ErrNotAvailable
+ }
+
+ _, maxLimits, _, err := c.uc.OpEV.CurrentLimits(evEntity)
if err != nil {
- return err
+ return errors.New("no limits available")
+ }
+
+ // setup the limit data structure
+ limits := []ucapi.LoadLimitsPhase{}
+ for phase, current := range currents {
+ if phase >= len(maxLimits) || phase >= len(ucapi.PhaseNameMapping) {
+ continue
+ }
+
+ limit := ucapi.LoadLimitsPhase{
+ Phase: ucapi.PhaseNameMapping[phase],
+ IsActive: true,
+ Value: current,
+ }
+
+ // if the limit equals to the max allowed, then the obligation limit is actually inactive
+ if current >= maxLimits[phase] {
+ limit.IsActive = false
+ }
+
+ limits = append(limits, limit)
+ }
+
+ // if VAS VW is available, limits are completely covered by it
+ // this way evcc can fully control the charging behaviour
+ if c.writeLoadControlLimitsVASVW(limits) {
+ return nil
}
- // Only send currents smaller than 6A if the communication standard is known.
- // Otherwise this could cause ISO15118 capable OBCs to stick with IEC61851 when plugging
- // the charge cable in. Or even worse show an error and the cable then needs to be unplugged,
- // wait for the car to go into sleep and plug it back in.
- // So if there are currents smaller than 6A with unknown communication standard change them to 6A.
- // Keep in mind that this will still confuse evcc as it thinks charging is stopped, but it hasn't yet.
- if comStandard == emobility.EVCommunicationStandardTypeUnknown {
- minLimits, _, _, err := c.emobility.EVCurrentLimits()
- if err == nil {
- for index, current := range currents {
- if index < len(minLimits) && current < minLimits[index] {
- currents[index] = minLimits[index]
- }
+ // make sure the recommendations are inactive, otherwise the EV won't go to sleep
+ if recommendations, err := c.uc.OscEV.LoadControlLimits(evEntity); err == nil {
+ var writeNeeded bool
+
+ for index, item := range recommendations {
+ if item.IsActive {
+ recommendations[index].IsActive = false
+ writeNeeded = true
}
}
+
+ if writeNeeded {
+ _, _ = c.uc.OscEV.WriteLoadControlLimits(evEntity, recommendations, nil)
+ }
}
- // Set overload protection limits and self consumption limits to identical values,
- // so if the EV supports self consumption it will be used automatically.
- if err = c.emobility.EVWriteLoadControlLimits(currents, currents); err == nil {
+ // Set overload protection limits
+ if _, err = c.uc.OpEV.WriteLoadControlLimits(evEntity, limits, nil); err == nil {
c.currentLimit = currents[0]
}
return err
}
+// returns if the connected EV has an active VW PV mode
+// in this mode, the EV does not have an active charging demand
+func (c *EEBus) hasActiveVASVW() bool {
+ // EVSE has to support VW VAS
+ if !c.vasVW {
+ return false
+ }
+
+ evEntity := c.evEntity()
+ if evEntity == nil {
+ return false
+ }
+
+ // ISO15118-2 has to be used between EVSE and EV
+ if comStandard, err := c.uc.EvCC.CommunicationStandard(evEntity); err != nil || comStandard != model.DeviceConfigurationKeyValueStringTypeISO151182ED2 {
+ return false
+ }
+
+ // SoC has to be available, otherwise it is plain ISO15118-2
+ if _, err := c.Soc(); err != nil {
+ return false
+ }
+
+ // Optimization of self consumption use case support has to be available
+ if !c.uc.EvSoc.IsScenarioAvailableAtEntity(evEntity, 1) {
+ return false
+ }
+
+ // the use case has to be reported as active
+ // only then the EV has no active charging demand and will charge based on OSCEV recommendations
+ // this is a workaround for EVSE changing isActive to false, even though they should
+ // not announce the usecase at all in that case
+ ucs := evEntity.Device().UseCases()
+ for _, item := range ucs {
+ // check if the referenced entity address is identical to the ev entity address
+ // the address may not exist, as it only available since SPINE 1.3
+ if item.Address != nil &&
+ evEntity.Address() != nil &&
+ slices.Compare(item.Address.Entity, evEntity.Address().Entity) != 0 {
+ continue
+ }
+
+ for _, uc := range item.UseCaseSupport {
+ if uc.UseCaseName != nil &&
+ *uc.UseCaseName == model.UseCaseNameTypeOptimizationOfSelfConsumptionDuringEVCharging &&
+ uc.UseCaseAvailable != nil &&
+ *uc.UseCaseAvailable == true {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// provides support for the special VW VAS ISO15118-2 charging behaviour if supported
+// will return false if it isn't supported or successful
+//
+// this functionality allows to fully control charging without the EV actually having a
+// charging demand by itself
+func (c *EEBus) writeLoadControlLimitsVASVW(limits []ucapi.LoadLimitsPhase) bool {
+ if !c.hasActiveVASVW() {
+ return false
+ }
+
+ evEntity := c.evEntity()
+ if evEntity == nil {
+ return false
+ }
+
+ // check if the EVSE supports optimization of self consumption limits
+ if !c.uc.OscEV.IsScenarioAvailableAtEntity(evEntity, 1) {
+ return false
+ }
+
+ // on OSCEV all limits have to be active except they are set to the default value
+ minLimit, _, _, err := c.uc.OscEV.CurrentLimits(evEntity)
+ if err != nil {
+ return false
+ }
+
+ for index, item := range limits {
+ limits[index].IsActive = item.Value >= minLimit[index]
+ }
+
+ // send the write command
+ if _, err := c.uc.OscEV.WriteLoadControlLimits(evEntity, limits, nil); err != nil {
+ return false
+ }
+ c.currentLimit = limits[0].Value
+
+ // make sure the obligations are inactive, otherwise the EV won't go to sleep
+ if obligations, err := c.uc.OpEV.LoadControlLimits(evEntity); err == nil {
+ writeNeeded := false
+
+ for index, item := range obligations {
+ if item.IsActive {
+ obligations[index].IsActive = false
+ writeNeeded = true
+ }
+ }
+
+ if writeNeeded {
+ _, _ = c.uc.OpEV.WriteLoadControlLimits(evEntity, obligations, nil)
+ }
+ }
+
+ return true
+}
+
// MaxCurrent implements the api.Charger interface
func (c *EEBus) MaxCurrent(current int64) error {
return c.MaxCurrentMillis(float64(current))
@@ -379,7 +523,7 @@ var _ api.ChargerEx = (*EEBus)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (c *EEBus) MaxCurrentMillis(current float64) error {
- if !c.connected || !c.emobility.EVConnected() {
+ if !c.Connected() || c.evEntity() == nil {
return errors.New("can't set new current as ev is unplugged")
}
@@ -392,34 +536,51 @@ func (c *EEBus) MaxCurrentMillis(current float64) error {
return nil
}
-var _ api.CurrentGetter = (*Easee)(nil)
+var _ api.CurrentGetter = (*EEBus)(nil)
// GetMaxCurrent implements the api.CurrentGetter interface
func (c *EEBus) GetMaxCurrent() (float64, error) {
+ if c.currentLimit == -1 {
+ return 0, api.ErrNotAvailable
+ }
+
return c.currentLimit, nil
}
// CurrentPower implements the api.Meter interface
func (c *EEBus) currentPower() (float64, error) {
- if !c.emobility.EVConnected() {
+ evEntity := c.evEntity()
+ if evEntity == nil {
return 0, nil
}
- connectedPhases, err := c.emobility.EVConnectedPhases()
- if err != nil {
- return 0, err
+ var powers []float64
+
+ // does the EVSE provide power data?
+ if c.uc.EvCem.IsScenarioAvailableAtEntity(evEntity, 2) {
+ // is power data available for real? Elli Gen1 says it supports it, but doesn't provide any data
+ if powerData, err := c.uc.EvCem.PowerPerPhase(evEntity); err == nil {
+ powers = powerData
+ }
}
- powers, err := c.emobility.EVPowerPerPhase()
- if err != nil {
- return 0, err
+ // if no power data is available, and currents are reported to be supported, use currents
+ if len(powers) == 0 && c.uc.EvCem.IsScenarioAvailableAtEntity(evEntity, 1) {
+ // no power provided, calculate from current
+ if currents, err := c.uc.EvCem.CurrentPerPhase(evEntity); err == nil {
+ for _, current := range currents {
+ powers = append(powers, current*voltage)
+ }
+ }
+ }
+
+ // if still no power data is available, return an error
+ if len(powers) == 0 {
+ return 0, api.ErrNotAvailable
}
var power float64
- for index, phasePower := range powers {
- if index >= int(connectedPhases) {
- break
- }
+ for _, phasePower := range powers {
power += phasePower
}
@@ -428,13 +589,18 @@ func (c *EEBus) currentPower() (float64, error) {
// ChargedEnergy implements the api.ChargeRater interface
func (c *EEBus) chargedEnergy() (float64, error) {
- if !c.emobility.EVConnected() {
+ evEntity := c.evEntity()
+ if evEntity == nil {
return 0, nil
}
- energy, err := c.emobility.EVChargedEnergy()
+ if !c.uc.EvCem.IsScenarioAvailableAtEntity(evEntity, 3) {
+ return 0, api.ErrNotAvailable
+ }
+
+ energy, err := c.uc.EvCem.EnergyCharged(evEntity)
if err != nil {
- return 0, err
+ return 0, api.ErrNotAvailable
}
return energy / 1e3, nil
@@ -442,13 +608,19 @@ func (c *EEBus) chargedEnergy() (float64, error) {
// Currents implements the api.PhaseCurrents interface
func (c *EEBus) currents() (float64, float64, float64, error) {
- if !c.emobility.EVConnected() {
+ evEntity := c.evEntity()
+ if evEntity == nil {
return 0, 0, 0, nil
}
- res, err := c.emobility.EVCurrentsPerPhase()
+ // check if the EVSE supports currents
+ if !c.uc.EvCem.IsScenarioAvailableAtEntity(evEntity, 1) {
+ return 0, 0, 0, api.ErrNotAvailable
+ }
+
+ res, err := c.uc.EvCem.CurrentPerPhase(evEntity)
if err != nil {
- if err == features.ErrDataNotAvailable {
+ if err == eebusapi.ErrDataNotAvailable {
err = api.ErrNotAvailable
}
return 0, 0, 0, err
@@ -466,21 +638,24 @@ var _ api.Identifier = (*EEBus)(nil)
// Identify implements the api.Identifier interface
func (c *EEBus) Identify() (string, error) {
- if !c.isConnected() || !c.emobility.EVConnected() {
+ evEntity := c.evEntity()
+ if !c.Connected() || evEntity == nil {
return "", nil
}
- if !c.emobility.EVConnected() {
- return "", nil
- }
- if identification, _ := c.emobility.EVIdentification(); identification != "" {
- return identification, nil
+ if identification, err := c.uc.EvCC.Identifications(evEntity); err == nil && len(identification) > 0 {
+ // return the first identification for now
+ // later this could be multiple, e.g. MAC Address and PCID
+ return identification[0].Value, nil
}
- if comStandard, _ := c.emobility.EVCommunicationStandard(); comStandard == emobility.EVCommunicationStandardTypeIEC61851 {
+ if comStandard, _ := c.uc.EvCC.CommunicationStandard(evEntity); comStandard == model.DeviceConfigurationKeyValueStringTypeIEC61851 {
return "", nil
}
+ c.mux.RLock()
+ defer c.mux.RUnlock()
+
if time.Since(c.connectedTime) < maxIdRequestTimespan {
return "", api.ErrMustRetry
}
@@ -492,11 +667,13 @@ var _ api.Battery = (*EEBus)(nil)
// Soc implements the api.Vehicle interface
func (c *EEBus) Soc() (float64, error) {
- if socSupported, err := c.emobility.EVSoCSupported(); err != nil || !socSupported {
+ evEntity := c.evEntity()
+
+ if !c.uc.EvSoc.IsScenarioAvailableAtEntity(evEntity, 1) {
return 0, api.ErrNotAvailable
}
- soc, err := c.emobility.EVSoC()
+ soc, err := c.uc.EvSoc.StateOfCharge(evEntity)
if err != nil {
return 0, api.ErrNotAvailable
}
diff --git a/charger/eebus/eebus.go b/charger/eebus/eebus.go
deleted file mode 100644
index 5e9d601c4e..0000000000
--- a/charger/eebus/eebus.go
+++ /dev/null
@@ -1,295 +0,0 @@
-package eebus
-
-import (
- "bytes"
- "crypto/ecdsa"
- "crypto/rsa"
- "crypto/tls"
- "crypto/x509"
- "encoding/pem"
- "errors"
- "fmt"
- "net"
- "strconv"
- "strings"
- "sync"
-
- "dario.cat/mergo"
- "github.com/enbility/cemd/cem"
- "github.com/enbility/cemd/emobility"
- "github.com/enbility/eebus-go/service"
- "github.com/enbility/eebus-go/spine/model"
- "github.com/evcc-io/evcc/util"
- "github.com/evcc-io/evcc/util/machine"
-)
-
-const (
- EEBUSBrandName string = "EVCC"
- EEBUSModel string = "HEMS"
- EEBUSDeviceCode string = "EVCC_HEMS_01" // used as common name in cert generation
-)
-
-type Config struct {
- URI string
- ShipID string
- Interfaces []string
- Certificate struct {
- Public, Private string
- }
-}
-
-// Configured returns true if the EEbus server is configured
-func (c Config) Configured() bool {
- return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0
-}
-
-type EEBusClientCBs struct {
- onConnect func(string) // , ship.Conn) error
- onDisconnect func(string)
-}
-
-type EEBus struct {
- Cem *cem.CemImpl
-
- mux sync.Mutex
- log *util.Logger
-
- SKI string
-
- clients map[string]EEBusClientCBs
-}
-
-var Instance *EEBus
-
-func NewServer(other Config) (*EEBus, error) {
- cc := Config{
- URI: ":4712",
- }
-
- if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil {
- return nil, err
- }
-
- log := util.NewLogger("eebus")
-
- protectedID, err := machine.ProtectedID("evcc-eebus")
- if err != nil {
- return nil, err
- }
- serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
-
- if len(cc.ShipID) != 0 {
- serial = cc.ShipID
- }
-
- certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private))
- if err != nil {
- return nil, err
- }
-
- _, portValue, err := net.SplitHostPort(cc.URI)
- if err != nil {
- return nil, err
- }
-
- port, err := strconv.Atoi(portValue)
- if err != nil {
- return nil, err
- }
-
- // TODO: get the voltage from the site
- configuration, err := service.NewConfiguration(
- EEBUSBrandName, EEBUSBrandName, EEBUSModel, serial,
- model.DeviceTypeTypeEnergyManagementSystem, port, certificate, 230,
- )
- if err != nil {
- return nil, err
- }
-
- // for backward compatibility
- configuration.SetAlternateMdnsServiceName("EVCC_HEMS_01")
- configuration.SetAlternateIdentifier(serial)
- configuration.SetInterfaces(cc.Interfaces)
- configuration.SetRegisterAutoAccept(true)
-
- ski, err := SkiFromCert(certificate)
- if err != nil {
- return nil, err
- }
-
- c := &EEBus{
- log: log,
- clients: make(map[string]EEBusClientCBs),
- SKI: ski,
- }
-
- c.Cem = cem.NewCEM(configuration, c, c)
- if err := c.Cem.Setup(); err != nil {
- return nil, err
- }
- c.Cem.EnableEmobility(emobility.EmobilityConfiguration{
- CoordinatedChargingEnabled: false,
- })
-
- return c, nil
-}
-
-func (c *EEBus) RegisterEVSE(ski, ip string, connectHandler func(string), disconnectHandler func(string), dataProvider emobility.EmobilityDataProvider) *emobility.EMobilityImpl {
- ski = strings.ReplaceAll(ski, "-", "")
- ski = strings.ReplaceAll(ski, " ", "")
- ski = strings.ToLower(ski)
- c.log.TRACE.Printf("registering ski: %s", ski)
-
- if ski == c.SKI {
- c.log.FATAL.Fatal("The charger SKI can not be identical to the SKI of evcc!")
- }
-
- serviceDetails := service.NewServiceDetails(ski)
- serviceDetails.SetIPv4(ip)
-
- c.mux.Lock()
- defer c.mux.Unlock()
- c.clients[ski] = EEBusClientCBs{onConnect: connectHandler, onDisconnect: disconnectHandler}
-
- return c.Cem.RegisterEmobilityRemoteDevice(serviceDetails, dataProvider)
-}
-
-func (c *EEBus) Run() {
- c.Cem.Start()
-}
-
-func (c *EEBus) Shutdown() {
- c.Cem.Shutdown()
-}
-
-// EEBUSServiceHandler
-
-// report the Ship ID of a newly trusted connection
-func (c *EEBus) RemoteServiceShipIDReported(service *service.EEBUSService, ski string, shipID string) {
- // we should associated the Ship ID with the SKI and store it
- // so the next connection can start trusted
- c.log.DEBUG.Println("SKI", ski, "has Ship ID:", shipID)
-}
-
-func (c *EEBus) RemoteSKIConnected(service *service.EEBUSService, ski string) {
- c.mux.Lock()
- defer c.mux.Unlock()
-
- client, exists := c.clients[ski]
- if !exists {
- return
- }
- client.onConnect(ski)
-}
-
-func (c *EEBus) RemoteSKIDisconnected(service *service.EEBUSService, ski string) {
- c.mux.Lock()
- defer c.mux.Unlock()
-
- client, exists := c.clients[ski]
- if !exists {
- return
- }
- client.onDisconnect(ski)
-}
-
-func (h *EEBus) ReportServiceShipID(ski string, shipdID string) {}
-
-// EEBUS Logging interface
-
-func (c *EEBus) Trace(args ...interface{}) {
- c.log.TRACE.Println(args...)
-}
-
-func (c *EEBus) Tracef(format string, args ...interface{}) {
- c.log.TRACE.Printf(format, args...)
-}
-
-func (c *EEBus) Debug(args ...interface{}) {
- c.log.DEBUG.Println(args...)
-}
-
-func (c *EEBus) Debugf(format string, args ...interface{}) {
- c.log.DEBUG.Printf(format, args...)
-}
-
-func (c *EEBus) Info(args ...interface{}) {
- c.log.INFO.Println(args...)
-}
-
-func (c *EEBus) Infof(format string, args ...interface{}) {
- c.log.INFO.Printf(format, args...)
-}
-
-func (c *EEBus) Error(args ...interface{}) {
- c.log.ERROR.Println(args...)
-}
-
-func (c *EEBus) Errorf(format string, args ...interface{}) {
- c.log.ERROR.Printf(format, args...)
-}
-
-// Certificate helpers
-
-// CreateCertificate returns a newly created EEBUS compatible certificate
-func CreateCertificate() (tls.Certificate, error) {
- return service.CreateCertificate("", EEBUSBrandName, "DE", EEBUSDeviceCode)
-}
-
-// pemBlockForKey marshals private key into pem block
-func pemBlockForKey(priv interface{}) (*pem.Block, error) {
- switch k := priv.(type) {
- case *rsa.PrivateKey:
- return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
- case *ecdsa.PrivateKey:
- b, err := x509.MarshalECPrivateKey(k)
- if err != nil {
- return nil, fmt.Errorf("unable to marshal ECDSA private key: %w", err)
- }
- return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
- default:
- return nil, errors.New("unknown private key type")
- }
-}
-
-// GetX509KeyPair saves returns the cert and key string values
-func GetX509KeyPair(cert tls.Certificate) (string, string, error) {
- var certValue, keyValue string
-
- out := new(bytes.Buffer)
- err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
- if err == nil {
- certValue = out.String()
- }
-
- if len(certValue) > 0 {
- var pb *pem.Block
- if pb, err = pemBlockForKey(cert.PrivateKey); err == nil {
- out.Reset()
- err = pem.Encode(out, pb)
- }
- }
-
- if err == nil {
- keyValue = out.String()
- }
-
- return certValue, keyValue, err
-}
-
-// SkiFromX509 extracts SKI from certificate
-func skiFromX509(leaf *x509.Certificate) (string, error) {
- if len(leaf.SubjectKeyId) == 0 {
- return "", errors.New("missing SubjectKeyId")
- }
- return fmt.Sprintf("%0x", leaf.SubjectKeyId), nil
-}
-
-// SkiFromCert extracts SKI from certificate
-func SkiFromCert(cert tls.Certificate) (string, error) {
- leaf, err := x509.ParseCertificate(cert.Certificate[0])
- if err != nil {
- return "", errors.New("failed parsing certificate: " + err.Error())
- }
- return skiFromX509(leaf)
-}
diff --git a/charger/eebus_test.go b/charger/eebus_test.go
index 68570b2917..bd058d786e 100644
--- a/charger/eebus_test.go
+++ b/charger/eebus_test.go
@@ -1,8 +1,15 @@
package charger
import (
+ "errors"
"testing"
+ "github.com/enbility/eebus-go/usecases/mocks"
+ spinemocks "github.com/enbility/spine-go/mocks"
+ "github.com/evcc-io/evcc/server/eebus"
+ "github.com/evcc-io/evcc/util"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
"go.uber.org/mock/gomock"
)
@@ -116,9 +123,19 @@ func TestEEBusIsCharging(t *testing.T) {
for index, m := range tc.measurements {
ctrl := gomock.NewController(t)
- emobilityMock := NewMockEmobilityI(ctrl)
+ evcc := mocks.NewCemEVCCInterface(t)
+ evcem := mocks.NewCemEVCEMInterface(t)
+ opev := mocks.NewCemOPEVInterface(t)
+
+ uc := &eebus.UseCasesEVSE{
+ EvCC: evcc,
+ EvCem: evcem,
+ OpEV: opev,
+ }
+ evEntity := spinemocks.NewEntityRemoteInterface(t)
eebus := &EEBus{
- emobility: emobilityMock,
+ uc: uc,
+ ev: evEntity,
}
currents := make([]float64, 0)
@@ -127,8 +144,9 @@ func TestEEBusIsCharging(t *testing.T) {
currents = append(currents, d.current)
}
- emobilityMock.EXPECT().EVCurrentsPerPhase().Return(currents, nil).AnyTimes()
- emobilityMock.EXPECT().EVCurrentLimits().Return(limitsMin, limitsMax, limitsDefault, nil)
+ evcc.EXPECT().EVConnected(evEntity).Return(true)
+ evcem.EXPECT().CurrentPerPhase(evEntity).Return(currents, nil)
+ opev.EXPECT().CurrentLimits(evEntity).Return(limitsMin, limitsMax, limitsDefault, nil)
result := eebus.isCharging()
if result != m.expected {
@@ -139,3 +157,48 @@ func TestEEBusIsCharging(t *testing.T) {
})
}
}
+
+func TestEEBusCurrentPower(t *testing.T) {
+ evcem := mocks.NewCemEVCEMInterface(t)
+
+ uc := &eebus.UseCasesEVSE{
+ EvCem: evcem,
+ }
+ evEntity := spinemocks.NewEntityRemoteInterface(t)
+ logger := util.NewLogger("test")
+ eebus := &EEBus{
+ uc: uc,
+ ev: evEntity,
+ log: logger,
+ }
+
+ evcem.EXPECT().IsScenarioAvailableAtEntity(evEntity, mock.Anything).Return(true)
+ evcem.EXPECT().PowerPerPhase(evEntity).Return([]float64{600, 600, 600}, nil)
+
+ power, err := eebus.currentPower()
+ assert.Nil(t, err)
+ assert.Equal(t, 1800.0, power)
+}
+
+func TestEEBusCurrentPower_Elli(t *testing.T) {
+ evcem := mocks.NewCemEVCEMInterface(t)
+
+ uc := &eebus.UseCasesEVSE{
+ EvCem: evcem,
+ }
+ evEntity := spinemocks.NewEntityRemoteInterface(t)
+ logger := util.NewLogger("test")
+ eebus := &EEBus{
+ uc: uc,
+ ev: evEntity,
+ log: logger,
+ }
+
+ evcem.EXPECT().IsScenarioAvailableAtEntity(evEntity, mock.Anything).Return(true)
+ evcem.EXPECT().PowerPerPhase(evEntity).Return(nil, errors.New("error"))
+ evcem.EXPECT().CurrentPerPhase(evEntity).Return([]float64{5.8, 5.8, 5.8}, nil)
+
+ power, err := eebus.currentPower()
+ assert.Nil(t, err)
+ assert.Equal(t, 4002.0, power)
+}
diff --git a/charger/eebus_test_mock.go b/charger/eebus_test_mock.go
deleted file mode 100644
index bc42eaaeb5..0000000000
--- a/charger/eebus_test_mock.go
+++ /dev/null
@@ -1,350 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/enbility/cemd/emobility (interfaces: EmobilityI)
-//
-// Generated by this command:
-//
-// mockgen -package charger -destination eebus_test_mock.go github.com/enbility/cemd/emobility EmobilityI
-//
-
-// Package charger is a generated GoMock package.
-package charger
-
-import (
- reflect "reflect"
-
- emobility "github.com/enbility/cemd/emobility"
- gomock "go.uber.org/mock/gomock"
-)
-
-// MockEmobilityI is a mock of EmobilityI interface.
-type MockEmobilityI struct {
- ctrl *gomock.Controller
- recorder *MockEmobilityIMockRecorder
-}
-
-// MockEmobilityIMockRecorder is the mock recorder for MockEmobilityI.
-type MockEmobilityIMockRecorder struct {
- mock *MockEmobilityI
-}
-
-// NewMockEmobilityI creates a new mock instance.
-func NewMockEmobilityI(ctrl *gomock.Controller) *MockEmobilityI {
- mock := &MockEmobilityI{ctrl: ctrl}
- mock.recorder = &MockEmobilityIMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockEmobilityI) EXPECT() *MockEmobilityIMockRecorder {
- return m.recorder
-}
-
-// EVChargeStrategy mocks base method.
-func (m *MockEmobilityI) EVChargeStrategy() emobility.EVChargeStrategyType {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVChargeStrategy")
- ret0, _ := ret[0].(emobility.EVChargeStrategyType)
- return ret0
-}
-
-// EVChargeStrategy indicates an expected call of EVChargeStrategy.
-func (mr *MockEmobilityIMockRecorder) EVChargeStrategy() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVChargeStrategy", reflect.TypeOf((*MockEmobilityI)(nil).EVChargeStrategy))
-}
-
-// EVChargedEnergy mocks base method.
-func (m *MockEmobilityI) EVChargedEnergy() (float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVChargedEnergy")
- ret0, _ := ret[0].(float64)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVChargedEnergy indicates an expected call of EVChargedEnergy.
-func (mr *MockEmobilityIMockRecorder) EVChargedEnergy() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVChargedEnergy", reflect.TypeOf((*MockEmobilityI)(nil).EVChargedEnergy))
-}
-
-// EVCommunicationStandard mocks base method.
-func (m *MockEmobilityI) EVCommunicationStandard() (emobility.EVCommunicationStandardType, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVCommunicationStandard")
- ret0, _ := ret[0].(emobility.EVCommunicationStandardType)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVCommunicationStandard indicates an expected call of EVCommunicationStandard.
-func (mr *MockEmobilityIMockRecorder) EVCommunicationStandard() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCommunicationStandard", reflect.TypeOf((*MockEmobilityI)(nil).EVCommunicationStandard))
-}
-
-// EVConnected mocks base method.
-func (m *MockEmobilityI) EVConnected() bool {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVConnected")
- ret0, _ := ret[0].(bool)
- return ret0
-}
-
-// EVConnected indicates an expected call of EVConnected.
-func (mr *MockEmobilityIMockRecorder) EVConnected() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVConnected", reflect.TypeOf((*MockEmobilityI)(nil).EVConnected))
-}
-
-// EVConnectedPhases mocks base method.
-func (m *MockEmobilityI) EVConnectedPhases() (uint, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVConnectedPhases")
- ret0, _ := ret[0].(uint)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVConnectedPhases indicates an expected call of EVConnectedPhases.
-func (mr *MockEmobilityIMockRecorder) EVConnectedPhases() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVConnectedPhases", reflect.TypeOf((*MockEmobilityI)(nil).EVConnectedPhases))
-}
-
-// EVCoordinatedChargingSupported mocks base method.
-func (m *MockEmobilityI) EVCoordinatedChargingSupported() (bool, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVCoordinatedChargingSupported")
- ret0, _ := ret[0].(bool)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVCoordinatedChargingSupported indicates an expected call of EVCoordinatedChargingSupported.
-func (mr *MockEmobilityIMockRecorder) EVCoordinatedChargingSupported() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCoordinatedChargingSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVCoordinatedChargingSupported))
-}
-
-// EVCurrentChargeState mocks base method.
-func (m *MockEmobilityI) EVCurrentChargeState() (emobility.EVChargeStateType, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVCurrentChargeState")
- ret0, _ := ret[0].(emobility.EVChargeStateType)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVCurrentChargeState indicates an expected call of EVCurrentChargeState.
-func (mr *MockEmobilityIMockRecorder) EVCurrentChargeState() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentChargeState", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentChargeState))
-}
-
-// EVCurrentLimits mocks base method.
-func (m *MockEmobilityI) EVCurrentLimits() ([]float64, []float64, []float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVCurrentLimits")
- ret0, _ := ret[0].([]float64)
- ret1, _ := ret[1].([]float64)
- ret2, _ := ret[2].([]float64)
- ret3, _ := ret[3].(error)
- return ret0, ret1, ret2, ret3
-}
-
-// EVCurrentLimits indicates an expected call of EVCurrentLimits.
-func (mr *MockEmobilityIMockRecorder) EVCurrentLimits() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentLimits))
-}
-
-// EVCurrentsPerPhase mocks base method.
-func (m *MockEmobilityI) EVCurrentsPerPhase() ([]float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVCurrentsPerPhase")
- ret0, _ := ret[0].([]float64)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVCurrentsPerPhase indicates an expected call of EVCurrentsPerPhase.
-func (mr *MockEmobilityIMockRecorder) EVCurrentsPerPhase() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentsPerPhase", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentsPerPhase))
-}
-
-// EVEnergyDemand mocks base method.
-func (m *MockEmobilityI) EVEnergyDemand() (emobility.EVDemand, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVEnergyDemand")
- ret0, _ := ret[0].(emobility.EVDemand)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVEnergyDemand indicates an expected call of EVEnergyDemand.
-func (mr *MockEmobilityIMockRecorder) EVEnergyDemand() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVEnergyDemand", reflect.TypeOf((*MockEmobilityI)(nil).EVEnergyDemand))
-}
-
-// EVGetIncentiveConstraints mocks base method.
-func (m *MockEmobilityI) EVGetIncentiveConstraints() emobility.EVIncentiveSlotConstraints {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVGetIncentiveConstraints")
- ret0, _ := ret[0].(emobility.EVIncentiveSlotConstraints)
- return ret0
-}
-
-// EVGetIncentiveConstraints indicates an expected call of EVGetIncentiveConstraints.
-func (mr *MockEmobilityIMockRecorder) EVGetIncentiveConstraints() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVGetIncentiveConstraints", reflect.TypeOf((*MockEmobilityI)(nil).EVGetIncentiveConstraints))
-}
-
-// EVGetPowerConstraints mocks base method.
-func (m *MockEmobilityI) EVGetPowerConstraints() emobility.EVTimeSlotConstraints {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVGetPowerConstraints")
- ret0, _ := ret[0].(emobility.EVTimeSlotConstraints)
- return ret0
-}
-
-// EVGetPowerConstraints indicates an expected call of EVGetPowerConstraints.
-func (mr *MockEmobilityIMockRecorder) EVGetPowerConstraints() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVGetPowerConstraints", reflect.TypeOf((*MockEmobilityI)(nil).EVGetPowerConstraints))
-}
-
-// EVIdentification mocks base method.
-func (m *MockEmobilityI) EVIdentification() (string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVIdentification")
- ret0, _ := ret[0].(string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVIdentification indicates an expected call of EVIdentification.
-func (mr *MockEmobilityIMockRecorder) EVIdentification() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVIdentification", reflect.TypeOf((*MockEmobilityI)(nil).EVIdentification))
-}
-
-// EVLoadControlObligationLimits mocks base method.
-func (m *MockEmobilityI) EVLoadControlObligationLimits() ([]float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVLoadControlObligationLimits")
- ret0, _ := ret[0].([]float64)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVLoadControlObligationLimits indicates an expected call of EVLoadControlObligationLimits.
-func (mr *MockEmobilityIMockRecorder) EVLoadControlObligationLimits() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVLoadControlObligationLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVLoadControlObligationLimits))
-}
-
-// EVOptimizationOfSelfConsumptionSupported mocks base method.
-func (m *MockEmobilityI) EVOptimizationOfSelfConsumptionSupported() (bool, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVOptimizationOfSelfConsumptionSupported")
- ret0, _ := ret[0].(bool)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVOptimizationOfSelfConsumptionSupported indicates an expected call of EVOptimizationOfSelfConsumptionSupported.
-func (mr *MockEmobilityIMockRecorder) EVOptimizationOfSelfConsumptionSupported() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVOptimizationOfSelfConsumptionSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVOptimizationOfSelfConsumptionSupported))
-}
-
-// EVPowerPerPhase mocks base method.
-func (m *MockEmobilityI) EVPowerPerPhase() ([]float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVPowerPerPhase")
- ret0, _ := ret[0].([]float64)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVPowerPerPhase indicates an expected call of EVPowerPerPhase.
-func (mr *MockEmobilityIMockRecorder) EVPowerPerPhase() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVPowerPerPhase", reflect.TypeOf((*MockEmobilityI)(nil).EVPowerPerPhase))
-}
-
-// EVSoC mocks base method.
-func (m *MockEmobilityI) EVSoC() (float64, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVSoC")
- ret0, _ := ret[0].(float64)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVSoC indicates an expected call of EVSoC.
-func (mr *MockEmobilityIMockRecorder) EVSoC() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVSoC", reflect.TypeOf((*MockEmobilityI)(nil).EVSoC))
-}
-
-// EVSoCSupported mocks base method.
-func (m *MockEmobilityI) EVSoCSupported() (bool, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVSoCSupported")
- ret0, _ := ret[0].(bool)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// EVSoCSupported indicates an expected call of EVSoCSupported.
-func (mr *MockEmobilityIMockRecorder) EVSoCSupported() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVSoCSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVSoCSupported))
-}
-
-// EVWriteIncentives mocks base method.
-func (m *MockEmobilityI) EVWriteIncentives(arg0 []emobility.EVDurationSlotValue) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVWriteIncentives", arg0)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// EVWriteIncentives indicates an expected call of EVWriteIncentives.
-func (mr *MockEmobilityIMockRecorder) EVWriteIncentives(arg0 any) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWriteIncentives", reflect.TypeOf((*MockEmobilityI)(nil).EVWriteIncentives), arg0)
-}
-
-// EVWriteLoadControlLimits mocks base method.
-func (m *MockEmobilityI) EVWriteLoadControlLimits(arg0, arg1 []float64) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVWriteLoadControlLimits", arg0, arg1)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// EVWriteLoadControlLimits indicates an expected call of EVWriteLoadControlLimits.
-func (mr *MockEmobilityIMockRecorder) EVWriteLoadControlLimits(arg0, arg1 any) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWriteLoadControlLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVWriteLoadControlLimits), arg0, arg1)
-}
-
-// EVWritePowerLimits mocks base method.
-func (m *MockEmobilityI) EVWritePowerLimits(arg0 []emobility.EVDurationSlotValue) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "EVWritePowerLimits", arg0)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// EVWritePowerLimits indicates an expected call of EVWritePowerLimits.
-func (mr *MockEmobilityIMockRecorder) EVWritePowerLimits(arg0 any) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWritePowerLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVWritePowerLimits), arg0)
-}
diff --git a/charger/em2go-home.go b/charger/em2go-home.go
deleted file mode 100644
index aeceec6d4b..0000000000
--- a/charger/em2go-home.go
+++ /dev/null
@@ -1,313 +0,0 @@
-package charger
-
-// LICENSE
-
-// Copyright (c) 2019-2024 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 (
- "encoding/binary"
- "fmt"
- "time"
-
- "github.com/evcc-io/evcc/api"
- "github.com/evcc-io/evcc/util"
- "github.com/evcc-io/evcc/util/modbus"
- "github.com/volkszaehler/mbmd/meters/rs485"
-)
-
-// https://www.em2go.de/download2/ModBus TCP Registers EM2GO Home Series.pdf
-
-// Em2Go charger implementation
-type Em2GoHome struct {
- log *util.Logger
- conn *modbus.Connection
-}
-
-const (
- em2GoHomeRegStatus = 0 // Uint16 RO ENUM
- em2GoHomeRegConnectorState = 2 // Uint16 RO ENUM
- em2GoHomeRegErrorCode = 4 // Uint16 RO ENUM
- em2GoHomeRegCurrents = 6 // Uint16 RO 0.1A
- em2GoHomeRegPower = 12 // Uint32 RO 1W
- em2GoHomeRegEnergy = 28 // Uint16 RO 0.1KWh
- em2GoHomeRegMaxCurrent = 32 // Uint16 RO 0.1A
- em2GoHomeRegMinCurrent = 34 // Uint16 RO 0.1A
- em2GoHomeRegCableMaxCurrent = 36 // Uint16 RO 0.1A
- em2GoHomeRegSerial = 38 // Chr[16] RO UTF16
- em2GoHomeRegChargedEnergy = 72 // Uint16 RO 0.1kWh
- em2GoHomeRegChargeDuration = 78 // Uint32 RO 1s
- em2GoHomeRegSafeCurrent = 87 // Uint16 WR 0.1A
- em2GoHomeRegCommTimeout = 89 // Uint16 WR 1s
- em2GoHomeRegCurrentLimit = 91 // Uint16 WR 0.1A
- em2GoHomeRegChargeMode = 93 // Uint16 WR ENUM
- em2GoHomeRegChargeCommand = 95 // Uint16 WR ENUM
- em2GoHomeRegVoltages = 109 // Uint16 RO 0.1V
- em2GoHomeRegPhases = 200 // Set charging phase 1 unsigned
-)
-
-func init() {
- registry.Add("em2go-home", NewEm2GoHomeFromConfig)
-}
-
-//go:generate go run ../cmd/tools/decorate.go -f decorateEm2GoHome -b *Em2GoHome -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
-
-// NewEm2GoHomeFromConfig creates a Em2Go charger from generic config
-func NewEm2GoHomeFromConfig(other map[string]interface{}) (api.Charger, error) {
- cc := modbus.TcpSettings{
- ID: 255,
- }
-
- if err := util.DecodeOther(other, &cc); err != nil {
- return nil, err
- }
-
- return NewEm2GoHome(cc.URI, cc.ID)
-}
-
-// NewEm2GoHome creates Em2GoHome charger
-func NewEm2GoHome(uri string, slaveID uint8) (api.Charger, error) {
- uri = util.DefaultPort(uri, 502)
-
- conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
- if err != nil {
- return nil, err
- }
-
- // Add delay of 60 miliseconds between requests
- conn.Delay(60 * time.Millisecond)
-
- log := util.NewLogger("em2go-home")
- conn.Logger(log.TRACE)
-
- wb := &Em2GoHome{
- log: log,
- conn: conn,
- }
-
- _, v2, v3, err := wb.Voltages()
-
- var phases1p3p func(int) error
- if v2 != 0 && v3 != 0 {
- phases1p3p = wb.phases1p3p
- }
-
- return decorateEm2GoHome(wb, phases1p3p), err
-}
-
-// Status implements the api.Charger interface
-func (wb *Em2GoHome) Status() (api.ChargeStatus, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegStatus, 1)
- if err != nil {
- return api.StatusNone, err
- }
-
- switch binary.BigEndian.Uint16(b) {
- case 1:
- return api.StatusA, nil
- case 2, 3:
- return api.StatusB, nil
- case 4, 6:
- return api.StatusC, nil
- case 5, 7:
- return api.StatusF, nil
- default:
- return api.StatusNone, fmt.Errorf("invalid status: %0x", b[1])
- }
-}
-
-// Enabled implements the api.Charger interface
-func (wb *Em2GoHome) Enabled() (bool, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegChargeCommand, 1)
- if err != nil {
- return false, err
- }
-
- u := binary.BigEndian.Uint16(b)
-
- return u == 1, nil
-}
-
-// Enable implements the api.Charger interface
-func (wb *Em2GoHome) Enable(enable bool) error {
- b := make([]byte, 2)
- binary.BigEndian.PutUint16(b, map[bool]uint16{true: 1, false: 2}[enable])
-
- _, err := wb.conn.WriteMultipleRegisters(em2GoHomeRegChargeCommand, 1, b)
- return err
-}
-
-// MaxCurrent implements the api.Charger interface
-func (wb *Em2GoHome) MaxCurrent(current int64) error {
- return wb.MaxCurrentMillis(float64(current))
-}
-
-var _ api.ChargerEx = (*Em2GoHome)(nil)
-
-// MaxCurrentMillis implements the api.ChargerEx interface
-func (wb *Em2GoHome) MaxCurrentMillis(current float64) error {
- b := make([]byte, 2)
- binary.BigEndian.PutUint16(b, uint16(10*current))
-
- _, err := wb.conn.WriteMultipleRegisters(em2GoHomeRegCurrentLimit, 1, b)
- return err
-}
-
-var _ api.CurrentGetter = (*Em2GoHome)(nil)
-
-// GetMaxCurrent implements the api.CurrentGetter interface
-func (wb Em2GoHome) GetMaxCurrent() (float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegCurrentLimit, 1)
- if err != nil {
- return 0, err
- }
-
- return float64(binary.BigEndian.Uint16(b)) / 10, err
-}
-
-var _ api.Meter = (*Em2GoHome)(nil)
-
-// CurrentPower implements the api.Meter interface
-func (wb *Em2GoHome) CurrentPower() (float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegPower, 2)
- if err != nil {
- return 0, err
- }
-
- return rs485.RTUUint32ToFloat64(b), nil
-}
-
-var _ api.MeterEnergy = (*Em2GoHome)(nil)
-
-// TotalEnergy implements the api.MeterEnergy interface
-func (wb *Em2GoHome) TotalEnergy() (float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegEnergy, 2)
- if err != nil {
- return 0, err
- }
-
- return rs485.RTUUint32ToFloat64(b) / 10, nil
-}
-
-// getPhaseValues returns 3 register values offset by 2
-func (wb *Em2GoHome) getPhaseValues(reg uint16) (float64, float64, float64, error) {
- var res [3]float64
-
- for i := range 3 {
- b, err := wb.conn.ReadHoldingRegisters(reg+2*uint16(i), 1)
- if err != nil {
- return 0, 0, 0, err
- }
-
- res[i] = float64(binary.BigEndian.Uint16(b)) / 10
- }
-
- return res[0], res[1], res[2], nil
-}
-
-var _ api.PhaseCurrents = (*Em2GoHome)(nil)
-
-// Currents implements the api.PhaseCurrents interface
-func (wb *Em2GoHome) Currents() (float64, float64, float64, error) {
- return wb.getPhaseValues(em2GoHomeRegCurrents)
-}
-
-var _ api.PhaseVoltages = (*Em2GoHome)(nil)
-
-// Currents implements the api.PhaseVoltages interface
-func (wb *Em2GoHome) Voltages() (float64, float64, float64, error) {
- return wb.getPhaseValues(em2GoHomeRegVoltages)
-}
-
-var _ api.ChargeRater = (*Em2GoHome)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (wb *Em2GoHome) ChargedEnergy() (float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegChargedEnergy, 2)
- if err != nil {
- return 0, err
- }
-
- return float64(binary.BigEndian.Uint16(b)) / 10, nil
-}
-
-var _ api.ChargeTimer = (*Em2GoHome)(nil)
-
-// ChargeDuration implements the api.ChargeTimer interface
-func (wb *Em2GoHome) ChargeDuration() (time.Duration, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegChargeDuration, 2)
- if err != nil {
- return 0, err
- }
-
- return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil
-}
-
-// phases1p3p implements the api.PhaseSwitcher interface
-func (wb *Em2GoHome) phases1p3p(phases int) error {
- b := make([]byte, 2)
- binary.BigEndian.PutUint16(b, uint16(phases))
-
- _, err := wb.conn.WriteMultipleRegisters(em2GoHomeRegPhases, 1, b)
- return err
-}
-
-var _ api.Diagnosis = (*Em2GoHome)(nil)
-
-// Diagnose implements the api.Diagnosis interface
-func (wb *Em2GoHome) Diagnose() {
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegStatus, 1); err == nil {
- fmt.Printf("\tCharging Station Status:\t%d\n", binary.BigEndian.Uint16(b))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegConnectorState, 1); err == nil {
- fmt.Printf("\tConnector State:\t%d\n", binary.BigEndian.Uint16(b))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegErrorCode, 1); err == nil {
- fmt.Printf("\tError Code:\t%d\n", binary.BigEndian.Uint16(b))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegMaxCurrent, 1); err == nil {
- fmt.Printf("\tEVSE Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegMaxCurrent, 1); err == nil {
- fmt.Printf("\tEVSE Min. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegCableMaxCurrent, 1); err == nil {
- fmt.Printf("\tCable Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
- }
- var serial []byte
- for reg := 0; reg < 8; reg++ {
- b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegSerial+2*uint16(reg), 2)
- if err != nil {
- return
- }
- serial = append(serial, b...)
- }
- fmt.Printf("\tSerial: %s\n", string(serial))
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegSafeCurrent, 1); err == nil {
- fmt.Printf("\tSafe Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegCommTimeout, 1); err == nil {
- fmt.Printf("\tConnection Timeout:\t%d\n", binary.BigEndian.Uint16(b))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegCurrentLimit, 1); err == nil {
- fmt.Printf("\tCurrent Limit:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegChargeMode, 1); err == nil {
- fmt.Printf("\tCharge Mode:\t%d\n", binary.BigEndian.Uint16(b))
- }
- if b, err := wb.conn.ReadHoldingRegisters(em2GoHomeRegChargeCommand, 1); err == nil {
- fmt.Printf("\tCharge Command:\t%d\n", binary.BigEndian.Uint16(b))
- }
-}
diff --git a/charger/em2go-home_decorators.go b/charger/em2go-home_decorators.go
deleted file mode 100644
index 3f319e1e51..0000000000
--- a/charger/em2go-home_decorators.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package charger
-
-// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
-
-import (
- "github.com/evcc-io/evcc/api"
-)
-
-func decorateEm2GoHome(base *Em2GoHome, phaseSwitcher func(int) error) api.Charger {
- switch {
- case phaseSwitcher == nil:
- return base
-
- case phaseSwitcher != nil:
- return &struct {
- *Em2GoHome
- api.PhaseSwitcher
- }{
- Em2GoHome: base,
- PhaseSwitcher: &decorateEm2GoHomePhaseSwitcherImpl{
- phaseSwitcher: phaseSwitcher,
- },
- }
- }
-
- return nil
-}
-
-type decorateEm2GoHomePhaseSwitcherImpl struct {
- phaseSwitcher func(int) error
-}
-
-func (impl *decorateEm2GoHomePhaseSwitcherImpl) Phases1p3p(p0 int) error {
- return impl.phaseSwitcher(p0)
-}
diff --git a/charger/em2go.go b/charger/em2go.go
index 23cef9883d..693682b77f 100644
--- a/charger/em2go.go
+++ b/charger/em2go.go
@@ -2,7 +2,7 @@ package charger
// LICENSE
-// Copyright (c) 2019-2023 andig
+// Copyright (c) 2019-2024 andig
// This module is NOT covered by the MIT license. All rights reserved.
@@ -28,41 +28,49 @@ import (
"github.com/volkszaehler/mbmd/meters/rs485"
)
-// https://files2.elv.com/public/25/2522/252210/Internet/252210_modbus_tcp_register.pdf
+// https://www.em2go.de/download2/ModBus TCP Registers EM2GO Series.pdf
// Em2Go charger implementation
type Em2Go struct {
+ log *util.Logger
conn *modbus.Connection
}
const (
em2GoRegStatus = 0 // Uint16 RO ENUM
- em2goRegConnectorState = 2 // Uint16 RO ENUM
- em2goRegErrorCode = 4 // Uint16 RO ENUM
- em2GoRegCurrents = 6 // 3xUint16 RO 0.1A
+ em2GoRegConnectorState = 2 // Uint16 RO ENUM
+ em2GoRegErrorCode = 4 // Uint16 RO ENUM
+ em2GoRegCurrents = 6 // Uint16 RO 0.1A
em2GoRegPower = 12 // Uint32 RO 1W
- em2GoRegPowers = 16 // 3xUint16 RO 1W
em2GoRegEnergy = 28 // Uint16 RO 0.1KWh
em2GoRegMaxCurrent = 32 // Uint16 RO 0.1A
em2GoRegMinCurrent = 34 // Uint16 RO 0.1A
- em2goRegCableMaxCurrent = 36 // Uint16 RO 0.1A
+ em2GoRegCableMaxCurrent = 36 // Uint16 RO 0.1A
em2GoRegSerial = 38 // Chr[16] RO UTF16
em2GoRegChargedEnergy = 72 // Uint16 RO 0.1kWh
em2GoRegChargeDuration = 78 // Uint32 RO 1s
- em2goRegSafeCurrent = 87 // Uint16 WR 0.1A
- em2goRegCommTimeout = 89 // Uint16 WR 1s
- em2goRegCurrentLimit = 91 // Uint16 WR 0.1A
+ em2GoRegSafeCurrent = 87 // Uint16 WR 0.1A
+ em2GoRegCommTimeout = 89 // Uint16 WR 1s
+ em2GoRegCurrentLimit = 91 // Uint16 WR 0.1A
em2GoRegChargeMode = 93 // Uint16 WR ENUM
em2GoRegChargeCommand = 95 // Uint16 WR ENUM
- em2goRegVoltages = 109 // 3xUint16 RO 0.1V
+ em2GoRegVoltages = 109 // Uint16 RO 0.1V
+ em2GoRegPhases = 200 // Set charging phase 1 unsigned
)
func init() {
- registry.Add("em2go", NewEm2GoFromConfig)
+ registry.Add("em2go", func(other map[string]any) (api.Charger, error) {
+ return NewEm2GoFromConfig(other, true)
+ })
+ registry.Add("em2go-home", func(other map[string]any) (api.Charger, error) {
+ return NewEm2GoFromConfig(other, false)
+ })
}
+//go:generate go run ../cmd/tools/decorate.go -f decorateEm2Go -b *Em2Go -r api.Charger -t "api.ChargerEx,MaxCurrentMillis,func(float64) error" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.PhaseGetter,GetPhases,func() (int, error)"
+
// NewEm2GoFromConfig creates a Em2Go charger from generic config
-func NewEm2GoFromConfig(other map[string]interface{}) (api.Charger, error) {
+func NewEm2GoFromConfig(other map[string]interface{}, milli bool) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: 255,
}
@@ -71,11 +79,11 @@ func NewEm2GoFromConfig(other map[string]interface{}) (api.Charger, error) {
return nil, err
}
- return NewEm2Go(cc.URI, cc.ID)
+ return NewEm2Go(cc.URI, cc.ID, milli)
}
// NewEm2Go creates Em2Go charger
-func NewEm2Go(uri string, slaveID uint8) (api.Charger, error) {
+func NewEm2Go(uri string, slaveID uint8, milli bool) (api.Charger, error) {
uri = util.DefaultPort(uri, 502)
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
@@ -83,18 +91,33 @@ func NewEm2Go(uri string, slaveID uint8) (api.Charger, error) {
return nil, err
}
+ // Add delay of 60 milliseconds between requests
+ conn.Delay(60 * time.Millisecond)
+
log := util.NewLogger("em2go")
conn.Logger(log.TRACE)
wb := &Em2Go{
+ log: log,
conn: conn,
}
- // set charge on command
- // b := make([]byte, 2)
- // _, err = wb.conn.WriteMultipleRegisters(em2GoRegChargeMode, 1,b)
+ var (
+ maxCurrent func(float64) error
+ phases1p3p func(int) error
+ phasesG func() (int, error)
+ )
+
+ if milli {
+ maxCurrent = wb.maxCurrentMillis
+ }
+
+ if _, err := wb.conn.ReadHoldingRegisters(em2GoRegPhases, 1); err == nil {
+ phases1p3p = wb.phases1p3p
+ phasesG = wb.getPhases
+ }
- return wb, err
+ return decorateEm2Go(wb, maxCurrent, phases1p3p, phasesG), err
}
// Status implements the api.Charger interface
@@ -141,20 +164,30 @@ func (wb *Em2Go) Enable(enable bool) error {
// MaxCurrent implements the api.Charger interface
func (wb *Em2Go) MaxCurrent(current int64) error {
- return wb.MaxCurrentMillis(float64(current))
+ return wb.maxCurrentMillis(float64(current))
}
-var _ api.ChargerEx = (*Em2Go)(nil)
-
-// MaxCurrentMillis implements the api.ChargerEx interface
-func (wb *Em2Go) MaxCurrentMillis(current float64) error {
+// maxCurrentMillis implements the api.ChargerEx interface
+func (wb *Em2Go) maxCurrentMillis(current float64) error {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, uint16(10*current))
- _, err := wb.conn.WriteMultipleRegisters(em2goRegCurrentLimit, 1, b)
+ _, err := wb.conn.WriteMultipleRegisters(em2GoRegCurrentLimit, 1, b)
return err
}
+var _ api.CurrentGetter = (*Em2Go)(nil)
+
+// GetMaxCurrent implements the api.CurrentGetter interface
+func (wb Em2Go) GetMaxCurrent() (float64, error) {
+ b, err := wb.conn.ReadHoldingRegisters(em2GoRegCurrentLimit, 1)
+ if err != nil {
+ return 0, err
+ }
+
+ return float64(binary.BigEndian.Uint16(b)) / 10, err
+}
+
var _ api.Meter = (*Em2Go)(nil)
// CurrentPower implements the api.Meter interface
@@ -176,35 +209,37 @@ func (wb *Em2Go) TotalEnergy() (float64, error) {
return 0, err
}
- return float64(binary.BigEndian.Uint16(b)) / 10, nil
+ return rs485.RTUUint32ToFloat64(b) / 10, nil
+}
+
+// getPhaseValues returns 3 register values offset by 2
+func (wb *Em2Go) getPhaseValues(reg uint16) (float64, float64, float64, error) {
+ var res [3]float64
+
+ for i := range 3 {
+ b, err := wb.conn.ReadHoldingRegisters(reg+2*uint16(i), 1)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+
+ res[i] = float64(binary.BigEndian.Uint16(b)) / 10
+ }
+
+ return res[0], res[1], res[2], nil
}
var _ api.PhaseCurrents = (*Em2Go)(nil)
// Currents implements the api.PhaseCurrents interface
func (wb *Em2Go) Currents() (float64, float64, float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2GoRegCurrents, 6)
- if err != nil {
- return 0, 0, 0, err
- }
-
- return float64(binary.BigEndian.Uint16(b)) / 10,
- float64(binary.BigEndian.Uint16(b[4:])) / 10,
- float64(binary.BigEndian.Uint16(b[8:])) / 10, nil
+ return wb.getPhaseValues(em2GoRegCurrents)
}
var _ api.PhaseVoltages = (*Em2Go)(nil)
// Currents implements the api.PhaseVoltages interface
func (wb *Em2Go) Voltages() (float64, float64, float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(em2goRegVoltages, 6)
- if err != nil {
- return 0, 0, 0, err
- }
-
- return float64(binary.BigEndian.Uint16(b)) / 10,
- float64(binary.BigEndian.Uint16(b[4:])) / 10,
- float64(binary.BigEndian.Uint16(b[8:])) / 10, nil
+ return wb.getPhaseValues(em2GoRegVoltages)
}
var _ api.ChargeRater = (*Em2Go)(nil)
@@ -231,6 +266,25 @@ func (wb *Em2Go) ChargeDuration() (time.Duration, error) {
return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil
}
+// phases1p3p implements the api.PhaseSwitcher interface
+func (wb *Em2Go) phases1p3p(phases int) error {
+ b := make([]byte, 2)
+ binary.BigEndian.PutUint16(b, uint16(phases))
+
+ _, err := wb.conn.WriteMultipleRegisters(em2GoRegPhases, 1, b)
+ return err
+}
+
+// getPhases implements the api.PhaseGetter interface
+func (wb *Em2Go) getPhases() (int, error) {
+ b, err := wb.conn.ReadHoldingRegisters(em2GoRegPhases, 1)
+ if err != nil {
+ return 0, err
+ }
+
+ return int(binary.BigEndian.Uint16(b)), nil
+}
+
var _ api.Diagnosis = (*Em2Go)(nil)
// Diagnose implements the api.Diagnosis interface
@@ -238,19 +292,19 @@ func (wb *Em2Go) Diagnose() {
if b, err := wb.conn.ReadHoldingRegisters(em2GoRegStatus, 1); err == nil {
fmt.Printf("\tCharging Station Status:\t%d\n", binary.BigEndian.Uint16(b))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegConnectorState, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegConnectorState, 1); err == nil {
fmt.Printf("\tConnector State:\t%d\n", binary.BigEndian.Uint16(b))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegErrorCode, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegErrorCode, 1); err == nil {
fmt.Printf("\tError Code:\t%d\n", binary.BigEndian.Uint16(b))
}
if b, err := wb.conn.ReadHoldingRegisters(em2GoRegMaxCurrent, 1); err == nil {
fmt.Printf("\tEVSE Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2GoRegMinCurrent, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegMaxCurrent, 1); err == nil {
fmt.Printf("\tEVSE Min. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegCableMaxCurrent, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegCableMaxCurrent, 1); err == nil {
fmt.Printf("\tCable Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
}
var serial []byte
@@ -262,13 +316,13 @@ func (wb *Em2Go) Diagnose() {
serial = append(serial, b...)
}
fmt.Printf("\tSerial: %s\n", string(serial))
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegSafeCurrent, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegSafeCurrent, 1); err == nil {
fmt.Printf("\tSafe Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegCommTimeout, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegCommTimeout, 1); err == nil {
fmt.Printf("\tConnection Timeout:\t%d\n", binary.BigEndian.Uint16(b))
}
- if b, err := wb.conn.ReadHoldingRegisters(em2goRegCurrentLimit, 1); err == nil {
+ if b, err := wb.conn.ReadHoldingRegisters(em2GoRegCurrentLimit, 1); err == nil {
fmt.Printf("\tCurrent Limit:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10))
}
if b, err := wb.conn.ReadHoldingRegisters(em2GoRegChargeMode, 1); err == nil {
diff --git a/charger/em2go_decorators.go b/charger/em2go_decorators.go
new file mode 100644
index 0000000000..96c4efc79b
--- /dev/null
+++ b/charger/em2go_decorators.go
@@ -0,0 +1,137 @@
+package charger
+
+// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
+
+import (
+ "github.com/evcc-io/evcc/api"
+)
+
+func decorateEm2Go(base *Em2Go, chargerEx func(float64) error, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger {
+ switch {
+ case chargerEx == nil && phaseGetter == nil && phaseSwitcher == nil:
+ return base
+
+ case chargerEx != nil && phaseGetter == nil && phaseSwitcher == nil:
+ return &struct {
+ *Em2Go
+ api.ChargerEx
+ }{
+ Em2Go: base,
+ ChargerEx: &decorateEm2GoChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ }
+
+ case chargerEx == nil && phaseGetter == nil && phaseSwitcher != nil:
+ return &struct {
+ *Em2Go
+ api.PhaseSwitcher
+ }{
+ Em2Go: base,
+ PhaseSwitcher: &decorateEm2GoPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case chargerEx != nil && phaseGetter == nil && phaseSwitcher != nil:
+ return &struct {
+ *Em2Go
+ api.ChargerEx
+ api.PhaseSwitcher
+ }{
+ Em2Go: base,
+ ChargerEx: &decorateEm2GoChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ PhaseSwitcher: &decorateEm2GoPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case chargerEx == nil && phaseGetter != nil && phaseSwitcher == nil:
+ return &struct {
+ *Em2Go
+ api.PhaseGetter
+ }{
+ Em2Go: base,
+ PhaseGetter: &decorateEm2GoPhaseGetterImpl{
+ phaseGetter: phaseGetter,
+ },
+ }
+
+ case chargerEx != nil && phaseGetter != nil && phaseSwitcher == nil:
+ return &struct {
+ *Em2Go
+ api.ChargerEx
+ api.PhaseGetter
+ }{
+ Em2Go: base,
+ ChargerEx: &decorateEm2GoChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ PhaseGetter: &decorateEm2GoPhaseGetterImpl{
+ phaseGetter: phaseGetter,
+ },
+ }
+
+ case chargerEx == nil && phaseGetter != nil && phaseSwitcher != nil:
+ return &struct {
+ *Em2Go
+ api.PhaseGetter
+ api.PhaseSwitcher
+ }{
+ Em2Go: base,
+ PhaseGetter: &decorateEm2GoPhaseGetterImpl{
+ phaseGetter: phaseGetter,
+ },
+ PhaseSwitcher: &decorateEm2GoPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case chargerEx != nil && phaseGetter != nil && phaseSwitcher != nil:
+ return &struct {
+ *Em2Go
+ api.ChargerEx
+ api.PhaseGetter
+ api.PhaseSwitcher
+ }{
+ Em2Go: base,
+ ChargerEx: &decorateEm2GoChargerExImpl{
+ chargerEx: chargerEx,
+ },
+ PhaseGetter: &decorateEm2GoPhaseGetterImpl{
+ phaseGetter: phaseGetter,
+ },
+ PhaseSwitcher: &decorateEm2GoPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+ }
+
+ return nil
+}
+
+type decorateEm2GoChargerExImpl struct {
+ chargerEx func(float64) error
+}
+
+func (impl *decorateEm2GoChargerExImpl) MaxCurrentMillis(p0 float64) error {
+ return impl.chargerEx(p0)
+}
+
+type decorateEm2GoPhaseGetterImpl struct {
+ phaseGetter func() (int, error)
+}
+
+func (impl *decorateEm2GoPhaseGetterImpl) GetPhases() (int, error) {
+ return impl.phaseGetter()
+}
+
+type decorateEm2GoPhaseSwitcherImpl struct {
+ phaseSwitcher func(int) error
+}
+
+func (impl *decorateEm2GoPhaseSwitcherImpl) Phases1p3p(p0 int) error {
+ return impl.phaseSwitcher(p0)
+}
diff --git a/charger/etrel.go b/charger/etrel.go
index cc230bbe8a..2fa84cc4d4 100644
--- a/charger/etrel.go
+++ b/charger/etrel.go
@@ -190,17 +190,8 @@ func (wb *Etrel) MaxCurrentMillis(current float64) error {
return err
}
-var _ api.CurrentGetter = (*Etrel)(nil)
-
-// GetMaxCurrent implements the api.CurrentGetter interface
-func (wb *Etrel) GetMaxCurrent() (float64, error) {
- b, err := wb.conn.ReadHoldingRegisters(wb.base+etrelRegMaxCurrent, 2)
- if err != nil {
- return 0, err
- }
-
- return float64(math.Float32frombits(binary.BigEndian.Uint32(b))), nil
-}
+// removed due to https://github.com/evcc-io/evcc/issues/14507
+// var _ api.CurrentGetter = (*Etrel)(nil)
var _ api.Meter = (*Etrel)(nil)
diff --git a/charger/fritzdect.go b/charger/fritzdect.go
index b0604c2d60..81a0fc9fa1 100644
--- a/charger/fritzdect.go
+++ b/charger/fritzdect.go
@@ -57,14 +57,15 @@ func NewFritzDECT(embed embed, uri, ain, user, password string, standbypower flo
// Status implements the api.Charger interface
func (c *FritzDECT) Status() (api.ChargeStatus, error) {
resp, err := c.conn.ExecCmd("getswitchpresent")
+ if err != nil {
+ return api.StatusNone, err
+ }
- if err == nil {
- var present bool
- present, err = strconv.ParseBool(resp)
- if err == nil && !present {
- err = api.ErrNotAvailable
- }
+ present, err := strconv.ParseBool(resp)
+ if err == nil && !present {
+ err = api.ErrNotAvailable
}
+
if err != nil {
return api.StatusNone, err
}
@@ -91,13 +92,13 @@ func (c *FritzDECT) Enable(enable bool) error {
// on 0/1 - DECT Switch state off/on (empty if unknown or error)
resp, err := c.conn.ExecCmd(cmd)
+ if err != nil {
+ return err
+ }
- var on bool
- if err == nil {
- on, err = strconv.ParseBool(resp)
- if err == nil && enable != on {
- err = errors.New("switch failed")
- }
+ on, err := strconv.ParseBool(resp)
+ if err == nil && enable != on {
+ err = errors.New("switch failed")
}
return err
diff --git a/charger/fronius-wattpilot.go b/charger/fronius-wattpilot.go
index 6dc87aae91..994e3f73a0 100644
--- a/charger/fronius-wattpilot.go
+++ b/charger/fronius-wattpilot.go
@@ -85,7 +85,6 @@ func (c *Wattpilot) Enable(enable bool) error {
if !enable {
forceState = 1 // off
}
-
return c.api.SetProperty("frc", forceState)
}
@@ -101,16 +100,8 @@ func (c *Wattpilot) CurrentPower() (float64, error) {
return c.api.GetPower()
}
-var _ api.ChargeRater = (*Wattpilot)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (c *Wattpilot) ChargedEnergy() (float64, error) {
- resp, err := c.api.GetProperty("wh")
- if err != nil {
- return 0, err
- }
- return resp.(float64) / 1e3, err
-}
+// removed: https://github.com/evcc-io/evcc/issues/13726
+// var _ api.ChargeRater = (*Wattpilot)(nil)
var _ api.PhaseCurrents = (*Wattpilot)(nil)
diff --git a/charger/go-e.go b/charger/go-e.go
index 02fbaf5c21..b920f42e97 100644
--- a/charger/go-e.go
+++ b/charger/go-e.go
@@ -149,17 +149,8 @@ func (c *GoE) CurrentPower() (float64, error) {
return resp.CurrentPower(), err
}
-var _ api.ChargeRater = (*GoE)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (c *GoE) ChargedEnergy() (float64, error) {
- resp, err := c.api.Status()
- if err != nil {
- return 0, err
- }
-
- return resp.ChargedEnergy(), err
-}
+// removed: https://github.com/evcc-io/evcc/issues/13726
+// var _ api.ChargeRater = (*GoE)(nil)
var _ api.PhaseCurrents = (*GoE)(nil)
diff --git a/charger/heidelberg-ec.go b/charger/heidelberg-ec.go
index 514378072a..1a3c882994 100644
--- a/charger/heidelberg-ec.go
+++ b/charger/heidelberg-ec.go
@@ -103,8 +103,8 @@ func NewHeidelbergEC(uri, device, comset string, baudrate int, proto modbus.Prot
if err != nil {
return nil, fmt.Errorf("failsafe timeout: %w", err)
}
- if u := binary.BigEndian.Uint16(b); u > 0 {
- go wb.heartbeat(time.Duration(u) * time.Millisecond / 2)
+ if u := binary.BigEndian.Uint16(b) / 4; u > 0 {
+ go wb.heartbeat(time.Duration(u) * time.Millisecond)
}
return wb, nil
diff --git a/charger/keba-modbus.go b/charger/keba-modbus.go
index d868b26a99..8ef5bb5ae6 100644
--- a/charger/keba-modbus.go
+++ b/charger/keba-modbus.go
@@ -36,6 +36,7 @@ import (
// Keba is an api.Charger implementation
type Keba struct {
+ *embed
log *util.Logger
conn *modbus.Connection
}
@@ -68,15 +69,20 @@ func init() {
// NewKebaFromConfig creates a new Keba ModbusTCP charger
func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
- cc := modbus.TcpSettings{
- ID: 255,
+ cc := struct {
+ embed `mapstructure:",squash"`
+ modbus.TcpSettings `mapstructure:",squash"`
+ }{
+ TcpSettings: modbus.TcpSettings{
+ ID: 255,
+ },
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
- wb, err := NewKeba(cc.URI, cc.ID)
+ wb, err := NewKeba(cc.embed, cc.URI, cc.ID)
if err != nil {
return nil, err
}
@@ -129,7 +135,7 @@ func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
}
// NewKeba creates a new charger
-func NewKeba(uri string, slaveID uint8) (*Keba, error) {
+func NewKeba(embed embed, uri string, slaveID uint8) (*Keba, error) {
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
if err != nil {
return nil, err
@@ -143,8 +149,9 @@ func NewKeba(uri string, slaveID uint8) (*Keba, error) {
conn.Logger(log.TRACE)
wb := &Keba{
- log: log,
- conn: conn,
+ embed: &embed,
+ log: log,
+ conn: conn,
}
return wb, err
diff --git a/charger/mcc.go b/charger/mcc.go
deleted file mode 100644
index bc1a3fe19b..0000000000
--- a/charger/mcc.go
+++ /dev/null
@@ -1,361 +0,0 @@
-package charger
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- "github.com/evcc-io/evcc/api"
- "github.com/evcc-io/evcc/util"
- "github.com/evcc-io/evcc/util/request"
- "github.com/evcc-io/evcc/util/transport"
-)
-
-const (
- mccAPILogin = "jwt/login"
- mccAPIRefresh = "jwt/refresh"
- mccAPIChargeState = "v1/api/WebServer/properties/chargeState"
- mccAPICurrentSession = "v1/api/WebServer/properties/swaggerCurrentSession"
- mccAPIEnergy = "v1/api/iCAN/properties/propjIcanEnergy"
- mccAPISetCurrentLimit = "v1/api/SCC/properties/propHMICurrentLimit?value="
- mccAPICurrentCableInformation = "v1/api/SCC/properties/json_CurrentCableInformation"
-)
-
-// MCCTokenResponse is the apiLogin response
-type MCCTokenResponse struct {
- Token string
- Error string
-}
-
-// MCCCurrentSession is the apiCurrentSession response
-type MCCCurrentSession struct {
- Duration int64
- EnergySumKwh float64
-}
-
-// MCCEnergyPhase is the apiEnergy response for a single phase
-type MCCEnergyPhase struct {
- Ampere float64
- Power float64
-}
-
-// MCCEnergy is the apiEnergy response
-type MCCEnergy struct {
- L1, L2, L3 MCCEnergyPhase
-}
-
-// MCCCurrentCableInformation is the apiCurrentCableInformation response
-type MCCCurrentCableInformation struct {
- MaxValue, MinValue, Value int64
-}
-
-// MobileConnect charger supporting devices from Audi, Bentley, Porsche
-type MobileConnect struct {
- *request.Helper
- uri string
- password string
- token string
- tokenExpiry time.Time
- cableInformation MCCCurrentCableInformation
-}
-
-func init() {
- registry.Add("mcc", NewMobileConnectFromConfig)
-}
-
-// NewMobileConnectFromConfig creates a MCC charger from generic config
-func NewMobileConnectFromConfig(other map[string]interface{}) (api.Charger, error) {
- var cc struct {
- URI, Password string
- }
- if err := util.DecodeOther(other, &cc); err != nil {
- return nil, err
- }
-
- return NewMobileConnect(util.DefaultScheme(cc.URI, "https"), cc.Password)
-}
-
-// NewMobileConnect creates MCC charger
-func NewMobileConnect(uri, password string) (*MobileConnect, error) {
- log := util.NewLogger("mcc")
-
- mcc := &MobileConnect{
- Helper: request.NewHelper(log),
- uri: strings.TrimRight(uri, "/"),
- password: password,
- }
-
- // ignore the self signed certificate
- mcc.Client.Transport = request.NewTripper(log, transport.Insecure())
-
- return mcc, nil
-}
-
-// construct the URL for a given api
-func (mcc *MobileConnect) apiURL(api string) string {
- return fmt.Sprintf("%s/%s", mcc.uri, api)
-}
-
-// process the http request to fetch the auth token for a login or refresh request
-func (mcc *MobileConnect) fetchToken(request *http.Request) error {
- var tr MCCTokenResponse
- err := mcc.DoJSON(request, &tr)
- if err == nil {
- if len(tr.Token) == 0 {
- return fmt.Errorf("response: %s", tr.Error)
- }
-
- mcc.token = tr.Token
- // According to tests, the token is valid for 10 minutes
- // but the web interface updates the token every 2 minutes, so let's enforce this
- mcc.tokenExpiry = time.Now().Add(2 * time.Minute)
- }
-
- return err
-}
-
-// login as the home user with the given password
-func (mcc *MobileConnect) login() error {
- uri := fmt.Sprintf("%s/%s", mcc.uri, mccAPILogin)
-
- data := url.Values{
- "user": []string{"user"},
- "pass": []string{mcc.password},
- }
-
- req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), map[string]string{
- "Referer": fmt.Sprintf("%s/login", mcc.uri),
- "Content-Type": "application/x-www-form-urlencoded",
- })
- if err != nil {
- return err
- }
-
- return mcc.fetchToken(req)
-}
-
-// refresh the auth token with a new one
-func (mcc *MobileConnect) refresh() error {
- uri := fmt.Sprintf("%s/%s", mcc.uri, mccAPIRefresh)
-
- req, err := http.NewRequest(http.MethodGet, uri, nil)
- if err != nil {
- return err
- }
-
- req.Header.Set("Referer", fmt.Sprintf("%s/login", mcc.uri))
- req.Header.Set("Authorization", "Bearer "+mcc.token)
-
- return mcc.fetchToken(req)
-}
-
-// creates a http request that contains the auth token
-func (mcc *MobileConnect) request(method, uri string) (*http.Request, error) {
- // do we need a token refresh?
- if mcc.token != "" {
- // is it time to refresh the token?
- if time.Until(mcc.tokenExpiry) < 10*time.Second {
- if err := mcc.refresh(); err != nil {
- // if refreshing the token fails it most likely is expired
- // hence a new login is required, so let's enforce this
- // and ignore this error
- mcc.token = ""
- }
- }
- }
-
- // do we need to login?
- if mcc.token == "" {
- if err := mcc.login(); err != nil {
- return nil, err
- }
- }
-
- // now lets process the request with the fetched token
- req, err := http.NewRequest(method, uri, nil)
- if err != nil {
- return req, err
- }
-
- req.Header.Set("Authorization", "Bearer "+mcc.token)
- req.Header.Set("Referer", fmt.Sprintf("%s/dashboard", mcc.uri))
-
- return req, nil
-}
-
-// use http GET to fetch a non structured value from an URI and stores it in result
-func (mcc *MobileConnect) getValue(uri string) ([]byte, error) {
- req, err := mcc.request(http.MethodGet, uri)
- if err != nil {
- return nil, err
- }
-
- return mcc.DoBody(req)
-}
-
-// use http GET to fetch an escaped JSON string and unmarshal the data in result
-func (mcc *MobileConnect) getEscapedJSON(uri string, result interface{}) error {
- req, err := mcc.request(http.MethodGet, uri)
- if err != nil {
- return err
- }
-
- b, err := mcc.DoBody(req)
- if err != nil {
- return err
- }
-
- s, err := strconv.Unquote(strings.Trim(string(b), "\n"))
- if err != nil {
- return err
- }
-
- if s == "" {
- return nil // empty response
- }
-
- return json.Unmarshal([]byte(s), &result)
-}
-
-// Status implements the api.Charger interface
-func (mcc *MobileConnect) Status() (api.ChargeStatus, error) {
- b, err := mcc.getValue(mcc.apiURL(mccAPIChargeState))
- if err != nil {
- return api.StatusNone, err
- }
-
- chargeState, err := strconv.ParseInt(strings.Trim(string(b), "\n"), 10, 8)
- if err != nil {
- return api.StatusNone, err
- }
-
- switch chargeState {
- case 0: // Unplugged
- return api.StatusA, nil
- case 1, 3, 4, 6: // 1: Connecting, 3: Established, 4: Paused, 6: Finished
- return api.StatusB, nil
- case 2: // Error
- return api.StatusF, nil
- case 5: // Active
- return api.StatusC, nil
- default:
- return api.StatusNone, fmt.Errorf("properties unknown result: %d", chargeState)
- }
-}
-
-// Enabled implements the api.Charger interface
-func (mcc *MobileConnect) Enabled() (bool, error) {
- // Check if the car is connected and Paused, Active, or Finished
- b, err := mcc.getValue(mcc.apiURL(mccAPIChargeState))
- if err != nil {
- return false, err
- }
-
- // return value is returned in the format 0\n
- chargeState, err := strconv.ParseInt(strings.Trim(string(b), "\n"), 10, 8)
- if err != nil {
- return false, err
- }
-
- if chargeState >= 4 && chargeState <= 6 {
- return true, nil
- }
-
- return false, nil
-}
-
-// Enable implements the api.Charger interface
-func (mcc *MobileConnect) Enable(enable bool) error {
- // As we don't know of the API to disable charging this for now always returns an error
- return nil
-}
-
-// MaxCurrent implements the api.Charger interface
-func (mcc *MobileConnect) MaxCurrent(current int64) error {
- // The device doesn't return an error if we set a value greater than the
- // current allowed max or smaller than the allowed min
- // instead it will simply set it to max or min and return "OK" anyway
- // Since the API here works differently, we fetch the limits
- // and then return an error if the value is outside of the limits or
- // otherwise set the new value
- if mcc.cableInformation.MaxValue == 0 {
- if err := mcc.getEscapedJSON(mcc.apiURL(mccAPICurrentCableInformation), &mcc.cableInformation); err != nil {
- return err
- }
- }
-
- if current < mcc.cableInformation.MinValue {
- return fmt.Errorf("value is lower than the allowed minimum value %d", mcc.cableInformation.MinValue)
- }
-
- if current > mcc.cableInformation.MaxValue {
- return fmt.Errorf("value is higher than the allowed maximum value %d", mcc.cableInformation.MaxValue)
- }
-
- url := fmt.Sprintf("%s%d", mcc.apiURL(mccAPISetCurrentLimit), current)
-
- req, err := mcc.request(http.MethodPut, url)
- if err != nil {
- return err
- }
-
- b, err := mcc.DoBody(req)
- if err != nil {
- return err
- }
-
- // return value is returned in the format "OK"\n
- if strings.Trim(string(b), "\n\"") != "OK" {
- return fmt.Errorf("maxcurrent unexpected response: %s", string(b))
- }
-
- return nil
-}
-
-var _ api.Meter = (*MobileConnect)(nil)
-
-// CurrentPower implements the api.Meter interface
-func (mcc *MobileConnect) CurrentPower() (float64, error) {
- var energy MCCEnergy
- err := mcc.getEscapedJSON(mcc.apiURL(mccAPIEnergy), &energy)
-
- return energy.L1.Power + energy.L2.Power + energy.L3.Power, err
-}
-
-var _ api.ChargeRater = (*MobileConnect)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (mcc *MobileConnect) ChargedEnergy() (float64, error) {
- var currentSession MCCCurrentSession
- if err := mcc.getEscapedJSON(mcc.apiURL(mccAPICurrentSession), ¤tSession); err != nil {
- return 0, err
- }
-
- return currentSession.EnergySumKwh, nil
-}
-
-var _ api.ChargeTimer = (*MobileConnect)(nil)
-
-// ChargeDuration implements the api.ChargeTimer interface
-func (mcc *MobileConnect) ChargeDuration() (time.Duration, error) {
- var currentSession MCCCurrentSession
- if err := mcc.getEscapedJSON(mcc.apiURL(mccAPICurrentSession), ¤tSession); err != nil {
- return 0, err
- }
-
- return time.Duration(currentSession.Duration) * time.Second, nil
-}
-
-var _ api.PhaseCurrents = (*MobileConnect)(nil)
-
-// Currents implements the api.PhaseCurrents interface
-func (mcc *MobileConnect) Currents() (float64, float64, float64, error) {
- var energy MCCEnergy
- err := mcc.getEscapedJSON(mcc.apiURL(mccAPIEnergy), &energy)
-
- return energy.L1.Ampere, energy.L2.Ampere, energy.L3.Ampere, err
-}
diff --git a/charger/mcc_test.go b/charger/mcc_test.go
deleted file mode 100644
index 1cfa41ba52..0000000000
--- a/charger/mcc_test.go
+++ /dev/null
@@ -1,441 +0,0 @@
-package charger
-
-import (
- "bytes"
- "io"
- "net/http"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/evcc-io/evcc/api"
- "github.com/evcc-io/evcc/util"
- "github.com/evcc-io/evcc/util/request"
-)
-
-// HTTP testing appproach from http://hassansin.github.io/Unit-Testing-http-client-in-Go
-type roundTripFunc func(req *http.Request) *http.Response
-
-// RoundTrip .
-func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
- return f(req), nil
-}
-
-// apiResponse helps to map an API Call to a test response
-type apiResponse struct {
- apiCall string
- apiResponse string
-}
-
-// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
-func NewTestClient(fn roundTripFunc) *http.Client {
- return &http.Client{
- Transport: fn,
- }
-}
-
-// NewTestMobileConnect .
-func NewTestMobileConnect(t *testing.T, responses []apiResponse) *MobileConnect {
- mcc := &MobileConnect{
- Helper: request.NewHelper(util.NewLogger("foo")),
- uri: "http://192.0.2.2:502",
- password: "none",
- token: "token",
- tokenExpiry: time.Now().Add(10 * time.Minute),
- }
-
- mcc.Client = NewTestClient(func(req *http.Request) *http.Response {
- // Each method may have multiple API calls, so we need to finde the proper
- // response string for the currently invoked call
- var responseString string
- for _, s := range responses {
- if strings.Contains("/"+s.apiCall, req.URL.Path) {
- responseString = s.apiResponse
- }
- }
-
- return &http.Response{
- StatusCode: http.StatusOK,
- // Send response to be tested
- Body: io.NopCloser(bytes.NewBufferString(responseString)),
- // Must be set to non-nil value or it panics
- Header: make(http.Header),
- }
- })
-
- return mcc
-}
-
-func TestMobileConnectLogin(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- password string
- wantErr bool
- }{
- // test cases for software version 2914
- {"login - success", []apiResponse{{mccAPILogin, "{\n \"token\": \"1234567890._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n}"}}, "password", false},
- {"login - wrong password", []apiResponse{{mccAPILogin, "{\n \"error\": \"wrong password\"\n}"}}, "wrong", true},
- {"login - bad return", []apiResponse{{mccAPILogin, "{{\n \"error\": \"wrong password\"\n}"}}, "wrong", true},
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- if err := mcc.login(); (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.login() error = %v, wantErr %v", err, tc.wantErr)
- }
- })
- }
-}
-
-func TestMobileConnectRefresh(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- wantErr bool
- }{
- // test cases for software version 2914
- {"refresh - success", []apiResponse{{mccAPIRefresh, "{\n \"token\": \"1234567890._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n}"}}, false},
- {"refresh - wrong password", []apiResponse{{mccAPIRefresh, "{\n \"error\": \"signature mismatch: OP-gWPOgQ9fdKujMgRNHkeH4WHqYrHe3Z2RqVXeUEuw1\"\n}"}}, true},
- {"refresh - bad return", []apiResponse{{mccAPIRefresh, "{{\n \"error\": \"\"\n}"}}, true},
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- if err := mcc.refresh(); (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.login() error = %v, wantErr %v", err, tc.wantErr)
- }
- })
- }
-}
-
-func TestMobileConnectStatus(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- want api.ChargeStatus
- wantErr bool
- }{
- // test cases for software version 2914
- {"home plug - Unexpected API response", []apiResponse{{mccAPIChargeState, "abc"}}, api.StatusNone, true},
- {"home plug - Unplugged", []apiResponse{{mccAPIChargeState, "0\n"}}, api.StatusA, false},
- {"home plug - Connecting", []apiResponse{{mccAPIChargeState, "1\n"}}, api.StatusB, false},
- {"home plug - Error", []apiResponse{{mccAPIChargeState, "2\n"}}, api.StatusF, false},
- {"home plug - Established", []apiResponse{{mccAPIChargeState, "3\n"}}, api.StatusB, false},
- {"home plug - Paused", []apiResponse{{mccAPIChargeState, "4\n"}}, api.StatusB, false},
- {"home plug - Active", []apiResponse{{mccAPIChargeState, "5\n"}}, api.StatusC, false},
- {"home plug - Finished", []apiResponse{{mccAPIChargeState, "6\n"}}, api.StatusB, false},
- {"home plug - Unexpected status value", []apiResponse{{mccAPIChargeState, "10\n"}}, api.StatusNone, true},
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- got, err := mcc.Status()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.Status() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if !reflect.DeepEqual(got, tc.want) {
- t.Errorf("MobileConnect.Status() = %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestMobileConnectEnabled(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- want bool
- wantErr bool
- }{
- // test cases for software version 2914
- {"home plug - Unexpected API response", []apiResponse{{mccAPIChargeState, "abc"}}, false, true},
- {"home plug - Unplugged", []apiResponse{{mccAPIChargeState, "0\n"}}, false, false},
- {"home plug - Connecting", []apiResponse{{mccAPIChargeState, "1\n"}}, false, false},
- {"home plug - Error", []apiResponse{{mccAPIChargeState, "2\n"}}, false, false},
- {"home plug - Established", []apiResponse{{mccAPIChargeState, "3\n"}}, false, false},
- {"home plug - Paused", []apiResponse{{mccAPIChargeState, "4\n"}}, true, false},
- {"home plug - Active", []apiResponse{{mccAPIChargeState, "5\n"}}, true, false},
- {"home plug - Finished", []apiResponse{{mccAPIChargeState, "6\n"}}, true, false},
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- got, err := mcc.Enabled()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.Enabled() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if got != tc.want {
- t.Errorf("MobileConnect.Enabled() = %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestMobileConnectMaxCurrent(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- current int64
- wantErr bool
- }{
- // test cases for software version 2914
- {
- "home plug - success min value",
- []apiResponse{
- {mccAPICurrentCableInformation, "\"{\\\"carCable\\\":5,\\\"gridCable\\\":8,\\\"hwfpMaxLimit\\\":32,\\\"maxValue\\\":10,\\\"minValue\\\":6,\\\"value\\\":10}\""},
- {mccAPISetCurrentLimit, "\"OK\"\n"},
- },
- 6, false,
- },
- {
- "home plug - success max value",
- []apiResponse{
- {mccAPICurrentCableInformation, "\"{\\\"carCable\\\":5,\\\"gridCable\\\":8,\\\"hwfpMaxLimit\\\":32,\\\"maxValue\\\":10,\\\"minValue\\\":6,\\\"value\\\":10}\""},
- {mccAPISetCurrentLimit, "\"OK\"\n"},
- },
- 10, false,
- },
- {
- "home plug - error value too small",
- []apiResponse{
- {mccAPICurrentCableInformation, "\"{\\\"carCable\\\":5,\\\"gridCable\\\":8,\\\"hwfpMaxLimit\\\":32,\\\"maxValue\\\":10,\\\"minValue\\\":6,\\\"value\\\":10}\""},
- },
- 0, true,
- },
- {
- "home plug - error value too big",
- []apiResponse{
- {mccAPICurrentCableInformation, "\"{\\\"carCable\\\":5,\\\"gridCable\\\":8,\\\"hwfpMaxLimit\\\":32,\\\"maxValue\\\":10,\\\"minValue\\\":6,\\\"value\\\":10}\""},
- },
- 16, true,
- },
- {
- "home plug - 1st API success but 2nd API error",
- []apiResponse{
- {mccAPICurrentCableInformation, "\"{\\\"carCable\\\":5,\\\"gridCable\\\":8,\\\"hwfpMaxLimit\\\":32,\\\"maxValue\\\":10,\\\"minValue\\\":6,\\\"value\\\":10}\""},
- {mccAPISetCurrentLimit, "Unexpected response"},
- },
- 10, true,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- if err := mcc.MaxCurrent(tc.current); (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.MaxCurrent() error = %v, wantErr %v", err, tc.wantErr)
- }
- })
- }
-}
-
-func TestMobileConnectCurrentPower(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- want float64
- wantErr bool
- }{
- // test cases for software version 2914
- {
- "home plug - charging",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 9.9000000000000004,\\n \\\"Power\\\": 2308,\\n \\\"Volts\\\": 230.5\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 13.700000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 13.9\\n }\\n}\\n\""},
- },
- 2308, false,
- },
- {
- "3 phase low power - charging",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 7,\\n \\\"Volts\\\": 244.40000000000001\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 242.10000000000002\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 1,\\n \\\"Volts\\\": 242.30000000000001\\n }\\n}\\n\""},
- },
- 8, false,
- },
- {
- "no data response",
- []apiResponse{
- {mccAPIEnergy, "\"\"\n"},
- },
- 0, false,
- },
- {
- "home plug - error response",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""},
- },
- 0, false,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- got, err := mcc.CurrentPower()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.CurrentPower() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if got != tc.want {
- t.Errorf("MobileConnect.CurrentPower() = %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestMobileConnectChargedEnergy(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- want float64
- wantErr bool
- }{
- // test cases for software version 2914
- {
- "valid response",
- []apiResponse{
- {mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""},
- },
- 18.832000000000001, false,
- },
- {
- "no data response",
- []apiResponse{
- {mccAPICurrentSession, "\"\"\n"},
- },
- 0, false,
- },
- {
- "error response",
- []apiResponse{
- {mccAPICurrentSession, "invalidjson"},
- },
- 0, true,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- got, err := mcc.ChargedEnergy()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.ChargedEnergy() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if got != tc.want {
- t.Errorf("MobileConnect.ChargedEnergy() = %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestMobileConnectChargingTime(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- want time.Duration
- wantErr bool
- }{
- {
- "valid response",
- []apiResponse{
- {mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""},
- },
- 30789 * time.Second, false,
- },
- {
- "no data response",
- []apiResponse{
- {mccAPICurrentSession, "\"\"\n"},
- },
- 0, false,
- },
- {
- "error response",
- []apiResponse{
- {mccAPICurrentSession, "invalidjson"},
- },
- 0, true,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- got, err := mcc.ChargeDuration()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.ChargeDuration() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if got != tc.want {
- t.Errorf("MobileConnect.ChargeDuration() = %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestMobileConnectCurrents(t *testing.T) {
- tests := []struct {
- name string
- responses []apiResponse
- wantL1, wantL2, wantL3 float64
- wantErr bool
- }{
- // test cases for software version 2914
- {
- "home plug - charging",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 9.9000000000000004,\\n \\\"Power\\\": 2308,\\n \\\"Volts\\\": 230.5\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 13.700000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 13.9\\n }\\n}\\n\""},
- },
- 9.9000000000000004, 0, 0, false,
- },
- {
- "3 phase low power - charging",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 7,\\n \\\"Volts\\\": 244.40000000000001\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 242.10000000000002\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0.5,\\n \\\"Power\\\": 1,\\n \\\"Volts\\\": 242.30000000000001\\n }\\n}\\n\""},
- },
- 0.5, 0.5, 0.5, false,
- },
- {
- "no data response",
- []apiResponse{
- {mccAPIEnergy, "\"\"\n"},
- },
- 0, 0, 0, false,
- },
- {
- "home plug - error response",
- []apiResponse{
- {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""},
- },
- 0, 0, 0, false,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mcc := NewTestMobileConnect(t, tc.responses)
-
- gotL1, gotL2, gotL3, err := mcc.Currents()
- if (err != nil) != tc.wantErr {
- t.Errorf("MobileConnect.Currents() error = %v, wantErr %v", err, tc.wantErr)
- return
- }
- if gotL1 != tc.wantL1 {
- t.Errorf("MobileConnect.Currents() = %v, want %v", gotL1, tc.wantL1)
- }
- if gotL2 != tc.wantL2 {
- t.Errorf("MobileConnect.Currents() = %v, want %v", gotL2, tc.wantL2)
- }
- if gotL3 != tc.wantL3 {
- t.Errorf("MobileConnect.Currents() = %v, want %v", gotL3, tc.wantL3)
- }
- })
- }
-}
diff --git a/charger/ocpp.go b/charger/ocpp.go
index b98c6758f1..d31802d1e1 100644
--- a/charger/ocpp.go
+++ b/charger/ocpp.go
@@ -24,17 +24,17 @@ type OCPP struct {
log *util.Logger
conn *ocpp.Connector
idtag string
- enabled bool
phases int
current float64
meterValuesSample string
timeout time.Duration
phaseSwitching bool
+ remoteStart bool
chargingRateUnit types.ChargingRateUnitType
lp loadpoint.API
}
-const defaultIdTag = "evcc"
+const defaultIdTag = "evcc" // RemoteStartTransaction only
func init() {
registry.Add("ocpp", NewOCPPFromConfig)
@@ -53,6 +53,9 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
BootNotification *bool
GetConfiguration *bool
ChargingRateUnit string
+ AutoStart bool // deprecated, to be removed
+ NoStop bool // deprecated, to be removed
+ RemoteStart bool
}{
Connector: 1,
IdTag: defaultIdTag,
@@ -70,7 +73,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
c, err := NewOCPP(cc.StationId, cc.Connector, cc.IdTag,
cc.MeterValues, cc.MeterInterval,
- boot, noConfig,
+ boot, noConfig, cc.RemoteStart,
cc.ConnectTimeout, cc.Timeout, cc.ChargingRateUnit)
if err != nil {
return c, err
@@ -91,20 +94,35 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
currentsG = c.currents
}
+ var voltagesG func() (float64, float64, float64, error)
+ if c.hasMeasurement(types.MeasurandVoltage + ".L3") {
+ voltagesG = c.voltages
+ }
+
var phasesS func(int) error
if c.phaseSwitching {
phasesS = c.phases1p3p
}
- return decorateOCPP(c, powerG, totalEnergyG, currentsG, phasesS), nil
+ var socG func() (float64, error)
+ if c.hasMeasurement(types.MeasurandSoC) {
+ socG = c.soc
+ }
+
+ //var currentG func() (float64, error)
+ //if c.hasMeasurement(types.MeasurandCurrentOffered) {
+ // currentG = c.getMaxCurrent
+ //}
+
+ return decorateOCPP(c, powerG, totalEnergyG, currentsG, voltagesG, phasesS, socG), nil
}
-//go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -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.PhaseSwitcher,Phases1p3p,func(int) error"
+//go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -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.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Battery,Soc,func() (float64, error)"
// NewOCPP creates OCPP charger
func NewOCPP(id string, connector int, idtag string,
meterValues string, meterInterval time.Duration,
- boot, noConfig bool,
+ boot, noConfig, remoteStart bool,
connectTimeout, timeout time.Duration,
chargingRateUnit string,
) (*OCPP, error) {
@@ -132,10 +150,11 @@ func NewOCPP(id string, connector int, idtag string,
}
c := &OCPP{
- log: log,
- conn: conn,
- idtag: idtag,
- timeout: timeout,
+ log: log,
+ conn: conn,
+ idtag: idtag,
+ remoteStart: remoteStart,
+ timeout: timeout,
}
c.log.DEBUG.Printf("waiting for chargepoint: %v", connectTimeout)
@@ -227,7 +246,7 @@ func NewOCPP(id string, connector int, idtag string,
}
case ocpp.KeyChargingScheduleAllowedChargingRateUnit:
- if *opt.Value == "W" {
+ if *opt.Value == "W" || *opt.Value == "Power" {
c.chargingRateUnit = types.ChargingRateUnitWatts
}
}
@@ -287,6 +306,13 @@ func (c *OCPP) hasMeasurement(val types.Measurand) bool {
return slices.Contains(strings.Split(c.meterValuesSample, ","), string(val))
}
+func (c *OCPP) effectiveIdTag() string {
+ if idtag := c.conn.IdTag(); idtag != "" {
+ return idtag
+ }
+ return c.idtag
+}
+
// configure updates CP configuration
func (c *OCPP) configure(key, val string) error {
rc := make(chan error, 1)
@@ -317,72 +343,56 @@ func (c *OCPP) wait(err error, rc chan error) error {
// Status implements the api.Charger interface
func (c *OCPP) Status() (api.ChargeStatus, error) {
+ if c.remoteStart {
+ needtxn, err := c.conn.NeedsTransaction()
+ if err != nil {
+ return api.StatusNone, err
+ }
+
+ if needtxn {
+ // lock the cable by starting remote transaction after vehicle connected
+ if err := c.initTransaction(); err != nil {
+ return api.StatusNone, err
+ }
+ }
+ }
+
return c.conn.Status()
}
// Enabled implements the api.Charger interface
func (c *OCPP) Enabled() (bool, error) {
- return c.enabled, nil
-}
-
-// Enable implements the api.Charger interface
-func (c *OCPP) Enable(enable bool) (err error) {
- rc := make(chan error, 1)
- txn, err := c.conn.TransactionID()
+ current, err := c.getCurrent()
- defer func() {
- if err == nil {
- c.enabled = enable
- }
- }()
+ return current > 0, err
+}
+func (c *OCPP) Enable(enable bool) error {
+ var current float64
if enable {
- if txn > 0 {
- // we have the transaction id, treat as enabled
- return nil
- }
-
- err = ocpp.Instance().RemoteStartTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) {
- if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted {
- err = errors.New(string(resp.Status))
- }
+ current = c.current
+ }
- rc <- err
- }, c.idtag, func(request *core.RemoteStartTransactionRequest) {
- connector := c.conn.ID()
- request.ConnectorId = &connector
- request.ChargingProfile = c.getTxChargingProfile(c.current, 0)
- })
- } else {
- // if no transaction is running, the vehicle may have stopped it (which is ok) or an unknown transaction is running
- if txn == 0 {
- // we cannot tell if a transaction is really running, so we check the status
- status, err := c.Status()
- if err != nil {
- return err
- }
- if status == api.StatusC {
- return errors.New("cannot disable: unknown transaction running")
- }
+ return c.setCurrent(current)
+}
- return nil
+func (c *OCPP) initTransaction() error {
+ rc := make(chan error, 1)
+ err := ocpp.Instance().RemoteStartTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) {
+ if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted {
+ err = errors.New(string(resp.Status))
}
- err = ocpp.Instance().RemoteStopTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStopTransactionConfirmation, err error) {
- if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted {
- err = errors.New(string(resp.Status))
- }
-
- rc <- err
- }, txn)
- }
+ rc <- err
+ }, c.effectiveIdTag(), func(request *core.RemoteStartTransactionRequest) {
+ connector := c.conn.ID()
+ request.ConnectorId = &connector
+ })
return c.wait(err, rc)
}
func (c *OCPP) setChargingProfile(profile *types.ChargingProfile) error {
- connector := c.conn.ID()
-
rc := make(chan error, 1)
err := ocpp.Instance().SetChargingProfile(c.conn.ChargePoint().ID(), func(resp *smartcharging.SetChargingProfileConfirmation, err error) {
if err == nil && resp != nil && resp.Status != smartcharging.ChargingProfileStatusAccepted {
@@ -390,34 +400,53 @@ func (c *OCPP) setChargingProfile(profile *types.ChargingProfile) error {
}
rc <- err
- }, connector, profile)
+ }, c.conn.ID(), profile)
return c.wait(err, rc)
}
-// updatePeriod sets a single charging schedule period with given current
-func (c *OCPP) updatePeriod(current float64) error {
- // current period can only be updated if transaction is active
- if enabled, err := c.Enabled(); err != nil || !enabled {
- return err
- }
-
- txn, err := c.conn.TransactionID()
+// setCurrent sets the TxDefaultChargingProfile with given current
+func (c *OCPP) setCurrent(current float64) error {
+ err := c.setChargingProfile(c.createTxDefaultChargingProfile(math.Trunc(10*current) / 10))
if err != nil {
- return err
+ err = fmt.Errorf("set charging profile: %w", err)
}
- current = math.Trunc(10*current) / 10
+ return err
+}
+
+// getCurrent returns the internal current offered by the chargepoint
+func (c *OCPP) getCurrent() (float64, error) {
+ var current float64
- err = c.setChargingProfile(c.getTxChargingProfile(current, txn))
- if err != nil {
- err = fmt.Errorf("set charging profile: %w", err)
+ if c.hasMeasurement(types.MeasurandCurrentOffered) {
+ return c.getMaxCurrent()
}
- return err
+ // fallback to GetCompositeSchedule request
+ rc := make(chan error, 1)
+ err := ocpp.Instance().GetCompositeSchedule(c.conn.ChargePoint().ID(), func(resp *smartcharging.GetCompositeScheduleConfirmation, err error) {
+ if err == nil && resp != nil && resp.Status != smartcharging.GetCompositeScheduleStatusAccepted {
+ err = errors.New(string(resp.Status))
+ }
+
+ if err == nil {
+ if resp.ChargingSchedule != nil && len(resp.ChargingSchedule.ChargingSchedulePeriod) > 0 {
+ current = resp.ChargingSchedule.ChargingSchedulePeriod[0].Limit
+ } else {
+ err = fmt.Errorf("invalid ChargingSchedule")
+ }
+ }
+
+ rc <- err
+ }, c.conn.ID(), 1)
+
+ err = c.wait(err, rc)
+
+ return current, err
}
-func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.ChargingProfile {
+func (c *OCPP) createTxDefaultChargingProfile(current float64) *types.ChargingProfile {
phases := c.phases
period := types.NewChargingSchedulePeriod(0, current)
if c.chargingRateUnit == types.ChargingRateUnitWatts {
@@ -437,10 +466,9 @@ func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.C
}
return &types.ChargingProfile{
- ChargingProfileId: 1,
- TransactionId: transactionId,
+ ChargingProfileId: 0,
StackLevel: 0,
- ChargingProfilePurpose: types.ChargingProfilePurposeTxProfile,
+ ChargingProfilePurpose: types.ChargingProfilePurposeTxDefaultProfile,
ChargingProfileKind: types.ChargingProfileKindRelative,
ChargingSchedule: &types.ChargingSchedule{
ChargingRateUnit: c.chargingRateUnit,
@@ -458,43 +486,56 @@ var _ api.ChargerEx = (*OCPP)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (c *OCPP) MaxCurrentMillis(current float64) error {
- err := c.updatePeriod(current)
+ err := c.setCurrent(current)
if err == nil {
c.current = current
}
return err
}
-// CurrentPower implements the api.Meter interface
+// getMaxCurrent implements the api.CurrentGetter interface
+func (c *OCPP) getMaxCurrent() (float64, error) {
+ return c.conn.GetMaxCurrent()
+}
+
+// currentPower implements the api.Meter interface
func (c *OCPP) currentPower() (float64, error) {
return c.conn.CurrentPower()
}
-// TotalEnergy implements the api.MeterTotal interface
+// totalEnergy implements the api.MeterTotal interface
func (c *OCPP) totalEnergy() (float64, error) {
return c.conn.TotalEnergy()
}
-// Currents implements the api.PhaseCurrents interface
+// currents implements the api.PhaseCurrents interface
func (c *OCPP) currents() (float64, float64, float64, error) {
return c.conn.Currents()
}
-// Phases1p3p implements the api.PhaseSwitcher interface
+// voltages implements the api.PhaseVoltages interface
+func (c *OCPP) voltages() (float64, float64, float64, error) {
+ return c.conn.Voltages()
+}
+
+// phases1p3p implements the api.PhaseSwitcher interface
func (c *OCPP) phases1p3p(phases int) error {
c.phases = phases
- // NOTE: this will currently _never_ do anything since
- // loadpoint disabled the charger before switching so
- // updatePeriod will short-circuit
- return c.updatePeriod(c.current)
+ return c.setCurrent(c.current)
}
-// // Identify implements the api.Identifier interface
-// Unless charger uses vehicle ID as idTag in authorize.req it is not possible to implement this in ocpp1.6
-// func (c *OCPP) Identify() (string, error) {
-// return "", errors.New("not implemented")
-// }
+// soc implements the api.Battery interface
+func (c *OCPP) soc() (float64, error) {
+ return c.conn.Soc()
+}
+
+var _ api.Identifier = (*OCPP)(nil)
+
+// Identify implements the api.Identifier interface
+func (c *OCPP) Identify() (string, error) {
+ return c.conn.IdTag(), nil
+}
var _ loadpoint.Controller = (*OCPP)(nil)
diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go
index 9ffbd1948d..8c4459aa22 100644
--- a/charger/ocpp/connector.go
+++ b/charger/ocpp/connector.go
@@ -31,6 +31,7 @@ type Connector struct {
txnCount int // change initial value to the last known global transaction. Needs persistence
txnId int
+ idTag string
}
func NewConnector(log *util.Logger, id int, cp *CP, timeout time.Duration) (*Connector, error) {
@@ -61,6 +62,12 @@ func (conn *Connector) ID() int {
return conn.id
}
+func (conn *Connector) IdTag() string {
+ conn.mu.Lock()
+ defer conn.mu.Unlock()
+ return conn.idTag
+}
+
func (conn *Connector) TriggerMessageRequest(feature remotetrigger.MessageTrigger, f ...func(request *remotetrigger.TriggerMessageRequest)) {
Instance().TriggerMessageRequest(conn.cp.ID(), feature, func(request *remotetrigger.TriggerMessageRequest) {
request.ConnectorId = &conn.id
@@ -151,12 +158,45 @@ func (conn *Connector) Status() (api.ChargeStatus, error) {
return res, nil
}
+// NeedsTransaction checks if an initial RemoteStart of a transaction is required
+func (conn *Connector) NeedsTransaction() (bool, error) {
+ if !conn.cp.Connected() {
+ return false, api.ErrTimeout
+ }
+
+ conn.mu.Lock()
+ defer conn.mu.Unlock()
+
+ return conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing, nil
+}
+
// isMeterTimeout checks if meter values are outdated.
// Must only be called while holding lock.
func (conn *Connector) isMeterTimeout() bool {
return conn.timeout > 0 && conn.clock.Since(conn.meterUpdated) > conn.timeout
}
+func (conn *Connector) GetMaxCurrent() (float64, error) {
+ if !conn.cp.Connected() {
+ return 0, api.ErrTimeout
+ }
+
+ conn.mu.Lock()
+ defer conn.mu.Unlock()
+
+ // fallthrough for last value on timeout when no transaction is running
+ if conn.txnId != 0 && conn.isMeterTimeout() {
+ return 0, api.ErrTimeout
+ }
+
+ if m, ok := conn.measurements[types.MeasurandCurrentOffered]; ok {
+ f, err := strconv.ParseFloat(m.Value, 64)
+ return scale(f, m.Unit) / 1e3, err
+ }
+
+ return 0, api.ErrNotAvailable
+}
+
var _ api.Meter = (*Connector)(nil)
func (conn *Connector) CurrentPower() (float64, error) {
@@ -167,7 +207,7 @@ func (conn *Connector) CurrentPower() (float64, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
- // zero value on timeout when not charging
+ // zero value on timeout when no transaction is running
if conn.isMeterTimeout() {
if conn.txnId != 0 {
return 0, api.ErrTimeout
@@ -194,7 +234,7 @@ func (conn *Connector) TotalEnergy() (float64, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
- // fallthrough for last value on timeout when not charging
+ // fallthrough for last value on timeout when no transaction is running
if conn.txnId != 0 && conn.isMeterTimeout() {
return 0, api.ErrTimeout
}
@@ -207,6 +247,27 @@ func (conn *Connector) TotalEnergy() (float64, error) {
return 0, api.ErrNotAvailable
}
+func (conn *Connector) Soc() (float64, error) {
+ if !conn.cp.Connected() {
+ return 0, api.ErrTimeout
+ }
+
+ conn.mu.Lock()
+ defer conn.mu.Unlock()
+
+ // fallthrough for last value on timeout when no transaction is running
+ if conn.txnId != 0 && conn.isMeterTimeout() {
+ return 0, api.ErrTimeout
+ }
+
+ if m, ok := conn.measurements[types.MeasurandSoC]; ok {
+ f, err := strconv.ParseFloat(m.Value, 64)
+ return scale(f, m.Unit) / 1e3, err
+ }
+
+ return 0, api.ErrNotAvailable
+}
+
func scale(f float64, scale types.UnitOfMeasure) float64 {
switch {
case strings.HasPrefix(string(scale), "k"):
@@ -232,7 +293,7 @@ func (conn *Connector) Currents() (float64, float64, float64, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
- // zero value on timeout when not charging
+ // zero value on timeout when no transaction is running
if conn.isMeterTimeout() {
if conn.txnId != 0 {
return 0, 0, 0, api.ErrTimeout
@@ -259,3 +320,35 @@ func (conn *Connector) Currents() (float64, float64, float64, error) {
return currents[0], currents[1], currents[2], nil
}
+
+func (conn *Connector) Voltages() (float64, float64, float64, error) {
+ if !conn.cp.Connected() {
+ return 0, 0, 0, api.ErrTimeout
+ }
+
+ conn.mu.Lock()
+ defer conn.mu.Unlock()
+
+ // fallthrough for last value on timeout when no transaction is running
+ if conn.txnId != 0 && conn.isMeterTimeout() {
+ return 0, 0, 0, api.ErrTimeout
+ }
+
+ voltages := make([]float64, 0, 3)
+
+ for phase := 1; phase <= 3; phase++ {
+ m, ok := conn.measurements[getPhaseKey(types.MeasurandVoltage, phase)]
+ if !ok {
+ return 0, 0, 0, api.ErrNotAvailable
+ }
+
+ f, err := strconv.ParseFloat(m.Value, 64)
+ if err != nil {
+ return 0, 0, 0, fmt.Errorf("invalid voltage for phase %d: %w", phase, err)
+ }
+
+ voltages = append(voltages, scale(f, m.Unit))
+ }
+
+ return voltages[0], voltages[1], voltages[2], nil
+}
diff --git a/charger/ocpp/connector_core.go b/charger/ocpp/connector_core.go
index d288d87a3d..c851a6e3ab 100644
--- a/charger/ocpp/connector_core.go
+++ b/charger/ocpp/connector_core.go
@@ -52,7 +52,10 @@ func (conn *Connector) MeterValues(request *core.MeterValuesRequest) (*core.Mete
conn.mu.Lock()
defer conn.mu.Unlock()
- if request.TransactionId != nil && conn.txnId == 0 {
+ if request.TransactionId != nil && conn.txnId == 0 && conn.status != nil &&
+ (conn.status.Status == core.ChargePointStatusCharging ||
+ conn.status.Status == core.ChargePointStatusSuspendedEV ||
+ conn.status.Status == core.ChargePointStatusSuspendedEVSE) {
conn.log.DEBUG.Printf("hijacking transaction: %d", *request.TransactionId)
conn.txnId = *request.TransactionId
}
@@ -88,6 +91,7 @@ func (conn *Connector) StartTransaction(request *core.StartTransactionRequest) (
conn.txnCount++
conn.txnId = conn.txnCount
+ conn.idTag = request.IdTag
res := &core.StartTransactionConfirmation{
IdTagInfo: &types.IdTagInfo{
@@ -135,6 +139,7 @@ func (conn *Connector) StopTransaction(request *core.StopTransactionRequest) (*c
}
conn.txnId = 0
+ conn.idTag = ""
res := &core.StopTransactionConfirmation{
IdTagInfo: &types.IdTagInfo{
diff --git a/charger/ocpp/cs_core.go b/charger/ocpp/cs_core.go
index 551a4644a9..af7c82c4b1 100644
--- a/charger/ocpp/cs_core.go
+++ b/charger/ocpp/cs_core.go
@@ -10,17 +10,9 @@ import (
func (cs *CS) TriggerResetRequest(id string, resetType core.ResetType) {
if err := cs.Reset(id, func(request *core.ResetConfirmation, err error) {
- log := cs.log.TRACE
if err == nil && request != nil && request.Status != core.ResetStatusAccepted {
- log = cs.log.ERROR
+ cs.log.ERROR.Printf("TriggerReset for %s: %+v", id, request.Status)
}
-
- var status core.ResetStatus
- if request != nil {
- status = request.Status
- }
-
- log.Printf("TriggerReset for %s: %+v", id, status)
}, resetType); err != nil {
cs.log.ERROR.Printf("send TriggerReset for %s failed: %v", id, err)
}
@@ -28,17 +20,9 @@ func (cs *CS) TriggerResetRequest(id string, resetType core.ResetType) {
func (cs *CS) TriggerMessageRequest(id string, requestedMessage remotetrigger.MessageTrigger, props ...func(request *remotetrigger.TriggerMessageRequest)) {
if err := cs.TriggerMessage(id, func(request *remotetrigger.TriggerMessageConfirmation, err error) {
- log := cs.log.TRACE
if err == nil && request != nil && request.Status != remotetrigger.TriggerMessageStatusAccepted {
- log = cs.log.ERROR
+ cs.log.ERROR.Printf("TriggerMessage %s for %s: %+v", requestedMessage, id, request.Status)
}
-
- var status remotetrigger.TriggerMessageStatus
- if request != nil {
- status = request.Status
- }
-
- log.Printf("TriggerMessage %s for %s: %+v", requestedMessage, id, status)
}, requestedMessage, props...); err != nil {
cs.log.ERROR.Printf("send TriggerMessage %s for %s failed: %v", requestedMessage, id, err)
}
diff --git a/charger/ocpp/cs_log.go b/charger/ocpp/cs_log.go
index c4d047e83c..1a95cedb18 100644
--- a/charger/ocpp/cs_log.go
+++ b/charger/ocpp/cs_log.go
@@ -6,7 +6,13 @@ import (
)
func (cs *CS) print(s string) {
- if strings.Contains(s, "JSON message") {
+ var ok bool
+ if s, ok = strings.CutPrefix(s, "sent JSON message to"); ok {
+ s = "send" + s
+ } else if s, ok = strings.CutPrefix(s, "received JSON message from"); ok {
+ s = "recv" + s
+ }
+ if ok {
cs.log.TRACE.Println(s)
}
}
diff --git a/charger/ocpp_decorators.go b/charger/ocpp_decorators.go
index 9dfcba05fc..0f84169b13 100644
--- a/charger/ocpp_decorators.go
+++ b/charger/ocpp_decorators.go
@@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
-func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error) api.Charger {
+func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), phaseSwitcher func(int) error, battery func() (float64, error)) api.Charger {
switch {
- case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
return base
- case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.Meter
@@ -22,7 +22,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.MeterEnergy
@@ -33,7 +33,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.Meter
@@ -48,7 +48,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.PhaseCurrents
@@ -59,7 +59,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.Meter
@@ -74,7 +74,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.MeterEnergy
@@ -89,7 +89,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
return &struct {
*OCPP
api.Meter
@@ -108,55 +108,996 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
- case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseCurrents
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.PhaseCurrents
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ api.PhaseCurrents
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.MeterEnergy
+ api.PhaseCurrents
api.PhaseSwitcher
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.Meter
+ api.MeterEnergy
+ api.PhaseCurrents
+ api.PhaseSwitcher
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
+ Meter: &decorateOCPPMeterImpl{
+ meter: meter,
+ },
+ MeterEnergy: &decorateOCPPMeterEnergyImpl{
+ meterEnergy: meterEnergy,
+ },
+ PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
+ phaseCurrents: phaseCurrents,
+ },
+ PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
+ phaseSwitcher: phaseSwitcher,
+ },
+ }
+
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
+ return &struct {
+ *OCPP
+ api.Battery
+ api.PhaseSwitcher
+ api.PhaseVoltages
+ }{
+ OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.Meter
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.MeterEnergy
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.Meter
api.MeterEnergy
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
@@ -166,31 +1107,47 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
+ case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.PhaseCurrents
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
+ case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
@@ -200,16 +1157,24 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
+ case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
@@ -219,17 +1184,25 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
- case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
+ case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil:
return &struct {
*OCPP
+ api.Battery
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
+ api.PhaseVoltages
}{
OCPP: base,
+ Battery: &decorateOCPPBatteryImpl{
+ battery: battery,
+ },
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
@@ -242,12 +1215,23 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
+ PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{
+ phaseVoltages: phaseVoltages,
+ },
}
}
return nil
}
+type decorateOCPPBatteryImpl struct {
+ battery func() (float64, error)
+}
+
+func (impl *decorateOCPPBatteryImpl) Soc() (float64, error) {
+ return impl.battery()
+}
+
type decorateOCPPMeterImpl struct {
meter func() (float64, error)
}
@@ -279,3 +1263,11 @@ type decorateOCPPPhaseSwitcherImpl struct {
func (impl *decorateOCPPPhaseSwitcherImpl) Phases1p3p(p0 int) error {
return impl.phaseSwitcher(p0)
}
+
+type decorateOCPPPhaseVoltagesImpl struct {
+ phaseVoltages func() (float64, float64, float64, error)
+}
+
+func (impl *decorateOCPPPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) {
+ return impl.phaseVoltages()
+}
diff --git a/charger/ocpp_test.go b/charger/ocpp_test.go
index 8f70c18a20..355a215aff 100644
--- a/charger/ocpp_test.go
+++ b/charger/ocpp_test.go
@@ -45,6 +45,7 @@ func (suite *ocppTestSuite) startChargePoint(id string, connectorId int) ocpp16.
cp := ocpp16.NewChargePoint(id, nil, nil)
cp.SetCoreHandler(handler)
cp.SetRemoteTriggerHandler(handler)
+ cp.SetSmartChargingHandler(handler)
// let cs handle the trigger messages
go func() {
@@ -66,7 +67,7 @@ func (suite *ocppTestSuite) handleTrigger(cp ocpp16.ChargePoint, connectorId int
}
case core.StatusNotificationFeatureName:
- if res, err := cp.StatusNotification(connectorId, core.NoError, core.ChargePointStatusAvailable); err != nil {
+ if res, err := cp.StatusNotification(connectorId, core.NoError, core.ChargePointStatusCharging); err != nil {
suite.T().Log("StatusNotification:", err)
} else {
suite.T().Log("StatusNotification:", res)
@@ -99,7 +100,7 @@ func (suite *ocppTestSuite) TestConnect() {
suite.Require().True(cp1.IsConnected())
// 1st charge point- local
- c1, err := NewOCPP("test-1", 1, "", "", 0, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A")
+ c1, err := NewOCPP("test-1", 1, "", "", 0, false, false, true, ocppTestConnectTimeout, ocppTestTimeout, "A")
suite.Require().NoError(err)
// status and meter values
@@ -158,7 +159,7 @@ func (suite *ocppTestSuite) TestConnect() {
suite.Require().True(cp2.IsConnected())
// 2nd charge point - local
- c2, err := NewOCPP("test-2", 1, "", "", 0, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A")
+ c2, err := NewOCPP("test-2", 1, "", "", 0, false, false, true, ocppTestConnectTimeout, ocppTestTimeout, "A")
suite.Require().NoError(err)
{
@@ -192,3 +193,43 @@ WAIT_DISCONNECT:
}
}
}
+
+func (suite *ocppTestSuite) TestAutoStart() {
+ // 1st charge point- remote
+ cp1 := suite.startChargePoint("test-3", 1)
+ suite.Require().NoError(cp1.Start(ocppTestUrl))
+ suite.Require().True(cp1.IsConnected())
+
+ // 1st charge point- local
+ c1, err := NewOCPP("test-3", 1, "", "", 0, false, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A")
+ suite.Require().NoError(err)
+
+ // status and meter values
+ {
+ suite.clock.Add(ocppTestTimeout)
+ c1.conn.TestClock(suite.clock)
+ }
+
+ // acquire
+ {
+ expectedIdTag := "tag"
+
+ // always accept stopping unknown transaction, see https://github.com/evcc-io/evcc/pull/13990
+ _, err := cp1.StartTransaction(1, expectedIdTag, 0, types.NewDateTime(suite.clock.Now()))
+ suite.Require().NoError(err)
+
+ id, err := c1.Identify()
+ suite.Require().NoError(err)
+ suite.Require().Equal(expectedIdTag, id)
+
+ conn1 := c1.Connector()
+ _, err = conn1.TransactionID()
+ suite.Require().NoError(err)
+ }
+
+ err = c1.Enable(true)
+ suite.Require().NoError(err)
+
+ err = c1.Enable(false)
+ suite.Require().NoError(err)
+}
diff --git a/charger/ocpp_test_handler.go b/charger/ocpp_test_handler.go
index 3481dc196e..02a3e91f3c 100644
--- a/charger/ocpp_test_handler.go
+++ b/charger/ocpp_test_handler.go
@@ -5,6 +5,7 @@ import (
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
+ "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
)
@@ -12,6 +13,8 @@ type ChargePointHandler struct {
triggerC chan remotetrigger.MessageTrigger
}
+// core
+
func (handler *ChargePointHandler) OnChangeAvailability(request *core.ChangeAvailabilityRequest) (confirmation *core.ChangeAvailabilityConfirmation, err error) {
fmt.Printf("%T %+v\n", request, request)
return core.NewChangeAvailabilityConfirmation(core.AvailabilityStatusAccepted), nil
@@ -79,3 +82,20 @@ func (handler *ChargePointHandler) OnTriggerMessage(request *remotetrigger.Trigg
return remotetrigger.NewTriggerMessageConfirmation(remotetrigger.TriggerMessageStatusAccepted), nil
}
+
+// smart charging
+
+func (handler *ChargePointHandler) OnSetChargingProfile(request *smartcharging.SetChargingProfileRequest) (*smartcharging.SetChargingProfileConfirmation, error) {
+ fmt.Printf("%T %+v\n", request, request)
+ return smartcharging.NewSetChargingProfileConfirmation(smartcharging.ChargingProfileStatusAccepted), nil
+}
+
+func (handler *ChargePointHandler) OnClearChargingProfile(request *smartcharging.ClearChargingProfileRequest) (*smartcharging.ClearChargingProfileConfirmation, error) {
+ fmt.Printf("%T %+v\n", request, request)
+ return smartcharging.NewClearChargingProfileConfirmation(smartcharging.ClearChargingProfileStatusAccepted), nil
+}
+
+func (handler *ChargePointHandler) OnGetCompositeSchedule(request *smartcharging.GetCompositeScheduleRequest) (*smartcharging.GetCompositeScheduleConfirmation, error) {
+ fmt.Printf("%T %+v\n", request, request)
+ return smartcharging.NewGetCompositeScheduleConfirmation(smartcharging.GetCompositeScheduleStatusAccepted), nil
+}
diff --git a/charger/phoenix-ev-eth.go b/charger/phoenix-ev-eth.go
index a6aca9fbd4..4d98ca48bb 100644
--- a/charger/phoenix-ev-eth.go
+++ b/charger/phoenix-ev-eth.go
@@ -122,7 +122,7 @@ func NewPhoenixEVEth(uri string, slaveID uint8) (api.Charger, error) {
maxCurrentMillis = wb.maxCurrentMillis
}
- return decoratePhoenixEVEth(wb, currentPower, totalEnergy, currents, voltages, maxCurrentMillis, identify), err
+ return decoratePhoenixEVEth(wb, currentPower, totalEnergy, currents, voltages, maxCurrentMillis, identify), nil
}
// Status implements the api.Charger interface
diff --git a/charger/versicharge.go b/charger/versicharge.go
index bf1a99d3c9..8251d4357a 100644
--- a/charger/versicharge.go
+++ b/charger/versicharge.go
@@ -164,7 +164,7 @@ func (wb *Versicharge) TotalEnergy() (float64, error) {
return 0, err
}
- return float64(binary.BigEndian.Uint32(b)) / 1e4, err
+ return float64(binary.BigEndian.Uint32(b)) / 1e3, err
}
// getPhaseValues returns 3 sequential register values
diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go"
index 035b6af52f..efebcd1171 100644
--- "a/charger/weidm\303\274ller.go"
+++ "b/charger/weidm\303\274ller.go"
@@ -25,6 +25,7 @@ import (
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus"
"github.com/evcc-io/evcc/util/sponsor"
+ "github.com/volkszaehler/mbmd/encoding"
)
// Weidmüller charger implementation
@@ -37,7 +38,7 @@ type Weidmüller struct {
const (
wmRegCarStatus = 301 // GD_ID_EVCC_CAR_STATE CHAR
wmRegEvccStatus = 302 // GD_ID_EVCC_EVSE_STATE UINT16
- wmRegPhases = 317 // GD_ID_EVCC_PHASES UINT16
+ wmRegPhases = 318 // GD_ID_EVCC_PHASES_LLM UINT16
wmRegVoltages = 400 // GD_ID_CM_VOLTAGE_PHASE UINT32
wmRegCurrents = 406 // GD_ID_CM_CURRENT_PHASE UINT32
wmRegActivePower = 418 // GD_ID_CM_ACTIVE_POWER UINT32
@@ -122,7 +123,7 @@ func (wb *Weidmüller) getPhaseValues(reg uint16) (float64, float64, float64, er
var res [3]float64
for i := range res {
- res[i] = float64(binary.BigEndian.Uint32(b[4*i:])) / 1e3
+ res[i] = float64(encoding.Uint32LswFirst(b[4*i:])) / 1e3
}
return res[0], res[1], res[2], nil
@@ -135,7 +136,7 @@ func (wb *Weidmüller) Status() (api.ChargeStatus, error) {
return api.StatusNone, err
}
- switch s := string(b[0]); s {
+ switch s := string(b[1]); s {
case "A", "B", "C":
return api.ChargeStatus(s), nil
default:
@@ -166,7 +167,7 @@ func (wb *Weidmüller) MaxCurrent(current int64) error {
return fmt.Errorf("invalid current %d", current)
}
- wb.curr = uint16(current * 10)
+ wb.curr = uint16(current)
return wb.setCurrent(wb.curr)
}
@@ -180,7 +181,7 @@ func (wb *Weidmüller) CurrentPower() (float64, error) {
return 0, err
}
- return float64(binary.BigEndian.Uint32(b)) / 1e3, err
+ return float64(encoding.Uint32LswFirst(b)) / 1e3, err
}
var _ api.MeterEnergy = (*Weidmüller)(nil)
@@ -192,7 +193,7 @@ func (wb *Weidmüller) TotalEnergy() (float64, error) {
return 0, err
}
- return float64(binary.BigEndian.Uint32(b)) / 1e3, err
+ return float64(encoding.Uint32LswFirst(b)) / 1e3, err
}
var _ api.PhaseCurrents = (*Weidmüller)(nil)
diff --git a/cmd/class_enumer.go b/cmd/class_enumer.go
index 91dc339b50..68cb935c03 100644
--- a/cmd/class_enumer.go
+++ b/cmd/class_enumer.go
@@ -7,11 +7,11 @@ import (
"strings"
)
-const _ClassName = "meterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
+const _ClassName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
-var _ClassIndex = [...]uint8{0, 5, 12, 19, 25, 32, 36, 40, 48, 59, 64, 74, 76, 80, 86, 95, 106}
+var _ClassIndex = [...]uint8{0, 10, 15, 22, 29, 35, 42, 46, 50, 58, 69, 74, 84, 86, 90, 96, 105, 116}
-const _ClassLowerName = "meterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
+const _ClassLowerName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
func (i Class) String() string {
i -= 1
@@ -25,78 +25,82 @@ func (i Class) String() string {
// Re-run the stringer command to generate them again.
func _ClassNoOp() {
var x [1]struct{}
- _ = x[ClassMeter-(1)]
- _ = x[ClassCharger-(2)]
- _ = x[ClassVehicle-(3)]
- _ = x[ClassTariff-(4)]
- _ = x[ClassCircuit-(5)]
- _ = x[ClassSite-(6)]
- _ = x[ClassMqtt-(7)]
- _ = x[ClassDatabase-(8)]
- _ = x[ClassModbusProxy-(9)]
- _ = x[ClassEEBus-(10)]
- _ = x[ClassJavascript-(11)]
- _ = x[ClassGo-(12)]
- _ = x[ClassHEMS-(13)]
- _ = x[ClassInflux-(14)]
- _ = x[ClassMessenger-(15)]
- _ = x[ClassSponsorship-(16)]
+ _ = x[ClassConfigFile-(1)]
+ _ = x[ClassMeter-(2)]
+ _ = x[ClassCharger-(3)]
+ _ = x[ClassVehicle-(4)]
+ _ = x[ClassTariff-(5)]
+ _ = x[ClassCircuit-(6)]
+ _ = x[ClassSite-(7)]
+ _ = x[ClassMqtt-(8)]
+ _ = x[ClassDatabase-(9)]
+ _ = x[ClassModbusProxy-(10)]
+ _ = x[ClassEEBus-(11)]
+ _ = x[ClassJavascript-(12)]
+ _ = x[ClassGo-(13)]
+ _ = x[ClassHEMS-(14)]
+ _ = x[ClassInflux-(15)]
+ _ = x[ClassMessenger-(16)]
+ _ = x[ClassSponsorship-(17)]
}
-var _ClassValues = []Class{ClassMeter, ClassCharger, ClassVehicle, ClassTariff, ClassCircuit, ClassSite, ClassMqtt, ClassDatabase, ClassModbusProxy, ClassEEBus, ClassJavascript, ClassGo, ClassHEMS, ClassInflux, ClassMessenger, ClassSponsorship}
+var _ClassValues = []Class{ClassConfigFile, ClassMeter, ClassCharger, ClassVehicle, ClassTariff, ClassCircuit, ClassSite, ClassMqtt, ClassDatabase, ClassModbusProxy, ClassEEBus, ClassJavascript, ClassGo, ClassHEMS, ClassInflux, ClassMessenger, ClassSponsorship}
var _ClassNameToValueMap = map[string]Class{
- _ClassName[0:5]: ClassMeter,
- _ClassLowerName[0:5]: ClassMeter,
- _ClassName[5:12]: ClassCharger,
- _ClassLowerName[5:12]: ClassCharger,
- _ClassName[12:19]: ClassVehicle,
- _ClassLowerName[12:19]: ClassVehicle,
- _ClassName[19:25]: ClassTariff,
- _ClassLowerName[19:25]: ClassTariff,
- _ClassName[25:32]: ClassCircuit,
- _ClassLowerName[25:32]: ClassCircuit,
- _ClassName[32:36]: ClassSite,
- _ClassLowerName[32:36]: ClassSite,
- _ClassName[36:40]: ClassMqtt,
- _ClassLowerName[36:40]: ClassMqtt,
- _ClassName[40:48]: ClassDatabase,
- _ClassLowerName[40:48]: ClassDatabase,
- _ClassName[48:59]: ClassModbusProxy,
- _ClassLowerName[48:59]: ClassModbusProxy,
- _ClassName[59:64]: ClassEEBus,
- _ClassLowerName[59:64]: ClassEEBus,
- _ClassName[64:74]: ClassJavascript,
- _ClassLowerName[64:74]: ClassJavascript,
- _ClassName[74:76]: ClassGo,
- _ClassLowerName[74:76]: ClassGo,
- _ClassName[76:80]: ClassHEMS,
- _ClassLowerName[76:80]: ClassHEMS,
- _ClassName[80:86]: ClassInflux,
- _ClassLowerName[80:86]: ClassInflux,
- _ClassName[86:95]: ClassMessenger,
- _ClassLowerName[86:95]: ClassMessenger,
- _ClassName[95:106]: ClassSponsorship,
- _ClassLowerName[95:106]: ClassSponsorship,
+ _ClassName[0:10]: ClassConfigFile,
+ _ClassLowerName[0:10]: ClassConfigFile,
+ _ClassName[10:15]: ClassMeter,
+ _ClassLowerName[10:15]: ClassMeter,
+ _ClassName[15:22]: ClassCharger,
+ _ClassLowerName[15:22]: ClassCharger,
+ _ClassName[22:29]: ClassVehicle,
+ _ClassLowerName[22:29]: ClassVehicle,
+ _ClassName[29:35]: ClassTariff,
+ _ClassLowerName[29:35]: ClassTariff,
+ _ClassName[35:42]: ClassCircuit,
+ _ClassLowerName[35:42]: ClassCircuit,
+ _ClassName[42:46]: ClassSite,
+ _ClassLowerName[42:46]: ClassSite,
+ _ClassName[46:50]: ClassMqtt,
+ _ClassLowerName[46:50]: ClassMqtt,
+ _ClassName[50:58]: ClassDatabase,
+ _ClassLowerName[50:58]: ClassDatabase,
+ _ClassName[58:69]: ClassModbusProxy,
+ _ClassLowerName[58:69]: ClassModbusProxy,
+ _ClassName[69:74]: ClassEEBus,
+ _ClassLowerName[69:74]: ClassEEBus,
+ _ClassName[74:84]: ClassJavascript,
+ _ClassLowerName[74:84]: ClassJavascript,
+ _ClassName[84:86]: ClassGo,
+ _ClassLowerName[84:86]: ClassGo,
+ _ClassName[86:90]: ClassHEMS,
+ _ClassLowerName[86:90]: ClassHEMS,
+ _ClassName[90:96]: ClassInflux,
+ _ClassLowerName[90:96]: ClassInflux,
+ _ClassName[96:105]: ClassMessenger,
+ _ClassLowerName[96:105]: ClassMessenger,
+ _ClassName[105:116]: ClassSponsorship,
+ _ClassLowerName[105:116]: ClassSponsorship,
}
var _ClassNames = []string{
- _ClassName[0:5],
- _ClassName[5:12],
- _ClassName[12:19],
- _ClassName[19:25],
- _ClassName[25:32],
- _ClassName[32:36],
- _ClassName[36:40],
- _ClassName[40:48],
- _ClassName[48:59],
- _ClassName[59:64],
- _ClassName[64:74],
- _ClassName[74:76],
- _ClassName[76:80],
- _ClassName[80:86],
- _ClassName[86:95],
- _ClassName[95:106],
+ _ClassName[0:10],
+ _ClassName[10:15],
+ _ClassName[15:22],
+ _ClassName[22:29],
+ _ClassName[29:35],
+ _ClassName[35:42],
+ _ClassName[42:46],
+ _ClassName[46:50],
+ _ClassName[50:58],
+ _ClassName[58:69],
+ _ClassName[69:74],
+ _ClassName[74:84],
+ _ClassName[84:86],
+ _ClassName[86:90],
+ _ClassName[90:96],
+ _ClassName[96:105],
+ _ClassName[105:116],
}
// ClassString retrieves an enum value from the enum constants string name.
diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go
index 97f94f0667..5c6ade40d2 100644
--- a/cmd/configure/configure.go
+++ b/cmd/configure/configure.go
@@ -116,7 +116,7 @@ var configTmpl string
// RenderConfiguration creates a yaml configuration
func (c *Configure) RenderConfiguration() ([]byte, error) {
- tmpl, err := template.New("yaml").Funcs(sprout.TxtFuncMap()).Parse(configTmpl)
+ tmpl, err := template.New("yaml").Funcs(sprout.FuncMap()).Parse(configTmpl)
if err != nil {
panic(err)
}
diff --git a/cmd/configure/eebus.go b/cmd/configure/eebus.go
index 0ae1e9b9fd..5a48e322da 100644
--- a/cmd/configure/eebus.go
+++ b/cmd/configure/eebus.go
@@ -3,8 +3,8 @@ package configure
import (
"fmt"
- "github.com/evcc-io/evcc/charger/eebus"
"github.com/evcc-io/evcc/cmd/shutdown"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util"
)
diff --git a/cmd/configure/helper.go b/cmd/configure/helper.go
index 9a0bcea97c..8474df9141 100644
--- a/cmd/configure/helper.go
+++ b/cmd/configure/helper.go
@@ -123,9 +123,6 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template
fmt.Println("-------------------------------------------------")
fmt.Println(c.localizedString("Requirements_Title"))
fmt.Println(requirementDescription)
- if len(templateItem.Requirements.URI) > 0 {
- fmt.Println(" " + c.localizedString("Requirements_More") + " " + templateItem.Requirements.URI)
- }
fmt.Println("-------------------------------------------------")
}
@@ -186,30 +183,6 @@ func (c *CmdConfigure) processDeviceRequirements(templateItem templates.Template
return nil
}
-// processParamRequirements handles param requirements
-func (c *CmdConfigure) processParamRequirements(param templates.Param) error {
- requirementDescription := stripmd.Strip(param.Requirements.Description.String(c.lang))
- if len(requirementDescription) > 0 {
- fmt.Println()
- fmt.Println("-------------------------------------------------")
- fmt.Println(c.localizedString("Requirements_Title"))
- fmt.Println(requirementDescription)
- if len(param.Requirements.URI) > 0 {
- fmt.Println(" " + c.localizedString("Requirements_More") + " " + param.Requirements.URI)
- }
- fmt.Println("-------------------------------------------------")
- }
-
- // check if sponsorship is required
- if slices.Contains(param.Requirements.EVCC, templates.RequirementSponsorship) && c.configuration.config.SponsorToken == "" {
- if err := c.askSponsortoken(true, true); err != nil {
- return err
- }
- }
-
- return nil
-}
-
func (c *CmdConfigure) askSponsortoken(required, feature bool) error {
fmt.Println("-- Sponsorship -----------------------------")
if required {
@@ -456,28 +429,17 @@ func (c *CmdConfigure) processInputConfig(param templates.Param) string {
label = langLabel
}
- help := param.Help.ShortString(c.lang)
- if slices.Contains(param.Requirements.EVCC, templates.RequirementSponsorship) {
- help = fmt.Sprintf("%s\n\n%s", help, c.localizedString("Requirements_Sponsorship_Feature_Title"))
- }
-
value := c.askValue(question{
label: label,
defaultValue: param.Default,
exampleValue: param.Example,
- help: help,
+ help: param.Help.ShortString(c.lang),
valueType: param.Type,
validValues: param.ValidValues,
mask: param.IsMasked(),
required: param.IsRequired(),
})
- if param.Type == templates.TypeBool && value == "true" {
- if err := c.processParamRequirements(param); err != nil {
- return "false"
- }
- }
-
return value
}
diff --git a/cmd/demo.yaml b/cmd/demo.yaml
index bac2efb870..dcfe211e03 100644
--- a/cmd/demo.yaml
+++ b/cmd/demo.yaml
@@ -208,6 +208,9 @@ vehicles:
script: |
"B"
capacity: 80
+ limitsoc:
+ source: const
+ value: 90
- name: vehicle_3
type: template
template: offline
diff --git a/cmd/dump.go b/cmd/dump.go
index eedb1b7644..76934cdf90 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -70,7 +70,7 @@ func runDump(cmd *cobra.Command, args []string) {
tmpl := template.Must(
template.New("dump").
- Funcs(sprout.TxtFuncMap()).
+ Funcs(sprout.FuncMap()).
Parse(dumpTmpl))
out := new(bytes.Buffer)
diff --git a/cmd/dumper.go b/cmd/dumper.go
index afc0e5255a..b369acdb38 100644
--- a/cmd/dumper.go
+++ b/cmd/dumper.go
@@ -74,7 +74,7 @@ func (d *dumper) Dump(name string, v interface{}) {
if p1, p2, p3, err := v.Powers(); err != nil {
fmt.Fprintf(w, "Power L1..L3:\t%v\n", err)
} else {
- fmt.Fprintf(w, "Power L1..L3:\t%.3gW %.3gW %.3gW\n", p1, p2, p3)
+ fmt.Fprintf(w, "Power L1..L3:\t%.0fW %.0fW %.0fW\n", p1, p2, p3)
}
}
@@ -140,6 +140,14 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}
+ if v, ok := v.(api.CurrentLimiter); ok {
+ if min, max, err := v.GetMinMaxCurrent(); err != nil {
+ fmt.Fprintf(w, "Mix/Max Current:\t%v\n", err)
+ } else {
+ fmt.Fprintf(w, "Mix/Max Current:\t%.1f/%.1fA\n", min, max)
+ }
+ }
+
// vehicle
if v, ok := v.(api.VehicleRange); ok {
@@ -199,6 +207,24 @@ func (d *dumper) Dump(name string, v interface{}) {
}
}
+ // currents and phases
+
+ if v, ok := v.(api.CurrentGetter); ok {
+ if f, err := v.GetMaxCurrent(); err != nil {
+ fmt.Fprintf(w, "Max Current:\t%v\n", err)
+ } else {
+ fmt.Fprintf(w, "Max Current:\t%.1fA\n", f)
+ }
+ }
+
+ if v, ok := v.(api.PhaseGetter); ok {
+ if f, err := v.GetPhases(); err != nil {
+ fmt.Fprintf(w, "Phases:\t%v\n", err)
+ } else {
+ fmt.Fprintf(w, "Phases:\t%d\n", f)
+ }
+ }
+
// Identity
if v, ok := v.(api.Identifier); ok {
@@ -215,8 +241,9 @@ func (d *dumper) Dump(name string, v interface{}) {
// features
if v, ok := v.(api.FeatureDescriber); ok {
- ff := v.Features()
- fmt.Fprintf(w, "Features:\t%v\n", ff)
+ if ff := v.Features(); len(ff) > 0 {
+ fmt.Fprintf(w, "Features:\t%v\n", ff)
+ }
}
w.Flush()
diff --git a/cmd/eebus.go b/cmd/eebus.go
index 51977deb93..30b2ddbc69 100644
--- a/cmd/eebus.go
+++ b/cmd/eebus.go
@@ -4,7 +4,7 @@ import (
"os"
"text/template"
- "github.com/evcc-io/evcc/charger/eebus"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/go-sprout/sprout"
"github.com/spf13/cobra"
)
@@ -42,7 +42,7 @@ func generateEEBUSCert() {
log.FATAL.Fatal("could not process generated certificate", err)
}
- t := template.Must(template.New("out").Funcs(sprout.TxtFuncMap()).Parse(tmpl))
+ t := template.Must(template.New("out").Funcs(sprout.FuncMap()).Parse(tmpl))
if err := t.Execute(os.Stdout, map[string]interface{}{
"public": pubKey,
"private": privKey,
diff --git a/cmd/error.go b/cmd/error.go
index 7744080c88..3d8c78e8ae 100644
--- a/cmd/error.go
+++ b/cmd/error.go
@@ -10,6 +10,7 @@ type Class int
//go:generate enumer -type Class -trimprefix Class -transform=lower -text
const (
_ Class = iota
+ ClassConfigFile
ClassMeter
ClassCharger
ClassVehicle
diff --git a/cmd/root.go b/cmd/root.go
index ecdd9aa19d..9f7f167bac 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -119,7 +119,7 @@ func runRoot(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}
} else {
- err = cfgErr
+ err = wrapErrorWithClass(ClassConfigFile, cfgErr)
}
// setup environment
diff --git a/cmd/setup.go b/cmd/setup.go
index ebc8fa0b80..7dc7764f54 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -18,9 +18,9 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/globalconfig"
"github.com/evcc-io/evcc/charger"
- "github.com/evcc-io/evcc/charger/eebus"
"github.com/evcc-io/evcc/cmd/shutdown"
"github.com/evcc-io/evcc/core"
+ "github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/hems"
"github.com/evcc-io/evcc/meter"
@@ -31,6 +31,7 @@ import (
"github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/server/db/settings"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/server/modbus"
"github.com/evcc-io/evcc/server/oauth2redirect"
"github.com/evcc-io/evcc/tariff"
@@ -132,8 +133,15 @@ If you know what you're doing, you can run evcc ignoring the service database wi
return err
}
-func configureCircuits(static []config.Named, names ...string) error {
- children := slices.Clone(static)
+func configureCircuits(conf []config.Named) error {
+ // migrate settings
+ if settings.Exists(keys.Circuits) {
+ if err := settings.Yaml(keys.Circuits, new([]map[string]any), &conf); err != nil {
+ return err
+ }
+ }
+
+ children := slices.Clone(conf)
// TODO: check for circular references
NEXT:
@@ -153,7 +161,7 @@ NEXT:
}
log := util.NewLogger("circuit-" + cc.Name)
- instance, err := core.NewCircuitFromConfig(log, cc.Other)
+ instance, err := circuit.NewFromConfig(log, cc.Other)
if err != nil {
return fmt.Errorf("cannot create circuit '%s': %w", cc.Name, err)
}
@@ -176,52 +184,6 @@ NEXT:
return fmt.Errorf("circuit is missing parent: %s", children[0].Name)
}
- // append devices from database
- configurable, err := config.ConfigurationsByClass(templates.Circuit)
- if err != nil {
- return err
- }
-
- children2 := slices.Clone(configurable)
-
-NEXT2:
- for i, conf := range children2 {
- cc := conf.Named()
-
- if len(names) > 0 && !slices.Contains(names, cc.Name) {
- return nil
- }
-
- if parent := cast.ToString(cc.Property("parent")); parent != "" {
- if _, err := config.Circuits().ByName(parent); err != nil {
- continue
- }
- }
-
- log := util.NewLogger("circuit-" + cc.Name)
- instance, err := core.NewCircuitFromConfig(log, cc.Other)
- if err != nil {
- return fmt.Errorf("cannot create circuit '%s': %w", cc.Name, err)
- }
-
- // ensure config has title
- if instance.GetTitle() == "" {
- //lint:ignore SA1019 as Title is safe on ascii
- instance.SetTitle(strings.Title(cc.Name))
- }
-
- if err := config.Circuits().Add(config.NewConfigurableDevice(conf, instance)); err != nil {
- return err
- }
-
- children2 = slices.Delete(children2, i, i+1)
- goto NEXT2
- }
-
- if len(children2) > 0 {
- return fmt.Errorf("missing parent circuit: %s", children2[0].Named().Name)
- }
-
var rootFound bool
for _, dev := range config.Circuits().Devices() {
c := dev.Instance()
@@ -486,15 +448,9 @@ func configureEnvironment(cmd *cobra.Command, conf *globalconfig.All) (err error
request.LogHeaders = true
}
- // setup machine id
- if conf.Plant != "" {
- // TODO decide wrapping
- err = machine.CustomID(conf.Plant)
- }
-
- // setup sponsorship (allow env override)
+ // setup persistence
if err == nil {
- err = wrapErrorWithClass(ClassSponsorship, configureSponsorship(conf.SponsorToken))
+ err = wrapErrorWithClass(ClassDatabase, configureDatabase(conf.Database))
}
// setup translations
@@ -503,9 +459,15 @@ func configureEnvironment(cmd *cobra.Command, conf *globalconfig.All) (err error
err = locale.Init()
}
- // setup persistence
- if err == nil && conf.Database.Dsn != "" {
- err = wrapErrorWithClass(ClassDatabase, configureDatabase(conf.Database))
+ // setup machine id
+ if conf.Plant != "" {
+ // TODO decide wrapping
+ err = machine.CustomID(conf.Plant)
+ }
+
+ // setup sponsorship (allow env override)
+ if err == nil {
+ err = wrapErrorWithClass(ClassSponsorship, configureSponsorship(conf.SponsorToken))
}
// setup mqtt client listener
@@ -539,6 +501,10 @@ func configureEnvironment(cmd *cobra.Command, conf *globalconfig.All) (err error
// configureDatabase configures session database
func configureDatabase(conf globalconfig.DB) error {
+ if conf.Dsn == "" {
+ return errors.New("database dsn not configured")
+ }
+
if err := db.NewInstance(conf.Type, conf.Dsn); err != nil {
return err
}
@@ -558,7 +524,7 @@ func configureDatabase(conf globalconfig.DB) error {
// persist unsaved settings every 30 minutes
go func() {
- for range time.Tick(30 * time.Minute) {
+ for range time.Tick(time.Minute) {
persistSettings()
}
}()
@@ -937,14 +903,10 @@ CONTINUE:
hasRoot = true
}
- for _, lp := range loadpoints {
- if lp.GetCircuit() == instance {
- if isRoot {
- return fmt.Errorf("root circuit must not be assigned to loadpoint %s", lp.Title())
- }
-
- continue CONTINUE
- }
+ if slices.ContainsFunc(loadpoints, func(lp *core.Loadpoint) bool {
+ return lp.GetCircuit() == instance
+ }) {
+ continue CONTINUE
}
if !isRoot && !instance.HasMeter() {
diff --git a/cmd/setup_circuits_test.go b/cmd/setup_circuits_test.go
index c8d1f8aa07..80f65190a9 100644
--- a/cmd/setup_circuits_test.go
+++ b/cmd/setup_circuits_test.go
@@ -98,40 +98,22 @@ loadpoints:
}
func (suite *circuitsTestSuite) TestMissingRootCircuit() {
- var conf globalconfig.All
- viper.SetConfigType("yaml")
-
- suite.Require().NoError(viper.ReadConfig(strings.NewReader(`
-loadpoints:
-- charger: test
-`)))
-
- suite.Require().NoError(viper.UnmarshalExact(&conf))
-
ctrl := gomock.NewController(suite.T())
circuit := api.NewMockCircuit(ctrl)
- // mock circuit
+ // circuit device
suite.Require().NoError(config.Circuits().Add(config.NewStaticDevice(config.Named{
- Name: "test",
+ Name: "master",
}, api.Circuit(circuit))))
- // mock charger
- suite.Require().NoError(config.Chargers().Add(config.NewStaticDevice(config.Named{
- Name: "test",
- }, api.Charger(nil))))
-
- lps, err := configureLoadpoints(conf)
- suite.Require().NoError(err)
-
- // root circuit
+ // root circuit present
circuit.EXPECT().GetParent().Return(nil)
circuit.EXPECT().HasMeter().Return(true)
- suite.Require().NoError(validateCircuits(lps))
+ suite.Require().NoError(validateCircuits(nil))
- // no root circuit
+ // root circuit missing
circuit.EXPECT().GetParent().Return(circuit)
- err = validateCircuits(lps)
+ err := validateCircuits(nil)
suite.Require().Error(err)
suite.Require().Equal("missing root circuit", err.Error())
}
@@ -141,20 +123,17 @@ func (suite *circuitsTestSuite) TestLoadpointUsingRootCircuit() {
viper.SetConfigType("yaml")
suite.Require().NoError(viper.ReadConfig(strings.NewReader(`
+circuits:
+- name: master
loadpoints:
- charger: test
- circuit: root
+ circuit: master
`)))
suite.Require().NoError(viper.UnmarshalExact(&conf))
- ctrl := gomock.NewController(suite.T())
- circuit := api.NewMockCircuit(ctrl)
-
- // mock circuit
- suite.Require().NoError(config.Circuits().Add(config.NewStaticDevice(config.Named{
- Name: "root",
- }, api.Circuit(circuit))))
+ suite.Require().NoError(configureCircuits(conf.Circuits))
+ suite.Require().Len(config.Circuits().Devices(), 1)
// mock charger
suite.Require().NoError(config.Chargers().Add(config.NewStaticDevice(config.Named{
@@ -164,9 +143,6 @@ loadpoints:
lps, err := configureLoadpoints(conf)
suite.Require().NoError(err)
- // root circuit
- circuit.EXPECT().GetParent().Return(nil)
- err = validateCircuits(lps)
- suite.Require().Error(err)
- suite.Require().Equal("root circuit must not be assigned to loadpoint ", err.Error())
+ // lp using root circuit is valid
+ suite.Require().NoError(validateCircuits(lps))
}
diff --git a/cmd/tools/decorate.go b/cmd/tools/decorate.go
index 9d0fdc38dc..a4d013f064 100644
--- a/cmd/tools/decorate.go
+++ b/cmd/tools/decorate.go
@@ -3,7 +3,6 @@ package main
import (
"bytes"
_ "embed"
- "errors"
"fmt"
"go/format"
"io"
@@ -12,6 +11,7 @@ import (
"strings"
"text/template"
+ "github.com/go-sprout/sprout"
combinations "github.com/mxschmitt/golang-combinations"
"github.com/spf13/pflag"
"golang.org/x/tools/imports"
@@ -33,22 +33,7 @@ func generate(out io.Writer, packageName, functionName, baseType string, dynamic
types := make(map[string]typeStruct, len(dynamicTypes))
combos := make([]string, 0)
- tmpl, err := template.New("gen").Funcs(template.FuncMap{
- // dict combines key value pairs for passing structs into templates
- "dict": func(values ...interface{}) (map[string]interface{}, error) {
- if len(values)%2 != 0 {
- return nil, errors.New("invalid dict call")
- }
- dict := make(map[string]interface{}, len(values)/2)
- for i := 0; i < len(values); i += 2 {
- key, ok := values[i].(string)
- if !ok {
- return nil, errors.New("dict keys must be strings")
- }
- dict[key] = values[i+1]
- }
- return dict, nil
- },
+ tmpl, err := template.New("gen").Funcs(sprout.FuncMap()).Funcs(template.FuncMap{
// contains checks if slice contains string
"contains": slices.Contains[[]string, string],
// ordered returns a slice of typeStructs ordered by dynamicType
diff --git a/core/circuit.go b/core/circuit/circuit.go
similarity index 89%
rename from core/circuit.go
rename to core/circuit/circuit.go
index b19991449c..ad31ee7e8f 100644
--- a/core/circuit.go
+++ b/core/circuit/circuit.go
@@ -1,4 +1,4 @@
-package core
+package circuit
import (
"fmt"
@@ -34,8 +34,8 @@ type Circuit struct {
powerUpdated time.Time
}
-// NewCircuitFromConfig creates a new Circuit
-func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.Circuit, error) {
+// NewFromConfig creates a new Circuit
+func NewFromConfig(log *util.Logger, other map[string]interface{}) (api.Circuit, error) {
cc := struct {
Title string `mapstructure:"title"` // title
ParentRef string `mapstructure:"parent"` // parent circuit reference
@@ -60,7 +60,7 @@ func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.C
meter = dev.Instance()
}
- circuit, err := NewCircuit(log, cc.Title, cc.MaxCurrent, cc.MaxPower, meter, cc.Timeout)
+ circuit, err := New(log, cc.Title, cc.MaxCurrent, cc.MaxPower, meter, cc.Timeout)
if err != nil {
return nil, err
}
@@ -70,14 +70,14 @@ func NewCircuitFromConfig(log *util.Logger, other map[string]interface{}) (api.C
if err != nil {
return nil, err
}
- circuit.SetParent(dev.Instance())
+ circuit.setParent(dev.Instance())
}
return circuit, err
}
-// NewCircuit creates a circuit
-func NewCircuit(log *util.Logger, title string, maxCurrent, maxPower float64, meter api.Meter, timeout time.Duration) (*Circuit, error) {
+// New creates a circuit
+func New(log *util.Logger, title string, maxCurrent, maxPower float64, meter api.Meter, timeout time.Duration) (*Circuit, error) {
c := &Circuit{
log: log,
title: title,
@@ -119,14 +119,26 @@ func (c *Circuit) GetParent() api.Circuit {
return c.parent
}
-// SetParent set parent circuit
-func (c *Circuit) SetParent(parent api.Circuit) {
+// setParent set parent circuit
+func (c *Circuit) setParent(parent api.Circuit) error {
c.mu.Lock()
defer c.mu.Unlock()
+ if c.parent != nil {
+ return fmt.Errorf("circuit already has a parent")
+ }
c.parent = parent
if parent != nil {
parent.RegisterChild(c)
}
+ return nil
+}
+
+// Wrap wraps circuit with parent, keeping the original meter
+func (c *Circuit) Wrap(parent api.Circuit) error {
+ if c.meter != nil {
+ parent.(*Circuit).meter = c.meter
+ }
+ return c.setParent(parent)
}
// HasMeter returns the max power setting
diff --git a/core/circuit_test.go b/core/circuit/circuit_test.go
similarity index 94%
rename from core/circuit_test.go
rename to core/circuit/circuit_test.go
index dbcd82a4e2..d7c5175c40 100644
--- a/core/circuit_test.go
+++ b/core/circuit/circuit_test.go
@@ -1,4 +1,4 @@
-package core
+package circuit
import (
"testing"
@@ -61,7 +61,7 @@ func TestCircuitPower(t *testing.T) {
circ := func(t *testing.T, ctrl *gomock.Controller, maxP float64) (*Circuit, *api.MockMeter) {
m := api.NewMockMeter(ctrl)
- c, err := NewCircuit(log, "foo", 0, maxP, m, 0)
+ c, err := New(log, "foo", 0, maxP, m, 0)
require.NoError(t, err)
return c, m
}
@@ -73,8 +73,8 @@ func TestCircuitPower(t *testing.T) {
c1, cm1 := circ(t, ctrl, 1)
c2, cm2 := circ(t, ctrl, 1)
- c1.SetParent(pc)
- c2.SetParent(pc)
+ c1.setParent(pc)
+ c2.setParent(pc)
// update meters
pm.EXPECT().CurrentPower().Return(tc.p, nil)
@@ -100,7 +100,7 @@ func TestCircuitCurrents(t *testing.T) {
api.NewMockMeter(ctrl),
api.NewMockPhaseCurrents(ctrl),
}
- c, err := NewCircuit(log, "foo", maxC, 0, m, 0)
+ c, err := New(log, "foo", maxC, 0, m, 0)
require.NoError(t, err)
return c, m
}
@@ -112,8 +112,8 @@ func TestCircuitCurrents(t *testing.T) {
c1, cm1 := circ(t, ctrl, 1)
c2, cm2 := circ(t, ctrl, 1)
- c1.SetParent(pc)
- c2.SetParent(pc)
+ c1.setParent(pc)
+ c2.setParent(pc)
// update meters
pm.MockMeter.EXPECT().CurrentPower().AnyTimes().Return(0.0, nil)
diff --git a/core/coordinator/adapter.go b/core/coordinator/adapter.go
index 1642e750b9..e700828168 100644
--- a/core/coordinator/adapter.go
+++ b/core/coordinator/adapter.go
@@ -19,8 +19,8 @@ func NewAdapter(lp loadpoint.API, c *Coordinator) API {
}
}
-func (a *adapter) GetVehicles() []api.Vehicle {
- return a.c.GetVehicles()
+func (a *adapter) GetVehicles(availableOnly bool) []api.Vehicle {
+ return a.c.GetVehicles(availableOnly)
}
func (a *adapter) Owner(v api.Vehicle) loadpoint.API {
diff --git a/core/coordinator/api.go b/core/coordinator/api.go
index 6bb1963271..5e2cc91332 100644
--- a/core/coordinator/api.go
+++ b/core/coordinator/api.go
@@ -7,8 +7,8 @@ import (
// API is the coordinator API
type API interface {
- // GetVehicles returns the list of all vehicles
- GetVehicles() []api.Vehicle
+ // GetVehicles returns the list of all vehicles, filtered by availability
+ GetVehicles(availableOnly bool) []api.Vehicle
// Owner returns the loadpoint that currently owns the vehicle
Owner(api.Vehicle) loadpoint.API
diff --git a/core/coordinator/coordinator.go b/core/coordinator/coordinator.go
index d80ddbdb47..6793c3b56a 100644
--- a/core/coordinator/coordinator.go
+++ b/core/coordinator/coordinator.go
@@ -1,7 +1,6 @@
package coordinator
import (
- "slices"
"sync"
"github.com/evcc-io/evcc/api"
@@ -27,11 +26,18 @@ func New(log *util.Logger, vehicles []api.Vehicle) *Coordinator {
}
// GetVehicles returns the list of all vehicles
-func (c *Coordinator) GetVehicles() []api.Vehicle {
+func (c *Coordinator) GetVehicles(availableOnly bool) []api.Vehicle {
c.mu.RLock()
defer c.mu.RUnlock()
- return slices.Clone(c.vehicles)
+ res := make([]api.Vehicle, 0, len(c.vehicles))
+ for _, v := range c.vehicles {
+ if _, tracked := c.tracked[v]; !availableOnly || availableOnly && !tracked {
+ res = append(res, v)
+ }
+ }
+
+ return res
}
// Owner returns the loadpoint that currently owns the vehicle
diff --git a/core/coordinator/dummy.go b/core/coordinator/dummy.go
index 07eaee8099..fddb037ab9 100644
--- a/core/coordinator/dummy.go
+++ b/core/coordinator/dummy.go
@@ -12,7 +12,7 @@ func NewDummy() API {
return new(dummy)
}
-func (a *dummy) GetVehicles() []api.Vehicle {
+func (a *dummy) GetVehicles(_ bool) []api.Vehicle {
return nil
}
diff --git a/core/keys/loadpoint.go b/core/keys/loadpoint.go
index c084e3f4e1..118040d335 100644
--- a/core/keys/loadpoint.go
+++ b/core/keys/loadpoint.go
@@ -28,8 +28,9 @@ const (
Charging = "charging" // charging
// smart charging
- SmartCostActive = "smartCostActive" // smart cost active
- SmartCostLimit = "smartCostLimit" // smart cost limit
+ SmartCostActive = "smartCostActive" // smart cost active
+ SmartCostLimit = "smartCostLimit" // smart cost limit
+ SmartCostNextStart = "smartCostNextStart" // smart cost next start
// effective values
EffectivePriority = "effectivePriority" // effective priority
@@ -59,6 +60,7 @@ const (
PlanSoc = "planSoc" // charge plan soc goal
PlanActive = "planActive" // charge plan has determined current slot to be an active slot
PlanProjectedStart = "planProjectedStart" // charge plan start time (earliest slot)
+ PlanProjectedEnd = "planProjectedEnd" // charge plan ends (end of last slot)
PlanOverrun = "planOverrun" // charge plan goal not reachable in time
// remote control
@@ -74,4 +76,5 @@ const (
VehicleSoc = "vehicleSoc" // vehicle soc
VehicleLimitSoc = "vehicleLimitSoc" // vehicle api soc limit
VehicleClimaterActive = "vehicleClimaterActive" // vehicle climater active
+ VehicleWelcomeActive = "vehicleWelcomeActive" // vehicle might need welcome charge
)
diff --git a/core/loadpoint.go b/core/loadpoint.go
index ade1ae39ed..5f32ced5d2 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"reflect"
+ "slices"
"strings"
"sync"
"testing"
@@ -703,10 +704,17 @@ func (lp *Loadpoint) syncCharger() error {
// #2: sync charger
switch {
- case enabled == lp.enabled:
+ case enabled && lp.enabled:
// sync max current
- if charger, ok := lp.charger.(api.CurrentGetter); ok && enabled {
- if current, err := charger.GetMaxCurrent(); err == nil {
+ var (
+ current float64
+ err error
+ )
+
+ // use chargers actual set current if available
+ cg, isCg := lp.charger.(api.CurrentGetter)
+ if isCg {
+ if current, err = cg.GetMaxCurrent(); err == nil {
// smallest adjustment most PWM-Controllers can do is: 100%÷256×0,6A = 0.234A
if math.Abs(lp.chargeCurrent-current) > 0.23 {
if shouldBeConsistent {
@@ -720,19 +728,54 @@ func (lp *Loadpoint) syncCharger() error {
}
}
+ // use measured phase currents as fallback if charger does not provide max current or does not currently relay from vehicle (TWC3)
+ if !isCg || errors.Is(err, api.ErrNotAvailable) {
+ // validate if current too high by more than 1A (https://github.com/evcc-io/evcc/issues/14731)
+ if current := lp.GetMaxPhaseCurrent(); current > lp.chargeCurrent+1.0 {
+ if shouldBeConsistent {
+ lp.log.WARN.Printf("charger logic error: current mismatch (got %.3gA measured, expected %.3gA)", current, lp.chargeCurrent)
+ }
+ lp.chargeCurrent = current
+ lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
+ }
+ }
+
// sync phases
- phases := lp.GetPhases()
- if ps, ok := lp.charger.(api.PhaseGetter); ok && enabled && shouldBeConsistent && phases > 0 {
- if chargerPhases, err := ps.GetPhases(); err == nil {
- if chargerPhases != phases {
- lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases)
+ _, isPs := lp.charger.(api.PhaseSwitcher)
+ if phases := lp.GetPhases(); isPs && shouldBeConsistent && phases > 0 {
+ // fallback to active phases from measured phases
+ chargerPhases := lp.measuredPhases
+ if chargerPhases == 2 {
+ chargerPhases = 3
+ }
+
+ pg, isPg := lp.charger.(api.PhaseGetter)
+ if isPg {
+ if chargerPhases, err = pg.GetPhases(); err == nil {
+ if chargerPhases > 0 && chargerPhases != phases {
+ lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases)
+ lp.setPhases(chargerPhases)
+ }
+ } else {
+ if errors.Is(err, api.ErrNotAvailable) {
+ return nil
+ }
+ return fmt.Errorf("charger get phases: %w", err)
+ }
+ }
+
+ // use measured phase currents for active phases as fallback if charger does not provide phases
+ if !isPg || errors.Is(err, api.ErrNotAvailable) {
+ if chargerPhases > phases {
+ lp.log.WARN.Printf("charger logic error: phases mismatch (got %d measured, expected %d)", chargerPhases, phases)
lp.setPhases(chargerPhases)
}
- } else if !errors.Is(err, api.ErrNotAvailable) {
- return fmt.Errorf("charger get phases: %w", err)
}
}
+ case enabled == lp.enabled:
+ // sync disabled state
+
case !enabled && !lp.phaseSwitchCompleted():
// some chargers (i.E. Easee in some configurations) disable themselves to be able to switch phases
// -> enable charger
@@ -843,7 +886,7 @@ func (lp *Loadpoint) charging() bool {
return lp.GetStatus() == api.StatusC
}
-// charging returns the EVs charging state
+// setStatus updates the internal charging state according to EV
func (lp *Loadpoint) setStatus(status api.ChargeStatus) {
lp.Lock()
defer lp.Unlock()
@@ -951,10 +994,12 @@ func statusEvents(prevStatus, status api.ChargeStatus) []string {
}
// updateChargerStatus updates charger status and detects car connected/disconnected events
-func (lp *Loadpoint) updateChargerStatus() error {
+func (lp *Loadpoint) updateChargerStatus() (bool, error) {
+ var welcomeCharge bool
+
status, err := lp.charger.Status()
if err != nil {
- return fmt.Errorf("charger status: %w", err)
+ return false, fmt.Errorf("charger status: %w", err)
}
lp.log.DEBUG.Printf("charger status: %s", status)
@@ -969,6 +1014,20 @@ func (lp *Loadpoint) updateChargerStatus() error {
if prevStatus != api.StatusNone {
switch ev {
case evVehicleConnect:
+ welcomeCharge = lp.chargerHasFeature(api.WelcomeCharge)
+
+ // Enable charging on connect if any available vehicle requires it.
+ // We're using the PV timer to disable after the welcome
+ if !welcomeCharge {
+ for _, v := range lp.availableVehicles() {
+ if slices.Contains(v.Features(), api.WelcomeCharge) {
+ welcomeCharge = true
+ lp.log.DEBUG.Printf("welcome charge: %s", v.Title())
+ break
+ }
+ }
+ }
+
lp.pushEvent(evVehicleConnect)
case evVehicleDisconnect:
lp.pushEvent(evVehicleDisconnect)
@@ -980,7 +1039,7 @@ func (lp *Loadpoint) updateChargerStatus() error {
lp.bus.Publish(evChargeCurrent, lp.chargeCurrent)
}
- return nil
+ return welcomeCharge, nil
}
// effectiveCurrent returns the currently effective charging current
@@ -1573,8 +1632,9 @@ func (lp *Loadpoint) phaseSwitchCompleted() bool {
}
// Update is the main control function. It reevaluates meters and charger state
-func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) {
- lp.publish(keys.SmartCostActive, autoCharge)
+func (lp *Loadpoint) Update(sitePower float64, smartCostActive bool, smartCostNextStart time.Time, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64) {
+ lp.publish(keys.SmartCostActive, smartCostActive)
+ lp.publish(keys.SmartCostNextStart, smartCostNextStart)
lp.processTasks()
// read and publish meters first- charge power and currents have already been updated by the site
@@ -1592,11 +1652,13 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
lp.PublishEffectiveValues()
// read and publish status
- if err := lp.updateChargerStatus(); err != nil {
+ welcomeCharge, err := lp.updateChargerStatus()
+ if err != nil {
lp.log.ERROR.Println(err)
return
}
+ lp.publish(keys.VehicleWelcomeActive, welcomeCharge)
lp.publish(keys.Connected, lp.connected())
lp.publish(keys.Charging, lp.charging())
@@ -1621,9 +1683,6 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
return
}
- // check if car connected and ready for charging
- var err error
-
// track if remote disabled is actually active
remoteDisabled := loadpoint.RemoteEnable
@@ -1648,7 +1707,11 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
fallthrough
case mode == api.ModeOff:
- err = lp.setLimit(0)
+ var current float64
+ if welcomeCharge {
+ current = lp.effectiveMinCurrent()
+ }
+ err = lp.setLimit(current)
// minimum or target charging
case lp.minSocNotReached() || plannerActive:
@@ -1670,7 +1733,7 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
case mode == api.ModeMinPV || mode == api.ModePV:
// cheap tariff
- if autoCharge && lp.EffectivePlanTime().IsZero() {
+ if smartCostActive && lp.EffectivePlanTime().IsZero() {
err = lp.fastCharging()
lp.resetPhaseTimer()
lp.elapsePVTimer() // let PV mode disable immediately afterwards
@@ -1683,6 +1746,11 @@ func (lp *Loadpoint) Update(sitePower float64, autoCharge, batteryBuffered, batt
targetCurrent = lp.effectiveMinCurrent()
}
+ if targetCurrent == 0 && welcomeCharge {
+ targetCurrent = lp.effectiveMinCurrent()
+ lp.resetPVTimer()
+ }
+
// Sunny Home Manager
if lp.remoteControlled(loadpoint.RemoteSoftDisable) {
remoteDisabled = loadpoint.RemoteSoftDisable
diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go
index 70cc0c0eed..c4e23faac8 100644
--- a/core/loadpoint_plan.go
+++ b/core/loadpoint_plan.go
@@ -88,9 +88,11 @@ func (lp *Loadpoint) plannerActive() (active bool) {
}()
var planStart time.Time
+ var planEnd time.Time
var planOverrun time.Duration
defer func() {
lp.publish(keys.PlanProjectedStart, planStart)
+ lp.publish(keys.PlanProjectedEnd, planEnd)
lp.publish(keys.PlanOverrun, planOverrun)
}()
@@ -98,7 +100,9 @@ func (lp *Loadpoint) plannerActive() (active bool) {
if planTime.IsZero() {
return false
}
- if lp.clock.Until(planTime) < 0 && !lp.planActive {
+ // keep overrunning plans as long as a vehicle is connected
+ if lp.clock.Until(planTime) < 0 && (!lp.planActive || !lp.connected()) {
+ lp.log.DEBUG.Println("plan: deleting expired plan")
lp.deletePlan()
return false
}
@@ -129,6 +133,7 @@ func (lp *Loadpoint) plannerActive() (active bool) {
}
planStart = planner.Start(plan)
+ planEnd = planner.End(plan)
lp.log.DEBUG.Printf("plan: charge %v between %v until %v (%spower: %.0fW, avg cost: %.3f)",
planner.Duration(plan).Round(time.Second), planStart.Round(time.Second).Local(), planTime.Round(time.Second).Local(), overrun,
maxPower, planner.AverageCost(plan))
diff --git a/core/loadpoint_sync_test.go b/core/loadpoint_sync_test.go
index 995d28aacb..2107c91bee 100644
--- a/core/loadpoint_sync_test.go
+++ b/core/loadpoint_sync_test.go
@@ -3,6 +3,7 @@ package core
import (
"testing"
+ evbus "github.com/asaskevich/EventBus"
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
@@ -47,3 +48,177 @@ func TestSyncCharger(t *testing.T) {
assert.Equal(t, tc.corrected, lp.enabled)
}
}
+
+func TestSyncChargerCurrentsByGetter(t *testing.T) {
+ tc := []struct {
+ lpCurrent, actualCurrent, outCurrent float64
+ }{
+ {6, 5, 5}, // force
+ {6, 6.1, 6},
+ {6, 6.5, 6.5},
+ {6, 7, 7},
+ }
+
+ for _, tc := range tc {
+ ctrl := gomock.NewController(t)
+ t.Logf("%+v", tc)
+
+ ch := api.NewMockCharger(ctrl)
+ cg := api.NewMockCurrentGetter(ctrl)
+
+ charger := struct {
+ api.Charger
+ api.CurrentGetter
+ }{
+ ch, cg,
+ }
+
+ ch.EXPECT().Enabled().Return(true, nil)
+ cg.EXPECT().GetMaxCurrent().Return(tc.actualCurrent, nil).MaxTimes(1)
+
+ lp := &Loadpoint{
+ log: util.NewLogger("foo"),
+ bus: evbus.New(),
+ clock: clock.New(),
+ charger: charger,
+ status: api.StatusC,
+ enabled: true,
+ phases: 3,
+ chargeCurrent: tc.lpCurrent,
+ }
+
+ require.NoError(t, lp.syncCharger())
+ assert.Equal(t, tc.outCurrent, lp.chargeCurrent)
+ }
+}
+
+func TestSyncChargerCurrentsByMeasurement(t *testing.T) {
+ tc := []struct {
+ lpCurrent float64
+ actualCurrent float64
+ outCurrent float64
+ }{
+ {6, 5, 6}, // ignore
+ {6, 6.1, 6},
+ {6, 6.5, 6},
+ {6, 7, 6}, // ignore
+ {6, 7.1, 7.1},
+ }
+
+ for _, tc := range tc {
+ ctrl := gomock.NewController(t)
+ t.Logf("%+v", tc)
+
+ charger := api.NewMockCharger(ctrl)
+ charger.EXPECT().Enabled().Return(true, nil)
+
+ lp := &Loadpoint{
+ log: util.NewLogger("foo"),
+ bus: evbus.New(),
+ clock: clock.New(),
+ charger: charger,
+ status: api.StatusC,
+ enabled: true,
+ phases: 3,
+ chargeCurrent: tc.lpCurrent,
+ chargeCurrents: []float64{tc.actualCurrent, 0, 0},
+ }
+
+ require.NoError(t, lp.syncCharger())
+ assert.Equal(t, tc.outCurrent, lp.chargeCurrent)
+ }
+}
+
+func TestSyncChargerPhasesByGetter(t *testing.T) {
+ tc := []struct {
+ lpPhases, actualPhases, outPhases int
+ }{
+ {0, 0, 0},
+ {1, 0, 1},
+ {1, 1, 1},
+ {1, 3, 3},
+ {3, 0, 3},
+ {3, 1, 1}, // force
+ {3, 3, 3},
+ }
+
+ for _, tc := range tc {
+ ctrl := gomock.NewController(t)
+ t.Logf("%+v", tc)
+
+ ch := api.NewMockCharger(ctrl)
+ ps := api.NewMockPhaseSwitcher(ctrl)
+ pg := api.NewMockPhaseGetter(ctrl)
+
+ charger := struct {
+ api.Charger
+ api.PhaseSwitcher
+ api.PhaseGetter
+ }{
+ ch, ps, pg,
+ }
+
+ ch.EXPECT().Enabled().Return(true, nil)
+ pg.EXPECT().GetPhases().Return(tc.actualPhases, nil).MaxTimes(1)
+
+ lp := &Loadpoint{
+ log: util.NewLogger("foo"),
+ bus: evbus.New(),
+ clock: clock.New(),
+ charger: charger,
+ status: api.StatusC,
+ enabled: true,
+ phases: tc.lpPhases,
+ measuredPhases: tc.actualPhases,
+ }
+
+ require.NoError(t, lp.syncCharger())
+ assert.Equal(t, tc.outPhases, lp.phases)
+ }
+}
+
+func TestSyncChargerPhasesByMeasurement(t *testing.T) {
+ tc := []struct {
+ lpPhases, actualPhases, outPhases int
+ }{
+ {0, 0, 0},
+ {1, 0, 1},
+ {1, 1, 1},
+ {1, 3, 3},
+ {3, 0, 3},
+ {3, 1, 3}, // ignore
+ {3, 3, 3},
+ }
+
+ ctrl := gomock.NewController(t)
+
+ for _, tc := range tc {
+ t.Logf("%+v", tc)
+
+ ch := api.NewMockCharger(ctrl)
+ ps := api.NewMockPhaseSwitcher(ctrl)
+
+ charger := struct {
+ api.Charger
+ api.PhaseSwitcher
+ }{
+ ch, ps,
+ }
+
+ ch.EXPECT().Enabled().Return(true, nil)
+
+ lp := &Loadpoint{
+ log: util.NewLogger("foo"),
+ bus: evbus.New(),
+ clock: clock.New(),
+ charger: charger,
+ status: api.StatusC,
+ enabled: true,
+ phases: tc.lpPhases,
+ measuredPhases: tc.actualPhases,
+ }
+
+ require.NoError(t, lp.syncCharger())
+ assert.Equal(t, tc.outPhases, lp.phases)
+ }
+}
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index 92ecbaf1a3..2c74d4742a 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -182,7 +182,7 @@ func TestUpdatePowerZero(t *testing.T) {
}
lp.mode = tc.mode
- lp.Update(0, false, false, false, 0, nil, nil) // false,sitePower false,0
+ lp.Update(0, false, time.Time{}, false, false, 0, nil, nil) // false,sitePower false,0
ctrl.Finish()
}
@@ -428,7 +428,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
- lp.Update(500, false, false, false, 0, nil, nil)
+ lp.Update(500, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("charging above target - soc deactivates charger")
@@ -437,7 +437,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Enable(false).Return(nil)
- lp.Update(500, false, false, false, 0, nil, nil)
+ lp.Update(500, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("deactivated charger changes status to B")
@@ -445,14 +445,14 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
vehicle.EXPECT().Soc().Return(95.0, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
- lp.Update(-5000, false, false, false, 0, nil, nil)
+ lp.Update(-5000, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("soc has fallen below target - soc update prevented by timer")
clock.Add(5 * time.Minute)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
- lp.Update(-5000, false, false, false, 0, nil, nil)
+ lp.Update(-5000, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
t.Log("soc has fallen below target - soc update timer expired")
@@ -461,7 +461,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Enable(true).Return(nil)
- lp.Update(-5000, false, false, false, 0, nil, nil)
+ lp.Update(-5000, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
}
@@ -496,14 +496,14 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
- lp.Update(500, false, false, false, 0, nil, nil)
+ lp.Update(500, false, time.Time{}, false, false, 0, nil, nil)
t.Log("switch off when disconnected")
clock.Add(5 * time.Minute)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusA, nil)
charger.EXPECT().Enable(false).Return(nil)
- lp.Update(-3000, false, false, false, 0, nil, nil)
+ lp.Update(-3000, false, time.Time{}, false, false, 0, nil, nil)
if mode := lp.GetMode(); mode != api.ModeOff {
t.Error("unexpected mode", mode)
@@ -566,14 +566,14 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(0.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
t.Log("at 1:00h charging at 5 kWh")
clock.Add(time.Hour)
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h stop charging at 5 kWh")
@@ -581,7 +581,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h restart charging at 5 kWh")
@@ -589,7 +589,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:30h continue charging at 7.5 kWh")
@@ -597,7 +597,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(7.5, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
expectCache("chargedEnergy", 7500.0)
t.Log("at 2:00h stop charging at 10 kWh")
@@ -605,7 +605,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
rater.EXPECT().ChargedEnergy().Return(10.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
- lp.Update(-1, false, false, false, 0, nil, nil)
+ lp.Update(-1, false, time.Time{}, false, false, 0, nil, nil)
expectCache("chargedEnergy", 10000.0)
ctrl.Finish()
diff --git a/core/loadpoint_vehicle.go b/core/loadpoint_vehicle.go
index a8b5b502fb..c2afa4caf8 100644
--- a/core/loadpoint_vehicle.go
+++ b/core/loadpoint_vehicle.go
@@ -20,12 +20,20 @@ const (
vehicleDetectDuration = 10 * time.Minute
)
+// availableVehicles is the slice of vehicles from the coordinator that are available
+func (lp *Loadpoint) availableVehicles() []api.Vehicle {
+ if lp.coordinator == nil {
+ return nil
+ }
+ return lp.coordinator.GetVehicles(true)
+}
+
// coordinatedVehicles is the slice of vehicles from the coordinator
func (lp *Loadpoint) coordinatedVehicles() []api.Vehicle {
if lp.coordinator == nil {
return nil
}
- return lp.coordinator.GetVehicles()
+ return lp.coordinator.GetVehicles(false)
}
// setVehicleIdentifier updated the vehicle id as read from the charger
diff --git a/core/loadpoint_vehicle_test.go b/core/loadpoint_vehicle_test.go
index 927293de06..3ac7320b1b 100644
--- a/core/loadpoint_vehicle_test.go
+++ b/core/loadpoint_vehicle_test.go
@@ -270,7 +270,7 @@ func TestReconnectVehicle(t *testing.T) {
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusA, nil)
- lp.Update(0, false, false, false, 0, nil, nil)
+ lp.Update(0, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
// detection started
@@ -284,7 +284,7 @@ func TestReconnectVehicle(t *testing.T) {
// vehicle not updated yet
vehicle.MockChargeState.EXPECT().Status().Return(api.StatusB, nil)
- lp.Update(0, false, false, false, 0, nil, nil)
+ lp.Update(0, false, time.Time{}, false, false, 0, nil, nil)
ctrl.Finish()
// vehicle detected
diff --git a/core/planner/helper.go b/core/planner/helper.go
index ef0be143cc..6c2977d111 100644
--- a/core/planner/helper.go
+++ b/core/planner/helper.go
@@ -17,6 +17,16 @@ func Start(plan api.Rates) time.Time {
return start
}
+func End(plan api.Rates) time.Time {
+ var end time.Time
+ for _, slot := range plan {
+ if end.IsZero() || slot.End.After(end) {
+ end = slot.End
+ }
+ }
+ return end
+}
+
// Duration returns the sum of all slot's durations
func Duration(plan api.Rates) time.Duration {
var duration time.Duration
diff --git a/core/site.go b/core/site.go
index bad0f92104..f069063bf1 100644
--- a/core/site.go
+++ b/core/site.go
@@ -2,7 +2,6 @@ package core
import (
"context"
- "errors"
"fmt"
"math"
"strings"
@@ -20,6 +19,7 @@ import (
"github.com/evcc-io/evcc/core/planner"
"github.com/evcc-io/evcc/core/prioritizer"
"github.com/evcc-io/evcc/core/session"
+ "github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/core/soc"
"github.com/evcc-io/evcc/core/vehicle"
"github.com/evcc-io/evcc/push"
@@ -37,7 +37,7 @@ const standbyPower = 10 // consider less than 10W as charger in standby
// updater abstracts the Loadpoint implementation for testing
type updater interface {
loadpoint.API
- Update(availablePower float64, autoCharge, batteryBuffered, batteryStart bool, greenShare float64, effectivePrice, effectiveCo2 *float64)
+ Update(availablePower float64, smartCostActive bool, smartCostNextStart time.Time, batteryBuffered, batteryStart bool, greenShare float64, effectivePrice, effectiveCo2 *float64)
}
// meterMeasurement is used as slice element for publishing structured data
@@ -55,6 +55,8 @@ type batteryMeasurement struct {
Controllable bool `json:"controllable"`
}
+var _ site.API = (*Site)(nil)
+
// Site is the main configuration container. A site can host multiple loadpoints.
type Site struct {
uiChan chan<- util.Param // client push messages
@@ -215,11 +217,6 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif
site.auxMeters = append(site.auxMeters, dev.Instance())
}
- // configure meter from references
- if site.gridMeter == nil && len(site.pvMeters) == 0 {
- return errors.New("missing either grid or pv meter")
- }
-
// revert battery mode on shutdown
shutdown.Register(func() {
if mode := site.GetBatteryMode(); batteryModeModified(mode) {
@@ -570,21 +567,21 @@ func (site *Site) updateGridMeter() error {
return fmt.Errorf("grid meter: %v", err)
}
- // grid phase powers
- var p1, p2, p3 float64
- if phaseMeter, ok := site.gridMeter.(api.PhasePowers); ok {
- var err error // phases needed for signed currents
- if p1, p2, p3, err = phaseMeter.Powers(); err == nil {
- phases := []float64{p1, p2, p3}
- site.log.DEBUG.Printf("grid powers: %.0fW", phases)
- site.publish(keys.GridPowers, phases)
- } else {
- site.log.ERROR.Printf("grid powers: %v", err)
- }
- }
-
// grid phase currents (signed)
if phaseMeter, ok := site.gridMeter.(api.PhaseCurrents); ok {
+ // grid phase powers
+ var p1, p2, p3 float64
+ if phaseMeter, ok := site.gridMeter.(api.PhasePowers); ok {
+ var err error // phases needed for signed currents
+ if p1, p2, p3, err = phaseMeter.Powers(); err == nil {
+ phases := []float64{p1, p2, p3}
+ site.log.DEBUG.Printf("grid powers: %.0fW", phases)
+ site.publish(keys.GridPowers, phases)
+ } else {
+ site.log.ERROR.Printf("grid powers: %v", err)
+ }
+ }
+
if i1, i2, i3, err := phaseMeter.Currents(); err == nil {
phases := []float64{util.SignFromPower(i1, p1), util.SignFromPower(i2, p2), util.SignFromPower(i3, p3)}
site.log.DEBUG.Printf("grid currents: %.3gA", phases)
@@ -799,7 +796,16 @@ func (site *Site) update(lp updater) {
if rate, err := site.plannerRate(); err == nil {
smartCostActive = site.smartCostActive(lp, rate)
} else {
- site.log.WARN.Println("smartCost:", err)
+ site.log.WARN.Println("smartCostActive:", err)
+ }
+
+ var smartCostNextStart time.Time
+ if !smartCostActive {
+ if rates, err := site.plannerRates(); err == nil {
+ smartCostNextStart = site.smartCostNextStart(lp, rates)
+ } else {
+ site.log.WARN.Println("smartCostNextStart:", err)
+ }
}
if sitePower, batteryBuffered, batteryStart, err := site.sitePower(totalChargePower, flexiblePower); err == nil {
@@ -814,7 +820,7 @@ func (site *Site) update(lp updater) {
greenShareHome := site.greenShare(0, homePower)
greenShareLoadpoints := site.greenShare(nonChargePower, nonChargePower+totalChargePower)
- lp.Update(sitePower, smartCostActive, batteryBuffered, batteryStart, greenShareLoadpoints, site.effectivePrice(greenShareLoadpoints), site.effectiveCo2(greenShareLoadpoints))
+ lp.Update(sitePower, smartCostActive, smartCostNextStart, batteryBuffered, batteryStart, greenShareLoadpoints, site.effectivePrice(greenShareLoadpoints), site.effectiveCo2(greenShareLoadpoints))
site.Health.Update()
diff --git a/core/site/api.go b/core/site/api.go
index 13ab7fe8b8..2dc9430fc4 100644
--- a/core/site/api.go
+++ b/core/site/api.go
@@ -11,9 +11,6 @@ type API interface {
Loadpoints() []loadpoint.API
Vehicles() Vehicles
- // GetCircuit returns the assigned circuit
- GetCircuit() api.Circuit
-
// Meta
GetTitle() string
SetTitle(string)
@@ -26,6 +23,10 @@ type API interface {
GetBatteryMeterRefs() []string
SetBatteryMeterRefs([]string)
+ // circuits
+ GetCircuit() api.Circuit
+ SetCircuit(api.Circuit)
+
//
// battery
//
diff --git a/core/site_api.go b/core/site_api.go
index 62ae175ab2..55f28d0329 100644
--- a/core/site_api.go
+++ b/core/site_api.go
@@ -137,15 +137,20 @@ func (site *Site) Vehicles() site.Vehicles {
return &vehicles{log: site.log}
}
-// GetCircuit returns the circuit
+// GetCircuit returns the root circuit
func (site *Site) GetCircuit() api.Circuit {
- if site.circuit == nil {
- // return untyped nil
- return nil
- }
+ site.RLock()
+ defer site.RUnlock()
return site.circuit
}
+// SetCircuit sets the root circuit
+func (site *Site) SetCircuit(circuit api.Circuit) {
+ site.Lock()
+ defer site.Unlock()
+ site.circuit = circuit
+}
+
// GetPrioritySoc returns the PrioritySoc
func (site *Site) GetPrioritySoc() float64 {
site.RLock()
diff --git a/core/site_battery.go b/core/site_battery.go
index 186275d7ba..3804aebc94 100644
--- a/core/site_battery.go
+++ b/core/site_battery.go
@@ -55,14 +55,18 @@ func (site *Site) applyBatteryMode(mode api.BatteryMode) error {
return nil
}
-func (site *Site) plannerRate() (*api.Rate, error) {
+func (site *Site) plannerRates() (api.Rates, error) {
tariff := site.GetTariff(PlannerTariff)
if tariff == nil || tariff.Type() == api.TariffTypePriceStatic {
return nil, nil
}
- rates, err := tariff.Rates()
- if err != nil {
+ return tariff.Rates()
+}
+
+func (site *Site) plannerRate() (*api.Rate, error) {
+ rates, err := site.plannerRates()
+ if rates == nil || err != nil {
return nil, err
}
@@ -79,6 +83,22 @@ func (site *Site) smartCostActive(lp loadpoint.API, rate *api.Rate) bool {
return limit != 0 && rate != nil && rate.Price <= limit
}
+func (site *Site) smartCostNextStart(lp loadpoint.API, rate api.Rates) time.Time {
+ limit := lp.GetSmartCostLimit()
+ if limit == 0 || rate == nil {
+ return time.Time{}
+ }
+
+ now := time.Now()
+ for _, slot := range rate {
+ if slot.Start.After(now) && slot.Price <= limit {
+ return slot.Start
+ }
+ }
+
+ return time.Time{}
+}
+
func (site *Site) updateBatteryMode() {
mode := api.BatteryNormal
diff --git a/core/site_circuits.go b/core/site_circuits.go
index 1e73380096..12e09660ab 100644
--- a/core/site_circuits.go
+++ b/core/site_circuits.go
@@ -2,8 +2,8 @@ package core
import (
"github.com/evcc-io/evcc/core/keys"
- "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
+ "github.com/samber/lo"
)
type circuitStruct struct {
@@ -28,7 +28,7 @@ func (site *Site) publishCircuits() {
}
if instance.GetMaxCurrent() > 0 {
- data.Current = util.PtrTo(instance.GetMaxPhaseCurrent())
+ data.Current = lo.EmptyableToPtr(instance.GetMaxPhaseCurrent())
}
res[c.Config().Name] = data
diff --git a/go.mod b/go.mod
index 7dffd14729..8b84e7c211 100644
--- a/go.mod
+++ b/go.mod
@@ -5,13 +5,13 @@ go 1.22.0
require (
dario.cat/mergo v1.0.0
github.com/AlecAivazis/survey/v2 v2.3.7
- github.com/BurntSushi/toml v1.3.2
+ github.com/BurntSushi/toml v1.4.0
github.com/PuerkitoBio/goquery v1.9.2
github.com/andig/go-powerwall v0.2.1-0.20230808194509-dd70cdb6e140
github.com/andig/gosunspec v0.0.0-20231205122018-1daccfa17912
github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
- github.com/aws/aws-sdk-go v1.52.4
+ github.com/aws/aws-sdk-go v1.54.11
github.com/basgys/goxml2json v1.1.0
github.com/basvdlei/gotsmart v0.0.3
github.com/benbjohnson/clock v1.3.5
@@ -21,17 +21,18 @@ require (
github.com/containrrr/shoutrrr v0.8.0
github.com/coreos/go-oidc/v3 v3.10.0
github.com/denisbrodbeck/machineid v1.0.1
- github.com/dmarkham/enumer v1.5.9
+ github.com/dmarkham/enumer v1.5.10
github.com/dylanmei/iso8601 v0.1.0
github.com/eclipse/paho.mqtt.golang v1.4.3
- github.com/enbility/cemd v0.2.2
- github.com/enbility/eebus-go v0.2.0
+ github.com/enbility/eebus-go v0.6.1
+ github.com/enbility/ship-go v0.5.2
+ github.com/enbility/spine-go v0.6.1
github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701
github.com/fatih/structs v1.1.0
github.com/glebarez/sqlite v1.11.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
- github.com/go-playground/validator/v10 v10.20.0
- github.com/go-sprout/sprout v0.3.1-0.20240510210334-9d4a544518d7
+ github.com/go-playground/validator/v10 v10.22.0
+ github.com/go-sprout/sprout v0.4.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/go-viper/mapstructure/v2 v2.0.0
github.com/godbus/dbus/v5 v5.1.0
@@ -44,11 +45,11 @@ require (
github.com/gregdel/pushover v1.3.1
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/grid-x/modbus v0.0.0-20240503115206-582f2ab60a18
- github.com/hashicorp/go-version v1.6.0
- github.com/hasura/go-graphql-client v0.12.1
+ github.com/hashicorp/go-version v1.7.0
+ github.com/hasura/go-graphql-client v0.12.2
github.com/influxdata/influxdb-client-go/v2 v2.13.0
github.com/insomniacslk/tapo v1.0.1
- github.com/itchyny/gojq v0.12.15
+ github.com/itchyny/gojq v0.12.16
github.com/jeremywohl/flatten v1.0.1
github.com/jinzhu/copier v0.4.0
github.com/jinzhu/now v1.1.5
@@ -61,7 +62,7 @@ require (
github.com/libp2p/zeroconf/v2 v2.2.0
github.com/lorenzodonini/ocpp-go v0.18.0
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
- github.com/mabunixda/wattpilot v1.7.2
+ github.com/mabunixda/wattpilot v1.8.1
github.com/mitchellh/go-homedir v1.1.0
github.com/mlnoga/rct v0.1.2-0.20240421173556-1c5b75037e2f
github.com/muka/go-bluetooth v0.0.0-20240115085408-dfdf79b8f61d
@@ -71,37 +72,37 @@ require (
github.com/philippseith/signalr v0.6.3
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/prometheus-community/pro-bing v0.4.0
- github.com/prometheus/client_golang v1.19.0
- github.com/prometheus/common v0.53.0
+ github.com/prometheus/client_golang v1.19.1
+ github.com/prometheus/common v0.55.0
github.com/robertkrimen/otto v0.4.0
- github.com/samber/lo v1.39.0
+ github.com/samber/lo v1.43.0
github.com/sirupsen/logrus v1.9.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/smallnest/chanx v1.2.0
github.com/spali/go-rscp v0.2.0
github.com/spf13/cast v1.6.0
- github.com/spf13/cobra v1.8.0
+ github.com/spf13/cobra v1.8.1
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.5
- github.com/spf13/viper v1.20.0-alpha.1
+ github.com/spf13/viper v1.20.0-alpha.4
github.com/stretchr/testify v1.9.0
github.com/teslamotors/vehicle-command v0.0.2
github.com/traefik/yaegi v0.16.1
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
- github.com/volkszaehler/mbmd v0.0.0-20240229124119-740c5c8ad344
+ github.com/volkszaehler/mbmd v0.0.0-20240611142726-33463eb0324e
github.com/writeas/go-strip-markdown/v2 v2.1.1
gitlab.com/bboehmke/sunny v0.16.0
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.24.0
- golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
- golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
+ golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658
+ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/net v0.26.0
- golang.org/x/oauth2 v0.20.0
+ golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.7.0
golang.org/x/text v0.16.0
golang.org/x/tools v0.22.0
- google.golang.org/grpc v1.63.2
- google.golang.org/protobuf v1.34.1
+ google.golang.org/grpc v1.64.0
+ google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.10
nhooyr.io/websocket v1.8.11
@@ -119,43 +120,43 @@ require (
github.com/cstockton/go-conv v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/fatih/color v1.16.0 // indirect
+ github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b // indirect
+ github.com/fatih/color v1.17.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
- github.com/go-jose/go-jose/v4 v4.0.1 // indirect
+ github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/golang/mock v1.6.0 // indirect
+ github.com/golanguzb70/lrucache v1.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
- github.com/gorilla/websocket v1.5.2 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
github.com/holoplot/go-avahi v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
- github.com/insomniacslk/xjson v0.0.0-20240314172816-ab1449dc107f // indirect
- github.com/itchyny/timefmt-go v0.1.5 // indirect
+ github.com/insomniacslk/xjson v0.0.0-20240624131953-2ef5f14e6a74 // indirect
+ github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
- github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
- github.com/miekg/dns v1.1.59 // indirect
+ github.com/miekg/dns v1.1.61 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
@@ -164,7 +165,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/procfs v0.14.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
github.com/relvacode/iso8601 v1.4.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rickb777/date v1.20.6 // indirect
@@ -175,6 +176,7 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spali/go-slicereader v0.0.0-20201122145524-8e262e1a5127 // indirect
github.com/spf13/afero v1.11.0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/teivah/onecontext v1.3.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
@@ -184,14 +186,19 @@ require (
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
- modernc.org/libc v1.50.5 // indirect
+ modernc.org/libc v1.53.4 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
- modernc.org/sqlite v1.29.9 // indirect
+ modernc.org/sqlite v1.30.1 // indirect
)
replace gopkg.in/yaml.v3 => github.com/andig/yaml v0.0.0-20240531135838-1ff5761ab467
+
+replace github.com/enbility/spine-go => github.com/enbility/spine-go v0.0.0-20240726200332-a983de1e34b8
+
+replace github.com/enbility/ship-go => github.com/enbility/ship-go v0.0.0-20240731093131-37b1302bca66
+
+replace github.com/lorenzodonini/ocpp-go => github.com/evcc-io/ocpp-go v0.0.0-20240730071053-d69e53b0fce9
diff --git a/go.sum b/go.sum
index 6954ce1fc1..5fa521d367 100644
--- a/go.sum
+++ b/go.sum
@@ -5,8 +5,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
-github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
+github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
@@ -49,8 +49,8 @@ github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.52.4 h1:9VsBVJ2TKf8xPP3+yIPGSYcEBIEymXsJzQoFgQuyvA0=
-github.com/aws/aws-sdk-go v1.52.4/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aws/aws-sdk-go v1.54.11 h1:Zxuv/R+IVS0B66yz4uezhxH9FN9/G2nbxejYqAMFjxk=
+github.com/aws/aws-sdk-go v1.54.11/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/azihsoyn/rijndael256 v0.0.0-20200316065338-d14eefa2b66b h1:/2dABok/UswXOj5rjbR5bZ411ApGBq1pAEZdy5rvFrY=
github.com/azihsoyn/rijndael256 v0.0.0-20200316065338-d14eefa2b66b/go.mod h1:ef+2vMUkiKcy2Tz7HykB01KbgUnkK4gQKq4ZeR4RYVs=
@@ -92,7 +92,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -108,8 +108,8 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dmarkham/enumer v1.5.2/go.mod h1:jZ3PNbNJDEkFGx54MlkSjnDQUo7445l7/guoKdh9cY8=
-github.com/dmarkham/enumer v1.5.9 h1:NM/1ma/AUNieHZg74w67GkHFBNB15muOt3sj486QVZk=
-github.com/dmarkham/enumer v1.5.9/go.mod h1:e4VILe2b1nYK3JKJpRmNdl5xbDQvELc6tQ8b+GsGk6E=
+github.com/dmarkham/enumer v1.5.10 h1:ygL0L6quiTiH1jpp68DyvsWaea6MaZLZrTTkIS++R0M=
+github.com/dmarkham/enumer v1.5.10/go.mod h1:e4VILe2b1nYK3JKJpRmNdl5xbDQvELc6tQ8b+GsGk6E=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -121,19 +121,25 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/enbility/cemd v0.2.2 h1:NrN7DCxv7C6YD5CaYgiebS/iPA3QmQeugF/hvNFKHNA=
-github.com/enbility/cemd v0.2.2/go.mod h1:BZoHbJQJ9/7le4WMFAJWRSgKCCTfNVEOM0c1E3H1JxE=
-github.com/enbility/eebus-go v0.2.0 h1:znQUfG1QYk0Q+vOacrsSNtXmitF1F2Rx9+ohwcRNlRw=
-github.com/enbility/eebus-go v0.2.0/go.mod h1:Ozg1eDUfSbHfQ1dWfyAUa3h8dMtgM/01eO30kHca5zk=
+github.com/enbility/eebus-go v0.6.1 h1:2xKf3+tuScfV0ZWK/spPzoc2ekix6oZdZ0mjmcS9rdQ=
+github.com/enbility/eebus-go v0.6.1/go.mod h1:XulY17uTjq65MWG4LQh28C/D6ogXBUa1e8nNNKssPg4=
+github.com/enbility/ship-go v0.0.0-20240731093131-37b1302bca66 h1:xyyTo5DD8RMegL1ztiCZ93TVG1hogrKMVh1bvbayY6g=
+github.com/enbility/ship-go v0.0.0-20240731093131-37b1302bca66/go.mod h1:jewJWYQ10jNhsnhS1C4jESx3CNmDa5HNWZjBhkTug5Y=
+github.com/enbility/spine-go v0.0.0-20240726200332-a983de1e34b8 h1:hDJgZKbRE2b3wQmjr/5LG1C4YaU5Xy3hrZVxWKOeHIE=
+github.com/enbility/spine-go v0.0.0-20240726200332-a983de1e34b8/go.mod h1:pRGS+C5rZ5rhxTAA1whU8fC9p7lH5ixyut++yEZe470=
+github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b h1:sg3c6LJ4eWffwtt9SW0lgcIX4Oh274vwdJnNFNNrDco=
+github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b/go.mod h1:BjzRRiYX6mWdOgku1xxDE+NsV8PijTby7Q7BkYVdfDU=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evcc-io/ocpp-go v0.0.0-20240730071053-d69e53b0fce9 h1:FLv1vmLnfc8DanI5U1qOTe1Zr0OgZ/tuOvHALtZ2sOI=
+github.com/evcc-io/ocpp-go v0.0.0-20240730071053-d69e53b0fce9/go.mod h1:ZynYDWGw6CslG3vyPuucLsy6AyE+h3XXYlr39jhNiQY=
github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701 h1:3JplY3KS6KMDVDNAU+3+KWmSWmoHIU34qwuIpW6SiHk=
github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701/go.mod h1:zWtAweBqXJTk3HSrPSecz3Q3a2hAUQ4vOE6paJfn03I=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
+github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -146,8 +152,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
+github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
@@ -159,8 +165,8 @@ github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2D
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
-github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
-github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
+github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@@ -181,10 +187,10 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
-github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
-github.com/go-sprout/sprout v0.3.1-0.20240510210334-9d4a544518d7 h1:1g2VKjGbRV/da+uAyd4WMB4XYXTRImXXFNQlt5mxOzU=
-github.com/go-sprout/sprout v0.3.1-0.20240510210334-9d4a544518d7/go.mod h1:BG7Zrds7XG7VZvLAkiT3pOK9rLQ6HSJIB4lAXzLdtaA=
+github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
+github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-sprout/sprout v0.4.1 h1:grvsR21YepGs64EFoIXg4g+5OzIZFwmsw5Y88Wod9sI=
+github.com/go-sprout/sprout v0.4.1/go.mod h1:jRgO0n+24zLgiPAg/6rMaeq2oEnBSGlZiHUoK3hnQc4=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@@ -214,8 +220,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -229,6 +233,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golanguzb70/lrucache v1.2.0 h1:VjpjmB4VTf9VXBtZTJGcgcN0CNFM5egDrrSjkGyQOlg=
+github.com/golanguzb70/lrucache v1.2.0/go.mod h1:zc2GD26KwGEDdTHsCCTcJorv/11HyKwQVS9gqg2bizc=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -264,8 +270,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw=
-github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo=
github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
@@ -292,19 +298,17 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
-github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hasura/go-graphql-client v0.12.1 h1:tL+BCoyubkYYyaQ+tJz+oPe/pSxYwOJHwe5SSqqi6WI=
-github.com/hasura/go-graphql-client v0.12.1/go.mod h1:F4N4kR6vY8amio3gEu3tjSZr8GPOXJr3zj72DKixfLE=
+github.com/hasura/go-graphql-client v0.12.2 h1:cYeQK/CELtvFy2jvik4kG0b5UMGngQRYWTTXQkbGHDo=
+github.com/hasura/go-graphql-client v0.12.2/go.mod h1:17qYcHgGSensF/wMAHKUhtMYaRZwZa3TyD7biqH9L3k=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/holoplot/go-avahi v1.0.1 h1:XcqR2keL4qWRnlxHD5CAOdWpLFZJ+EOUK0vEuylfvvk=
@@ -321,12 +325,12 @@ github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmne
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/insomniacslk/tapo v1.0.1 h1:W7K/1SXR8fvCNqY1Qg+GB3ELUjsHAqXtBjBjes7aRQ0=
github.com/insomniacslk/tapo v1.0.1/go.mod h1:1wuMYu0+alZ4oE4BIzxAwQNveZgbb2tRqiIUwIe7SZE=
-github.com/insomniacslk/xjson v0.0.0-20240314172816-ab1449dc107f h1:fU9XEYZOydvaOH7AjYcTyyhR2kRvDjiN2s7pRyWY2pM=
-github.com/insomniacslk/xjson v0.0.0-20240314172816-ab1449dc107f/go.mod h1:Z4EVr4bVv9LZbbje9xyZEyOLpdCOmCvr5S9BJtrdTfw=
-github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
-github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10=
-github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
-github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
+github.com/insomniacslk/xjson v0.0.0-20240624131953-2ef5f14e6a74 h1:vtc2PF74Oi/Z92JO4feHB62J6sO1nmtcm1nfiE3G9ZM=
+github.com/insomniacslk/xjson v0.0.0-20240624131953-2ef5f14e6a74/go.mod h1:Z4EVr4bVv9LZbbje9xyZEyOLpdCOmCvr5S9BJtrdTfw=
+github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
+github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
+github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
+github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
@@ -388,15 +392,11 @@ github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv
github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lorenzodonini/ocpp-go v0.18.0 h1:XhsKAzrG/1QJym2SYyiwTzr4cOa8geN8qSid3MFuiQ4=
-github.com/lorenzodonini/ocpp-go v0.18.0/go.mod h1:ZynYDWGw6CslG3vyPuucLsy6AyE+h3XXYlr39jhNiQY=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
-github.com/mabunixda/wattpilot v1.7.2 h1:DZcyZSJvu3YTVhbQSKnr4w1T4zL3krKourKAekRaevA=
-github.com/mabunixda/wattpilot v1.7.2/go.mod h1:9Gre9Jt8rVak0snzzlwLHCLxGfz36EmJ98fhgJ+jYkw=
-github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mabunixda/wattpilot v1.8.1 h1:XP+Udu1HrSTjBd4Odl0SNpSr12uvnGndoDg2vSNqcdI=
+github.com/mabunixda/wattpilot v1.8.1/go.mod h1:oXWlvKInaVt8FFSdEoNObEP5N8+4m6cbiF11M+8DAHY=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -419,8 +419,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
-github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
-github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
+github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
+github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@@ -445,6 +445,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muka/go-bluetooth v0.0.0-20240115085408-dfdf79b8f61d h1:7ZjEBZo3QkT5hu4koEPrNJ5SKQ2NdHjpB+uJpxUcWDc=
github.com/muka/go-bluetooth v0.0.0-20240115085408-dfdf79b8f61d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -527,8 +529,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
-github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
-github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -542,16 +544,16 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
-github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
-github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/relvacode/iso8601 v1.3.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs=
@@ -576,8 +578,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
-github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
-github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
+github.com/samber/lo v1.43.0 h1:ts0VhPi8+ZQZFVLv/2Vkgt2Cds05FM2v3Enmv+YMBtg=
+github.com/samber/lo v1.43.0/go.mod h1:w7R6fO7h2lrnx/s0bWcZ55vXJI89p5UPM6+kyDL373E=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -611,15 +613,15 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
-github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.20.0-alpha.1 h1:ScUDXU3yX4i/I1ovQQtaGe9kwl63fGzG3UsFr+pGtyY=
-github.com/spf13/viper v1.20.0-alpha.1/go.mod h1:VJcuMrHwg6XArbUYF9KBRrFpavzx9NrQMFtdcRsZCLs=
+github.com/spf13/viper v1.20.0-alpha.4 h1:GgiFxprBcBIOY/1gBby+91p7wmJPWbqjScQWtok9eiY=
+github.com/spf13/viper v1.20.0-alpha.4/go.mod h1:CGBZzv0c9fOUASm6rfus4wdeIjR/04NOLq1P4KRhX3k=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -660,13 +662,12 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
-github.com/volkszaehler/mbmd v0.0.0-20240229124119-740c5c8ad344 h1:pxfwqAjssh3n0bz0WcR77sg7twlYdopTjTkBzsjcOSU=
-github.com/volkszaehler/mbmd v0.0.0-20240229124119-740c5c8ad344/go.mod h1:p1nUKfszvbZ+mMMsMi1vaKGA/eBAXC34VSKCk5slyG4=
+github.com/volkszaehler/mbmd v0.0.0-20240611142726-33463eb0324e h1:1iAo0qalenDcyo9ySgRWjDuMTpfyA/rBDKklMMNahZk=
+github.com/volkszaehler/mbmd v0.0.0-20240611142726-33463eb0324e/go.mod h1:p1nUKfszvbZ+mMMsMi1vaKGA/eBAXC34VSKCk5slyG4=
github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iLei22Ijc3q2e28=
github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/bboehmke/sunny v0.16.0 h1:arcU5MNupJ9KELOcSC82mYPGP/friBop+PxtybhqRwo=
gitlab.com/bboehmke/sunny v0.16.0/go.mod h1:F5AIuL7kYteSJFR5E+YEocxIdpyCXmtDciFmMQVjP88=
@@ -701,11 +702,11 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
-golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
-golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 h1:i7K6wQLN/0oxF7FT3tKkfMCstxoT4VGG36YIB9ZKLzI=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
-golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -714,7 +715,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
@@ -740,7 +740,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -750,8 +749,8 @@ golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
-golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -796,10 +795,8 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -848,7 +845,6 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
@@ -866,8 +862,8 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae h1:c55+MER4zkBS14uJhSZMGGmya0yJx5iHV4x/fpOSNRk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -877,16 +873,16 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
-google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -903,8 +899,6 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
@@ -927,16 +921,16 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-modernc.org/cc/v4 v4.21.0 h1:D/gLKtcztomvWbsbvBKo3leKQv+86f+DdqEZBBXhnag=
-modernc.org/cc/v4 v4.21.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
-modernc.org/ccgo/v4 v4.17.3 h1:t2CQci84jnxKw3GGnHvjGKjiNZeZqyQx/023spkk4hU=
-modernc.org/ccgo/v4 v4.17.3/go.mod h1:1FCbAtWYJoKuc+AviS+dH+vGNtYmFJqBeRWjmnDWsIg=
+modernc.org/cc/v4 v4.21.3 h1:2mhBdWKtivdFlLR1ecKXTljPG1mfvbByX7QKztAIJl8=
+modernc.org/cc/v4 v4.21.3/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
+modernc.org/ccgo/v4 v4.18.2 h1:PUQPShG4HwghpOekNujL0sFavdkRvmxzTbI4rGJ5mg0=
+modernc.org/ccgo/v4 v4.18.2/go.mod h1:ao1fAxf9a2KEOL15WY8+yP3wnpaOpP/QuyFOZ9HJolM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
-modernc.org/libc v1.50.5 h1:ZzeUd0dIc/sUtoPTCYIrgypkuzoGzNu6kbEWj2VuEmk=
-modernc.org/libc v1.50.5/go.mod h1:rhzrUx5oePTSTIzBgM0mTftwWHK8tiT9aNFUt1mldl0=
+modernc.org/libc v1.53.4 h1:YAgFS7tGIFBfqje2UOqiXtIwuDUCF8AUonYw0seup34=
+modernc.org/libc v1.53.4/go.mod h1:aGsLofnkcct8lTJnKQnCqJO37ERAXSHamSuWLFoF2Cw=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -945,8 +939,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
-modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow=
-modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
+modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
+modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
diff --git a/hems/config.go b/hems/config.go
index 5de65493fb..fb5be71ac4 100644
--- a/hems/config.go
+++ b/hems/config.go
@@ -5,6 +5,8 @@ import (
"strings"
"github.com/evcc-io/evcc/core/site"
+ "github.com/evcc-io/evcc/hems/eebus"
+ "github.com/evcc-io/evcc/hems/relay"
"github.com/evcc-io/evcc/hems/semp"
"github.com/evcc-io/evcc/server"
)
@@ -19,8 +21,10 @@ func NewFromConfig(typ string, other map[string]interface{}, site site.API, http
switch strings.ToLower(typ) {
case "sma", "shm", "semp":
return semp.New(other, site, httpd)
- // case "ocpp":
- // return ocpp.New(other, site)
+ case "eebus":
+ return eebus.New(other, site)
+ case "relay":
+ return relay.New(other, site)
default:
return nil, errors.New("unknown hems: " + typ)
}
diff --git a/hems/eebus/eebus.go b/hems/eebus/eebus.go
new file mode 100644
index 0000000000..3a7a7947be
--- /dev/null
+++ b/hems/eebus/eebus.go
@@ -0,0 +1,216 @@
+package eebus
+
+import (
+ "errors"
+ "sync"
+ "time"
+
+ ucapi "github.com/enbility/eebus-go/usecases/api"
+ "github.com/evcc-io/evcc/api"
+ "github.com/evcc-io/evcc/core/circuit"
+ "github.com/evcc-io/evcc/core/site"
+ "github.com/evcc-io/evcc/provider"
+ "github.com/evcc-io/evcc/server/eebus"
+ "github.com/evcc-io/evcc/util"
+)
+
+type EEBus struct {
+ mux sync.RWMutex
+ log *util.Logger
+
+ *eebus.Connector
+ uc *eebus.UseCasesCS
+
+ root api.Circuit
+
+ status status
+ statusUpdated time.Time
+
+ consumptionLimit *ucapi.LoadLimit // LPC-041
+ failsafeLimit float64
+ failsafeDuration time.Duration
+
+ heartbeat *provider.Value[struct{}]
+}
+
+type Limits struct {
+ ContractualConsumptionNominalMax float64
+ ConsumptionLimit float64
+ FailsafeConsumptionActivePowerLimit float64
+ FailsafeDurationMinimum time.Duration
+}
+
+// New creates an EEBus HEMS from generic config
+func New(other map[string]interface{}, site site.API) (*EEBus, error) {
+ cc := struct {
+ Ski string
+ Limits `mapstructure:",squash"`
+ }{
+ Limits: Limits{
+ ContractualConsumptionNominalMax: 24800,
+ ConsumptionLimit: 0,
+ FailsafeConsumptionActivePowerLimit: 4200,
+ FailsafeDurationMinimum: 2 * time.Hour,
+ },
+ }
+
+ if err := util.DecodeOther(other, &cc); err != nil {
+ return nil, err
+ }
+
+ // get root circuit
+ root := circuit.Root()
+ if root == nil {
+ return nil, errors.New("hems requires load management- please configure root circuit")
+ }
+
+ // create new root circuit for LPC
+ lpc, err := circuit.New(util.NewLogger("lpc"), "eebus", 0, 0, nil, time.Minute)
+ if err != nil {
+ return nil, err
+ }
+
+ // wrap old root with new pc parent
+ if err := root.Wrap(lpc); err != nil {
+ return nil, err
+ }
+ site.SetCircuit(lpc)
+
+ return NewEEBus(cc.Ski, cc.Limits, lpc)
+}
+
+// NewEEBus creates EEBus charger
+func NewEEBus(ski string, limits Limits, root api.Circuit) (*EEBus, error) {
+ if eebus.Instance == nil {
+ return nil, errors.New("eebus not configured")
+ }
+
+ c := &EEBus{
+ log: util.NewLogger("eebus"),
+ root: root,
+ uc: eebus.Instance.ControllableSystem(),
+ Connector: eebus.NewConnector(nil),
+ heartbeat: provider.NewValue[struct{}](2 * time.Minute), // LPC-031
+
+ consumptionLimit: &ucapi.LoadLimit{
+ Value: limits.ConsumptionLimit,
+ IsChangeable: true,
+ },
+
+ failsafeLimit: limits.FailsafeConsumptionActivePowerLimit,
+ failsafeDuration: limits.FailsafeDurationMinimum,
+ }
+
+ if err := eebus.Instance.RegisterDevice(ski, c); err != nil {
+ return nil, err
+ }
+
+ if err := c.Wait(90 * time.Second); err != nil {
+ return c, err
+ }
+
+ // scenarios
+ for _, s := range c.uc.LPC.RemoteEntitiesScenarios() {
+ c.log.DEBUG.Println("LPC RemoteEntitiesScenarios:", s.Scenarios)
+ }
+ for _, s := range c.uc.LPP.RemoteEntitiesScenarios() {
+ c.log.DEBUG.Println("LPP RemoteEntitiesScenarios:", s.Scenarios)
+ }
+ for _, s := range c.uc.MGCP.RemoteEntitiesScenarios() {
+ c.log.DEBUG.Println("MGCP RemoteEntitiesScenarios:", s.Scenarios)
+ }
+
+ // set initial values
+ if err := c.uc.LPC.SetContractualConsumptionNominalMax(limits.ContractualConsumptionNominalMax); err != nil {
+ c.log.ERROR.Println("LPC SetContractualConsumptionNominalMax:", err)
+ }
+ if err := c.uc.LPC.SetConsumptionLimit(*c.consumptionLimit); err != nil {
+ c.log.ERROR.Println("LPC SetConsumptionLimit:", err)
+ }
+ if err := c.uc.LPC.SetFailsafeConsumptionActivePowerLimit(c.failsafeLimit, true); err != nil {
+ c.log.ERROR.Println("LPC SetFailsafeConsumptionActivePowerLimit:", err)
+ }
+ if err := c.uc.LPC.SetFailsafeDurationMinimum(c.failsafeDuration, true); err != nil {
+ c.log.ERROR.Println("LPC SetFailsafeDurationMinimum:", err)
+ }
+
+ return c, nil
+}
+
+func (c *EEBus) Run() {
+ for range time.Tick(10 * time.Second) {
+ if err := c.run(); err != nil {
+ c.log.ERROR.Println(err)
+ }
+ }
+}
+
+// TODO check state machine against spec
+func (c *EEBus) run() error {
+ c.mux.RLock()
+ defer c.mux.RUnlock()
+
+ c.log.TRACE.Println("status:", c.status)
+
+ // check heartbeat
+ _, heartbeatErr := c.heartbeat.Get()
+ if heartbeatErr != nil && c.status != StatusFailsafe {
+ // LPC-914/2
+ c.log.WARN.Println("missing heartbeat- entering failsafe mode")
+ c.setStatusAndLimit(StatusFailsafe, c.failsafeLimit)
+
+ return nil
+ }
+
+ // TODO
+ // status init
+ // status Unlimited/controlled
+ // status Unlimited/autonomous
+
+ switch c.status {
+ case StatusUnlimited:
+ // LPC-914/1
+ if c.consumptionLimit != nil && c.consumptionLimit.IsActive {
+ c.log.WARN.Println("active consumption limit")
+ c.setStatusAndLimit(StatusLimited, c.consumptionLimit.Value)
+ }
+
+ case StatusLimited:
+ // limit updated?
+ if !c.consumptionLimit.IsActive {
+ c.log.WARN.Println("inactive consumption limit")
+ c.setStatusAndLimit(StatusUnlimited, 0)
+ break
+ }
+
+ c.setLimit(c.consumptionLimit.Value)
+
+ // LPC-914/1
+ if d := c.consumptionLimit.Duration; d > 0 && time.Since(c.statusUpdated) > d {
+ c.consumptionLimit = nil
+
+ c.log.DEBUG.Println("limit duration exceeded- return to normal")
+ c.setStatusAndLimit(StatusUnlimited, 0)
+ }
+
+ case StatusFailsafe:
+ // LPC-914/2
+ if d := c.failsafeDuration; heartbeatErr == nil && time.Since(c.statusUpdated) > d {
+ c.log.DEBUG.Println("heartbeat returned and failsafe duration exceeded- return to normal")
+ c.setStatusAndLimit(StatusUnlimited, 0)
+ }
+ }
+
+ return nil
+}
+
+func (c *EEBus) setStatusAndLimit(status status, limit float64) {
+ c.status = status
+ c.statusUpdated = time.Now()
+
+ c.setLimit(limit)
+}
+
+func (c *EEBus) setLimit(limit float64) {
+ c.root.SetMaxPower(limit)
+}
diff --git a/hems/eebus/events.go b/hems/eebus/events.go
new file mode 100644
index 0000000000..8808047aad
--- /dev/null
+++ b/hems/eebus/events.go
@@ -0,0 +1,158 @@
+package eebus
+
+import (
+ eebusapi "github.com/enbility/eebus-go/api"
+ "github.com/enbility/eebus-go/usecases/cs/lpc"
+ spineapi "github.com/enbility/spine-go/api"
+ "github.com/evcc-io/evcc/server/eebus"
+)
+
+var _ eebus.Device = (*EEBus)(nil)
+
+// UseCaseEvent implements the eebus.Device interface
+func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
+ switch event {
+ // Load control obligation limit data update received
+ //
+ // Use `ConsumptionLimit` to get the current data
+ //
+ // Use Case LPC, Scenario 1
+ case lpc.DataUpdateLimit:
+ c.dataUpdateLimit()
+
+ // An incoming load control obligation limit needs to be approved or denied
+ //
+ // Use `PendingConsumptionLimits` to get the currently pending write approval requests
+ // and invoke `ApproveOrDenyConsumptionLimit` for each
+ //
+ // Use Case LPC, Scenario 1
+ case lpc.WriteApprovalRequired:
+ c.writeApprovalRequired()
+
+ // Failsafe limit for the consumed active (real) power of the
+ // Controllable System data update received
+ //
+ // Use `FailsafeConsumptionActivePowerLimit` to get the current data
+ //
+ // Use Case LPC, Scenario 2
+ case lpc.DataUpdateFailsafeConsumptionActivePowerLimit:
+ c.dataUpdateFailsafeConsumptionActivePowerLimit()
+
+ // Minimum time the Controllable System remains in "failsafe state" unless conditions
+ // specified in this Use Case permit leaving the "failsafe state" data update received
+ //
+ // Use `FailsafeDurationMinimum` to get the current data
+ //
+ // Use Case LPC, Scenario 2
+ case lpc.DataUpdateFailsafeDurationMinimum:
+ c.dataUpdateFailsafeDurationMinimum()
+
+ // Indicates a notify heartbeat event the application should care of.
+ // E.g. going into or out of the Failsafe state
+ //
+ // Use Case LPC, Scenario 3
+ case lpc.DataUpdateHeartbeat:
+ c.dataUpdateHeartbeat()
+
+ // // Load control obligation limit data update received
+ // //
+ // // Use `ProductionLimit` to get the current data
+ // //
+ // // Use Case LPC, Scenario 1
+ // case lpp.DataUpdateLimit:
+ // c.dataUpdateLimit()
+
+ // // An incoming load control obligation limit needs to be approved or denied
+ // //
+ // // Use `PendingProductionLimits` to get the currently pending write approval requests
+ // // and invoke `ApproveOrDenyProductionLimit` for each
+ // //
+ // // Use Case LPC, Scenario 1
+ // case lpp.WriteApprovalRequired:
+ // c.writeApprovalRequired()
+
+ // // Failsafe limit for the produced active (real) power of the
+ // // Controllable System data update received
+ // //
+ // // Use `FailsafeProductionActivePowerLimit` to get the current data
+ // //
+ // // Use Case LPC, Scenario 2
+ // case lpp.DataUpdateFailsafeProductionActivePowerLimit:
+ // c.dataUpdateFailsafeProductionActivePowerLimit()
+
+ // // Minimum time the Controllable System remains in "failsafe state" unless conditions
+ // // specified in this Use Case permit leaving the "failsafe state" data update received
+ // //
+ // // Use `FailsafeDurationMinimum` to get the current data
+ // //
+ // // Use Case LPC, Scenario 2
+ // case lpp.DataUpdateFailsafeDurationMinimum:
+ // c.dataUpdateFailsafeDurationMinimum()
+
+ // // Indicates a notify heartbeat event the application should care of.
+ // // E.g. going into or out of the Failsafe state
+ // //
+ // // Use Case LPP, Scenario 3
+ // case lpp.DataUpdateHeartbeat:
+ // c.dataUpdateHeartbeat()
+ }
+}
+
+func (c *EEBus) dataUpdateLimit() {
+ limit, err := c.uc.LPC.ConsumptionLimit()
+ if err != nil {
+ c.log.ERROR.Println("LPC.ConsumptionLimit:", err)
+ return
+ }
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.consumptionLimit = &limit
+}
+
+func (c *EEBus) writeApprovalRequired() {
+ for k, v := range c.uc.LPC.PendingConsumptionLimits() {
+ c.log.DEBUG.Println("LPC.PendingConsumptionLimit:", k, v)
+ c.uc.LPC.ApproveOrDenyConsumptionLimit(k, false, "not implemented")
+ }
+}
+
+func (c *EEBus) dataUpdateFailsafeConsumptionActivePowerLimit() {
+ limit, _, err := c.uc.LPC.FailsafeConsumptionActivePowerLimit()
+ if err != nil {
+ c.log.ERROR.Println("LPC.FailsafeConsumptionActivePowerLimit:", err)
+ return
+ }
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.failsafeLimit = limit
+}
+
+func (c *EEBus) dataUpdateFailsafeDurationMinimum() {
+ duration, _, err := c.uc.LPC.FailsafeDurationMinimum()
+ if err != nil {
+ c.log.ERROR.Println("LPC.FailsafeDurationMinimum:", err)
+ return
+ }
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.failsafeDuration = duration
+}
+
+func (c *EEBus) dataUpdateHeartbeat() {
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.heartbeat.Set(struct{}{})
+}
+
+// func (c *EEBus)dataUpdateLimit(){}
+// func (c *EEBus)writeApprovalRequired(){}
+// func (c *EEBus)dataUpdateFailsafeProductionActivePowerLimit(){}
+// func (c *EEBus)dataUpdateFailsafeDurationMinimum(){}
+// func (c *EEBus)dataUpdateHeartbeat(){}
diff --git a/hems/eebus/types.go b/hems/eebus/types.go
new file mode 100644
index 0000000000..f0a6623e2a
--- /dev/null
+++ b/hems/eebus/types.go
@@ -0,0 +1,9 @@
+package eebus
+
+type status int
+
+const (
+ StatusUnlimited status = iota
+ StatusLimited
+ StatusFailsafe
+)
diff --git a/hems/relay/steuerbox.go b/hems/relay/steuerbox.go
new file mode 100644
index 0000000000..741b3bc77c
--- /dev/null
+++ b/hems/relay/steuerbox.go
@@ -0,0 +1,94 @@
+package relay
+
+import (
+ "errors"
+ "time"
+
+ "github.com/evcc-io/evcc/api"
+ "github.com/evcc-io/evcc/core/circuit"
+ "github.com/evcc-io/evcc/core/site"
+ "github.com/evcc-io/evcc/provider"
+ "github.com/evcc-io/evcc/util"
+)
+
+type Relay struct {
+ log *util.Logger
+
+ root api.Circuit
+ limit func() (bool, error)
+ maxPower float64
+}
+
+// New creates an Relay HEMS from generic config
+func New(other map[string]interface{}, site site.API) (*Relay, error) {
+ var cc struct {
+ MaxPower float64
+ Limit provider.Config
+ }
+
+ if err := util.DecodeOther(other, &cc); err != nil {
+ return nil, err
+ }
+
+ // get root circuit
+ root := circuit.Root()
+ if root == nil {
+ return nil, errors.New("hems requires load management- please configure root circuit")
+ }
+
+ // create new root circuit for LPC
+ lpc, err := circuit.New(util.NewLogger("lpc"), "relay", 0, 0, nil, time.Minute)
+ if err != nil {
+ return nil, err
+ }
+
+ // wrap old root with new pc parent
+ if err := root.Wrap(lpc); err != nil {
+ return nil, err
+ }
+ site.SetCircuit(lpc)
+
+ // limit getter
+ limitG, err := provider.NewBoolGetterFromConfig(cc.Limit)
+ if err != nil {
+ return nil, err
+ }
+
+ return NewRelay(lpc, limitG, cc.MaxPower)
+}
+
+// NewRelay creates Relay HEMS
+func NewRelay(root api.Circuit, limit func() (bool, error), maxPower float64) (*Relay, error) {
+ c := &Relay{
+ log: util.NewLogger("relay"),
+ root: root,
+ maxPower: maxPower,
+ limit: limit,
+ }
+
+ return c, nil
+}
+
+func (c *Relay) Run() {
+ for range time.Tick(10 * time.Second) {
+ if err := c.run(); err != nil {
+ c.log.ERROR.Println(err)
+ }
+ }
+}
+
+func (c *Relay) run() error {
+ limit, err := c.limit()
+ if err != nil {
+ return err
+ }
+
+ var power float64
+ if limit {
+ power = c.maxPower
+ }
+
+ c.root.SetMaxPower(power)
+
+ return nil
+}
diff --git a/histoire.config.js b/histoire.config.js
index f6ff3a517d..53f1a99a6d 100644
--- a/histoire.config.js
+++ b/histoire.config.js
@@ -4,7 +4,7 @@ import { HstVue } from "@histoire/plugin-vue";
export default defineConfig({
plugins: [HstVue()],
setupFile: "./histoire.setup.js",
- viteNodeInlineDeps: [/!axios/],
+ viteNodeInlineDeps: [/!axios/, /shopicons/],
routerMode: "hash",
backgroundPresets: [
{
diff --git a/i18n/da.toml b/i18n/da.toml
index 1f0b3402cb..8063f56a95 100644
--- a/i18n/da.toml
+++ b/i18n/da.toml
@@ -364,7 +364,7 @@ update = "Opdater automatisk"
[loginModal]
cancel = "Afbryd"
-error = "Login mislykkedes"
+error = "Login mislykkedes: "
iframeHint = "Åbn evcc i en ny fane."
iframeIssue = "Dit kodeord er korrekt, men din browser ser ud til at have droppet godkendelses-cookien. Dette kan ske, hvis du kører evcc i en iframe via HTTP."
invalid = "Ugyldig adgangskode"
@@ -394,7 +394,7 @@ update = "Anvend"
[main.energyflow]
battery = "Batteri"
-batteryCharge = "Batteri opladning"
+batteryCharge = "Batteriet oplades"
batteryDischarge = "Batteriet aflades"
batteryHold = "Batteri (låst)"
batteryTooltip = "{energi} af {total} ({soc})"
@@ -463,6 +463,7 @@ minpv = "Min+Sol"
now = "Hurtig"
off = "Fra"
pv = "Sol"
+smart = "Smart"
[main.provider]
login = "Log på"
@@ -478,7 +479,7 @@ descriptionSoc = "Hvornår skal køretøjet være opladet til {targetSoc}?"
inactiveLabel = "Planlagt tid"
modalTitle = "Sæt måltid"
noActivePlan = "Ingen aktiv plan"
-notReachableInTime = "Mål kan ikke opnås i tide. Forventet færdig: {endTime}."
+notReachableInTime = "Målet vil blive nået {overrun} senere."
onlyInPvMode = "Opladningsplan virker kun i solar mode"
planDescription = "Angiv et afgangstidpunkt og evcc vil oplade køretøjet så billigt og miljøvenligt som muligt"
planDuration = "Opladningstid"
@@ -491,7 +492,7 @@ remove = "Fjern"
setPlan = "Angiv en ladeplan"
setTargetTime = "ingen"
targetIsAboveLimit = "Den indstillede opladningsgrænse på {limit} ignoreres i denne periode\""
-targetIsAboveVehicleLimit = "Forøg køretøjets begrænsning {limit} for at kunne nå opladningsmålet"
+targetIsAboveVehicleLimit = "Køretøjets begrænsning er under opladningsmålet"
targetIsInThePast = "Vælg et tidspunkt i fremtiden, Marty."
targetIsTooFarInTheFuture = "Vi justerer planen så snart vi ved mere om fremtiden."
title = "Planlagt tid"
@@ -535,21 +536,27 @@ vehicleLimit = "Køretøjsgrænse: {soc}"
[main.vehicleStatus]
charging = "Oplader..."
-cheapEnergyCharging = "Oplader med billig energi: {price} (limit {limit})"
-cleanEnergyCharging = "Oplader med CO2 neutral energi: {co2} (limit {limit})"
+cheapEnergyCharging = "Billig energi er tilgængelig."
+cheapEnergyNextStart = "Billig energi i {duration}."
+cheapEnergySet = "Prisgrænse fastsat"
+cleanEnergyCharging = "Grøn energi er tilgængelig."
+cleanEnergyNextStart = "Grøn energi i {duration}."
+cleanEnergySet = "CO₂ grænse fastsat."
climating = "Forkonditionering registreret."
connected = "Forbundet."
disconnected = "Afbrudt."
+finished = "Færdig."
minCharge = "Minimum opladning til {soc}."
-pvDisable = "Ikke nok overskud. Pause i {remaining}..."
-pvEnable = "Overskud tilgængeligt. Starter om {remaining}..."
-scale1p = "Reducerer til 1-faset strøm i {remaining}..."
-scale3p = "Øger til 3-faset opladning om {remaining}..."
-targetChargeActive = "Plan for ladning er aktiv..."
-targetChargePlanned = "Opladningsplan starter kl. {time}."
+pvDisable = "Ikke nok overskud. Holder snart pause."
+pvEnable = "Overskud tilgængeligt. Starter snart."
+scale1p = "Reducerer snart til 1-faset opladning."
+scale3p = "Øger snart til 3-faset opladning."
+targetChargeActive = "Opladningsplan er aktiv. Forventes afsluttet om {duration}."
+targetChargePlanned = "Opladningsplan starter om {duration}."
targetChargeWaitForVehicle = "Plan for opladning er klar. Venter på køretøj..."
unknown = ""
-vehicleLimitReached = "Køretøjsgrænsen {soc} nået."
+vehicleLimit = "Køretøjets grænse"
+vehicleLimitReached = "Køretøjets grænse er nået."
waitForVehicle = "Parat. Venter på køretøj ..."
[notifications]
@@ -558,6 +565,7 @@ logs = "Se hele log filen"
modalTitle = "Underretninger"
[offline]
+configurationError = "Fejl under opstart. Tjek din konfiguration og genstart."
message = "Ikke forbundet til en server."
reload = "Genindlæs?"
restart = "Genstart"
@@ -606,7 +614,7 @@ noData = "Ingen opladninger i denne måned."
price = "Σ pris"
reallyDelete = "Er du sikker på at du vil slette denne session?"
solar = "solenergi"
-title = "Opladning Sessioner"
+title = "Opladnings Sessioner"
total = "Total"
vehicle = "Køretøj"
diff --git a/i18n/de.toml b/i18n/de.toml
index 974c05acdd..257f6a79ba 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -42,6 +42,7 @@ title = "Regelverhalten"
[config.deviceValue]
broker = "Broker"
+bucket = "Bucket"
capacity = "Kapazität"
chargeStatus = "Status"
chargeStatusA = "nicht verbunden"
@@ -54,6 +55,7 @@ co2 = "Netz-CO₂"
configured = "Konfiguriert"
currency = "Währung"
current = "Strom"
+currentRange = "Strom"
enabled = "Aktiviert"
energy = "Energie"
feedinPrice = "Einspeisevergütung"
@@ -65,6 +67,7 @@ phaseCurrents = "Strom L1..L3"
phasePowers = "Leistung L1..L3"
phaseVoltages = "Spannung L1..L3"
power = "Leistung"
+powerRange = "Leistung"
range = "Reichweite"
soc = "SoC"
socLimit = "SoC Begrenzung"
@@ -396,8 +399,6 @@ selfConsumption = "Eigenverbrauch"
[main.heatingStatus]
charging = "Heize …"
-cheapEnergyCharging = "Heize mit günstiger Energie: {price} (Grenze {limit})"
-cleanEnergyCharging = "Heize mit sauberer Energie: {co2} (Grenze {limit})"
waitForVehicle = "Bereit. Warte auf Heizung …"
[main.loadpoint]
@@ -451,6 +452,7 @@ minpv = "Min+PV"
now = "Schnell"
off = "Aus"
pv = "PV"
+smart = "Smart"
[main.provider]
login = "anmelden"
@@ -464,7 +466,7 @@ currentPlan = "Aktiver Plan"
descriptionEnergy = "Bis wann sollen {targetEnergy} ins Fahrzeug geladen sein?"
descriptionSoc = "Wann soll das Fahrzeug auf {targetSoc} geladen sein?"
inactiveLabel = "Zielzeit"
-notReachableInTime = "Zielzeit nicht erreichbar. Voraussichtliches Ende: {endTime}."
+notReachableInTime = "Zielzeit wird {overrun} später erreicht."
onlyInPvMode = "Ladeplanung ist nur im PV-Modus aktiv."
planDuration = "Ladedauer"
planPeriodLabel = "Zeitraum"
@@ -475,7 +477,7 @@ priceLimit = "Preisgrenze von {price}"
remove = "Entfernen"
setTargetTime = "keine"
targetIsAboveLimit = "Das eingestellte Ladelimit von {limit} wird in diesem Zeitraum ignoriert."
-targetIsAboveVehicleLimit = "Erhöhe das Fahrzeuglimit ({limit}), um das Ladeziel zu erreichen."
+targetIsAboveVehicleLimit = "Fahrzeuglimit ist kleiner als das Ladeziel."
targetIsInThePast = "Wähle einen Zeitpunkt in der Zukunft, Marty."
targetIsTooFarInTheFuture = "Wir passen den Plan an, sobald wir mehr über die Zukunft wissen."
title = "Zielzeit"
@@ -519,22 +521,29 @@ vehicleLimit = "Fahrzeuglimit: {soc}"
[main.vehicleStatus]
charging = "Ladevorgang aktiv …"
-cheapEnergyCharging = "Lade günstige Energie: {price} (Grenze {limit})"
-cleanEnergyCharging = "Lade saubere Energie: {co2} (Grenze {limit})"
+cheapEnergyCharging = "Günstige Energie verfügbar."
+cheapEnergyNextStart = "Günstige Energie in {duration}."
+cheapEnergySet = "Preislimit gesetzt."
+cleanEnergyCharging = "Saubere Energie verfügbar."
+cleanEnergyNextStart = "Saubere Energie in {duration}."
+cleanEnergySet = "CO₂-Limit gesetzt."
climating = "Vorklimatisierung erkannt."
connected = "Verbunden."
disconnected = "Nicht verbunden."
+finished = "Abgeschlossen."
minCharge = "Mindestladung bis {soc}."
-pvDisable = "Zu wenig Überschuss. Pausiere in {remaining} …"
-pvEnable = "Überschuss verfügbar. Starte in {remaining} …"
-scale1p = "Reduziere auf einphasig in {remaining} …"
-scale3p = "Erhöhe auf dreiphasig in {remaining} …"
-targetChargeActive = "Ladeplan aktiv …"
-targetChargePlanned = "Ladeplan startet {time} Uhr."
+pvDisable = "Zu wenig Überschuss. Ladung wird gleich pausiert."
+pvEnable = "Überschuss verfügbar. Ladung wird gleich fortgesetzt."
+scale1p = "Reduziere gleich auf einphasiges Laden."
+scale3p = "Erhöhe gleich auf dreiphasiges Laden."
+targetChargeActive = "Ladeplan aktiv. Geplantes Ende in {duration}."
+targetChargePlanned = "Ladeplan startet in {duration}."
targetChargeWaitForVehicle = "Ladeplan bereit. Warte auf Fahrzeug …"
unknown = ""
-vehicleLimitReached = "Fahrzeuglimit {soc} erreicht."
+vehicleLimit = "Fahrzeuglimit."
+vehicleLimitReached = "Fahrzeuglimit erreicht."
waitForVehicle = "Ladebereit. Warte auf Fahrzeug …"
+welcome = "Kurze initiale Ladung zur Bestätigung der Verbindung."
[notifications]
dismissAll = "Meldungen entfernen"
@@ -542,6 +551,7 @@ logs = "Vollständiges Log ansehen"
modalTitle = "Meldungen"
[offline]
+configurationError = "Fehler beim Starten. Überprüfe deine Konfiguration und starte neu."
message = "Keine Verbindung zum Server."
reload = "Erneut laden?"
restart = "Neustart"
diff --git a/i18n/en.toml b/i18n/en.toml
index ad0a78108d..9ebaaed554 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -55,6 +55,7 @@ co2 = "Grid CO₂"
configured = "Configured"
currency = "Currency"
current = "Current"
+currentRange = "Current"
enabled = "Enabled"
energy = "Energy"
feedinPrice = "Feed-in price"
@@ -66,6 +67,7 @@ phaseCurrents = "Current L1..L3"
phasePowers = "Power L1..L3"
phaseVoltages = "Voltage L1..L3"
power = "Power"
+powerRange = "Power"
range = "Range"
soc = "SoC"
socLimit = "Limit"
@@ -170,6 +172,10 @@ title = "Network"
[config.options]
+[config.options.boolean]
+no = "no"
+yes = "yes"
+
[config.options.endianness]
big = "big-endian"
little = "little-endian"
@@ -395,8 +401,6 @@ selfConsumption = "Self-consumption"
[main.heatingStatus]
charging = "Heating…"
-cheapEnergyCharging = "Heating with cheap energy: {price} (limit {limit})"
-cleanEnergyCharging = "Heating with clean energy: {co2} (limit {limit})"
waitForVehicle = "Ready. Waiting for heater…"
[main.loadpoint]
@@ -450,6 +454,7 @@ minpv = "Min+Solar"
now = "Fast"
off = "Off"
pv = "Solar"
+smart = "Smart"
[main.provider]
login = "log in"
@@ -463,7 +468,7 @@ currentPlan = "Active plan"
descriptionEnergy = "Until when should {targetEnergy} be loaded into the vehicle?"
descriptionSoc = "When should the vehicle be charged to {targetSoc}?"
inactiveLabel = "Target time"
-notReachableInTime = "Goal not reachable in time. Estimated finish: {endTime}."
+notReachableInTime = "Goal will be reached {overrun} later."
onlyInPvMode = "Charging plan only works in solar mode."
planDuration = "Charging time"
planPeriodLabel = "Period"
@@ -474,7 +479,7 @@ priceLimit = "price limit of {price}"
remove = "Remove"
setTargetTime = "none"
targetIsAboveLimit = "The configured charging limit of {limit} will be ignored during this period."
-targetIsAboveVehicleLimit = "Increase vehicle limit ({limit}) to reach the charging goal."
+targetIsAboveVehicleLimit = "Vehicle limit is below charging goal."
targetIsInThePast = "Pick a time in the future, Marty."
targetIsTooFarInTheFuture = "We will adjust the plan as soon as we know more about the future."
title = "Target Time"
@@ -518,22 +523,29 @@ vehicleLimit = "Vehicle limit: {soc}"
[main.vehicleStatus]
charging = "Charging…"
-cheapEnergyCharging = "Charging cheap energy: {price} (limit {limit})"
-cleanEnergyCharging = "Charging clean energy: {co2} (limit {limit})"
+cheapEnergyCharging = "Cheap energy available."
+cheapEnergyNextStart = "Cheap energy in {duration}."
+cheapEnergySet = "Price limit set."
+cleanEnergyCharging = "Clean energy available."
+cleanEnergyNextStart = "Clean energy in {duration}."
+cleanEnergySet = "CO₂ limit set."
climating = "Pre-conditioning detected."
connected = "Connected."
disconnected = "Disconnected."
+finished = "Finished."
minCharge = "Minimum charging to {soc}."
-pvDisable = "Not enough surplus. Pausing in {remaining}…"
-pvEnable = "Surplus available. Starting in {remaining}…"
-scale1p = "Reducing to 1-phase charging in {remaining}…"
-scale3p = "Increasing to 3-phase charging in {remaining}…"
-targetChargeActive = "Charging plan active…"
-targetChargePlanned = "Charging plan starts at {time}."
+pvDisable = "Not enough surplus. Pausing soon."
+pvEnable = "Surplus available. Starting soon."
+scale1p = "Reducing to 1-phase charging soon."
+scale3p = "Increasing to 3-phase charging soon."
+targetChargeActive = "Charging plan active. Estimated finish in {duration}."
+targetChargePlanned = "Charging plan starts in {duration}."
targetChargeWaitForVehicle = "Charging plan ready. Waiting for vehicle…"
unknown = ""
-vehicleLimitReached = "Vehicle limit {soc} reached."
+vehicleLimit = "Vehicle limit."
+vehicleLimitReached = "Vehicle limit reached."
waitForVehicle = "Ready. Waiting for vehicle…"
+welcome = "Short initial charge to confirm connection."
[notifications]
dismissAll = "Dismiss all"
@@ -541,6 +553,7 @@ logs = "View full logs"
modalTitle = "Notifications"
[offline]
+configurationError = "Error during startup. Check your configuration and restart."
message = "Not connected to a server."
restart = "Restart"
restartNeeded = "Required to apply changes."
diff --git a/i18n/es.toml b/i18n/es.toml
index 4758a67f6a..14a2a24293 100644
--- a/i18n/es.toml
+++ b/i18n/es.toml
@@ -28,6 +28,20 @@ never = "cuando hay suficiente excedente"
titleAdd = "Añadir medidor de batería"
titleEdit = "Editar el medidor de batería"
+[config.circuits]
+description = "Se asegura que la suma de todos los puntos de carga conectados a un circuito no exceda la potencia configurada y los límites actuales. Los circuitos pueden ser anidados para construir una jerarquía"
+title = "Gestión de carga"
+
+[config.control]
+description = "Usualmente los valores predeterminados están bien. Solo cámbialos si sabes lo que estás haciendo."
+descriptionInterval = "El ciclo de actualización del circuito de control en segundos. Define con qué frecuencia evcc lee los datos del medidor, ajusta la potencia de carga y actualiza la UI. Los intervalos cortos ( 30s) pueden causar oscillaciones y comportamiento no deseado."
+descriptionMaxGridSupply = "Solo relevante para inversores híbridos que no son capaces de producir la fuerza completa de generación DC al hogar a través de AC. Este szenario puede llevar al uso no deseado de la red, ya que evcc asume que la potencia DC completa está disponible. Utilice un valor de al menos 50 W para prevenir esto."
+descriptionResidualPower = "Cambia el punto de funcionamiento del circuito de control. Si tienes una batería doméstica, se recomienda establecer un valor de 100 W. De esta manera, la batería tendrá una ligera prioridad sobre el uso de la red eléctrica"
+labelInterval = "Intervalo de actualización"
+labelMaxGridSupply = "Suministro máximo de red durante la carga"
+labelResidualPower = "Fuerza residual"
+title = "Controlar la conducta"
+
[config.deviceValue]
capacity = "Capacidad"
chargeStatus = "Estado"
diff --git a/i18n/fi.toml b/i18n/fi.toml
index bb22fbb39a..182084dfd7 100644
--- a/i18n/fi.toml
+++ b/i18n/fi.toml
@@ -1,20 +1,23 @@
[batterySettings]
batteryLevel = "Akun varaustaso"
capacity = "{energy} / {total}"
+control = "Akun hallinta"
+discharge = "Vältä purkamista nopeassa tilassa ja suunnitellussa latauksessa."
disclaimerHint = "Huomaa:"
disclaimerText = "Nämä asetukset vaikuttavat vain aurinkotilaan. Latauskäyttäytymistä säädetään vastaavasti."
-legendBottomName = "talo on etusijalla"
-legendBottomSubline = "ei käytetty lataamiseen"
+legendBottomName = "koti on etusijalla"
+legendBottomSubline = "ei käytetty lataukseen"
legendMiddleName = "ajoneuvo ensin"
legendMiddleSubline = "koti on toissijainen"
-legendTitle = "Kuinka aurinkoenergiaa pitäisi käyttää?"
+legendTitle = "Kuinka aurinkoenergiaa tulisi käyttää?"
legendTopAutostart = "alkaa automaattisesti"
legendTopName = "akkutuettu lataus"
legendTopSubline = "keskeytyksettä"
-modalTitle = "Akun asetukset"
+modalTitle = "Kodin akku"
[batterySettings.bufferStart]
-full = "kun melkein täynnä"
+above = "kun ylittää {soc}"
+full = "kun {soc}"
low = "jos jonkin verran täynnä"
medium = "kun puolillaan"
never = "ainoastaan kun riittävästi ylijäämää"
@@ -22,38 +25,199 @@ never = "ainoastaan kun riittävästi ylijäämää"
[config]
[config.battery]
+titleAdd = "Lisää akkumittari"
+titleEdit = "Muokkaa akkumittaria"
+
+[config.circuits]
+description = "Varmistaa, että kaikkien piiriin kytkettyjen kuormituspisteiden summa ei ylitä määritettyjä teho- ja virtarajoja. Piirejä voi rakentaa sisäkäin hierarkiaan."
+title = "Kuormanhallinta"
+
+[config.control]
+description = "Yleensä oletusarvot ovat sopivat. Muuta vain jos tiedät mitä olet tekemässä."
+descriptionInterval = "Ohjaussilmukan päivitysjakso sekunneissa. Määrittää, kuinka usein evcc lukee mittaritietoja, säätää lataustehoa ja päivittää käyttöliittymän. Lyhyet välit alle (30 s) voivat aiheuttaa oskillaatiota ja ei-toivotua toimintaa"
+descriptionMaxGridSupply = "Sopii vain hybridi-inverttereille, jotka eivät pysty tuottamaan täyttä tasavirtaa kotiin AC:n kautta. Tämä skenaario voi johtaa ei toivottuun verkon käyttöön, koska evcc olettaa koko tasavirran olevan käytettävissä. Käytä vähintään 50 W:n arvoa tämän estämiseksi."
+descriptionResidualPower = "Siirtää ohjaussilmukan toimintapistettä. Jos sinulla on kodissa akku on suositeltavaa asettaa arvo 100 W. Näin akku on hieman etusijalla verkkokäyttöön nähden."
+labelInterval = "Päivitystiheys"
+labelMaxGridSupply = "Maksimi teho verkosta latauksen aikana"
+labelResidualPower = "Jäännösteho"
+title = "Hallitse käyttäytymistä"
[config.deviceValue]
+broker = "Broker"
+bucket = "Bucket"
+capacity = "Kapasiteetti"
+chargeStatus = "Tila"
+chargeStatusA = "ei yhdistetty"
+chargeStatusB = "yhdistetty"
+chargeStatusC = "lataa"
+chargeStatusE = "ei virtaa"
+chargeStatusF = "virhe"
+chargedEnergy = "Ladattu"
+co2 = "Verkon CO₂"
+configured = "Määritetty"
+currency = "Valuutta"
+current = "Virta"
+enabled = "Käytössä"
+energy = "Energia"
+feedinPrice = "Verkkoon myynninhinta"
+gridPrice = "Verkosta ostonhinta"
+no = "ei"
+odometer = "Matkamittari"
+org = "Organisaatio"
+phaseCurrents = "Virta L1..L3"
+phasePowers = "Teho L1..L3"
+phaseVoltages = "Jännite L1..L3"
+power = "Teho"
+range = "Toimintasäde"
+soc = "SoC"
+socLimit = "Raja"
+temp = "Lämpötila"
+topic = "Aihe"
+url = "URL"
+yes = "kyllä"
+
+[config.eebus]
+description = "Konfiguraatio, jonka avulla evcc voi kommunikoida muiden EEBus-laitteiden kanssa."
+title = "EEBus"
[config.form]
example = "Esimerkiksi"
optional = "valinnainen"
[config.general]
+cancel = "Peruuta"
+docsLink = "Katso dokumentaatio"
+experimental = "Kokeellinen"
+off = "pois"
+on = "päällä"
+password = "Salasana"
+remove = "Poista"
+save = "Tallenna"
+telemetry = "Telemetria"
+title = "Otsikko"
[config.grid]
+title = "Verkkomittari"
+titleAdd = "Lisää verkkomittari"
+titleEdit = "Muokkaa verkkomittaria"
+
+[config.hems]
+description = "Yhdistä evcc toiseen kodin energianhallintajärjestelmään."
+title = "HEMS"
+
+[config.influx]
+description = "Kirjoittaa kulutustiedot ja muut mittaukset InfluxDB:hen. Käytä Grafanaa tai muita työkaluja tietojen visualisointiin."
+descriptionToken = "Tarkista InfluxDB-dokumentaatiosta, kuinka voit luoda sellaisen. https://docs.influxdata.com/influxdb/v2/admin/"
+labelBucket = "Bucket"
+labelDatabase = "Tietokanta"
+labelOrg = "Organisaatio"
+labelPassword = "Salasana"
+labelToken = "API Token"
+labelUrl = "URL"
+labelUser = "Käyttäjätunnus"
+title = "InfluxDB"
+v1Support = "Tarvitsetko tukea InfluxDB 1.x:lle?"
+v2Support = "Takaisin InfluxDB 2.x:ään"
[config.main]
+addLoadpoint = "Lisää latauspiste"
+addPvBattery = "Lisää aurinkovoimala tai akusto"
addVehicle = "Lisää ajoneuvo"
+configured = "asetettu"
edit = "muokkaa"
title = "Määritykset"
+unconfigured = "ei määritetty"
vehicles = "Minun ajoneuvot"
+yaml = "Määritetty tiedostossa evcc.yaml. Ei muokattavissa käyttöliittymästä."
+
+[config.messaging]
+description = "Vastaanota viestejä koskien lataustapahtumiasi."
+title = "Ilmoitukset"
[config.meter]
+cancel = "Peruuta"
+delete = "Poista"
+save = "Tallenna"
+template = "Valmistaja"
+titleChoice = "Mitä haluat lisätä?"
+validateSave = "Vahvista ja tallenna"
+
+[config.modbusproxy]
+description = "Salli useiden asiakkaiden käyttää yhtä Modbus-laitetta."
+title = "Modbus Proxy"
+
+[config.mqtt]
+authentication = "Todennus"
+description = "Yhdistä MQTT-välittäjään vaihtaaksesi tietoja muiden verkossasi olevien järjestelmien kanssa."
+descriptionClientId = "Viestien kirjoittaja. Jos tyhjää `evcc-[rand]` käytetään."
+descriptionTopic = "Jätä tyhjäksi, jos haluat poistaa julkaisut käytöstä."
+labelBroker = "Broker"
+labelCheckInsecure = "Salli itse-allekirjoitetut varmenteet"
+labelClientId = "Asiakas ID"
+labelInsecure = "Sertifikaatin todentaminen"
+labelPassword = "Salasana"
+labelTopic = "Aihe"
+labelUser = "Käyttäjätunnus"
+publishing = "Julkaise"
+title = "MQTT"
+
+[config.network]
+descriptionHost = "Ota mDNS käyttöön käyttämällä .local-liitettä. Olennaista mobiilisovelluksen ja joidenkin OCPP-laturien löytämisen kannalta."
+descriptionPort = "Portti verkkokäyttöliittymälle ja API:lle. Sinun on päivitettävä selaimesi URL-osoite, jos muutat tätä."
+descriptionSchema = "Vaikuttaa vain URL-osoitteiden luomiseen. HTTPS:n valitseminen ei ota salausta käyttöön."
+labelHost = "Isäntänimi"
+labelPort = "Portti"
+labelSchema = "Kaavio"
+title = "Verkko"
[config.options]
[config.options.endianness]
+big = "big-endian"
+little = "little-endian"
[config.options.schema]
+http = "HTTP (salaamaton)"
+https = "HTTPS (salattu)"
[config.pv]
+titleAdd = "Lisää aurinkovoimalan mittari"
+titleEdit = "Muokkaa aurinkovoimalan mittaria"
[config.section]
+general = "Yleinen"
+grid = "Verkko"
+integrations = "Integraatiot"
+loadpoints = "Latauspisteet"
+meter = "Aurinko & Akku"
+system = "Järjestelmä"
+vehicles = "Ajoneuvot"
+
+[config.sponsor]
+addToken = "Anna sponsoritunnus"
+changeToken = "Vaihda sponsoritunnus"
+description = "Sponsorointi auttaa meitä ylläpitämään projektia ja rakentamaan kestävästi uusia ja jännittäviä ominaisuuksia. Sponsorina saat käyttöösi kaikki latureiden toteutukset."
+descriptionToken = "Saat tunnuksen osoitteesta {url}. Tarjoamme myös kokeilutunnuksen testausta varten."
+error = "Sponsoritunnus ei kelpaa."
+labelToken = "Sponsoritunnus"
+title = "Sponsorointi"
[config.system]
+logs = "Logi"
+restart = "Käynnistä uudelleen"
+restartRequiredDescription = "Ole hyvä ja käynnistä uudelleen, jotta näet muutoksen"
+restartRequiredMessage = "Määritykset muutettu."
+restartingDescription = "Ole hyvä ja odota..."
+restartingMessage = "Käynnistetään uudelleen evcc."
+
+[config.tariffs]
+description = "Määrittele energiatariffisi, jotta latausistuntojesi kustannukset lasketaan."
+title = "Tariffit"
[config.title]
+description = "Näytetään pääikkunassa ja selaimen välilehdessä."
+label = "Otsikko"
+title = "Muokkaa otsikkoa"
[config.validation]
failed = "epäonnistui"
@@ -65,7 +229,7 @@ validate = "vahvista"
[config.vehicle]
cancel = "Peruuta"
-delete = "Poista ajoneuvo"
+delete = "Poista"
generic = "Muut integraatiot"
offline = "Yleinen ajoneuvo"
online = "Ajoneuvot, joissa API-käyttöliittymä"
@@ -120,13 +284,18 @@ total = "kaikki"
[footer.sponsor]
becomeSponsor = "Ryhdy sponsoriksi"
+becomeSponsorExtended = "Tue meitä suoraan saadaksesi tarroja."
confetti = "Valmiina konfetteihin?"
confettiPromise = "Saat tarroja ja myös digitaali konfetteja"
sticker = "… vai evcc tarroja?"
-supportUs = "Missiomme on tehdä aurinkoenergiasta normi. Auta evcc:tä maksamalla sen verran minkä arvoinen se on sinulle."
+supportUs = "Missiomme on tehdä aurinkoenergialla lataamisesta normi. Auta evcc:tä maksamalla sen verran minkä arvoinen se on sinulle."
thanks = "Kiitos, {sponsor}! Panoksesi auttaa kehittämään evcc:tä myös jatkossa."
titleNoSponsor = "Tue meitä"
titleSponsor = "Olet kannattaja"
+titleTrial = "Kokeilu tila"
+titleVictron = "Sponsored by Victron Energy"
+trial = "Olet kokeilutilassa, voit käyttää kaikkia ominaisuuksia. Ole hyvä ja harkitse projektin tukemista."
+victron = "Käytät evcc:tä Victron Energy -laitteistossa ja sinulla on pääsy kaikkiin ominaisuuksiin."
[footer.telemetry]
optIn = "Haluan jakaa tietojani"
@@ -152,6 +321,8 @@ blog = "Blogi"
docs = "Dokumentaatio"
github = "GitHub"
login = "Ajoneuvon kirjautumiset"
+logout = "Kirjaudu ulos"
+nativeSettings = "Vaihda palvelin"
needHelp = "Tarvitsetko apua?"
sessions = "Lataustapahtumat"
settings = "Asetukset"
@@ -161,39 +332,67 @@ discussionsButton = "GitHub keskustelut"
documentationButton = "Dokumentaatio"
issueButton = "Ilmoita virheestä"
issueDescription = "Löysitkö outoa tai vääränlaista käytöstä?"
+logsButton = "Näytä logit"
+logsDescription = "Tarkasta logi virheiden varalta"
modalTitle = "Tarvitsetko apua?"
primaryActions = "Jokin ei toimi niin kuin sen pitäisi toimia? Nämä ovat hyviä paikkoja saada apua."
-restartButton = "Uudelleen käynnistä"
-restartDescription = "Yrititkö sammuttaa ja uudelleen käynnistää sen?"
+restartButton = "Käynnistä uudelleen"
+restartDescription = "Yrititkö sammuttaa ja käynnistää uudelleen?"
secondaryActions = "Etkö vieläkään pysty ratkaisemaan ongelmaasi? Tässä on joitain raskaampia vaihtoehtoja."
[help.restart]
cancel = "Peruuta"
-confirm = "Kyllä, uudelleen käynnistä!"
+confirm = "Kyllä, käynnistä uudelleen!"
description = "Normaaleissa olosuhteissa uudelleenkäynnistyksen ei pitäisi olla välttämätöntä. Harkitse virheilmoituksen tekemistä, jos sinun on käynnistettävä evcc uudelleen säännöllisesti."
disclaimer = "Huomaa: evcc lopettaa toimintansa ja käynnistää palvelun uudelleen"
-modalTitle = "Oletko varma että haluat uudelleen käynnistää?"
+modalTitle = "Oletko varma että haluat käynnistää uudelleen?"
[log]
+areaLabel = "Rajaa alueella"
+areas = "Kaikki alueet"
+download = "Lataa täydellinen logi"
+levelLabel = "Rajaa logi tasolla"
+noResults = "Ei vastaavia lokimerkintöjä."
+search = "Etsi"
+showAll = "Näytä kaikki"
+title = "Logi"
+update = "Päivitä automaattisesti"
[loginModal]
+cancel = "Peruuta"
+error = "Kirjautuminen epäonnistui: "
+iframeHint = "Avaa evcc uudessa välilehdessä."
+iframeIssue = "Salasanasi on oikea, mutta selaimesi näyttää pudonneen todennusevästeen. Näin voi käydä, jos suoritat evcc:n iframe-kehyksessä HTTP:n kautta."
+invalid = "Salasana ei kelpaa"
+login = "Kirjaudu"
+password = "Salasana"
+reset = "Määritä salasana uudelleen?"
+title = "Vahvistus"
[main]
vehicles = "Pysäköinti"
[main.chargingPlan]
+active = "Aktiivinen"
arrivalTab = "Saapuminen"
+day = "Päivä"
departureTab = "Lähtö"
+goal = "Lataus tavoite"
modalTitle = "Lataussuunnitelma"
none = "ei mitään"
+remove = "Poista"
+time = "Aika"
title = "Suunnitelma"
titleMinSoc = "Minimi lataus"
titleTargetCharge = "Lähtö"
+unsavedChanges = "Tallentamattomia muutoksia. Asetetaanko nyt?"
+update = "Aseta"
[main.energyflow]
battery = "Akku"
batteryCharge = "Akunlataus"
batteryDischarge = "Akku purkautuu"
+batteryHold = "Akku (lukittu)"
batteryTooltip = "{energy} / {total} ({soc})"
gridImport = "Kulutus verkosta"
homePower = "Kulutus"
@@ -204,6 +403,10 @@ pvProduction = "Tuotanto"
selfConsumption = "Tuotannon kulutus"
[main.heatingStatus]
+charging = "Lämmitys..."
+cheapEnergyCharging = "Lataa halvalla energialla: {price} (raja {limit})"
+cleanEnergyCharging = "Lämmitä puhtaalla energialla: {co2} (raja {limit})"
+waitForVehicle = "Valmiina. Odottaa lämmitintä..."
[main.loadpoint]
avgPrice = "⌀ Hinta"
@@ -213,16 +416,24 @@ duration = "Kesto"
fallbackName = "Latauspiste"
power = "Teho"
price = "Σ Hinta"
+remaining = "Jäljellä"
+remoteDisabledHard = "{source}: sammui"
+remoteDisabledSoft = "{source}: sammutti adaptiivisen aurinkolatauksen"
solar = "Aurinkoenergia"
[main.loadpointSettings]
currents = "Latausvirta"
default = "oletus"
disclaimerHint = "Huomautus:"
+onlyForSocBasedCharging = "Nämä vaihtoehdot ovat käytettävissä vain ajoneuvoille, joiden lataustaso on tiedossa."
+smartCostCheap = "Edullinen verkosta lataus"
+smartCostClean = "Hiilineutraali verkosta lataaminen"
title = "Asetukset {0}"
vehicle = "Ajoneuvo"
[main.loadpointSettings.limitSoc]
+description = "Latausraja mitä käytetään kun ajoneuvo yhdistetty."
+label = "Oletus raja"
[main.loadpointSettings.maxCurrent]
label = "Maksimivirta"
@@ -236,6 +447,7 @@ label = "Min. lataustaso %"
[main.loadpointSettings.phasesConfigured]
label = "Vaihetila"
+no1p3pSupport = "Kuinka laturisi on kytketty?"
phases_0 = "automaattinen"
phases_1 = "1-vaihe"
phases_1_hint = "({min} - {max})"
@@ -247,6 +459,7 @@ minpv = "Minimi+PV"
now = "Nopea"
off = "Seis"
pv = "PV"
+smart = "Älykäs"
[main.provider]
login = "kirjaudu sisään"
@@ -256,17 +469,23 @@ logout = "kirjaudu ulos"
activate = "Aktivoi"
co2Limit = "Raja CO₂ / {co2}"
costLimitIgnore = "Määritetty {limit} ohitetaan tänä aikana."
+currentPlan = "Aktiivinen suunnitelma"
descriptionEnergy = "Mihin mennessä {targetEnergy} tulisi ladata autoon?"
descriptionSoc = "Milloin ajoneuvon tulee olla ladattu {targetSoc}?"
inactiveLabel = "Tavoiteaika"
modalTitle = "Aseta tavoiteaika"
+notReachableInTime = "Tavoite saavutetaan {overrun} myöhemmin."
onlyInPvMode = "Lataussuunnitelma toimii ainoastaan aurinkoenergiatilassa."
planDuration = "Latausaika"
planPeriodLabel = "Jakso"
planPeriodValue = "{start} - {end}"
planUnknown = "ei vielä tiedossa"
+preview = "Esikatsele suunnitelmaa"
priceLimit = "{price} hintaraja"
+remove = "Poista"
setTargetTime = "ei mitään"
+targetIsAboveLimit = "Asetettu latausraja {limit} ohitetaan tänä aikana."
+targetIsAboveVehicleLimit = "Ajoneuvon latausraja on alle lataustavoitteen."
targetIsInThePast = "Valitse aika tulevaisuudessa"
targetIsTooFarInTheFuture = "Muokkaamme suunnitelmaa heti kun tiedämme enemmän tulevaisuudesta."
title = "Tavoiteaika"
@@ -294,6 +513,7 @@ detectionActive = "Tunnistetaan ajoneuvoa..."
fallbackName = "Ajoneuvo"
moreActions = "Lisää toimintoja"
none = "Ei ajoneuvoa"
+notReachable = "Ajoneuvo ei ollut saatavilla. Kokeile käynnistää evcc uudelleen."
targetSoc = "Raja"
temp = "Lämpötila."
tempLimit = "Lämpötila raja-arvo"
@@ -309,31 +529,54 @@ vehicleLimit = "Ajoneuvon raja {soc}"
[main.vehicleStatus]
charging = "Lataa…"
-cheapEnergyCharging = "Lataa edullista energiaa: {price} (limit {limit})"
-cleanEnergyCharging = "Lataa puhdasta energiaa: {co2} (limit {limit})"
+cheapEnergyCharging = "Edullista energiaa saatavilla"
+cheapEnergyNextStart = "Energia on edullista {duration} ajan."
+cheapEnergySet = "Hintaraja asetettu."
+cleanEnergyCharging = "Puhdasta energiaa saatavilla."
+cleanEnergyNextStart = "Puhdasta energiaa {duration} kuluessa."
+cleanEnergySet = "CO₂-raja asetettu."
climating = "Esilämmitys/-viilennys havaittu."
connected = "Yhdistetty."
disconnected = "Irroitettu."
+finished = "Valmis."
minCharge = "Ladataan minimissään {soc}."
-pvDisable = "Ei tarpeeksi ylijäämää. Tauko {remaining}…"
-pvEnable = "Ylijäämää saatavilla. Aloitus {remaining}…"
-scale1p = "Siirrytään 1-vaihe lataukseen {remaining}…"
-scale3p = "Siirrytään 3-vaihe lataukseen {remaining}…"
-targetChargeActive = "Lataaminen käynnissä…"
-targetChargePlanned = "Lataaminen alkaa {time}."
-targetChargeWaitForVehicle = "Tavoite lataus valmis. Odotetaan ajoneuvoa…"
-vehicleLimitReached = "Ajoneuvon raja {soc} saavutettu."
+pvDisable = "Ei tarpeeksi ylijäämää. Lataus keskeytetään pian."
+pvEnable = "Ylijäämää saatavilla. Lataus alkaa pian."
+scale1p = "Siirrytään pian 1-vaihe lataukseen."
+scale3p = "Siirrytään pian 3-vaihe lataukseen"
+targetChargeActive = "Lataussuunnitelma aktiivinen. Arvioitu valmistuminen {duration} kuluttua."
+targetChargePlanned = "Lataussuunnitelma alkaa {duration} kuluttua."
+targetChargeWaitForVehicle = "Lataussuunnitelma valmis. Odotetaan ajoneuvoa…"
+unknown = ""
+vehicleLimit = "Ajoneuvon latausraja"
+vehicleLimitReached = "Ajoneuvon raja saavutettu."
waitForVehicle = "Valmiina. Odotetaan ajoneuvoa…"
[notifications]
dismissAll = "Hylkää kaikki"
+logs = "Näytä täydellinen logi"
modalTitle = "Ilmoitukset"
[offline]
+configurationError = "Virhe käynnistyksessä. Tarkista määritykset ja käynnistä uudelleen."
message = "Ei yhteyttä serveriin"
reload = "Lataa uudelleen?"
+restart = "Käynnistä uudelleen"
+restartNeeded = "Tarvitaan muutosten käyttöönottamiseksi."
+restarting = "Palvelin tulee takaisin hetken kuluttua."
[passwordModal]
+description = "Aseta salasana suojellaksesi määritys asetuksia. Pääikkunan käyttö on silti mahdollista ilman kirjautumista."
+empty = "Salasana ei tulisi olla tyhjä"
+error = "Virhe: "
+labelCurrent = "Nykyinen salasana"
+labelNew = "Uusi salasana"
+labelRepeat = "Toista salasana"
+newPassword = "Aseta salasana"
+noMatch = "Salasanat eivät täsmää"
+titleNew = "Aseta Pääkäyttäjän salasana"
+titleUpdate = "Päivitä Pääkäyttäjän salasana"
+updatePassword = "Päivitä salasana"
[session]
cancel = "Peruuta"
@@ -370,6 +613,8 @@ vehicle = "Ajoneuvo"
[sessions.csv]
chargedenergy = "Energia (kWh)"
+chargeduration = "Kesto"
+co2perkwh = "CO₂/kWh"
created = "Luotu"
finished = "Päättynyt"
identifier = "Tunnus"
@@ -377,6 +622,9 @@ loadpoint = "Latauspiste"
meterstart = "Mittarin alku (kWh)"
meterstop = "Mittarin loppu (kWh)"
odometer = "Ajokilometrit (km)"
+price = "Hinta"
+priceperkwh = "Hinta/kWh"
+solarpercentage = "Aurinkoenergia (%)"
vehicle = "Ajoneuvo"
[sessions.filter]
@@ -385,9 +633,12 @@ allVehicles = "kaikki ajoneuvot"
filter = "Suodatin"
[settings]
-title = "Asetukset"
+title = "Käyttöliittymä"
[settings.fullscreen]
+enter = "Mene kokonäyttötilaan"
+exit = "Poistu kokonäyttötilasta"
+label = "Kokonäyttö"
[settings.gridDetails]
co2 = "CO₂"
@@ -404,7 +655,7 @@ auto = "Automaattinen"
label = "Kieli"
[settings.sponsorToken]
-expires = "Sponsorointi tunnuksesi vanhenee {inXDays}. {getNewToken} ja päivitä määritystiedostosi"
+expires = "Sponsorointitunnuksesi vanhenee {inXDays}. {getNewToken} ja päivitä se tänne"
getNew = "Ota uusi"
hint = "Huomaa: automatisoimme tämän tulevaisuudessa."
@@ -425,6 +676,10 @@ mi = "mailit"
[smartCost]
activeHours = "{charging} / {total}"
activeHoursLabel = "Aktiiviset tunnit"
+applyToAll = "Käytä kaikkialla?"
+batteryDescription = "Lataa kodin akun sähköverkosta."
+cheapTitle = "Halpa verkkolataus"
+cleanTitle = "Viheränenergian lataminen verkosta"
co2Label = "CO₂ päästö"
co2Limit = "CO₂ raja"
loadpointDescription = "Mahdollistaa väliaikaisesti latauksen nopeasti PV tilassa."
@@ -432,6 +687,7 @@ modalTitle = "Älykäs lataus verkosta"
none = "ei mitään"
priceLabel = "Energian hinta"
priceLimit = "Hintaraja"
+saved = "Tallenettu"
[startupError]
configFile = "Käytetty määritystiedostoa:"
diff --git a/i18n/fr.toml b/i18n/fr.toml
index 1932081860..e66186ebf4 100644
--- a/i18n/fr.toml
+++ b/i18n/fr.toml
@@ -468,6 +468,7 @@ minpv = "Min+Sol"
now = "Rapide"
off = "Arrêté"
pv = "Solaire"
+smart = "Intelligent"
[main.provider]
login = "connexion"
@@ -482,7 +483,7 @@ descriptionEnergy = "Jusqu'à quand le véhicule doit-il être chargé à {targe
descriptionSoc = "Quand le véhicule doit-il être chargé à {targetSoc}?"
inactiveLabel = "Temps visé"
modalTitle = "Définir l'heure cible"
-notReachableInTime = "Objectif pas atteignable dans le temps défini. Heure de fin estimée : {endTime}."
+notReachableInTime = "L’objectif sera atteint avec un retard de {overrun}."
onlyInPvMode = "Le plan de recharge ne fonctionne qu’en mode solaire."
planDuration = "Durée de charge"
planPeriodLabel = "Période"
@@ -493,7 +494,7 @@ priceLimit = "limite de prix : {price}"
remove = "Ôter"
setTargetTime = "aucun"
targetIsAboveLimit = "La limite de charge configurée de {limit} sera ignorée durant cette période."
-targetIsAboveVehicleLimit = "Augmenter la limite de véhicule ({limit]) pour atteindre l'objectif de recharge."
+targetIsAboveVehicleLimit = "La limite du véhicule est en dessous de l'objectif de recharge."
targetIsInThePast = "Choisis une période dans le futur, Marty."
targetIsTooFarInTheFuture = "Nous ajusterons le plan dès qu'on en saura plus sur le futur."
title = "Temps visé"
@@ -537,21 +538,27 @@ vehicleLimit = "Limite du véhicule : {soc}"
[main.vehicleStatus]
charging = "En charge..."
-cheapEnergyCharging = "En charge avec énergie bon marché : {price} (limit < {limit})"
-cleanEnergyCharging = "En charge avec énergie propre : {co2} (limit < {limit})"
+cheapEnergyCharging = "Énergie bon marché disponible."
+cheapEnergyNextStart = "Énergie bon marché dans {duration}."
+cheapEnergySet = "Limite de prix définie."
+cleanEnergyCharging = "Énergie propre disponible."
+cleanEnergyNextStart = "Énergie propre dans {duration}."
+cleanEnergySet = "Limite CO₂ définie."
climating = "Pré-conditionnement détecté."
connected = "Connecté."
disconnected = "Déconnecté."
+finished = "Terminée."
minCharge = "Charge minimale jusqu'à {soc}."
-pvDisable = "Pas assez de surplus. Pause dans {remaining}…"
-pvEnable = "Surplus disponible. Démarre dans {remaining}…"
-scale1p = "Réduction en monophasé dans {remaining}..."
-scale3p = "Augmentation en triphasé dans {remaining}…"
-targetChargeActive = "Plan de charge actif..."
-targetChargePlanned = "Le plan de charge démarre à {time}."
+pvDisable = "Pas assez de surplus. Pause imminente."
+pvEnable = "Surplus disponible. Démarrage imminent."
+scale1p = "Réduction en monophasé imminente."
+scale3p = "Augmentation en triphasé imminente."
+targetChargeActive = "Plan de charge actif. Fin estimée dans {duration}."
+targetChargePlanned = "Le plan de charge démarre dans {duration}."
targetChargeWaitForVehicle = "Plan de charge prêt. Attente du véhicule..."
unknown = "-"
-vehicleLimitReached = "Limite de véhicule {soc} atteinte."
+vehicleLimit = "Limit du véhicule."
+vehicleLimitReached = "Limite du véhicule atteinte."
waitForVehicle = "Prêt à charger. Attente du véhicule…"
[notifications]
@@ -560,6 +567,7 @@ logs = "Voir les journaux complets"
modalTitle = "Notifications"
[offline]
+configurationError = "Erreur lors du démarrage. Vérifiez votre configuration et redémarrez."
message = "Pas de connexion au serveur."
reload = "Recharger?"
restart = "Redémarrer"
diff --git a/i18n/hr.toml b/i18n/hr.toml
index 3d2d4c3407..a6401e60a0 100644
--- a/i18n/hr.toml
+++ b/i18n/hr.toml
@@ -16,7 +16,8 @@ legendTopSubline = "bez prekida"
modalTitle = "Postavke baterije"
[batterySettings.bufferStart]
-full = "kad je skoro pun"
+above = "kada je iznad {soc}"
+full = "kad na {soc}"
low = "kad je donekle pun"
medium = "kad je napola pun"
never = "samo s dovoljno viška"
@@ -24,8 +25,41 @@ never = "samo s dovoljno viška"
[config]
[config.battery]
+titleAdd = "Dodaj mjerač baterije"
+titleEdit = "Uredi mjerač baterije"
+
+[config.circuits]
+title = "Upravljanje opterećenjem"
+
+[config.control]
+labelInterval = "Interval ažuriranja"
+labelMaxGridSupply = "Maksimalna opskrba mreže tijekom punjenja"
+labelResidualPower = "Preostala snaga"
+title = "Kontrolno ponašanje"
[config.deviceValue]
+broker = "Broker"
+bucket = "Bucket"
+capacity = "Kapacitet"
+chargeStatus = "Stanje"
+chargeStatusA = "nije povezano"
+chargeStatusB = "povezano"
+chargeStatusC = "napajanje"
+chargeStatusE = "bez snage"
+chargeStatusF = "greška"
+chargedEnergy = "Napojeno"
+co2 = "Mreža CO₂"
+configured = "Konfigurirano"
+currency = "Valuta"
+current = "Tok Struje"
+enabled = "Omogućeno"
+energy = "Energija"
+feedinPrice = "Cijena za nabavu"
+gridPrice = "Cijena mreže"
+no = "ne"
+odometer = "Odometar"
+org = "Organizacija"
+power = "Snaga"
[config.form]
example = "Primjer"
@@ -225,7 +259,7 @@ charged = "Napojeno"
co2 = "⌀ CO₂"
duration = "Trajanje"
fallbackName = "Mjesto napajanja"
-power = "Energija"
+power = "Snaga"
price = "Σ cijena"
remaining = "Preostalo"
remoteDisabledHard = "{source}: isključeno"
diff --git a/i18n/hu.toml b/i18n/hu.toml
index 3cad477a9d..b997d41480 100644
--- a/i18n/hu.toml
+++ b/i18n/hu.toml
@@ -27,19 +27,27 @@ titleAdd = "Energiatároló Mérő Hozzáadása"
titleEdit = "Energiatároló Mérő Szerkesztése"
[config.circuits]
+description = "Gondoskodik arról, hogy az áramkörhöz csatlakoztatott összes terhelési pont összege ne haladja meg a beállított teljesítmény- és áramkorlátokat. Az áramkörök egymásba ágyazhatók a hierarchia felépítéséhez."
title = "Terhelés Menedzsment"
[config.control]
description = "Általában az alapértelmezett értékek működőképesek. Csak akkor változtasd meg őket, ha tudod, hogy mit csinálsz."
+descriptionInterval = "A vezérlőkör frissítési ciklusa másodpercekben. Meghatározza, hogy az evcc milyen gyakran olvassa ki a mérőadatokat, állítsa be a töltési teljesítményt és frissítse a felhasználói felületet. A rövid időközök (<30 s) oszllációt és nem kívánt viselkedést okozhatnak."
+descriptionMaxGridSupply = "Csak azokra a hibrid inverterekre vonatkozik, amelyek nem képesek váltakozó áramon keresztül a teljes egyenáramú áramot az otthonba kiadni. Ez a forgatókönyv nem kívánt hálózathasználathoz vezethet, mivel az evcc feltételezi, hogy a teljes egyenáram rendelkezésre áll. Ennek elkerülésére használjon legalább 50 W értéket."
+descriptionResidualPower = "Eltolja a vezérlőkör működési pontját. Ha otthoni akkumulátorral rendelkezik, javasoljuk, hogy 100 W-os értéket állítson be. Így az akkumulátor kismértékben élvez prioritást a hálózati használattal szemben."
labelInterval = "Frissítési intervallum"
labelMaxGridSupply = "Max. hálózati ellátás töltés közben"
+title = "Vezérlési mód"
[config.deviceValue]
+broker = "Bróker"
+bucket = "Vödör"
capacity = "Kapacitás"
chargeStatus = "Státusz"
chargeStatusA = "nincs csatlakoztatva"
chargeStatusB = "csatlakoztatva"
chargeStatusC = "töltés"
+chargeStatusE = "nincs áram"
chargeStatusF = "hiba"
chargedEnergy = "Töltve"
co2 = "Hálózat CO₂"
@@ -48,6 +56,8 @@ currency = "Valuta"
current = "Jelenlegi"
enabled = "Engedélyezve"
energy = "Energia"
+feedinPrice = "Kötelező átvételi ár"
+gridPrice = "Villamosenergia ára"
no = "nem"
odometer = "Óraállás"
org = "Szervezet"
@@ -64,6 +74,7 @@ url = "URL"
yes = "igen"
[config.eebus]
+description = "Konfiguráció ami engedélyezi az evcc-nek, hogy kommunikáljon más EEBus eszközökkel."
title = "EEBus"
[config.form]
@@ -72,6 +83,7 @@ optional = "opcionális"
[config.general]
cancel = "Mégse"
+docsLink = "Dokumentáció megtekintése"
experimental = "Kísérleti"
off = "ki"
on = "be"
@@ -79,15 +91,21 @@ password = "Jelszó"
remove = "Eltávolítás"
save = "Mentés"
telemetry = "Telemetria"
+title = "Cím"
[config.grid]
+title = "Hálózati mérő"
titleAdd = "Hálózati Mérő Hozzáadása"
titleEdit = "Hálózati Mérő Szerkesztése"
[config.hems]
description = "Az evcc csatlakoztatása más otthoni energia menedzsment rendszerhez."
+title = "HEMS"
[config.influx]
+description = "Lementi a töltési adatokat és egyéb metrikákat az InfluxDB-be. Használd a Grafana-t vagy egyéb eszközöket az adatok vizualizálásához."
+descriptionToken = "Ellenőrizd az InfluxDB dokumentációját, ha szeretnél létrehozni egyet. https://docs.influxdata.com/influxdb/v2/admin/"
+labelBucket = "Vödör (Bucket)"
labelDatabase = "Adatbázis"
labelOrg = "Szervezet"
labelPassword = "Jelszó"
@@ -95,6 +113,8 @@ labelToken = "API Token"
labelUrl = "URL"
labelUser = "Felhasználónév"
title = "InfluxDB"
+v1Support = "Szükséged van támogatásra az InfluxDB 1.x verzióhoz?"
+v2Support = "Vissza az InfluxDB 2.x verzióhoz"
[config.main]
addLoadpoint = "Töltőpont hozzáadása"
@@ -108,6 +128,7 @@ vehicles = "Járműveim"
yaml = "Az evcc.yaml fájlban konfigurálva. Nem szerkeszthető az UI-ról."
[config.messaging]
+description = "Értesítési üzenetek beállítása a töltési folyamatokról."
title = "Értesítések"
[config.meter]
@@ -119,20 +140,29 @@ titleChoice = "Mit szeretnél hozzáadni?"
validateSave = "Ellenőrzés & mentés"
[config.modbusproxy]
+description = "Több kliens csatlakozásának engedélyezése egy Modbus eszközhöz."
title = "Modbus Proxy"
[config.mqtt]
authentication = "Hitelesítés"
+description = "Csatlakozás egy MQTT brókerhez az adatok cseréjéhez egy másik rendszerrel a hálózaton."
+descriptionClientId = "Az üzenetek szerzője. Ha üres, akkor az `evcc-[rand]` lesz használva."
+descriptionTopic = "Hagyd üresen a publikálás letiltásához."
+labelBroker = "Bróker"
labelCheckInsecure = "Önaláírt tanúsítványok engedélyezése"
labelClientId = "Kliens ID"
labelInsecure = "Tanúsítvány hitelesítés"
labelPassword = "Jelszó"
labelTopic = "Téma"
labelUser = "Felhasználónév"
+publishing = "Közzététel"
title = "MQTT"
[config.network]
+descriptionHost = "Használd a .local utótagot az mDNS engedélyezéséhez. A mobilalkalmazás és egyes OCPP-töltők felfedezése szempontjából releváns."
descriptionPort = "Port a webes felülethez és az API-hoz. Frissítened kell a böngésződ URL-jét, ha ezt megváltoztatod."
+descriptionSchema = "Csak az URL-ek létrehozásának módját érinti. A HTTPS kiválasztása nem engedélyezi a titkosítást."
+labelHost = "Állomásnév"
labelPort = "Port"
labelSchema = "Séma"
title = "Hálózat"
@@ -153,7 +183,10 @@ titleEdit = "Napelemes Mérő Szerkesztése"
[config.section]
general = "Általános"
+grid = "Hálózat"
integrations = "Integrációk"
+loadpoints = "Töltőpontok"
+meter = "Napelem és Akkumulátor"
system = "Rendszer"
vehicles = "Járművek"
@@ -161,8 +194,10 @@ vehicles = "Járművek"
addToken = "Szponzor token beírása"
changeToken = "Szponzor token megváltoztatása"
description = "A szponzorációs modell segít a projekt fenntartásában és az új izgalmas funkciók bevezetésében. Szponzorként teljes hozzáférést kapsz az összes töltőberendezés implementációjához."
+descriptionToken = "A tokent innen kapja: {url}. A teszteléshez próba tokent is kínálunk."
error = "A szponzor token nem érvényes."
labelToken = "Szponzor token"
+title = "Szponzoráció"
[config.system]
logs = "Naplók"
@@ -172,6 +207,10 @@ restartRequiredMessage = "A Konfiguráció megváltozott."
restartingDescription = "Kérlek várj…"
restartingMessage = "evcc újraindítása."
+[config.tariffs]
+description = "Határozza meg energiatarifáját a töltési folyamatok költségeinek kiszámításához."
+title = "Tarifák"
+
[config.title]
description = "Ez jelenik meg a főképernyőn és a böngésző címsorában."
label = "Megnevezés"
@@ -276,7 +315,7 @@ login = "Jármű Bejelentkezések"
logout = "Kijelentkezés"
nativeSettings = "Szerver Váltás"
needHelp = "Szükséged van segítségre?"
-sessions = "Töltési Munkamenetek"
+sessions = "Töltési Folyamatok"
[help]
discussionsButton = "GitHub Közösségi oldalát"
@@ -410,6 +449,7 @@ minpv = "Min+Szolár"
now = "Gyors"
off = "Ki"
pv = "Szolár"
+smart = "Okos"
[main.provider]
login = "bejelentkezés"
@@ -423,7 +463,7 @@ currentPlan = "Aktív terv"
descriptionEnergy = "Meddig kell a {targetEnergy}-t tölteni a járműbe?"
descriptionSoc = "Mikor legyen a jármű feltöltve {targetSoc}-ra?"
inactiveLabel = "Tervezett idő"
-notReachableInTime = "A tervezet nem teljesíthető a megadott időben. Várható befejezés: {endTime}."
+notReachableInTime = "A tervezet el lesz érve {overrun}."
onlyInPvMode = "A töltési idő csak napelemes üzemmódban működik."
planDuration = "Töltési idő"
planPeriodLabel = "Periódus"
@@ -434,7 +474,7 @@ priceLimit = "ár limit: {price}"
remove = "Eltávolítás"
setTargetTime = "nincs"
targetIsAboveLimit = "A konfigurált töltési limit, ami {limit} figyelmen kívül lesz hagyva ebben a periódusban."
-targetIsAboveVehicleLimit = "Növelje a jármű limitet ({limit}) hogy elérje a töltési célt."
+targetIsAboveVehicleLimit = "A Jármű limitje a töltési cél alatt van."
targetIsInThePast = "Válassz egy időpontot a jövőben, Marty."
targetIsTooFarInTheFuture = "Hozzáigazítjuk a tervet, amint többet tudunk a jövőről."
title = "Cél Idő"
@@ -478,22 +518,29 @@ vehicleLimit = "Jármű limit: {soc}"
[main.vehicleStatus]
charging = "Töltés…"
-cheapEnergyCharging = "Olcsó energiával töltés: {price} (limit {limit})"
-cleanEnergyCharging = "Tiszta energiával töltés: {co2} (limit {limit})"
+cheapEnergyCharging = "Olcsó energia elérhető."
+cheapEnergyNextStart = "Olcsó energia {duration}."
+cheapEnergySet = "Ár limit beállítva."
+cleanEnergyCharging = "Tiszta energia elérhető."
+cleanEnergyNextStart = "Olcsó energia {duration}."
+cleanEnergySet = "CO₂ limit beállítva."
climating = "Elő-kondícionálás érzékelve."
connected = "Csatlakoztatva."
disconnected = "Lecsatlakoztatva."
+finished = "Befejezve."
minCharge = "Minimális töltés {soc}-ig."
-pvDisable = "Nincs elég többlet. Szüneteltetés ekkor: {remaining}…"
-pvEnable = "Többlet elérhető. Indítás ekkor: {remaining}…"
-scale1p = "1 Fázisú töltésre váltás ekkor: {remaining}…"
-scale3p = "3 Fázisú töltésre váltás ekkor: {remaining}…"
-targetChargeActive = "Töltési terv aktív…"
-targetChargePlanned = "A töltési tervezet ekkor keződik: {time}."
+pvDisable = "Nincs elég többlet. Szüneteltetés hamarosan."
+pvEnable = "Többlet elérhető. Indítás hamarosan."
+scale1p = "1 Fázisú töltésre váltás hamarosan."
+scale3p = "3 Fázisú töltésre váltás hamarosan."
+targetChargeActive = "Töltési terv aktív. Becsült befejezés {duration}."
+targetChargePlanned = "A töltési tervezet ekkor keződik: {duration}."
targetChargeWaitForVehicle = "Töltési terv üzemkész. Várakozás járműre…"
unknown = ""
-vehicleLimitReached = "Jármű limit {soc} elérve."
+vehicleLimit = "Jármű limit."
+vehicleLimitReached = "Jármű limit elérve."
waitForVehicle = "Üzemkész. Járműre várakozás…"
+welcome = "Rövid kezdeti töltés a csatlakozás megerősítéséhez."
[notifications]
dismissAll = "Összeset figyelmen kívül hagyja"
@@ -501,9 +548,12 @@ logs = "Teljes napló megtekintése"
modalTitle = "Értesítések"
[offline]
+configurationError = "Hiba az indítás során. Ellenőrizd a konfigurációd és indítsd újra."
message = "Nem csatlakozik a szerverhez."
reload = "Újratöltés?"
restart = "Újraindítás"
+restartNeeded = "A módosítások végrehajtásához szükséges."
+restarting = "A szerver egy pillanat múlva elérhető lesz."
[passwordModal]
description = "Állítson be egy jelszót a konfigurációs beállítások védelmére. A főképernyő használata továbbra is lehetséges bejelentkezés nélkül."
@@ -530,7 +580,7 @@ meterstop = "Mérő leállítás"
odometer = "Futásteljesítmény"
price = "Ár"
started = "Elkezdődött"
-title = "Töltési munkamenet"
+title = "Töltési Folyamatok"
[sessions]
avgPower = "⌀ Teljesítmény"
@@ -539,15 +589,15 @@ chargeDuration = "Időtartam"
co2 = "⌀ CO₂"
csvMonth = "Letöltés - {month} .CSV"
csvTotal = "Letöltés - Összes időszak .CSV"
-date = "Indulás"
+date = "Kezdés"
downloadCsv = "Letöltés mint: CSV"
energy = "Töltve"
loadpoint = "Töltőpont"
-noData = "Nincs töltési munkamenet ebben a hónapban."
+noData = "Nincs töltési folyamat ebben a hónapban."
price = "Σ Ár"
-reallyDelete = "Biztosan szeretné törölni ezt a munkamenetet?"
+reallyDelete = "Biztosan szeretné törölni ezt a folyamatot?"
solar = "Napenergia"
-title = "Töltési munkamenetek"
+title = "Töltési Folyamatok"
total = "Összesen"
vehicle = "Jármű"
@@ -589,7 +639,7 @@ auto = "Automatikus"
label = "Nyelv"
[settings.sponsorToken]
-expires = "A szponzor token-ed le fog járni {inXDays} nap múlva. {getNewToken} és frissítsd a konfigurációs fájlodat."
+expires = "A szponzor token-ed le fog járni {inXDays}. {getNewToken} és frissítsd itt."
getNew = "Kérj egy újat"
hint = "Megjegyzés: Ezt a jövőben automatizálni fogjuk."
diff --git a/i18n/lt.toml b/i18n/lt.toml
index 10c873e14a..84b311df31 100644
--- a/i18n/lt.toml
+++ b/i18n/lt.toml
@@ -468,6 +468,7 @@ minpv = "Min+Saulė"
now = "Greitas"
off = "Stop"
pv = "Saulė"
+smart = "Išmanus"
[main.provider]
login = "prisijungti"
@@ -483,7 +484,7 @@ descriptionSoc = "Kada automobilis turėtų būti įkrautas iki {targetSoc}?"
inactiveLabel = "Suplanuotas laikas"
modalTitle = "Nustatyti įkrovimo pabaigos laiką"
noActivePlan = "Nėra Aktyvaus Plano"
-notReachableInTime = "Tikslo pasiekti nespėsime. Numatoma pabaiga: {endTime}."
+notReachableInTime = "Tikslas bus pasiektas {overrun} vėliau."
onlyInPvMode = "Įkrovimo planas veikia tik nustatyme Saulė."
planDescription = "Įveskite išvykimo laiką ir evcc įkraus automobilį kiek įmanoma ekonomiškiau arba ekologiškiau."
planDuration = "Įkrovimo laikas"
@@ -496,7 +497,7 @@ remove = "Panaikinti"
setPlan = "Nustatyti įkrovimo planą"
setTargetTime = "nesuplanuotas"
targetIsAboveLimit = "Sukonfigūruotas įkrovimo limitas {limit} šiuo periodu bus ignoruojamas."
-targetIsAboveVehicleLimit = "Padidinkite automobilio limitą ({limit}), kad pasiektumėte įkrovimo tikslą."
+targetIsAboveVehicleLimit = "Automobilyje nustatytas limitas yra mažesnis už įkrovimo tikslą."
targetIsInThePast = "Pasirinkite laiką ateityje."
targetIsTooFarInTheFuture = "Pakoreguosime planą, kai tik gausime naujų duomenų."
title = "Planinis Įkrovimas"
@@ -540,21 +541,27 @@ vehicleLimit = "Automobilyje nustatytas limitas: {soc}"
[main.vehicleStatus]
charging = "Įkraunama..."
-cheapEnergyCharging = "Įkraunama pigia energija: {price} (limitas {limit})"
-cleanEnergyCharging = "Įkraunama švaria energija: {co2} (limitas {limit})"
+cheapEnergyCharging = "Šiuo metu energija yra pigi."
+cheapEnergyNextStart = "Pigi energija už {duration}."
+cheapEnergySet = "Kainos riba nustatyta."
+cleanEnergyCharging = "Šiuo metu energija yra švari"
+cleanEnergyNextStart = "Švari energija už {duration}."
+cleanEnergySet = "CO₂ riba nustatyta."
climating = "Aptiktas išankstinis kondicionavimas."
connected = "Prijungtas."
disconnected = "Neprijungtas."
+finished = "Baigta."
minCharge = "Minimalus įkrovimas iki {soc}."
-pvDisable = "Trūksta saulės. Pauzė už {remaining}..."
-pvEnable = "Saulės užtenka, įkrovimas už {remaining}..."
-scale1p = "Sumažinimas į vienfazį įkrovimą už {remaining}..."
-scale3p = "Padidinimas į trifazį įkrovimą už {remaining}..."
-targetChargeActive = "Įkrovimo planas aktyvuotas..."
-targetChargePlanned = "Suplanuotas įkrovimas prasidės {time}."
+pvDisable = "Trūksta pertekliaus. Pauzė netrukus."
+pvEnable = "Pertekliaus užtenka, įkrovimas netrukus."
+scale1p = "Sumažinimas į vienfazį įkrovimą netrukus."
+scale3p = "Padidinimas į trifazį įkrovimą netrukus."
+targetChargeActive = "Įkrovimo planas aktyvuotas. Numatoma pabaiga už {duration}."
+targetChargePlanned = "Suplanuotas įkrovimas prasidės už {duration}."
targetChargeWaitForVehicle = "Įkrovimas pagal planą leidžiamas. Laukiama automobilio..."
unknown = ""
-vehicleLimitReached = "Automobilyje nustatytas limitas {soc} pasiektas."
+vehicleLimit = "Automobilyje nustatytas limitas."
+vehicleLimitReached = "Automobilyje nustatytas limitas pasiektas."
waitForVehicle = "Paruošta. Laukiama automobilio..."
[notifications]
@@ -563,6 +570,7 @@ logs = "Peržiūrėti visus žurnalus"
modalTitle = "Pranešimai"
[offline]
+configurationError = "Klaida startuojant. Patikrinkite konfigūraciją ir restartuokite."
message = "Nėra ryšio su serveriu."
reload = "Perkrauti?"
restart = "Restartuoti"
diff --git a/i18n/sl.toml b/i18n/sl.toml
index 94ec705dad..7b0d7542d5 100644
--- a/i18n/sl.toml
+++ b/i18n/sl.toml
@@ -144,6 +144,31 @@ validateSave = "Preveri in shrani"
[config.modbusproxy]
description = "Dovoli več odjemalcem dostop do ene naprave Modbus."
+title = "Modbus Proxy"
+
+[config.mqtt]
+authentication = "Avtentikacija"
+description = "Povežite se s posrednikom MQTT za izmenjavo podatkov z drugimi sistemi v vašem omrežju."
+descriptionClientId = "Avtor sporočil. Če se pusti prazno, se uporabi `evcc-[rand]`."
+descriptionTopic = "Pustite prazno, če želite onemogočiti objavljanje."
+labelBroker = "Posrednik"
+labelCheckInsecure = "Dovoli samopodpisane certifikate"
+labelClientId = "ID Stranke"
+labelInsecure = "Preverjanje certifikata"
+labelPassword = "Geslo"
+labelTopic = "Topic"
+labelUser = "Uporabniško ime"
+publishing = "Objavljanje"
+title = "MQTT"
+
+[config.network]
+descriptionHost = "Uporabite pripono .local, da omogočite mDNS. Pomembno za odkrivanje mobilne aplikacije in nekaterih polnilnikov OCPP."
+descriptionPort = "Vrata za spletni vmesnik in API. Če spremenite, boste morali posodobiti URL v brskalniku."
+descriptionSchema = "Vpliva samo na to, kako so URL-ji ustvarjeni. Če izberete HTTPS, šifriranje ne bo omogočeno."
+labelHost = "Ime gostitelja"
+labelPort = "Port"
+labelSchema = "Shema"
+title = "Mreža"
[config.options]
@@ -161,12 +186,26 @@ titleEdit = "Uredi merilnik sončne energije"
[config.section]
general = "Splošno"
+grid = "Omrežje"
+integrations = "Integracije"
+loadpoints = "Polnilne točke"
+meter = "Sončna energija in baterije"
system = "Sistem"
+vehicles = "Vozila"
[config.site]
cancel = "Prekliči"
save = "Shrani"
+[config.sponsor]
+addToken = "Vnesi sponzorski žeton"
+changeToken = "Spremeni sponzorski žeton"
+description = "Model sponzoriranja nam pomaga vzdrževati projekt in trajnostno graditi nove in vznemirljive funkcije. Kot sponzor dobite dostop do vseh izvedb polnilnikov."
+descriptionToken = "Žeton dobite na naslovu {url}. Ponujamo tudi poskusni žeton za testiranje."
+error = "Sponzorski žeton ni veljaven."
+labelToken = "Sponzorski žeton"
+title = "Sponzorstvo"
+
[config.system]
logs = "Logi"
restart = "Ponovni zagon"
@@ -175,6 +214,10 @@ restartRequiredMessage = "Konfiguracija je bila spremenjena."
restartingDescription = "Prosim počakajte..."
restartingMessage = "Ponovni zagon evcc."
+[config.tariffs]
+description = "Določite svoje tarife za energijo, da izračunate stroške svojih polnilnih sej."
+title = "Tarife"
+
[config.title]
description = "Prikazano na glavnem zaslonu in na zavihku brskalnika."
label = "Naslov"
@@ -265,12 +308,12 @@ optInMoreDetailsLink = "tukaj"
optInSponsorship = "Potrebno je sponzoriranje."
[footer.version]
-availableLong = "na voljo nova različica"
+availableLong = "na voljo je nova različica"
modalCancel = "Prekliči"
modalDownload = "Prenos"
-modalInstalledVersion = "Nameščena različica: "
+modalInstalledVersion = "Trenutno nameščena različica"
modalNoReleaseNotes = "Opombe ob izdaji niso na voljo. Več informacij o novi različici:"
-modalTitle = "Na voljo nova različica"
+modalTitle = "Na voljo je nova različica"
modalUpdate = "Namesti"
modalUpdateNow = "Namesti zdaj"
modalUpdateStarted = "Zagon nove različice evcc…"
@@ -355,11 +398,11 @@ batteryCharge = "Polnjenje baterije"
batteryDischarge = "Praznjenje baterije"
batteryHold = "Baterija (zaklenjena)"
batteryTooltip = "{energy} od {total} ({soc})"
-gridImport = "Uporaba omrežja"
-homePower = "Poraba"
-loadpoints = "Polnilnik | Polnilnik | {count} polnilnih mest"
+gridImport = "Uvoz iz omrežja"
+homePower = "Poraba objekta"
+loadpoints = "Polnilnica | Polnilnici | {count} polnilnih mest"
noEnergy = "Ni podatkov o merilniku"
-pvExport = "Izvoz v omreže"
+pvExport = "Izvoz v omrežje"
pvProduction = "Proizvodnja"
selfConsumption = "Samoporaba"
@@ -420,6 +463,7 @@ minpv = "Min+Sonce"
now = "Hitro"
off = "Izklop"
pv = "Sonce"
+smart = "Pametno"
[main.provider]
login = "prijava"
@@ -434,7 +478,7 @@ descriptionEnergy = "Do kdaj želite da se vozilo napolni za {targetEnergy}?"
descriptionSoc = "Kdaj želite, da je vozilo napolnjeno na {targetSoc}?"
inactiveLabel = "Ciljni čas"
modalTitle = "Nastavite ciljni čas"
-notReachableInTime = "Cilja ni mogoče doseči pravočasno. Predvideni cilj: {endTime}."
+notReachableInTime = "Cilj bo dosežen {overrun} pozneje."
onlyInPvMode = "Načrt polnjenja deluje samo v solarnem načinu."
planDuration = "Čas polnjenja"
planPeriodLabel = "Obdobje"
@@ -445,7 +489,7 @@ priceLimit = "cenovna omejitev {price}"
remove = "Odstrani"
setTargetTime = "brez"
targetIsAboveLimit = "Konfigurirana omejitev polnjenja {limit} se ne bo upoštevala v tem obdobju."
-targetIsAboveVehicleLimit = "Povečaj omejitev vozila ({limit}), da dosežeš cilj polnjenja."
+targetIsAboveVehicleLimit = "Omejitev vozila je pod ciljem polnjenja."
targetIsInThePast = "Izberi čas v prihodnosti, Marty."
targetIsTooFarInTheFuture = "Načrt bomo prilagodili takoj, ko bomo izvedeli več o novi funkcionalnosti."
title = "Ciljni čas"
@@ -489,15 +533,20 @@ vehicleLimit = "Omejitev vozila: {soc}"
[main.vehicleStatus]
charging = "Polnjenje…"
-cheapEnergyCharging = "Polnjenje s poceni energijo: {price} (omejitev {limit})"
-cleanEnergyCharging = "Polnjenje s čisto energijo: {co2} (omejitev {limit})"
+cheapEnergyCharging = "Poceni energija je na voljo."
+cheapEnergyNextStart = "Poceni energija čez {duration}."
+cheapEnergySet = "Nastavljena je omejitev cene."
+cleanEnergyCharging = "Čista energija je na voljo."
+cleanEnergyNextStart = "Čista energija na voljo čez {duration}."
+cleanEnergySet = "Omejitev CO₂ je nastavljena."
climating = "Zaznano predkondicioniranje."
connected = "Povezan."
disconnected = "Odklopljen."
+finished = "Končano."
guard = "Zaščita preklopa je aktivna za {remaining}."
minCharge = "Minimalno polnjenje na {soc}."
-pvDisable = "Premalo presežka. Prekinitev čez {remaining}..."
-pvEnable = "Na voljo so presežki. Začetek čez {remaining}..."
+pvDisable = "Premalo presežka. Prekinitev polnjenja se bo izvedla kmalu."
+pvEnable = "Na voljo so presežki. Začetek polnjenja se bo izvedel kmalu."
scale1p = "Zmanjšanje na 1-fazno napajanje čez {remaining} ..."
scale3p = "Povečanje na 3-fazno napajanje čez {remaining}..."
targetChargeActive = "Ciljno polnjenje je aktivno..."
@@ -513,8 +562,11 @@ logs = "Prikaži celotne loge"
modalTitle = "Obvestila"
[offline]
-message = "Ni povezan s strežnikom."
+message = "Ni povezave s strežnikom."
reload = "Ponovno naloži?"
+restart = "Ponovni zagon"
+restartNeeded = "Potrebno za uveljavitev sprememb."
+restarting = "Strežnik bo kmalu nazaj."
[passwordModal]
description = "Nastavite geslo za zaščito konfiguracijskih nastavitev. Uporaba glavnega vmesnika je še vedno možna brez prijave."
@@ -550,7 +602,7 @@ chargeDuration = "Trajanje"
co2 = "⌀ CO₂"
csvMonth = "Prenesi {month} CSV"
csvTotal = "Prenesi celoten CSV"
-date = "Začni"
+date = "Začetek"
downloadCsv = "Prenesi kot CSV"
energy = "Napolnjeno"
loadpoint = "Polnilno mesto"
diff --git a/i18n/sv.toml b/i18n/sv.toml
index 66064f4ce9..7dd64f6767 100644
--- a/i18n/sv.toml
+++ b/i18n/sv.toml
@@ -35,15 +35,35 @@ title = "Belastningshantering"
[config.control]
description = "Standardvärdena är vanligtsvis ok. Ändra bara om du vet vad du gör."
descriptionInterval = "Uppdateringsintervall i sekunder. Definierar hur ofta evcc läser in data, ändrar laddeffekt och uppdaterar UI. Korta intervall (<30s) kan orsaka självsvängning och andra felaktiga resultat."
+descriptionMaxGridSupply = "Endast relevant för hybridinverter som inte kan ge full DC-effekt för att försörja hemmet via AC. Detta scenario kan leda till oönskad nätanvändning eftersom evcc förutsätter att full DC-effekt är tillgänglig. Lägg in ett värde på minst 50W för att förhindra detta."
+descriptionResidualPower = "Flyttar fokuspunkt på kontrolloopen. Om du har ett hemmabatteri så rekommenderas att ha ett värde på minst 100 W. Då får batteriet lite högre prioritet jämfört med nätanvändning."
+labelInterval = "Uppdateringsintervall"
+labelMaxGridSupply = "Max. näteffekt vid laddning"
+labelResidualPower = "Kvarvarande effekt"
+title = "Kontrollera beteende"
[config.deviceValue]
+broker = "Broker"
+bucket = "Bucket"
capacity = "Kapacitet"
chargeStatus = "Status"
+chargeStatusA = "ej ansluten"
+chargeStatusB = "ansluten"
+chargeStatusC = "laddar"
+chargeStatusE = "ingen ström"
+chargeStatusF = "fel"
chargedEnergy = "Laddad"
+co2 = "Elnät CO₂"
+configured = "Konfigurerad"
+currency = "Valuta"
current = "Ström"
enabled = "Aktiverad"
energy = "Energi"
+feedinPrice = "Inmatningspris"
+gridPrice = "Nätpris"
+no = "nej"
odometer = "Mätarställning"
+org = "Organisation"
phaseCurrents = "Ström L1..L3"
phasePowers = "Effekt L1..L3"
phaseVoltages = "Spänning L1..L3"
@@ -51,6 +71,14 @@ power = "Effekt"
range = "Räckvidd"
soc = "SoC"
socLimit = "SoC gräns"
+temp = "Temperatur"
+topic = "Ämne"
+url = "URL"
+yes = "ja"
+
+[config.eebus]
+description = "Inställning som tillåter evcc att kommunicera med andra EEBus-enheter."
+title = "EEBus"
[config.form]
example = "Exempel"
@@ -58,22 +86,54 @@ optional = "valfri"
[config.general]
cancel = "Avbryt"
+docsLink = "Se dokumentation."
+experimental = "Experimentell"
+off = "av"
+on = "på"
+password = "Lösenord"
+remove = "Ta bort"
save = "Spara"
+telemetry = "Telemetri"
+title = "Titel"
[config.grid]
+title = "Elmätare"
titleAdd = "Lägg till elnätsmätare"
titleEdit = "Ändra elnätsmätare"
+[config.hems]
+description = "Anslut evcc till annat energistyrningssystem i hemmet"
+title = "HEMS"
+
+[config.influx]
+description = "Skriver laddata och andra mätningar till InfluxDB. Använd Grafana eller andra verktyg för att visualisera data."
+descriptionToken = "Se dokumentationen för InfluxDB. https://docs.influxdata.com/influxdb/v2/admin/"
+labelBucket = "Bucket"
+labelDatabase = "Databas"
+labelOrg = "Organisation"
+labelPassword = "Lösenord"
+labelToken = "API Token"
+labelUrl = "URL"
+labelUser = "Användarnamn"
+title = "InfluxDB"
+v1Support = "Behöver du support till InfluxDB 1.x?"
+v2Support = "Tillbaka till InfluxDB 2.x"
+
[config.main]
addLoadpoint = "Lägg till laddbox"
addPvBattery = "Lägg till solceller eller batteri"
addVehicle = "Lägg till fordon"
+configured = "konfigurerad"
edit = "ändra"
title = "Inställningar"
unconfigured = "ej konfigurerad"
vehicles = "Mina fordon"
yaml = "Konfigurera i evcc.yaml. Kan inte ändras i UI."
+[config.messaging]
+description = "Mottag notiser om dina laddningar."
+title = "Notiser"
+
[config.meter]
cancel = "Avbryt"
delete = "Radera"
@@ -82,6 +142,34 @@ template = "Tillverkare"
titleChoice = "Vad vill du lägga till?"
validateSave = "Validera & spara"
+[config.modbusproxy]
+description = "Tillåt flera klienter till en Modbus-enhet."
+title = "Modbus Proxy"
+
+[config.mqtt]
+authentication = "Autentisering"
+description = "Anslut till en MQTT-broker för att utbyta data med andra system på ditt nätverk."
+descriptionClientId = "Avsändare av notis. Om tomt används `evcc-[rand]` ."
+descriptionTopic = "Lämna tomt för att deaktivera publicering."
+labelBroker = "Broker"
+labelCheckInsecure = "Tillåt egensignerade certifikat"
+labelClientId = "Klient ID"
+labelInsecure = "Certifikatkontroll"
+labelPassword = "Lösenord"
+labelTopic = "Ämne"
+labelUser = "Användarnamn"
+publishing = "Publicerar"
+title = "MQTT"
+
+[config.network]
+descriptionHost = "Använd .local suffix för att aktivera mDNS. Relevant för upptäckt av mobilappen och vissa OCPP-laddare."
+descriptionPort = "Port för webinterface och API. Uppdatera webläsarens URL om du ändrar detta."
+descriptionSchema = "Påverkar endast hur URLer skapas. Val av HTTPS kommer ej att aktivera kryptering."
+labelHost = "Värdnamn"
+labelPort = "Port"
+labelSchema = "Schema"
+title = "Nätverk"
+
[config.options]
[config.options.endianness]
@@ -98,12 +186,26 @@ titleEdit = "Ändra solcellsmätare"
[config.section]
general = "Allmänna"
+grid = "Elnät"
+integrations = "Integrationer"
+loadpoints = "Laddpunkter"
+meter = "Sol & batteri"
system = "System"
+vehicles = "Fordon"
[config.site]
cancel = "Avbryt"
save = "Spara"
+[config.sponsor]
+addToken = "Ange sponsor-token"
+changeToken = "Ändra sponsor-token"
+description = "Sponsormodellen hjälper oss att underhålla projektet och hållbart bygga nya och spännande funktioner. Som sponsor får du tillgång till alla laddningsmöjligheter."
+descriptionToken = "Du får token från {url}. Vi erbjuder också en prov-token för testning."
+error = "Din sponsor-token är inte giltig."
+labelToken = "Sponsor-token"
+title = "Sponsor"
+
[config.system]
logs = "Loggar"
restart = "Omstart"
@@ -112,6 +214,10 @@ restartRequiredMessage = "Inställningar ändrade."
restartingDescription = "Vänligen vänta..."
restartingMessage = "Startar om evcc."
+[config.tariffs]
+description = "Lägg in din energitariff för att beräkna kostnaden för dina laddningar."
+title = "Tariffer"
+
[config.title]
description = "Visas på huvudskärm och tabbar."
label = "Titel"
@@ -357,6 +463,7 @@ minpv = "Min+Sol"
now = "Snabbt"
off = "Av"
pv = "Sol"
+smart = "Smart"
[main.provider]
login = "logga in"
@@ -454,6 +561,9 @@ modalTitle = "Meddelanden"
[offline]
message = "Inte ansluten till en server."
reload = "Ladda igen?"
+restart = "Omstart"
+restartNeeded = "Krävs för att spara ändringar."
+restarting = "Servern är strax tillbaka."
[passwordModal]
description = "Använd lösenord för att skydda inställningarna. Huvudskärmen kan användas utan att logga in."
@@ -545,7 +655,7 @@ auto = "Automatisk"
label = "Språk"
[settings.sponsorToken]
-expires = "Din sponsortoken löper ut om {inXDays}. {getNewToken} och uppdatera din konfigurationsfil."
+expires = "Din sponsortoken löper ut om {inXDays}. {getNewToken} och uppdatera här."
getNew = "Hämta en ny"
hint = "Observera: Vi kommer att automatisera detta i framtiden."
diff --git a/i18n/tr.toml b/i18n/tr.toml
index 3c9545edaa..1322b05b7d 100644
--- a/i18n/tr.toml
+++ b/i18n/tr.toml
@@ -26,14 +26,42 @@ never = "yalnızca yeterli güneş enerjisi fazlalığı ile"
titleAdd = "Batarya Sayacı Ekle"
titleEdit = "Batarya Sayacını Düzenle"
+[config.circuits]
+description = "Aynı elektrik devresine bağlı tüm doldurma noktalarının yekûnunun güç ve akım sınırını aşmadığından emin ol. Devreler aşama sılaralaması oluşturmak için iç içe geçebilirler."
+title = "Yük yönetimi"
+
+[config.control]
+description = "Genellikle varsayılan değerler iyidir. Bunları yalnızca ne yaptığını biliyorsan değiştir."
+descriptionInterval = "Saniye cinsinden denetim çevrimi güncelleme döngüsü. evcc'nin ölçüm verilerini ne sıklıkla okuyacağını, doldurma gücünü ayarlayacağını ve kullanıcı arayüzünü güncelleyeceğini tanımlar. Kısa aralıklar (<30s) salınımlara ve istenmeyen davranışlara yol açabilir."
+descriptionMaxGridSupply = "Sadece DC üretim gücünün tamamını AC üzerinden eve veremeyen hibrit inverterler için geçerlidir. Bu senaryo istenmeyen şebeke kullanımına yol açabilir, çünkü evcc tüm DC gücünün mevcut olduğunu varsayar. Bunu önlemek için en az 50 W'lık bir değer kullanın."
+descriptionResidualPower = "Denetim çevriminin çalışma noktasını değiştirir. Eğer bir ev bataryan varsa 100 W değerini ayarlaman önerilir. Bu şekilde batarya, şebeke kullanımına göre hafif bir önceliğe sahip olacaktır."
+labelInterval = "Güncelleme aralığı"
+labelMaxGridSupply = "Doldurma esnasında azami şebeke kullanımı"
+labelResidualPower = "Artık güç"
+title = "Kontrol davranışı"
+
[config.deviceValue]
+broker = "Aracı"
+bucket = "Kova"
capacity = "Kapasite"
chargeStatus = "Durum"
+chargeStatusA = "bağlı değil"
+chargeStatusB = "bağlı"
+chargeStatusC = "dolduruyor"
+chargeStatusE = "elektrik yok"
+chargeStatusF = "hata"
chargedEnergy = "Doldu"
+co2 = "Şebeke CO₂"
+configured = "Yapılandırıldı"
+currency = "Para Birimi"
current = "Akım"
enabled = "Etkin"
energy = "Enerji"
+feedinPrice = "Satış fiyatı"
+gridPrice = "Alım fiyatı"
+no = "hayır"
odometer = "Kilometre Sayacı"
+org = "Organizasyon"
phaseCurrents = "Faz Akımı L1..L3"
phasePowers = "Faz Gücü L1..L3"
phaseVoltages = "Faz Voltajı L1..L3"
@@ -41,6 +69,14 @@ power = "Güç"
range = "Menzil"
soc = "Doluluk durumu"
socLimit = "Doluluk Sınırlaması"
+temp = "Isı"
+topic = "Konu"
+url = "URL"
+yes = "evet"
+
+[config.eebus]
+description = "evcc'nin diğer EEBus cihazlarıyla iletişim kurmasını sağlayan yapılandırma."
+title = "EEBus"
[config.form]
example = "Örnek"
@@ -48,22 +84,54 @@ optional = "isteğe bağlı"
[config.general]
cancel = "İptal"
+docsLink = "Belgelere bak."
+experimental = "Deneysel"
+off = "kapalı"
+on = "açık"
+password = "Şifre"
+remove = "Kaldır"
save = "Kaydet"
+telemetry = "Uzölçüm"
+title = "Başlık"
[config.grid]
+title = "Elektrik sayacı"
titleAdd = "Elektrik Sayacı Ekle"
titleEdit = "Elektrik Sayacını Düzenle"
+[config.hems]
+description = "evcc'yi başka bir ev enerji yönetim sistemine bağla."
+title = "HEMS"
+
+[config.influx]
+description = "Doldurma verilerini ve diğer ölçümleri InfluxDB'ye yazar. Verileri görselleştirmek için Grafana veya başka araçlar kullan."
+descriptionToken = "Nasıl oluşturulacağını öğrenmek için InfluxDB belgelerine göz at. https://docs.influxdata.com/influxdb/v2/admin/"
+labelBucket = "Kova"
+labelDatabase = "Veritabanı"
+labelOrg = "Organizasyon"
+labelPassword = "Parola"
+labelToken = "API Jetonu"
+labelUrl = "URL"
+labelUser = "Kullanıcı Adı"
+title = "InfluxDB"
+v1Support = "InfluxDB 1.x için desteğe mi ihtiyacın var?"
+v2Support = "InfluxDB 2.x'e geri dön"
+
[config.main]
addLoadpoint = "Doldurma noktası ekle"
addPvBattery = "Güneş enerjisi veya enerji deposu ekle"
addVehicle = "Araç ekle"
+configured = "yapılandırıldı"
edit = "düzenle"
title = "Yapılandırma"
unconfigured = "yapılandırılmadı"
vehicles = "Araçlarım"
yaml = "evcc.yaml içerisinde yapılandırıldı. Kullanıcı arayüzünde düzenlenemez."
+[config.messaging]
+description = "Dolum oturumları ve diğer olaylar hakkında bildirimler al."
+title = "Bildirimler"
+
[config.meter]
cancel = "İptal"
delete = "Sil"
@@ -72,6 +140,34 @@ template = "Üretici"
titleChoice = "Ne Eklemek İstersin:"
validateSave = "Doğrula ve kaydet"
+[config.modbusproxy]
+description = "Birden fazla istemcinin tek bir Modbus cihazına erişmesine izin verir."
+title = "Modbus vekili"
+
+[config.mqtt]
+authentication = "Kimlik Doğrulama"
+description = "Ağındaki diğer sistemlerle veri alışverişi yapmak için evcc'yi bir MQTT aracısına bağla."
+descriptionClientId = "İletilerin yazarı. Eğer boşsa `evcc-[rand]` kullanılır."
+descriptionTopic = "Yayınlamayı devre dışı bırakmak için boş bırak."
+labelBroker = "Aracı"
+labelCheckInsecure = "Güvenli olmayan bağlantılara izin ver"
+labelClientId = "Müşteri kimliği"
+labelInsecure = "Sertifika doğrulama"
+labelPassword = "Parola"
+labelTopic = "Konu"
+labelUser = "Kullanıcı Adı"
+publishing = "Yayınla\""
+title = "MQTT"
+
+[config.network]
+descriptionHost = "mDNS'yi etkinleştirmek için .local sonekini kullan. Mobil uygulamanın ve bazı OCPP doldurma cihazlarının tanınması ile ilgili."
+descriptionPort = "Web arayüzü ve API için bağlantı noktası. Bunu değiştirirsen tarayıcının URL'sini güncellemen gerekir."
+descriptionSchema = "Yalnızca URL'lerin oluşturuluşunu etkiler. HTTPS'nin seçilmesi şifrelemeyi etkinleştirmez."
+labelHost = "Ana makine adı"
+labelPort = "Port"
+labelSchema = "Şema"
+title = "Ağ"
+
[config.options]
[config.options.endianness]
@@ -88,7 +184,21 @@ titleEdit = "GES Sayacını Düzenle"
[config.section]
general = "Genel"
+grid = "Şebeke"
+integrations = "Bütünleştirmeler"
+loadpoints = "Dolum noktaları"
+meter = "GES ve Batarya"
system = "Sistem"
+vehicles = "Araçlar"
+
+[config.sponsor]
+addToken = "Destekçi jetonunu gir"
+changeToken = "Destekçi jetonunu değiştir"
+description = "Destek modeli, projeyi sürdürmemize ve sürdürülebilir bir şekilde yeni ve heyecan verici özellikler geliştirmemize yardımcı oluyor. Destekçi olarak tüm doldurma cihazı uygulamalarına erişimin oluyor."
+descriptionToken = "Jetonu {url} adresinden alabilirsiniz. Denemek için bir deneme jetonu da sunuyoruz."
+error = "Destekçi jetonu geçerli değil."
+labelToken = "Destekçi jetonu"
+title = "Destekçilik"
[config.system]
logs = "Loglar"
@@ -98,6 +208,10 @@ restartRequiredMessage = "Yapılandırma ayarları değiştirildi."
restartingDescription = "Lütfen bekle…"
restartingMessage = "evcc yeniden başlatılıyor…"
+[config.tariffs]
+description = "Doldurma oturumlarının maliyetlerini hesaplamak için elektrik tarifelerini gir."
+title = "Tarifeler"
+
[config.title]
description = "Ana ekranda ve tarayıcı sekmesinde görüntülenir."
label = "Başlık"
@@ -162,6 +276,7 @@ total = "tüm zaman"
[footer.sponsor]
becomeSponsor = "Destekçi ol"
+becomeSponsorExtended = "Çıkartma almak için bizi doğrudan destekle."
confetti = "Konfeti istermisin?"
confettiPromise = "Çıkartmalar ve dijital konfeti de var"
sticker = "… ya da evcc çıkartmaları?"
@@ -169,6 +284,10 @@ supportUs = "Hedefimiz güneş enerjisi ile yakıt ikmalini gelenek haline getir
thanks = "Teşekkürler {sponsor}! Katkın evcc'yi daha da geliştirmemize yardımcı oluyor."
titleNoSponsor = "Bize destek ol"
titleSponsor = "Destekçisin"
+titleTrial = "Deneme modu"
+titleVictron = "Victron Energy tarafından desteklenmektedir"
+trial = "Deneme modundasın ve tüm özellikleri kullanabilirsin. Destekçi olursan seviniriz."
+victron = "Victron Energy donanımı üzerinde evcc kullanıyorsun ve tüm özelliklere erişebiliyorsun."
[footer.telemetry]
optIn = "Doldurma verilerimi paylaşmak istiyorum."
@@ -233,6 +352,8 @@ update = "Otomatik güncelle"
[loginModal]
cancel = "İptal"
error = "Giriş başarısız: "
+iframeHint = "evcc'yi yeni bir sekmede aç."
+iframeIssue = "Parolan doğru, ancak tarayıcın kimlik doğrulama çerezini reddetti. Bu, evcc'yi HTTP üzerinden bir iframe içinde çalıştırırsan meydana gelebilir."
invalid = "Şifre geçersiz."
login = "Giriş yap"
password = "Şifre"
@@ -329,6 +450,7 @@ minpv = "Asg.+GES"
now = "Hızlı"
off = "Kapalı"
pv = "GES"
+smart = "Akıllı"
[main.provider]
login = "giriş yap"
@@ -342,7 +464,7 @@ currentPlan = "Etkin plan"
descriptionEnergy = "{targetEnergy} ne zamana kadar araca doldurulmalı?"
descriptionSoc = "Araç ne zaman {targetSoc} seviyesine doldurulmalı?"
inactiveLabel = "Hedeflenen zaman"
-notReachableInTime = "Hedeflenen zamana ulaşılamaz. Tahmini bitiş: {endTime}."
+notReachableInTime = "Hedeflenen zamana {overrun} sonra ulaşılacak."
onlyInPvMode = "Doldurma planı sadece güneş enerjisi modunda çalışır."
planDuration = "Doldurma süresi"
planPeriodLabel = "Zaman aralığı"
@@ -353,7 +475,7 @@ priceLimit = "{price} fiyat sınırı"
remove = "Kaldır"
setTargetTime = "yok"
targetIsAboveLimit = "Yapılandırılan {limit} seviyesindeki doldurma sınırı bu zaman aralığında yok sayılacaktır."
-targetIsAboveVehicleLimit = "Doldurma hedefine ulaşmak için araç sınırını ({limit}) artır."
+targetIsAboveVehicleLimit = "Araç sınırı doldurma hedefinin altında."
targetIsInThePast = "Gelecekte bir zaman seç, Marty."
targetIsTooFarInTheFuture = "Gelecek hakkında daha fazla bilgi edindiğimizde planı uyarlayacağız."
title = "Hedeflenen Zaman"
@@ -398,23 +520,30 @@ vehicleTarget = "Araç sınırı: {soc}"
[main.vehicleStatus]
charging = "doluyor…"
-cheapEnergyCharging = "Ucuz enerjiyle doluyor: {price} (limit {limit})"
-cleanEnergyCharging = "Temiz enerjiyle doluyor: {co2} (sınır {limit})"
+cheapEnergyCharging = "Ucuz enerji mevcut."
+cheapEnergyNextStart = "{duration} içinde ucuz enerji."
+cheapEnergySet = "Fiyat sınırı belirlendi."
+cleanEnergyCharging = "Temiz enerji mevcut."
+cleanEnergyNextStart = "{duration} içinde temiz enerji."
+cleanEnergySet = "CO₂ sınırı belirlendi."
climating = "Ön iklimlendirme algılandı."
connected = "Bağlı."
disconnected = "Bağlantı kesildi."
-minCharge = " {soc} kadar asgari dolum."
-pvDisable = "Yeterli güneş enerjisi fazlalığı yok. {remaining} içinde duraklatılıyor…"
-pvEnable = "Güneş enerjisi fazlalığı mevcut. {remaining} içinde başlatılıyor…"
-scale1p = "{remaining} içinde 1 faza düşürülüyor…"
-scale3p = "{remaining} içinde 3 faza yükseltiliyor…"
-targetChargeActive = "Doldurma planı aktif…"
-targetChargePlanned = "Doldurma planı saat {time} başlayacak."
+finished = "Tamamlandı."
+minCharge = "{soc} kadar asgari dolum."
+pvDisable = "Yeterli fazlalık yok. Birazdan duraklatılacak."
+pvEnable = "Fazlalık mevcut. Birazdan başlatılacak."
+scale1p = "Birazdan 1 fazla doldurmaya düşürülecek."
+scale3p = "Birazdan 3 fazla doldurmaya yükseltilecek."
+targetChargeActive = "Doldurma planı yürürlükte. Tahmini bitiş süresi {duration} içerisinde."
+targetChargePlanned = "Doldurma planı {duration} içerisinde başlayacak."
targetChargeWaitForVehicle = "Doldurma planı hazır. Araç bekleniyor…"
unknown = ""
-vehicleLimitReached = "{soc} araç sınırına ulaşıldı."
+vehicleLimit = "Araç sınırı."
+vehicleLimitReached = "Araç sınırına ulaşıldı."
vehicleTargetReached = "Araç sınırı {soc} ulaşıldı."
waitForVehicle = "Doldurmaya hazır. Araç bekleniyor…"
+welcome = "Bağlantıyı onaylamak için kısa ilk dolum."
[notifications]
dismissAll = "Bildirimleri kaldır"
@@ -422,8 +551,12 @@ logs = "Bütün logları görüntüle"
modalTitle = "Bildirimler"
[offline]
+configurationError = "Başlatma sırasında hata oluştu. Yapılandırmanı gözden geçir ve yeniden başlat."
message = "Ana makineye bağlantı yok."
reload = "Tekrar yüklensin mi?"
+restart = "Yeniden başlat"
+restartNeeded = "Değişiklikleri uygulamak için gerekli."
+restarting = "Sunucu birazdan dönecek."
[passwordModal]
description = "Yapılandırma ayarlarını korumak için bir şifre belirle. Ana görünüme erişim oturum açmadan da mümkün."
@@ -509,7 +642,7 @@ auto = "Otomatik"
label = "Dil"
[settings.sponsorToken]
-expires = "Sponsor jetonun {inXDays} sonra sona erecek. {getNewToken} ve yapılandırma dosyanı güncelle."
+expires = "Sponsor jetonun {inXDays} sonra sona erecek. {getNewToken} ve burada güncelle."
getNew = "Yeni bir tane al"
hint = "Not: İleride bunu otomatik hale getireceğiz."
@@ -517,7 +650,7 @@ hint = "Not: İleride bunu otomatik hale getireceğiz."
label = "Uzölçüm"
[settings.theme]
-auto = "Sistem"
+auto = "sistem"
dark = "Karanlık"
label = "Görünüm"
light = "Aydınlık"
diff --git a/meter/eebus.go b/meter/eebus.go
new file mode 100644
index 0000000000..db81000ddb
--- /dev/null
+++ b/meter/eebus.go
@@ -0,0 +1,153 @@
+package meter
+
+import (
+ "errors"
+ "time"
+
+ eebusapi "github.com/enbility/eebus-go/api"
+ "github.com/enbility/eebus-go/usecases/ma/mgcp"
+ spineapi "github.com/enbility/spine-go/api"
+ "github.com/evcc-io/evcc/api"
+ "github.com/evcc-io/evcc/provider"
+ "github.com/evcc-io/evcc/server/eebus"
+ "github.com/evcc-io/evcc/util"
+)
+
+type EEBus struct {
+ log *util.Logger
+
+ *eebus.Connector
+ uc *eebus.UseCasesCS
+
+ power, energy *provider.Value[float64]
+ voltages, currents *provider.Value[[]float64]
+}
+
+func init() {
+ registry.Add("eebus", NewEEBusFromConfig)
+}
+
+// New creates an EEBus HEMS from generic config
+func NewEEBusFromConfig(other map[string]interface{}) (api.Meter, error) {
+ cc := struct {
+ Ski string
+ Timeout time.Duration
+ }{
+ Timeout: 10 * time.Second,
+ }
+
+ if err := util.DecodeOther(other, &cc); err != nil {
+ return nil, err
+ }
+
+ return NewEEBus(cc.Ski, cc.Timeout)
+}
+
+// NewEEBus creates EEBus charger
+func NewEEBus(ski string, timeout time.Duration) (*EEBus, error) {
+ if eebus.Instance == nil {
+ return nil, errors.New("eebus not configured")
+ }
+
+ c := &EEBus{
+ log: util.NewLogger("eebus"),
+ uc: eebus.Instance.ControllableSystem(),
+ Connector: eebus.NewConnector(nil),
+ power: provider.NewValue[float64](timeout),
+ energy: provider.NewValue[float64](timeout),
+ voltages: provider.NewValue[[]float64](timeout),
+ currents: provider.NewValue[[]float64](timeout),
+ }
+
+ if err := eebus.Instance.RegisterDevice(ski, c); err != nil {
+ return nil, err
+ }
+
+ if err := c.Wait(90 * time.Second); err != nil {
+ return c, err
+ }
+
+ return c, nil
+}
+
+var _ eebus.Device = (*EEBus)(nil)
+
+// UseCaseEvent implements the eebus.Device interface
+func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
+ switch event {
+ case mgcp.DataUpdatePower:
+ c.dataUpdatePower(entity)
+ case mgcp.DataUpdateEnergyConsumed:
+ c.dataUpdateEnergyConsumed(entity)
+ case mgcp.DataUpdateCurrentPerPhase:
+ c.dataUpdateCurrentPerPhase(entity)
+ case mgcp.DataUpdateVoltagePerPhase:
+ c.dataUpdateVoltagePerPhase(entity)
+ }
+}
+
+func (c *EEBus) dataUpdatePower(entity spineapi.EntityRemoteInterface) {
+ data, err := c.uc.MGCP.Power(entity)
+ if err != nil {
+ c.log.ERROR.Println("MGCP.Power:", err)
+ return
+ }
+ c.power.Set(data)
+}
+
+func (c *EEBus) dataUpdateEnergyConsumed(entity spineapi.EntityRemoteInterface) {
+ data, err := c.uc.MGCP.EnergyConsumed(entity)
+ if err != nil {
+ c.log.ERROR.Println("MGCP.EnergyConsumed:", err)
+ return
+ }
+ c.energy.Set(data)
+}
+
+func (c *EEBus) dataUpdateCurrentPerPhase(entity spineapi.EntityRemoteInterface) {
+ data, err := c.uc.MGCP.CurrentPerPhase(entity)
+ if err != nil {
+ c.log.ERROR.Println("MGCP.CurrentPerPhase:", err)
+ return
+ }
+ c.currents.Set(data)
+}
+
+func (c *EEBus) dataUpdateVoltagePerPhase(entity spineapi.EntityRemoteInterface) {
+ data, err := c.uc.MGCP.VoltagePerPhase(entity)
+ if err != nil {
+ c.log.ERROR.Println("MGCP.VoltagePerPhase:", err)
+ return
+ }
+ c.voltages.Set(data)
+}
+
+func (c *EEBus) CurrentPower() (float64, error) {
+ return c.power.Get()
+}
+
+func (c *EEBus) TotalEnergy() (float64, error) {
+ return c.energy.Get()
+}
+
+func (c *EEBus) PhaseCurrents() (float64, float64, float64, error) {
+ res, err := c.currents.Get()
+ if err == nil && len(res) != 3 {
+ err = errors.New("invalid phase currents")
+ }
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ return res[0], res[1], res[2], nil
+}
+
+func (c *EEBus) PhaseVoltages() (float64, float64, float64, error) {
+ res, err := c.voltages.Get()
+ if err == nil && len(res) != 3 {
+ err = errors.New("invalid phase voltages")
+ }
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ return res[0], res[1], res[2], nil
+}
diff --git a/meter/goodwe-wifi.go b/meter/goodwe-wifi.go
index c8ea6abf64..5cb6d4303d 100644
--- a/meter/goodwe-wifi.go
+++ b/meter/goodwe-wifi.go
@@ -20,6 +20,8 @@ func init() {
//go:generate go run ../cmd/tools/decorate.go -f decorateGoodWeWifi -b *goodWeWiFi -r api.Meter -t "api.Battery,Soc,func() (float64, error)"
+// TODO deprecated remove
+
func NewGoodWeWifiFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
capacity `mapstructure:",squash"`
diff --git a/meter/helper.go b/meter/helper.go
new file mode 100644
index 0000000000..7d57d474ed
--- /dev/null
+++ b/meter/helper.go
@@ -0,0 +1,96 @@
+package meter
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/evcc-io/evcc/provider"
+)
+
+// BuildMeasurements returns typical meter measurement getters from config
+func BuildMeasurements(power, energy *provider.Config) (func() (float64, error), func() (float64, error), error) {
+ var powerG func() (float64, error)
+ if power != nil {
+ var err error
+ powerG, err = provider.NewFloatGetterFromConfig(*power)
+ if err != nil {
+ return nil, nil, fmt.Errorf("power: %w", err)
+ }
+ }
+
+ var energyG func() (float64, error)
+ if energy != nil {
+ var err error
+ energyG, err = provider.NewFloatGetterFromConfig(*energy)
+ if err != nil {
+ return nil, nil, fmt.Errorf("energy: %w", err)
+ }
+ }
+
+ return powerG, energyG, nil
+}
+
+// BuildPhaseMeasurements returns typical meter measurement getters from config
+func BuildPhaseMeasurements(currents, voltages, powers []provider.Config) (
+ func() (float64, float64, float64, error),
+ func() (float64, float64, float64, error),
+ func() (float64, float64, float64, error),
+ error,
+) {
+ currentsG, err := buildPhaseProviders(currents)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("currents: %w", err)
+ }
+
+ voltagesG, err := buildPhaseProviders(voltages)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("voltages: %w", err)
+ }
+
+ powersG, err := buildPhaseProviders(powers)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("powers: %w", err)
+ }
+
+ return currentsG, voltagesG, powersG, nil
+}
+
+// buildPhaseProviders returns phases getter for given config
+func buildPhaseProviders(providers []provider.Config) (func() (float64, float64, float64, error), error) {
+ if len(providers) == 0 {
+ return nil, nil
+ }
+
+ if len(providers) != 3 {
+ return nil, errors.New("need one per phase, total three")
+ }
+
+ var phases [3]func() (float64, error)
+ for idx, prov := range providers {
+ c, err := provider.NewFloatGetterFromConfig(prov)
+ if err != nil {
+ return nil, fmt.Errorf("[%d] %w", idx, err)
+ }
+
+ phases[idx] = c
+ }
+
+ return collectPhaseProviders(phases), nil
+}
+
+// collectPhaseProviders combines phase getters into combined api function
+func collectPhaseProviders(g [3]func() (float64, error)) func() (float64, float64, float64, error) {
+ return func() (float64, float64, float64, error) {
+ var res [3]float64
+ for idx, currentG := range g {
+ c, err := currentG()
+ if err != nil {
+ return 0, 0, 0, err
+ }
+
+ res[idx] = c
+ }
+
+ return res[0], res[1], res[2], nil
+ }
+}
diff --git a/meter/homewizard.go b/meter/homewizard.go
index 72ccfd856a..52d2f1d5bc 100644
--- a/meter/homewizard.go
+++ b/meter/homewizard.go
@@ -61,3 +61,17 @@ var _ api.MeterEnergy = (*HomeWizard)(nil)
func (c *HomeWizard) TotalEnergy() (float64, error) {
return c.conn.TotalEnergy()
}
+
+var _ api.PhaseCurrents = (*HomeWizard)(nil)
+
+// Currents implements the api.PhaseCurrents interface
+func (c *HomeWizard) Currents() (float64, float64, float64, error) {
+ return c.conn.Currents()
+}
+
+var _ api.PhaseVoltages = (*HomeWizard)(nil)
+
+// Voltages implements the api.PhaseVoltages interface
+func (c *HomeWizard) Voltages() (float64, float64, float64, error) {
+ return c.conn.Voltages()
+}
diff --git a/meter/homewizard/connection.go b/meter/homewizard/connection.go
index cd299e68d3..4411eca616 100644
--- a/meter/homewizard/connection.go
+++ b/meter/homewizard/connection.go
@@ -108,3 +108,15 @@ func (c *Connection) TotalEnergy() (float64, error) {
res, err := c.dataG.Get()
return res.TotalPowerImportT1kWh + res.TotalPowerImportT2kWh + res.TotalPowerImportT3kWh + res.TotalPowerImportT4kWh, err
}
+
+// Currents implements the api.PhaseCurrents interface
+func (c *Connection) Currents() (float64, float64, float64, error) {
+ res, err := c.dataG.Get()
+ return res.ActiveCurrentL1A, res.ActiveCurrentL2A, res.ActiveCurrentL3A, err
+}
+
+// Voltages implements the api.PhaseVoltages interface
+func (c *Connection) Voltages() (float64, float64, float64, error) {
+ res, err := c.dataG.Get()
+ return res.ActiveVoltageL1V, res.ActiveVoltageL2V, res.ActiveVoltageL3V, err
+}
diff --git a/meter/homewizard/types.go b/meter/homewizard/types.go
index a0039fa7bc..2ab9d97dc0 100644
--- a/meter/homewizard/types.go
+++ b/meter/homewizard/types.go
@@ -21,4 +21,10 @@ type DataResponse struct {
TotalPowerImportT2kWh float64 `json:"total_power_import_t2_kwh"`
TotalPowerImportT3kWh float64 `json:"total_power_import_t3_kwh"`
TotalPowerImportT4kWh float64 `json:"total_power_import_t4_kwh"`
+ ActiveCurrentL1A float64 `json:"active_current_l1_a"`
+ ActiveCurrentL2A float64 `json:"active_current_l2_a"`
+ ActiveCurrentL3A float64 `json:"active_current_l3_a"`
+ ActiveVoltageL1V float64 `json:"active_voltage_l1_v"`
+ ActiveVoltageL2V float64 `json:"active_voltage_l2_v"`
+ ActiveVoltageL3V float64 `json:"active_voltage_l3_v"`
}
diff --git a/meter/homewizard/types_test.go b/meter/homewizard/types_test.go
index 9fa79816f0..b05e889351 100644
--- a/meter/homewizard/types_test.go
+++ b/meter/homewizard/types_test.go
@@ -38,10 +38,16 @@ func TestUnmarshalDataResponse(t *testing.T) {
{
var res DataResponse
- jsonstr := `{"wifi_ssid": "My Wi-Fi","wifi_strength": 100,"total_power_import_t1_kwh": 30.511,"total_power_export_t1_kwh": 85.951,"active_power_w": 543,"active_power_l1_w": 676}`
+ jsonstr := `{"wifi_ssid": "My Wi-Fi","wifi_strength": 100,"total_power_import_t1_kwh": 30.511,"total_power_export_t1_kwh": 85.951,"active_power_w": 543,"active_power_l1_w": 28,"active_power_l2_w": 0,"active_power_l3_w": -181,"active_voltage_l1_v": 235.4,"active_voltage_l2_v": 235.8,"active_voltage_l3_v": 236.1,"active_current_l1_a": 1.19,"active_current_l2_a": 0.37,"active_current_l3_a": -0.93}`
require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, float64(30.511), res.TotalPowerImportT1kWh+res.TotalPowerImportT2kWh+res.TotalPowerImportT3kWh+res.TotalPowerImportT4kWh)
assert.Equal(t, float64(543), res.ActivePowerW)
+ assert.Equal(t, float64(235.4), res.ActiveVoltageL1V)
+ assert.Equal(t, float64(235.8), res.ActiveVoltageL2V)
+ assert.Equal(t, float64(236.1), res.ActiveVoltageL3V)
+ assert.Equal(t, float64(1.19), res.ActiveCurrentL1A)
+ assert.Equal(t, float64(0.37), res.ActiveCurrentL2A)
+ assert.Equal(t, float64(-0.93), res.ActiveCurrentL3A)
}
}
diff --git a/meter/mbmd.go b/meter/mbmd.go
index fbff86016d..5516448434 100644
--- a/meter/mbmd.go
+++ b/meter/mbmd.go
@@ -145,30 +145,27 @@ func NewModbusMbmdFromConfig(other map[string]interface{}) (api.Meter, error) {
}
func (m *ModbusMbmd) buildPhaseProviders(readings []string) (func() (float64, float64, float64, error), error) {
- var res func() (float64, float64, float64, error)
- if len(readings) > 0 {
- if len(readings) != 3 {
- return nil, errors.New("need one per phase, total three")
- }
-
- phases := make([]func() (float64, error), 0, 3)
- for idx, reading := range readings {
- opCurrent, err := modbus.ParseOperation(m.device, reading)
- if err != nil {
- return nil, fmt.Errorf("invalid measurement [%d]: %s", idx, reading)
- }
+ if len(readings) == 0 {
+ return nil, nil
+ }
- c := func() (float64, error) {
- return m.floatGetter(opCurrent)
- }
+ if len(readings) != 3 {
+ return nil, errors.New("need one per phase, total three")
+ }
- phases = append(phases, c)
+ var phases [3]func() (float64, error)
+ for idx, reading := range readings {
+ opCurrent, err := modbus.ParseOperation(m.device, reading)
+ if err != nil {
+ return nil, fmt.Errorf("invalid measurement [%d]: %s", idx, reading)
}
- res = collectPhaseProviders(phases)
+ phases[idx] = func() (float64, error) {
+ return m.floatGetter(opCurrent)
+ }
}
- return res, nil
+ return collectPhaseProviders(phases), nil
}
// floatGetter executes configured modbus read operation and implements func() (float64, error)
diff --git a/meter/meter.go b/meter/meter.go
index f7893ae86c..cb8352d86c 100644
--- a/meter/meter.go
+++ b/meter/meter.go
@@ -1,7 +1,6 @@
package meter
import (
- "errors"
"fmt"
"github.com/evcc-io/evcc/api"
@@ -41,39 +40,17 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
return nil, err
}
- power, err := provider.NewFloatGetterFromConfig(cc.Power)
+ powerG, energyG, err := BuildMeasurements(&cc.Power, cc.Energy)
if err != nil {
- return nil, fmt.Errorf("power: %w", err)
- }
-
- m, _ := NewConfigurable(power)
-
- // decorate energy
- var totalEnergyG func() (float64, error)
- if cc.Energy != nil {
- totalEnergyG, err = provider.NewFloatGetterFromConfig(*cc.Energy)
- if err != nil {
- return nil, fmt.Errorf("energy: %w", err)
- }
- }
-
- // decorate currents
- currentsG, err := buildPhaseProviders(cc.Currents)
- if err != nil {
- return nil, fmt.Errorf("currents: %w", err)
+ return nil, err
}
- // decorate voltages
- voltagesG, err := buildPhaseProviders(cc.Voltages)
+ currentsG, voltagesG, powersG, err := BuildPhaseMeasurements(cc.Currents, cc.Voltages, cc.Powers)
if err != nil {
- return nil, fmt.Errorf("voltages: %w", err)
+ return nil, err
}
- // decorate powers
- powersG, err := buildPhaseProviders(cc.Powers)
- if err != nil {
- return nil, fmt.Errorf("powers: %w", err)
- }
+ m, _ := NewConfigurable(powerG)
// decorate soc
var socG func() (float64, error)
@@ -104,51 +81,11 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Meter, error)
batModeS = cc.battery.ModeController(modeS)
}
- res := m.Decorate(totalEnergyG, currentsG, voltagesG, powersG, socG, cc.capacity.Decorator(), batModeS)
+ res := m.Decorate(energyG, currentsG, voltagesG, powersG, socG, cc.capacity.Decorator(), batModeS)
return res, nil
}
-func buildPhaseProviders(providers []provider.Config) (func() (float64, float64, float64, error), error) {
- var res func() (float64, float64, float64, error)
- if len(providers) > 0 {
- if len(providers) != 3 {
- return nil, errors.New("need one per phase, total three")
- }
-
- phases := make([]func() (float64, error), 0, 3)
- for idx, prov := range providers {
- c, err := provider.NewFloatGetterFromConfig(prov)
- if err != nil {
- return nil, fmt.Errorf("[%d] %w", idx, err)
- }
-
- phases = append(phases, c)
- }
-
- res = collectPhaseProviders(phases)
- }
-
- return res, nil
-}
-
-// collectPhaseProviders combines phase getters into currents api function
-func collectPhaseProviders(g []func() (float64, error)) func() (float64, float64, float64, error) {
- return func() (float64, float64, float64, error) {
- var res []float64
- for _, currentG := range g {
- c, err := currentG()
- if err != nil {
- return 0, 0, 0, err
- }
-
- res = append(res, c)
- }
-
- return res[0], res[1], res[2], nil
- }
-}
-
// NewConfigurable creates a new meter
func NewConfigurable(currentPowerG func() (float64, error)) (*Meter, error) {
m := &Meter{
diff --git a/meter/openwb.go b/meter/openwb.go
index 11242cb2fd..6ebb8fa9b5 100644
--- a/meter/openwb.go
+++ b/meter/openwb.go
@@ -64,13 +64,13 @@ func NewOpenWBFromConfig(other map[string]interface{}) (api.Meter, error) {
return nil, err
}
- var curr []func() (float64, error)
- for i := 1; i <= 3; i++ {
- current, err := to.FloatGetter(mq("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i))
+ var curr [3]func() (float64, error)
+ for i := 0; i < 3; i++ {
+ current, err := to.FloatGetter(mq("%s/evu/%s%d", cc.Topic, openwb.CurrentTopic, i+1))
if err != nil {
return nil, err
}
- curr = append(curr, current)
+ curr[i] = current
}
currents = collectPhaseProviders(curr)
diff --git a/meter/shelly/switch.go b/meter/shelly/switch.go
index 20a4ebbeb4..48dad7fc84 100644
--- a/meter/shelly/switch.go
+++ b/meter/shelly/switch.go
@@ -49,11 +49,11 @@ func (sh *Switch) CurrentPower() (float64, error) {
switch d.channel {
case 1:
- power = res.Switch1.Apower
+ power = res.Switch1.Apower + res.Pm1.Apower
case 2:
- power = res.Switch2.Apower
+ power = res.Switch2.Apower + res.Pm2.Apower
default:
- power = res.Switch0.Apower
+ power = res.Switch0.Apower + res.Pm0.Apower
}
}
@@ -130,11 +130,11 @@ func (sh *Switch) TotalEnergy() (float64, error) {
switch d.channel {
case 1:
- energy = res.Switch1.Aenergy.Total
+ energy = res.Switch1.Aenergy.Total + res.Pm1.Aenergy.Total
case 2:
- energy = res.Switch2.Aenergy.Total
+ energy = res.Switch2.Aenergy.Total + res.Pm2.Aenergy.Total
default:
- energy = res.Switch0.Aenergy.Total
+ energy = res.Switch0.Aenergy.Total + res.Pm0.Aenergy.Total
}
}
diff --git a/meter/shelly/types.go b/meter/shelly/types.go
index 229df88513..c06228c532 100644
--- a/meter/shelly/types.go
+++ b/meter/shelly/types.go
@@ -35,6 +35,9 @@ type Gen2StatusResponse struct {
Switch0 Gen2Switch `json:"switch:0"`
Switch1 Gen2Switch `json:"switch:1"`
Switch2 Gen2Switch `json:"switch:2"`
+ Pm0 Gen2Switch `json:"pm1:0"`
+ Pm1 Gen2Switch `json:"pm2:1"`
+ Pm2 Gen2Switch `json:"pm3:2"`
}
type Gen2EmStatusResponse struct {
diff --git a/meter/shelly/types_test.go b/meter/shelly/types_test.go
index d6ab287531..ad4006749c 100644
--- a/meter/shelly/types_test.go
+++ b/meter/shelly/types_test.go
@@ -44,14 +44,27 @@ func TestUnmarshalGen1StatusResponse(t *testing.T) {
}
}
-// Test Gen2StatusResponse response
+// Test Gen2+ status responses
func TestUnmarshalGen2StatusResponse(t *testing.T) {
- // Shelly Pro 1PM channel 0 (1)
- var res Gen2StatusResponse
+ {
+ // Shelly Pro 1PM channel 0 (1)
+ var res Gen2StatusResponse
- jsonstr := `{"ble":{},"cloud":{"connected":true},"eth":{"ip":null},"input:0":{"id":0,"state":false},"input:1":{"id":1,"state":false},"mqtt":{"connected":false},"switch:0":{"id":0, "source":"HTTP", "output":false, "apower":47.11, "voltage":232.0, "current":0.000, "pf":0.00, "aenergy":{"total":5.125,"by_minute":[0.000,0.000,0.000],"minute_ts":1675718520},"temperature":{"tC":25.3, "tF":77.5}},"sys":{"mac":"30C6F78BB4D8","restart_required":false,"time":"22:22","unixtime":1675718522,"uptime":45070,"ram_size":234204,"ram_free":137716,"fs_size":524288,"fs_free":172032,"cfg_rev":13,"kvs_rev":1,"schedule_rev":0,"webhook_rev":0,"available_updates":{"beta":{"version":"0.13.0-beta3"}}},"wifi":{"sta_ip":"192.168.178.64","status":"got ip","ssid":"***","rssi":-62},"ws":{"connected":false}}`
- require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
+ jsonstr := `{"ble":{},"cloud":{"connected":true},"eth":{"ip":null},"input:0":{"id":0,"state":false},"input:1":{"id":1,"state":false},"mqtt":{"connected":false},"switch:0":{"id":0, "source":"HTTP", "output":false, "apower":47.11, "voltage":232.0, "current":0.000, "pf":0.00, "aenergy":{"total":5.125,"by_minute":[0.000,0.000,0.000],"minute_ts":1675718520},"temperature":{"tC":25.3, "tF":77.5}},"sys":{"mac":"30C6F78BB4D8","restart_required":false,"time":"22:22","unixtime":1675718522,"uptime":45070,"ram_size":234204,"ram_free":137716,"fs_size":524288,"fs_free":172032,"cfg_rev":13,"kvs_rev":1,"schedule_rev":0,"webhook_rev":0,"available_updates":{"beta":{"version":"0.13.0-beta3"}}},"wifi":{"sta_ip":"192.168.178.64","status":"got ip","ssid":"***","rssi":-62},"ws":{"connected":false}}`
+ require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
- assert.Equal(t, 5.125, res.Switch0.Aenergy.Total)
- assert.Equal(t, 47.11, res.Switch0.Apower)
+ assert.Equal(t, 5.125, res.Switch0.Aenergy.Total)
+ assert.Equal(t, 47.11, res.Switch0.Apower)
+ }
+
+ {
+ // Shelly PM Mini Gen3 channel 0 (1)
+ var res Gen2StatusResponse
+
+ jsonstr := `{"ble":{},"cloud":{"connected":true},"mqtt":{"connected":false},"pm1:0":{"id":0, "voltage":239.9, "current":7.434, "apower":1780.1 ,"freq":50.1,"aenergy":{"total":3551.682,"by_minute":[15234.772,29611.247,29825.821],"minute_ts":1719917850},"ret_aenergy":{"total":0.000,"by_minute":[0.000,0.000,0.000],"minute_ts":1719917850}},"sys":{"mac":"84FCE638D818","restart_required":false,"time":"12:57","unixtime":1719917851,"uptime":62328,"ram_size":261744,"ram_free":151436,"fs_size":1048576,"fs_free":712704,"cfg_rev":10,"kvs_rev":1,"schedule_rev":0,"webhook_rev":0,"available_updates":{"stable":{"version":"1.3.3"}},"reset_reason":1},"wifi":{"sta_ip":"192.168.178.89","status":"got ip","ssid":"FritzBox 8 2.4","rssi":-62},"ws":{"connected":false}}`
+ require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
+
+ assert.Equal(t, 3551.682, res.Pm0.Aenergy.Total)
+ assert.Equal(t, 1780.1, res.Pm0.Apower)
+ }
}
diff --git a/meter/tq-em420.go b/meter/tq-em420.go
index 8567cc0b2d..8c05926f68 100644
--- a/meter/tq-em420.go
+++ b/meter/tq-em420.go
@@ -165,3 +165,11 @@ func (m *TqEM420) Currents() (float64, float64, float64, error) {
}
return res.SmartMeter.Values.CurrentL1 / 1e3, res.SmartMeter.Values.CurrentL2 / 1e3, res.SmartMeter.Values.CurrentL3 / 1e3, nil
}
+
+func (m *TqEM420) Voltages() (float64, float64, float64, error) {
+ res, err := m.dataG()
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ return res.SmartMeter.Values.VoltageL1 / 1e3, res.SmartMeter.Values.VoltageL2 / 1e3, res.SmartMeter.Values.VoltageL3 / 1e3, nil
+}
diff --git a/package-lock.json b/package-lock.json
index accb044ef5..a16124810b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9522,9 +9522,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
- "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
diff --git a/packaging/docker/bin/entrypoint.sh b/packaging/docker/bin/entrypoint.sh
index a346c7be94..ed4228b662 100755
--- a/packaging/docker/bin/entrypoint.sh
+++ b/packaging/docker/bin/entrypoint.sh
@@ -23,7 +23,7 @@ if [ -f ${HASSIO_OPTIONSFILE} ]; then
fi
fi
else
- if [ "$1" == '"evcc"' ] || expr "$1" : '-*' > /dev/null; then
+ if [ "$1" = '"evcc"' ] || expr "$1" : '-*' > /dev/null; then
exec evcc "$@"
else
exec "$@"
diff --git a/playwright.config.js b/playwright.config.js
index 6ee55f81ef..90b59f06c4 100644
--- a/playwright.config.js
+++ b/playwright.config.js
@@ -6,9 +6,9 @@ import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
- timeout: 15000, // 15s (default 30s)
- workers: process.env.CI ? 3 : 8,
+ retries: process.env.CI ? 3 : 0,
+ timeout: 30000, // default 30s
+ workers: process.env.CI ? 3 : 4,
reporter: "html",
use: {
baseURL: "http://127.0.0.1:7070",
diff --git a/provider/cache.go b/provider/cache.go
index 99b790b175..081903acc9 100644
--- a/provider/cache.go
+++ b/provider/cache.go
@@ -105,3 +105,39 @@ func (c *cached[T]) shouldRetryWithBackoff() bool {
return false
}
+
+// Value is a cacheable value that can expire
+type Value[T any] struct {
+ mux sync.RWMutex
+ clock clock.Clock
+ updated time.Time
+ cache time.Duration
+ val T
+}
+
+func NewValue[T any](cache time.Duration) *Value[T] {
+ return &Value[T]{
+ clock: clock.New(),
+ cache: cache,
+ }
+}
+
+func (v *Value[T]) Get() (T, error) {
+ v.mux.RLock()
+ defer v.mux.RUnlock()
+
+ if v.clock.Since(v.updated) > v.cache {
+ var zero T
+ return zero, api.ErrTimeout
+ }
+
+ return v.val, nil
+}
+
+func (v *Value[T]) Set(val T) {
+ v.mux.Lock()
+ defer v.mux.Unlock()
+
+ v.val = val
+ v.updated = v.clock.Now()
+}
diff --git a/provider/go.go b/provider/go.go
index d9e4610ac9..7cf096e14d 100644
--- a/provider/go.go
+++ b/provider/go.go
@@ -1,6 +1,7 @@
package provider
import (
+ "errors"
"fmt"
"reflect"
@@ -144,6 +145,10 @@ func (p *Go) handleGetter() (any, error) {
}
func (p *Go) handleSetter(param string, val any) error {
+ if err := transformInputs(p.in, p.setParam); err != nil {
+ return err
+ }
+
if err := p.setParam(param, val); err != nil {
return err
}
@@ -168,6 +173,10 @@ func (p *Go) evaluate() (res any, err error) {
return nil, err
}
+ if !v.IsValid() {
+ return nil, errors.New("missing result")
+ }
+
if (v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) && v.IsNil() {
return nil, nil
}
diff --git a/provider/helper.go b/provider/helper.go
index da9cd94881..94d2e8aed2 100644
--- a/provider/helper.go
+++ b/provider/helper.go
@@ -3,6 +3,7 @@ package provider
import (
"fmt"
+ "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
)
@@ -16,3 +17,17 @@ func setFormattedValue(message, param string, v interface{}) (string, error) {
param: v,
})
}
+
+// knownErrors maps string responses to known error codes
+func knownErrors(b []byte) error {
+ switch string(b) {
+ case "ErrAsleep":
+ return api.ErrAsleep
+ case "ErrMustRetry":
+ return api.ErrMustRetry
+ case "ErrNotAvailable":
+ return api.ErrNotAvailable
+ default:
+ return nil
+ }
+}
diff --git a/provider/http.go b/provider/http.go
index 6ee6f0eedc..b47c0b02dc 100644
--- a/provider/http.go
+++ b/provider/http.go
@@ -168,6 +168,11 @@ func (p *HTTP) request(url string, body string) ([]byte, error) {
}
p.val, p.err = p.DoBody(req)
+ if p.err != nil {
+ if err := knownErrors(p.val); err != nil {
+ p.err = err
+ }
+ }
p.updated = time.Now()
}
diff --git a/provider/javascript.go b/provider/javascript.go
index ca87eb4e4f..67f0338d15 100644
--- a/provider/javascript.go
+++ b/provider/javascript.go
@@ -125,6 +125,10 @@ func (p *Javascript) handleGetter() (any, error) {
}
func (p *Javascript) handleSetter(param string, val any) error {
+ if err := transformInputs(p.in, p.setParamSync); err != nil {
+ return err
+ }
+
javascript.Lock()
if err := p.setParam(param, val); err != nil {
javascript.Unlock()
diff --git a/provider/mqtt_handler.go b/provider/mqtt_handler.go
index 4597348517..acc11d3e56 100644
--- a/provider/mqtt_handler.go
+++ b/provider/mqtt_handler.go
@@ -27,6 +27,10 @@ func (h *msgHandler) hasValue() (string, error) {
return "", err
}
+ if err := knownErrors([]byte(payload)); err != nil {
+ return "", err
+ }
+
if h.pipeline != nil {
b, err := h.pipeline.Process([]byte(payload))
return string(b), err
diff --git a/provider/socket.go b/provider/socket.go
index 39b45f164c..f66171cda8 100644
--- a/provider/socket.go
+++ b/provider/socket.go
@@ -152,7 +152,15 @@ var _ StringProvider = (*Socket)(nil)
func (p *Socket) StringGetter() (func() (string, error), error) {
return func() (string, error) {
val, err := p.val.Get()
- return string(val), err
+ if err != nil {
+ return "", err
+ }
+
+ if err := knownErrors(val); err != nil {
+ return "", err
+ }
+
+ return string(val), nil
}, nil
}
diff --git a/provider/watchdog.go b/provider/watchdog.go
index 4048c3d7cb..27f1757cdf 100644
--- a/provider/watchdog.go
+++ b/provider/watchdog.go
@@ -126,3 +126,23 @@ func (o *watchdogProvider) FloatSetter(param string) (func(float64) error, error
return setter(o, set, reset), nil
}
+
+var _ SetBoolProvider = (*watchdogProvider)(nil)
+
+func (o *watchdogProvider) BoolSetter(param string) (func(bool) error, error) {
+ set, err := NewBoolSetterFromConfig(param, o.set)
+ if err != nil {
+ return nil, err
+ }
+
+ var reset *bool
+ if o.reset != nil {
+ val, err := strconv.ParseBool(*o.reset)
+ if err != nil {
+ return nil, err
+ }
+ reset = &val
+ }
+
+ return setter(o, set, reset), nil
+}
diff --git a/push/hub.go b/push/hub.go
index 1b141da56f..1376a4218d 100644
--- a/push/hub.go
+++ b/push/hub.go
@@ -38,10 +38,10 @@ type Hub struct {
func NewHub(cc map[string]EventTemplateConfig, vv Vehicles, cache *util.Cache) (*Hub, error) {
// instantiate all event templates
for k, v := range cc {
- if _, err := template.New("out").Funcs(sprout.TxtFuncMap()).Parse(v.Title); err != nil {
+ if _, err := template.New("out").Funcs(sprout.FuncMap()).Parse(v.Title); err != nil {
return nil, fmt.Errorf("invalid event title: %s (%w)", k, err)
}
- if _, err := template.New("out").Funcs(sprout.TxtFuncMap()).Parse(v.Msg); err != nil {
+ if _, err := template.New("out").Funcs(sprout.FuncMap()).Parse(v.Msg); err != nil {
return nil, fmt.Errorf("invalid event message: %s (%w)", k, err)
}
}
diff --git a/server/db/settings/setting.go b/server/db/settings/setting.go
index d0d1d81297..2cf8d68516 100644
--- a/server/db/settings/setting.go
+++ b/server/db/settings/setting.go
@@ -5,6 +5,7 @@ import (
"cmp"
"encoding/json"
"errors"
+ "fmt"
"io"
"reflect"
"slices"
@@ -197,10 +198,19 @@ func Json(key string, res any) error {
func DecodeOtherSliceOrMap(other, res any) error {
var len int
- if typ := reflect.TypeOf(other); typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map {
- len = reflect.ValueOf(other).Len()
+
+ val := reflect.ValueOf(other)
+ typ := reflect.TypeOf(other)
+
+ if typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ val = reflect.Indirect(val)
+ }
+
+ if typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map {
+ len = val.Len()
} else {
- panic("invalid kind: " + typ.Kind().String())
+ return fmt.Errorf("cannot decode into slice or map: %v", other)
}
if len == 0 {
diff --git a/server/eebus/certificate.go b/server/eebus/certificate.go
new file mode 100644
index 0000000000..32509c818d
--- /dev/null
+++ b/server/eebus/certificate.go
@@ -0,0 +1,77 @@
+package eebus
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "fmt"
+
+ "github.com/enbility/ship-go/cert"
+)
+
+// CreateCertificate returns a newly created EEBUS compatible certificate
+func CreateCertificate() (tls.Certificate, error) {
+ return cert.CreateCertificate("", BrandName, "DE", DeviceCode)
+}
+
+// pemBlockForKey marshals private key into pem block
+func pemBlockForKey(priv interface{}) (*pem.Block, error) {
+ switch k := priv.(type) {
+ case *rsa.PrivateKey:
+ return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+ case *ecdsa.PrivateKey:
+ b, err := x509.MarshalECPrivateKey(k)
+ if err != nil {
+ return nil, fmt.Errorf("unable to marshal ECDSA private key: %w", err)
+ }
+ return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
+ default:
+ return nil, errors.New("unknown private key type")
+ }
+}
+
+// GetX509KeyPair saves returns the cert and key string values
+func GetX509KeyPair(cert tls.Certificate) (string, string, error) {
+ var certValue, keyValue string
+
+ out := new(bytes.Buffer)
+ err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
+ if err == nil {
+ certValue = out.String()
+ }
+
+ if len(certValue) > 0 {
+ var pb *pem.Block
+ if pb, err = pemBlockForKey(cert.PrivateKey); err == nil {
+ out.Reset()
+ err = pem.Encode(out, pb)
+ }
+ }
+
+ if err == nil {
+ keyValue = out.String()
+ }
+
+ return certValue, keyValue, err
+}
+
+// SkiFromX509 extracts SKI from certificate
+func skiFromX509(leaf *x509.Certificate) (string, error) {
+ if len(leaf.SubjectKeyId) == 0 {
+ return "", errors.New("missing SubjectKeyId")
+ }
+ return fmt.Sprintf("%0x", leaf.SubjectKeyId), nil
+}
+
+// SkiFromCert extracts SKI from certificate
+func SkiFromCert(cert tls.Certificate) (string, error) {
+ leaf, err := x509.ParseCertificate(cert.Certificate[0])
+ if err != nil {
+ return "", errors.New("failed parsing certificate: " + err.Error())
+ }
+ return skiFromX509(leaf)
+}
diff --git a/server/eebus/connector.go b/server/eebus/connector.go
new file mode 100644
index 0000000000..7ea7ce0a68
--- /dev/null
+++ b/server/eebus/connector.go
@@ -0,0 +1,53 @@
+package eebus
+
+import (
+ "sync"
+ "time"
+
+ "github.com/evcc-io/evcc/api"
+)
+
+type Connector struct {
+ cb func(connected bool)
+
+ mu sync.RWMutex
+ once sync.Once
+ connected bool
+ connectC chan struct{}
+}
+
+func NewConnector(cb func(connected bool)) *Connector {
+ return &Connector{
+ cb: cb,
+ connectC: make(chan struct{}),
+ }
+}
+
+func (c *Connector) Wait(timeout time.Duration) error {
+ select {
+ case <-time.After(timeout):
+ return api.ErrTimeout
+ case <-c.connectC:
+ return nil
+ }
+}
+
+func (c *Connector) Connect(connected bool) {
+ c.mu.Lock()
+ c.connected = connected
+ c.mu.Unlock()
+
+ if connected {
+ c.once.Do(func() { close(c.connectC) })
+ }
+
+ if c.cb != nil {
+ c.cb(connected)
+ }
+}
+
+func (c *Connector) Connected() bool {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.connected
+}
diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go
new file mode 100644
index 0000000000..8598deb53b
--- /dev/null
+++ b/server/eebus/eebus.go
@@ -0,0 +1,305 @@
+package eebus
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+
+ "dario.cat/mergo"
+ eebusapi "github.com/enbility/eebus-go/api"
+ service "github.com/enbility/eebus-go/service"
+ ucapi "github.com/enbility/eebus-go/usecases/api"
+ "github.com/enbility/eebus-go/usecases/cem/evcc"
+ "github.com/enbility/eebus-go/usecases/cem/evcem"
+ "github.com/enbility/eebus-go/usecases/cem/evsecc"
+ "github.com/enbility/eebus-go/usecases/cem/evsoc"
+ "github.com/enbility/eebus-go/usecases/cem/opev"
+ "github.com/enbility/eebus-go/usecases/cem/oscev"
+ "github.com/enbility/eebus-go/usecases/cs/lpc"
+ "github.com/enbility/eebus-go/usecases/cs/lpp"
+ "github.com/enbility/eebus-go/usecases/ma/mgcp"
+ shipapi "github.com/enbility/ship-go/api"
+ "github.com/enbility/ship-go/mdns"
+ shiputil "github.com/enbility/ship-go/util"
+ spineapi "github.com/enbility/spine-go/api"
+ "github.com/enbility/spine-go/model"
+ "github.com/evcc-io/evcc/util"
+ "github.com/evcc-io/evcc/util/machine"
+)
+
+type Device interface {
+ Connect(connected bool)
+ UseCaseEvent(device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType)
+}
+
+// EVSE UseCases
+type UseCasesEVSE struct {
+ EvseCC ucapi.CemEVSECCInterface
+ EvCC ucapi.CemEVCCInterface
+ EvCem ucapi.CemEVCEMInterface
+ EvSoc ucapi.CemEVSOCInterface
+ OpEV ucapi.CemOPEVInterface
+ OscEV ucapi.CemOSCEVInterface
+}
+type UseCasesCS struct {
+ LPC ucapi.CsLPCInterface
+ LPP ucapi.CsLPPInterface
+ MGCP ucapi.MaMGCPInterface
+}
+
+type EEBus struct {
+ service eebusapi.ServiceInterface
+
+ evseUC UseCasesEVSE
+ csUC UseCasesCS
+
+ mux sync.Mutex
+ log *util.Logger
+
+ SKI string
+
+ clients map[string][]Device
+}
+
+var Instance *EEBus
+
+func NewServer(other Config) (*EEBus, error) {
+ cc := Config{
+ URI: ":4712",
+ }
+
+ if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil {
+ return nil, err
+ }
+
+ log := util.NewLogger("eebus")
+
+ protectedID, err := machine.ProtectedID("evcc-eebus")
+ if err != nil {
+ return nil, err
+ }
+ serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
+
+ if len(cc.ShipID) != 0 {
+ serial = cc.ShipID
+ }
+
+ certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private))
+ if err != nil {
+ return nil, err
+ }
+
+ _, portValue, err := net.SplitHostPort(cc.URI)
+ if err != nil {
+ return nil, err
+ }
+
+ port, err := strconv.Atoi(portValue)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: get the voltage from the site
+ configuration, err := eebusapi.NewConfiguration(
+ BrandName, BrandName, Model, serial,
+ model.DeviceTypeTypeEnergyManagementSystem,
+ []model.EntityTypeType{model.EntityTypeTypeCEM},
+ port, certificate, time.Second*4,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // use avahi if available, otherwise use go based zeroconf
+ configuration.SetMdnsProviderSelection(mdns.MdnsProviderSelectionAll)
+
+ // for backward compatibility
+ configuration.SetAlternateMdnsServiceName(DeviceCode)
+ configuration.SetAlternateIdentifier(serial)
+ configuration.SetInterfaces(cc.Interfaces)
+
+ ski, err := SkiFromCert(certificate)
+ if err != nil {
+ return nil, err
+ }
+
+ c := &EEBus{
+ log: log,
+ SKI: ski,
+ clients: make(map[string][]Device),
+ }
+
+ c.service = service.NewService(configuration, c)
+ c.service.SetLogging(c)
+ if err := c.service.Setup(); err != nil {
+ return nil, err
+ }
+
+ localEntity := c.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
+
+ // evse
+ c.evseUC = UseCasesEVSE{
+ EvseCC: evsecc.NewEVSECC(localEntity, c.ucCallback),
+ EvCC: evcc.NewEVCC(c.service, localEntity, c.ucCallback),
+ EvCem: evcem.NewEVCEM(c.service, localEntity, c.ucCallback),
+ OpEV: opev.NewOPEV(localEntity, c.ucCallback),
+ OscEV: oscev.NewOSCEV(localEntity, c.ucCallback),
+ EvSoc: evsoc.NewEVSOC(localEntity, c.ucCallback),
+ }
+
+ // controllable system
+ c.csUC = UseCasesCS{
+ LPC: lpc.NewLPC(localEntity, c.ucCallback),
+ LPP: lpp.NewLPP(localEntity, c.ucCallback),
+ MGCP: mgcp.NewMGCP(localEntity, c.ucCallback),
+ }
+
+ // register use cases
+ for _, uc := range []eebusapi.UseCaseInterface{
+ c.evseUC.EvseCC, c.evseUC.EvCC,
+ c.evseUC.EvCem, c.evseUC.OpEV,
+ c.evseUC.OscEV, c.evseUC.EvSoc,
+ c.csUC.LPC, c.csUC.LPP, c.csUC.MGCP,
+ } {
+ c.service.AddUseCase(uc)
+ }
+
+ return c, nil
+}
+
+func (c *EEBus) RegisterDevice(ski string, device Device) error {
+ ski = shiputil.NormalizeSKI(ski)
+ c.log.TRACE.Printf("registering ski: %s", ski)
+
+ if ski == c.SKI {
+ return errors.New("device ski can not be identical to host ski")
+ }
+
+ c.service.RegisterRemoteSKI(ski)
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+ c.clients[ski] = append(c.clients[ski], device)
+
+ return nil
+}
+
+func (c *EEBus) Evse() *UseCasesEVSE {
+ return &c.evseUC
+}
+
+func (c *EEBus) ControllableSystem() *UseCasesCS {
+ return &c.csUC
+}
+
+func (c *EEBus) Run() {
+ c.service.Start()
+}
+
+func (c *EEBus) Shutdown() {
+ c.service.Shutdown()
+}
+
+// Use case callback
+func (c *EEBus) ucCallback(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.log.DEBUG.Printf("ski %s event %s", ski, event)
+
+ if clients, ok := c.clients[ski]; ok {
+ for _, client := range clients {
+ client.UseCaseEvent(device, entity, event)
+ }
+ }
+}
+
+// EEBUSServiceHandler
+
+func (c *EEBus) connect(ski string, connected bool) {
+ action := map[bool]string{true: "connected", false: "disconnected"}[connected]
+ c.log.DEBUG.Printf("ski %s %s", ski, action)
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ if clients, ok := c.clients[ski]; ok {
+ for _, client := range clients {
+ client.Connect(connected)
+ }
+ }
+}
+
+func (c *EEBus) RemoteSKIConnected(service eebusapi.ServiceInterface, ski string) {
+ c.connect(ski, true)
+}
+
+func (c *EEBus) RemoteSKIDisconnected(service eebusapi.ServiceInterface, ski string) {
+ c.connect(ski, false)
+}
+
+// report all currently visible EEBUS services
+// this is needed to provide an UI for pairing with other devices
+// if not all incoming pairing requests should be accepted
+func (c *EEBus) VisibleRemoteServicesUpdated(service eebusapi.ServiceInterface, entries []shipapi.RemoteService) {
+}
+
+// Provides the SHIP ID the remote service reported during the handshake process
+// This needs to be persisted and passed on for future remote service connections
+// when using `PairRemoteService`
+func (c *EEBus) ServiceShipIDUpdate(ski string, shipdID string) {}
+
+// Provides the current pairing state for the remote service
+// This is called whenever the state changes and can be used to
+// provide user information for the pairing/connection process
+func (c *EEBus) ServicePairingDetailUpdate(ski string, detail *shipapi.ConnectionStateDetail) {
+ if detail.State() != shipapi.ConnectionStateReceivedPairingRequest {
+ return
+ }
+
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ if clients, ok := c.clients[ski]; !ok || len(clients) == 0 {
+ // this is an unknown SKI, so deny pairing
+ c.service.CancelPairingWithSKI(ski)
+ }
+}
+
+// EEBUS Logging interface
+
+func (c *EEBus) Trace(args ...interface{}) {
+ c.log.TRACE.Println(args...)
+}
+
+func (c *EEBus) Tracef(format string, args ...interface{}) {
+ c.log.TRACE.Printf(format, args...)
+}
+
+func (c *EEBus) Debug(args ...interface{}) {
+ c.log.DEBUG.Println(args...)
+}
+
+func (c *EEBus) Debugf(format string, args ...interface{}) {
+ c.log.DEBUG.Printf(format, args...)
+}
+
+func (c *EEBus) Info(args ...interface{}) {
+ c.log.INFO.Println(args...)
+}
+
+func (c *EEBus) Infof(format string, args ...interface{}) {
+ c.log.INFO.Printf(format, args...)
+}
+
+func (c *EEBus) Error(args ...interface{}) {
+ c.log.ERROR.Println(args...)
+}
+
+func (c *EEBus) Errorf(format string, args ...interface{}) {
+ c.log.ERROR.Printf(format, args...)
+}
diff --git a/charger/eebus/eebus_test.go b/server/eebus/eebus_test.go
similarity index 100%
rename from charger/eebus/eebus_test.go
rename to server/eebus/eebus_test.go
diff --git a/server/eebus/types.go b/server/eebus/types.go
new file mode 100644
index 0000000000..6ad1248033
--- /dev/null
+++ b/server/eebus/types.go
@@ -0,0 +1,21 @@
+package eebus
+
+const (
+ BrandName string = "EVCC"
+ Model string = "HEMS"
+ DeviceCode string = "EVCC_HEMS_01" // used as common name in cert generation
+)
+
+type Config struct {
+ URI string
+ ShipID string
+ Interfaces []string
+ Certificate struct {
+ Public, Private string
+ }
+}
+
+// Configured returns true if the EEbus server is configured
+func (c Config) Configured() bool {
+ return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0
+}
diff --git a/server/http.go b/server/http.go
index c9b9332504..2573bac71b 100644
--- a/server/http.go
+++ b/server/http.go
@@ -7,10 +7,10 @@ import (
eapi "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/api/globalconfig"
- "github.com/evcc-io/evcc/charger/eebus"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/server/assets"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/auth"
"github.com/evcc-io/evcc/util/config"
@@ -245,12 +245,12 @@ func (s *HTTPd) RegisterSystemHandler(valueChan chan<- util.Param, cache *util.C
// yaml handlers
for key, fun := range map[string]func() (any, any){
- keys.EEBus: func() (any, any) { return new(map[string]any), eebus.Config{} },
- keys.Hems: func() (any, any) { return new(map[string]any), config.Typed{} },
- keys.Tariffs: func() (any, any) { return new(map[string]any), globalconfig.Tariffs{} },
- keys.Messaging: func() (any, any) { return new(map[string]any), globalconfig.Messaging{} }, // has default
- keys.ModbusProxy: func() (any, any) { return new([]map[string]any), []globalconfig.ModbusProxy{} }, // slice
- keys.Circuits: func() (any, any) { return new([]map[string]any), []config.Named{} }, // slice
+ keys.EEBus: func() (any, any) { return map[string]any{}, eebus.Config{} },
+ keys.Hems: func() (any, any) { return map[string]any{}, config.Typed{} },
+ keys.Tariffs: func() (any, any) { return map[string]any{}, globalconfig.Tariffs{} },
+ keys.Messaging: func() (any, any) { return map[string]any{}, globalconfig.Messaging{} }, // has default
+ keys.ModbusProxy: func() (any, any) { return []map[string]any{}, []globalconfig.ModbusProxy{} }, // slice
+ keys.Circuits: func() (any, any) { return []map[string]any{}, []config.Named{} }, // slice
} {
other, struc := fun()
routes[key] = route{Method: "GET", Pattern: "/" + key, HandlerFunc: settingsGetStringHandler(key)}
diff --git a/server/http_config_device_handler.go b/server/http_config_device_handler.go
index a28cc5502c..fabc97117b 100644
--- a/server/http_config_device_handler.go
+++ b/server/http_config_device_handler.go
@@ -8,7 +8,7 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger"
- "github.com/evcc-io/evcc/core"
+ "github.com/evcc-io/evcc/core/circuit"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
@@ -233,7 +233,7 @@ func newDeviceHandler(w http.ResponseWriter, r *http.Request) {
case templates.Circuit:
conf, err = newDevice(class, req, func(_ string, other map[string]interface{}) (api.Circuit, error) {
- return core.NewCircuitFromConfig(util.NewLogger("circuit"), other)
+ return circuit.NewFromConfig(util.NewLogger("circuit"), other)
}, config.Circuits())
}
@@ -304,7 +304,7 @@ func updateDeviceHandler(w http.ResponseWriter, r *http.Request) {
case templates.Circuit:
err = updateDevice(id, class, req, func(_ string, other map[string]interface{}) (api.Circuit, error) {
- return core.NewCircuitFromConfig(util.NewLogger("circuit"), other)
+ return circuit.NewFromConfig(util.NewLogger("circuit"), other)
}, config.Circuits())
}
diff --git a/server/http_config_metadata_handler.go b/server/http_config_metadata_handler.go
index b47c320b98..a588870011 100644
--- a/server/http_config_metadata_handler.go
+++ b/server/http_config_metadata_handler.go
@@ -33,7 +33,20 @@ func templatesHandler(w http.ResponseWriter, r *http.Request) {
return
}
- jsonResult(w, templates.ByClass(class))
+ // filter deprecated properties
+ res := make([]templates.Template, 0)
+ for _, t := range templates.ByClass(class) {
+ params := make([]templates.Param, 0, len(t.Params))
+ for _, p := range t.Params {
+ if p.Deprecated == nil || !*p.Deprecated {
+ params = append(params, p)
+ }
+ }
+ t.Params = params
+ res = append(res, t)
+ }
+
+ jsonResult(w, res)
}
// productsHandler returns the list of products by class
diff --git a/tariff/elering.go b/tariff/elering.go
index 91ba8b53e5..4b87203caf 100644
--- a/tariff/elering.go
+++ b/tariff/elering.go
@@ -45,7 +45,7 @@ func NewEleringFromConfig(other map[string]interface{}) (api.Tariff, error) {
t := &Elering{
embed: &cc.embed,
- log: util.NewLogger("Elering"),
+ log: util.NewLogger("elering"),
region: strings.ToLower(cc.Region),
data: util.NewMonitor[api.Rates](2 * time.Hour),
}
diff --git a/tariff/template_test.go b/tariff/template_test.go
index 429efad99a..427d880452 100644
--- a/tariff/template_test.go
+++ b/tariff/template_test.go
@@ -12,6 +12,8 @@ var acceptable = []string{
"invalid zipcode", // grünstromindex
"invalid apikey format", // octopusenergy
"missing region", // octopusenergy
+ "missing securitytoken", // entsoe
+ "cannot define region and postcode simultaneously", // ngeso
}
func TestTemplates(t *testing.T) {
diff --git a/templates/definition/charger/ac-elwa-2.yaml b/templates/definition/charger/ac-elwa-2.yaml
new file mode 100644
index 0000000000..8d9060ee61
--- /dev/null
+++ b/templates/definition/charger/ac-elwa-2.yaml
@@ -0,0 +1,60 @@
+template: ac-elwa-2
+products:
+ - brand: my-PV
+ description:
+ generic: AC ELWA 2
+params:
+ - name: modbus
+ choice: ["tcpip"]
+render: |
+ type: custom
+ enable:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 1000
+ type: writeholding
+ decode: uint16
+ enabled:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 1000
+ type: holding
+ decode: uint16
+ maxcurrent:
+ source: go
+ script: |
+ 230 * maxcurrent
+ out:
+ - name: power
+ type: int
+ config:
+ source: modbus
+ {{- include "modbus" . | indent 8 }}
+ register:
+ address: 1000
+ type: writeholding
+ decode: uint16
+ status:
+ source: combined
+ plugged:
+ source: const
+ value: 1
+ charging:
+ source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 1074 # alternativ 1003 mit bit pattern
+ type: holding
+ decode: uint16
+ soc:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 1001
+ type: holding
+ decode: uint16
+ scale: 0.1
+ features: ["integrateddevice", "heating"]
+ icon: waterheater
diff --git a/templates/definition/charger/elli-charger-connect.yaml b/templates/definition/charger/elli-charger-connect.yaml
index 3ee39124b4..b593a52905 100644
--- a/templates/definition/charger/elli-charger-connect.yaml
+++ b/templates/definition/charger/elli-charger-connect.yaml
@@ -37,7 +37,5 @@ requirements:
Note: If you've added an energy meter to your charger please use the Pro or Connected+ integration.
params:
- preset: eebus
- - name: ip
- required: true
render: |
{{ include "eebus" . }}
diff --git a/templates/definition/charger/elli-charger-pro.yaml b/templates/definition/charger/elli-charger-pro.yaml
index d89ff78213..d46854e590 100644
--- a/templates/definition/charger/elli-charger-pro.yaml
+++ b/templates/definition/charger/elli-charger-pro.yaml
@@ -33,8 +33,6 @@ requirements:
Important: A mostly flawless functionality can only be provided with an external energy meter and no usage of CT coils, due to sosftware bugs of the Wallbox. Using a LAN connection is highly recommended.
params:
- preset: eebus
- - name: ip
- required: true
render: |
{{ include "eebus" . }}
meter: true
diff --git a/templates/definition/charger/em2go-home.yaml b/templates/definition/charger/em2go-home.yaml
index f3a45fb3a8..2334fa2b5a 100644
--- a/templates/definition/charger/em2go-home.yaml
+++ b/templates/definition/charger/em2go-home.yaml
@@ -3,7 +3,7 @@ products:
- brand: EM2GO
description:
generic: Home
-capabilities: ["1p3p", "mA"]
+capabilities: ["1p3p"]
requirements:
description:
de: "Benötigt Firmware version E3C_V1.1 oder neuer."
diff --git a/templates/definition/charger/fronius-wattpilot.yaml b/templates/definition/charger/fronius-wattpilot.yaml
index e47aba0caa..90f7eac343 100644
--- a/templates/definition/charger/fronius-wattpilot.yaml
+++ b/templates/definition/charger/fronius-wattpilot.yaml
@@ -10,7 +10,6 @@ requirements:
Benötigt mindestens Firmware 36.3 oder neuer.
en: |
Requires firmware 36.3 or later.
- uri: https://docs.evcc.io/docs/devices/chargers#fronius-wattpilot
params:
- name: host
- name: password
diff --git a/templates/definition/charger/go-e-v3.yaml b/templates/definition/charger/go-e-v3.yaml
index 4b1308384f..af427d079b 100644
--- a/templates/definition/charger/go-e-v3.yaml
+++ b/templates/definition/charger/go-e-v3.yaml
@@ -19,7 +19,6 @@ requirements:
For 1P/3P-Phase switching the HTTP API v2 in the charger setup needs to be activated.
The “simulate unplugging” option should be activated in the Go-E app ("Car" menu item).
- uri: https://docs.evcc.io/docs/devices/chargers#go-e
evcc: ["sponsorship"]
params:
- name: host
diff --git a/templates/definition/charger/keba-modbus.yaml b/templates/definition/charger/keba-modbus.yaml
index 5468c050b2..11cfb4829b 100644
--- a/templates/definition/charger/keba-modbus.yaml
+++ b/templates/definition/charger/keba-modbus.yaml
@@ -19,6 +19,11 @@ params:
- name: modbus
choice: ["tcpip"]
id: 255
+ - name: welcomecharge
+ advanced: true
render: |
type: keba-modbus
{{- include "modbus" . }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/charger/mennekes-compact.yaml b/templates/definition/charger/mennekes-compact.yaml
index 6e4b6a0e4b..1d72ca0932 100644
--- a/templates/definition/charger/mennekes-compact.yaml
+++ b/templates/definition/charger/mennekes-compact.yaml
@@ -7,11 +7,20 @@ products:
- brand: Mennekes
description:
generic: Amtron Start 2.0s
+ - brand: Kostal
+ description:
+ generic: Enector
capabilities: ["1p3p", "mA"]
requirements:
description:
- de: Die Wallbox muss mit Hilfe der DIP-Schalter auf der Hauptplatine als Satellit konfiguriert werden.
- en: The charger needs to be configured as Satellite with help of the DIP-Switches on the baseboard.
+ de: |
+ Die Wallbox muss mit Hilfe der DIP-Schalter auf der Hauptplatine als Satellit/Slave konfiguriert werden und Modbus RTU aktiviert sein (Bank S1: 4=ON, 5=ON, 7=OFF).
+ Es sollte kein externes Meter direkt mit der Wallbox verbunden sein, da die Steuerung aller Funktionen direkt durch evcc erfolgt.
+ Bei Kostal-Systemen mit Smart Energy Meter (KSEM) ist der zusätzliche Aktivierungscode (Solar Pure Mode / Solar Plus Mode) für das KSEM *nicht* erforderlich.
+ en: |
+ The wallbox must be configured as satellite/slave using the DIP switches on the mainboard and Modbus RTU must be enabled (bank S1: 4=ON, 5=ON, 7=OFF).
+ No external meter should be connected directly to the wallbox, as all functions are controlled directly by evcc.
+ For Kostal systems with Smart Energy Meter (KSEM), the additional activation code (Solar Pure Mode / Solar Plus Mode) for the KSEM is *not* required.
evcc: ["sponsorship"]
params:
- name: modbus
diff --git a/templates/definition/charger/nrgkick-bluetooth.yaml b/templates/definition/charger/nrgkick-bluetooth.yaml
index e9b1e57631..b5fda55149 100644
--- a/templates/definition/charger/nrgkick-bluetooth.yaml
+++ b/templates/definition/charger/nrgkick-bluetooth.yaml
@@ -1,4 +1,5 @@
template: nrgkick-bluetooth
+deprecated: true
products:
- brand: NRGKick
description:
diff --git a/templates/definition/charger/obo.yaml b/templates/definition/charger/obo.yaml
index 0f8863ce30..dc8e44bac2 100644
--- a/templates/definition/charger/obo.yaml
+++ b/templates/definition/charger/obo.yaml
@@ -1,8 +1,14 @@
template: obo
products:
+ - brand: EcoHarmony
+ description:
+ generic: EVSE EPC 2.0 Plus
- brand: OBO Bettermann
description:
generic: Ion
+ - brand: Viridian EV
+ description:
+ generic: EVSE EPC 2.0 Plus
params:
- name: modbus
choice: ["rs485", "tcpip"]
diff --git a/templates/definition/charger/ocpp.yaml b/templates/definition/charger/ocpp.yaml
index cc677cb45e..12a14a2bb0 100644
--- a/templates/definition/charger/ocpp.yaml
+++ b/templates/definition/charger/ocpp.yaml
@@ -12,8 +12,11 @@ requirements:
Standardmäßig wird die erste eingehende Verbindung mit einer beliebigen Ladepunktkennung verwendet.
Um mehrere Ladepunkte eindeutig zuordnen zu können müssen die jeweilige Stationskennung (`stationid: `) und Anschlussnummer (`connector: `) hinterlegt werden.
Viele Wallboxen fügen die `stationid` automatisch der Backend-URL hinzu, bei manchen muss dies händisch geschehen `ws://:8887/`.
- Gegebenenfalls benötigt der Ladepunkt eine vorkonfigurierte (virtuelle) Token-ID/RFID-Kennung (`idtag: `) mit der die Ladevorgänge ohne Authentifizierung gestartet werden können.
Für Zählermesswerte sollte in der Wallbox ein kurzes Zeitintervall konfiguriert werden.
+ Nutzen Sie Ihre RFID-Tags (dies ermöglicht z.B. eine Fahrzeugidentifizierung) oder setzen Sie Ihre Wallbox auf "freies Laden" oder "Autostart" um die jeweils für die Ladefreigabe benötigte Transaktion zu erzeugen.
+
+ Falls die Wallbox keinerlei Option bietet die Transaktionen lokal zu starten, kann die Option `remotetransaction` genutzt werden um automatisch eine Transaktion zu starten sobald ein Ladekabel angeschlossen wird.
+ Dies sollte nur in Ausnahmefällen erforderlich sein.
Voraussetzungen:
* Ggf. zuvor konfigurierte OCPP-Profile (z.B. durch eine andere Backend-Anbindung) in der Wallboxkonfiguration entfernen
@@ -27,8 +30,11 @@ requirements:
By default, the first incoming connection with any station identifier is used.
In order to be able to clearly assign several charging points, the respective station identifier (`stationid: `) and connector number (`connector: `) must be configured.
Many wallboxes automatically add the `station id` to the backend URL, some have to do this manually `ws://:8887/`.
- The charger may need a pre-configured (virtual) token ID/RFID identifier (`idtag: `) with which the charging sessions can be started without authorization.
If the charger supports sending metering values try to adjust the interval to a short time span.
+ Use your RFID tags (this allows e.g. vehicle identification) or set your charger to "free charging" or "autostart" to generate the transaction required for charging release.
+
+ If the charger does not offer any option to start transactions locally, the `remotetransaction` option can be used to automatically start a transaction as soon as a cable is connected.
+ This should only be necessary in exceptional cases.
Requirements:
* If necessary, remove previously configured OCPP profiles (e.g. used for a different backend connection) in the charger configuration
@@ -38,6 +44,20 @@ requirements:
* Local network connection
params:
- preset: ocpp
+ - name: autostart
+ deprecated: true
+ - name: nostop
+ deprecated: true
+ - name: remotestart
+ advanced: true
+ type: bool
+ default: false
+ description:
+ de: Automatisch eine für die Ladefreigabe benötigte Transaktion starten (RemoteStartTransaction) sobald ein Ladekabel mit der Wallbox verbunden wird
+ en: Automatically start a transaction required for charging release (RemoteStartTransaction) as soon as a cable is connected to the charger
+ help:
+ de: Nur verwenden wenn keinerlei Möglichkeit besteht Transaktionen seitens des Ladepunktes zu starten
+ en: Only use if there is no other way to start transactions from the charger
- name: getconfiguration
advanced: true
type: bool
@@ -62,14 +82,14 @@ params:
advanced: true
type: duration
description:
- de: Zählerwerte nach Intervall anfordern
- en: Interval for requesting meter values
+ de: Gewünschtes Übertragungsintervall der Zählerwerte
+ en: Desired transmission interval of meter values
- name: metervalues
advanced: true
type: string
description:
- de: Liste der Zählerwerte
- en: List of meter values
+ de: Kommaseparierte Liste der zu sendenden Zählerwerte ("measurand")
+ en: Comma-separated list of meter values to send ("measurand")
- name: chargingrateunit
advanced: true
type: string
@@ -78,6 +98,7 @@ params:
en: Unit for setting ChargingProfile values ("W" or "A")
render: |
{{ include "ocpp" . }}
+ remotestart: {{ .remotestart }}
{{- if ne .getconfiguration "true" }}
getconfiguration: {{ .getconfiguration }}
{{- end }}
diff --git a/templates/definition/charger/openwb.yaml b/templates/definition/charger/openwb.yaml
index 95b36f8989..bcc1e26daf 100644
--- a/templates/definition/charger/openwb.yaml
+++ b/templates/definition/charger/openwb.yaml
@@ -7,7 +7,6 @@ requirements:
description:
en: The wallbox has to be configured as loadpoint.
de: Die Wallbox muss als Ladepunkt konfiguriert sein.
- uri: https://docs.evcc.io/docs/devices/chargers#openwb
params:
- name: host
- name: connector
diff --git a/templates/definition/charger/porsche-pmcc.yaml b/templates/definition/charger/porsche-pmcc.yaml
index 9d1c84b3ec..05a766b822 100644
--- a/templates/definition/charger/porsche-pmcc.yaml
+++ b/templates/definition/charger/porsche-pmcc.yaml
@@ -11,3 +11,4 @@ params:
render: |
{{ include "eebus" . }}
meter: true
+ vasvw: true
diff --git a/templates/definition/charger/tinkerforge-warp.yaml b/templates/definition/charger/tinkerforge-warp.yaml
index a294e13ad6..ec9375e632 100644
--- a/templates/definition/charger/tinkerforge-warp.yaml
+++ b/templates/definition/charger/tinkerforge-warp.yaml
@@ -13,7 +13,6 @@ requirements:
description:
en: WARP Firmware v2 required. Automatic phase switching requires the additional WARP Energy Manager.
de: WARP Firmware v2 erforderlich. Für automatische Phasenumschaltung wird zusätzlich der WARP Energy Manager benötigt.
- uri: https://docs.evcc.io/docs/devices/chargers#tinkerforge
params:
- preset: mqtt
- name: topic
diff --git a/templates/definition/charger/tinkerforge-warp3.yaml b/templates/definition/charger/tinkerforge-warp3.yaml
index 0df4dd20a1..45cc88c8aa 100644
--- a/templates/definition/charger/tinkerforge-warp3.yaml
+++ b/templates/definition/charger/tinkerforge-warp3.yaml
@@ -8,7 +8,9 @@ products:
generic: WARP3 Charger Pro
capabilities: ["mA", "1p3p", "rfid"]
requirements:
- uri: https://docs.evcc.io/docs/devices/chargers#tinkerforge
+ description:
+ de: Die automatische Phasenumschaltung bei 1p Fahrzeugen muss deaktiviert sein. Siehe 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. Siehe https://docs.warp-charger.com/docs/mqtt_http/api_reference/evse#evse_phase_auto_switch_warp3.
params:
- preset: mqtt
- name: topic
diff --git a/templates/definition/charger/wallbe-meter.yaml b/templates/definition/charger/wallbe-meter.yaml
index e76915e39c..bc487678ba 100644
--- a/templates/definition/charger/wallbe-meter.yaml
+++ b/templates/definition/charger/wallbe-meter.yaml
@@ -9,7 +9,6 @@ requirements:
description:
en: DIP switch 10 must be set to 'ON'.
de: Im Gerät muss der DIP Schalter 10 auf 'ON' gestellt sein.
- uri: https://docs.evcc.io/docs/devices/chargers#wallbe
params:
- name: host
- name: port
diff --git a/templates/definition/charger/wallbe-pre2019-meter.yaml b/templates/definition/charger/wallbe-pre2019-meter.yaml
index 26c6dbd657..bd7b62ebe7 100644
--- a/templates/definition/charger/wallbe-pre2019-meter.yaml
+++ b/templates/definition/charger/wallbe-pre2019-meter.yaml
@@ -9,7 +9,6 @@ requirements:
description:
en: DIP switch 10 must be set to 'ON'.
de: Im Gerät muss der DIP Schalter 10 auf 'ON' gestellt sein.
- uri: https://docs.evcc.io/docs/devices/chargers#eco-pro-vor-2019-mit-strommessgerät
params:
- name: host
- name: port
diff --git a/templates/definition/charger/wallbe-pre2019.yaml b/templates/definition/charger/wallbe-pre2019.yaml
index 2d29e32824..568d4f131d 100644
--- a/templates/definition/charger/wallbe-pre2019.yaml
+++ b/templates/definition/charger/wallbe-pre2019.yaml
@@ -9,7 +9,6 @@ requirements:
description:
en: DIP switch 10 must be set to 'ON'.
de: Im Gerät muss der DIP Schalter 10 auf 'ON' gestellt sein.
- uri: https://docs.evcc.io/docs/devices/chargers#eco-pro-vor-2019
params:
- name: host
- name: port
diff --git a/templates/definition/charger/wallbe.yaml b/templates/definition/charger/wallbe.yaml
index 8560d24bb6..c3b044db13 100644
--- a/templates/definition/charger/wallbe.yaml
+++ b/templates/definition/charger/wallbe.yaml
@@ -8,7 +8,6 @@ requirements:
description:
en: The Wallbe must be connected using Ethernet and the DIP switch 10 must be set to 'ON'.
de: Die Wallbox muss über ein Netzwerkkabel angebunden sein und im Gerät muss der DIP Schalter 10 auf 'ON' gestellt sein.
- uri: https://docs.evcc.io/docs/devices/chargers#wallbe
params:
- name: host
- name: port
diff --git a/templates/definition/meter/cg-em24.yaml b/templates/definition/meter/cg-em24.yaml
index af096d5f50..47616409ae 100644
--- a/templates/definition/meter/cg-em24.yaml
+++ b/templates/definition/meter/cg-em24.yaml
@@ -3,6 +3,9 @@ products:
- brand: Carlo Gavazzi
description:
generic: EM24
+ - brand: Victron
+ description:
+ generic: EM24
params:
- name: usage
choice: ["grid", "charge"]
diff --git a/templates/definition/meter/cg-emt1xx.yaml b/templates/definition/meter/cg-emt1xx.yaml
new file mode 100644
index 0000000000..c37cda9d87
--- /dev/null
+++ b/templates/definition/meter/cg-emt1xx.yaml
@@ -0,0 +1,35 @@
+template: cg-emt1xx
+products:
+ - brand: Carlo Gavazzi
+ description:
+ generic: EM110/111/112
+ - brand: Carlo Gavazzi
+ description:
+ generic: ET112
+params:
+ - name: usage
+ choice: ["pv", "charge"]
+ - name: modbus
+ choice: ["rs485"]
+render: |
+ type: custom
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 0x4 # W
+ type: input
+ decode: int32
+ scale: {{ if eq .usage "pv" }}-{{ end }}0.1
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ {{- if eq .usage "pv" }}
+ address: 0x20 # kWh (-) TOT
+ {{- else }}
+ address: 0x10 # kWh (+) TOT
+ {{- end }}
+ type: input
+ decode: int32
+ scale: 0.1
diff --git a/templates/definition/meter/cg-emt3xx.yaml b/templates/definition/meter/cg-emt3xx.yaml
index f5fcc100fc..b146494b1c 100644
--- a/templates/definition/meter/cg-emt3xx.yaml
+++ b/templates/definition/meter/cg-emt3xx.yaml
@@ -2,7 +2,22 @@ template: cg-emt3xx
products:
- brand: Carlo Gavazzi
description:
- generic: EM/ET 330/340
+ generic: ET330/ET340
+ - brand: Carlo Gavazzi
+ description:
+ generic: EM330/EM340
+ - brand: Carlo Gavazzi
+ description:
+ generic: EM530/EM540
+ - brand: Victron
+ description:
+ generic: ET340
+ - brand: Victron
+ description:
+ generic: EM530/EM540
+ - brand: Kostal
+ description:
+ generic: Energy Meter C (KEM-C)
params:
- name: usage
choice: ["grid", "charge"]
diff --git a/templates/definition/meter/deye-hybrid-hp3.yaml b/templates/definition/meter/deye-hybrid-hp3.yaml
new file mode 100644
index 0000000000..168b797f97
--- /dev/null
+++ b/templates/definition/meter/deye-hybrid-hp3.yaml
@@ -0,0 +1,260 @@
+template: deye-hybrid-hp3
+products:
+ - brand: Deye
+ description:
+ generic: hp3 hybrid inverter
+ - brand: Sunsynk
+ description:
+ generic: hp3 hybrid inverter
+capabilities: ["battery-control"]
+params:
+ - name: usage
+ choice: ["grid", "pv", "battery"]
+ allinone: true
+ - name: modbus
+ choice: ["rs485", "tcpip"]
+ baudrate: 9600
+ id: 1
+ - name: capacity
+ advanced: true
+ - name: minsoc
+ type: number
+ advanced: true
+ - name: maxsoc
+ type: number
+ advanced: true
+render: |
+ type: custom
+ {{- if eq .usage "grid" }}
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 625 # Grid side total power
+ type: holding
+ decode: int16
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 522 # "Total_GridBuy_Power Wh"
+ type: holding
+ decode: uint32s
+ scale: 0.1
+ currents:
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 613 # "Out-of-grid - current A"
+ type: holding
+ decode: int16
+ scale: 0.01
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 614 # "Out-of-grid - current B"
+ type: holding
+ decode: int16
+ scale: 0.01
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 615 # "Out-of-grid - current C"
+ type: holding
+ decode: int16
+ scale: 0.01
+ {{- end }}
+ {{- if eq .usage "pv" }}
+ power:
+ source: calc
+ add:
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 672 # "PV1 input power"
+ type: holding
+ decode: uint16
+ scale: 10
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 673 # "PV2 input power"
+ type: holding
+ decode: uint16
+ scale: 10
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 674 # "PV3 input power"
+ type: holding
+ decode: uint16
+ scale: 10
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ address: 675 # "PV4 input power"
+ type: holding
+ decode: uint16
+ scale: 10
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 534 # "Total_PV_Power_Wh"
+ type: holding
+ decode: uint32s
+ scale: 0.1
+ {{- end }}
+ {{- if eq .usage "battery" }}
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 590 # "Battery output power"
+ type: holding
+ decode: int16
+ scale: 10
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 518 # "Total discharge of the battery (Wh)"
+ type: holding
+ decode: uint32s
+ scale: 0.1
+ soc:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 588 # "battery capacity"
+ type: holding
+ decode: uint16
+ batterymode:
+ source: switch
+ switch:
+ - case: 1 # normal
+ set:
+ source: sequence
+ set:
+ - source: const
+ value: {{ .minsoc }}
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 166
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 0
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 148
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 2355
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 149
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 0
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 172
+ type: writemultiple
+ decode: int32s
+ - case: 2 # hold
+ set:
+ source: sequence
+ set:
+ - source: const
+ value: {{ .maxsoc }}
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 166
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 0
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 148 # soc time of use 1
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 2355
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 149 # soc time of use 1
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 0
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 172
+ type: writemultiple
+ decode: int32s
+ - case: 3 # charge
+ set:
+ source: sequence
+ set:
+ - source: const
+ value: {{ .maxsoc }}
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 166
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 0
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 148 # soc time of use 1
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 2355
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 149 # soc time of use 1
+ type: writemultiple
+ decode: int32s
+ - source: const
+ value: 1
+ set:
+ source: modbus
+ {{- include "modbus" . | indent 10 }}
+ register:
+ address: 172
+ type: writemultiple
+ decode: int32s
+ minsoc: {{.minsoc}}
+ maxsoc: {{.maxsoc}}
+ {{- if .capacity }}
+ capacity: {{ .capacity }} # kWh
+ {{- end }}
+ {{- end }}
diff --git a/templates/definition/meter/eastron-sdm72v2_630.yaml b/templates/definition/meter/eastron-sdm72v2_630.yaml
index 33dae88467..31e5452daa 100644
--- a/templates/definition/meter/eastron-sdm72v2_630.yaml
+++ b/templates/definition/meter/eastron-sdm72v2_630.yaml
@@ -12,6 +12,9 @@ products:
- brand: Weidmüller
description:
generic: EM122-RTU-2P
+ - brand: Kostal
+ description:
+ generic: Energy Meter P (KEM-P)
params:
- name: usage
choice: ["grid", "charge"]
diff --git a/templates/definition/meter/goodwe-dt.yaml b/templates/definition/meter/goodwe-dt.yaml
new file mode 100644
index 0000000000..8ca3d5fd02
--- /dev/null
+++ b/templates/definition/meter/goodwe-dt.yaml
@@ -0,0 +1,31 @@
+template: goodwe-dt
+products:
+ - brand: GoodWe
+ description:
+ generic: SDT/DT Inverter
+params:
+ - name: usage
+ choice: ["pv"]
+ - name: modbus
+ choice: ["rs485", "tcpip"]
+ baudrate: 9600
+ id: 247
+render: |
+ type: custom
+ {{- if eq .usage "pv" }}
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register: # manual non-sunspec register configuration
+ address: 781 # Actual Power
+ type: holding
+ decode: uint16
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register: # manual non-sunspec register configuration
+ address: 786 # PV Energy-Total
+ type: holding
+ decode: uint32
+ scale: 0.1
+ {{- end }}
diff --git a/templates/definition/meter/goodwe-wifi.yaml b/templates/definition/meter/goodwe-wifi.yaml
index 093637dc6e..1e29900e3a 100644
--- a/templates/definition/meter/goodwe-wifi.yaml
+++ b/templates/definition/meter/goodwe-wifi.yaml
@@ -1,4 +1,5 @@
template: goodwe-wifi
+deprecated: true
products:
- brand: GoodWe
description:
diff --git a/templates/definition/meter/slimmelezer-v2.yaml b/templates/definition/meter/slimmelezer-v2.yaml
new file mode 100644
index 0000000000..16348d0c9b
--- /dev/null
+++ b/templates/definition/meter/slimmelezer-v2.yaml
@@ -0,0 +1,102 @@
+template: slimmelezer-V2
+products:
+ - brand: Zuidwijk
+ description:
+ generic: SlimmeLezer(+) V2
+requirements:
+ description:
+ de: Neuere Slimmelezer-Geräte verwenden eine andere Konfiguration. Probieren Sie diese Vorlage aus, wenn die andere fehlschlägt.
+ en: More recent slimmelezer devices use a different configuration. Try this template if the other one fails.
+params:
+ - name: usage
+ choice: ["grid"]
+ - name: host
+render: |
+ type: custom
+ power:
+ source: calc
+ add:
+ - source: http
+ uri: http://{{ .host }}/sensor/power_consumed
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: 1000
+ - source: http
+ uri: http://{{ .host }}/sensor/power_produced
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: -1000
+ energy:
+ source: calc
+ add:
+ - source: http
+ uri: http://{{ .host }}/sensor/energy_produced_tariff_1
+ headers:
+ - content-type: application/json
+ jq: .value
+ - source: http
+ uri: http://{{ .host }}/sensor/energy_produced_tariff_2
+ headers:
+ - content-type: application/json
+ jq: .value
+ currents:
+ - source: http
+ uri: http://{{ .host }}/sensor/current_phase_1
+ headers:
+ - content-type: application/json
+ jq: .value
+ - source: http
+ uri: http://{{ .host }}/sensor/current_phase_2
+ headers:
+ - content-type: application/json
+ jq: .value
+ - source: http
+ uri: http://{{ .host }}/sensor/current_phase_3
+ headers:
+ - content-type: application/json
+ jq: .value
+ powers:
+ - source: calc
+ add:
+ - source: http
+ uri: http://{{ .host }}/sensor/power_produced_phase_1
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: 1000
+ - source: http
+ uri: http://{{ .host }}/sensor/power_consumed_phase_1
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: -1000
+ - source: calc
+ add:
+ - source: http
+ uri: http://{{ .host }}/sensor/power_produced_phase_2
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: 1000
+ - source: http
+ uri: http://{{ .host }}/sensor/power_consumed_phase_2
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: -1000
+ - source: calc
+ add:
+ - source: http
+ uri: http://{{ .host }}/sensor/power_produced_phase_3
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: 1000
+ - source: http
+ uri: http://{{ .host }}/sensor/power_consumed_phase_3
+ headers:
+ - content-type: application/json
+ jq: .value
+ scale: -1000
diff --git a/templates/definition/meter/solarwatt.yaml b/templates/definition/meter/solarwatt.yaml
index 8b18d26b4f..95fedc001e 100644
--- a/templates/definition/meter/solarwatt.yaml
+++ b/templates/definition/meter/solarwatt.yaml
@@ -40,15 +40,15 @@ render: |
power:
source: http
uri: http://{{ .host }}/rest/kiwigrid/wizard/devices # EnergyManager
- jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.location.Location" ) | .tagValues.PowerReleased.value - .tagValues.PowerBuffered.value
+ jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.location.Location" ) | (.tagValues.PowerReleased.value // 0) - (.tagValues.PowerBuffered.value // 0)
soc:
source: http
uri: http://{{ .host }}/rest/kiwigrid/wizard/devices # EnergyManager
- jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.batteryconverter.BatteryConverter") | .tagValues.StateOfCharge.value
+ jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.batteryconverter.BatteryConverter") | (.tagValues.StateOfCharge.value // 0)
energy:
source: http
uri: http://{{ .host }}/rest/kiwigrid/wizard/devices # EnergyManager
- jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.location.Location" ) | .tagValues.WorkReleased.value / 1000
+ jq: .result.items[] | select(.deviceModel[].deviceClass == "com.kiwigrid.devices.location.Location" ) | (.tagValues.WorkReleased.value // 0) / 1000
{{- if .capacity }}
capacity: {{ .capacity }} # kWh
{{- end }}
diff --git a/templates/definition/meter/solis-hybrid-s.yaml b/templates/definition/meter/solis-hybrid-s.yaml
new file mode 100644
index 0000000000..d851f53c19
--- /dev/null
+++ b/templates/definition/meter/solis-hybrid-s.yaml
@@ -0,0 +1,134 @@
+template: solis-hybrid-s
+products:
+ - brand: Ginlong
+ description:
+ generic: Solis Hybrid Inverter (S Series)
+params:
+ - name: usage
+ choice: ["grid", "pv", "battery"]
+ allinone: true
+ - name: modbus
+ choice: ["rs485"]
+ baudrate: 9600
+ id: 1
+ - name: capacity
+ advanced: true
+render: |
+ type: custom
+ {{- if eq .usage "grid" }}
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33263 # Meter total active power
+ type: input
+ decode: int32
+ scale: -1
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33283 # Meter total active energy from grid
+ type: input
+ decode: uint32
+ scale: 0.01
+ currents:
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33252 # Meter ac current A
+ type: input
+ decode: uint16
+ scale: 0.01
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33254 # Meter ac current B
+ type: input
+ decode: uint16
+ scale: 0.01
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33256 # Meter ac current C
+ type: input
+ decode: uint16
+ scale: 0.01
+ powers:
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33257 # Meter active power A
+ type: input
+ decode: int32
+ scale: -1
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33259 # Meter active power B
+ type: input
+ decode: int32
+ scale: -1
+ - source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33261 # Meter active power C
+ type: input
+ decode: int32
+ scale: -1
+ {{- end }}
+ {{- if eq .usage "pv" }}
+ power:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ type: input
+ address: 33057 # Total DC output power (PV Power)
+ decode: uint32
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33029 # Total energy generation
+ type: input
+ decode: uint32
+ {{- end }}
+ {{- if eq .usage "battery" }}
+ power:
+ source: calc
+ mul:
+ - source: modbus
+ {{- include "modbus" . | indent 4 }}
+ register:
+ type: input
+ address: 33149 # Battery power
+ decode: uint32
+ - source: calc
+ add:
+ - source: modbus
+ {{- include "modbus" . | indent 6 }}
+ register:
+ type: input
+ address: 33135 # Battery current direction
+ decode: uint16 # 0:charge, 1:discharge
+ scale: 2
+ - source: const
+ value: -1
+ energy:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33165 # Battery total discharge energy
+ type: input
+ decode: uint32
+ soc:
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ address: 33139 # Battery capacity SOC
+ type: input
+ decode: uint16
+ {{- if .capacity }}
+ capacity: {{ .capacity }} # kWh
+ {{- end }}
+ {{- end }}
diff --git a/templates/definition/meter/solis-hybrid.yaml b/templates/definition/meter/solis-hybrid.yaml
index 78cc83d9fd..5f913f098e 100644
--- a/templates/definition/meter/solis-hybrid.yaml
+++ b/templates/definition/meter/solis-hybrid.yaml
@@ -2,7 +2,7 @@ template: solis-hybrid
products:
- brand: Ginlong
description:
- generic: Solis Hybrid Inverter
+ generic: Solis Hybrid Inverter (RHI series)
params:
- name: usage
choice: ["grid", "pv", "battery"]
@@ -95,25 +95,13 @@ render: |
{{- end }}
{{- if eq .usage "battery" }}
power:
- source: calc
- mul:
- - source: modbus
- {{- include "modbus" . | indent 4 }}
- register:
- type: input
- address: 33149 # Battery power
- decode: uint32
- - source: calc
- add:
- - source: modbus
- {{- include "modbus" . | indent 6 }}
- register:
- type: input
- address: 33135 # Battery current direction
- decode: uint16 # 0:charge, 1:discharge
- scale: 2
- - source: const
- value: -1
+ source: modbus
+ {{- include "modbus" . | indent 2 }}
+ register:
+ type: input
+ address: 33149 # Battery power
+ decode: int32
+ scale: -1
energy:
source: modbus
{{- include "modbus" . | indent 2 }}
diff --git a/templates/definition/meter/sungrow-hybrid.yaml b/templates/definition/meter/sungrow-hybrid.yaml
index f2ae9cfb1e..75562ed670 100644
--- a/templates/definition/meter/sungrow-hybrid.yaml
+++ b/templates/definition/meter/sungrow-hybrid.yaml
@@ -107,23 +107,23 @@ render: |
source: sequence
set:
- source: const
- value: 0 # self consumption
+ value: 0 # Self-consumption mode (Default)
set:
source: modbus
{{- include "modbus" . | indent 10 }}
timeout: {{ .timeout }}
register:
- address: 13049 # EMS mode
+ address: 13049 # EMS mode selection
type: writesingle
decode: uint16
- source: const
- value: 0xCC # stop
+ value: 0xCC # Stop (Default)
set:
source: modbus
{{- include "modbus" . | indent 10 }}
timeout: {{ .timeout }}
register:
- address: 13050 # Forced mode
+ address: 13050 # Charge/discharge command
type: writesingle
decode: uint16
- case: 2 # hold
@@ -131,23 +131,23 @@ render: |
source: sequence
set:
- source: const
- value: 2 # forced mode
+ value: 2 # Forced mode (charge/discharge/stop)
set:
source: modbus
{{- include "modbus" . | indent 10 }}
timeout: {{ .timeout }}
register:
- address: 13049 # EMS mode
+ address: 13049 # EMS mode selection
type: writesingle
decode: uint16
- source: const
- value: 0xCC
+ value: 0xCC # Stop (Default)
set:
source: modbus
{{- include "modbus" . | indent 10 }}
timeout: {{ .timeout }}
register:
- address: 13050 # Forced mode
+ address: 13050 # Charge/discharge command
type: writesingle
decode: uint16
- case: 3 # charge
@@ -155,7 +155,7 @@ render: |
source: sequence
set:
- source: const
- value: 2 # forced mode
+ value: 2 # Forced mode (charge/discharge/stop)
set:
source: modbus
{{- include "modbus" . | indent 10 }}
@@ -165,15 +165,40 @@ render: |
type: writesingle
decode: uint16
- source: const
- value: 0xAA
+ value: 0xAA # Charge
set:
source: modbus
{{- include "modbus" . | indent 10 }}
timeout: {{ .timeout }}
register:
- address: 13050 # Forced mode
+ address: 13050 # Charge/discharge command
type: writesingle
decode: uint16
+ - source: go
+ script: power
+ in:
+ - name: power
+ type: int
+ config:
+ source: modbus
+ {{- include "modbus" . | indent 12 }}
+ timeout: {{ .timeout }}
+ register:
+ address: 5627 # BDC rated power
+ type: input
+ decode: uint16
+ scale: 100
+ out:
+ - name: power
+ type: int
+ config:
+ source: modbus
+ {{- include "modbus" . | indent 12 }}
+ timeout: {{ .timeout }}
+ register:
+ address: 13051 # Charge/discharge power
+ type: writesingle
+ decode: uint16
{{- if .capacity }}
capacity: {{ .capacity }} # kWh
{{- end }}
diff --git a/templates/definition/meter/tesla-powerwall.yaml b/templates/definition/meter/tesla-powerwall.yaml
index 6c33b672ba..49b8ea1cd1 100644
--- a/templates/definition/meter/tesla-powerwall.yaml
+++ b/templates/definition/meter/tesla-powerwall.yaml
@@ -31,9 +31,6 @@ params:
en: Password of the user "customer". By default this is the last 5 characters of password stated on the Tesla Gateway.
de: Passwort des Benutzers "Kunde". Default sind die letzten 5 Zeichen des auf dem Tesla Gateway genannten Passworts.
- name: refreshToken
- help:
- en: "See https://docs.evcc.io/en/docs/devices/meters#tesla-powerwall"
- de: "Siehe https://docs.evcc.io/docs/devices/meters#tesla-powerwall"
- name: siteId
help:
en: optional product identifier of the energy site, use to override autodectction
diff --git a/templates/definition/tariff/entsoe.yaml b/templates/definition/tariff/entsoe.yaml
index e2493283ab..5a7fd4a2f5 100644
--- a/templates/definition/tariff/entsoe.yaml
+++ b/templates/definition/tariff/entsoe.yaml
@@ -23,5 +23,6 @@ params:
- preset: tariff-base
render: |
type: entsoe
- region: {{ .region }}
+ securitytoken: {{ .securitytoken }}
+ domain: {{ .domain }}
{{ include "tariff-base" . }}
diff --git a/templates/definition/tariff/ngeso.yaml b/templates/definition/tariff/ngeso.yaml
index ac6c9de6f6..a63b5a17c0 100644
--- a/templates/definition/tariff/ngeso.yaml
+++ b/templates/definition/tariff/ngeso.yaml
@@ -24,4 +24,4 @@ params:
render: |
type: ngeso
region: {{ .region }}
- postalcode: {{ .postalcode }}
+ postcode: {{ .postcode }}
diff --git a/templates/definition/vehicle/bmw.yaml b/templates/definition/vehicle/bmw.yaml
index 7b89ab209d..0fb06944bc 100644
--- a/templates/definition/vehicle/bmw.yaml
+++ b/templates/definition/vehicle/bmw.yaml
@@ -15,6 +15,8 @@ params:
# - EU
default: EU
advanced: true
+ - name: welcomecharge
+ advanced: true
render: |
type: bmw
{{ include "vehicle-base" . }}
@@ -22,3 +24,6 @@ render: |
{{- if ne .region "EU" }}
region: {{ .region }}
{{- end }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/citroen.yaml b/templates/definition/vehicle/citroen.yaml
index e04ed96ce7..9ae48764c7 100644
--- a/templates/definition/vehicle/citroen.yaml
+++ b/templates/definition/vehicle/citroen.yaml
@@ -19,15 +19,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#citroen"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#citroen"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#citroen"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#citroen"
- name: vin
example: V...
- name: capacity
@@ -36,9 +30,10 @@ params:
- preset: vehicle-identify
render: |
type: citroen
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
user: {{ .user }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/dacia.yaml b/templates/definition/vehicle/dacia.yaml
index e4fb880a8d..c48e746c25 100644
--- a/templates/definition/vehicle/dacia.yaml
+++ b/templates/definition/vehicle/dacia.yaml
@@ -4,7 +4,12 @@ products:
params:
- preset: vehicle-base
- preset: vehicle-identify
+ - name: welcomecharge
+ advanced: true
render: |
type: dacia
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/ds.yaml b/templates/definition/vehicle/ds.yaml
index 65c4d5139a..1eed9a9c90 100644
--- a/templates/definition/vehicle/ds.yaml
+++ b/templates/definition/vehicle/ds.yaml
@@ -19,15 +19,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#ds"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#ds"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#ds"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#ds"
- name: vin
example: V...
- name: capacity
@@ -36,9 +30,10 @@ params:
- preset: vehicle-identify
render: |
type: ds
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
user: {{ .user }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/fiat.yaml b/templates/definition/vehicle/fiat.yaml
index 78dfc81932..d55272f7b7 100644
--- a/templates/definition/vehicle/fiat.yaml
+++ b/templates/definition/vehicle/fiat.yaml
@@ -9,6 +9,8 @@ params:
- name: pin
mask: true
- preset: vehicle-identify
+ - name: welcomecharge
+ advanced: true
render: |
type: fiat
{{ include "vehicle-base" . }}
@@ -16,3 +18,6 @@ render: |
pin: {{ .pin }} # mandatory to deep refresh Soc
{{- end }}
{{ include "vehicle-identify" . }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/ford-connect.yaml b/templates/definition/vehicle/ford-connect.yaml
index 5da48fd258..da0e14c4e4 100644
--- a/templates/definition/vehicle/ford-connect.yaml
+++ b/templates/definition/vehicle/ford-connect.yaml
@@ -23,15 +23,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#ford-connect"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#ford-connect"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#ford-connect"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#ford-connect"
- name: vin
example: WF0FXX...
- name: capacity
@@ -41,11 +35,12 @@ params:
- preset: vehicle-identify
render: |
type: ford-connect
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
credentials:
id: {{ .clientid }}
secret: {{ .clientsecret }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/mazda2mqtt.yaml b/templates/definition/vehicle/mazda2mqtt.yaml
index 8b9c5f7e31..73dd7381b3 100644
--- a/templates/definition/vehicle/mazda2mqtt.yaml
+++ b/templates/definition/vehicle/mazda2mqtt.yaml
@@ -1,4 +1,5 @@
template: mazda2mqtt
+deprecated: true
products:
- description:
generic: mazda2mqtt
diff --git a/templates/definition/vehicle/mercedes.yaml b/templates/definition/vehicle/mercedes.yaml
index 1c2854862e..b2abd37fec 100644
--- a/templates/definition/vehicle/mercedes.yaml
+++ b/templates/definition/vehicle/mercedes.yaml
@@ -21,15 +21,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#mercedes"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#mercedes"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#mercedes"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#mercedes"
- name: vin
example: V...
- name: capacity
@@ -38,10 +32,11 @@ params:
- preset: vehicle-identify
render: |
type: mercedes
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
user: {{ .user }}
region: {{ .region }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/mini.yaml b/templates/definition/vehicle/mini.yaml
index cfb84b2c6f..2e797d79a4 100644
--- a/templates/definition/vehicle/mini.yaml
+++ b/templates/definition/vehicle/mini.yaml
@@ -15,6 +15,8 @@ params:
# - EU
default: EU
advanced: true
+ - name: welcomecharge
+ advanced: true
render: |
type: mini
{{ include "vehicle-base" . }}
@@ -22,3 +24,6 @@ render: |
{{- if ne .region "EU" }}
region: {{ .region }}
{{- end }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/mz2mqtt.yaml b/templates/definition/vehicle/mz2mqtt.yaml
new file mode 100644
index 0000000000..cb5e495307
--- /dev/null
+++ b/templates/definition/vehicle/mz2mqtt.yaml
@@ -0,0 +1,59 @@
+template: mz2mqtt
+products:
+ - description:
+ generic: mz2mqtt
+group: generic
+requirements:
+ description:
+ en: myMazda to MQTT. Required MQTT broker configuration and a mz2mqtt installation https://github.com/C64Axel/mz2mqtt.
+ de: myMazda zu MQTT. Voraussetzung ist ein konfigurierter MQTT Broker und eine mz2mqtt Installation https://github.com/C64Axel/mz2mqtt.
+params:
+ - name: title
+ - name: vin
+ required: true
+ help:
+ de: Erforderlich
+ en: Required
+ - name: capacity
+ - name: phases
+ advanced: true
+ - name: icon
+ default: car
+ advanced: true
+ - name: timeout
+ default: 720h
+ advanced: true
+ - preset: vehicle-identify
+render: |
+ type: custom
+ {{- if .title }}
+ title: {{ .title }}
+ {{- end }}
+ {{- if .icon }}
+ icon: {{ .icon }}
+ {{- end }}
+ {{- if .capacity }}
+ capacity: {{ .capacity }}
+ {{- end }}
+ {{- if .phases }}
+ phases: {{ .phases }}
+ {{- end }}
+ {{- include "vehicle-identify" . }}
+ soc:
+ source: mqtt
+ topic: mz2mqtt/{{ .vin }}/chargeInfo/batteryLevelPercentage
+ timeout: {{ .timeout }}
+ status:
+ source: combined
+ plugged:
+ source: mqtt
+ topic: mz2mqtt/{{ .vin }}/chargeInfo/pluggedIn
+ timeout: {{ .timeout }}
+ charging:
+ source: mqtt
+ topic: mz2mqtt/{{ .vin }}/chargeInfo/charging
+ timeout: {{ .timeout }}
+ range:
+ source: mqtt
+ topic: mz2mqtt/{{ .vin }}/chargeInfo/drivingRangeKm
+ timeout: {{ .timeout }}
diff --git a/templates/definition/vehicle/opel.yaml b/templates/definition/vehicle/opel.yaml
index 3e4d7a1ac0..5e3b9997ab 100644
--- a/templates/definition/vehicle/opel.yaml
+++ b/templates/definition/vehicle/opel.yaml
@@ -19,15 +19,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#opel"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#opel"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#opel"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#opel"
- name: vin
example: V...
- name: capacity
@@ -36,9 +30,10 @@ params:
- preset: vehicle-identify
render: |
type: opel
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
user: {{ .user }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/peugeot.yaml b/templates/definition/vehicle/peugeot.yaml
index 92b9a751d0..86ee364b7a 100644
--- a/templates/definition/vehicle/peugeot.yaml
+++ b/templates/definition/vehicle/peugeot.yaml
@@ -19,15 +19,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#peugeot"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#peugeot"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#peugeot"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#peugeot"
- name: vin
example: V...
- name: capacity
@@ -36,9 +30,10 @@ params:
- preset: vehicle-identify
render: |
type: peugeot
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
user: {{ .user }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/templates/definition/vehicle/porsche.yaml b/templates/definition/vehicle/porsche.yaml
index 801b567bab..4635be5fdd 100644
--- a/templates/definition/vehicle/porsche.yaml
+++ b/templates/definition/vehicle/porsche.yaml
@@ -1,4 +1,5 @@
template: porsche
+deprecated: true
products:
- brand: Porsche
params:
diff --git a/templates/definition/vehicle/seat-cupra.yaml b/templates/definition/vehicle/seat-cupra.yaml
index 3ebd497e64..aacb774a8d 100644
--- a/templates/definition/vehicle/seat-cupra.yaml
+++ b/templates/definition/vehicle/seat-cupra.yaml
@@ -6,7 +6,12 @@ products:
params:
- preset: vehicle-base
- preset: vehicle-identify
+ - name: welcomecharge
+ advanced: true
render: |
type: cupra
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/smart-hello.yaml b/templates/definition/vehicle/smart-hello.yaml
index 892fe3f97e..064990d5e9 100644
--- a/templates/definition/vehicle/smart-hello.yaml
+++ b/templates/definition/vehicle/smart-hello.yaml
@@ -6,7 +6,12 @@ products:
params:
- preset: vehicle-base
- preset: vehicle-identify
+ - name: welcomecharge
+ advanced: true
render: |
type: smart-hello
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}
+ {{- if .welcomecharge }}
+ features: ["welcomecharge"]
+ {{- end }}
diff --git a/templates/definition/vehicle/tesla.yaml b/templates/definition/vehicle/tesla.yaml
index 5c7c5d9690..82c2a838b9 100644
--- a/templates/definition/vehicle/tesla.yaml
+++ b/templates/definition/vehicle/tesla.yaml
@@ -21,15 +21,9 @@ params:
- name: accessToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#tesla"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#tesla"
- name: refreshToken
required: true
mask: true
- help:
- en: "See https://docs.evcc.io/en/docs/devices/vehicles#tesla"
- de: "Siehe https://docs.evcc.io/docs/devices/vehicles#tesla"
- name: vin
example: W...
- name: capacity
@@ -37,12 +31,20 @@ params:
advanced: true
- name: control
deprecated: true
+ - name: commandProxy
+ default: https://tesla.evcc.io/
+ advanced: true
+ help:
+ en: "When using a TWC3 (or other 'dumb' charger not capable of control), evcc can manage the charge directly by communicating with the vehicle through a Command Proxy. By default, the proxy provided by evcc is used. With this parameter, you set the base URL of a custom Command Proxy to use instead of the default evcc one. See for example https://github.com/wimaha/TeslaBleHttpProxy for a proxy sending commands via bluetooth."
+ de: "Bei Verwendung eines TWC3 (oder eines anderen 'dummen' Ladegeräts, das nicht steuerbar ist) kann evcc die Ladung direkt verwalten, indem es über einen Command Proxy mit dem Fahrzeug kommuniziert. Standardmäßig wird der von evcc bereitgestellte Proxy verwendet. Dieses parameter setzt die Basis-URL eines benutzerdefinierten Command Proxy, der anstelle des standardmäßigen evcc-Proxy verwendet werden soll. Siehe zum Beispiel https://github.com/wimaha/TeslaBleHttpProxy für einen Proxy, der Kommandos über Bluetooth sendet."
- preset: vehicle-identify
render: |
type: tesla
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
tokens:
access: {{ .accessToken }}
refresh: {{ .refreshToken }}
+ commandProxy: {{ .commandProxy }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
features: ["coarsecurrent"]
diff --git a/templates/definition/vehicle/teslamate.yaml b/templates/definition/vehicle/teslamate.yaml
index e2feb88a1d..488944124d 100644
--- a/templates/definition/vehicle/teslamate.yaml
+++ b/templates/definition/vehicle/teslamate.yaml
@@ -43,4 +43,8 @@ render: |
source: mqtt
topic: teslamate/cars/{{ .id }}/rated_battery_range_km
timeout: 720h # 30d
+ odometer:
+ source: mqtt
+ topic: teslamate/cars/{{ .id }}/odometer
+ timeout: 720h # 30d
features: ["coarsecurrent"]
diff --git a/templates/definition/vehicle/tronity.yaml b/templates/definition/vehicle/tronity.yaml
index b017eb92b3..0310de39d5 100644
--- a/templates/definition/vehicle/tronity.yaml
+++ b/templates/definition/vehicle/tronity.yaml
@@ -33,8 +33,9 @@ params:
- preset: vehicle-identify
render: |
type: tronity
- {{ include "vehicle-common" . }}
+ vin: {{ .vin }}
credentials:
id: {{ .clientid }}
secret: {{ .clientsecret }}
+ {{ include "vehicle-common" . }}
{{ include "vehicle-identify" . }}
diff --git a/tests/auth.spec.js b/tests/auth.spec.js
index f11b621f5a..04c1665d2f 100644
--- a/tests/auth.spec.js
+++ b/tests/auth.spec.js
@@ -3,16 +3,12 @@ import { start, stop, baseUrl } from "./evcc";
test.use({ baseURL: baseUrl() });
-test.beforeEach(async ({ page }) => {
- await start("basics.evcc.yaml");
- await page.goto("/");
-});
-
-test.afterEach(async () => {
- await stop();
-});
+const BASIC = "basics.evcc.yaml";
test("set initial password", async ({ page }) => {
+ await start(BASIC);
+ await page.goto("/");
+
const modal = page.getByTestId("password-modal");
await expect(modal).toBeVisible();
@@ -33,14 +29,13 @@ test("set initial password", async ({ page }) => {
await modal.getByLabel("Repeat password").fill("secret");
await modal.getByRole("button", { name: "Create Password" }).click();
await expect(modal).not.toBeVisible();
+
+ await stop();
});
test("login", async ({ page }) => {
- // set initial password
- const modal = page.getByTestId("password-modal");
- await modal.getByLabel("New password").fill("secret");
- await modal.getByLabel("Repeat password").fill("secret");
- await modal.getByRole("button", { name: "Create Password" }).click();
+ await start(BASIC, "password.sql");
+ await page.goto("/");
// go to config
await page.getByTestId("topnavigation-button").click();
@@ -61,14 +56,13 @@ test("login", async ({ page }) => {
await login.getByRole("button", { name: "Login" }).click();
await expect(login).not.toBeVisible();
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();
+
+ await stop();
});
test("http iframe hint", async ({ page }) => {
- // set initial password
- const modal = page.getByTestId("password-modal");
- await modal.getByLabel("New password").fill("secret");
- await modal.getByLabel("Repeat password").fill("secret");
- await modal.getByRole("button", { name: "Create Password" }).click();
+ await start(BASIC, "password.sql");
+ await page.goto("/");
// go to config
await page.getByTestId("topnavigation-button").click();
@@ -90,26 +84,27 @@ test("http iframe hint", async ({ page }) => {
// iframe hint visible (login-iframe-hint)
await expect(login.getByTestId("login-iframe-hint")).toBeVisible();
+
+ await stop();
});
test("update password", async ({ page }) => {
+ const instance = await start(BASIC, "password.sql");
+ await page.goto("/");
+
const oldPassword = "secret";
const newPassword = "newsecret";
- // set initial password
- const modal = page.getByTestId("password-modal");
- await modal.getByLabel("New password").fill(oldPassword);
- await modal.getByLabel("Repeat password").fill(oldPassword);
- await modal.getByRole("button", { name: "Create Password" }).click();
-
// login modal
page.goto("/#/config");
const loginOld = page.getByTestId("login-modal");
await loginOld.getByLabel("Password").fill(oldPassword);
await loginOld.getByRole("button", { name: "Login" }).click();
+ await expect(loginOld).not.toBeVisible();
// update password
await page.getByTestId("generalconfig-password").getByRole("button", { name: "edit" }).click();
+ const modal = page.getByTestId("password-modal");
await expect(modal.getByRole("heading", { name: "Update Administrator Password" })).toBeVisible();
await modal.getByLabel("Current password").fill(oldPassword);
await modal.getByLabel("New password").fill(newPassword);
@@ -133,11 +128,6 @@ test("update password", async ({ page }) => {
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();
await expect(loginNew).not.toBeVisible();
- // revert password
- await page.getByTestId("generalconfig-password").getByRole("button", { name: "edit" }).click();
- await modal.getByLabel("Current password").fill(newPassword);
- await modal.getByLabel("New password").fill(oldPassword);
- await modal.getByLabel("Repeat password").fill(oldPassword);
- await modal.getByRole("button", { name: "Update Password" }).click();
- await expect(page.getByTestId("password-modal")).not.toBeVisible();
+ // hard stop, since password is updated
+ await stop(instance);
});
diff --git a/tests/config-meters.spec.js b/tests/config-battery.spec.js
similarity index 75%
rename from tests/config-meters.spec.js
rename to tests/config-battery.spec.js
index 24d3b17011..1c08320fdb 100644
--- a/tests/config-meters.spec.js
+++ b/tests/config-battery.spec.js
@@ -2,13 +2,13 @@ import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
import { startSimulator, stopSimulator, simulatorUrl, simulatorHost } from "./simulator";
-const CONFIG_EMPTY = "config-empty.evcc.yaml";
+const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
- await start(CONFIG_EMPTY, "password.sql");
await startSimulator();
+ await start(CONFIG_GRID_ONLY, "password.sql");
});
test.afterAll(async () => {
await stop();
@@ -18,6 +18,7 @@ test.afterAll(async () => {
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
async function enableExperimental(page) {
@@ -29,7 +30,7 @@ async function enableExperimental(page) {
await page.getByRole("button", { name: "Close" }).click();
}
-test.describe("meters", async () => {
+test.describe("battery meter", async () => {
test("create, edit and remove battery meter", async ({ page }) => {
// setup test data for mock openems api
await page.goto(simulatorUrl());
@@ -71,7 +72,7 @@ test.describe("meters", async () => {
await expect(battery.getByTestId("device-tag-capacity")).toContainText("20.0 kWh");
// restart and check in main ui
- await restart(CONFIG_EMPTY);
+ await restart(CONFIG_GRID_ONLY);
await page.goto("/");
await page.getByTestId("visualization").click();
await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.5 kW");
@@ -83,4 +84,21 @@ test.describe("meters", async () => {
await expect(page.getByTestId("battery")).toHaveCount(0);
});
+
+ test("advanced fields", async ({ page }) => {
+ await page.goto("/#/config");
+ await login(page);
+ await enableExperimental(page);
+
+ await page.getByRole("button", { name: "Add solar or battery" }).click();
+
+ const meterModal = page.getByTestId("meter-modal");
+ await meterModal.getByRole("button", { name: "Add battery meter" }).click();
+ await meterModal.getByLabel("Manufacturer").selectOption("OpenEMS");
+ await expect(meterModal.getByLabel("Password optional")).not.toBeVisible();
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await expect(meterModal.getByLabel("Password optional")).toBeVisible();
+ await page.getByRole("button", { name: "Hide advanced settings" }).click();
+ await expect(meterModal.getByLabel("Password optional")).not.toBeVisible();
+ });
});
diff --git a/tests/config-empty.evcc.yaml b/tests/config-empty.evcc.yaml
index 99832e6359..c7b0b6519a 100755
--- a/tests/config-empty.evcc.yaml
+++ b/tests/config-empty.evcc.yaml
@@ -1,15 +1,5 @@
site:
title: Hello World
- meters:
- grid: grid
-
-meters:
- - name: grid
- type: custom
- power:
- source: js
- script: |
- 1000
loadpoints:
- title: Carport
diff --git a/tests/config-grid-only.evcc.yaml b/tests/config-grid-only.evcc.yaml
new file mode 100755
index 0000000000..99832e6359
--- /dev/null
+++ b/tests/config-grid-only.evcc.yaml
@@ -0,0 +1,34 @@
+site:
+ title: Hello World
+ meters:
+ grid: grid
+
+meters:
+ - name: grid
+ type: custom
+ power:
+ source: js
+ script: |
+ 1000
+
+loadpoints:
+ - title: Carport
+ charger: charger
+
+chargers:
+ - name: charger
+ type: custom
+ enable:
+ source: js
+ script:
+ enabled:
+ source: js
+ script: |
+ false
+ status:
+ source: js
+ script: |
+ "B"
+ maxcurrent:
+ source: js
+ script:
diff --git a/tests/config-grid.spec.js b/tests/config-grid.spec.js
new file mode 100644
index 0000000000..75ec5c263d
--- /dev/null
+++ b/tests/config-grid.spec.js
@@ -0,0 +1,85 @@
+import { test, expect } from "@playwright/test";
+import { start, stop, restart, baseUrl } from "./evcc";
+import { startSimulator, stopSimulator, simulatorUrl, simulatorHost } from "./simulator";
+
+const CONFIG_EMPTY = "config-empty.evcc.yaml";
+
+test.use({ baseURL: baseUrl() });
+
+test.beforeAll(async () => {
+ await startSimulator();
+ await start(CONFIG_EMPTY, "password.sql");
+});
+test.afterAll(async () => {
+ await stop();
+ await stopSimulator();
+});
+
+async function login(page) {
+ await page.locator("#loginPassword").fill("secret");
+ await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
+}
+
+async function enableExperimental(page) {
+ await page
+ .getByTestId("generalconfig-experimental")
+ .getByRole("button", { name: "edit" })
+ .click();
+ await page.getByLabel("Experimental 🧪").click();
+ await page.getByRole("button", { name: "Close" }).click();
+}
+
+test.describe("main screen", async () => {
+ test("modes", async ({ page }) => {
+ await page.goto("/");
+ await expect(page.getByRole("button", { name: "Off" })).toBeVisible();
+ await expect(page.getByRole("button", { name: "Fast" })).toBeVisible();
+ });
+});
+
+test.describe("grid meter", async () => {
+ test("create, edit and remove grid meter", async ({ page }) => {
+ // setup test data for mock openems api
+ await page.goto(simulatorUrl());
+ await page.getByLabel("Grid Power").fill("5000");
+ await page.getByRole("button", { name: "Apply changes" }).click();
+
+ await page.goto("/#/config");
+ await login(page);
+ await enableExperimental(page);
+
+ await expect(page.getByTestId("grid")).toHaveCount(1);
+ await expect(page.getByTestId("grid").getByTestId("device-tag-configured")).toContainText("no");
+
+ // create #1
+ await page.getByTestId("grid").getByRole("button", { name: "edit" }).click();
+
+ const meterModal = page.getByTestId("meter-modal");
+ await meterModal.getByLabel("Manufacturer").selectOption("OpenEMS");
+ await meterModal.getByLabel("IP address or hostname").fill(simulatorHost());
+ await expect(meterModal.getByRole("button", { name: "Validate & save" })).toBeVisible();
+ await meterModal.getByRole("link", { name: "validate" }).click();
+ await expect(meterModal.getByTestId("device-tag-power")).toContainText("5.0 kW");
+ await meterModal.getByRole("button", { name: "Save" }).click();
+ await expect(meterModal).not.toBeVisible();
+
+ // restart
+ await restart(CONFIG_EMPTY);
+ await expect(page.getByTestId("grid").getByTestId("device-tag-power")).toContainText("5.0 kW");
+
+ // check in main ui
+ await page.goto("/");
+ await page.getByTestId("visualization").click();
+ await expect(page.getByTestId("energyflow")).toContainText(["Grid use", "5.0 kW"].join(""));
+
+ // delete #1
+ await page.goto("/#/config");
+ await page.getByTestId("grid").getByRole("button", { name: "edit" }).click();
+ await meterModal.getByRole("button", { name: "Delete" }).click();
+ await expect(meterModal).not.toBeVisible();
+
+ await expect(page.getByTestId("grid")).toHaveCount(1);
+ await expect(page.getByTestId("grid").getByTestId("device-tag-configured")).toContainText("no");
+ });
+});
diff --git a/tests/config-messaging.spec.js b/tests/config-messaging.spec.js
new file mode 100644
index 0000000000..bf03761d0e
--- /dev/null
+++ b/tests/config-messaging.spec.js
@@ -0,0 +1,60 @@
+import { test, expect } from "@playwright/test";
+import { start, stop, baseUrl } from "./evcc";
+
+const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";
+
+test.use({ baseURL: baseUrl() });
+
+test.afterEach(async () => {
+ await stop();
+});
+
+const SELECT_ALL = "ControlOrMeta+KeyA";
+
+async function login(page) {
+ await page.locator("#loginPassword").fill("secret");
+ await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
+}
+
+async function enableExperimental(page) {
+ await page
+ .getByTestId("generalconfig-experimental")
+ .getByRole("button", { name: "edit" })
+ .click();
+ await page.getByLabel("Experimental 🧪").click();
+ await page.getByRole("button", { name: "Close" }).click();
+ await expect(page.locator(".modal-backdrop")).not.toBeVisible();
+}
+
+async function goToConfig(page) {
+ await page.goto("/#/config");
+ await login(page);
+ await enableExperimental(page);
+}
+
+test.describe("messaging", async () => {
+ test("save a comment", async ({ page }) => {
+ await start(CONFIG_GRID_ONLY, "password.sql");
+ await goToConfig(page);
+
+ await page.getByTestId("messaging").getByRole("button", { name: "edit" }).click();
+ const modal = await page.getByTestId("messaging-modal");
+ await expect(modal).toBeVisible();
+
+ await modal.locator(".monaco-editor .view-line").nth(0).click();
+ for (let i = 0; i < 4; i++) {
+ await page.keyboard.press(SELECT_ALL, { delay: 10 });
+ await page.keyboard.press("Backspace", { delay: 10 });
+ }
+ await page.keyboard.type("# hello world");
+ await page.getByRole("button", { name: "Save" }).click();
+ await expect(modal).not.toBeVisible();
+
+ page.reload();
+
+ await page.getByTestId("messaging").getByRole("button", { name: "edit" }).click();
+ await expect(modal).toBeVisible();
+ await expect(modal).toContainText("# hello world");
+ });
+});
diff --git a/tests/config-mqtt.spec.js b/tests/config-mqtt.spec.js
index 9d253f49ea..505044c6b9 100644
--- a/tests/config-mqtt.spec.js
+++ b/tests/config-mqtt.spec.js
@@ -1,7 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
-const CONFIG = "config-empty.evcc.yaml";
+const CONFIG = "config-grid-only.evcc.yaml";
test.use({ baseURL: baseUrl() });
@@ -19,6 +19,7 @@ test.afterEach(async () => {
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
async function enableExperimental(page) {
diff --git a/tests/config-tariffs.spec.js b/tests/config-tariffs.spec.js
index 853c5f98e1..248ec31a80 100644
--- a/tests/config-tariffs.spec.js
+++ b/tests/config-tariffs.spec.js
@@ -1,7 +1,7 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
-const CONFIG_EMPTY = "config-empty.evcc.yaml";
+const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";
const CONFIG_WITH_TARIFFS = "config-with-tariffs.evcc.yaml";
test.use({ baseURL: baseUrl() });
@@ -15,6 +15,7 @@ const SELECT_ALL = "ControlOrMeta+KeyA";
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
async function enableExperimental(page) {
@@ -34,7 +35,7 @@ async function goToConfig(page) {
test.describe("tariffs", async () => {
test("tariffs not configured", async ({ page }) => {
- await start(CONFIG_EMPTY, "password.sql");
+ await start(CONFIG_GRID_ONLY, "password.sql");
await goToConfig(page);
await expect(page.getByTestId("tariffs")).toBeVisible();
@@ -44,30 +45,36 @@ test.describe("tariffs", async () => {
});
test("tariffs via ui", async ({ page }) => {
- await start(CONFIG_EMPTY, "password.sql");
+ await start(CONFIG_GRID_ONLY, "password.sql");
await goToConfig(page);
await page.getByTestId("tariffs").getByRole("button", { name: "edit" }).click();
const modal = await page.getByTestId("tariffs-modal");
await expect(modal).toBeVisible();
+ await page.waitForLoadState("networkidle");
// default content
await expect(modal).toContainText("# currency: EUR");
// clear and enter invalid yaml
await modal.locator(".monaco-editor .view-line").nth(0).click();
- await page.keyboard.press(SELECT_ALL);
- await page.keyboard.press("Backspace");
- await page.keyboard.press(SELECT_ALL);
- await page.keyboard.press("Backspace");
+
+ for (let i = 0; i < 4; i++) {
+ await page.keyboard.press(SELECT_ALL, { delay: 10 });
+ await page.keyboard.press("Backspace", { delay: 10 });
+ }
+
await page.keyboard.type("foo: bar\n");
await page.getByRole("button", { name: "Save" }).click();
await expect(modal.getByTestId("error")).toContainText("invalid keys: foo");
// clear and enter valid yaml
await modal.locator(".monaco-editor .view-line").nth(0).click();
- await page.keyboard.press(SELECT_ALL);
- await page.keyboard.press("Backspace");
+ for (let i = 0; i < 4; i++) {
+ await page.keyboard.press(SELECT_ALL, { delay: 10 });
+ await page.keyboard.press("Backspace", { delay: 10 });
+ }
+
await page.keyboard.type("currency: CHF\n");
await page.keyboard.type("grid:\n");
await page.keyboard.type(" type: fixed\n");
@@ -85,7 +92,7 @@ test.describe("tariffs", async () => {
.getByRole("button", { name: "Restart" });
await expect(restartButton).toBeVisible();
- await restart(CONFIG_EMPTY);
+ await restart(CONFIG_GRID_ONLY);
// restart done
await expect(restartButton).not.toBeVisible();
diff --git a/tests/config-vehicles.spec.js b/tests/config-vehicles.spec.js
index c35821e0f6..442154aa6c 100644
--- a/tests/config-vehicles.spec.js
+++ b/tests/config-vehicles.spec.js
@@ -1,13 +1,13 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, cleanRestart, baseUrl } from "./evcc";
-const CONFIG_EMPTY = "config-empty.evcc.yaml";
+const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";
const CONFIG_WITH_VEHICLE = "config-with-vehicle.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
- await start(CONFIG_EMPTY, "password.sql");
+ await start(CONFIG_GRID_ONLY, "password.sql");
});
test.afterAll(async () => {
await stop();
@@ -16,6 +16,7 @@ test.afterAll(async () => {
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
async function enableExperimental(page) {
@@ -101,7 +102,7 @@ test.describe("vehicles", async () => {
await expect(page.getByTestId("vehicle")).toHaveCount(2);
// restart evcc
- await restart(CONFIG_EMPTY);
+ await restart(CONFIG_GRID_ONLY);
await page.reload();
await expect(page.getByTestId("vehicle")).toHaveCount(2);
@@ -129,4 +130,41 @@ test.describe("vehicles", async () => {
await expect(page.getByTestId("vehicle").nth(0)).toHaveText(/YAML Bike/);
await expect(page.getByTestId("vehicle").nth(1)).toHaveText(/Green Car/);
});
+
+ test("advanced fields", async ({ page }) => {
+ await page.goto("/#/config");
+ await login(page);
+ await enableExperimental(page);
+
+ await page.getByTestId("add-vehicle").click();
+ const vehicleModal = page.getByTestId("vehicle-modal");
+
+ // generic
+ await vehicleModal.getByLabel("Manufacturer").selectOption("Generic vehicle");
+ await expect(vehicleModal.getByLabel("Title")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Car")).toBeVisible(); // icon
+ await expect(vehicleModal.getByLabel("Battery capacity")).toBeVisible();
+
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await expect(vehicleModal.getByLabel("Default mode")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Maximum phases")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Minimum current")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Maximum current")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Priority")).toBeVisible();
+ await expect(vehicleModal.getByLabel("RFID identifiers")).toBeVisible();
+
+ await page.getByRole("button", { name: "Hide advanced settings" }).click();
+ await expect(vehicleModal.getByLabel("Default mode")).not.toBeVisible();
+
+ // polestar template
+ await vehicleModal.getByLabel("Manufacturer").selectOption("Polestar");
+ await expect(vehicleModal.getByLabel("Username")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Password")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Cache optional")).not.toBeVisible();
+ await expect(vehicleModal.getByLabel("Default mode")).not.toBeVisible();
+
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await expect(vehicleModal.getByLabel("Cache optional")).toBeVisible();
+ await expect(vehicleModal.getByLabel("Default mode")).toBeVisible();
+ });
});
diff --git a/tests/config.spec.js b/tests/config.spec.js
index ba326787fa..fb2a72134f 100644
--- a/tests/config.spec.js
+++ b/tests/config.spec.js
@@ -1,12 +1,12 @@
import { test, expect } from "@playwright/test";
import { start, stop, baseUrl } from "./evcc";
-const CONFIG_EMPTY = "config-empty.evcc.yaml";
+const CONFIG_GRID_ONLY = "config-grid-only.evcc.yaml";
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
- await start(CONFIG_EMPTY, "password.sql");
+ await start(CONFIG_GRID_ONLY, "password.sql");
});
test.afterAll(async () => {
await stop();
@@ -15,6 +15,7 @@ test.afterAll(async () => {
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
async function enableExperimental(page) {
diff --git a/tests/evcc.js b/tests/evcc.js
index 81309e7454..d36e53063b 100644
--- a/tests/evcc.js
+++ b/tests/evcc.js
@@ -7,17 +7,21 @@ import path from "path";
const BINARY = "./evcc";
-function port() {
+function workerPort() {
const index = process.env.TEST_WORKER_INDEX * 1;
return 11000 + index;
}
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
export function baseUrl() {
- return `http://localhost:${port()}`;
+ return `http://localhost:${workerPort()}`;
}
function dbPath() {
- const file = `evcc-${port()}.db`;
+ const file = `evcc-${workerPort()}.db`;
return path.join(os.tmpdir(), file);
}
@@ -26,11 +30,11 @@ export async function start(config, sqlDumps) {
if (sqlDumps) {
await _restoreDatabase(sqlDumps);
}
- await _start(config);
+ return await _start(config);
}
-export async function stop() {
- await _stop();
+export async function stop(instance) {
+ await _stop(instance);
await _clean();
}
@@ -58,27 +62,39 @@ async function _restoreDatabase(sqlDumps) {
async function _start(config) {
const configFile = config.includes("/") ? config : `tests/${config}`;
- console.log("starting evcc", { config });
+ const port = workerPort();
+ console.log(`wait until port ${port} is available`);
+ await waitOn({ resources: [`tcp:localhost:${port}`], reverse: true });
+ console.log("starting evcc", { config, port });
const instance = exec(
- `EVCC_NETWORK_PORT=${port()} EVCC_DATABASE_DSN=${dbPath()} ${BINARY} --config ${configFile}`
+ `EVCC_NETWORK_PORT=${port} EVCC_DATABASE_DSN=${dbPath()} ${BINARY} --config ${configFile}`
);
instance.stdout.pipe(process.stdout);
instance.stderr.pipe(process.stderr);
instance.on("exit", (code) => {
- console.log("evcc terminated", code);
+ console.log("evcc terminated", { code, port, config });
});
await waitOn({ resources: [baseUrl()] });
+ return instance;
}
-async function _stop() {
- console.log("shutting down evcc");
+async function _stop(instance) {
+ if (instance) {
+ console.log("shutting down evcc hard");
+ // hard kill, only use of normal shutdown doesn't work
+ instance.kill("SIGKILL");
+ await sleep(300);
+ return;
+ }
+ const port = workerPort();
+ console.log("shutting down evcc", { port });
const res = await axios.post(`${baseUrl()}/api/auth/login`, { password: "secret" });
console.log(res.status, res.statusText);
const cookie = res.headers["set-cookie"];
await axios.post(`${baseUrl()}/api/system/shutdown`, {}, { headers: { cookie } });
- console.log("wait until network port is closed");
- await waitOn({ resources: [`tcp:localhost:${port()}`], reverse: true });
- console.log("evcc is down");
+ console.log(`wait until port ${port} is closed`);
+ await waitOn({ resources: [`tcp:localhost:${port}`], reverse: true });
+ console.log("evcc is down", { port });
}
async function _clean() {
diff --git a/tests/fatal-db.evcc.yaml b/tests/fatal-db.evcc.yaml
new file mode 100755
index 0000000000..f86240d930
--- /dev/null
+++ b/tests/fatal-db.evcc.yaml
@@ -0,0 +1,6 @@
+site:
+ title: Hello World
+
+database:
+ type: sqliteInvalid
+ dsn: /path/to/db
diff --git a/tests/fatal-syntax.evcc.yaml b/tests/fatal-syntax.evcc.yaml
new file mode 100755
index 0000000000..09bb239e24
--- /dev/null
+++ b/tests/fatal-syntax.evcc.yaml
@@ -0,0 +1,2 @@
+s!ite:
+ title: Hello World
diff --git a/tests/fatal.spec.js b/tests/fatal.spec.js
new file mode 100644
index 0000000000..ffd17be41a
--- /dev/null
+++ b/tests/fatal.spec.js
@@ -0,0 +1,23 @@
+import { test, expect } from "@playwright/test";
+import { start, stop, baseUrl } from "./evcc";
+
+test.use({ baseURL: baseUrl() });
+
+test.describe("fatal", async () => {
+ test("evcc yaml error", async ({ page }) => {
+ const instance = await start("fatal-syntax.evcc.yaml");
+ await page.goto("/");
+ await expect(page.getByTestId("bottom-banner")).toBeVisible();
+ await expect(page.getByTestId("bottom-banner")).toContainText("failed parsing config file");
+ await expect(page.getByTestId("generalconfig-password")).not.toBeVisible();
+ await stop(instance);
+ });
+ test("database error", async ({ page }) => {
+ const instance = await start("fatal-db.evcc.yaml");
+ await page.goto("/");
+ await expect(page.getByTestId("bottom-banner")).toBeVisible();
+ await expect(page.getByTestId("bottom-banner")).toContainText("invalid database");
+ await expect(page.getByTestId("generalconfig-password")).not.toBeVisible();
+ await stop(instance);
+ });
+});
diff --git a/tests/logs.spec.js b/tests/logs.spec.js
index 7df2a289eb..6ac4e44123 100644
--- a/tests/logs.spec.js
+++ b/tests/logs.spec.js
@@ -13,6 +13,7 @@ test.afterAll(async () => {
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
test.describe("opening logs", async () => {
diff --git a/tests/modals.spec.js b/tests/modals.spec.js
index 7d6fe553a1..ddcbe85f07 100644
--- a/tests/modals.spec.js
+++ b/tests/modals.spec.js
@@ -11,6 +11,7 @@ test.use({ baseURL: baseUrl() });
async function login(page) {
await page.locator("#loginPassword").fill("secret");
await page.getByRole("button", { name: "Login" }).click();
+ await expect(page.locator("#loginPassword")).not.toBeVisible();
}
test.describe("Basics", async () => {
@@ -56,13 +57,13 @@ test.describe("Basics", async () => {
test.describe("Advanced", async () => {
test.beforeAll(async () => {
- await start(simulatorConfig(), "password.sql");
await startSimulator();
+ await start(simulatorConfig(), "password.sql");
});
test.afterAll(async () => {
- await stopSimulator();
await stop();
+ await stopSimulator();
});
test("Menu options. All available.", async ({ page }) => {
diff --git a/tests/plan.spec.js b/tests/plan.spec.js
index 92020e3084..c7e34ff5f8 100644
--- a/tests/plan.spec.js
+++ b/tests/plan.spec.js
@@ -62,7 +62,8 @@ test.describe("basic functionality", async () => {
"tomorrow 9:30 AM80%"
);
- await expect(lp1.getByTestId("vehicle-status")).toContainText("Charging plan starts at");
+ await expect(lp1.getByTestId("vehicle-status-charger")).toHaveText("Connected.");
+ await expect(lp1.getByTestId("vehicle-status-planstart")).toHaveText(/tomorrow .* AM/);
await expect(lp1.getByTestId("plan-marker")).toBeVisible();
await expect(lp1.getByTestId("charging-plan").getByRole("button")).toHaveText(
"tomorrow 9:30 AM80%"
@@ -277,9 +278,8 @@ test.describe("warnings", async () => {
await page.getByTestId("plan-active").click();
- await expect(page.getByTestId("plan-warnings")).toContainText(
- "Goal not reachable in time. Estimated finish"
- );
+ // match this text but with fuzzy date "getByText('Goal will be reached 52:10 h')"
+ await expect(page.getByTestId("plan-warnings")).toHaveText(/Goal will be reached .* later/);
});
test("time in the past", async ({ page }) => {
await page.goto("/");
diff --git a/tests/simulator.js b/tests/simulator.js
index 8e8ad7b8e1..328a842bc0 100644
--- a/tests/simulator.js
+++ b/tests/simulator.js
@@ -5,13 +5,13 @@ import waitOn from "wait-on";
import axios from "axios";
import { exec } from "child_process";
-function port() {
- const index = process.env.TEST_PARALLEL_INDEX * 1;
+function workerPort() {
+ const index = process.env.TEST_WORKER_INDEX * 1;
return 12000 + index;
}
export function simulatorHost() {
- return `localhost:${port()}`;
+ return `localhost:${workerPort()}`;
}
export function simulatorUrl() {
@@ -29,9 +29,11 @@ export function simulatorConfig() {
}
export async function startSimulator() {
- console.log("starting simulator");
- const instance = exec(`npm run simulator -- --port ${port()}`);
- console.log("exec end");
+ const port = workerPort();
+ console.log("starting simulator", { port });
+ console.log(`wait until port ${port} is available`);
+ await waitOn({ resources: [`tcp:localhost:${port}`], reverse: true });
+ const instance = exec(`npm run simulator -- --port ${port}`);
instance.stdout.pipe(process.stdout);
instance.stderr.pipe(process.stderr);
@@ -40,11 +42,13 @@ export async function startSimulator() {
throw new Error("simulator terminated", code);
}
});
- console.log("waiton");
await waitOn({ resources: [`${simulatorUrl()}/api/state`], log: true });
}
export async function stopSimulator() {
- console.log("shutting down simulator");
+ const port = workerPort();
+ console.log("shutting down simulator", { port });
await axios.post(`${simulatorUrl()}/api/shutdown`);
+ console.log(`wait until port ${port} is closed`);
+ await waitOn({ resources: [`tcp:localhost:${port}`], reverse: true });
}
diff --git a/tests/simulator/api.js b/tests/simulator/api.js
index 0a76b69abd..3a13a39424 100644
--- a/tests/simulator/api.js
+++ b/tests/simulator/api.js
@@ -12,11 +12,11 @@ let state = {
const stateApiMiddleware = (req, res, next) => {
if (req.method === "POST" && req.originalUrl === "/api/state") {
- console.log("POST /api/state", req.body);
+ console.log("[simulator] POST /api/state", req.body);
state = req.body;
res.end();
} else if (req.method === "POST" && req.originalUrl === "/api/shutdown") {
- console.log("POST /api/shutdown", req.body);
+ console.log("[simulator] POST /api/shutdown", req.body);
res.end();
process.exit();
} else if (req.originalUrl === "/api/state") {
@@ -35,7 +35,7 @@ const openemsMiddleware = (req, res, next) => {
};
const endpoint = endpoints[req.originalUrl];
if (req.method === "GET" && endpoint) {
- console.log("GET", req.originalUrl, endpoint);
+ console.log("[simulator] GET", req.originalUrl, endpoint);
res.end(JSON.stringify(endpoint));
} else {
next();
@@ -46,7 +46,7 @@ export default () => ({
name: "api",
enforce: "pre",
configureServer(server) {
- console.log("configureServer");
+ console.log("[simulator] configured");
return () => {
server.middlewares.use(bodyParser.json());
server.middlewares.use(stateApiMiddleware);
diff --git a/tests/smart-cost-only.evcc.yaml b/tests/smart-cost-only.evcc.yaml
new file mode 100755
index 0000000000..10677a225b
--- /dev/null
+++ b/tests/smart-cost-only.evcc.yaml
@@ -0,0 +1,44 @@
+interval: 0.1s
+
+site:
+ title: Smart Cost, No Grid & PV
+
+loadpoints:
+ - title: Loadpoint
+ charger: charger
+ meter: meter
+
+meters:
+ - name: meter
+ type: custom
+ power:
+ source: js
+ script: |
+ 11000
+
+chargers:
+ - name: charger
+ type: custom
+ enable:
+ source: js
+ script:
+ enabled:
+ source: js
+ script: |
+ true
+ status:
+ source: js
+ script: |
+ "C"
+ maxcurrent:
+ source: js
+ script:
+
+tariffs:
+ currency: EUR
+ grid:
+ type: fixed
+ price: 0.4 # EUR/kWh
+ zones:
+ - hours: 1-6
+ price: 0.2
diff --git a/tests/smart-cost-only.spec.js b/tests/smart-cost-only.spec.js
new file mode 100644
index 0000000000..bb890f70ce
--- /dev/null
+++ b/tests/smart-cost-only.spec.js
@@ -0,0 +1,31 @@
+import { test, expect } from "@playwright/test";
+import { start, stop, baseUrl } from "./evcc";
+
+test.use({ baseURL: baseUrl() });
+
+test.beforeAll(async () => {
+ await start("smart-cost-only.evcc.yaml", "password.sql");
+});
+test.afterAll(async () => {
+ await stop();
+});
+
+test.beforeEach(async ({ page }) => {
+ await page.goto("/");
+});
+
+test.describe("main screen", async () => {
+ test("smart mode", async ({ page }) => {
+ await expect(page.getByRole("button", { name: "Off" })).toBeVisible();
+ await expect(page.getByRole("button", { name: "Smart" })).toBeVisible();
+ await expect(page.getByRole("button", { name: "Fast" })).toBeVisible();
+ });
+
+ test("no production and feedin", async ({ page }) => {
+ await page.getByTestId("energyflow").click();
+ await expect(page.getByTestId("energyflow-entry-gridimport")).toBeVisible();
+ await expect(page.getByTestId("energyflow-entry-home")).not.toBeVisible();
+ await expect(page.getByTestId("energyflow-entry-loadpoints")).toBeVisible();
+ await expect(page.getByTestId("energyflow-entry-gridexport")).not.toBeVisible();
+ });
+});
diff --git a/tests/smart-cost.spec.js b/tests/smart-cost.spec.js
index a0f3ae589a..d9110ef37e 100644
--- a/tests/smart-cost.spec.js
+++ b/tests/smart-cost.spec.js
@@ -5,12 +5,12 @@ import { startSimulator, stopSimulator, simulatorUrl, simulatorConfig } from "./
test.use({ baseURL: baseUrl() });
test.beforeAll(async () => {
- await start(simulatorConfig(), "password.sql");
await startSimulator();
+ await start(simulatorConfig(), "password.sql");
});
test.afterAll(async () => {
- await stopSimulator();
await stop();
+ await stopSimulator();
});
test.beforeEach(async ({ page }) => {
@@ -25,7 +25,7 @@ test.beforeEach(async ({ page }) => {
test.describe("smart cost limit", async () => {
test("no limit, normal charging", async ({ page }) => {
await page.goto("/");
- await expect(page.getByTestId("vehicle-status")).toHaveText("Charging…");
+ await expect(page.getByTestId("vehicle-status-charger")).toHaveText("Charging…");
});
test("price below limit", async ({ page }) => {
await page.goto("/");
@@ -37,8 +37,8 @@ test.describe("smart cost limit", async () => {
.selectOption("≤ 40.0 ct/kWh");
await page.getByTestId("loadpoint-settings-modal").getByLabel("Close").click();
await expect(page.getByTestId("loadpoint-settings-modal")).not.toBeVisible();
- await expect(page.getByTestId("vehicle-status")).toContainText("Charging cheap energy");
- await expect(page.getByTestId("vehicle-status")).toContainText("(limit 40.0 ct)");
+ await expect(page.getByTestId("vehicle-status-charger")).toHaveText("Charging…");
+ await expect(page.getByTestId("vehicle-status-smartcost")).toHaveText(/[24]0\.0 ct ≤ 40\.0 ct/);
});
test("price above limit", async ({ page }) => {
await page.goto("/");
@@ -50,6 +50,7 @@ test.describe("smart cost limit", async () => {
.selectOption("≤ 10.0 ct/kWh");
await page.getByTestId("loadpoint-settings-modal").getByLabel("Close").click();
await expect(page.getByTestId("loadpoint-settings-modal")).not.toBeVisible();
- await expect(page.getByTestId("vehicle-status")).toHaveText("Charging…");
+ await expect(page.getByTestId("vehicle-status-charger")).toHaveText("Charging…");
+ await expect(page.getByTestId("vehicle-status-smartcost")).toHaveText("≤ 10.0 ct");
});
});
diff --git a/tests/vehicle-settings.spec.js b/tests/vehicle-settings.spec.js
index 42940af6f5..86b8199fd8 100644
--- a/tests/vehicle-settings.spec.js
+++ b/tests/vehicle-settings.spec.js
@@ -44,7 +44,7 @@ test.describe("minSoc", async () => {
await expect(page.getByText("charged to 20% in solar mode")).toBeVisible();
});
- test("show minsoc instead of plan when minsoc is active", async ({ page }) => {
+ test("show minsoc indicator when minsoc is active", async ({ page }) => {
await page.goto("/");
await expect(page.getByTestId("charging-plan")).toContainText("Plan");
@@ -53,14 +53,14 @@ test.describe("minSoc", async () => {
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("50%");
await page.getByRole("button", { name: "Close" }).click();
- await expect(page.getByTestId("charging-plan")).toContainText("Min charge");
- await expect(page.getByTestId("vehicle-status")).toHaveText("Minimum charging to 50%.");
- await page.getByTestId("charging-plan").getByRole("button", { name: "50%" }).click();
+ await expect(page.getByTestId("vehicle-status-minsoc")).toBeVisible();
+ await expect(page.getByTestId("vehicle-status-minsoc")).toHaveText("50%");
+ await page.getByTestId("vehicle-status-minsoc").click();
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("---");
await page.getByRole("button", { name: "Close" }).click();
- await expect(page.getByTestId("charging-plan")).toContainText("Plan");
- await expect(page.getByTestId("charging-plan").getByRole("button")).toHaveText("none");
+
+ await expect(page.getByTestId("vehicle-status-minsoc")).not.toBeVisible();
});
});
diff --git a/util/format.go b/util/format.go
index 30938b35a7..01e196295c 100644
--- a/util/format.go
+++ b/util/format.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/go-sprout/sprout"
+ "github.com/samber/lo"
"golang.org/x/exp/maps"
)
@@ -85,7 +86,7 @@ func ReplaceFormatted(s string, kv map[string]interface{}) (string, error) {
if val == nil {
wanted = append(wanted, key)
format = "%s"
- val = PtrTo(any("?"))
+ val = lo.ToPtr(any("?"))
}
// update all literal matches
diff --git a/util/metering.go b/util/metering.go
index c054b86f0b..f24d67c934 100644
--- a/util/metering.go
+++ b/util/metering.go
@@ -1,7 +1,7 @@
package util
// SignFromPower is a helper function to create signed current from signed power bypassing already signed current
-func SignFromPower(current float64, power float64) float64 {
+func SignFromPower(current, power float64) float64 {
if current > 0 && power < 0 {
return -current
}
diff --git a/util/ptr.go b/util/ptr.go
deleted file mode 100644
index 5525b75452..0000000000
--- a/util/ptr.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package util
-
-// PtrTo returns a pointer to the value passed as argument. The zero is returned as nil.
-func PtrTo[T comparable](v T) *T {
- var zero T
- if v == zero {
- return nil
- }
- return &v
-}
-
-// PtrToWithZero returns a pointer to the value passed as argument, including its zero value.
-func PtrToWithZero[T any](v T) *T {
- return &v
-}
diff --git a/util/templates/defaults.yaml b/util/templates/defaults.yaml
index d684041717..93982c6984 100644
--- a/util/templates/defaults.yaml
+++ b/util/templates/defaults.yaml
@@ -289,6 +289,14 @@ params:
help:
en: The AIN is printed on the type label on the back of the device. Embed it in double quotes in case of leading zeroes.
de: Die AIN ist auf dem Typenschild auf der Geräterückseite aufgedruckt. Bei führenden Nullen bitte in doppelte Hochkommata setzen.
+ - name: welcomecharge
+ type: bool
+ description:
+ en: Charge on connection
+ de: Laden bei Verbindung
+ help:
+ en: Charger will enable charging for short time when vehicle is connected, irrespective of configured charge mode. This is useful for vehicles that require power supply when connecting.
+ de: Wallbox gibt kurzzeitige Ladefreigabe bei Fahrzeugverbindung. Das ermöglicht es Fahrzeugen, die eine Stromversorgung beim Anschließen benötigen, einen Fehlerzustand zu vermeiden.
presets:
vehicle-base:
@@ -340,7 +348,11 @@ presets:
eebus:
params:
- name: ski
+ help:
+ de: SKI (Subject Key Identifier)
+ en: SKI (Subject Key Identifier)
- name: ip
+ deprecated: true
switchsocket:
params:
- name: standbypower
diff --git a/util/templates/documentation.go b/util/templates/documentation.go
index 0308690a51..004d7a18d9 100644
--- a/util/templates/documentation.go
+++ b/util/templates/documentation.go
@@ -44,7 +44,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
var modbusRender string
if modbusChoices := t.ModbusChoices(); len(modbusChoices) > 0 {
if i, _ := t.ParamByName(ParamModbus); i > -1 {
- modbusTmpl, err := template.New("yaml").Funcs(sprout.TxtFuncMap()).Parse(documentationModbusTmpl)
+ modbusTmpl, err := template.New("yaml").Funcs(sprout.FuncMap()).Parse(documentationModbusTmpl)
if err != nil {
panic(err)
}
diff --git a/util/templates/includes/eebus.tpl b/util/templates/includes/eebus.tpl
index db81f30cd0..dd3514cc5f 100644
--- a/util/templates/includes/eebus.tpl
+++ b/util/templates/includes/eebus.tpl
@@ -1,5 +1,4 @@
{{ define "eebus" }}
type: eebus
ski: {{ .ski }}
-{{ if .ip }}ip: {{ .ip }}{{ end }}
{{- end}}
diff --git a/util/templates/includes/vehicle-base.tpl b/util/templates/includes/vehicle-base.tpl
index fd9a103e82..4713136040 100644
--- a/util/templates/includes/vehicle-base.tpl
+++ b/util/templates/includes/vehicle-base.tpl
@@ -1,5 +1,6 @@
{{ define "vehicle-base" }}
user: {{ .user }}
password: {{ .password }}
+vin: {{ .vin }}
{{ template "vehicle-common" . }}
{{- end }}
diff --git a/util/templates/includes/vehicle-common.tpl b/util/templates/includes/vehicle-common.tpl
index f9c5457fbc..01cc4fd644 100644
--- a/util/templates/includes/vehicle-common.tpl
+++ b/util/templates/includes/vehicle-common.tpl
@@ -8,9 +8,6 @@ icon: {{ .icon }}
{{- if .capacity }}
capacity: {{ .capacity }}
{{- end }}
-{{- if .vin }}
-vin: {{ .vin }}
-{{- end }}
{{- if .phases }}
phases: {{ .phases }}
{{- end }}
diff --git a/util/templates/template.go b/util/templates/template.go
index 519b9d4618..b982a8f291 100644
--- a/util/templates/template.go
+++ b/util/templates/template.go
@@ -208,7 +208,7 @@ var proxyTmpl string
// RenderProxyWithValues renders the proxy template
func (t *Template) RenderProxyWithValues(values map[string]interface{}, lang string) ([]byte, error) {
- tmpl, err := template.New("yaml").Funcs(sprout.TxtFuncMap()).Parse(proxyTmpl)
+ tmpl, err := template.New("yaml").Funcs(sprout.FuncMap()).Parse(proxyTmpl)
if err != nil {
panic(err)
}
diff --git a/util/templates/types.go b/util/templates/types.go
index 52510e1c47..8a598bb221 100644
--- a/util/templates/types.go
+++ b/util/templates/types.go
@@ -133,7 +133,6 @@ func (t *TextLanguage) MarshalJSON() (out []byte, err error) {
type Requirements struct {
EVCC []string // EVCC requirements, e.g. sponsorship
Description TextLanguage // Description of requirements, e.g. how the device needs to be prepared
- URI string // URI to a webpage with more details about the preparation requirements
}
// Linked Template
@@ -175,7 +174,6 @@ type Param struct {
ValidValues []string `json:",omitempty"` // list of valid values the user can provide
Choice []string `json:",omitempty"` // defines a set of choices, e.g. "grid", "pv", "battery", "charge" for "usage"
AllInOne *bool `json:"-"` // defines if the defined usages can all be present in a single device
- Requirements Requirements `json:"-"` // requirements for this param to be usable, only supported via Type "bool"
// TODO move somewhere else should not be part of the param definition
Baudrate int `json:",omitempty"` // device specific default for modbus RS485 baudrate
diff --git a/util/templates/utils.go b/util/templates/utils.go
index 36b5a73c5d..3058743e70 100644
--- a/util/templates/utils.go
+++ b/util/templates/utils.go
@@ -59,5 +59,5 @@ func FuncMap(tmpl *template.Template) *template.Template {
"urlEncode": url.QueryEscape,
}
- return tmpl.Funcs(sprout.TxtFuncMap()).Funcs(funcMap)
+ return tmpl.Funcs(sprout.FuncMap()).Funcs(funcMap)
}
diff --git a/vehicle/bmw/provider.go b/vehicle/bmw/provider.go
index 9d1dcbac69..6978347b2b 100644
--- a/vehicle/bmw/provider.go
+++ b/vehicle/bmw/provider.go
@@ -96,6 +96,18 @@ func (v *Provider) Odometer() (float64, error) {
return float64(res.State.CurrentMileage), nil
}
+var _ api.SocLimiter = (*Provider)(nil)
+
+// GetLimitSoc implements the api.SocLimiter interface
+func (v *Provider) GetLimitSoc() (int64, error) {
+ res, err := v.statusG()
+ if err != nil {
+ return 0, err
+ }
+
+ return res.State.ElectricChargingState.ChargingTarget, nil
+}
+
var _ api.Resurrector = (*Provider)(nil)
func (v *Provider) WakeUp() error {
diff --git a/vehicle/jlr.go b/vehicle/jlr.go
index b2af947559..41b3cf9cd0 100644
--- a/vehicle/jlr.go
+++ b/vehicle/jlr.go
@@ -54,8 +54,7 @@ func NewJLRFromConfig(other map[string]interface{}) (api.Vehicle, error) {
log := util.NewLogger("jlr").Redact(cc.User, cc.Password, cc.VIN, cc.DeviceID)
if cc.DeviceID == "" {
- uid := uuid.New()
- cc.DeviceID = uid.String()
+ cc.DeviceID = uuid.NewString()
log.WARN.Println("new device id generated, add `deviceid` to config:", cc.DeviceID)
}
diff --git a/vehicle/mercedes/api.go b/vehicle/mercedes/api.go
index bb348cab78..1ba5546d5b 100644
--- a/vehicle/mercedes/api.go
+++ b/vehicle/mercedes/api.go
@@ -97,5 +97,21 @@ func (v *API) Status(vin string) (StatusResponse, error) {
res.EvInfo.Battery.ChargingStatus = 3
}
+ if val, ok := message.Attributes["selectedChargeProgram"]; ok && val != nil {
+ selectedChargeProgram := val.GetIntValue()
+ res.EvInfo.Battery.SelectedChargeProgram = int(selectedChargeProgram)
+
+ if cps, ok := message.Attributes["chargePrograms"]; ok && cps != nil {
+ if cpVal := cps.GetChargeProgramsValue(); cpVal != nil && res.EvInfo.Battery.SelectedChargeProgram < len(cpVal.ChargeProgramParameters) {
+ if chargeProgramParam := cpVal.ChargeProgramParameters[res.EvInfo.Battery.SelectedChargeProgram]; chargeProgramParam != nil {
+ res.EvInfo.Battery.SocLimit = int(chargeProgramParam.GetMaxSoc())
+ }
+ }
+ }
+ } else {
+ if val, ok := message.Attributes["maxSoc"]; ok && val != nil {
+ res.EvInfo.Battery.SocLimit = int(val.GetIntValue())
+ }
+ }
return res, err
}
diff --git a/vehicle/mercedes/identity.go b/vehicle/mercedes/identity.go
index b07adb315c..8404d3db3b 100644
--- a/vehicle/mercedes/identity.go
+++ b/vehicle/mercedes/identity.go
@@ -65,21 +65,20 @@ func NewIdentity(log *util.Logger, token *oauth2.Token, account string, region s
}
if !token.Valid() {
- v.log.DEBUG.Println("identity.NewIdentity - token not valid - Add expiry")
token.Expiry = time.Now().Add(time.Duration(10) * time.Second)
}
// database token
if !token.Valid() {
- v.log.DEBUG.Println("identity.NewIdentity - token not valid - database token check started")
var tok oauth2.Token
if err := settings.Json(v.settingsKey(), &tok); err == nil {
+ v.log.DEBUG.Println("identity.NewIdentity - database token found")
token = &tok
}
}
if !token.Valid() && token.RefreshToken != "" {
- v.log.DEBUG.Println("identity.NewIdentity - token not valid - refreshToken started")
+ v.log.DEBUG.Println("identity.NewIdentity - refreshToken started")
if tok, err := v.RefreshToken(token); err == nil {
token = tok
}
diff --git a/vehicle/mercedes/provider.go b/vehicle/mercedes/provider.go
index de2f5fc979..babbb57da2 100644
--- a/vehicle/mercedes/provider.go
+++ b/vehicle/mercedes/provider.go
@@ -42,6 +42,14 @@ func (v *Provider) Odometer() (float64, error) {
return float64(res.VehicleInfo.Odometer.Value), err
}
+var _ api.SocLimiter = (*Provider)(nil)
+
+// GetLimitSoc implements the api.SocLimiter interface
+func (v *Provider) GetLimitSoc() (int64, error) {
+ res, err := v.dataG()
+ return int64(res.EvInfo.Battery.SocLimit), err
+}
+
var _ api.ChargeState = (*Provider)(nil)
// Status implements the api.ChargeState interface
diff --git a/vehicle/mercedes/types.go b/vehicle/mercedes/types.go
index 01b0331266..98278087ca 100644
--- a/vehicle/mercedes/types.go
+++ b/vehicle/mercedes/types.go
@@ -60,9 +60,11 @@ type StatusResponse struct {
Value int
Unit string
}
- StateOfCharge float64 // 75
- EndOfChargeTime int // Minutes after midnight
- TotalRange int // 17
+ StateOfCharge float64 // 75
+ EndOfChargeTime int // Minutes after midnight
+ TotalRange int // 17
+ SocLimit int // 50-100
+ SelectedChargeProgram int
}
Timestamp time.Time
}
diff --git a/vehicle/renault/kamereon/api.go b/vehicle/renault/kamereon/api.go
index a7fdef270e..3991a2e97c 100644
--- a/vehicle/renault/kamereon/api.go
+++ b/vehicle/renault/kamereon/api.go
@@ -39,6 +39,7 @@ func New(log *util.Logger, keys keys.ConfigServer, identity *gigya.Identity, log
func (v *API) request_(uri string, body io.Reader) (Response, error) {
params := url.Values{"country": []string{"DE"}}
headers := map[string]string{
+ "content-type": "application/vnd.api+json",
"x-gigya-id_token": v.identity.Token,
"apikey": v.keys.APIKey,
}
diff --git a/vehicle/renault/kamereon/types.go b/vehicle/renault/kamereon/types.go
index 59762efc8a..2757488561 100644
--- a/vehicle/renault/kamereon/types.go
+++ b/vehicle/renault/kamereon/types.go
@@ -36,9 +36,10 @@ func (v *Vehicle) Available() error {
return errors.New("vehicle is not active")
}
- if v.ConnectedDriver.Role == "" {
- return errors.New("vehicle is not connected to driver")
- }
+ // DEPRECATED
+ // if v.ConnectedDriver.Role == "" {
+ // return errors.New("vehicle is not connected to driver")
+ // }
return nil
}
diff --git a/vehicle/renault/provider.go b/vehicle/renault/provider.go
index ad0a42682e..c523f5bf84 100644
--- a/vehicle/renault/provider.go
+++ b/vehicle/renault/provider.go
@@ -74,7 +74,7 @@ func (v *Provider) Status() (api.ChargeStatus, error) {
res, err := v.batteryG()
if err == nil {
- if res.Data.Attributes.PlugStatus > 0 {
+ if res.Data.Attributes.PlugStatus == 1 {
status = api.StatusB
}
if res.Data.Attributes.ChargingStatus >= 1.0 {
diff --git a/vehicle/seat/cupra/api.go b/vehicle/seat/cupra/api.go
index 3ba653033e..7d1a8eca9e 100644
--- a/vehicle/seat/cupra/api.go
+++ b/vehicle/seat/cupra/api.go
@@ -52,7 +52,23 @@ func (v *API) Vehicles(userID string) ([]Vehicle, error) {
// Status implements the /status response
func (v *API) Status(userID, vin string) (Status, error) {
var res Status
- uri := fmt.Sprintf("%s/v2/users/%s/vehicles/%s/mycar", BaseURL, userID, vin)
+ uri := fmt.Sprintf("%s/v5/users/%s/vehicles/%s/mycar", BaseURL, userID, vin)
+ err := v.GetJSON(uri, &res)
+ return res, err
+}
+
+// ParkingPosition implements the /parkingposition response
+func (v *API) ParkingPosition(vin string) (Position, error) {
+ var res Position
+ uri := fmt.Sprintf("%s/v1/vehicles/%s/parkingposition", BaseURL, vin)
+ err := v.GetJSON(uri, &res)
+ return res, err
+}
+
+// Mileage implements the /mileage response
+func (v *API) Mileage(vin string) (Mileage, error) {
+ var res Mileage
+ uri := fmt.Sprintf("%s/v1/vehicles/%s/mileage", BaseURL, vin)
err := v.GetJSON(uri, &res)
return res, err
}
diff --git a/vehicle/seat/cupra/provider.go b/vehicle/seat/cupra/provider.go
index 404d216687..ee06d70e60 100644
--- a/vehicle/seat/cupra/provider.go
+++ b/vehicle/seat/cupra/provider.go
@@ -10,8 +10,10 @@ import (
// Provider is an api.Vehicle implementation for Seat Cupra cars
type Provider struct {
- statusG func() (Status, error)
- action func(string, string) error
+ statusG func() (Status, error)
+ positionG func() (Position, error)
+ milageG func() (Mileage, error)
+ action func(string, string) error
}
// NewProvider creates a vehicle api provider
@@ -20,6 +22,12 @@ func NewProvider(api *API, userID, vin string, cache time.Duration) *Provider {
statusG: provider.Cached(func() (Status, error) {
return api.Status(userID, vin)
}, cache),
+ positionG: provider.Cached(func() (Position, error) {
+ return api.ParkingPosition(vin)
+ }, cache),
+ milageG: provider.Cached(func() (Mileage, error) {
+ return api.Mileage(vin)
+ }, cache),
action: func(action, cmd string) error {
return api.Action(vin, action, cmd)
},
@@ -32,7 +40,7 @@ var _ api.Battery = (*Provider)(nil)
// Soc implements the api.Vehicle interface
func (v *Provider) Soc() (float64, error) {
res, err := v.statusG()
- return res.Engines.Primary.Level, err
+ return res.Engines.Primary.LevelPct, err
}
var _ api.ChargeState = (*Provider)(nil)
@@ -81,15 +89,23 @@ var _ api.VehicleRange = (*Provider)(nil)
// Range implements the api.VehicleRange interface
func (v *Provider) Range() (int64, error) {
res, err := v.statusG()
- return int64(res.Engines.Primary.Range.Value), err
+ return int64(res.Engines.Primary.RangeKm), err
}
var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) {
- res, err := v.statusG()
- return res.Measurements.MileageKm, err
+ res, err := v.milageG()
+ return res.MileageKm, err
+}
+
+var _ api.VehiclePosition = (*Provider)(nil)
+
+// Position implements the api.VehiclePosition interface
+func (v *Provider) Position() (float64, float64, error) {
+ res, err := v.positionG()
+ return res.Lat, res.Lon, err
}
var _ api.VehicleClimater = (*Provider)(nil)
diff --git a/vehicle/seat/cupra/types.go b/vehicle/seat/cupra/types.go
index a65cb4a797..2177da6728 100644
--- a/vehicle/seat/cupra/types.go
+++ b/vehicle/seat/cupra/types.go
@@ -10,11 +10,8 @@ type Vehicle struct {
type Engine struct {
Type string
FuelType string
- Range struct {
- Value float64
- Unit string
- }
- Level float64
+ RangeKm float64
+ LevelPct float64
}
type Status struct {
@@ -28,6 +25,7 @@ type Status struct {
ChargeMode string
Active bool
RemainingTime int64
+ CurrentPct float64
ProgressBarPct float64
}
Climatisation struct {
@@ -41,3 +39,11 @@ type Status struct {
MileageKm float64
}
}
+
+type Mileage struct {
+ MileageKm float64
+}
+
+type Position struct {
+ Lat, Lon float64
+}
diff --git a/vehicle/tesla.go b/vehicle/tesla.go
index 8e10762751..fd5525968f 100644
--- a/vehicle/tesla.go
+++ b/vehicle/tesla.go
@@ -37,14 +37,16 @@ func init() {
// NewTeslaFromConfig creates a new vehicle
func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
cc := struct {
- embed `mapstructure:",squash"`
- Tokens Tokens
- VIN string
- Timeout time.Duration
- Cache time.Duration
+ embed `mapstructure:",squash"`
+ Tokens Tokens
+ VIN string
+ CommandProxy string
+ Cache time.Duration
+ Timeout time.Duration
}{
- Timeout: request.Timeout,
- Cache: interval,
+ CommandProxy: tesla.ProxyBaseUrl,
+ Cache: interval,
+ Timeout: request.Timeout,
}
if err := util.DecodeOther(other, &cc); err != nil {
@@ -107,12 +109,12 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
if err != nil {
return nil, err
}
- tcc.SetBaseUrl(tesla.ProxyBaseUrl)
+ tcc.SetBaseUrl(cc.CommandProxy)
v := &Tesla{
embed: &cc.embed,
Provider: tesla.NewProvider(vehicle, cc.Cache),
- Controller: tesla.NewController(vehicle, vehicle.WithClient(tcc)),
+ Controller: tesla.NewController(vehicle.WithClient(tcc)),
}
v.fromVehicle(vehicle.DisplayName, 0)
diff --git a/vehicle/tesla/api_test.go b/vehicle/tesla/api_test.go
index 19b02f359f..f677469254 100644
--- a/vehicle/tesla/api_test.go
+++ b/vehicle/tesla/api_test.go
@@ -40,5 +40,5 @@ func TestCommandResponse(t *testing.T) {
v, err := client.Vehicle("abc")
require.NoError(t, err)
- require.ErrorIs(t, NewController(v, v).ChargeEnable(true), api.ErrAsleep)
+ require.ErrorIs(t, NewController(v).ChargeEnable(true), api.ErrAsleep)
}
diff --git a/vehicle/tesla/controller.go b/vehicle/tesla/controller.go
index 749e229d6c..e9d1dc5690 100644
--- a/vehicle/tesla/controller.go
+++ b/vehicle/tesla/controller.go
@@ -3,10 +3,8 @@ package tesla
import (
"errors"
"slices"
- "time"
"github.com/evcc-io/evcc/api"
- "github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util/sponsor"
"github.com/evcc-io/tesla-proxy-client"
)
@@ -15,28 +13,14 @@ const ProxyBaseUrl = "https://tesla.evcc.io"
type Controller struct {
vehicle *tesla.Vehicle
- current int64
- dataG provider.Cacheable[float64]
}
// NewController creates a vehicle current and charge controller
-func NewController(ro, rw *tesla.Vehicle) *Controller {
+func NewController(vehicle *tesla.Vehicle) *Controller {
v := &Controller{
- vehicle: rw,
+ vehicle: vehicle,
}
- v.dataG = provider.ResettableCached(func() (float64, error) {
- if v.current >= 6 {
- // assume match above 6A to save API requests
- return float64(v.current), nil
- }
- res, err := ro.Data()
- if err != nil {
- return 0, apiError(err)
- }
- return float64(res.Response.ChargeState.ChargeAmps), nil
- }, time.Minute)
-
return v
}
@@ -48,19 +32,9 @@ func (v *Controller) MaxCurrent(current int64) error {
return api.ErrSponsorRequired
}
- v.current = current
- v.dataG.Reset()
-
return apiError(v.vehicle.SetChargingAmps(int(current)))
}
-var _ api.CurrentGetter = (*Controller)(nil)
-
-// StartCharge implements the api.VehicleChargeController interface
-func (v *Controller) GetMaxCurrent() (float64, error) {
- return v.dataG.Get()
-}
-
var _ api.ChargeController = (*Controller)(nil)
// ChargeEnable implements the api.ChargeController interface