Add dedicated co2 tariff (#8089)
--------- Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
15b60863c1
commit
7c3ca53f02
39 changed files with 267 additions and 215 deletions
18
api/api.go
18
api/api.go
|
|
@ -201,26 +201,10 @@ type Resurrector interface {
|
|||
WakeUp() error
|
||||
}
|
||||
|
||||
// Rate is a grid tariff rate
|
||||
type Rate struct {
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
|
||||
// IsEmpty returns is the rate is the zero value
|
||||
func (r Rate) IsEmpty() bool {
|
||||
return r.Start.IsZero() && r.End.IsZero() && r.Price == 0
|
||||
}
|
||||
|
||||
// Rates is a slice of (future) tariff rates
|
||||
type Rates []Rate
|
||||
|
||||
// Tariff is a tariff capable of retrieving tariff rates
|
||||
type Tariff interface {
|
||||
Unit() string
|
||||
Rates() (Rates, error)
|
||||
IsDynamic() bool
|
||||
Type() TariffType
|
||||
}
|
||||
|
||||
// AuthProvider is the ability to provide OAuth authentication through the ui
|
||||
|
|
|
|||
15
api/rates.go
15
api/rates.go
|
|
@ -5,6 +5,21 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Rate is a grid tariff rate
|
||||
type Rate struct {
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
|
||||
// IsEmpty returns is the rate is the zero value
|
||||
func (r Rate) IsEmpty() bool {
|
||||
return r.Start.IsZero() && r.End.IsZero() && r.Price == 0
|
||||
}
|
||||
|
||||
// Rates is a slice of (future) tariff rates
|
||||
type Rates []Rate
|
||||
|
||||
// Current returns the rates current rate or error
|
||||
func (r Rates) Current(now time.Time) (Rate, error) {
|
||||
for _, rr := range r {
|
||||
|
|
|
|||
12
api/tariff.go
Normal file
12
api/tariff.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package api
|
||||
|
||||
//go:generate enumer -type TariffType -trimprefix TariffType -transform=lower
|
||||
|
||||
type TariffType int
|
||||
|
||||
const (
|
||||
_ TariffType = iota
|
||||
TariffTypePriceStatic
|
||||
TariffTypePriceDynamic
|
||||
TariffTypeCo2
|
||||
)
|
||||
83
api/tarifftype_enumer.go
Normal file
83
api/tarifftype_enumer.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Code generated by "enumer -type TariffType -trimprefix TariffType -transform=lower"; DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _TariffTypeName = "pricestaticpricedynamicco2"
|
||||
|
||||
var _TariffTypeIndex = [...]uint8{0, 11, 23, 26}
|
||||
|
||||
const _TariffTypeLowerName = "pricestaticpricedynamicco2"
|
||||
|
||||
func (i TariffType) String() string {
|
||||
i -= 1
|
||||
if i < 0 || i >= TariffType(len(_TariffTypeIndex)-1) {
|
||||
return fmt.Sprintf("TariffType(%d)", i+1)
|
||||
}
|
||||
return _TariffTypeName[_TariffTypeIndex[i]:_TariffTypeIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _TariffTypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[TariffTypePriceStatic-(1)]
|
||||
_ = x[TariffTypePriceDynamic-(2)]
|
||||
_ = x[TariffTypeCo2-(3)]
|
||||
}
|
||||
|
||||
var _TariffTypeValues = []TariffType{TariffTypePriceStatic, TariffTypePriceDynamic, TariffTypeCo2}
|
||||
|
||||
var _TariffTypeNameToValueMap = map[string]TariffType{
|
||||
_TariffTypeName[0:11]: TariffTypePriceStatic,
|
||||
_TariffTypeLowerName[0:11]: TariffTypePriceStatic,
|
||||
_TariffTypeName[11:23]: TariffTypePriceDynamic,
|
||||
_TariffTypeLowerName[11:23]: TariffTypePriceDynamic,
|
||||
_TariffTypeName[23:26]: TariffTypeCo2,
|
||||
_TariffTypeLowerName[23:26]: TariffTypeCo2,
|
||||
}
|
||||
|
||||
var _TariffTypeNames = []string{
|
||||
_TariffTypeName[0:11],
|
||||
_TariffTypeName[11:23],
|
||||
_TariffTypeName[23:26],
|
||||
}
|
||||
|
||||
// TariffTypeString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func TariffTypeString(s string) (TariffType, error) {
|
||||
if val, ok := _TariffTypeNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _TariffTypeNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to TariffType values", s)
|
||||
}
|
||||
|
||||
// TariffTypeValues returns all values of the enum
|
||||
func TariffTypeValues() []TariffType {
|
||||
return _TariffTypeValues
|
||||
}
|
||||
|
||||
// TariffTypeStrings returns a slice of all String values of the enum
|
||||
func TariffTypeStrings() []string {
|
||||
strs := make([]string, len(_TariffTypeNames))
|
||||
copy(strs, _TariffTypeNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsATariffType returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i TariffType) IsATariffType() bool {
|
||||
for _, v := range _TariffTypeValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -16,13 +16,25 @@ const hoursFromNow = function (hours) {
|
|||
<ChargingPlan id="1" :targetTime="hoursFromNow(14)" />
|
||||
</Variant>
|
||||
<Variant title="price EUR limit">
|
||||
<ChargingPlan id="1" :smartCostLimit="0.13" smartCostUnit="EUR" :tariffGrid="0.3" />
|
||||
<ChargingPlan
|
||||
id="1"
|
||||
:smartCostLimit="0.13"
|
||||
smartCostType="price"
|
||||
currency="EUR"
|
||||
:tariffGrid="0.3"
|
||||
/>
|
||||
</Variant>
|
||||
<Variant title="price SEK limit">
|
||||
<ChargingPlan id="1" :smartCostLimit="0.43" smartCostUnit="SEK" :tariffGrid="0.32" />
|
||||
<ChargingPlan
|
||||
id="1"
|
||||
:smartCostLimit="0.43"
|
||||
smartCostType="price"
|
||||
currency="SEK"
|
||||
:tariffGrid="0.32"
|
||||
/>
|
||||
</Variant>
|
||||
<Variant title="co2 limit">
|
||||
<ChargingPlan id="1" :smartCostLimit="400" smartCostUnit="gCO2eq" :tariffCo2="623" />
|
||||
<ChargingPlan id="1" :smartCostLimit="400" smartCostType="co2" :tariffCo2="623" />
|
||||
</Variant>
|
||||
<Variant title="min soc">
|
||||
<ChargingPlan id="1" :minSoc="30" :vehicleSoc="25" />
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ export default {
|
|||
vehicleSoc: Number,
|
||||
vehicleName: String,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
currency: String,
|
||||
},
|
||||
emits: ["target-time-updated", "target-time-removed", "minsoc-updated"],
|
||||
data: function () {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import Energyflow from "./Energyflow.vue";
|
|||
:tariffGrid="0.25"
|
||||
:tariffFeedIn="0.08"
|
||||
:tariffEffectivePrice="0.08"
|
||||
smartCostUnit="EUR"
|
||||
smartCostType="price"
|
||||
smartCostAvailable
|
||||
currency="EUR"
|
||||
siteTitle="Home"
|
||||
|
|
@ -147,7 +147,7 @@ import Energyflow from "./Energyflow.vue";
|
|||
:tariffEffectivePrice="0.08"
|
||||
:tariffCo2="723"
|
||||
:tariffEffectiveCo2="0"
|
||||
smartCostUnit="gCO2eq"
|
||||
smartCostType="co2"
|
||||
smartCostAvailable
|
||||
currency="EUR"
|
||||
siteTitle="Home"
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ import GridSettingsModal from "../GridSettingsModal.vue";
|
|||
import formatter from "../../mixins/formatter";
|
||||
import AnimatedNumber from "../AnimatedNumber.vue";
|
||||
import settings from "../../settings";
|
||||
import { CO2_UNIT } from "../../units";
|
||||
import { CO2_TYPE, PRICE_DYNAMIC_TYPE } from "../../units";
|
||||
import collector from "../../mixins/collector";
|
||||
import BatterySettingsModal from "../BatterySettingsModal.vue";
|
||||
|
||||
|
|
@ -197,9 +197,8 @@ export default {
|
|||
tariffEffectivePrice: { type: Number },
|
||||
tariffCo2: { type: Number },
|
||||
tariffEffectiveCo2: { type: Number },
|
||||
smartCostAvailable: { type: Boolean },
|
||||
smartCostLimit: { type: Number },
|
||||
smartCostUnit: { type: String },
|
||||
smartCostType: { type: String },
|
||||
currency: { type: String },
|
||||
prioritySoc: { type: Number },
|
||||
bufferSoc: { type: Number },
|
||||
|
|
@ -209,6 +208,9 @@ export default {
|
|||
return { detailsOpen: false, detailsCompleteHeight: null, gridSettingsModal: null };
|
||||
},
|
||||
computed: {
|
||||
smartCostAvailable: function () {
|
||||
return [CO2_TYPE, PRICE_DYNAMIC_TYPE].includes(this.smartCostType);
|
||||
},
|
||||
gridImport: function () {
|
||||
return Math.max(0, this.gridPower);
|
||||
},
|
||||
|
|
@ -257,7 +259,7 @@ export default {
|
|||
return this.collectProps(BatterySettingsModal);
|
||||
},
|
||||
co2Available() {
|
||||
return this.smartCostUnit === CO2_UNIT;
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
<script>
|
||||
import formatter from "../mixins/formatter";
|
||||
import TariffChart from "./TariffChart.vue";
|
||||
import { CO2_UNIT } from "../units";
|
||||
import { CO2_TYPE } from "../units";
|
||||
import api from "../api";
|
||||
|
||||
export default {
|
||||
|
|
@ -113,7 +113,8 @@ export default {
|
|||
mixins: [formatter],
|
||||
props: {
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
currency: String,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
|
|
@ -126,7 +127,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
isCo2() {
|
||||
return this.smartCostUnit === CO2_UNIT;
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
},
|
||||
costOptions() {
|
||||
const values = [];
|
||||
|
|
@ -144,7 +145,7 @@ export default {
|
|||
const name = `< ${
|
||||
this.isCo2
|
||||
? this.fmtCo2Medium(value)
|
||||
: this.fmtPricePerKWh(value, this.smartCostUnit)
|
||||
: this.fmtPricePerKWh(value, this.currency)
|
||||
}`;
|
||||
return { value, name };
|
||||
});
|
||||
|
|
@ -218,7 +219,7 @@ export default {
|
|||
if (this.isCo2) {
|
||||
return this.fmtCo2Medium(price);
|
||||
}
|
||||
return this.fmtPricePerKWh(price, this.smartCostUnit);
|
||||
return this.fmtPricePerKWh(price, this.currency);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -287,10 +288,10 @@ export default {
|
|||
fmtCostRange({ min, max }) {
|
||||
const fmtMin = this.isCo2
|
||||
? this.fmtCo2Short(min)
|
||||
: this.fmtPricePerKWh(min, this.smartCostUnit, true);
|
||||
: this.fmtPricePerKWh(min, this.currency, true);
|
||||
const fmtMax = this.isCo2
|
||||
? this.fmtCo2Short(max)
|
||||
: this.fmtPricePerKWh(max, this.smartCostUnit, true);
|
||||
: this.fmtPricePerKWh(max, this.currency, true);
|
||||
return `${fmtMin} - ${fmtMax}`;
|
||||
},
|
||||
slotHovered(index) {
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export default {
|
|||
guardRemaining: Number,
|
||||
guardAction: String,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
tariffCo2: Number,
|
||||
currency: String,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
:id="index + 1"
|
||||
:vehicles="vehicles"
|
||||
:smartCostLimit="smartCostLimit"
|
||||
:smartCostUnit="smartCostUnit"
|
||||
:smartCostType="smartCostType"
|
||||
:tariffGrid="tariffGrid"
|
||||
:tariffCo2="tariffCo2"
|
||||
:currency="currency"
|
||||
|
|
@ -49,7 +49,7 @@ export default {
|
|||
loadpoints: Array,
|
||||
vehicles: Array,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
tariffCo2: Number,
|
||||
currency: String,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
:loadpoints="loadpoints"
|
||||
:vehicles="vehicles"
|
||||
:smartCostLimit="smartCostLimit"
|
||||
:smartCostUnit="smartCostUnit"
|
||||
:smartCostType="smartCostType"
|
||||
:tariffGrid="tariffGrid"
|
||||
:tariffCo2="tariffCo2"
|
||||
:currency="currency"
|
||||
|
|
@ -100,8 +100,7 @@ export default {
|
|||
sponsor: String,
|
||||
sponsorTokenExpires: Number,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostAvailable: Boolean,
|
||||
smartCostType: String,
|
||||
},
|
||||
computed: {
|
||||
energyflow: function () {
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
<script>
|
||||
import "@h2d2/shopicons/es/filled/plus";
|
||||
import "@h2d2/shopicons/es/filled/edit";
|
||||
import { CO2_UNIT } from "../units";
|
||||
import { CO2_TYPE } from "../units";
|
||||
import TargetChargePlan from "./TargetChargePlan.vue";
|
||||
import api from "../api";
|
||||
|
||||
|
|
@ -99,7 +99,8 @@ export default {
|
|||
socBasedCharging: Boolean,
|
||||
disabled: Boolean,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
currency: String,
|
||||
},
|
||||
emits: ["target-time-updated", "target-time-removed"],
|
||||
data: function () {
|
||||
|
|
@ -144,8 +145,9 @@ export default {
|
|||
targetChargePlanProps: function () {
|
||||
const targetTime = this.selectedTargetTime;
|
||||
const { rates } = this.tariff;
|
||||
const { duration, unit, plan } = this.plan;
|
||||
return rates ? { duration, rates, plan, unit, targetTime } : null;
|
||||
const { duration, plan } = this.plan;
|
||||
const { currency, smartCostType } = this;
|
||||
return rates ? { duration, rates, plan, targetTime, currency, smartCostType } : null;
|
||||
},
|
||||
tariffLowest: function () {
|
||||
return this.tariff?.rates.reduce((res, slot) => {
|
||||
|
|
@ -167,11 +169,11 @@ export default {
|
|||
});
|
||||
}
|
||||
return this.$t("main.targetCharge.priceLimit", {
|
||||
price: this.fmtPricePerKWh(this.smartCostLimit, this.smartCostUnit, true),
|
||||
price: this.fmtPricePerKWh(this.smartCostLimit, this.currency, true),
|
||||
});
|
||||
},
|
||||
isCo2() {
|
||||
return this.smartCostUnit === CO2_UNIT;
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<script>
|
||||
import formatter from "../mixins/formatter";
|
||||
import { CO2_UNIT } from "../units";
|
||||
import { CO2_TYPE } from "../units";
|
||||
import TariffChart from "./TariffChart.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -33,8 +33,9 @@ export default {
|
|||
duration: Number,
|
||||
rates: Array,
|
||||
plan: Array,
|
||||
unit: String,
|
||||
smartCostType: String,
|
||||
targetTime: Date,
|
||||
currency: String,
|
||||
},
|
||||
data() {
|
||||
return { activeIndex: null, startTime: new Date() };
|
||||
|
|
@ -44,7 +45,7 @@ export default {
|
|||
return this.fmtShortDuration(this.duration, true);
|
||||
},
|
||||
isCo2() {
|
||||
return this.unit === CO2_UNIT;
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
},
|
||||
hasTariff() {
|
||||
return this.rates?.length > 1;
|
||||
|
|
@ -66,7 +67,9 @@ export default {
|
|||
if (price === undefined) {
|
||||
return this.$t("main.targetChargePlan.unknownPrice");
|
||||
}
|
||||
return this.isCo2 ? this.fmtCo2Medium(price) : this.fmtPricePerKWh(price, this.unit);
|
||||
return this.isCo2
|
||||
? this.fmtCo2Medium(price)
|
||||
: this.fmtPricePerKWh(price, this.currency);
|
||||
},
|
||||
activeSlot() {
|
||||
return this.slots[this.activeIndex];
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<script>
|
||||
import formatter from "../mixins/formatter";
|
||||
import { CO2_UNIT } from "../units";
|
||||
import { CO2_TYPE } from "../units";
|
||||
|
||||
export default {
|
||||
name: "TariffChart",
|
||||
|
|
@ -66,7 +66,7 @@ export default {
|
|||
return sum / count;
|
||||
},
|
||||
isCo2() {
|
||||
return this.unit === CO2_UNIT;
|
||||
return this.unit === CO2_TYPE;
|
||||
},
|
||||
activeSlot() {
|
||||
return this.slots[this.activeIndex];
|
||||
|
|
|
|||
|
|
@ -115,9 +115,10 @@ export default {
|
|||
vehicles: Array,
|
||||
climaterActive: Boolean,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
tariffCo2: Number,
|
||||
currency: String,
|
||||
},
|
||||
emits: [
|
||||
"target-time-removed",
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ describe("smart grid charging", () => {
|
|||
charging: true,
|
||||
tariffCo2: 400,
|
||||
smartCostLimit: 500,
|
||||
smartCostUnit: "gCO2eq",
|
||||
smartCostType: "co2",
|
||||
},
|
||||
"cleanEnergyCharging"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<script>
|
||||
import formatter from "../mixins/formatter";
|
||||
import { CO2_UNIT } from "../units";
|
||||
import { CO2_TYPE } from "../units";
|
||||
|
||||
export default {
|
||||
name: "VehicleStatus",
|
||||
|
|
@ -27,7 +27,7 @@ export default {
|
|||
targetChargeDisabled: Boolean,
|
||||
climaterActive: Boolean,
|
||||
smartCostLimit: Number,
|
||||
smartCostUnit: String,
|
||||
smartCostType: String,
|
||||
tariffGrid: Number,
|
||||
tariffCo2: Number,
|
||||
},
|
||||
|
|
@ -47,7 +47,7 @@ export default {
|
|||
return this.guardRemainingInterpolated > 0 && this.guardAction === "enable";
|
||||
},
|
||||
isCo2() {
|
||||
return this.smartCostUnit === CO2_UNIT;
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
},
|
||||
message: function () {
|
||||
const t = (key, data) => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ const KM = "km";
|
|||
const MILES = "mi";
|
||||
export const UNITS = [KM, MILES];
|
||||
|
||||
export const CO2_UNIT = "gCO2eq";
|
||||
export const CO2_TYPE = "co2";
|
||||
export const PRICE_DYNAMIC_TYPE = "pricedynamic";
|
||||
|
||||
const MILES_FACTOR = 0.6213711922;
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ type tariffConfig struct {
|
|||
Currency string
|
||||
Grid typedConfig
|
||||
FeedIn typedConfig
|
||||
Co2 typedConfig
|
||||
Planner typedConfig
|
||||
}
|
||||
|
||||
|
|
|
|||
14
cmd/setup.go
14
cmd/setup.go
|
|
@ -244,7 +244,7 @@ func configureMessengers(conf messagingConfig, valueChan chan util.Param, cache
|
|||
}
|
||||
|
||||
func configureTariffs(conf tariffConfig) (tariff.Tariffs, error) {
|
||||
var grid, feedin, planner api.Tariff
|
||||
var grid, feedin, co2, planner api.Tariff
|
||||
var currencyCode currency.Unit = currency.EUR
|
||||
var err error
|
||||
|
||||
|
|
@ -268,15 +268,25 @@ func configureTariffs(conf tariffConfig) (tariff.Tariffs, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if conf.Co2.Type != "" {
|
||||
co2, err = tariff.NewFromConfig(conf.Co2.Type, conf.Co2.Other)
|
||||
if err != nil {
|
||||
co2 = nil
|
||||
log.ERROR.Printf("failed configuring co2 tariff: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Planner.Type != "" {
|
||||
planner, err = tariff.NewFromConfig(conf.Planner.Type, conf.Planner.Other)
|
||||
if err != nil {
|
||||
planner = nil
|
||||
log.ERROR.Printf("failed configuring planner tariff: %v", err)
|
||||
} else if planner.Type() == api.TariffTypeCo2 {
|
||||
log.WARN.Printf("tariff configuration changed, use co2 instead of planner for co2 tariff")
|
||||
}
|
||||
}
|
||||
|
||||
tariffs := tariff.NewTariffs(currencyCode, grid, feedin, planner)
|
||||
tariffs := tariff.NewTariffs(currencyCode, grid, feedin, co2, planner)
|
||||
|
||||
return *tariffs, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ func runTariff(cmd *cobra.Command, args []string) {
|
|||
name := cmd.Flags().Lookup(flagName).Value.String()
|
||||
|
||||
for key, cc := range map[string]typedConfig{
|
||||
"grid": conf.Tariffs.Grid,
|
||||
"feedin": conf.Tariffs.FeedIn,
|
||||
"planner": conf.Tariffs.Planner,
|
||||
"grid": conf.Tariffs.Grid,
|
||||
"feedin": conf.Tariffs.FeedIn,
|
||||
"co2": conf.Tariffs.Co2,
|
||||
} {
|
||||
if cc.Type == "" || (name != "" && key != name) {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ type API interface {
|
|||
GetTargetSoc() int
|
||||
// SetTargetSoc sets the charge target soc
|
||||
SetTargetSoc(int)
|
||||
// GetPlannerUnit returns the planning tariffs unit
|
||||
GetPlannerUnit() string
|
||||
// GetPlan creates a charging plan
|
||||
GetPlan(targetTime time.Time, maxPower float64) (time.Duration, api.Rates, error)
|
||||
// GetEnableThreshold gets the loadpoint enable threshold
|
||||
|
|
|
|||
|
|
@ -205,20 +205,6 @@ func (mr *MockAPIMockRecorder) GetPlan(arg0, arg1 interface{}) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlan", reflect.TypeOf((*MockAPI)(nil).GetPlan), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetPlannerUnit mocks base method.
|
||||
func (m *MockAPI) GetPlannerUnit() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPlannerUnit")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetPlannerUnit indicates an expected call of GetPlannerUnit.
|
||||
func (mr *MockAPIMockRecorder) GetPlannerUnit() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlannerUnit", reflect.TypeOf((*MockAPI)(nil).GetPlannerUnit))
|
||||
}
|
||||
|
||||
// GetRemainingDuration mocks base method.
|
||||
func (m *MockAPI) GetRemainingDuration() time.Duration {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
|||
|
|
@ -41,10 +41,6 @@ func (lp *Loadpoint) planRequiredDuration(maxPower float64) time.Duration {
|
|||
return lp.socEstimator.RemainingChargeDuration(targetSoc, maxPower)
|
||||
}
|
||||
|
||||
func (lp *Loadpoint) GetPlannerUnit() string {
|
||||
return lp.planner.Unit()
|
||||
}
|
||||
|
||||
// GetPlan creates a charging plan
|
||||
//
|
||||
// Results:
|
||||
|
|
|
|||
|
|
@ -81,13 +81,6 @@ func (t *Planner) plan(rates api.Rates, requiredDuration time.Duration, targetTi
|
|||
return plan
|
||||
}
|
||||
|
||||
func (t *Planner) Unit() string {
|
||||
if t.tariff == nil {
|
||||
return ""
|
||||
}
|
||||
return t.tariff.Unit()
|
||||
}
|
||||
|
||||
// Plan creates a lowest-cost charging plan, considering edge conditions
|
||||
func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) (api.Rates, error) {
|
||||
if t == nil || requiredDuration <= 0 {
|
||||
|
|
|
|||
|
|
@ -739,12 +739,9 @@ func (site *Site) prepare() {
|
|||
site.publish("prioritySoc", site.PrioritySoc)
|
||||
site.publish("residualPower", site.ResidualPower)
|
||||
site.publish("smartCostLimit", site.SmartCostLimit)
|
||||
site.publish("smartCostType", nil)
|
||||
if tariff := site.GetTariff(PlannerTariff); tariff != nil {
|
||||
site.publish("smartCostUnit", tariff.Unit())
|
||||
site.publish("smartCostAvailable", tariff.IsDynamic())
|
||||
} else {
|
||||
site.publish("smartCostUnit", nil)
|
||||
site.publish("smartCostAvailable", nil)
|
||||
site.publish("smartCostType", tariff.Type().String())
|
||||
}
|
||||
site.publish("currency", site.tariffs.Currency.String())
|
||||
site.publish("savingsSince", site.savings.Since())
|
||||
|
|
|
|||
|
|
@ -134,17 +134,37 @@ func (site *Site) GetTariff(tariff string) api.Tariff {
|
|||
site.Lock()
|
||||
defer site.Unlock()
|
||||
|
||||
var t api.Tariff
|
||||
switch tariff {
|
||||
case GridTariff:
|
||||
t = site.tariffs.Grid
|
||||
case FeedinTariff:
|
||||
t = site.tariffs.FeedIn
|
||||
case PlannerTariff:
|
||||
if t = site.tariffs.Planner; t == nil {
|
||||
t = site.tariffs.Grid
|
||||
}
|
||||
}
|
||||
return site.tariffs.Grid
|
||||
|
||||
return t
|
||||
case FeedinTariff:
|
||||
return site.tariffs.FeedIn
|
||||
|
||||
case PlannerTariff:
|
||||
switch {
|
||||
case site.tariffs.Planner != nil:
|
||||
// prio 0: manually set planner tariff
|
||||
site.log.DEBUG.Printf("planner tariff")
|
||||
return site.tariffs.Planner
|
||||
|
||||
case site.tariffs.Grid != nil && site.tariffs.Grid.Type() == api.TariffTypePriceDynamic:
|
||||
// prio 1: dynamic grid tariff
|
||||
site.log.DEBUG.Printf("dynamic grid tariff")
|
||||
return site.tariffs.Grid
|
||||
|
||||
case site.tariffs.Co2 != nil:
|
||||
// prio 2: co2 tariff
|
||||
site.log.DEBUG.Printf("co2 tariff")
|
||||
return site.tariffs.Co2
|
||||
|
||||
default:
|
||||
// prio 3: static grid tariff
|
||||
site.log.DEBUG.Printf("static grid tariff")
|
||||
return site.tariffs.Grid
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,8 +183,8 @@ tariffs:
|
|||
# rate for feeding excess (pv) energy to the grid
|
||||
type: fixed
|
||||
price: 0.08 # EUR/kWh
|
||||
planner:
|
||||
# planner tariff provides co2 intensity forecast and is for co2-optimized target charging if no variable grid tariff is specified
|
||||
co2:
|
||||
# co2 tariff provides co2 intensity forecast and is for co2-optimized target charging if no variable grid tariff is specified
|
||||
# type: grünstromindex # GrünStromIndex (Germany only)
|
||||
# zip: <zip>
|
||||
|
||||
|
|
|
|||
|
|
@ -514,20 +514,6 @@ func (m *MockTariff) EXPECT() *MockTariffMockRecorder {
|
|||
return m.recorder
|
||||
}
|
||||
|
||||
// IsDynamic mocks base method.
|
||||
func (m *MockTariff) IsDynamic() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsDynamic")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsDynamic indicates an expected call of IsDynamic.
|
||||
func (mr *MockTariffMockRecorder) IsDynamic() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDynamic", reflect.TypeOf((*MockTariff)(nil).IsDynamic))
|
||||
}
|
||||
|
||||
// Rates mocks base method.
|
||||
func (m *MockTariff) Rates() (api.Rates, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
@ -543,16 +529,16 @@ func (mr *MockTariffMockRecorder) Rates() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rates", reflect.TypeOf((*MockTariff)(nil).Rates))
|
||||
}
|
||||
|
||||
// Unit mocks base method.
|
||||
func (m *MockTariff) Unit() string {
|
||||
// Type mocks base method.
|
||||
func (m *MockTariff) Type() api.TariffType {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Unit")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret := m.ctrl.Call(m, "Type")
|
||||
ret0, _ := ret[0].(api.TariffType)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Unit indicates an expected call of Unit.
|
||||
func (mr *MockTariffMockRecorder) Unit() *gomock.Call {
|
||||
// Type indicates an expected call of Type.
|
||||
func (mr *MockTariffMockRecorder) Type() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unit", reflect.TypeOf((*MockTariff)(nil).Unit))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockTariff)(nil).Type))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,7 +392,6 @@ func planHandler(lp loadpoint.API) http.HandlerFunc {
|
|||
}{
|
||||
Duration: int64(requiredDuration.Seconds()),
|
||||
Plan: plan,
|
||||
Unit: lp.GetPlannerUnit(),
|
||||
Power: power,
|
||||
}
|
||||
jsonResult(w, res)
|
||||
|
|
|
|||
|
|
@ -32,12 +32,10 @@ func init() {
|
|||
func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
Cheap any // TODO deprecated
|
||||
Currency string
|
||||
Currency string // TODO deprecated
|
||||
Region string
|
||||
}{
|
||||
Currency: "EUR",
|
||||
Region: "DE",
|
||||
Region: "DE",
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -55,11 +53,6 @@ func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
uri: fmt.Sprintf(awattar.RegionURI, strings.ToLower(cc.Region)),
|
||||
}
|
||||
|
||||
// TODO deprecated
|
||||
if cc.Cheap != nil {
|
||||
t.log.WARN.Println("cheap rate configuration has been replaced by target charging and is deprecated")
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
go t.run(done)
|
||||
err := <-done
|
||||
|
|
@ -99,11 +92,6 @@ func (t *Awattar) run(done chan error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *Awattar) Unit() string {
|
||||
return t.unit
|
||||
}
|
||||
|
||||
// Rates implements the api.Tariff interface
|
||||
func (t *Awattar) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
|
|
@ -111,7 +99,7 @@ func (t *Awattar) Rates() (api.Rates, error) {
|
|||
return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *Awattar) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *Awattar) Type() api.TariffType {
|
||||
return api.TariffTypePriceDynamic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,11 +102,6 @@ func (t *ElectricityMaps) run(done chan error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *ElectricityMaps) Unit() string {
|
||||
return Co2Equivalent
|
||||
}
|
||||
|
||||
func (t *ElectricityMaps) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
|
@ -124,7 +119,7 @@ func (t *ElectricityMaps) Rates() (api.Rates, error) {
|
|||
return res, outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *ElectricityMaps) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *ElectricityMaps) Type() api.TariffType {
|
||||
return api.TariffTypeCo2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,12 +32,10 @@ func init() {
|
|||
}
|
||||
|
||||
func NewEleringFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
cc := struct {
|
||||
var cc struct {
|
||||
embed `mapstructure:",squash"`
|
||||
Currency string
|
||||
Currency string // TODO deprecated
|
||||
Region string
|
||||
}{
|
||||
Currency: "EUR",
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -104,11 +102,6 @@ func (t *Elering) run(done chan error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *Elering) Unit() string {
|
||||
return t.unit
|
||||
}
|
||||
|
||||
// Rates implements the api.Tariff interface
|
||||
func (t *Elering) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
|
|
@ -116,7 +109,7 @@ func (t *Elering) Rates() (api.Rates, error) {
|
|||
return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *Elering) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *Elering) Type() api.TariffType {
|
||||
return api.TariffTypePriceDynamic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,13 @@ func init() {
|
|||
}
|
||||
|
||||
func NewFixedFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
||||
cc := struct {
|
||||
Currency string
|
||||
var cc struct {
|
||||
Currency string // TODO deprecated
|
||||
Price float64
|
||||
Zones []struct {
|
||||
Price float64
|
||||
Days, Hours string
|
||||
}
|
||||
}{
|
||||
Currency: "EUR",
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -84,11 +82,6 @@ func NewFixedFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
return t, nil
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *Fixed) Unit() string {
|
||||
return t.unit
|
||||
}
|
||||
|
||||
// Rates implements the api.Tariff interface
|
||||
func (t *Fixed) Rates() (api.Rates, error) {
|
||||
var res api.Rates
|
||||
|
|
@ -139,7 +132,10 @@ func (t *Fixed) Rates() (api.Rates, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *Fixed) IsDynamic() bool {
|
||||
return t.dynamic
|
||||
// Type returns the tariff type
|
||||
func (t *Fixed) Type() api.TariffType {
|
||||
if t.dynamic {
|
||||
return api.TariffTypePriceDynamic
|
||||
}
|
||||
return api.TariffTypePriceStatic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,11 +128,6 @@ func (t *GrünStromIndex) run(done chan error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *GrünStromIndex) Unit() string {
|
||||
return Co2Equivalent
|
||||
}
|
||||
|
||||
// Rates implements the api.Tariff interface
|
||||
func (t *GrünStromIndex) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
|
|
@ -140,7 +135,7 @@ func (t *GrünStromIndex) Rates() (api.Rates, error) {
|
|||
return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *GrünStromIndex) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *GrünStromIndex) Type() api.TariffType {
|
||||
return api.TariffTypeCo2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func (t *Octopus) Rates() (api.Rates, error) {
|
|||
return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *Octopus) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *Octopus) Type() api.TariffType {
|
||||
return api.TariffTypePriceDynamic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,17 @@ import (
|
|||
"golang.org/x/text/currency"
|
||||
)
|
||||
|
||||
// Co2Equivalent is the unit for co2 emissions
|
||||
const Co2Equivalent = "gCO2eq"
|
||||
|
||||
type Tariffs struct {
|
||||
Currency currency.Unit
|
||||
Grid, FeedIn, Planner api.Tariff
|
||||
Currency currency.Unit
|
||||
Grid, FeedIn, Co2, Planner api.Tariff
|
||||
}
|
||||
|
||||
func NewTariffs(currency currency.Unit, grid, feedin, planner api.Tariff) *Tariffs {
|
||||
if planner == nil {
|
||||
planner = grid
|
||||
}
|
||||
|
||||
func NewTariffs(currency currency.Unit, grid, feedin, co2 api.Tariff, planner api.Tariff) *Tariffs {
|
||||
return &Tariffs{
|
||||
Currency: currency,
|
||||
Grid: grid,
|
||||
FeedIn: feedin,
|
||||
Co2: co2,
|
||||
Planner: planner,
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +45,8 @@ func (t *Tariffs) CurrentFeedInPrice() (float64, error) {
|
|||
|
||||
// CurrentCo2 determines the grids co2 emission.
|
||||
func (t *Tariffs) CurrentCo2() (float64, error) {
|
||||
if t.Planner != nil && t.Planner.Unit() == Co2Equivalent {
|
||||
return currentPrice(t.Planner)
|
||||
if t.Co2 != nil {
|
||||
return currentPrice(t.Co2)
|
||||
}
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
Token string
|
||||
HomeID string
|
||||
Unit string
|
||||
Cheap any // TODO deprecated
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -69,11 +68,6 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO deprecated
|
||||
if cc.Cheap != nil {
|
||||
t.log.WARN.Println("cheap rate configuration has been replaced by target charging and is deprecated")
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
go t.run(done)
|
||||
err := <-done
|
||||
|
|
@ -136,11 +130,6 @@ func (t *Tibber) rates(pi []tibber.Price) api.Rates {
|
|||
return data
|
||||
}
|
||||
|
||||
// Unit implements the api.Tariff interface
|
||||
func (t *Tibber) Unit() string {
|
||||
return t.unit
|
||||
}
|
||||
|
||||
// Rates implements the api.Tariff interface
|
||||
func (t *Tibber) Rates() (api.Rates, error) {
|
||||
t.mux.Lock()
|
||||
|
|
@ -148,7 +137,7 @@ func (t *Tibber) Rates() (api.Rates, error) {
|
|||
return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
|
||||
}
|
||||
|
||||
// IsDynamic implements the api.Tariff interface
|
||||
func (t *Tibber) IsDynamic() bool {
|
||||
return true
|
||||
// Type returns the tariff type
|
||||
func (t *Tibber) Type() api.TariffType {
|
||||
return api.TariffTypePriceDynamic
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue