diff --git a/assets/js/components/BatterySettingsModal.vue b/assets/js/components/BatterySettingsModal.vue index a5554035b9..fe5df78026 100644 --- a/assets/js/components/BatterySettingsModal.vue +++ b/assets/js/components/BatterySettingsModal.vue @@ -271,7 +271,7 @@ export default { batterySoc: Number, bufferStartSoc: Number, batteryDischargeControl: Boolean, - battery: { type: Array, default: () => [] }, + battery: Object, batteryGridChargeLimit: Number, smartCostType: String, tariffGrid: Number, @@ -302,7 +302,7 @@ export default { return options; }, controllable() { - return this.battery.some(({ controllable }) => controllable); + return Object.values(this.battery || {}).some(({ controllable }) => controllable); }, gridChargePossible() { return ( @@ -356,13 +356,13 @@ export default { return this.prioritySoc; }, batteryDetails() { - if (!Array.isArray(this.battery)) { - return; - } - return this.battery + const batteries = Object.values(this.battery || {}); + if (batteries.length === 0) return; + + return batteries .filter(({ capacity }) => capacity > 0) .map(({ soc, capacity }) => { - const multipleBatteries = this.battery.length > 1; + const multipleBatteries = batteries.length > 1; const energy = this.fmtKWh( (capacity / 100) * soc * 1e3, true, diff --git a/assets/js/components/Energyflow/Energyflow.vue b/assets/js/components/Energyflow/Energyflow.vue index c2bcc2034c..cfdb41623e 100644 --- a/assets/js/components/Energyflow/Energyflow.vue +++ b/assets/js/components/Energyflow/Energyflow.vue @@ -198,7 +198,6 @@ import formatter from "../../mixins/formatter"; import AnimatedNumber from "../AnimatedNumber.vue"; import settings from "../../settings"; import { CO2_TYPE } from "../../units"; -import collector from "../../mixins/collector"; export default { name: "Energyflow", @@ -207,17 +206,16 @@ export default { EnergyflowEntry, AnimatedNumber, }, - mixins: [formatter, collector], + mixins: [formatter], props: { gridConfigured: Boolean, gridPower: { type: Number, default: 0 }, homePower: { type: Number, default: 0 }, pvConfigured: Boolean, - pv: { type: Array }, + pv: { type: Object }, pvPower: { type: Number, default: 0 }, loadpointsCompact: { type: Array, default: () => [] }, batteryConfigured: { type: Boolean }, - battery: { type: Array }, batteryPower: { type: Number, default: 0 }, batterySoc: { type: Number, default: 0 }, batteryDischargeControl: { type: Boolean }, @@ -305,10 +303,9 @@ export default { return this.detailsOpen ? this.detailsCompleteHeight + "px" : 0; }, pvTooltip() { - if (!Array.isArray(this.pv) || this.pv.length <= 1) { - return; - } - return this.pv.map(({ power }) => this.fmtKw(power, this.powerInKw)); + const pvs = Object.values(this.pv || {}); + if (pvs.length <= 1) return; + return pvs.map(({ power }) => this.fmtKw(power, this.powerInKw)); }, batteryFmt() { return (soc) => this.fmtPercentage(soc, 0); diff --git a/assets/js/components/Site.vue b/assets/js/components/Site.vue index 7e904a7a5d..4b519636d1 100644 --- a/assets/js/components/Site.vue +++ b/assets/js/components/Site.vue @@ -68,14 +68,14 @@ export default { gridPower: Number, homePower: Number, pvPower: Number, - pv: Array, + pv: Object, batteryPower: Number, batterySoc: Number, batteryDischargeControl: Boolean, batteryGridChargeLimit: Number, batteryGridChargeActive: Boolean, batteryMode: String, - battery: Array, + battery: Object, gridCurrents: Array, prioritySoc: Number, bufferSoc: Number, @@ -106,10 +106,10 @@ export default { }, computed: { batteryConfigured: function () { - return this.battery?.length > 0; + return Object.keys(this.battery || {}).length > 0; }, pvConfigured: function () { - return this.pv?.length > 0; + return Object.keys(this.pv || {}).length > 0; }, energyflow: function () { return this.collectProps(Energyflow); diff --git a/assets/js/components/TopNavigation.vue b/assets/js/components/TopNavigation.vue index 7a1c6043ca..f923563e54 100644 --- a/assets/js/components/TopNavigation.vue +++ b/assets/js/components/TopNavigation.vue @@ -142,7 +142,7 @@ export default { return {}; }, }, - battery: Array, + battery: Object, fatal: Object, }, data() { @@ -152,7 +152,7 @@ export default { }, computed: { batteryConfigured: function () { - return this.battery?.length; + return Object.keys(this.battery || {}).length > 0; }, logoutCount() { return this.providerLogins.filter((login) => !login.loggedIn).length; diff --git a/assets/js/views/App.vue b/assets/js/views/App.vue index 74cc6d6c0d..569d900533 100644 --- a/assets/js/views/App.vue +++ b/assets/js/views/App.vue @@ -72,7 +72,7 @@ export default { return store.state.version; }, batteryModalAvailabe: function () { - return store.state.battery?.length; + return Object.keys(store.state.battery || {}).length > 0; }, globalSettingsProps: function () { return this.collectProps(GlobalSettingsModal, store.state); diff --git a/core/site.go b/core/site.go index f663c4e62d..e2d4ef8e02 100644 --- a/core/site.go +++ b/core/site.go @@ -78,12 +78,12 @@ type Site struct { MaxGridSupplyWhileBatteryCharging float64 `mapstructure:"maxGridSupplyWhileBatteryCharging"` // ignore battery charging if AC consumption is above this value // meters - circuit api.Circuit // Circuit - gridMeter api.Meter // Grid usage meter - pvMeters []api.Meter // PV generation meters - batteryMeters []api.Meter // Battery charging meters - extMeters []api.Meter // External meters - for monitoring only - auxMeters []api.Meter // Auxiliary meters + circuit api.Circuit // Circuit + gridMeter api.Meter // Grid usage meter + pvMeters map[string]api.Meter // PV generation meters + batteryMeters map[string]api.Meter // Battery charging meters + extMeters map[string]api.Meter // External meters - for monitoring only + auxMeters map[string]api.Meter // Auxiliary meters // battery settings prioritySoc float64 // prefer battery up to this Soc @@ -190,43 +190,47 @@ func (site *Site) Boot(log *util.Logger, loadpoints []*Loadpoint, tariffs *tarif } // multiple pv + site.pvMeters = make(map[string]api.Meter) for _, ref := range site.Meters.PVMetersRef { dev, err := config.Meters().ByName(ref) if err != nil { return err } - site.pvMeters = append(site.pvMeters, dev.Instance()) + site.pvMeters[ref] = dev.Instance() } // multiple batteries + site.batteryMeters = make(map[string]api.Meter) for _, ref := range site.Meters.BatteryMetersRef { dev, err := config.Meters().ByName(ref) if err != nil { return err } - site.batteryMeters = append(site.batteryMeters, dev.Instance()) + site.batteryMeters[ref] = dev.Instance() } if len(site.batteryMeters) > 0 && site.GetResidualPower() <= 0 { site.log.WARN.Println("battery configured but residualPower is missing or <= 0 (add residualPower: 100 to site), see https://docs.evcc.io/en/docs/reference/configuration/site#residualpower") } - // Meters used only for monitoring + // ext meters + site.extMeters = make(map[string]api.Meter) for _, ref := range site.Meters.ExtMetersRef { dev, err := config.Meters().ByName(ref) if err != nil { return err } - site.extMeters = append(site.extMeters, dev.Instance()) + site.extMeters[ref] = dev.Instance() } // auxiliary meters + site.auxMeters = make(map[string]api.Meter) for _, ref := range site.Meters.AuxMetersRef { dev, err := config.Meters().ByName(ref) if err != nil { return err } - site.auxMeters = append(site.auxMeters, dev.Instance()) + site.auxMeters[ref] = dev.Instance() } // revert battery mode on shutdown @@ -356,22 +360,30 @@ func (site *Site) DumpConfig() { site.log.INFO.Println(meterCapabilities("grid", site.gridMeter)) } - if len(site.pvMeters) > 0 { - for i, pv := range site.pvMeters { - site.log.INFO.Println(meterCapabilities(fmt.Sprintf("pv %d", i+1), pv)) - } + // TODO go 1.23 use sorted + for ref, pv := range site.pvMeters { + site.log.INFO.Println(meterCapabilities(fmt.Sprintf("pv %s", ref), pv)) } - if len(site.batteryMeters) > 0 { - for i, battery := range site.batteryMeters { - _, ok := battery.(api.Battery) - _, hasCapacity := battery.(api.BatteryCapacity) + // TODO go 1.23 use sorted + for ref, battery := range site.batteryMeters { + _, ok := battery.(api.Battery) + _, hasCapacity := battery.(api.BatteryCapacity) - site.log.INFO.Println( - meterCapabilities(fmt.Sprintf("battery %d", i+1), battery), - fmt.Sprintf("soc %s capacity %s", presence[ok], presence[hasCapacity]), - ) - } + site.log.INFO.Println( + meterCapabilities(fmt.Sprintf("battery %s", ref), battery), + fmt.Sprintf("soc %s capacity %s", presence[ok], presence[hasCapacity]), + ) + } + + // TODO go 1.23 use sorted + for ref, pv := range site.auxMeters { + site.log.INFO.Println(meterCapabilities(fmt.Sprintf("aux %s", ref), pv)) + } + + // TODO go 1.23 use sorted + for ref, ext := range site.extMeters { + site.log.INFO.Println(meterCapabilities(fmt.Sprintf("ext %s", ref), ext)) } if vehicles := site.Vehicles().Instances(); len(vehicles) > 0 { @@ -445,19 +457,19 @@ func (site *Site) updatePvMeters() { site.pvPower = 0 - mm := make([]meterMeasurement, len(site.pvMeters)) + mm := make(map[string]meterMeasurement, len(site.pvMeters)) - for i, meter := range site.pvMeters { + for ref, meter := range site.pvMeters { // pv power power, err := backoff.RetryWithData(meter.CurrentPower, bo()) if err == nil { // ignore negative values which represent self-consumption site.pvPower += max(0, power) if power < -500 { - site.log.WARN.Printf("pv %d power: %.0fW is negative - check configuration if sign is correct", i+1, power) + site.log.WARN.Printf("pv %s power: %.0fW is negative - check configuration if sign is correct", ref, power) } } else { - site.log.ERROR.Printf("pv %d power: %v", i+1, err) + site.log.ERROR.Printf("pv %s power: %v", ref, err) } // pv energy (production) @@ -467,11 +479,11 @@ func (site *Site) updatePvMeters() { if err == nil { totalEnergy += energy } else { - site.log.ERROR.Printf("pv %d energy: %v", i+1, err) + site.log.ERROR.Printf("pv %s energy: %v", ref, err) } } - mm[i] = meterMeasurement{ + mm[ref] = meterMeasurement{ Power: power, Energy: energy, } @@ -489,13 +501,13 @@ func (site *Site) updateExtMeters() { return } - mm := make([]meterMeasurement, len(site.extMeters)) + mm := make(map[string]meterMeasurement, len(site.extMeters)) - for i, meter := range site.extMeters { + for ref, meter := range site.extMeters { // ext power power, err := backoff.RetryWithData(meter.CurrentPower, bo()) if err != nil { - site.log.ERROR.Printf("ext meter %d power: %v", i+1, err) + site.log.ERROR.Printf("ext meter %s power: %v", ref, err) } // ext energy @@ -503,11 +515,11 @@ func (site *Site) updateExtMeters() { if m, ok := meter.(api.MeterEnergy); err == nil && ok { energy, err = m.TotalEnergy() if err != nil { - site.log.ERROR.Printf("ext meter %d energy: %v", i+1, err) + site.log.ERROR.Printf("ext meter %s energy: %v", ref, err) } } - mm[i] = meterMeasurement{ + mm[ref] = meterMeasurement{ Power: power, Energy: energy, } @@ -527,18 +539,18 @@ func (site *Site) updateBatteryMeters() error { site.batteryPower = 0 site.batterySoc = 0 - mm := make([]batteryMeasurement, len(site.batteryMeters)) + mm := make(map[string]batteryMeasurement, len(site.batteryMeters)) - for i, meter := range site.batteryMeters { + for ref, meter := range site.batteryMeters { power, err := backoff.RetryWithData(meter.CurrentPower, bo()) if err != nil { // power is required- return on error - return fmt.Errorf("battery %d power: %v", i+1, err) + return fmt.Errorf("battery %s power: %v", ref, err) } site.batteryPower += power if len(site.batteryMeters) > 1 { - site.log.DEBUG.Printf("battery %d power: %.0fW", i+1, power) + site.log.DEBUG.Printf("battery %s power: %.0fW", ref, power) } // battery energy (discharge) @@ -548,7 +560,7 @@ func (site *Site) updateBatteryMeters() error { if err == nil { totalEnergy += energy } else { - site.log.ERROR.Printf("battery %d energy: %v", i+1, err) + site.log.ERROR.Printf("battery %s energy: %v", ref, err) } } @@ -568,16 +580,16 @@ func (site *Site) updateBatteryMeters() error { site.batterySoc += weighedSoc if len(site.batteryMeters) > 1 { - site.log.DEBUG.Printf("battery %d soc: %.0f%%", i+1, batSoc) + site.log.DEBUG.Printf("battery %s soc: %.0f%%", ref, batSoc) } } else { - site.log.ERROR.Printf("battery %d soc: %v", i+1, err) + site.log.ERROR.Printf("battery %s soc: %v", ref, err) } } _, controllable := meter.(api.BatteryController) - mm[i] = batteryMeasurement{ + mm[ref] = batteryMeasurement{ Power: power, Energy: energy, Soc: batSoc, @@ -717,15 +729,17 @@ func (site *Site) sitePower(totalChargePower, flexiblePower float64) (float64, b // deduct smart loads if len(site.auxMeters) > 0 { var auxPower float64 - mm := make([]meterMeasurement, len(site.auxMeters)) + mm := make(map[string]meterMeasurement, len(site.auxMeters)) - for i, meter := range site.auxMeters { + for ref, meter := range site.auxMeters { if power, err := meter.CurrentPower(); err == nil { auxPower += power - mm[i].Power = power - site.log.DEBUG.Printf("aux power %d: %.0fW", i+1, power) + mm[ref] = meterMeasurement{ + Power: power, + } + site.log.DEBUG.Printf("aux %s power: %.0fW", ref, power) } else { - site.log.ERROR.Printf("aux meter %d: %v", i+1, err) + site.log.ERROR.Printf("aux %s power: %v", ref, err) } } @@ -733,7 +747,6 @@ func (site *Site) sitePower(totalChargePower, flexiblePower float64) (float64, b site.log.DEBUG.Printf("aux power: %.0fW", auxPower) site.publish(keys.AuxPower, auxPower) - site.publish(keys.Aux, mm) }