Add dedicated co2 tariff (#8089)

---------

Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
andig 2023-05-31 09:19:36 +02:00 • committed by GitHub
parent 15b60863c1
commit 7c3ca53f02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 267 additions and 215 deletions

View file

@ -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

View file

@ -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
View 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
View 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
}

View file

@ -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" />

View file

@ -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 () {

View file

@ -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"

View file

@ -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() {

View file

@ -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) {

View file

@ -257,7 +257,7 @@ export default {
guardRemaining: Number,
guardAction: String,
smartCostLimit: Number,
smartCostUnit: String,
smartCostType: String,
tariffGrid: Number,
tariffCo2: Number,
currency: String,

View file

@ -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,

View file

@ -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 () {

View file

@ -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: {

View file

@ -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];

View file

@ -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];

View file

@ -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",

View file

@ -206,7 +206,7 @@ describe("smart grid charging", () => {
charging: true,
tariffCo2: 400,
smartCostLimit: 500,
smartCostUnit: "gCO2eq",
smartCostType: "co2",
},
"cleanEnergyCharging"
);

View file

@ -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) => {

View file

@ -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;

View file

@ -120,6 +120,7 @@ type tariffConfig struct {
Currency string
Grid typedConfig
FeedIn typedConfig
Co2 typedConfig
Planner typedConfig
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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 {

View file

@ -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())

View file

@ -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
}
}

View file

@ -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>

View file

@ -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))
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}