HEMS: published detailed config including max power (#25869)
This commit is contained in:
parent
f365e2099a
commit
c25bf05086
50 changed files with 364 additions and 177 deletions
|
|
@ -105,7 +105,8 @@ export default {
|
|||
},
|
||||
sponsorStatus() {
|
||||
const sponsor = store.state?.sponsor || {};
|
||||
const { name, expiresSoon } = sponsor;
|
||||
const name = sponsor.status.name;
|
||||
const expiresSoon = sponsor.status.expiresSoon;
|
||||
let textClass = "";
|
||||
let badgeClass = "";
|
||||
let title = name;
|
||||
|
|
|
|||
|
|
@ -110,13 +110,13 @@ export default {
|
|||
return store?.state?.sponsor;
|
||||
},
|
||||
token() {
|
||||
return this.sponsor?.token;
|
||||
return this.sponsor?.status.token;
|
||||
},
|
||||
fromYaml() {
|
||||
return this.sponsor?.fromYaml;
|
||||
},
|
||||
name() {
|
||||
return this.sponsor?.name || "";
|
||||
return this.sponsor?.status.name || "";
|
||||
},
|
||||
showTokenForm() {
|
||||
return this.editMode || !this.token;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
data-testid="telemetry-modal"
|
||||
>
|
||||
<p>{{ $t("config.telemetry.description") }}</p>
|
||||
<TelemetrySettings :sponsorActive="sponsor && !!sponsor.name" :telemetry="telemetry" />
|
||||
<TelemetrySettings :sponsorActive="sponsorActive" :telemetry="telemetry" />
|
||||
</GenericModal>
|
||||
</template>
|
||||
|
||||
|
|
@ -23,5 +23,10 @@ export default defineComponent({
|
|||
sponsor: { type: Object as PropType<Sponsor> },
|
||||
telemetry: { type: Boolean },
|
||||
},
|
||||
computed: {
|
||||
sponsorActive(): boolean {
|
||||
return !!this.sponsor?.status.name;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
<div v-else class="my-4">
|
||||
<LiveCommunity />
|
||||
<TelemetrySettings
|
||||
:sponsorActive="!!sponsor?.name"
|
||||
:sponsorActive="sponsorActive"
|
||||
:telemetry="telemetry"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -241,6 +241,9 @@ export default defineComponent({
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
sponsorActive(): boolean {
|
||||
return !!this.sponsor?.status.name;
|
||||
},
|
||||
tariffLink() {
|
||||
return `${docsPrefix()}/docs/reference/configuration/tariffs`;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,23 +46,25 @@ NoSponsor.args = {};
|
|||
|
||||
export const Trial = Template.bind({});
|
||||
Trial.args = {
|
||||
name: "trial",
|
||||
status: { name: "trial" },
|
||||
};
|
||||
|
||||
export const IndividualSponsor = Template.bind({});
|
||||
IndividualSponsor.args = {
|
||||
name: "naltatis",
|
||||
status: { name: "naltatis" },
|
||||
};
|
||||
|
||||
export const VictronDevice = Template.bind({});
|
||||
VictronDevice.args = {
|
||||
name: "victron",
|
||||
status: { name: "victron" },
|
||||
};
|
||||
|
||||
// Add an extra story showing the expiring state
|
||||
export const ExpiringSponsor = Template.bind({});
|
||||
ExpiringSponsor.args = {
|
||||
name: "naltatis",
|
||||
expiresSoon: true,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days from now
|
||||
status: {
|
||||
name: "naltatis",
|
||||
expiresSoon: true,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days from now
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ import "@h2d2/shopicons/es/regular/heart";
|
|||
import "@h2d2/shopicons/es/regular/stars";
|
||||
import "@h2d2/shopicons/es/regular/clock";
|
||||
import { docsPrefix } from "@/i18n";
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { SponsorStatus } from "@/types/evcc";
|
||||
|
||||
export const TRIAL = "trial";
|
||||
export const VICTRON_DEVICE = "victron";
|
||||
|
|
@ -75,13 +76,13 @@ export const VICTRON_DEVICE = "victron";
|
|||
export default defineComponent({
|
||||
name: "Sponsor",
|
||||
props: {
|
||||
name: String,
|
||||
expiresAt: String,
|
||||
expiresSoon: Boolean,
|
||||
status: Object as PropType<SponsorStatus>,
|
||||
fromYaml: Boolean,
|
||||
token: String,
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.status?.name;
|
||||
},
|
||||
isTrial() {
|
||||
return this.name === TRIAL;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ const Template: StoryFn<typeof SponsorTokenExpires> = (args) => ({
|
|||
// Create stories for each variant
|
||||
export const SomeDay = Template.bind({});
|
||||
SomeDay.args = {
|
||||
expiresSoon: true,
|
||||
expiresAt: new Date(Date.now() + 22 * 20 * 60 * 1000).toISOString(),
|
||||
status: {
|
||||
expiresSoon: true,
|
||||
expiresAt: new Date(Date.now() + 22 * 20 * 60 * 1000).toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
<template>
|
||||
<div v-if="expiresSoon" class="alert alert-warning my-4" role="alert">
|
||||
<i18n-t tag="div" keypath="settings.sponsorToken.expires" scope="global">
|
||||
<template #inXDays>
|
||||
{{ inXDays }}
|
||||
</template>
|
||||
<template #getNewToken>
|
||||
<a href="https://sponsor.evcc.io" target="_blank" class="text-danger">
|
||||
{{ $t("settings.sponsorToken.getNew") }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div>
|
||||
<i18n-t tag="span" keypath="settings.sponsorToken.expires" scope="global">
|
||||
<template #inXDays>
|
||||
{{ inXDays }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
{{ " " }}
|
||||
<i18n-t
|
||||
tag="span"
|
||||
:keypath="
|
||||
fromYaml
|
||||
? 'settings.sponsorToken.expiresUpdateYaml'
|
||||
: 'settings.sponsorToken.expiresUpdateUi'
|
||||
"
|
||||
scope="global"
|
||||
>
|
||||
<template #getNewToken>
|
||||
<a href="https://sponsor.evcc.io" target="_blank" class="text-danger">
|
||||
{{ $t("settings.sponsorToken.getNewToken") }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<em v-if="!isTrial" class="d-block mt-2">
|
||||
{{ $t("settings.sponsorToken.hint") }}
|
||||
|
|
@ -20,18 +33,26 @@
|
|||
<script lang="ts">
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { TRIAL } from "./Sponsor.vue";
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { SponsorStatus } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SponsorTokenExpires",
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
expiresSoon: Boolean,
|
||||
expiresAt: String,
|
||||
name: String,
|
||||
status: Object as PropType<SponsorStatus>,
|
||||
fromYaml: Boolean,
|
||||
},
|
||||
computed: {
|
||||
expiresSoon() {
|
||||
return this.status?.expiresSoon;
|
||||
},
|
||||
expiresAt() {
|
||||
return this.status?.expiresAt;
|
||||
},
|
||||
name() {
|
||||
return this.status?.name;
|
||||
},
|
||||
inXDays() {
|
||||
return this.expiresAt
|
||||
? this.fmtTimeAgo(new Date(this.expiresAt).getTime() - new Date().getTime())
|
||||
|
|
|
|||
|
|
@ -55,9 +55,11 @@ PendingOAuthStatus.args = {
|
|||
export const TokenExpires = Template.bind({});
|
||||
TokenExpires.args = {
|
||||
sponsor: {
|
||||
name: "Sponsor",
|
||||
expiresAt: new Date().toISOString(),
|
||||
expiresSoon: true,
|
||||
status: {
|
||||
name: "Sponsor",
|
||||
expiresAt: new Date().toISOString(),
|
||||
expiresSoon: true,
|
||||
},
|
||||
fromYaml: false,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -175,9 +175,11 @@ export default defineComponent({
|
|||
authorizationRequired() {
|
||||
return this.providers.length > 0;
|
||||
},
|
||||
sponsorExpires(): boolean {
|
||||
return !!this.sponsor?.status.expiresSoon;
|
||||
},
|
||||
showConfigBadge() {
|
||||
const userConfigError = isUserConfigError(this.fatal);
|
||||
return this.sponsor.expiresSoon || userConfigError;
|
||||
return this.sponsorExpires || isUserConfigError(this.fatal);
|
||||
},
|
||||
showRootBadge() {
|
||||
return this.authorizationRequired || this.showConfigBadge;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,17 @@ export interface InfluxConfig {
|
|||
}
|
||||
|
||||
export interface HemsConfig {
|
||||
type: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface HemsStatus {
|
||||
maxPower: number;
|
||||
}
|
||||
|
||||
export interface Hems {
|
||||
status?: HemsStatus;
|
||||
config: HemsConfig;
|
||||
fromYaml: boolean;
|
||||
}
|
||||
|
||||
export interface ShmConfig {
|
||||
|
|
@ -69,7 +79,7 @@ export interface State {
|
|||
tariffSolar?: number;
|
||||
mqtt?: MqttConfig;
|
||||
influx?: InfluxConfig;
|
||||
hems?: HemsConfig;
|
||||
hems?: Hems;
|
||||
shm?: ShmConfig;
|
||||
sponsor?: Sponsor;
|
||||
eebus?: any;
|
||||
|
|
@ -345,11 +355,15 @@ export type SessionInfoKey =
|
|||
| "price"
|
||||
| "co2";
|
||||
|
||||
export interface Sponsor {
|
||||
name: string;
|
||||
expiresAt: string;
|
||||
expiresSoon: boolean;
|
||||
export interface SponsorStatus {
|
||||
name?: string;
|
||||
expiresAt?: string;
|
||||
expiresSoon?: boolean;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface Sponsor {
|
||||
status: SponsorStatus;
|
||||
fromYaml: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@
|
|||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.hems.title')"
|
||||
editable
|
||||
:editable="!hems?.fromYaml"
|
||||
:error="hasClassError('hems')"
|
||||
data-testid="hems"
|
||||
@edit="openModal('hemsModal')"
|
||||
|
|
@ -636,8 +636,11 @@ export default defineComponent({
|
|||
shmTags() {
|
||||
return { configured: { value: true } };
|
||||
},
|
||||
hems() {
|
||||
return store.state?.hems;
|
||||
},
|
||||
hemsTags() {
|
||||
const { type } = store.state?.hems || {};
|
||||
const type = this.hems?.config?.type;
|
||||
if (!type) {
|
||||
return { configured: { value: false } };
|
||||
}
|
||||
|
|
@ -656,13 +659,12 @@ export default defineComponent({
|
|||
|
||||
return result;
|
||||
},
|
||||
isSponsor() {
|
||||
const { name } = store.state?.sponsor || {};
|
||||
return !!name;
|
||||
},
|
||||
sponsor() {
|
||||
return store.state?.sponsor;
|
||||
},
|
||||
isSponsor(): boolean {
|
||||
return !!this.sponsor?.status.name;
|
||||
},
|
||||
telemetry() {
|
||||
// @ts-expect-error: telemetry property exists but not in TypeScript definitions
|
||||
return store.state?.telemetry === true;
|
||||
|
|
|
|||
31
cmd/root.go
31
cmd/root.go
|
|
@ -12,8 +12,10 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api/globalconfig"
|
||||
"github.com/evcc-io/evcc/core"
|
||||
"github.com/evcc-io/evcc/core/keys"
|
||||
hemsapi "github.com/evcc-io/evcc/hems/hems"
|
||||
"github.com/evcc-io/evcc/push"
|
||||
"github.com/evcc-io/evcc/server"
|
||||
"github.com/evcc-io/evcc/server/db"
|
||||
|
|
@ -280,8 +282,12 @@ func runRoot(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
// start HEMS server
|
||||
var hems hemsapi.API
|
||||
if err == nil {
|
||||
err = wrapErrorWithClass(ClassHEMS, configureHEMS(&conf.HEMS, site))
|
||||
hems, err = configureHEMS(&conf.HEMS, site)
|
||||
if err != nil {
|
||||
err = wrapErrorWithClass(ClassHEMS, err)
|
||||
}
|
||||
}
|
||||
|
||||
// setup MCP
|
||||
|
|
@ -303,7 +309,6 @@ func runRoot(cmd *cobra.Command, args []string) {
|
|||
|
||||
// publish initial settings
|
||||
valueChan <- util.Param{Key: keys.EEBus, Val: conf.EEBus.Configured()}
|
||||
valueChan <- util.Param{Key: keys.Hems, Val: conf.HEMS}
|
||||
valueChan <- util.Param{Key: keys.Shm, Val: conf.SHM}
|
||||
valueChan <- util.Param{Key: keys.Influx, Val: conf.Influx}
|
||||
valueChan <- util.Param{Key: keys.Interval, Val: conf.Interval}
|
||||
|
|
@ -311,7 +316,23 @@ func runRoot(cmd *cobra.Command, args []string) {
|
|||
valueChan <- util.Param{Key: keys.ModbusProxy, Val: conf.ModbusProxy}
|
||||
valueChan <- util.Param{Key: keys.Mqtt, Val: conf.Mqtt}
|
||||
valueChan <- util.Param{Key: keys.Network, Val: conf.Network}
|
||||
valueChan <- util.Param{Key: keys.Sponsor, Val: sponsor.Status()}
|
||||
valueChan <- util.Param{Key: keys.Sponsor, Val: struct {
|
||||
Status sponsor.Status `json:"status"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Status: sponsor.GetStatus(),
|
||||
FromYaml: fromYaml.sponsor,
|
||||
}}
|
||||
|
||||
valueChan <- util.Param{Key: keys.Hems, Val: struct {
|
||||
Config globalconfig.Hems `json:"config"`
|
||||
Status *hemsapi.Status `json:"status,omitempty"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Config: conf.HEMS,
|
||||
Status: hemsapi.GetStatus(hems),
|
||||
FromYaml: fromYaml.hems,
|
||||
}}
|
||||
|
||||
// publish system infos
|
||||
valueChan <- util.Param{Key: keys.Version, Val: util.FormattedVersion()}
|
||||
|
|
@ -347,7 +368,9 @@ func runRoot(cmd *cobra.Command, args []string) {
|
|||
valueChan <- util.Param{Key: keys.DemoMode, Val: true}
|
||||
}
|
||||
|
||||
httpd.RegisterSystemHandler(site, valueChan, cache, authObject, func() {
|
||||
httpd.RegisterSystemHandler(site, func(k string, v any) {
|
||||
valueChan <- util.Param{Key: k, Val: v}
|
||||
}, cache, authObject, func() {
|
||||
log.INFO.Println("evcc was stopped by user. OS should restart the service. Or restart manually.")
|
||||
err = errors.New("restart required") // https://gokrazy.org/development/process-interface/
|
||||
once.Do(func() { close(stopC) }) // signal loop to end
|
||||
|
|
|
|||
35
cmd/setup.go
35
cmd/setup.go
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/evcc-io/evcc/core/session"
|
||||
coresettings "github.com/evcc-io/evcc/core/settings"
|
||||
"github.com/evcc-io/evcc/hems"
|
||||
hemsapi "github.com/evcc-io/evcc/hems/hems"
|
||||
"github.com/evcc-io/evcc/hems/shm"
|
||||
"github.com/evcc-io/evcc/meter"
|
||||
"github.com/evcc-io/evcc/plugin/golang"
|
||||
|
|
@ -79,6 +80,11 @@ var conf = globalconfig.All{
|
|||
},
|
||||
}
|
||||
|
||||
var fromYaml struct {
|
||||
sponsor bool
|
||||
hems bool
|
||||
}
|
||||
|
||||
var nameRE = regexp.MustCompile(`^[a-zA-Z0-9_.:-]+$`)
|
||||
|
||||
func nameValid(name string) error {
|
||||
|
|
@ -492,11 +498,10 @@ func configureSponsorship(token string) (err error) {
|
|||
if token, err = settings.String(keys.SponsorToken); err != nil {
|
||||
return err
|
||||
}
|
||||
sponsor.SetFromYaml(false) // from database
|
||||
} else if token != "" {
|
||||
fromYaml.sponsor = true
|
||||
}
|
||||
|
||||
// TODO migrate settings
|
||||
|
||||
return sponsor.ConfigureSponsorship(token)
|
||||
}
|
||||
|
||||
|
|
@ -711,32 +716,36 @@ func configureGo(conf []globalconfig.Go) error {
|
|||
}
|
||||
|
||||
// setup HEMS
|
||||
func configureHEMS(conf *globalconfig.Hems, site *core.Site) error {
|
||||
// migrate settings
|
||||
if settings.Exists(keys.Hems) {
|
||||
*conf = globalconfig.Hems{}
|
||||
if err := settings.Yaml(keys.Hems, new(map[string]any), &conf); err != nil {
|
||||
return err
|
||||
func configureHEMS(conf *globalconfig.Hems, site *core.Site) (hemsapi.API, error) {
|
||||
// use yaml if configured
|
||||
if conf.Type == "" {
|
||||
if settings.Exists(keys.Hems) {
|
||||
*conf = globalconfig.Hems{}
|
||||
if err := settings.Yaml(keys.Hems, new(map[string]any), &conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fromYaml.hems = true
|
||||
}
|
||||
|
||||
if conf.Type == "" {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
props, err := customDevice(conf.Other)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode custom hems '%s': %w", conf.Type, err)
|
||||
return nil, fmt.Errorf("cannot decode custom hems '%s': %w", conf.Type, err)
|
||||
}
|
||||
|
||||
hems, err := hems.NewFromConfig(context.TODO(), conf.Type, props, site)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed configuring hems: %w", err)
|
||||
return nil, fmt.Errorf("failed configuring hems: %w", err)
|
||||
}
|
||||
|
||||
go hems.Run()
|
||||
|
||||
return nil
|
||||
return hems, nil
|
||||
}
|
||||
|
||||
// networkSettings reads/migrates network settings
|
||||
|
|
|
|||
19
core/site.go
19
core/site.go
|
|
@ -73,7 +73,7 @@ var _ site.API = (*Site)(nil)
|
|||
|
||||
// Site is the main configuration container. A site can host multiple loadpoints.
|
||||
type Site struct {
|
||||
uiChan chan<- util.Param // client push messages
|
||||
valueChan chan<- util.Param // client push messages
|
||||
lpUpdateChan chan *Loadpoint
|
||||
|
||||
*Health
|
||||
|
|
@ -485,11 +485,16 @@ func (site *Site) DumpConfig() {
|
|||
// publish sends values to UI and databases
|
||||
func (site *Site) publish(key string, val any) {
|
||||
// test helper
|
||||
if site.uiChan == nil {
|
||||
if site.valueChan == nil {
|
||||
return
|
||||
}
|
||||
|
||||
site.uiChan <- util.Param{Key: key, Val: val}
|
||||
site.valueChan <- util.Param{Key: key, Val: val}
|
||||
}
|
||||
|
||||
// publish sends values to UI and databases
|
||||
func (site *Site) Publish(key string, val any) {
|
||||
site.publish(key, val)
|
||||
}
|
||||
|
||||
func (site *Site) collectMeters(key string, meters []config.Device[api.Meter]) []measurement {
|
||||
|
|
@ -1043,7 +1048,7 @@ func (site *Site) prepare() {
|
|||
}
|
||||
|
||||
// Prepare attaches communication channels to site and loadpoints
|
||||
func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event) {
|
||||
func (site *Site) Prepare(valueChan chan<- util.Param, pushChan chan<- push.Event) {
|
||||
// https://github.com/evcc-io/evcc/issues/11191 prevent deadlock
|
||||
// https://github.com/evcc-io/evcc/pull/11675 maintain message order
|
||||
|
||||
|
|
@ -1051,12 +1056,12 @@ func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event)
|
|||
ch := chanx.NewUnboundedChan[util.Param](context.Background(), 2)
|
||||
|
||||
// use ch.In for writing
|
||||
site.uiChan = ch.In
|
||||
site.valueChan = ch.In
|
||||
|
||||
// use ch.Out for reading
|
||||
go func() {
|
||||
for p := range ch.Out {
|
||||
uiChan <- p
|
||||
valueChan <- p
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -1074,7 +1079,7 @@ func (site *Site) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Event)
|
|||
select {
|
||||
case param := <-lpUIChan:
|
||||
param.Loadpoint = &id
|
||||
site.uiChan <- param
|
||||
site.valueChan <- param
|
||||
case ev := <-lpPushChan:
|
||||
ev.Loadpoint = &id
|
||||
pushChan <- ev
|
||||
|
|
|
|||
|
|
@ -5,8 +5,15 @@ import (
|
|||
"github.com/evcc-io/evcc/core/loadpoint"
|
||||
)
|
||||
|
||||
// publisher gives access to the site's publish function
|
||||
type Publisher interface {
|
||||
Publish(key string, val any)
|
||||
}
|
||||
|
||||
// API is the external site API
|
||||
type API interface {
|
||||
Publisher
|
||||
|
||||
Healthy() bool
|
||||
Loadpoints() []loadpoint.API
|
||||
Vehicles() Vehicles
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@ import (
|
|||
|
||||
"github.com/evcc-io/evcc/core/site"
|
||||
"github.com/evcc-io/evcc/hems/eebus"
|
||||
"github.com/evcc-io/evcc/hems/hems"
|
||||
"github.com/evcc-io/evcc/hems/relay"
|
||||
)
|
||||
|
||||
// HEMS describes the HEMS system interface
|
||||
type HEMS interface {
|
||||
Run()
|
||||
}
|
||||
|
||||
// NewFromConfig creates new HEMS from config
|
||||
func NewFromConfig(ctx context.Context, typ string, other map[string]any, site site.API) (HEMS, error) {
|
||||
func NewFromConfig(ctx context.Context, typ string, other map[string]any, site site.API) (hems.API, error) {
|
||||
switch strings.ToLower(typ) {
|
||||
case "sma", "shm", "semp":
|
||||
return nil, errors.New("breaking change: Sunny Home Manager integration is always on. See https://github.com/evcc-io/evcc/releases and https://docs.evcc.io/en/docs/integrations/sma-sunny-home-manager")
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ func NewEEBus(ctx context.Context, ski string, limits Limits, passthrough func(b
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (c *EEBus) ConsumptionLimit() float64 {
|
||||
return c.consumptionLimit.Value
|
||||
}
|
||||
|
||||
func (c *EEBus) Run() {
|
||||
for range time.Tick(c.interval) {
|
||||
if err := c.run(); err != nil {
|
||||
|
|
|
|||
21
hems/hems/api.go
Normal file
21
hems/hems/api.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package hems
|
||||
|
||||
// API describes the HEMS system interface
|
||||
type API interface {
|
||||
Run()
|
||||
ConsumptionLimit() float64
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
ConsumptionLimit float64 `json:"consumptionLimit"`
|
||||
// FeedinLimit float64 `json:"feedinLimit,omitempty"`
|
||||
}
|
||||
|
||||
func GetStatus(api API) *Status {
|
||||
if api == nil {
|
||||
return nil
|
||||
}
|
||||
return &Status{
|
||||
ConsumptionLimit: api.ConsumptionLimit(),
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,10 @@ func NewRelay(root api.Circuit, limit func() (bool, error), passthrough func(boo
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Relay) ConsumptionLimit() float64 {
|
||||
return c.maxPower
|
||||
}
|
||||
|
||||
func (c *Relay) Run() {
|
||||
for range time.Tick(c.interval) {
|
||||
if err := c.run(); err != nil {
|
||||
|
|
|
|||
|
|
@ -965,8 +965,10 @@
|
|||
"label": "Език"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Вашият спонсорски токен изтича след {inXDays}. {getNewToken} и го актуализирайте тук.",
|
||||
"getNew": "Вземете нов",
|
||||
"expires": "Вашият спонсорски токен изтича след {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} и го актуализирайте тук.",
|
||||
"expiresUpdateYaml": "{getNewToken} и го актуализирайте във вашия evcc.yaml.",
|
||||
"getNewToken": "Вземете нов",
|
||||
"hint": "Забележка: В бъдеще ще автоматизираме това."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -279,8 +279,10 @@
|
|||
"label": "Idioma"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "El teu token de patrocinador expira en {inXDays}. {getNewToken} i actualitza el teu fitxer de configuració.",
|
||||
"getNew": "Agafa'n un de nou",
|
||||
"expires": "El teu token de patrocinador expira en {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} i actualitza el teu fitxer de configuració.",
|
||||
"expiresUpdateYaml": "{getNewToken} i actualitza'l al teu evcc.yaml.",
|
||||
"getNewToken": "Agafa'n un de nou",
|
||||
"hint": "Nota: automatitzarém això en el futur."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1158,8 +1158,10 @@
|
|||
"show": "Zobrazit {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Váš sponzorský token vyprší za {inXDays}. {getNewToken} a aktualizujte ho zde.",
|
||||
"getNew": "Vezmi čerstvý",
|
||||
"expires": "Váš sponzorský token vyprší za {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} a aktualizujte ho zde.",
|
||||
"expiresUpdateYaml": "{getNewToken} a aktualizujte ho ve vašem evcc.yaml.",
|
||||
"getNewToken": "Vezmi čerstvý",
|
||||
"hint": "Poznámka: Toto v budoucnu zautomatizujeme."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1163,8 +1163,10 @@
|
|||
"show": "Vis {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Din sponsortoken udløber {inXDays}. {getNewToken} og opdater det her.",
|
||||
"getNew": "Hent en ny",
|
||||
"expires": "Din sponsortoken udløber {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} og opdater det her.",
|
||||
"expiresUpdateYaml": "{getNewToken} og opdater det i din evcc.yaml.",
|
||||
"getNewToken": "Hent en ny",
|
||||
"hint": "Note: Vi automatiserer dette i fremtiden."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1172,8 +1172,10 @@
|
|||
"show": "{title} anzeigen"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Dein Sponsortoken läuft {inXDays} ab. {getNewToken} und aktualisiere es hier.",
|
||||
"getNew": "Hol dir ein Neues",
|
||||
"expires": "Dein Sponsortoken läuft {inXDays} ab.",
|
||||
"expiresUpdateUi": "{getNewToken} und aktualisiere es hier.",
|
||||
"expiresUpdateYaml": "{getNewToken} und aktualisiere es in deiner evcc.yaml.",
|
||||
"getNewToken": "Hol dir ein Neues",
|
||||
"hint": "Hinweis: Wir werden das zukünftig automatisieren."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1142,8 +1142,10 @@
|
|||
"show": "Εμφάνιση {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Το sponsor token λήγει {inXDays}. {getNewToken} και κάντε εδώ μία ενημέρωση.",
|
||||
"getNew": "Πάρτε ένα φρέσκο",
|
||||
"expires": "Το sponsor token λήγει {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} και κάντε εδώ μία ενημέρωση.",
|
||||
"expiresUpdateYaml": "{getNewToken} και ενημερώστε το στο evcc.yaml σας.",
|
||||
"getNewToken": "Πάρτε ένα φρέσκο",
|
||||
"hint": "Σημείωση: Θα το αυτοματοποιήσουμε στο μέλλον."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1172,8 +1172,10 @@
|
|||
"show": "Show {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "You sponsor token expires {inXDays}. {getNewToken} and update it here.",
|
||||
"getNew": "Grab a fresh one",
|
||||
"expires": "You sponsor token expires {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} and update it here.",
|
||||
"expiresUpdateYaml": "{getNewToken} and update it in your evcc.yaml.",
|
||||
"getNewToken": "Grab a fresh one",
|
||||
"hint": "Note: We will automate this in the future."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -728,8 +728,10 @@
|
|||
"label": "Idioma"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Tu token de patrocinador expira en {inXDays}. {getNewToken} y puedes renovarlo aquí.",
|
||||
"getNew": "Coge uno nuevo",
|
||||
"expires": "Tu token de patrocinador expira en {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} y puedes renovarlo aquí.",
|
||||
"expiresUpdateYaml": "{getNewToken} y actualízalo en tu evcc.yaml.",
|
||||
"getNewToken": "Coge uno nuevo",
|
||||
"hint": "Nota: automatizaremos esto en el futuro."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1162,8 +1162,10 @@
|
|||
"show": "Näytä {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Sponsorointitunnuksesi vanhenee {inXDays}. {getNewToken} ja päivitä se tänne.",
|
||||
"getNew": "Ota uusi",
|
||||
"expires": "Sponsorointitunnuksesi vanhenee {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} ja päivitä se tänne.",
|
||||
"expiresUpdateYaml": "{getNewToken} ja päivitä se evcc.yaml-tiedostossa.",
|
||||
"getNewToken": "Ota uusi",
|
||||
"hint": "Huomaa: automatisoimme tämän tulevaisuudessa."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1162,8 +1162,10 @@
|
|||
"show": "Afficher {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Votre jeton de sponsor expire dans {inXDays}. {getNewToken} et mettez-le à jour ici.",
|
||||
"getNew": "Obtenez-en un nouveau",
|
||||
"expires": "Votre jeton de sponsor expire dans {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} et mettez-le à jour ici.",
|
||||
"expiresUpdateYaml": "{getNewToken} et mettez-le à jour dans votre evcc.yaml.",
|
||||
"getNewToken": "Obtenez-en un nouveau",
|
||||
"hint": "Remarque : nous allons automatiser cela à l'avenir."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1157,8 +1157,10 @@
|
|||
"show": "Prikaži {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Vaš sponzorski token isteče {inXDays}. {getNewToken} i obnovi ga ovdje.",
|
||||
"getNew": "Preuzmi novi",
|
||||
"expires": "Vaš sponzorski token isteče {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} i obnovi ga ovdje.",
|
||||
"expiresUpdateYaml": "{getNewToken} i obnovi ga u svojoj evcc.yaml.",
|
||||
"getNewToken": "Preuzmi novi",
|
||||
"hint": "Napomena: To ćemo automatizirati u budućnosti."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1129,8 +1129,10 @@
|
|||
"show": "Mutasd a {title}-t"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "A szponzor token-ed le fog járni {inXDays}. {getNewToken} és frissítsd itt.",
|
||||
"getNew": "Kérj egy újat",
|
||||
"expires": "A szponzor token-ed le fog járni {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} és frissítsd itt.",
|
||||
"expiresUpdateYaml": "{getNewToken} és frissítsd az evcc.yaml fájlodban.",
|
||||
"getNewToken": "Kérj egy újat",
|
||||
"hint": "Megjegyzés: Ezt a jövőben automatizálni fogjuk."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -927,8 +927,10 @@
|
|||
"label": "Lingua"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Il tuo token sponsor scadrà in {inXDays}. {getNewToken} e inseriscilo qua.",
|
||||
"getNew": "Prendine uno nuovo",
|
||||
"expires": "Il tuo token sponsor scadrà in {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} e inseriscilo qua.",
|
||||
"expiresUpdateYaml": "{getNewToken} e inseriscilo nel tuo evcc.yaml.",
|
||||
"getNewToken": "Prendine uno nuovo",
|
||||
"hint": "Nota: questa funzione verrà automatizzata."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1161,8 +1161,10 @@
|
|||
"show": "{title} uweisen"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Däi Sponsor Token leeft aus an {inXDays}. {getNewToken} an update et hei.",
|
||||
"getNew": "Huel dir een neit",
|
||||
"expires": "Däi Sponsor Token leeft aus an {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} an update et hei.",
|
||||
"expiresUpdateYaml": "{getNewToken} an update et an dengem evcc.yaml.",
|
||||
"getNewToken": "Huel dir een neit",
|
||||
"hint": "N.b.: Mir wäerten dëst an Zukunft automatiséieren."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1163,8 +1163,10 @@
|
|||
"show": "Rodyti {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Jūsų rėmėjo žetonas baigsis {inXDays}. {getNewToken} ir išsaugokite jį čia.",
|
||||
"getNew": "Gaukite naują",
|
||||
"expires": "Jūsų rėmėjo žetonas baigsis {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} ir išsaugokite jį čia.",
|
||||
"expiresUpdateYaml": "{getNewToken} ir išsaugokite jį savo evcc.yaml faile.",
|
||||
"getNewToken": "Gaukite naują",
|
||||
"hint": "Pastaba: ateityje šis procesas bus automatizuotas."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1161,8 +1161,10 @@
|
|||
"show": "{title} tonen"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Je sponsortoken vervalt {inXDays}. {getNewToken} en update het hier.",
|
||||
"getNew": "Vraag een nieuwe aan",
|
||||
"expires": "Je sponsortoken vervalt {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} en update het hier.",
|
||||
"expiresUpdateYaml": "{getNewToken} en update het in je evcc.yaml.",
|
||||
"getNewToken": "Vraag een nieuwe aan",
|
||||
"hint": "Notabene: We zullen dit in de toekomst automatiseren."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -394,8 +394,10 @@
|
|||
"label": "Språk"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Sponsorsymbolet ditt utløper om {inXDays}. {getNewToken} og oppdater oppsettsfilen din.",
|
||||
"getNew": "Få en ny token",
|
||||
"expires": "Sponsorsymbolet ditt utløper om {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} og oppdater oppsettsfilen din.",
|
||||
"expiresUpdateYaml": "{getNewToken} og oppdater i evcc.yaml.",
|
||||
"getNewToken": "Få en ny token",
|
||||
"hint": "Merk: Dette vil bli automatisert i fremtiden."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -847,8 +847,10 @@
|
|||
"show": "Pokaż {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Twój token sponsora wygasa {inXDays}. {getNewToken} i zaktualizuj tutaj.",
|
||||
"getNew": "Weź świeżego",
|
||||
"expires": "Twój token sponsora wygasa {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} i zaktualizuj tutaj.",
|
||||
"expiresUpdateYaml": "{getNewToken} i zaktualizuj w swoim evcc.yaml.",
|
||||
"getNewToken": "Weź świeżego",
|
||||
"hint": "Uwaga: zautomatyzujemy to w przyszłości."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1161,8 +1161,10 @@
|
|||
"show": "Mostrar {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "O seu token de Apoiante (Sponsor) expira em {inXDays}. {getNewToken} e atualize-o aqui.",
|
||||
"getNew": "Adquira um novo",
|
||||
"expires": "O seu token de Apoiante (Sponsor) expira em {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} e atualize-o aqui.",
|
||||
"expiresUpdateYaml": "{getNewToken} e atualize-o no seu evcc.yaml.",
|
||||
"getNewToken": "Adquira um novo",
|
||||
"hint": "Nota: Vamos tornar isso automático no futuro."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -408,8 +408,10 @@
|
|||
"label": "Limba"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Tokenul de sponsorizare expira {inXDays}. {getNewToken} si adauga-l in fisierul de configurare.",
|
||||
"getNew": "Descarcati unul nou",
|
||||
"expires": "Tokenul de sponsorizare expira {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} si adauga-l in fisierul de configurare.",
|
||||
"expiresUpdateYaml": "{getNewToken} si adauga-l în fișierul evcc.yaml.",
|
||||
"getNewToken": "Descarcati unul nou",
|
||||
"hint": "Nota: Vom automatiza acest proces in viitor."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1129,8 +1129,10 @@
|
|||
"show": "Prikaži {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Sponzorskemu žetonu poteče veljavnost čez {inXDays}. {getNewToken} in ga posodobite tukaj.",
|
||||
"getNew": "Zgrabi svežega",
|
||||
"expires": "Sponzorskemu žetonu poteče veljavnost čez {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} in ga posodobite tukaj.",
|
||||
"expiresUpdateYaml": "{getNewToken} in ga posodobite v svoji evcc.yaml.",
|
||||
"getNewToken": "Zgrabi svežega",
|
||||
"hint": "Opomba: To bomo v prihodnosti avtomatizirali."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1163,8 +1163,10 @@
|
|||
"show": "Visa {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Din sponsortoken löper ut om {inXDays}. {getNewToken} och uppdatera här.",
|
||||
"getNew": "Hämta en ny",
|
||||
"expires": "Din sponsortoken löper ut om {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} och uppdatera här.",
|
||||
"expiresUpdateYaml": "{getNewToken} och uppdatera i din evcc.yaml.",
|
||||
"getNewToken": "Hämta en ny",
|
||||
"hint": "Observera: Vi kommer att automatisera detta i framtiden."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1014,8 +1014,10 @@
|
|||
"label": "மொழி"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "நீங்கள் ஒப்புரவாளர் கிள்ளாக்கு காலாவதியாகிறது {inXDays}. {getNewToken} மற்றும் அதை இங்கே புதுப்பிக்கவும்.",
|
||||
"getNew": "ஒரு புதிய ஒன்றைப் பிடிக்கவும்",
|
||||
"expires": "நீங்கள் ஒப்புரவாளர் கிள்ளாக்கு காலாவதியாகிறது {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} மற்றும் அதை இங்கே புதுப்பிக்கவும்.",
|
||||
"expiresUpdateYaml": "{getNewToken} மற்றும் உங்கள் evcc.yaml இல் புதுப்பிக்கவும்.",
|
||||
"getNewToken": "ஒரு புதிய ஒன்றைப் பிடிக்கவும்",
|
||||
"hint": "குறிப்பு: எதிர்காலத்தில் இதை தானியக்கமாக்குவோம்."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1161,8 +1161,10 @@
|
|||
"show": "{title} göster"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Sponsor jetonun {inXDays} sonra sona erecek. {getNewToken} ve burada güncelle.",
|
||||
"getNew": "Yeni bir tane al",
|
||||
"expires": "Sponsor jetonun {inXDays} sonra sona erecek.",
|
||||
"expiresUpdateUi": "{getNewToken} ve burada güncelle.",
|
||||
"expiresUpdateYaml": "{getNewToken} ve evcc.yaml dosyanızda güncelle.",
|
||||
"getNewToken": "Yeni bir tane al",
|
||||
"hint": "Not: İleride bunu otomatik hale getireceğiz."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -1163,8 +1163,10 @@
|
|||
"show": "Показати {title}"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "Термін дії вашого спонсорського токена закінчується {inXDays}. {getNewToken} і оновіть його тут.",
|
||||
"getNew": "Візьми свіжий",
|
||||
"expires": "Термін дії вашого спонсорського токена закінчується {inXDays}.",
|
||||
"expiresUpdateUi": "{getNewToken} і оновіть його тут.",
|
||||
"expiresUpdateYaml": "{getNewToken} і оновіть його у вашому evcc.yaml.",
|
||||
"getNewToken": "Візьми свіжий",
|
||||
"hint": "Примітка: Ми автоматизуємо це в майбутньому."
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -939,8 +939,10 @@
|
|||
"label": "语言"
|
||||
},
|
||||
"sponsorToken": {
|
||||
"expires": "您的赞助令牌将在 {inXDays} 后过期。{getNewToken} 并在此处更新。",
|
||||
"getNew": "获取一个新的",
|
||||
"expires": "您的赞助令牌将在 {inXDays} 后过期。",
|
||||
"expiresUpdateUi": "{getNewToken} 并在此处更新。",
|
||||
"expiresUpdateYaml": "{getNewToken} 并在你的 evcc.yaml 中更新。",
|
||||
"getNewToken": "获取一个新的",
|
||||
"hint": "注意:我们将来会自动化此过程。"
|
||||
},
|
||||
"telemetry": {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type publisher func(string, any)
|
||||
|
||||
type route struct {
|
||||
Method string
|
||||
Pattern string
|
||||
|
|
@ -221,7 +223,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param)
|
|||
}
|
||||
|
||||
// RegisterSystemHandler provides system level handlers
|
||||
func (s *HTTPd) RegisterSystemHandler(site *core.Site, valueChan chan<- util.Param, cache *util.ParamCache, auth auth.Auth, shutdown func(), configFile string) {
|
||||
func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *util.ParamCache, auth auth.Auth, shutdown func(), configFile string) {
|
||||
router := s.Server.Handler.(*mux.Router)
|
||||
|
||||
// api
|
||||
|
|
@ -288,8 +290,8 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, valueChan chan<- util.Par
|
|||
"testconfig": {"POST", "/test/{class:[a-z]+}", testConfigHandler},
|
||||
"testmerged": {"POST", "/test/{class:[a-z]+}/merge/{id:[0-9.]+}", testConfigHandler},
|
||||
"interval": {"POST", "/interval/{value:[0-9.]+}", settingsSetDurationHandler(keys.Interval)},
|
||||
"updatesponsortoken": {"POST", "/sponsortoken", updateSponsortokenHandler},
|
||||
"deletesponsortoken": {"DELETE", "/sponsortoken", deleteSponsorTokenHandler},
|
||||
"updatesponsortoken": {"POST", "/sponsortoken", updateSponsortokenHandler(pub)},
|
||||
"deletesponsortoken": {"DELETE", "/sponsortoken", deleteSponsorTokenHandler(pub)},
|
||||
}
|
||||
|
||||
// yaml handlers
|
||||
|
|
@ -314,8 +316,8 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, valueChan chan<- util.Par
|
|||
keys.Shm: func() any { return new(shm.Config) },
|
||||
keys.Influx: func() any { return new(globalconfig.Influx) },
|
||||
} {
|
||||
routes["update"+key] = route{Method: "POST", Pattern: "/" + key, HandlerFunc: settingsSetJsonHandler(key, valueChan, fun)}
|
||||
routes["delete"+key] = route{Method: "DELETE", Pattern: "/" + key, HandlerFunc: settingsDeleteJsonHandler(key, valueChan, fun())}
|
||||
routes["update"+key] = route{Method: "POST", Pattern: "/" + key, HandlerFunc: settingsSetJsonHandler(key, pub, fun)}
|
||||
routes["delete"+key] = route{Method: "DELETE", Pattern: "/" + key, HandlerFunc: settingsDeleteJsonHandler(key, pub, fun())}
|
||||
}
|
||||
|
||||
for _, r := range routes {
|
||||
|
|
|
|||
|
|
@ -9,33 +9,53 @@ import (
|
|||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
)
|
||||
|
||||
func updateSponsortokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Token != "" {
|
||||
if err := sponsor.ConfigureSponsorship(req.Token); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
sponsor.SetFromYaml(false)
|
||||
|
||||
if req.Token != "" {
|
||||
if err := sponsor.ConfigureSponsorship(req.Token); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
pub(keys.Sponsor, struct {
|
||||
Status sponsor.Status `json:"status"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Status: sponsor.GetStatus(),
|
||||
FromYaml: false,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO find better place
|
||||
settings.SetString(keys.SponsorToken, req.Token)
|
||||
setConfigDirty()
|
||||
|
||||
jsonWrite(w, sponsor.GetStatus())
|
||||
}
|
||||
|
||||
// TODO find better place
|
||||
settings.SetString(keys.SponsorToken, req.Token)
|
||||
setConfigDirty()
|
||||
|
||||
jsonWrite(w, sponsor.Status())
|
||||
}
|
||||
|
||||
func deleteSponsorTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
settings.SetString(keys.SponsorToken, "")
|
||||
setConfigDirty()
|
||||
jsonWrite(w, true)
|
||||
func deleteSponsorTokenHandler(pub publisher) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
settings.SetString(keys.SponsorToken, "")
|
||||
|
||||
pub(keys.Sponsor, struct {
|
||||
Status sponsor.Status `json:"status"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Status: sponsor.Status{},
|
||||
FromYaml: false,
|
||||
})
|
||||
|
||||
setConfigDirty()
|
||||
jsonWrite(w, true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/redact"
|
||||
"github.com/gorilla/mux"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
|
@ -79,7 +78,7 @@ func settingsSetYamlHandler(key string, other, struc any) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func settingsSetJsonHandler(key string, valueChan chan<- util.Param, newStruc func() any) http.HandlerFunc {
|
||||
func settingsSetJsonHandler(key string, pub publisher, newStruc func() any) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
struc := newStruc()
|
||||
dec := json.NewDecoder(r.Body)
|
||||
|
|
@ -103,18 +102,18 @@ func settingsSetJsonHandler(key string, valueChan chan<- util.Param, newStruc fu
|
|||
settings.SetJson(key, struc)
|
||||
setConfigDirty()
|
||||
|
||||
valueChan <- util.Param{Key: key, Val: struc}
|
||||
pub(key, struc)
|
||||
|
||||
jsonWrite(w, true)
|
||||
}
|
||||
}
|
||||
|
||||
func settingsDeleteJsonHandler(key string, valueChan chan<- util.Param, struc any) http.HandlerFunc {
|
||||
func settingsDeleteJsonHandler(key string, pub publisher, struc any) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
settings.SetString(key, "")
|
||||
setConfigDirty()
|
||||
|
||||
valueChan <- util.Param{Key: key, Val: struc}
|
||||
pub(key, struc)
|
||||
|
||||
jsonWrite(w, true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
var (
|
||||
mu sync.RWMutex
|
||||
Subject, Token string
|
||||
fromYaml bool = true
|
||||
ExpiresAt time.Time
|
||||
)
|
||||
|
||||
|
|
@ -36,13 +35,6 @@ func IsAuthorizedForApi() bool {
|
|||
return IsAuthorized() && Subject != unavailable && Token != ""
|
||||
}
|
||||
|
||||
// SetFromYaml sets whether the token comes from YAML config or database
|
||||
func SetFromYaml(val bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
fromYaml = val
|
||||
}
|
||||
|
||||
// check and set sponsorship token
|
||||
func ConfigureSponsorship(token string) error {
|
||||
mu.Lock()
|
||||
|
|
@ -98,16 +90,15 @@ func redactToken(token string) string {
|
|||
return token[:6] + "......." + token[len(token)-6:]
|
||||
}
|
||||
|
||||
type sponsorStatus struct {
|
||||
type Status struct {
|
||||
Name string `json:"name"`
|
||||
ExpiresAt time.Time `json:"expiresAt,omitempty"`
|
||||
ExpiresSoon bool `json:"expiresSoon,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}
|
||||
|
||||
// Status returns the sponsorship status
|
||||
func Status() sponsorStatus {
|
||||
// GetStatus returns the sponsorship status
|
||||
func GetStatus() Status {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
|
|
@ -116,11 +107,10 @@ func Status() sponsorStatus {
|
|||
expiresSoon = true
|
||||
}
|
||||
|
||||
return sponsorStatus{
|
||||
return Status{
|
||||
Name: Subject,
|
||||
ExpiresAt: ExpiresAt,
|
||||
ExpiresSoon: expiresSoon,
|
||||
Token: redactToken(Token),
|
||||
FromYaml: fromYaml,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue