Skip to content

Commit

Permalink
Refactor configuration handling (evcc-io#424)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Nov 12, 2020
1 parent 9090514 commit 7683f66
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 153 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,7 @@ EVCC provides a REST and MQTT APIs.

### REST API

- `/api/config`: EVCC static configuration
- `/api/state`: EVCC dynamic state
- `/api/state`: EVCC state (static configuration and dynamic state)
- `/api/mode`: global charge mode (writable)
- `/api/targetsoc`: global target SoC (writable)
- `/api/loadpoints/<id>/mode`: loadpoint charge mode (writable)
Expand Down
17 changes: 8 additions & 9 deletions assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@
</div>

<loadpoint v-for="(loadpoint,id) in state.loadpoints"
v-bind:id="id" :state="loadpoint" :pv="state.gridMeter" :multi="multi">
v-bind:id="id" :key="id" :state="loadpoint" :pv="state.gridConfigured" :multi="multi">
</loadpoint>
</div>
</script>

<script type="text/x-template" id="site-details-template">
<div class="row">
<div class="col-6 col-md-3 mt-3" v-if="state.gridMeter">
<div class="col-6 col-md-3 mt-3" v-if="state.gridConfigured">
<div class="mb-2 value" v-if="state.gridPower > 0">
Bezug <i class="text-primary fas fa-arrow-down"></i>
</div>
Expand All @@ -194,15 +194,15 @@ <h2 class="value">
{{fmt(state.gridPower)}} <small class="text-muted">{{fmtUnit(state.gridPower)}}W</small>
</h2>
</div>
<div class="col-6 col-md-3 mt-3" v-if="state.pvMeter">
<div class="col-6 col-md-3 mt-3" v-if="state.pvConfigured">
<div class="mb-2 value">
Erzeugung <i class="fas fa-sun" v-bind:class="{'text-primary':state.pvPower<0,'text-muted':state.pvPower>=0}"></i>
</div>
<h2 class="value">
{{fmt(state.pvPower)}} <small class="text-muted">{{fmtUnit(state.pvPower)}}W</small>
</h2>
</div>
<div class="d-md-block col-6 col-md-3 mt-3" v-bind:class="{'d-none':!state.batterySoC}" v-if="state.batteryMeter">
<div class="d-md-block col-6 col-md-3 mt-3" v-bind:class="{'d-none':!state.batterySoC}" v-if="state.batteryConfigured">
<div class="mb-2 value">Batterie
<i class="text-primary fas" v-bind:class="{'fa-arrow-down':state.batteryPower<0, 'fa-arrow-up':state.batteryPower>0}"></i>
</div>
Expand Down Expand Up @@ -407,23 +407,22 @@ <h2 class="value">
<div class="col-md-4"></div>
<div class="col-6 col-md-2 py-3">
Netzzähler:
<span class="text-primary" v-if="state.gridMeter"></span>
<span class="text-primary" v-if="state.gridConfigured"></span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
PV Zähler:
<span class="text-primary" v-if="state.pvMeter"></span>
<span class="text-primary" v-if="state.pvConfigured"></span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
Batteriezähler:
<span class="text-primary" v-if="state.batteryMeter"></span>
<span class="text-primary" v-if="state.batteryConfigured"></span>
<span class="text-primary" v-else>&mdash;</span>
</div>
</div>

<div v-for="(loadpoint,id) in state.loadpoints" v-bind:loadpoint="loadpoint" :id="'loadpoint-'+id">

<div class="row mt-4 border-bottom">
<div class="col-12">
<p class="h1">{{loadpoint.title||"Ladepunkt"}}</p>
Expand All @@ -434,7 +433,7 @@ <h2 class="value">
<div class="col-md-4"></div>
<div class="col-6 col-md-2 py-3">
Ladezähler:
<span class="text-primary" v-if="loadpoint.chargeMeter"></span>
<span class="text-primary" v-if="loadpoint.chargeConfigured"></span>
<span class="text-primary" v-else>&mdash;</span>
</div>
<div class="col-6 col-md-2 py-3">
Expand Down
93 changes: 44 additions & 49 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,38 +82,36 @@ let formatter = {
// State
//

function setProperty(obj, props, value) {
const prop = props.shift()
if (!obj[prop]) {
Vue.set(obj, prop, {})
}

if (!props.length) {
if (value && typeof value === 'object' && !Array.isArray(value)) {
obj[prop] = { ...obj[prop], ...value }
} else {
obj[prop] = value
}
return
}

setProperty(obj[prop], props, value)
}

let store = {
state: {
availableVersion: null,
loadpoints: [],
loadpoints: [], // ensure array type
},
update: function(msg) {
let target = this.state;
if (msg.loadpoint !== undefined) {
while (this.state.loadpoints.length <= msg.loadpoint) {
this.state.loadpoints.push({});
}
target = this.state.loadpoints[msg.loadpoint];
}

Object.keys(msg).forEach(function (k) {
if (typeof toasts[k] === "function") {
toasts[k]({message: msg[k]})
} else {
Vue.set(target, k, msg[k]);
setProperty(store.state, k.split('.'), msg[k])
}
});
},
init: function() {
axios.get("config").then(function(msg) {
for (let i=0; i<msg.data.loadpoints.length; i++) {
let data = Object.assign(msg.data.loadpoints[i], { loadpoint: i });
this.update(data);
}

delete msg.data.loadpoints;
this.update(msg.data);
}.bind(this)).catch(toasts.error);
}
};

Expand Down Expand Up @@ -178,9 +176,32 @@ const app = new Vue({
data: {
compact: false,
},
methods: {
connect: function() {
const protocol = loc.protocol == "https:" ? "wss:" : "ws:";
const uri = protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "ws";
const ws = new WebSocket(uri), self = this;
ws.onerror = function(evt) {
ws.close();
};
ws.onclose = function(evt) {
window.setTimeout(self.connect, 1000);
};
ws.onmessage = function(evt) {
try {
var msg = JSON.parse(evt.data);
store.update(msg);
}
catch (e) {
toasts.error(e, evt.data)
}
};
},
},
created: function () {
const urlParams = new URLSearchParams(window.location.search);
this.compact = urlParams.get("compact");
this.connect(); // websocket listener
},
});

Expand Down Expand Up @@ -265,33 +286,9 @@ Vue.component('site', {
mixins: [formatter],
computed: {
multi: function() {
console.log(this.state);
return this.state.loadpoints.length > 1 || app.compact;
},
},
methods: {
connect: function() {
const protocol = loc.protocol == "https:" ? "wss:" : "ws:";
const uri = protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "ws";
const ws = new WebSocket(uri), self = this;
ws.onerror = function(evt) {
ws.close();
};
ws.onclose = function(evt) {
window.setTimeout(self.connect, 1000);
};
ws.onmessage = function(evt) {
try {
var msg = JSON.parse(evt.data);
store.update(msg);
}
catch (e) {
toasts.error(e, evt.data)
}
};
},
},
created: function() {
this.connect();
}
});

Expand Down Expand Up @@ -424,5 +421,3 @@ Vue.component("soc", {
}
},
});

store.init();
1 change: 1 addition & 0 deletions core/loadpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
lp.publish("mode", lp.Mode)
lp.publish("targetSoC", lp.SoC.Target)
lp.publish("minSoC", lp.SoC.Min)
lp.publish("socLevels", lp.SoC.Levels)
lp.Unlock()

// prepare charger status
Expand Down
57 changes: 0 additions & 57 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,63 +101,6 @@ func NewSite() *Site {
return lp
}

// SiteConfiguration contains the global site configuration
type SiteConfiguration struct {
Title string `json:"title"`
GridMeter bool `json:"gridMeter"`
PVMeter bool `json:"pvMeter"`
BatteryMeter bool `json:"batteryMeter"`
LoadPoints []LoadpointConfiguration `json:"loadpoints"`
}

// LoadpointConfiguration is the loadpoint feature structure
type LoadpointConfiguration struct {
Mode string `json:"mode"`
Title string `json:"title"`
Phases int64 `json:"phases"`
MinCurrent int64 `json:"minCurrent"`
MaxCurrent int64 `json:"maxCurrent"`
ChargeMeter bool `json:"chargeMeter"`
SoC bool `json:"soc"`
SoCCapacity int64 `json:"socCapacity"`
SoCTitle string `json:"socTitle"`
SoCLevels []int `json:"socLevels"`
TargetSoC int `json:"targetSoC"`
}

// Configuration returns meter configuration
func (site *Site) Configuration() SiteConfiguration {
c := SiteConfiguration{
Title: site.Title,
GridMeter: site.gridMeter != nil,
PVMeter: site.pvMeter != nil,
BatteryMeter: site.batteryMeter != nil,
}

for _, lp := range site.loadpoints {
lpc := LoadpointConfiguration{
Mode: string(lp.GetMode()),
Title: lp.Name(),
Phases: lp.Phases,
MinCurrent: lp.MinCurrent,
MaxCurrent: lp.MaxCurrent,
ChargeMeter: lp.HasChargeMeter(),
}

if lp.vehicle != nil {
lpc.SoC = true
lpc.SoCCapacity = lp.vehicle.Capacity()
lpc.SoCTitle = lp.vehicle.Title()
lpc.SoCLevels = lp.SoC.Levels
lpc.TargetSoC = lp.GetTargetSoC()
}

c.LoadPoints = append(c.LoadPoints, lpc)
}

return c
}

func meterCapabilities(name string, meter interface{}) string {
_, power := meter.(api.Meter)
_, energy := meter.(api.MeterEnergy)
Expand Down
1 change: 0 additions & 1 deletion core/site_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "github.com/andig/evcc/api"
// SiteAPI is the external site API
type SiteAPI interface {
Healthy() bool
Configuration() SiteConfiguration
LoadPoints() []LoadPointAPI
LoadPointSettingsAPI
}
Expand Down
1 change: 0 additions & 1 deletion hems/semp/semp.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ type SEMP struct {

// site is the minimal interface for accessing site methods
type site interface {
Configuration() core.SiteConfiguration
LoadPoints() []core.LoadPointAPI
}

Expand Down
12 changes: 1 addition & 11 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func indexHandler(site core.SiteAPI, useLocal bool) http.HandlerFunc {
"Version": Version,
"Commit": Commit,
"Configured": len(site.LoadPoints()),
"Tag": time.Now().Unix(),
}); err != nil {
log.ERROR.Println("httpd: failed to render main page:", err.Error())
}
Expand Down Expand Up @@ -102,14 +101,6 @@ func HealthHandler(site core.SiteAPI) http.HandlerFunc {
}
}

// ConfigHandler returns current charge mode
func ConfigHandler(site core.SiteAPI) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res := site.Configuration()
jsonResponse(w, r, res)
}
}

// TemplatesHandler returns current charge mode
func TemplatesHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -259,9 +250,8 @@ type HTTPd struct {
func NewHTTPd(url string, site core.SiteAPI, hub *SocketHub, cache *util.Cache) *HTTPd {
var routes = map[string]route{
"health": {[]string{"GET"}, "/health", HealthHandler(site)},
"config": {[]string{"GET"}, "/config", ConfigHandler(site)},
"templates": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", TemplatesHandler()},
"state": {[]string{"GET"}, "/state", StateHandler(cache)},
"templates": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", TemplatesHandler()},
"getmode": {[]string{"GET"}, "/mode", CurrentChargeModeHandler(site)},
"setmode": {[]string{"POST", "OPTIONS"}, "/mode/{mode:[a-z]+}", ChargeModeHandler(site)},
"gettargetsoc": {[]string{"GET"}, "/targetsoc", CurrentTargetSoCHandler(site)},
Expand Down
2 changes: 1 addition & 1 deletion server/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (m *MQTT) encode(v interface{}) string {
case fmt.Stringer, string:
s = fmt.Sprintf("%s", val)
case float64:
s = fmt.Sprintf("%.4g", val)
s = fmt.Sprintf("%.5g", val)
default:
s = fmt.Sprintf("%v", val)
}
Expand Down
Loading

0 comments on commit 7683f66

Please sign in to comment.