Replace combinatorial decorators with capability registry (BC) (#28565)

This commit is contained in:
Maschga 2026-03-27 09:17:19 +01:00 • committed by GitHub
parent 33d2de24c1
commit 00967eb57e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
101 changed files with 2416 additions and 44670 deletions

View file

@ -121,7 +121,7 @@ func New(log *util.Logger, title string, maxCurrent, maxPower float64, meter api
if maxCurrent == 0 {
c.log.DEBUG.Printf("validation of max phase current disabled")
} else if _, ok := meter.(api.PhaseCurrents); meter != nil && !ok {
} else if meter != nil && !api.HasCap[api.PhaseCurrents](meter) {
return nil, errors.New("meter does not support phase currents")
}
@ -263,7 +263,7 @@ func (c *Circuit) updateMeters() error {
return fmt.Errorf("circuit power: %w", err)
}
if phaseMeter, ok := c.meter.(api.PhaseCurrents); ok {
if phaseMeter, ok := api.Cap[api.PhaseCurrents](c.meter); ok {
var i1, i2, i3 float64
if err := backoff.Retry(func() error {
var err error
@ -275,7 +275,7 @@ func (c *Circuit) updateMeters() error {
}
var p1, p2, p3 float64
if phaseMeter, ok := c.meter.(api.PhasePowers); ok {
if phaseMeter, ok := api.Cap[api.PhasePowers](c.meter); ok {
var err error // phases needed for signed currents
if p1, p2, p3, err = phaseMeter.Powers(); err != nil {
return fmt.Errorf("circuit powers: %w", err)

View file

@ -116,7 +116,7 @@ func (c *Coordinator) availableDetectibleVehicles(owner loadpoint.API) []api.Veh
for _, vv := range c.vehicles {
// status api available
if _, ok := vv.(api.ChargeState); ok {
if api.HasCap[api.ChargeState](vv) {
// available or associated to current loadpoint
if o, ok := c.tracked[vv]; o == owner || !ok {
res = append(res, vv)
@ -135,7 +135,7 @@ func (c *Coordinator) identifyVehicleByStatus(available []api.Vehicle) api.Vehic
defer c.mu.RUnlock()
for _, vehicle := range available {
if vs, ok := vehicle.(api.ChargeState); ok {
if vs, ok := api.Cap[api.ChargeState](vehicle); ok {
status, err := vs.Status()
if err != nil {
if !loadpoint.AcceptableError(err) {

View file

@ -50,7 +50,7 @@ func ptrValueEqual[T comparable](a, b *T) bool {
// hasFeature returns true if features are supported and given feature present
func hasFeature(a any, f api.Feature) bool {
c, ok := a.(api.FeatureDescriber)
c, ok := api.Cap[api.FeatureDescriber](a)
return ok && slices.Contains(c.Features(), f)
}

View file

@ -403,7 +403,7 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) {
if lp.chargeMeter == nil {
integrated = true
if mt, ok := charger.(api.Meter); ok {
if mt, ok := api.Cap[api.Meter](charger); ok {
lp.chargeMeter = mt
} else {
mt := new(wrapper.ChargeMeter)
@ -416,7 +416,7 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) {
// ensure charge rater exists
// measurement are obtained from separate charge meter if defined
// (https://github.com/evcc-io/evcc/issues/2469)
if rt, ok := charger.(api.ChargeRater); ok && integrated {
if rt, ok := api.Cap[api.ChargeRater](charger); ok && integrated {
lp.chargeRater = rt
// when restarting in the middle of charging session, use this as negative offset
@ -433,7 +433,7 @@ func (lp *Loadpoint) configureChargerType(charger api.Charger) {
}
// ensure charge timer exists
if ct, ok := charger.(api.ChargeTimer); ok {
if ct, ok := api.Cap[api.ChargeTimer](charger); ok {
lp.chargeTimer = ct
} else {
ct := wrapper.NewChargeTimer()
@ -674,7 +674,7 @@ func (lp *Loadpoint) Prepare(site site.API, uiChan chan<- util.Param, pushChan c
}
// charger icon
if c, ok := lp.charger.(api.IconDescriber); ok {
if c, ok := api.Cap[api.IconDescriber](lp.charger); ok {
lp.publish(keys.ChargerIcon, c.Icon())
} else {
lp.publish(keys.ChargerIcon, nil)
@ -777,7 +777,7 @@ func (lp *Loadpoint) syncCharger() error {
)
// use chargers actual set current if available
cg, isCg := lp.charger.(api.CurrentGetter)
cg, isCg := api.Cap[api.CurrentGetter](lp.charger)
if isCg {
if current, err = cg.GetMaxCurrent(); err == nil {
// smallest adjustment most PWM-Controllers can do is: 100%÷256×0,6A = 0.234A
@ -806,7 +806,7 @@ func (lp *Loadpoint) syncCharger() error {
}
// sync phases
_, isPs := lp.charger.(api.PhaseSwitcher)
_, isPs := api.Cap[api.PhaseSwitcher](lp.charger)
if phases := lp.GetPhases(); isPs && shouldBeConsistent && phases > 0 {
// fallback to active phases from measured phases
chargerPhases := lp.measuredPhases
@ -814,7 +814,7 @@ func (lp *Loadpoint) syncCharger() error {
chargerPhases = 3
}
pg, isPg := lp.charger.(api.PhaseGetter)
pg, isPg := api.Cap[api.PhaseGetter](lp.charger)
if isPg {
if chargerPhases, err = pg.GetPhases(); err == nil {
if chargerPhases > 0 && chargerPhases != phases {
@ -858,8 +858,7 @@ func (lp *Loadpoint) syncCharger() error {
// coarseCurrent returns true if charger or vehicle require full amp steps
func (lp *Loadpoint) coarseCurrent() bool {
_, ok := lp.charger.(api.ChargerEx)
return !ok || lp.vehicleHasFeature(api.CoarseCurrent)
return !api.HasCap[api.ChargerEx](lp.charger) || lp.vehicleHasFeature(api.CoarseCurrent)
}
// roundedCurrent rounds current down to full amps if charger or vehicle require it
@ -902,7 +901,7 @@ func (lp *Loadpoint) setLimit(current float64) error {
// set current
if current != lp.offeredCurrent && current >= effMinCurrent {
var err error
if charger, ok := lp.charger.(api.ChargerEx); ok {
if charger, ok := api.Cap[api.ChargerEx](lp.charger); ok {
err = charger.MaxCurrentMillis(current)
} else {
err = lp.charger.MaxCurrent(int64(current))
@ -910,7 +909,7 @@ func (lp *Loadpoint) setLimit(current float64) error {
if err != nil {
v := lp.GetVehicle()
if vv, ok := v.(api.Resurrector); ok && errors.Is(err, api.ErrAsleep) {
if vv, ok := api.Cap[api.Resurrector](v); ok && errors.Is(err, api.ErrAsleep) {
// https://github.com/evcc-io/evcc/issues/8254
// wakeup vehicle
lp.log.DEBUG.Printf("set charge current limit: waking up vehicle")
@ -931,7 +930,7 @@ func (lp *Loadpoint) setLimit(current float64) error {
if enabled := current >= effMinCurrent; enabled != lp.enabled {
if err := lp.charger.Enable(enabled); err != nil {
v := lp.GetVehicle()
if vv, ok := v.(api.Resurrector); enabled && ok && errors.Is(err, api.ErrAsleep) {
if vv, ok := api.Cap[api.Resurrector](v); enabled && ok && errors.Is(err, api.ErrAsleep) {
// https://github.com/evcc-io/evcc/issues/8254
// wakeup vehicle
lp.log.DEBUG.Printf("charger %s: waking up vehicle", status[enabled])
@ -1140,7 +1139,7 @@ func (lp *Loadpoint) getStatusChanges() ([]api.ChargeStatus, error) {
}
// check charger connection duration
if ct, ok := lp.charger.(api.ConnectionTimer); ok {
if ct, ok := api.Cap[api.ConnectionTimer](lp.charger); ok {
d, err := ct.ConnectionDuration()
if err != nil {
return nil, fmt.Errorf("connection duration: %w", err)
@ -1252,7 +1251,7 @@ func (lp *Loadpoint) scalePhasesIfAvailable(phases int) error {
// scalePhases adjusts the number of active phases and returns the appropriate charging current.
// Returns api.ErrNotAvailable if api.PhaseSwitcher is not available.
func (lp *Loadpoint) scalePhases(phases int) error {
cp, ok := lp.charger.(api.PhaseSwitcher)
cp, ok := api.Cap[api.PhaseSwitcher](lp.charger)
if !ok {
panic("charger does not implement api.PhaseSwitcher")
}
@ -1589,7 +1588,7 @@ func (lp *Loadpoint) UpdateChargePowerAndCurrents() float64 {
// update charge currents
lp.chargeCurrents = nil
if phaseMeter, ok := lp.chargeMeter.(api.PhaseCurrents); ok {
if phaseMeter, ok := api.Cap[api.PhaseCurrents](lp.chargeMeter); ok {
if err := backoff.Retry(func() error {
i1, i2, i3, err := phaseMeter.Currents()
if err != nil {
@ -1642,7 +1641,7 @@ func (lp *Loadpoint) phasesFromChargeCurrents() {
// updateChargeVoltages uses PhaseVoltages interface to count phases with nominal grid voltage
func (lp *Loadpoint) updateChargeVoltages() {
phaseMeter, ok := lp.chargeMeter.(api.PhaseVoltages)
phaseMeter, ok := api.Cap[api.PhaseVoltages](lp.chargeMeter)
if !ok {
return // don't guess
}
@ -1716,7 +1715,7 @@ func (lp *Loadpoint) publishChargeProgress() {
// TODO deprecated: use sessionEnergy instead
lp.publish(keys.ChargedEnergy, lp.GetChargedEnergy())
lp.publish(keys.ChargeDuration, lp.chargeDuration)
if _, ok := lp.chargeMeter.(api.MeterEnergy); ok {
if api.HasCap[api.MeterEnergy](lp.chargeMeter) {
lp.publish(keys.ChargeTotalImport, lp.chargeMeterTotal())
}
}
@ -1739,14 +1738,14 @@ func (lp *Loadpoint) publishSocAndRange() {
var socR *float64
var limitR *int64
if battery, ok := dev.(api.Battery); ok {
if battery, ok := api.Cap[api.Battery](dev); ok {
if soc, err := soc.Guard(battery.Soc()); err == nil {
socR = &soc
// don't publish here in case it needs be updated by the estimator
lp.log.DEBUG.Printf("%s soc: %.0f%%", typ, soc)
if socLimiter, ok := dev.(api.SocLimiter); ok {
if socLimiter, ok := api.Cap[api.SocLimiter](dev); ok {
if limit, err := socLimiter.GetLimitSoc(); err == nil {
limitR = &limit
@ -1771,7 +1770,7 @@ func (lp *Loadpoint) publishSocAndRange() {
socR, limitR = socAndLimit("vehicle", lp.GetVehicle())
// range
if vs, ok := lp.GetVehicle().(api.VehicleRange); ok {
if vs, ok := api.Cap[api.VehicleRange](lp.GetVehicle()); ok {
if rng, err := vs.Range(); err == nil {
lp.log.DEBUG.Printf("vehicle range: %dkm", rng)
lp.publish(keys.VehicleRange, rng)
@ -1915,7 +1914,7 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, f
lp.PublishEffectiveValues()
// §14a
if dimmer, ok := lp.charger.(api.Dimmer); ok {
if dimmer, ok := api.Cap[api.Dimmer](lp.charger); ok {
dimmed, err := dimmer.Dimmed()
if err != nil {
lp.log.ERROR.Printf("dimmed: %v", err)
@ -1952,7 +1951,7 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, f
lp.resetHeatingSession()
if sr, ok := lp.charger.(api.StatusReasoner); ok && lp.GetStatus() == api.StatusB {
if sr, ok := api.Cap[api.StatusReasoner](lp.charger); ok && lp.GetStatus() == api.StatusB {
if r, err := sr.StatusReason(); err == nil {
lp.publish(keys.ChargerStatusReason, r)
} else {

View file

@ -155,7 +155,7 @@ func (lp *Loadpoint) effectiveMinCurrent() float64 {
}
}
if c, ok := lp.charger.(api.CurrentLimiter); ok {
if c, ok := api.Cap[api.CurrentLimiter](lp.charger); ok {
if res, _, err := c.GetMinMaxCurrent(); err == nil {
chargerMin = res
}
@ -181,7 +181,7 @@ func (lp *Loadpoint) effectiveMaxCurrent() float64 {
}
}
if c, ok := lp.charger.(api.CurrentLimiter); ok {
if c, ok := api.Cap[api.CurrentLimiter](lp.charger); ok {
if _, res, err := c.GetMinMaxCurrent(); err == nil && res > 0 {
maxCurrent = min(maxCurrent, res)
}

View file

@ -151,7 +151,7 @@ func (lp *Loadpoint) getVehiclePhases() int {
}
func (lp *Loadpoint) getChargerPhysicalPhases() int {
if cc, ok := lp.charger.(api.PhaseDescriber); ok {
if cc, ok := api.Cap[api.PhaseDescriber](lp.charger); ok {
return cc.Phases()
}
@ -159,6 +159,5 @@ func (lp *Loadpoint) getChargerPhysicalPhases() int {
}
func (lp *Loadpoint) hasPhaseSwitching() bool {
_, ok := lp.charger.(api.PhaseSwitcher)
return ok
return api.HasCap[api.PhaseSwitcher](lp.charger)
}

View file

@ -11,7 +11,7 @@ import (
)
func (lp *Loadpoint) chargeMeterTotal() float64 {
m, ok := lp.chargeMeter.(api.MeterEnergy)
m, ok := api.Cap[api.MeterEnergy](lp.chargeMeter)
if !ok {
return 0
}
@ -45,7 +45,7 @@ func (lp *Loadpoint) createSession() {
lp.session.Vehicle = lp.GetTitle()
}
if c, ok := lp.charger.(api.Identifier); ok {
if c, ok := api.Cap[api.Identifier](lp.charger); ok {
if id, err := c.Identify(); err == nil {
lp.session.Identifier = id
}

View file

@ -54,7 +54,7 @@ func (lp *Loadpoint) setVehicleIdentifier(id string) {
// identifyVehicle reads vehicle identification from charger
func (lp *Loadpoint) identifyVehicle() {
identifier, ok := lp.charger.(api.Identifier)
identifier, ok := api.Cap[api.Identifier](lp.charger)
if !ok {
return
}
@ -176,8 +176,8 @@ func (lp *Loadpoint) setActiveVehicle(v api.Vehicle) {
func (lp *Loadpoint) wakeUpVehicle() {
// wake up charger or vehicle. First wakeupAttemptsLeft will be odd.
charger, chargerCanWakeUp := lp.charger.(api.Resurrector)
vehicle, vehicleCanWakeUp := lp.GetVehicle().(api.Resurrector)
charger, chargerCanWakeUp := api.Cap[api.Resurrector](lp.charger)
vehicle, vehicleCanWakeUp := api.Cap[api.Resurrector](lp.GetVehicle())
if lp.wakeUpTimer.wakeupAttemptsLeft%2 != 0 {
if chargerCanWakeUp {
@ -296,14 +296,14 @@ func (lp *Loadpoint) identifyVehicleByStatus() {
}
// remove previous vehicle if status was not confirmed
if _, ok := lp.GetVehicle().(api.ChargeState); ok {
if api.HasCap[api.ChargeState](lp.GetVehicle()) {
lp.setActiveVehicle(nil)
}
}
// vehicleOdometer updates odometer
func (lp *Loadpoint) vehicleOdometer() {
if vs, ok := lp.GetVehicle().(api.VehicleOdometer); ok {
if vs, ok := api.Cap[api.VehicleOdometer](lp.GetVehicle()); ok {
if odo, err := vs.Odometer(); err == nil {
lp.log.DEBUG.Printf("vehicle odometer: %.0fkm", odo)
lp.publish(keys.VehicleOdometer, odo)
@ -361,7 +361,7 @@ func (lp *Loadpoint) vehicleSocPollAllowed() bool {
// vehicleClimateActive checks if vehicle has active climate request
func (lp *Loadpoint) vehicleClimateActive() bool {
if cl, ok := lp.GetVehicle().(api.VehicleClimater); ok && lp.vehicleClimatePollAllowed() {
if cl, ok := api.Cap[api.VehicleClimater](lp.GetVehicle()); ok && lp.vehicleClimatePollAllowed() {
active, err := cl.Climater()
if err == nil {
if active {

View file

@ -346,13 +346,14 @@ func (site *Site) restoreSettings() error {
}
func meterCapabilities(name string, meter any) string {
if _, ok := meter.(api.Meter); !ok {
power := api.HasCap[api.Meter](meter)
if !power {
panic("not a meter: " + name)
}
_, power := meter.(api.Meter)
_, energy := meter.(api.MeterEnergy)
_, currents := meter.(api.PhaseCurrents)
energy := api.HasCap[api.MeterEnergy](meter)
currents := api.HasCap[api.PhaseCurrents](meter)
name += ":"
return fmt.Sprintf(" %-10s power %s energy %s currents %s",
@ -368,7 +369,7 @@ func (site *Site) DumpConfig() {
// verify vehicle detection
if vehicles := site.Vehicles().Instances(); len(vehicles) > 1 {
for _, v := range vehicles {
if _, ok := v.(api.ChargeState); !ok && len(v.Identifiers()) == 0 {
if !api.HasCap[api.ChargeState](v) && len(v.Identifiers()) == 0 {
site.log.INFO.Printf("vehicle '%s' does not support automatic detection", v.GetTitle())
}
}
@ -394,12 +395,12 @@ func (site *Site) DumpConfig() {
if len(site.batteryMeters) > 0 {
for i, dev := range site.batteryMeters {
battery := dev.Instance()
_, ok := battery.(api.Battery)
_, hasCapacity := battery.(api.BatteryCapacity)
isBattery := api.HasCap[api.Battery](battery)
hasCapacity := api.HasCap[api.BatteryCapacity](battery)
site.log.INFO.Println(
meterCapabilities(fmt.Sprintf("battery %d", i+1), battery),
fmt.Sprintf("soc %s capacity %s", presence[ok], presence[hasCapacity]),
fmt.Sprintf("soc %s capacity %s", presence[isBattery], presence[hasCapacity]),
)
}
}
@ -408,11 +409,11 @@ func (site *Site) DumpConfig() {
site.log.INFO.Println(" vehicles:")
for i, v := range vehicles {
_, rng := v.(api.VehicleRange)
_, finish := v.(api.VehicleFinishTimer)
_, status := v.(api.ChargeState)
_, climate := v.(api.VehicleClimater)
_, wakeup := v.(api.Resurrector)
_, rng := api.Cap[api.VehicleRange](v)
_, finish := api.Cap[api.VehicleFinishTimer](v)
_, status := api.Cap[api.ChargeState](v)
_, climate := api.Cap[api.VehicleClimater](v)
_, wakeup := api.Cap[api.Resurrector](v)
site.log.INFO.Printf(" vehicle %d: range %s finish %s status %s climate %s wakeup %s",
i+1, presence[rng], presence[finish], presence[status], presence[climate], presence[wakeup],
)
@ -435,11 +436,11 @@ func (site *Site) DumpConfig() {
lp.log.INFO.Printf("loadpoint %d:", i+1)
lp.log.INFO.Printf(" mode: %s", lp.GetMode())
_, power := lp.charger.(api.Meter)
_, energy := lp.charger.(api.MeterEnergy)
_, currents := lp.charger.(api.PhaseCurrents)
_, phases := lp.charger.(api.PhaseSwitcher)
_, wakeup := lp.charger.(api.Resurrector)
_, power := api.Cap[api.Meter](lp.charger)
_, energy := api.Cap[api.MeterEnergy](lp.charger)
_, currents := api.Cap[api.PhaseCurrents](lp.charger)
_, phases := api.Cap[api.PhaseSwitcher](lp.charger)
_, wakeup := api.Cap[api.Resurrector](lp.charger)
lp.log.INFO.Printf(" charger: power %s energy %s currents %s phases %s wakeup %s",
presence[power],
@ -499,7 +500,7 @@ func (site *Site) collectMeters(key string, meters []config.Device[api.Meter]) [
// energy (production)
var energy float64
if m, ok := meter.(api.MeterEnergy); err == nil && ok {
if m, ok := api.Cap[api.MeterEnergy](meter); err == nil && ok {
energy, err = m.TotalEnergy()
if err != nil {
site.log.ERROR.Printf("%s %d energy: %v", key, i+1, err)
@ -543,7 +544,7 @@ func (site *Site) updatePvMeters() {
site.log.WARN.Printf("pv %d power: %.0fW is negative - check configuration if sign is correct", i+1, power)
}
if m, ok := meter.(api.MaxACPowerGetter); ok {
if m, ok := api.Cap[api.MaxACPowerGetter](meter); ok {
if dc := power - m.MaxACPower(); dc > 0 && power > 0 {
mm[i].ExcessDCPower = dc
site.log.DEBUG.Printf("pv %d excess DC: %.0fW", i+1, dc)
@ -611,13 +612,13 @@ func (site *Site) updateBatteryMeters() {
meter := dev.Instance()
// battery soc and capacity
if m, ok := meter.(api.Battery); ok {
if m, ok := api.Cap[api.Battery](meter); ok {
batSoc, err := soc.Guard(m.Soc())
if err == nil {
mm[i].Soc = new(batSoc)
if m, ok := m.(api.BatteryCapacity); ok {
mm[i].Capacity = new(m.Capacity())
if bc, ok := api.Cap[api.BatteryCapacity](meter); ok {
mm[i].Capacity = new(bc.Capacity())
}
site.log.DEBUG.Printf("battery %d soc: %.0f%%", i+1, batSoc)
@ -626,7 +627,7 @@ func (site *Site) updateBatteryMeters() {
}
}
_, controllable := meter.(api.BatteryController)
_, controllable := api.Cap[api.BatteryController](meter)
mm[i].Controllable = new(controllable)
}
@ -717,10 +718,10 @@ func (site *Site) updateGridMeter() error {
}
// grid phase currents (signed)
if phaseMeter, ok := site.gridMeter.(api.PhaseCurrents); ok {
if phaseMeter, ok := api.Cap[api.PhaseCurrents](site.gridMeter); ok {
// grid phase powers
var p1, p2, p3 float64
if phaseMeter, ok := site.gridMeter.(api.PhasePowers); ok {
if phaseMeter, ok := api.Cap[api.PhasePowers](site.gridMeter); ok {
var err error // phases needed for signed currents
if p1, p2, p3, err = phaseMeter.Powers(); err == nil {
mm.Powers = []float64{p1, p2, p3}
@ -739,7 +740,7 @@ func (site *Site) updateGridMeter() error {
}
// grid energy (import)
if energyMeter, ok := site.gridMeter.(api.MeterEnergy); ok {
if energyMeter, ok := api.Cap[api.MeterEnergy](site.gridMeter); ok {
if f, err := energyMeter.TotalEnergy(); err == nil {
mm.Energy = f
} else {

View file

@ -22,7 +22,7 @@ func (site *Site) hasBatteryControl() bool {
for _, dev := range site.batteryMeters {
meter := dev.Instance()
if _, ok := meter.(api.BatteryController); ok {
if api.HasCap[api.BatteryController](meter) {
return true
}
}
@ -117,12 +117,12 @@ func (site *Site) requiredBatteryMode(batteryGridChargeActive bool, rate api.Rat
func (site *Site) batteryMaxSocReached(dev config.Device[api.Meter]) (bool, error) {
meter := dev.Instance()
batLimiter, ok := meter.(api.BatterySocLimiter)
batLimiter, ok := api.Cap[api.BatterySocLimiter](meter)
if !ok {
return false, nil
}
batSoc, ok := meter.(api.Battery)
batSoc, ok := api.Cap[api.Battery](meter)
if !ok {
return false, errors.New("battery with soc limits must have soc")
}
@ -152,7 +152,7 @@ func (site *Site) applyBatteryMode(mode api.BatteryMode) error {
for _, dev := range site.batteryMeters {
meter := dev.Instance()
batCtrl, ok := meter.(api.BatteryController)
batCtrl, ok := api.Cap[api.BatteryController](meter)
if !ok {
continue
}

View file

@ -62,7 +62,7 @@ func (site *Site) dimMeters(dim bool) error {
var errs error
for _, dev := range slices.Concat(site.auxMeters, site.extMeters) {
m, ok := dev.Instance().(api.Dimmer)
m, ok := api.Cap[api.Dimmer](dev.Instance())
if !ok {
continue
}
@ -92,7 +92,7 @@ func (site *Site) curtailPV(curtail bool) error {
var errs error
for _, dev := range site.pvMeters {
m, ok := dev.Instance().(api.Curtailer)
m, ok := api.Cap[api.Curtailer](dev.Instance())
if !ok {
continue
}

View file

@ -422,17 +422,17 @@ func (site *Site) batteryRequest(dev config.Device[api.Meter], b types.Measureme
instance := dev.Instance()
if _, ok := instance.(api.BatteryController); ok {
if api.HasCap[api.BatteryController](instance) {
bat.ChargeFromGrid = true
}
if m, ok := instance.(api.BatteryPowerLimiter); ok {
if m, ok := api.Cap[api.BatteryPowerLimiter](instance); ok {
charge, discharge := m.GetPowerLimits()
bat.CMax = float32(charge)
bat.DMax = float32(discharge)
}
if m, ok := instance.(api.BatterySocLimiter); ok {
if m, ok := api.Cap[api.BatterySocLimiter](instance); ok {
minSoc, maxSoc := m.GetSocLimits()
bat.SMin = float32(*b.Capacity * minSoc * 10) // Wh
bat.SMax = float32(*b.Capacity * maxSoc * 10) // Wh

View file

@ -49,7 +49,7 @@ func (cr *ChargeRater) StartCharge(continued bool) {
cr.start = cr.clck.Now()
// get end energy amount
if m, ok := cr.meter.(api.MeterEnergy); ok {
if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok {
if f, err := m.TotalEnergy(); err == nil {
cr.startEnergy = f
cr.log.DEBUG.Printf("charge start energy: %.3fkWh", f)
@ -74,7 +74,7 @@ func (cr *ChargeRater) StopCharge() {
cr.charging = false
// get end energy amount
if m, ok := cr.meter.(api.MeterEnergy); ok {
if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok {
if f, err := m.TotalEnergy(); err == nil {
cr.chargedEnergy += f - cr.startEnergy
cr.log.DEBUG.Printf("charge final energy: %.3fkWh", cr.chargedEnergy)
@ -92,7 +92,7 @@ func (cr *ChargeRater) ResetCharge() {
defer cr.Unlock()
// get end energy amount
if m, ok := cr.meter.(api.MeterEnergy); ok {
if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok {
if f, err := m.TotalEnergy(); err == nil {
cr.chargedEnergy += f - cr.startEnergy
cr.log.DEBUG.Printf("charge final energy: %.3fkWh", cr.chargedEnergy)
@ -117,7 +117,7 @@ func (cr *ChargeRater) SetChargePower(power float64) {
}
// update energy amount if not provided by meter
if _, ok := cr.meter.(api.MeterEnergy); !ok {
if !api.HasCap[api.MeterEnergy](cr.meter) {
// convert power to energy in kWh
cr.chargedEnergy += power / 1e3 * float64(cr.clck.Since(cr.start)) / float64(time.Hour)
// move timestamp
@ -137,7 +137,7 @@ func (cr *ChargeRater) ChargedEnergy() (float64, error) {
}
// get current energy amount
if m, ok := cr.meter.(api.MeterEnergy); ok {
if m, ok := api.Cap[api.MeterEnergy](cr.meter); ok {
f, err := m.TotalEnergy()
if err == nil {
return cr.chargedEnergy + f - cr.startEnergy, nil