Skip to content

Commit

Permalink
Merge branch 'master' into fix/UnfinishedSessions
Browse files Browse the repository at this point in the history
  • Loading branch information
GrimmiMeloni committed Oct 14, 2023
2 parents a335144 + 7ecf7e6 commit 5023892
Show file tree
Hide file tree
Showing 58 changed files with 2,021 additions and 1,281 deletions.
2 changes: 1 addition & 1 deletion api/actionconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"reflect"
"strings"

"dario.cat/mergo"
"github.com/fatih/structs"
"github.com/imdario/mergo"
"github.com/jinzhu/copier"
)

Expand Down
18 changes: 12 additions & 6 deletions assets/js/components/ChargingPlanArrival.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@
:disabled="!socBasedCharging"
@change="changeMinSoc"
>
<option
v-for="soc in [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]"
:key="soc"
:value="soc"
>
{{ soc ? `${soc}%` : "--" }}
<option v-for="soc in minSocOptions" :key="soc.value" :value="soc.value">
{{ soc.name }}
</option>
</select>
</div>
Expand All @@ -45,6 +41,16 @@ export default {
data: function () {
return { selectedMinSoc: this.minSoc };
},
computed: {
minSocOptions() {
// a list of entries from 0 to 95 with a step of 5
return Array.from(Array(20).keys())
.map((i) => i * 5)
.map((soc) => {
return { value: soc, name: soc === 0 ? "--" : `${soc}%` };
});
},
},
watch: {
minSoc: function (value) {
this.selectedMinSoc = value;
Expand Down
1 change: 1 addition & 0 deletions assets/js/components/Loadpoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export default {
guardAction: String,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
tariffGrid: Number,
tariffCo2: Number,
currency: String,
Expand Down
2 changes: 2 additions & 0 deletions assets/js/components/Loadpoints.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
:vehicles="vehicles"
:smartCostLimit="smartCostLimit"
:smartCostType="smartCostType"
:smartCostActive="smartCostActive"
:tariffGrid="tariffGrid"
:tariffCo2="tariffCo2"
:currency="currency"
Expand Down Expand Up @@ -51,6 +52,7 @@ export default {
vehicles: Array,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
tariffGrid: Number,
tariffCo2: Number,
currency: String,
Expand Down
2 changes: 2 additions & 0 deletions assets/js/components/Site.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
:vehicles="vehicles"
:smartCostLimit="smartCostLimit"
:smartCostType="smartCostType"
:smartCostActive="smartCostActive"
:tariffGrid="tariffGrid"
:tariffCo2="tariffCo2"
:currency="currency"
Expand Down Expand Up @@ -103,6 +104,7 @@ export default {
sponsorTokenExpires: Number,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
},
computed: {
energyflow: function () {
Expand Down
1 change: 1 addition & 0 deletions assets/js/components/Vehicle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default {
climaterActive: Boolean,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
tariffGrid: Number,
tariffCo2: Number,
currency: String,
Expand Down
8 changes: 5 additions & 3 deletions assets/js/components/VehicleStatus.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,9 @@ describe("smart grid charging", () => {
tariffCo2: 400,
smartCostLimit: 500,
smartCostType: "co2",
smartCostActive: true,
},
"cleanEnergyCharging"
`cleanEnergyCharging:{"co2":"400 g","limit":"500 g"}`
);
});
test("show cheap energy message", () => {
Expand All @@ -219,9 +220,10 @@ describe("smart grid charging", () => {
charging: true,
tariffGrid: 0.28,
smartCostLimit: 0.29,
currency: "EUR",
currency: "CHF",
smartCostActive: true,
},
"cheapEnergyCharging"
`cheapEnergyCharging:{"price":"28,0 rp","limit":"29,0 rp"}`
);
});
});
24 changes: 13 additions & 11 deletions assets/js/components/VehicleStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ export default {
climaterActive: Boolean,
smartCostLimit: Number,
smartCostType: String,
smartCostActive: Boolean,
tariffGrid: Number,
tariffCo2: Number,
currency: String,
},
computed: {
phaseTimerActive() {
Expand All @@ -46,9 +48,6 @@ export default {
guardTimerActive() {
return this.guardRemainingInterpolated > 0 && this.guardAction === "enable";
},
isCo2() {
return this.smartCostType === CO2_TYPE;
},
message: function () {
const t = (key, data) => {
return this.$t(`main.vehicleStatus.${key}`, data);
Expand Down Expand Up @@ -77,14 +76,17 @@ export default {
}
}
// clean energy
if (this.charging && this.isCo2 && this.tariffCo2 < this.smartCostLimit) {
return t("cleanEnergyCharging");
}
// cheap energy
if (this.charging && !this.isCo2 && this.tariffGrid < this.smartCostLimit) {
return t("cheapEnergyCharging");
// clean or cheap energy
if (this.charging && this.smartCostActive) {
return this.smartCostType === CO2_TYPE
? t("cleanEnergyCharging", {
co2: this.fmtCo2Short(this.tariffCo2),
limit: this.fmtCo2Short(this.smartCostLimit),
})
: t("cheapEnergyCharging", {
price: this.fmtPricePerKWh(this.tariffGrid, this.currency, true),
limit: this.fmtPricePerKWh(this.smartCostLimit, this.currency, true),
});
}
if (this.pvTimerActive && !this.enabled && this.pvAction === "enable") {
Expand Down
5 changes: 3 additions & 2 deletions charger/daheimladen-mb.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,10 @@ func (wb *DaheimLadenMB) MaxCurrent(current int64) error {
return fmt.Errorf("invalid current %d", current)
}

err := wb.setCurrent(wb.curr)
curr := uint16(current * 10)
err := wb.setCurrent(curr)
if err == nil {
wb.curr = uint16(current * 10)
wb.curr = curr
}

return err
Expand Down
127 changes: 105 additions & 22 deletions charger/easee.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,32 @@ import (
// Easee charger implementation
type Easee struct {
*request.Helper
charger string
site, circuit int
updated time.Time
log *util.Logger
mux sync.Mutex
done chan struct{}
dynamicChargerCurrent float64
current float64
chargerEnabled bool
smartCharging bool
authorize bool
enabled bool
opMode int
reasonForNoCurrent int
phaseMode int
charger string
site, circuit int
updated time.Time
lastEnergyPollTriggered time.Time
log *util.Logger
mux sync.Mutex
lastEnergyPollMux sync.Mutex
done chan struct{}
dynamicChargerCurrent float64
current float64
chargerEnabled bool
smartCharging bool
authorize bool
enabled bool
opMode int
reasonForNoCurrent int
phaseMode int
sessionStartEnergy *float64
currentPower, sessionEnergy, totalEnergy,
currentL1, currentL2, currentL3 float64
rfid string
lp loadpoint.API
cmdC chan easee.SignalRCommandResponse
obsC chan easee.Observation
obsTime map[easee.ObservationID]time.Time
rfid string
lp loadpoint.API
cmdC chan easee.SignalRCommandResponse
obsC chan easee.Observation
obsTime map[easee.ObservationID]time.Time
stopTicker chan struct{}
}

func init() {
Expand Down Expand Up @@ -280,6 +284,7 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
// received observation is outdated, ignoring
return
}

c.obsTime[res.ID] = res.Timestamp

switch res.ID {
Expand All @@ -292,9 +297,17 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
case easee.TOTAL_POWER:
c.currentPower = 1e3 * value.(float64)
case easee.SESSION_ENERGY:
c.sessionEnergy = value.(float64)
// SESSION_ENERGY must not be set to 0 by Productupdates, they occur erratic
// Reset to 0 is done in case CHARGER_OP_MODE
if value.(float64) != 0 {
c.sessionEnergy = value.(float64)
}
case easee.LIFETIME_ENERGY:
c.totalEnergy = value.(float64)
if c.sessionStartEnergy == nil {
f := c.totalEnergy
c.sessionStartEnergy = &f
}
case easee.IN_CURRENT_T3:
c.currentL1 = value.(float64)
case easee.IN_CURRENT_T4:
Expand All @@ -305,8 +318,55 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
c.phaseMode = value.(int)
case easee.DYNAMIC_CHARGER_CURRENT:
c.dynamicChargerCurrent = value.(float64)

case easee.CHARGER_OP_MODE:
c.opMode = value.(int)
opMode := value.(int)

// New charging session pending, reset internal value of SESSION_ENERGY to 0, and its observation timestamp to "now".
// This should be done in a proper way by the api, but it's not.
// Remember value of LIFETIME_ENERGY as start value of the charging session
if c.opMode <= easee.ModeDisconnected && opMode >= easee.ModeAwaitingStart {
c.sessionEnergy = 0
c.obsTime[easee.SESSION_ENERGY] = time.Now()
c.sessionStartEnergy = nil
}

// OpMode changed TO charging. Start ticker for periodic requests to update LIFETIME_ENERGY
if c.opMode != easee.ModeCharging && opMode == easee.ModeCharging {
if c.stopTicker == nil {
c.stopTicker = make(chan struct{})

go func() {
ticker := time.NewTicker(5 * time.Minute)
for {
select {
case <-c.stopTicker:
return
case <-ticker.C:
c.requestLifetimeEnergyUpdate()
}
}
}()
}
}

// OpMode changed FROM charging to something else - stop ticker if channel exists
if c.opMode == easee.ModeCharging && opMode != easee.ModeCharging && c.stopTicker != nil {
close(c.stopTicker)
c.stopTicker = nil
}

// for relevant OpModes changes indicating a start or stop of the charging session, request new update of LIFETIME_ENERGY
// relevant OpModes: leaving op modes 1 (car connected, charging will start uncontrolled if unauthorized)
// and 3 (charging stopped or pause), or reaching op mode 1 (car disconnected) and 7 (charging paused/ended by de-authenticating)
if c.opMode != opMode && // only if op mode actually changed AND
(c.opMode == easee.ModeDisconnected || c.opMode == easee.ModeCharging || // from these op modes
opMode == easee.ModeDisconnected || opMode == easee.ModeAwaitingAuthentication) { // or to these op modes
c.requestLifetimeEnergyUpdate()
}

c.opMode = opMode

case easee.REASON_FOR_NO_CURRENT:
c.reasonForNoCurrent = value.(int)
}
Expand Down Expand Up @@ -674,19 +734,42 @@ var _ api.Meter = (*Easee)(nil)

// CurrentPower implements the api.Meter interface
func (c *Easee) CurrentPower() (float64, error) {
if status, err := c.Status(); err != nil || status == api.StatusA {
return 0, err
}

c.mux.Lock()
defer c.mux.Unlock()

return c.currentPower, nil
}

func (c *Easee) requestLifetimeEnergyUpdate() {
c.lastEnergyPollMux.Lock()
defer c.lastEnergyPollMux.Unlock()
if time.Since(c.lastEnergyPollTriggered) > time.Minute*3 { // api rate limit, max once in 3 minutes
uri := fmt.Sprintf("%s/chargers/%s/commands/%s", easee.API, c.charger, easee.PollLifetimeEnergy)
if _, err := c.Post(uri, request.JSONContent, request.MarshalJSON(nil)); err != nil {
c.log.WARN.Printf("Failed to trigger an update of LIFETIME_ENERGY: %v", err)
}
c.lastEnergyPollTriggered = time.Now()
}
}

var _ api.ChargeRater = (*Easee)(nil)

// ChargedEnergy implements the api.ChargeRater interface
func (c *Easee) ChargedEnergy() (float64, error) {
c.mux.Lock()
defer c.mux.Unlock()

// return either the self calced session energy (current LIFETIME_ENERGY minus remembered start value),
// or the SESSION_ENERGY value by the API. Each value could be lower than the other, depending on
// order and receive timestamp of the product update. We want to return the higher (and newer) value.
if c.sessionStartEnergy != nil {
return max(c.sessionEnergy, c.totalEnergy-*c.sessionStartEnergy), nil
}

return c.sessionEnergy, nil
}

Expand Down
9 changes: 5 additions & 4 deletions charger/easee/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ package easee
const API = "https://api.easee.com/api"

const (
ChargeStart = "start_charging"
ChargeStop = "stop_charging"
ChargePause = "pause_charging"
ChargeResume = "resume_charging"
ChargeStart = "start_charging"
ChargeStop = "stop_charging"
ChargePause = "pause_charging"
ChargeResume = "resume_charging"
PollLifetimeEnergy = "poll_lifetimeenergy"
)

// charge mode definition
Expand Down
8 changes: 3 additions & 5 deletions charger/eebus.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,11 +453,9 @@ func (c *EEBus) currents() (float64, float64, float64, error) {
return 0, 0, 0, err
}

count := len(currents)
if count < 3 {
for fill := 0; fill < 3-count; fill++ {
currents = append(currents, 0)
}
// fill phases
for i := len(currents); i < 3; i++ {
currents = append(currents, 0)
}

return currents[0], currents[1], currents[2], nil
Expand Down
Loading

0 comments on commit 5023892

Please sign in to comment.