EEBUS: configure by default (#26944)
This commit is contained in:
parent
ca7bbba0a4
commit
768656e0c3
52 changed files with 712 additions and 324 deletions
|
|
@ -18,6 +18,13 @@ import (
|
|||
"github.com/evcc-io/evcc/util/modbus"
|
||||
)
|
||||
|
||||
// ConfigStatus for publishing config, status and source to UI and external systems
|
||||
type ConfigStatus struct {
|
||||
Config any `json:"config,omitempty"`
|
||||
Status any `json:"status,omitempty"`
|
||||
FromYaml bool `json:"fromYaml,omitempty"`
|
||||
}
|
||||
|
||||
type All struct {
|
||||
Network Network
|
||||
Ocpp ocpp.Config
|
||||
|
|
@ -79,13 +86,6 @@ func (c Hems) Redacted() any {
|
|||
|
||||
var _ api.Redactor = (*Mqtt)(nil)
|
||||
|
||||
func masked(s any) string {
|
||||
if s != "" {
|
||||
return "***"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Mqtt struct {
|
||||
mqtt.Config `mapstructure:",squash"`
|
||||
Topic string `json:"topic"`
|
||||
|
|
@ -97,12 +97,12 @@ func (m Mqtt) Redacted() any {
|
|||
Config: mqtt.Config{
|
||||
Broker: m.Broker,
|
||||
User: m.User,
|
||||
Password: masked(m.Password),
|
||||
Password: util.Masked(m.Password),
|
||||
ClientID: m.ClientID,
|
||||
Insecure: m.Insecure,
|
||||
CaCert: masked(m.CaCert),
|
||||
ClientCert: masked(m.ClientCert),
|
||||
ClientKey: masked(m.ClientKey),
|
||||
CaCert: util.Masked(m.CaCert),
|
||||
ClientCert: util.Masked(m.ClientCert),
|
||||
ClientKey: util.Masked(m.ClientKey),
|
||||
},
|
||||
Topic: m.Topic,
|
||||
}
|
||||
|
|
@ -124,10 +124,10 @@ func (c Influx) Redacted() any {
|
|||
return Influx{
|
||||
URL: c.URL,
|
||||
Database: c.Database,
|
||||
Token: masked(c.Token),
|
||||
Token: util.Masked(c.Token),
|
||||
Org: c.Org,
|
||||
User: c.User,
|
||||
Password: masked(c.Password),
|
||||
Password: util.Masked(c.Password),
|
||||
Insecure: c.Insecure,
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ type MessagingEventTemplate struct {
|
|||
Title, Msg string
|
||||
}
|
||||
|
||||
func (c Messaging) Configured() bool {
|
||||
func (c Messaging) IsConfigured() bool {
|
||||
return len(c.Services) > 0 || len(c.Events) > 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
'round-box--error': error,
|
||||
'round-box--warning': warning,
|
||||
'root--unconfigured': unconfigured,
|
||||
'root--with-tags': $slots.tags,
|
||||
}"
|
||||
>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="d-flex align-items-center" :class="{ 'mb-2': $slots.tags }">
|
||||
<div class="icon me-2">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
|
|
@ -33,8 +34,10 @@
|
|||
<shopicon-regular-adjust size="s"></shopicon-regular-adjust>
|
||||
</button>
|
||||
</div>
|
||||
<hr class="my-3 divide" />
|
||||
<slot name="tags" />
|
||||
<div v-if="$slots.tags">
|
||||
<hr class="my-3 divide" />
|
||||
<slot name="tags" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -67,7 +70,7 @@ export default {
|
|||
}
|
||||
let title = `${this.$t("config.main.name")}: <span class='font-monospace'>${this.name}</span>`;
|
||||
if (!this.editable) {
|
||||
title += `<div class="mt-1">${this.$t("config.main.yaml")}</div>`;
|
||||
title += `<div class="mt-1">${this.$t("config.general.fromYamlHint")}</div>`;
|
||||
}
|
||||
return `<div class="text-start">${title}</div>`;
|
||||
},
|
||||
|
|
@ -105,6 +108,8 @@ export default {
|
|||
list-style-type: none;
|
||||
border-radius: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
.root--with-tags {
|
||||
min-height: 8rem;
|
||||
}
|
||||
.root--unconfigured {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,153 @@
|
|||
<template>
|
||||
<YamlModal
|
||||
<JsonModal
|
||||
id="eebusModal"
|
||||
:title="$t('config.eebus.title')"
|
||||
:description="$t('config.eebus.description')"
|
||||
docs="/docs/reference/configuration/eebus"
|
||||
:defaultYaml="defaultYaml"
|
||||
removeKey="eebus"
|
||||
endpoint="/config/eebus"
|
||||
state-key="eebus.config"
|
||||
data-testid="eebus-modal"
|
||||
:no-buttons="fromYaml"
|
||||
:confirm-remove="$t('config.eebus.removeConfirm')"
|
||||
@changed="$emit('changed')"
|
||||
/>
|
||||
>
|
||||
<template #default="{ values }: { values: EebusConfig }">
|
||||
<p v-if="fromYaml" class="text-muted">
|
||||
{{ $t("config.general.fromYamlHint") }}
|
||||
</p>
|
||||
<FormRow
|
||||
v-if="values.shipid"
|
||||
:id="formId('shipid-display')"
|
||||
:label="$t('config.eebus.shipid')"
|
||||
:help="$t('config.eebus.shipidExplain')"
|
||||
>
|
||||
<input
|
||||
:id="formId('shipid-display')"
|
||||
:value="values.shipid"
|
||||
readonly
|
||||
class="form-control text-muted"
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
v-if="status.ski"
|
||||
:id="formId('ski-display')"
|
||||
:label="$t('config.eebus.ski')"
|
||||
:help="$t('config.eebus.skiExplain')"
|
||||
>
|
||||
<input
|
||||
:id="formId('ski-display')"
|
||||
:value="status.ski"
|
||||
readonly
|
||||
class="form-control text-muted"
|
||||
/>
|
||||
</FormRow>
|
||||
<PropertyCollapsible v-if="!fromYaml">
|
||||
<template #advanced>
|
||||
<div class="alert alert-danger">
|
||||
{{ $t("config.eebus.descriptionAdvanced") }}
|
||||
</div>
|
||||
<FormRow
|
||||
:id="formId('shipid')"
|
||||
:label="$t('config.eebus.shipid')"
|
||||
:help="$t('config.eebus.shipidHelp')"
|
||||
>
|
||||
<PropertyField
|
||||
:id="formId('shipid')"
|
||||
v-model="values.shipid"
|
||||
type="String"
|
||||
required
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
:id="formId('port')"
|
||||
:label="$t('config.eebus.port')"
|
||||
:help="$t('config.eebus.portHelp')"
|
||||
optional
|
||||
>
|
||||
<PropertyField
|
||||
:id="formId('port')"
|
||||
v-model="values.port"
|
||||
property="port"
|
||||
type="Int"
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
:id="formId('interfaces')"
|
||||
:label="$t('config.eebus.interfaces')"
|
||||
:help="$t('config.eebus.interfacesHelp')"
|
||||
optional
|
||||
example="eth0"
|
||||
>
|
||||
<PropertyField
|
||||
:id="formId('interfaces')"
|
||||
v-model="values.interfaces"
|
||||
type="List"
|
||||
/>
|
||||
</FormRow>
|
||||
<h6>{{ $t("config.eebus.certificate.title") }}</h6>
|
||||
<FormRow
|
||||
:id="formId('certificate-public')"
|
||||
:label="$t('config.eebus.certificate.public')"
|
||||
>
|
||||
<PropertyCertField
|
||||
:id="formId('certificate-public')"
|
||||
:model-value="values.certificate?.public"
|
||||
required
|
||||
@update:model-value="
|
||||
values.certificate ? (values.certificate.public = $event) : ''
|
||||
"
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
:id="formId('certificate-private')"
|
||||
:label="$t('config.eebus.certificate.private')"
|
||||
>
|
||||
<PropertyCertField
|
||||
:id="formId('certificate-private')"
|
||||
:model-value="values.certificate?.private"
|
||||
required
|
||||
@update:model-value="
|
||||
values.certificate ? (values.certificate.private = $event) : ''
|
||||
"
|
||||
/>
|
||||
</FormRow>
|
||||
</template>
|
||||
</PropertyCollapsible>
|
||||
</template>
|
||||
</JsonModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import YamlModal from "./YamlModal.vue";
|
||||
import defaultYaml from "./defaultYaml/eebus.yaml?raw";
|
||||
<script lang="ts">
|
||||
import type { PropType } from "vue";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { EebusConfig, EebusStatus } from "@/types/evcc";
|
||||
import JsonModal from "./JsonModal.vue";
|
||||
import FormRow from "./FormRow.vue";
|
||||
import PropertyField from "./PropertyField.vue";
|
||||
import PropertyCertField from "./PropertyCertField.vue";
|
||||
import PropertyCollapsible from "./PropertyCollapsible.vue";
|
||||
|
||||
export default {
|
||||
name: "EebusModal",
|
||||
components: { YamlModal },
|
||||
components: {
|
||||
JsonModal,
|
||||
FormRow,
|
||||
PropertyField,
|
||||
PropertyCertField,
|
||||
PropertyCollapsible,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: Object as PropType<EebusStatus>,
|
||||
default: () => ({}),
|
||||
},
|
||||
fromYaml: Boolean,
|
||||
},
|
||||
emits: ["changed"],
|
||||
data() {
|
||||
return { defaultYaml: defaultYaml.trim() };
|
||||
methods: {
|
||||
formId(s: string) {
|
||||
return `eebus-${s}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<p v-if="fromYaml" class="text-muted">
|
||||
{{ $t("config.hems.yamlConfigured") }}
|
||||
{{ $t("config.general.fromYamlHint") }}
|
||||
</p>
|
||||
</template>
|
||||
</YamlModal>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export default {
|
|||
saveMethod: { type: String, default: "post" },
|
||||
storeValuesInArray: Boolean,
|
||||
size: { type: String },
|
||||
confirmRemove: String,
|
||||
},
|
||||
emits: ["changed", "open"],
|
||||
data() {
|
||||
|
|
@ -121,7 +122,13 @@ export default {
|
|||
await this.load();
|
||||
},
|
||||
async load() {
|
||||
this.serverValues = this.stateKey ? store.state[this.stateKey] : store.state;
|
||||
if (this.stateKey) {
|
||||
// Support nested keys like "eebus.config"
|
||||
const keys = this.stateKey.split(".");
|
||||
this.serverValues = keys.reduce((obj, key) => obj?.[key], store.state);
|
||||
} else {
|
||||
this.serverValues = store.state;
|
||||
}
|
||||
if (this.transformReadValues) {
|
||||
this.serverValues = this.transformReadValues(this.serverValues);
|
||||
}
|
||||
|
|
@ -155,6 +162,9 @@ export default {
|
|||
this.saving = false;
|
||||
},
|
||||
async remove() {
|
||||
if (this.confirmRemove && !window.confirm(this.confirmRemove)) {
|
||||
return;
|
||||
}
|
||||
this.removing = true;
|
||||
this.error = "";
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<textarea :id="id" v-model="value" class="form-control" rows="3" />
|
||||
<textarea :id="id" v-model="value" class="form-control" rows="3" :required="required" />
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -26,6 +26,7 @@ export default {
|
|||
props: {
|
||||
id: String,
|
||||
modelValue: String,
|
||||
required: Boolean,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -220,7 +220,9 @@ export default {
|
|||
return this.property === "icon";
|
||||
},
|
||||
textarea() {
|
||||
return ["accessToken", "refreshToken", "identifiers"].includes(this.property);
|
||||
return (
|
||||
this.array || ["accessToken", "refreshToken", "identifiers"].includes(this.property)
|
||||
);
|
||||
},
|
||||
boolean() {
|
||||
return this.type === "Bool";
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
#shipid: EVCC-1234567890abcdef
|
||||
#interfaces:
|
||||
# - eth0
|
||||
#certificate:
|
||||
# public: |
|
||||
# -----BEGIN CERTIFICATE-----
|
||||
# 1234567890abcdef==
|
||||
# -----END CERTIFICATE-----
|
||||
# private: |
|
||||
# -----BEGIN EC PRIVATE KEY-----
|
||||
# 1234567890abcdef
|
||||
# -----END EC PRIVATE KEY-----
|
||||
|
|
@ -84,7 +84,7 @@ export interface State {
|
|||
hems?: Hems;
|
||||
shm?: ShmConfig;
|
||||
sponsor?: Sponsor;
|
||||
eebus?: any;
|
||||
eebus?: Eebus;
|
||||
modbusproxy?: ModbusProxy[];
|
||||
messaging?: any;
|
||||
interval?: number;
|
||||
|
|
@ -440,6 +440,29 @@ export enum MODBUS_PROTOCOL {
|
|||
RTU = "rtu",
|
||||
}
|
||||
|
||||
export type Certificate = {
|
||||
public: string;
|
||||
private: string;
|
||||
};
|
||||
|
||||
export type Eebus = {
|
||||
config: EebusConfig;
|
||||
status: EebusStatus;
|
||||
fromYaml?: boolean;
|
||||
};
|
||||
|
||||
export type EebusConfig = {
|
||||
uri: string;
|
||||
port: number;
|
||||
shipid: string;
|
||||
interfaces?: string[];
|
||||
certificate?: Certificate;
|
||||
};
|
||||
|
||||
export type EebusStatus = {
|
||||
ski: string;
|
||||
};
|
||||
|
||||
export type ModbusProxy = {
|
||||
port: number;
|
||||
readonly: MODBUS_PROXY_READONLY;
|
||||
|
|
|
|||
|
|
@ -210,33 +210,6 @@
|
|||
<DeviceTags :tags="influxTags" />
|
||||
</template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.eebus.title')"
|
||||
editable
|
||||
:error="hasClassError('eebus')"
|
||||
:unconfigured="isUnconfigured(eebusTags)"
|
||||
data-testid="eebus"
|
||||
@edit="openModal('eebusModal')"
|
||||
>
|
||||
<template #icon><EebusIcon /></template>
|
||||
<template #tags>
|
||||
<DeviceTags :tags="eebusTags" />
|
||||
</template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.ocpp.title')"
|
||||
editable
|
||||
:error="hasClassError('ocpp')"
|
||||
:unconfigured="isUnconfigured(ocppTags)"
|
||||
data-testid="ocpp"
|
||||
@edit="openModal('ocppModal')"
|
||||
>
|
||||
<template #icon><OcppIcon /></template>
|
||||
<template #tags>
|
||||
<DeviceTags :tags="ocppTags" />
|
||||
</template>
|
||||
</DeviceCard>
|
||||
|
||||
<DeviceCard
|
||||
:title="`${$t('config.circuits.title')}`"
|
||||
editable
|
||||
|
|
@ -278,19 +251,6 @@
|
|||
<DeviceTags :tags="modbusproxyTags" />
|
||||
</template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.shm.cardTitle')"
|
||||
editable
|
||||
:error="hasClassError('shm')"
|
||||
:unconfigured="isUnconfigured(shmTags)"
|
||||
data-testid="shm"
|
||||
@edit="openModal('shmModal')"
|
||||
>
|
||||
<template #icon><ShmIcon /></template>
|
||||
<template #tags>
|
||||
<DeviceTags :tags="shmTags" />
|
||||
</template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.hems.title')"
|
||||
editable
|
||||
|
|
@ -306,6 +266,37 @@
|
|||
</DeviceCard>
|
||||
</div>
|
||||
|
||||
<h2 class="my-4 mt-5">{{ $t("config.section.services") }}</h2>
|
||||
<div class="p-0 config-list">
|
||||
<DeviceCard
|
||||
:title="$t('config.ocpp.title')"
|
||||
editable
|
||||
:error="hasClassError('ocpp')"
|
||||
data-testid="ocpp"
|
||||
@edit="openModal('ocppModal')"
|
||||
>
|
||||
<template #icon><OcppIcon /></template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.shm.cardTitle')"
|
||||
editable
|
||||
:error="hasClassError('shm')"
|
||||
data-testid="shm"
|
||||
@edit="openModal('shmModal')"
|
||||
>
|
||||
<template #icon><ShmIcon /></template>
|
||||
</DeviceCard>
|
||||
<DeviceCard
|
||||
:title="$t('config.eebus.title')"
|
||||
editable
|
||||
:error="hasClassError('eebus')"
|
||||
data-testid="eebus"
|
||||
@edit="openModal('eebusModal')"
|
||||
>
|
||||
<template #icon><EebusIcon /></template>
|
||||
</DeviceCard>
|
||||
</div>
|
||||
|
||||
<hr class="my-5" />
|
||||
|
||||
<h2 class="my-4 mt-5">{{ $t("config.section.system") }}</h2>
|
||||
|
|
@ -387,7 +378,11 @@
|
|||
:extMeters="extMeters"
|
||||
@changed="yamlChanged"
|
||||
/>
|
||||
<EebusModal @changed="yamlChanged" />
|
||||
<EebusModal
|
||||
:status="eebus?.status"
|
||||
:from-yaml="eebus?.fromYaml"
|
||||
@changed="yamlChanged"
|
||||
/>
|
||||
<OcppModal :ocpp="ocpp" />
|
||||
<BackupRestoreModal v-bind="backupRestoreProps" />
|
||||
<PasswordModal update-mode />
|
||||
|
|
@ -656,12 +651,6 @@ export default defineComponent({
|
|||
vehicleOptions(): VehicleOption[] {
|
||||
return this.vehicles.map((v) => ({ key: v.name, name: v.config?.title || v.name }));
|
||||
},
|
||||
shmTags(): DeviceTags {
|
||||
const { vendorId, deviceId } = store.state?.shm || {};
|
||||
// TODO: use incoming SEMP connections to determin configured/active status
|
||||
const value = !!vendorId || !!deviceId;
|
||||
return { configured: { value } };
|
||||
},
|
||||
hems() {
|
||||
return store.state?.hems;
|
||||
},
|
||||
|
|
@ -698,30 +687,8 @@ export default defineComponent({
|
|||
// @ts-expect-error: telemetry property exists but not in TypeScript definitions
|
||||
return store.state?.telemetry === true;
|
||||
},
|
||||
eebusTags(): DeviceTags {
|
||||
return { configured: { value: store.state?.eebus || false } };
|
||||
},
|
||||
ocppTags(): DeviceTags {
|
||||
const ocpp = store.state?.ocpp;
|
||||
const stations = ocpp?.status?.stations || [];
|
||||
if (stations.length === 0) {
|
||||
return { configured: { value: false } };
|
||||
}
|
||||
|
||||
const connected = stations.filter((s) => s.status === "connected").length;
|
||||
const configured = stations.filter((s) => s.status === "configured").length;
|
||||
const detected = stations.filter((s) => s.status === "unknown").length;
|
||||
const total = connected + configured;
|
||||
|
||||
const tags: Record<string, any> = {
|
||||
connections: { value: `${connected}/${total}` },
|
||||
};
|
||||
|
||||
if (detected > 0) {
|
||||
tags["detected"] = { value: detected };
|
||||
}
|
||||
|
||||
return tags;
|
||||
eebus() {
|
||||
return store.state?.eebus;
|
||||
},
|
||||
modbusproxyTags(): DeviceTags {
|
||||
const config = store.state?.modbusproxy || [];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/cmd/shutdown"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/config"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
|
@ -120,3 +121,18 @@ func deviceHeader[T any](dev config.Device[T]) string {
|
|||
|
||||
return name
|
||||
}
|
||||
|
||||
// migrateYamlToJson converts a settings value from yaml to json if needed
|
||||
func migrateYamlToJson[T any](key string, res *T) error {
|
||||
if settings.IsJson(key) {
|
||||
return settings.Json(key, res)
|
||||
}
|
||||
|
||||
if err := settings.Yaml(key, new(T), res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.SetJson(key, res)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
34
cmd/helper_test.go
Normal file
34
cmd/helper_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/evcc-io/evcc/server/db"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrateYaml(t *testing.T) {
|
||||
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
|
||||
|
||||
const key = "foo"
|
||||
|
||||
type T struct {
|
||||
Key string
|
||||
Val []string
|
||||
}
|
||||
|
||||
expect := T{
|
||||
Key: "foo",
|
||||
Val: []string{"a", "b"},
|
||||
}
|
||||
|
||||
require.NoError(t, settings.SetYaml(key, expect))
|
||||
require.False(t, settings.IsJson(key))
|
||||
|
||||
var res T
|
||||
require.NoError(t, migrateYamlToJson(key, &res))
|
||||
require.True(t, settings.IsJson(key))
|
||||
|
||||
require.Equal(t, expect, res)
|
||||
}
|
||||
34
cmd/root.go
34
cmd/root.go
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/evcc-io/evcc/push"
|
||||
"github.com/evcc-io/evcc/server"
|
||||
"github.com/evcc-io/evcc/server/db"
|
||||
"github.com/evcc-io/evcc/server/eebus"
|
||||
"github.com/evcc-io/evcc/server/mcp"
|
||||
"github.com/evcc-io/evcc/server/network"
|
||||
"github.com/evcc-io/evcc/server/updater"
|
||||
|
|
@ -176,10 +177,7 @@ func runRoot(cmd *cobra.Command, args []string) {
|
|||
ocppCS := ocpp.Instance()
|
||||
ocppCS.SetUpdated(func() {
|
||||
// republish when OCPP state updates
|
||||
valueChan <- util.Param{Key: keys.Ocpp, Val: struct {
|
||||
Config ocpp.Config `json:"config"`
|
||||
Status ocpp.Status `json:"status"`
|
||||
}{
|
||||
valueChan <- util.Param{Key: keys.Ocpp, Val: globalconfig.ConfigStatus{
|
||||
Config: conf.Ocpp,
|
||||
Status: ocpp.GetStatus(),
|
||||
}}
|
||||
|
|
@ -329,35 +327,29 @@ 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.EEBus, Val: globalconfig.ConfigStatus{
|
||||
Config: conf.EEBus.Redacted(),
|
||||
Status: eebus.GetStatus(),
|
||||
FromYaml: fromYaml.eebus,
|
||||
}}
|
||||
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}
|
||||
valueChan <- util.Param{Key: keys.Messaging, Val: conf.Messaging.Configured()}
|
||||
valueChan <- util.Param{Key: keys.Messaging, Val: conf.Messaging.IsConfigured()}
|
||||
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.Ocpp, Val: struct {
|
||||
Config ocpp.Config `json:"config"`
|
||||
Status ocpp.Status `json:"status"`
|
||||
}{
|
||||
valueChan <- util.Param{Key: keys.Ocpp, Val: globalconfig.ConfigStatus{
|
||||
Config: conf.Ocpp,
|
||||
Status: ocpp.GetStatus(),
|
||||
}}
|
||||
valueChan <- util.Param{Key: keys.Sponsor, Val: struct {
|
||||
Status sponsor.Status `json:"status"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Status: sponsor.GetStatus(),
|
||||
valueChan <- util.Param{Key: keys.Sponsor, Val: globalconfig.ConfigStatus{
|
||||
Status: sponsor.RedactedStatus(),
|
||||
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,
|
||||
valueChan <- util.Param{Key: keys.Hems, Val: globalconfig.ConfigStatus{
|
||||
Config: conf.HEMS.Redacted(),
|
||||
Status: hemsapi.GetStatus(hems),
|
||||
FromYaml: fromYaml.hems,
|
||||
}}
|
||||
|
|
|
|||
46
cmd/setup.go
46
cmd/setup.go
|
|
@ -73,7 +73,7 @@ var conf = globalconfig.All{
|
|||
Topic: "evcc",
|
||||
},
|
||||
EEBus: eebus.Config{
|
||||
URI: ":4712",
|
||||
Port: 4712,
|
||||
},
|
||||
Database: globalconfig.DB{
|
||||
Type: "sqlite",
|
||||
|
|
@ -84,6 +84,7 @@ var conf = globalconfig.All{
|
|||
var fromYaml struct {
|
||||
sponsor bool
|
||||
hems bool
|
||||
eebus bool
|
||||
}
|
||||
|
||||
var nameRE = regexp.MustCompile(`^[a-zA-Z0-9_.:-]+$`)
|
||||
|
|
@ -164,7 +165,7 @@ func configureCircuits(conf *[]config.Named) error {
|
|||
|
||||
children := slices.Clone(*conf)
|
||||
|
||||
// TODO: check for circular references
|
||||
// TODO check for circular references
|
||||
NEXT:
|
||||
for i, cc := range children {
|
||||
if cc.Name == "" {
|
||||
|
|
@ -780,14 +781,23 @@ func configureOCPP(cfg *ocpp.Config, externalUrl string) {
|
|||
func configureEEBus(conf *eebus.Config) error {
|
||||
// migrate settings
|
||||
if settings.Exists(keys.EEBus) {
|
||||
*conf = eebus.Config{}
|
||||
if err := settings.Yaml(keys.EEBus, new(map[string]any), &conf); err != nil {
|
||||
if err := migrateYamlToJson(keys.EEBus, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if conf.IsConfigured() {
|
||||
fromYaml.eebus = true
|
||||
}
|
||||
|
||||
if !conf.Configured() {
|
||||
return nil
|
||||
if !conf.IsConfigured() {
|
||||
cc, err := eebus.DefaultConfig(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*conf = *cc
|
||||
if err := settings.SetJson(keys.EEBus, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
|
@ -968,33 +978,11 @@ func configureDevices(conf globalconfig.All) error {
|
|||
return joinErrors(errs...)
|
||||
}
|
||||
|
||||
// migrateYamlToJson converts a settings value from yaml to json if needed
|
||||
func migrateYamlToJson[T any](key string) error {
|
||||
var err error
|
||||
if settings.IsJson(key) {
|
||||
// already JSON, nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
var data T
|
||||
if err := settings.Yaml(key, new(T), &data); err == nil {
|
||||
settings.SetJson(key, data)
|
||||
log.INFO.Printf("migrated %s setting to JSON", key)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func configureModbusProxy(conf *[]globalconfig.ModbusProxy) error {
|
||||
if settings.Exists(keys.ModbusProxy) {
|
||||
// TODO: delete if not needed any more
|
||||
if err := migrateYamlToJson[[]globalconfig.ModbusProxy](keys.ModbusProxy); err != nil {
|
||||
if err := migrateYamlToJson(keys.ModbusProxy, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.Json(keys.ModbusProxy, &conf); err != nil {
|
||||
return fmt.Errorf("failed to read modbusproxy setting: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// prevent panic
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Списък за повторна поръчка",
|
||||
"experimental": "Експериментален",
|
||||
"forceSave": "Запази все пак",
|
||||
"fromYamlHint": "Забележка: Конфигурира се чрез evcc.yaml. Премахнете записа от файла, за да активирате редактирането тук.",
|
||||
"hideAdvancedSettings": "Скрий разширените настройки",
|
||||
"invalidFileSelected": "Избраният файл е невалиден",
|
||||
"noFileSelected": "Няма избран файл.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Изтегли CSV",
|
||||
"eventsRecorded": "Записани {count} събития за ограничение на мрежата.",
|
||||
"lastEvent": "Най-скорошно {timeAgo}.",
|
||||
"title": "Външен лимит",
|
||||
"yamlConfigured": "Забележка: Конфигурира се чрез evcc.yaml. Премахнете записа от файла, за да активирате редактирането тук."
|
||||
"title": "Външен лимит"
|
||||
},
|
||||
"icon": {
|
||||
"change": "промени",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "не е конфигуриран",
|
||||
"vehicles": "Моите Автомобили",
|
||||
"welcomeBannerText": "Започнете с създаването на поне един **зарядно устройство**, **нагревател**, **мрежа**, **соларна система**, **батерия** или **допълнителен електромер**. Ако искате само да тествате, изберете **демонстрационно устройство**..",
|
||||
"welcomeBannerTitle": "Да конфигурираме вашата система!",
|
||||
"yaml": "Устройствата от evcc.yaml не могат да се редактират."
|
||||
"welcomeBannerTitle": "Да конфигурираме вашата система!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Получавайте съобщения за вашите сесии на зареждане.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Ponovo naručiva lista",
|
||||
"experimental": "Eksperimentalni",
|
||||
"forceSave": "Sačuvaj svejedno",
|
||||
"fromYamlHint": "Napomena: Konfigurisano putem evcc.yaml. Uklonite unos iz datoteke da biste ovdje omogućili uređivanje.",
|
||||
"hideAdvancedSettings": "Sakrij napredne postavke",
|
||||
"invalidFileSelected": "Odabrana neispravna datoteka",
|
||||
"noFileSelected": "Nije odabrana nijedna datoteka.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Preuzmi CSV",
|
||||
"eventsRecorded": "Zabilježeno je {count} događaja ograničenja mreže.",
|
||||
"lastEvent": "Najnoviji prije {timeAgo}.",
|
||||
"title": "Vanjski limit",
|
||||
"yamlConfigured": "Napomena: Konfigurisano putem evcc.yaml. Uklonite unos iz datoteke da biste ovdje omogućili uređivanje."
|
||||
"title": "Vanjski limit"
|
||||
},
|
||||
"icon": {
|
||||
"change": "promjena",
|
||||
|
|
@ -344,6 +344,7 @@
|
|||
"thresholdDisableHelpZero": "Zaustavite kada se za {delay} ne može zadovoljiti minimalna potrebna snaga.",
|
||||
"thresholdEnableHelpZero": " Počnite kada se minimalna potrebna snaga može zadovoljiti za {delay}."
|
||||
},
|
||||
"main": {},
|
||||
"meter": {
|
||||
"save": "Sačuvaj"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Seznam umožňující úpravy pořadí",
|
||||
"experimental": "Experimentální nastavení",
|
||||
"forceSave": "Vynutit uložení",
|
||||
"fromYamlHint": "Poznámka: Konfigurace se provádí prostřednictvím souboru evcc.yaml. Chcete-li zde povolit úpravy, odstraňte záznam ze souboru.",
|
||||
"hideAdvancedSettings": "Skrýt pokročilá nastavení",
|
||||
"invalidFileSelected": "Zvolen neplatný soubor",
|
||||
"noFileSelected": "Nebyl zvolen žádný soubor.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Stáhnout CSV",
|
||||
"eventsRecorded": "Zaznamenáno {count} událostí omezení mřížky.",
|
||||
"lastEvent": "Nejnovější {timeAgo}.",
|
||||
"title": "Vnější limit",
|
||||
"yamlConfigured": "Poznámka: Konfigurace se provádí prostřednictvím souboru evcc.yaml. Chcete-li zde povolit úpravy, odstraňte záznam ze souboru."
|
||||
"title": "Vnější limit"
|
||||
},
|
||||
"icon": {
|
||||
"change": "Změnit",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "není konfigurováno",
|
||||
"vehicles": "Má vozidla",
|
||||
"welcomeBannerText": "Začněte vytvořením alespoň jednoho **nabíječe**, **ohřívače**, **sítě**, **solárního panelu**, **baterie** nebo **dalšího měřiče**. Pokud chcete pouze otestovat, vyberte **demo zařízení**.",
|
||||
"welcomeBannerTitle": "Pojďme nakonfigurovat váš systém!",
|
||||
"yaml": "Zařízení z evcc.yaml nelze upravovat."
|
||||
"welcomeBannerTitle": "Pojďme nakonfigurovat váš systém!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Dostávejte notifikace o průběhu nabíjení.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Rækkefølgen kan ændres",
|
||||
"experimental": "Eksperimentel",
|
||||
"forceSave": "Gem alligevel",
|
||||
"fromYamlHint": "Bemærk: Er konfigureret via evcc.yaml. Fjern indtastningen fra filen for at aktivere redigering her.",
|
||||
"hideAdvancedSettings": "Skjul avancerede indstillinger",
|
||||
"invalidFileSelected": "Ugyldig fil er valgt",
|
||||
"noFileSelected": "Ingen fil valgt.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Download CSV",
|
||||
"eventsRecorded": "Registrerede {count} netbegrænsningshændelser.",
|
||||
"lastEvent": "Sidst {timeAgo}.",
|
||||
"title": "Ekstern grænse",
|
||||
"yamlConfigured": "Bemærk: Er konfigureret via evcc.yaml. Fjern indtastningen fra filen for at aktivere redigering her."
|
||||
"title": "Ekstern grænse"
|
||||
},
|
||||
"icon": {
|
||||
"change": "ændre",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "ikke konfigureret",
|
||||
"vehicles": "Mine køretøjer",
|
||||
"welcomeBannerText": "Start med at oprette mindst én **lader**, **varmer**, **elnet**, **sol**, **batteri** eller **ekstra måler**. Hvis du blot vil teste, kan du vælge en **demoenhed**.",
|
||||
"welcomeBannerTitle": "Lad os konfigurere dit system!",
|
||||
"yaml": "Enheder fra evcc.yaml kan ikke ændres her."
|
||||
"welcomeBannerTitle": "Lad os konfigurere dit system!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Modtag beskeder om dine opladningssessioner.",
|
||||
|
|
|
|||
26
i18n/de.json
26
i18n/de.json
|
|
@ -172,7 +172,23 @@
|
|||
"loading": "Lade YAML-Editor …"
|
||||
},
|
||||
"eebus": {
|
||||
"description": "Grundkonfiguration für die Kommunikation mit anderen EEBus-Geräten.",
|
||||
"certificate": {
|
||||
"private": "Privater Schlüssel",
|
||||
"public": "Öffentliches Zertifikat",
|
||||
"title": "Zertifikate"
|
||||
},
|
||||
"description": "Konfiguration zur Kommunikation mit EEBus-kompatiblen Geräten wie Wallboxen oder dem Steuergerät des Netzbetreibers. Alle notwendigen Initialisierungen und die Zertifikatsgenerierung erfolgen automatisch beim ersten Start.",
|
||||
"descriptionAdvanced": "Keine Änderungen erforderlich. Bei Änderung ist Neukoppelung aller Geräte erforderlich.",
|
||||
"interfaces": "Schnittstellen",
|
||||
"interfacesHelp": "Begrenzt die Netzwerkschnittstellen, die EEBus nutzen soll, um Kommunikationsprobleme zu vermeiden. Feld leer lassen, um alle Schnittstellen zu verwenden. Ein Eintrag pro Zeile.",
|
||||
"port": "Port",
|
||||
"portHelp": "Der zu verwendende Port.",
|
||||
"removeConfirm": "Die gesamte EEBus-Konfiguration wird entfernt. Neue Zertifikate und Kennungen werden beim nächsten Start generiert. Bist du sicher?",
|
||||
"shipid": "SHIP-ID",
|
||||
"shipidExplain": "Permanente Gerätekennung zur Identifikation im EEBus-Netzwerk.",
|
||||
"shipidHelp": "Diese SHIP-ID ist mit den unten stehenden Zertifikaten verknüpft.",
|
||||
"ski": "SKI",
|
||||
"skiExplain": "Eindeutige Sicherheitskennung zum Koppeln von EEBus-Geräten.",
|
||||
"title": "EEBus"
|
||||
},
|
||||
"experimental": {
|
||||
|
|
@ -209,6 +225,7 @@
|
|||
"dragList": "Sortierbare Liste",
|
||||
"experimental": "Experimentell",
|
||||
"forceSave": "Trotzdem speichern",
|
||||
"fromYamlHint": "Hinweis: Konfiguriert über evcc.yaml. Entferne den Eintrag aus der Datei, um die Bearbeitung hier zu aktivieren.",
|
||||
"hideAdvancedSettings": "Erweiterte Einstellungen ausblenden",
|
||||
"invalidFileSelected": "Ungültige Datei ausgewählt",
|
||||
"noFileSelected": "Keine Datei ausgewählt.",
|
||||
|
|
@ -247,8 +264,7 @@
|
|||
"downloadCsv": "CSV herunterladen",
|
||||
"eventsRecorded": "{count} Netzbegrenzungen gespeichert.",
|
||||
"lastEvent": "Zuletzt {timeAgo}.",
|
||||
"title": "Externe Begrenzung",
|
||||
"yamlConfigured": "Hinweis: Konfiguriert über evcc.yaml. Entferne den Eintrag aus der Datei, um die Bearbeitung hier zu aktivieren."
|
||||
"title": "Externe Begrenzung"
|
||||
},
|
||||
"icon": {
|
||||
"change": "ändern",
|
||||
|
|
@ -390,8 +406,7 @@
|
|||
"unconfigured": "nicht konfiguriert",
|
||||
"vehicles": "Meine Fahrzeuge",
|
||||
"welcomeBannerText": "Lege zunächst eine **Wallbox**, **Heizung**, **Netzzähler**, **Solar**, **Batterie** oder **zusätzlichen Zähler** an. Falls du nur testen möchtest, wähle ein **Demogerät**.",
|
||||
"welcomeBannerTitle": "Los geht's mit der Konfiguration!",
|
||||
"yaml": "Geräte aus evcc.yaml sind nicht editierbar."
|
||||
"welcomeBannerTitle": "Los geht's mit der Konfiguration!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Benachrichtigungen über Ladevorgänge und andere Ereignisse erhalten.",
|
||||
|
|
@ -543,6 +558,7 @@
|
|||
"integrations": "Integrationen",
|
||||
"loadpoints": "Laden & Heizen",
|
||||
"meter": "PV & Batterie",
|
||||
"services": "Dienste",
|
||||
"system": "System",
|
||||
"vehicles": "Fahrzeuge"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Λίστα με αναδιάταξη",
|
||||
"experimental": "Πειραματικό",
|
||||
"forceSave": "Αποθήκευση ούτως ή άλλως",
|
||||
"fromYamlHint": "Σημείωση: Διαμορφώνεται μέσω του αρχείου evcc.yaml. Διαγράψτε την καταχώριση από το αρχείο για να ενεργοποιήσετε την επεξεργασία εδώ.",
|
||||
"hideAdvancedSettings": "Απόκρυψη ρυθμίσεων για προχωρημένους",
|
||||
"invalidFileSelected": "Επιλέχθηκε μη έγκυρο αρχείο",
|
||||
"noFileSelected": "Δεν έχει επιλεγεί αρχείο.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Λήψη CSV",
|
||||
"eventsRecorded": "Καταγράφηκαν {count} συμβάντα περιορισμού πλέγματος.",
|
||||
"lastEvent": "Πιο πρόσφατο {timeAgo}.",
|
||||
"title": "Εξωτερικό όριο",
|
||||
"yamlConfigured": "Σημείωση: Διαμορφώνεται μέσω του αρχείου evcc.yaml. Διαγράψτε την καταχώριση από το αρχείο για να ενεργοποιήσετε την επεξεργασία εδώ."
|
||||
"title": "Εξωτερικό όριο"
|
||||
},
|
||||
"icon": {
|
||||
"change": "αλλαγή",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "δεν έχει ρυθμιστεί",
|
||||
"vehicles": "Τα Οχήματά μου",
|
||||
"welcomeBannerText": "Ξεκινήστε δημιουργώντας τουλάχιστον έναν **φορτιστή**, **θερμαντήρα**, **πλέγμα**, **ηλιακό**, **μπαταρία** ή **επιπλέον μετρητή**. Αν θέλετε απλώς να δοκιμάσετε, επιλέξτε μια **συσκευή επίδειξης**.",
|
||||
"welcomeBannerTitle": "Ας διαμορφώσουμε το σύστημά σας!",
|
||||
"yaml": "Συσκευές που ορίζονται στο evcc.yaml δεν είναι εδώ προς επεξεργασία."
|
||||
"welcomeBannerTitle": "Ας διαμορφώσουμε το σύστημά σας!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Λάβετε μηνύματα σχετικά με τις συνεδρίες φόρτισης.",
|
||||
|
|
|
|||
26
i18n/en.json
26
i18n/en.json
|
|
@ -172,7 +172,23 @@
|
|||
"loading": "Loading YAML editor…"
|
||||
},
|
||||
"eebus": {
|
||||
"description": "Configuration that enables evcc to communicate with other EEBus devices.",
|
||||
"certificate": {
|
||||
"private": "Private key",
|
||||
"public": "Public certificate",
|
||||
"title": "Certificates"
|
||||
},
|
||||
"description": "Configuration that enables evcc to communicate with EEBus compatible devices like chargers or a control unit of your grid operator. All relevant initialization and certificate generation is done automatically on first start.",
|
||||
"descriptionAdvanced": "No changes required. Only perform changes if you really know what you're doing. If you change either the SHIP-id or the certificates, you'll need to pair your devices again.",
|
||||
"interfaces": "Interfaces",
|
||||
"interfacesHelp": "Limit the network interfaces that EEBus should use to avoid communication problems. Leave the field blank to use all interfaces. One entry per line.",
|
||||
"port": "Port",
|
||||
"portHelp": "The port to be used.",
|
||||
"removeConfirm": "All EEBus configuration will be removed. New certificates and identifiers will be generated on next start. Are you sure?",
|
||||
"shipid": "SHIP-ID",
|
||||
"shipidExplain": "Permanent device identifier for identification in the EEBus network.",
|
||||
"shipidHelp": "This SHIP-ID is linked to the certificates below.",
|
||||
"ski": "SKI",
|
||||
"skiExplain": "Unique security identifier for pairing EEBus devices.",
|
||||
"title": "EEBus"
|
||||
},
|
||||
"experimental": {
|
||||
|
|
@ -209,6 +225,7 @@
|
|||
"dragList": "Reorderable list",
|
||||
"experimental": "Experimental",
|
||||
"forceSave": "Save anyway",
|
||||
"fromYamlHint": "Note: Configured via evcc.yaml. Remove the entry from the file to enable editing here.",
|
||||
"hideAdvancedSettings": "Hide advanced settings",
|
||||
"invalidFileSelected": "Invalid file selected",
|
||||
"noFileSelected": "No file selected.",
|
||||
|
|
@ -247,8 +264,7 @@
|
|||
"downloadCsv": "Download CSV",
|
||||
"eventsRecorded": "Recorded {count} grid limitation events.",
|
||||
"lastEvent": "Most recent {timeAgo}.",
|
||||
"title": "External Limit",
|
||||
"yamlConfigured": "Note: Configured via evcc.yaml. Remove the entry from the file to enable editing here."
|
||||
"title": "External Limit"
|
||||
},
|
||||
"icon": {
|
||||
"change": "change",
|
||||
|
|
@ -390,8 +406,7 @@
|
|||
"unconfigured": "not configured",
|
||||
"vehicles": "My Vehicles",
|
||||
"welcomeBannerText": "Start with creating at least one **charger**, **heater**, **grid**, **solar**, **battery** or **additional meter**. If you just want to test, pick a **demo device**.",
|
||||
"welcomeBannerTitle": "Let's configure your system!",
|
||||
"yaml": "Devices from evcc.yaml are not editable."
|
||||
"welcomeBannerTitle": "Let's configure your system!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Receive messages about your charging sessions.",
|
||||
|
|
@ -543,6 +558,7 @@
|
|||
"integrations": "Integrations",
|
||||
"loadpoints": "Charging & Heating",
|
||||
"meter": "Solar & Battery",
|
||||
"services": "Services",
|
||||
"system": "System",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Lista reordenable",
|
||||
"experimental": "Experimental",
|
||||
"forceSave": "Guardar de todos modos",
|
||||
"fromYamlHint": "Nota: Configurado a través de evcc.yaml. Elimina la entrada del archivo para poder editar aquí.",
|
||||
"hideAdvancedSettings": "Ocultar configuración avanzada",
|
||||
"invalidFileSelected": "Se ha seleccionado un archivo no válido",
|
||||
"noFileSelected": "No hay ningún archivo seleccionado.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Descargar CSV",
|
||||
"eventsRecorded": "Se registraron {count} eventos de limitación de la red.",
|
||||
"lastEvent": "Más reciente {timeAgo}.",
|
||||
"title": "Límite externo",
|
||||
"yamlConfigured": "Nota: Configurado a través de evcc.yaml. Elimina la entrada del archivo para poder editar aquí."
|
||||
"title": "Límite externo"
|
||||
},
|
||||
"icon": {
|
||||
"change": "cambio",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "no configurado",
|
||||
"vehicles": "Mis vehículos",
|
||||
"welcomeBannerText": "Empiece por crear al menos un **cargador**, **calentador**, **red**, **solar**, **batería** o **contador adicional**. Si solo desea realizar una prueba, elija un **dispositivo de demostración**.",
|
||||
"welcomeBannerTitle": "¡Configuremos su sistema!",
|
||||
"yaml": "Los dispositivos de evcc.yaml no se pueden editar."
|
||||
"welcomeBannerTitle": "¡Configuremos su sistema!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Recibe mensajes sobre tus sesiones de carga.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Järjestettävä lista",
|
||||
"experimental": "Kokeellinen",
|
||||
"forceSave": "Tallenna joka tapauksessa",
|
||||
"fromYamlHint": "Huomio: Asetus on evcc.yaml-tiedostosta. Poista asetus tiedostosta, jotta voisit muokata sitä täällä.",
|
||||
"hideAdvancedSettings": "Piilota edistyneet asetukset",
|
||||
"invalidFileSelected": "Valittu virheellinen tiedosto",
|
||||
"noFileSelected": "Ei valittua tiedostoa.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Lataa CSV-taulukko",
|
||||
"eventsRecorded": "Tallennettu {count} verkon rajoitustapahtumaa.",
|
||||
"lastEvent": "Viimeisin {timeAgo}.",
|
||||
"title": "Ulkoinen raja",
|
||||
"yamlConfigured": "Huomio: Asetus on evcc.yaml-tiedostosta. Poista asetus tiedostosta, jotta voisit muokata sitä täällä."
|
||||
"title": "Ulkoinen raja"
|
||||
},
|
||||
"icon": {
|
||||
"change": "vaihda",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "ei määritetty",
|
||||
"vehicles": "Minun ajoneuvot",
|
||||
"welcomeBannerText": "Aloita luomalla vähintään yksi **laturi**, **lämmitin**, **sähköverkko**, **aurinkopaneeli**, **akku** tai **lisämittari**. Jos haluat vain testata, valitse **esittelylaite**.",
|
||||
"welcomeBannerTitle": "Määritetään järjestelmäsi!",
|
||||
"yaml": "evcc.yaml-asetuksissa määritettyjä laitteita ei voi muokata."
|
||||
"welcomeBannerTitle": "Määritetään järjestelmäsi!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Vastaanota viestejä koskien lataustapahtumiasi.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Liste à ordonner",
|
||||
"experimental": "Expérimental",
|
||||
"forceSave": "Enregistrer quand même",
|
||||
"fromYamlHint": "Remarque : configuré via evcc.yaml. Supprimez l'entrée du fichier pour pouvoir effectuer des modifications ici.",
|
||||
"hideAdvancedSettings": "Masquer les paramètres avancés",
|
||||
"invalidFileSelected": "Fichier sélectionné non valide",
|
||||
"noFileSelected": "Aucun fichier sélectionné.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Télécharger CSV",
|
||||
"eventsRecorded": "Enregistrement de {count} événements liés à la limitation de la grille.",
|
||||
"lastEvent": "Le plus récent {timeAgo}.",
|
||||
"title": "Limite externe",
|
||||
"yamlConfigured": "Remarque : configuré via evcc.yaml. Supprimez l'entrée du fichier pour pouvoir effectuer des modifications ici."
|
||||
"title": "Limite externe"
|
||||
},
|
||||
"icon": {
|
||||
"change": "changer",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "non configuré",
|
||||
"vehicles": "Mes véhicules",
|
||||
"welcomeBannerText": "Commencez par créer au moins un **chargeur**, un **chauffage**, un **réseau**, un **système solaire**, une **batterie** ou un **compteur supplémentaire**. Si vous souhaitez simplement tester, choisissez un **appareil de démonstration**.",
|
||||
"welcomeBannerTitle": "Configurons votre système !",
|
||||
"yaml": "Un appareil provenant du evcc.yaml n’est pas modifiable."
|
||||
"welcomeBannerTitle": "Configurons votre système !"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Recevoir des messages à propos de vos sessions de charge.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Preurediv popis",
|
||||
"experimental": "Eksperimentalno",
|
||||
"forceSave": "Svejedno spremi",
|
||||
"fromYamlHint": "Napomena: Konfigurirano putem evcc.yaml. Uklonite unos iz datoteke kako biste ovdje omogućili uređivanje.",
|
||||
"hideAdvancedSettings": "Sakrij napredne postavke",
|
||||
"invalidFileSelected": "Neispravna datoteka",
|
||||
"noFileSelected": "Datoteka nije odabrana.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Preuzmi CSV",
|
||||
"eventsRecorded": "Zabilježeno je {count} događaja ograničenja mreže.",
|
||||
"lastEvent": "Najnoviji {timeAgo}",
|
||||
"title": "Vanjski limit",
|
||||
"yamlConfigured": "Napomena: Konfigurirano putem evcc.yaml. Uklonite unos iz datoteke kako biste ovdje omogućili uređivanje."
|
||||
"title": "Vanjski limit"
|
||||
},
|
||||
"icon": {
|
||||
"change": "promjeni",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "nije postavljeno",
|
||||
"vehicles": "Moja vozila",
|
||||
"welcomeBannerText": "Počnite s izradom barem jednog **punjača**, **grijača**, **mreže**, **solarne ploče**, **baterije** ili **dodatnog brojila**. Ako samo želite testirati, odaberite **demo uređaj**.",
|
||||
"welcomeBannerTitle": "Konfigurirajmo vaš sustav!",
|
||||
"yaml": "U evcc.yaml datoteci definirani uređaji se ne mogu uređivati."
|
||||
"welcomeBannerTitle": "Konfigurirajmo vaš sustav!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Primajte poruke o svojim sesijama punjenja.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Átrendezhető lista",
|
||||
"experimental": "Kísérleti",
|
||||
"forceSave": "Mentés mindenképpen",
|
||||
"fromYamlHint": "Megjegyzés: Az evcc.yaml fájlban konfigurálható. A szerkesztés engedélyezéséhez távolítsa el a bejegyzést a fájlból.",
|
||||
"hideAdvancedSettings": "Speciális beállítások elrejtése",
|
||||
"invalidFileSelected": "Érvénytelen fájl kiválasztva",
|
||||
"noFileSelected": "Nincs fájl kiválasztva.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "CSV letöltése",
|
||||
"eventsRecorded": "{count} rácskorlátozási esemény rögzítve.",
|
||||
"lastEvent": "Legutóbbi {timeAgo}.",
|
||||
"title": "Külső határ",
|
||||
"yamlConfigured": "Megjegyzés: Az evcc.yaml fájlban konfigurálható. A szerkesztés engedélyezéséhez távolítsa el a bejegyzést a fájlból."
|
||||
"title": "Külső határ"
|
||||
},
|
||||
"icon": {
|
||||
"change": "változtatás",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "nincs konfigurálva",
|
||||
"vehicles": "Járműveim",
|
||||
"welcomeBannerText": "Kezdje azzal, hogy létrehoz legalább egy **töltőt**, **fűtőt**, **hálózatot**, **napelemet**, **akkumulátort** vagy **kiegészítő mérőt**. Ha csak tesztelni szeretne, válasszon egy **demo eszközt**.",
|
||||
"welcomeBannerTitle": "Konfiguráljuk a rendszerét!",
|
||||
"yaml": "Az evcc.yaml fájlban szereplő eszközök nem szerkeszthetők."
|
||||
"welcomeBannerTitle": "Konfiguráljuk a rendszerét!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Értesítési üzenetek beállítása a töltési folyamatokról.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Elenco riordinabile",
|
||||
"experimental": "Sperimentale",
|
||||
"forceSave": "Salva comunque",
|
||||
"fromYamlHint": "Nota: configurato tramite evcc.yaml. Rimuovere la voce dal file per abilitare la modifica qui.",
|
||||
"hideAdvancedSettings": "Nascondi impostazioni avanzate",
|
||||
"invalidFileSelected": "File non valido selezionato",
|
||||
"noFileSelected": "Nessun file selezionato.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Scarica CSV",
|
||||
"eventsRecorded": "Registrati {count} eventi di limitazione della griglia.",
|
||||
"lastEvent": "Più recente {timeAgo}.",
|
||||
"title": "HEMSLimite esterno",
|
||||
"yamlConfigured": "Nota: configurato tramite evcc.yaml. Rimuovere la voce dal file per abilitare la modifica qui."
|
||||
"title": "HEMSLimite esterno"
|
||||
},
|
||||
"icon": {
|
||||
"change": "cambiamento",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "non configurato",
|
||||
"vehicles": "I miei veicoli",
|
||||
"welcomeBannerText": "Inizia creando almeno un **caricabatterie**, un **riscaldatore**, una **rete**, un **impianto solare**, una **batteria** o un **contatore aggiuntivo**. Se desideri solo effettuare un test, scegli un **dispositivo demo**.",
|
||||
"welcomeBannerTitle": "Configuriamo il tuo sistema!",
|
||||
"yaml": "I dispositivi di evcc.yaml non sono modificabili."
|
||||
"welcomeBannerTitle": "Configuriamo il tuo sistema!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Ricevi messaggi sulle sessioni di ricarica.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Sortéirbar Lëscht",
|
||||
"experimental": "Experimentell",
|
||||
"forceSave": "Trotzdem späicheren",
|
||||
"fromYamlHint": "Bemierkung: Konfiguréiert iwwer evcc.yaml. Läscht d'Astellungen aus der Datei fir d'Ännerung hei z'erméiglechen.",
|
||||
"hideAdvancedSettings": "Fortgeschratten Astellunge verstoppen",
|
||||
"invalidFileSelected": "Ongülteg Datei ausgewielt",
|
||||
"noFileSelected": "Keng Datei ausgewielt.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "CSV eroflueden",
|
||||
"eventsRecorded": "{count} gespäichert Limitatiounen vum Netz.",
|
||||
"lastEvent": "Méi rezent {timeAgo}.",
|
||||
"title": "Äusserlech Grenz",
|
||||
"yamlConfigured": "Bemierkung: Konfiguréiert iwwer evcc.yaml. Läscht d'Astellungen aus der Datei fir d'Ännerung hei z'erméiglechen."
|
||||
"title": "Äusserlech Grenz"
|
||||
},
|
||||
"icon": {
|
||||
"change": "änneren",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "net konfiguréiert",
|
||||
"vehicles": "Meng Gefierer",
|
||||
"welcomeBannerText": "Fänkt un mat der Erstelle vun op d'mannst engem **Ladegerät**, **Heizer**, **Netz**, **Solarpanel**, **Batterie** oder **zousätzlechen Zähler**. Wann Dir just testen wëllt, wielt e **Demonstratiounsapparat**.",
|
||||
"welcomeBannerTitle": "Loosst eis de System konfiguréieren!",
|
||||
"yaml": "Geräter aus der evcc.yaml Datei si net editiérbar."
|
||||
"welcomeBannerTitle": "Loosst eis de System konfiguréieren!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Notifikatiounen iwwer Är Opluedsessioune kréien.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Galima sukeisti vietomis",
|
||||
"experimental": "Eksperimentinis",
|
||||
"forceSave": "Vistiek įrašyti",
|
||||
"fromYamlHint": "Pastaba: Sukonfigūruota naudojant evcc.yaml. Pašalinkite įrašą iš failo, kad būtų galima redaguoti čia.",
|
||||
"hideAdvancedSettings": "Slėpti išplėstinius nustatymus",
|
||||
"invalidFileSelected": "Pasirinktas netinkamas failas",
|
||||
"noFileSelected": "Nepasirinktas failas.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Atsisiųsti CSV failą",
|
||||
"eventsRecorded": "Užfiksuota {count} tinklo galios apribojimo įvykių.",
|
||||
"lastEvent": "Naujausias {timeAgo}.",
|
||||
"title": "Išorinė riba",
|
||||
"yamlConfigured": "Pastaba: Sukonfigūruota naudojant evcc.yaml. Pašalinkite įrašą iš failo, kad būtų galima redaguoti čia."
|
||||
"title": "Išorinė riba"
|
||||
},
|
||||
"icon": {
|
||||
"change": "pakeisti",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "nesukonfigūruota",
|
||||
"vehicles": "Mano automobiliai",
|
||||
"welcomeBannerText": "Pradžiai sukonfigūruokite bent vieną **įkroviklį**, **šildytuvą**, **tinklo skaitiklį**, **saulės jėgainę** ar **papildomą skaitiklį**. Jei norite tik pasibandyti, pasirinkite **demonstracinį įrenginį**.",
|
||||
"welcomeBannerTitle": "Sukonfigūruokime jūsų sistemą!",
|
||||
"yaml": "Įtaisas iš evcc.yaml , čia konfigūruoti neina."
|
||||
"welcomeBannerTitle": "Sukonfigūruokime jūsų sistemą!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Gaukite pranešimus apie įkrovimo sesijas.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Sorteerbare lijst",
|
||||
"experimental": "Experimenteel",
|
||||
"forceSave": "Toch opslaan",
|
||||
"fromYamlHint": "Opmerking: Geconfigureerd via evcc.yaml. Verwijder de betreffende regels uit het bestand om hier bewerkingen mogelijk te maken.",
|
||||
"hideAdvancedSettings": "Verberg geavanceerde instellingen",
|
||||
"invalidFileSelected": "Ongeldig bestand geselecteerd",
|
||||
"noFileSelected": "Geen bestand geselecteerd.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Download CSV",
|
||||
"eventsRecorded": "{count} gebeurtenissen met rasterbeperkingen geregistreerd.",
|
||||
"lastEvent": "Meest recent {timeAgo}.",
|
||||
"title": "Externe limiet",
|
||||
"yamlConfigured": "Opmerking: Geconfigureerd via evcc.yaml. Verwijder de betreffende regels uit het bestand om hier bewerkingen mogelijk te maken."
|
||||
"title": "Externe limiet"
|
||||
},
|
||||
"icon": {
|
||||
"change": "wijzig",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "Niet geconfigureerd",
|
||||
"vehicles": "Mijn voertuigen",
|
||||
"welcomeBannerText": "Begin met het aanmaken van minstens één **oplader**, **verwarmer**, **netstroom**, **zonnepanelen**, **thuisbatterij** of **extra meter**. Als je gewoon wilt testen, kies dan een **demo-apparaat**.",
|
||||
"welcomeBannerTitle": "Start met het configureren van uw systeem!",
|
||||
"yaml": "Apparaten geconfigureerd in evcc.yaml kunnen hier niet worden bewerkt."
|
||||
"welcomeBannerTitle": "Start met het configureren van uw systeem!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Ontvang berichten over je oplaadsessies.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Omordningsbar liste",
|
||||
"experimental": "Eksperimentell",
|
||||
"forceSave": "Lagre likevel",
|
||||
"fromYamlHint": "Merk: Konfigurert via evcc.yaml. Fjern oppføringen fra filen for å aktivere redigering her.",
|
||||
"hideAdvancedSettings": "Skjul avanserte innstillinger",
|
||||
"invalidFileSelected": "Ugyldig fil valgt",
|
||||
"noFileSelected": "Ingen fil valgt.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Last ned CSV",
|
||||
"eventsRecorded": "Registrerte {count} hendelser med rutenettbegrensning.",
|
||||
"lastEvent": "Siste {timeAgo}.",
|
||||
"title": "Ekstern grense",
|
||||
"yamlConfigured": "Merk: Konfigurert via evcc.yaml. Fjern oppføringen fra filen for å aktivere redigering her."
|
||||
"title": "Ekstern grense"
|
||||
},
|
||||
"icon": {
|
||||
"change": "endring",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "ikke konfigurert",
|
||||
"vehicles": "Mine kjøretøy",
|
||||
"welcomeBannerText": "Begynn med å opprette minst én **lader**, **varmeapparat**, **nett**, **solcelle**, **batteri** eller **ekstra måler**. Hvis du bare vil teste, velger du en **demoenhet**.",
|
||||
"welcomeBannerTitle": "La oss konfigurere systemet ditt!",
|
||||
"yaml": "Enheter fra evcc.yaml kan ikke redigeres."
|
||||
"welcomeBannerTitle": "La oss konfigurere systemet ditt!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Motta meldinger om ladingsøktene dine.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Lista do ponownego zamówienia",
|
||||
"experimental": "Eksperymentalny",
|
||||
"forceSave": "Zapisz mimo to",
|
||||
"fromYamlHint": "Uwaga: Konfiguracja odbywa się za pomocą pliku evcc.yaml. Aby umożliwić edycję w tym miejscu, usuń wpis z pliku.",
|
||||
"hideAdvancedSettings": "Ukryj ustawienia zaawansowane",
|
||||
"invalidFileSelected": "Wybrano nieprawidłowy plik",
|
||||
"noFileSelected": "Nie wybrano pliku.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Pobierz CSV",
|
||||
"eventsRecorded": "Zarejestrowano {count} zdarzeń związanych z ograniczeniami sieci.",
|
||||
"lastEvent": "Najnowsze {timeAgo}.",
|
||||
"title": "Limit zewnętrzny",
|
||||
"yamlConfigured": "Uwaga: Konfiguracja odbywa się za pomocą pliku evcc.yaml. Aby umożliwić edycję w tym miejscu, usuń wpis z pliku."
|
||||
"title": "Limit zewnętrzny"
|
||||
},
|
||||
"icon": {
|
||||
"change": "zmień",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "nie skonfigurowane",
|
||||
"vehicles": "Moje pojazdy",
|
||||
"welcomeBannerText": "Zacznij od utworzenia co najmniej jednej **ładowarki**, **grzejnika**, **sieci**, **panelu słonecznego**, **baterii** lub **dodatkowego licznika**. Jeśli chcesz tylko przetestować, wybierz **urządzenie demonstracyjne**.",
|
||||
"welcomeBannerTitle": "Skonfigurujmy Twój system!",
|
||||
"yaml": "Urządzenia z pliku evcc.yaml nie mogą być edytowane."
|
||||
"welcomeBannerTitle": "Skonfigurujmy Twój system!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Otrzymuj wiadomości o sesjach ładowania.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Lista reordenável",
|
||||
"experimental": "Experimental",
|
||||
"forceSave": "Guardar na mesma",
|
||||
"fromYamlHint": "Nota: Configurado através do evcc.yaml. Remova a entrada do ficheiro para permitir a edição aqui.",
|
||||
"hideAdvancedSettings": "Ocultar definições avançadas",
|
||||
"invalidFileSelected": "Ficheiro selecionado inválido",
|
||||
"noFileSelected": "Nenhum ficheiro selecionado.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Descarregar CSV",
|
||||
"eventsRecorded": "Registou {count} eventos de limitação da rede.",
|
||||
"lastEvent": "Mais recente {timeAgo}.",
|
||||
"title": "Limite externo",
|
||||
"yamlConfigured": "Nota: Configurado através do evcc.yaml. Remova a entrada do ficheiro para permitir a edição aqui."
|
||||
"title": "Limite externo"
|
||||
},
|
||||
"icon": {
|
||||
"change": "alterar",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "não configurado",
|
||||
"vehicles": "Os meus Veículos",
|
||||
"welcomeBannerText": "Comece por criar pelo menos um **carregador**, **aquecedor**, **rede**, **solar**, **bateria** ou **medidor adicional**. Se quiser apenas testar, escolha um **dispositivo de demonstração**.",
|
||||
"welcomeBannerTitle": "Configuremos agora o seu sistema!",
|
||||
"yaml": "Os dispositivos de evcc.yaml não são editáveis."
|
||||
"welcomeBannerTitle": "Configuremos agora o seu sistema!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Receber mensagens sobre as sessões de carregamento.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Listă reordonabilă",
|
||||
"experimental": "Experimental",
|
||||
"forceSave": "Salvați oricum",
|
||||
"fromYamlHint": "Notă: Configurat prin evcc.yaml. Ștergeți intrarea din fișier pentru a activa editarea aici.",
|
||||
"hideAdvancedSettings": "Ascunde setările avansate",
|
||||
"invalidFileSelected": "Fișier invalid selectat",
|
||||
"noFileSelected": "Nu a fost selectat niciun fișier.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Descărcați CSV",
|
||||
"eventsRecorded": "Au fost înregistrate {count} evenimente de limitare a grilei.",
|
||||
"lastEvent": "Cel mai recent {timeAgo}.",
|
||||
"title": "Limită externă",
|
||||
"yamlConfigured": "Notă: Configurat prin evcc.yaml. Ștergeți intrarea din fișier pentru a activa editarea aici."
|
||||
"title": "Limită externă"
|
||||
},
|
||||
"icon": {
|
||||
"change": "schimbare",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "neconfigurat",
|
||||
"vehicles": "Masinile mele",
|
||||
"welcomeBannerText": "Începeți prin a crea cel puțin un **încărcător**, **încălzitor**, **rețea**, **sistem solar**, **baterie** sau **contor suplimentar**. Dacă doriți doar să testați, alegeți un **dispozitiv demo**.",
|
||||
"welcomeBannerTitle": "Să configurăm sistemul tău!",
|
||||
"yaml": "Dispozitivele din evcc.yaml nu pot fi editate."
|
||||
"welcomeBannerTitle": "Să configurăm sistemul tău!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Primiți mesaje despre sesiunile dvs. de încărcare.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Список, который можно переупорядочить",
|
||||
"experimental": "Экспериментальный",
|
||||
"forceSave": "Сохранить в любом случае",
|
||||
"fromYamlHint": "Примечание: Настраивается через evcc.yaml. Удалите запись из файла, чтобы включить редактирование здесь.",
|
||||
"hideAdvancedSettings": "Скрыть расширенные настройки",
|
||||
"invalidFileSelected": "Выбран недействительный файл",
|
||||
"noFileSelected": "Файл не выбран.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Скачать CSV",
|
||||
"eventsRecorded": "Зарегистрировано {count} событий ограничения сетки.",
|
||||
"lastEvent": "Последнее {timeAgo}.",
|
||||
"title": "Внешний предел",
|
||||
"yamlConfigured": "Примечание: Настраивается через evcc.yaml. Удалите запись из файла, чтобы включить редактирование здесь."
|
||||
"title": "Внешний предел"
|
||||
},
|
||||
"icon": {
|
||||
"change": "изменение",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "не настроен",
|
||||
"vehicles": "Мои транспортные средства",
|
||||
"welcomeBannerText": "Начните с создания хотя бы одного **зарядного устройства**, **нагревателя**, **сети**, **солнечной батареи**, **аккумулятора** или **дополнительного счетчика**. Если вы просто хотите протестировать, выберите **демонстрационное устройство**.",
|
||||
"welcomeBannerTitle": "Давайте настроим вашу систему!",
|
||||
"yaml": "Устройства из evcc.yaml не подлежат редактированию."
|
||||
"welcomeBannerTitle": "Давайте настроим вашу систему!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Получайте сообщения о ваших сеансах зарядки.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Seznam, ki ga je mogoče prerazporediti",
|
||||
"experimental": "Eksperimentalno",
|
||||
"forceSave": "Vseeno shrani",
|
||||
"fromYamlHint": "Opomba: Konfigurirano prek evcc.yaml. Odstranite vnos iz datoteke, da omogočite urejanje tukaj.",
|
||||
"hideAdvancedSettings": "Skrij napredne nastavitve",
|
||||
"invalidFileSelected": "Izbrana neveljavna datoteka",
|
||||
"noFileSelected": "Izbrana ni nobena datoteka.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Prenesi CSV",
|
||||
"eventsRecorded": "Zabeleženo {count} dogodkov omejitve omrežja.",
|
||||
"lastEvent": "Najnovejše {timeAgo}.",
|
||||
"title": "Zunanja meja",
|
||||
"yamlConfigured": "Opomba: Konfigurirano prek evcc.yaml. Odstranite vnos iz datoteke, da omogočite urejanje tukaj."
|
||||
"title": "Zunanja meja"
|
||||
},
|
||||
"icon": {
|
||||
"change": "sprememba",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "ni konfigurirano",
|
||||
"vehicles": "Moja vozila",
|
||||
"welcomeBannerText": "Začnite z ustvarjanjem vsaj enega **polnilnika**, **grelca**, **omrežja**, **sončne energije**, **baterije** ali **dodatnega merilnika**. Če želite samo preizkusiti, izberite **demo napravo**.",
|
||||
"welcomeBannerTitle": "Konfigurirajmo vaš sistem!",
|
||||
"yaml": "Naprav iz datoteke evcc.yaml ni mogoče urejati."
|
||||
"welcomeBannerTitle": "Konfigurirajmo vaš sistem!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Prejemanje sporočil o vaših sejah polnjenja.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Sorterbar lista",
|
||||
"experimental": "Experimentella funktioner",
|
||||
"forceSave": "Spara ändå",
|
||||
"fromYamlHint": "Obs: Konfigurerad via evcc.yaml. Ta bort posten från filen för att aktivera redigering här.",
|
||||
"hideAdvancedSettings": "Dölj avancerade inställningar",
|
||||
"invalidFileSelected": "Ogiltig fil vald",
|
||||
"noFileSelected": "Ingen fil vald.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Hämta CSV",
|
||||
"eventsRecorded": "Registrerade {count} händelser med nätbegränsning.",
|
||||
"lastEvent": "Senaste {timeAgo}.",
|
||||
"title": "Extern gräns",
|
||||
"yamlConfigured": "Obs: Konfigurerad via evcc.yaml. Ta bort posten från filen för att aktivera redigering här."
|
||||
"title": "Extern gräns"
|
||||
},
|
||||
"icon": {
|
||||
"change": "ändra",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "ej konfigurerad",
|
||||
"vehicles": "Mina fordon",
|
||||
"welcomeBannerText": "Börja med att skapa minst en **laddare**, **värmare**, **nät**, **solcell**, **batteri** eller **extra mätare**. Om du bara vill testa, välj en **demoenhet**.",
|
||||
"welcomeBannerTitle": "Låt oss konfigurera ditt system!",
|
||||
"yaml": "Konfigurera i evcc.yaml. Kan inte ändras i UI."
|
||||
"welcomeBannerTitle": "Låt oss konfigurera ditt system!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Få notiser om dina laddningar.",
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@
|
|||
"delete": "நீக்கு",
|
||||
"docsLink": "ஆவணங்களைக் காண்க.",
|
||||
"experimental": "சோதனை",
|
||||
"fromYamlHint": "Evcc.yaml இல் கட்டமைக்கப்பட்டுள்ளது. இடைமுகம் இல் திருத்த முடியாது.",
|
||||
"hideAdvancedSettings": "மேம்பட்ட அமைப்புகளை மறைக்க",
|
||||
"invalidFileSelected": "தவறான கோப்பு தேர்ந்தெடுக்கப்பட்டது",
|
||||
"noFileSelected": "எந்த கோப்பும் தேர்ந்தெடுக்கப்படவில்லை.",
|
||||
|
|
@ -314,8 +315,7 @@
|
|||
"name": "பெயர்",
|
||||
"title": "உள்ளமைவு",
|
||||
"unconfigured": "கட்டமைக்கப்படவில்லை",
|
||||
"vehicles": "என் வாகனங்கள்",
|
||||
"yaml": "Evcc.yaml இல் கட்டமைக்கப்பட்டுள்ளது. இடைமுகம் இல் திருத்த முடியாது."
|
||||
"vehicles": "என் வாகனங்கள்"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "உங்கள் சார்சிங் அமர்வுகள் பற்றிய செய்திகளைப் பெறுங்கள்.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Yeniden sıralanabilir liste",
|
||||
"experimental": "Deneysel",
|
||||
"forceSave": "Yine de kaydet",
|
||||
"fromYamlHint": "Not: evcc.yaml aracılığıyla yapılandırıldı. Burada düzenlemeyi etkinleştirmek için dosyadan öğeyi kaldır.",
|
||||
"hideAdvancedSettings": "“Gelişmiş ayarları gizle”",
|
||||
"invalidFileSelected": "Geçersiz dosya seçildi",
|
||||
"noFileSelected": "Seçili dosya yok.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "CSV'yi indir",
|
||||
"eventsRecorded": "{count} şebeke sınırlama etkinliği kaydedildi.",
|
||||
"lastEvent": "En son {timeAgo}.",
|
||||
"title": "Dış Limit",
|
||||
"yamlConfigured": "Not: evcc.yaml aracılığıyla yapılandırıldı. Burada düzenlemeyi etkinleştirmek için dosyadan öğeyi kaldır."
|
||||
"title": "Dış Limit"
|
||||
},
|
||||
"icon": {
|
||||
"change": "değiştir",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "yapılandırılmadı",
|
||||
"vehicles": "Araçlarım",
|
||||
"welcomeBannerText": "Önce bir **doldurma cihazı**, **ısıtıcı**, **elektrik sayacı**, **güneş enerjisi**, **batarya** veya **ek sayaç** oluştur. Sadece denemek istiyorsan, bir **demo cihazı** seç.",
|
||||
"welcomeBannerTitle": "Haydi sistemini yapılandıralım!",
|
||||
"yaml": "evcc.yaml'daki cihazlar düzenlenemez."
|
||||
"welcomeBannerTitle": "Haydi sistemini yapılandıralım!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Dolum oturumları hakkında bildirimler al.",
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"dragList": "Список, який можна перевпорядкувати",
|
||||
"experimental": "Експериментальний",
|
||||
"forceSave": "Зберегти все одно",
|
||||
"fromYamlHint": "Примітка: Налаштовано через evcc.yaml. Видаліть запис із файлу, щоб дозволити редагування тут.",
|
||||
"hideAdvancedSettings": "Приховати розширені налаштування",
|
||||
"invalidFileSelected": "Вибрано недійсний файл",
|
||||
"noFileSelected": "Не вибрано файл.",
|
||||
|
|
@ -247,8 +248,7 @@
|
|||
"downloadCsv": "Завантажити CSV-файл",
|
||||
"eventsRecorded": "Записано {count} подій обмеження мережі.",
|
||||
"lastEvent": "Найновіші {timeAgo}.",
|
||||
"title": "Зовнішнє обмеження",
|
||||
"yamlConfigured": "Примітка: Налаштовано через evcc.yaml. Видаліть запис із файлу, щоб дозволити редагування тут."
|
||||
"title": "Зовнішнє обмеження"
|
||||
},
|
||||
"icon": {
|
||||
"change": "зміна",
|
||||
|
|
@ -390,8 +390,7 @@
|
|||
"unconfigured": "не налаштовано",
|
||||
"vehicles": "Мій транспорт",
|
||||
"welcomeBannerText": "Почніть зі створення принаймні одного **зарядного пристрою**, **обігрівача**, **мережевої**, **сонячної батареї**, **акумулятора** або **додаткового лічильника**. Якщо ви просто хочете протестувати, виберіть **демонстраційний пристрій**.",
|
||||
"welcomeBannerTitle": "Давайте налаштуємо вашу систему!",
|
||||
"yaml": "Пристрої з evcc.yaml не можна редагувати."
|
||||
"welcomeBannerTitle": "Давайте налаштуємо вашу систему!"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "Отримувати повідомлення про ваші сеанси зарядки.",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@
|
|||
"delete": "删除",
|
||||
"docsLink": "查看文档。",
|
||||
"experimental": "实验性",
|
||||
"fromYamlHint": "evcc.yaml 中的设备不可编辑。",
|
||||
"hideAdvancedSettings": "隐藏高级设置",
|
||||
"off": "关",
|
||||
"on": "开",
|
||||
|
|
@ -306,8 +307,7 @@
|
|||
"name": "名称",
|
||||
"title": "配置",
|
||||
"unconfigured": "未配置",
|
||||
"vehicles": "我的车辆",
|
||||
"yaml": "evcc.yaml 中的设备不可编辑。"
|
||||
"vehicles": "我的车辆"
|
||||
},
|
||||
"messaging": {
|
||||
"description": "接收有关您充电会话及其他事件的消息。",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import (
|
|||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -34,7 +32,6 @@ import (
|
|||
"github.com/enbility/spine-go/model"
|
||||
"github.com/enbility/spine-go/spine"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/machine"
|
||||
)
|
||||
|
||||
type Device interface {
|
||||
|
|
@ -82,29 +79,33 @@ type EEBus struct {
|
|||
mux sync.Mutex
|
||||
log *util.Logger
|
||||
|
||||
Ski string
|
||||
ski string
|
||||
|
||||
clients map[string][]Device
|
||||
}
|
||||
|
||||
var Instance *EEBus
|
||||
|
||||
func GetStatus() any {
|
||||
return struct {
|
||||
Ski string `json:"ski"`
|
||||
}{
|
||||
Ski: Ski(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewServer(other Config) (*EEBus, error) {
|
||||
cc := Config{
|
||||
URI: ":4712",
|
||||
Port: 4712,
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("eebus")
|
||||
|
||||
protectedID := machine.ProtectedID("evcc-eebus")
|
||||
serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
|
||||
|
||||
if len(cc.ShipID) != 0 {
|
||||
serial = cc.ShipID
|
||||
serial := cc.ShipID
|
||||
if serial == "" {
|
||||
serial = createShipID()
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private))
|
||||
|
|
@ -112,22 +113,11 @@ func NewServer(other Config) (*EEBus, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
_, portValue, err := net.SplitHostPort(cc.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: get the voltage from the site
|
||||
configuration, err := eebusapi.NewConfiguration(
|
||||
BrandName, BrandName, Model, serial,
|
||||
model.DeviceTypeTypeEnergyManagementSystem,
|
||||
[]model.EntityTypeType{model.EntityTypeTypeCEM},
|
||||
port, certificate, time.Second*4,
|
||||
cc.Port, certificate, time.Second*4,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -147,8 +137,8 @@ func NewServer(other Config) (*EEBus, error) {
|
|||
}
|
||||
|
||||
c := &EEBus{
|
||||
log: log,
|
||||
Ski: ski,
|
||||
log: util.NewLogger("eebus"),
|
||||
ski: ski,
|
||||
clients: make(map[string][]Device),
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +216,7 @@ func (c *EEBus) RegisterDevice(ski, ip string, device Device) error {
|
|||
ski = shiputil.NormalizeSKI(ski)
|
||||
c.log.TRACE.Printf("registering ski: %s", ski)
|
||||
|
||||
if ski == c.Ski {
|
||||
if ski == c.ski {
|
||||
return errors.New("device ski can not be identical to host ski")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,12 +38,13 @@ func TestEEBus(t *testing.T) {
|
|||
})
|
||||
|
||||
require.NoError(t, err, "server")
|
||||
require.NotEmpty(t, srv.Ski, "server ski")
|
||||
|
||||
server.Instance = srv
|
||||
go srv.Run()
|
||||
|
||||
box, err := createControlbox(t.Context(), server.Instance.Ski, remotePort)
|
||||
require.NotEmpty(t, server.Ski(), "server ski")
|
||||
|
||||
box, err := createControlbox(t.Context(), server.Ski(), remotePort)
|
||||
require.NoError(t, err, "controlbox")
|
||||
|
||||
eventC := make(chan api.EventType, 1)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package eebus
|
||||
|
||||
import "github.com/evcc-io/evcc/util"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/machine"
|
||||
)
|
||||
|
||||
const (
|
||||
BrandName string = "EVCC"
|
||||
|
|
@ -11,17 +16,69 @@ const (
|
|||
var DeviceCode = util.Getenv("EEBUS_DEVICE_CODE", "EVCC_HEMS_01")
|
||||
|
||||
type Certificate struct {
|
||||
Public, Private string
|
||||
Public string `json:"public"`
|
||||
Private string `json:"private"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
URI string
|
||||
ShipID string
|
||||
Interfaces []string
|
||||
Certificate Certificate
|
||||
URI_ string `mapstructure:"uri" json:"uri"` // TODO deprecated
|
||||
Port int `json:"port"`
|
||||
ShipID string `json:"shipid"`
|
||||
Interfaces []string `json:"interfaces,omitempty"`
|
||||
Certificate Certificate `json:"certificate"`
|
||||
}
|
||||
|
||||
// Configured returns true if the EEbus server is configured
|
||||
func (c Config) Configured() bool {
|
||||
// IsConfigured returns true if the EEbus server is configured
|
||||
func (c Config) IsConfigured() bool {
|
||||
return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0
|
||||
}
|
||||
|
||||
// Redacted implements the redactor interface used by the tee publisher
|
||||
func (c Config) Redacted() any {
|
||||
return Config{
|
||||
URI_: c.URI_,
|
||||
Port: c.Port,
|
||||
ShipID: c.ShipID,
|
||||
Interfaces: c.Interfaces,
|
||||
Certificate: Certificate{
|
||||
Public: c.Certificate.Public,
|
||||
Private: util.Masked(c.Certificate.Private),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createShipID() string {
|
||||
protectedID := machine.ProtectedID("evcc-eebus")
|
||||
return fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
|
||||
}
|
||||
|
||||
func DefaultConfig(conf *Config) (*Config, error) {
|
||||
cert, err := CreateCertificate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
public, private, err := GetX509KeyPair(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := Config{
|
||||
Port: 4712,
|
||||
ShipID: createShipID(),
|
||||
Certificate: Certificate{
|
||||
Public: public,
|
||||
Private: private,
|
||||
},
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// Ski returns the EEbus server SKI
|
||||
func Ski() string {
|
||||
if Instance == nil {
|
||||
return ""
|
||||
}
|
||||
return Instance.ski
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,6 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
|
|||
|
||||
// yaml handlers
|
||||
for key, fun := range map[string]func() (any, any){
|
||||
keys.EEBus: func() (any, any) { return map[string]any{}, eebus.Config{} },
|
||||
keys.Hems: func() (any, any) { return map[string]any{}, config.Typed{} },
|
||||
keys.Tariffs: func() (any, any) { return map[string]any{}, globalconfig.Tariffs{} },
|
||||
keys.Messaging: func() (any, any) { return map[string]any{}, globalconfig.Messaging{} }, // has default
|
||||
|
|
@ -316,6 +315,7 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
|
|||
keys.ModbusProxy: func() any { return new([]globalconfig.ModbusProxy) }, // slice
|
||||
keys.Shm: func() any { return new(shm.Config) },
|
||||
keys.Influx: func() any { return new(globalconfig.Influx) },
|
||||
keys.EEBus: func() any { return new(eebus.Config) },
|
||||
} {
|
||||
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())}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/evcc-io/evcc/api/globalconfig"
|
||||
"github.com/evcc-io/evcc/core/keys"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/util/sponsor"
|
||||
|
|
@ -26,11 +27,8 @@ func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
|
|||
return
|
||||
}
|
||||
|
||||
pub(keys.Sponsor, struct {
|
||||
Status sponsor.Status `json:"status"`
|
||||
FromYaml bool `json:"fromYaml"`
|
||||
}{
|
||||
Status: sponsor.GetStatus(),
|
||||
pub(keys.Sponsor, globalconfig.ConfigStatus{
|
||||
Status: sponsor.RedactedStatus(),
|
||||
FromYaml: false,
|
||||
})
|
||||
}
|
||||
|
|
@ -39,7 +37,7 @@ func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
|
|||
settings.SetString(keys.SponsorToken, req.Token)
|
||||
setConfigDirty()
|
||||
|
||||
jsonWrite(w, sponsor.GetStatus())
|
||||
jsonWrite(w, sponsor.RedactedStatus())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,10 +45,7 @@ func deleteSponsorTokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
|
|||
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"`
|
||||
}{
|
||||
pub(keys.Sponsor, globalconfig.ConfigStatus{
|
||||
Status: sponsor.Status{},
|
||||
FromYaml: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/core/keys"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/util/redact"
|
||||
"github.com/gorilla/mux"
|
||||
|
|
@ -80,6 +81,12 @@ func settingsSetYamlHandler(key string, other, struc any) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func allowPub(key string) bool {
|
||||
// don't publish on update - would overwrite globalconfig.Info struct with config
|
||||
// TODO come up with a general solution once all endpoinds use Info
|
||||
return key != keys.EEBus
|
||||
}
|
||||
|
||||
func settingsSetJsonHandler(key string, pub publisher, newStruc func() any) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
struc := newStruc()
|
||||
|
|
@ -104,7 +111,9 @@ func settingsSetJsonHandler(key string, pub publisher, newStruc func() any) http
|
|||
settings.SetJson(key, struc)
|
||||
setConfigDirty()
|
||||
|
||||
pub(key, struc)
|
||||
if allowPub(key) {
|
||||
pub(key, struc)
|
||||
}
|
||||
|
||||
jsonWrite(w, true)
|
||||
}
|
||||
|
|
@ -115,7 +124,9 @@ func settingsDeleteJsonHandler(key string, pub publisher, struc any) http.Handle
|
|||
settings.SetString(key, "")
|
||||
setConfigDirty()
|
||||
|
||||
pub(key, struc)
|
||||
if allowPub(key) {
|
||||
pub(key, struc)
|
||||
}
|
||||
|
||||
jsonWrite(w, true)
|
||||
}
|
||||
|
|
|
|||
27
tests/config-eebus.evcc.yaml
Normal file
27
tests/config-eebus.evcc.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
interval: 0.1s
|
||||
|
||||
eebus:
|
||||
shipid: EVCC_HEMS_01
|
||||
certificate:
|
||||
public: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuzCCAWKgAwIBAgIQatuHkiaPT4obu8RHSmfOwzAKBggqhkjOPQQDAjA+MQsw
|
||||
CQYDVQQGEwJERTENMAsGA1UEChMERVZDQzEJMAcGA1UECxMAMRUwEwYDVQQDDAxF
|
||||
VkNDX0hFTVNfMDEwHhcNMjYwMTMwMTAzNjUzWhcNMzYwMTI4MTAzNjUzWjA+MQsw
|
||||
CQYDVQQGEwJERTENMAsGA1UEChMERVZDQzEJMAcGA1UECxMAMRUwEwYDVQQDDAxF
|
||||
VkNDX0hFTVNfMDEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ8Z40GPe8HzV17
|
||||
ZhbBPkGBwhu8emdvzO5OWJUPSaX1R8rrWSaNwlh6COPG3fOVvUzCYcQbgmMFXK/o
|
||||
nhuAslXTo0IwQDAOBgNVHQ8BAf8EBAMCB4AwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
|
||||
HQ4EFgQUdQegutt1VxBfwDAlPdBpvrp7ztowCgYIKoZIzj0EAwIDRwAwRAIgS2ia
|
||||
A+q0Tiy7NLyEjuKCV8QCVPjxt2OnfbSb9ZlDT3YCIDkwJoLc4+lsfvidDimUB+/I
|
||||
ZvF1asyp3PVL7BDSFHil
|
||||
-----END CERTIFICATE-----
|
||||
private: |
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIIVZ2cLeTiENyCchgpj7eyJ5JKP5qpgj8eHUbLxn8Du1oAoGCCqGSM49
|
||||
AwEHoUQDQgAEPGeNBj3vB81de2YWwT5BgcIbvHpnb8zuTliVD0ml9UfK61kmjcJY
|
||||
egjjxt3zlb1MwmHEG4JjBVyv6J4bgLJV0w==
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
||||
circuits:
|
||||
- name: main
|
||||
137
tests/config-eebus.spec.ts
Normal file
137
tests/config-eebus.spec.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { start, stop, baseUrl, restart } from "./evcc";
|
||||
import { expectModalHidden, expectModalVisible } from "./utils";
|
||||
|
||||
test.use({ baseURL: baseUrl() });
|
||||
|
||||
test.afterEach(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
test.describe("eebus", async () => {
|
||||
test("first start, auto-configured", async ({ page }) => {
|
||||
await start();
|
||||
await page.goto("/#/config");
|
||||
|
||||
await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
|
||||
const modal = page.getByTestId("eebus-modal");
|
||||
await expectModalVisible(modal);
|
||||
|
||||
await expect(modal.getByLabel("SHIP-ID")).not.toBeEmpty();
|
||||
await expect(modal.getByLabel("SKI")).not.toBeEmpty();
|
||||
|
||||
await page.getByRole("button", { name: "Show advanced settings" }).click();
|
||||
await expect(modal.getByLabel("Port")).toHaveValue("4712");
|
||||
await expect(modal.getByLabel("Interfaces")).toBeVisible();
|
||||
await expect(modal.getByLabel("Public certificate")).not.toBeEmpty();
|
||||
await expect(modal.getByLabel("Private key")).toHaveValue("***");
|
||||
});
|
||||
|
||||
test("change values", async ({ page }) => {
|
||||
await start();
|
||||
await page.goto("/#/config");
|
||||
|
||||
const eebusCard = page.getByTestId("eebus");
|
||||
|
||||
await eebusCard.getByRole("button", { name: "edit" }).click();
|
||||
const modal = page.getByTestId("eebus-modal");
|
||||
await expectModalVisible(modal);
|
||||
|
||||
const shipidReadonly = modal.locator("#eebus-shipid-display");
|
||||
const ski = modal.getByLabel("SKI");
|
||||
const shipidEditable = modal.locator("#eebus-shipid");
|
||||
const port = modal.getByLabel("Port");
|
||||
const interfaces = modal.getByLabel("Interfaces");
|
||||
const publicCertificate = modal.getByLabel("Public certificate");
|
||||
const privateKey = modal.getByLabel("Private key");
|
||||
|
||||
await page.getByRole("button", { name: "Show advanced settings" }).click();
|
||||
await shipidEditable.fill("EVCC-1234");
|
||||
await port.fill("4321");
|
||||
await interfaces.fill("eth0");
|
||||
await publicCertificate.fill("public certificate");
|
||||
await privateKey.fill("private key");
|
||||
|
||||
// validate connection
|
||||
await modal.getByRole("button", { name: "Save" }).click();
|
||||
await expectModalHidden(modal);
|
||||
|
||||
// restart button appears
|
||||
const restartButton = page
|
||||
.getByTestId("bottom-banner")
|
||||
.getByRole("button", { name: "Restart" });
|
||||
await expect(restartButton).toBeVisible();
|
||||
|
||||
await restart();
|
||||
await page.reload();
|
||||
|
||||
await eebusCard.getByRole("button", { name: "edit" }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Show advanced settings" }).click();
|
||||
await expect(shipidReadonly).toHaveValue("EVCC-1234");
|
||||
await expect(ski).not.toBeVisible();
|
||||
await expect(shipidEditable).toHaveValue("EVCC-1234");
|
||||
await expect(port).toHaveValue("4321");
|
||||
await expect(interfaces).toHaveValue("eth0");
|
||||
await expect(publicCertificate).toHaveValue("public certificate");
|
||||
await expect(privateKey).toHaveValue("***");
|
||||
});
|
||||
test("delete: ski changes", async ({ page }) => {
|
||||
await start();
|
||||
await page.goto("/#/config");
|
||||
|
||||
const eebusCard = page.getByTestId("eebus");
|
||||
|
||||
await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
|
||||
const modal = page.getByTestId("eebus-modal");
|
||||
await expectModalVisible(modal);
|
||||
|
||||
// remember ski
|
||||
const ski = modal.getByLabel("SKI");
|
||||
const skiValue = await ski.inputValue();
|
||||
|
||||
page.on("dialog", async (dialog) => await dialog.accept());
|
||||
await modal.getByRole("button", { name: "Remove" }).click();
|
||||
|
||||
// restart button appears
|
||||
const restartButton = page
|
||||
.getByTestId("bottom-banner")
|
||||
.getByRole("button", { name: "Restart" });
|
||||
await expect(restartButton).toBeVisible();
|
||||
|
||||
await restart();
|
||||
await page.reload();
|
||||
|
||||
await eebusCard.getByRole("button", { name: "edit" }).click();
|
||||
|
||||
await expect(ski).not.toHaveValue(skiValue);
|
||||
});
|
||||
|
||||
test("configured in yaml", async ({ page }) => {
|
||||
await start("config-eebus.evcc.yaml");
|
||||
await page.goto("/#/config");
|
||||
|
||||
await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
|
||||
const modal = page.getByTestId("eebus-modal");
|
||||
await expectModalVisible(modal);
|
||||
|
||||
// yaml configuration note is visible
|
||||
await expect(modal.getByText("Configured via evcc.yaml", { exact: false })).toBeVisible();
|
||||
|
||||
// SHIP-ID readonly field is visible with configured value
|
||||
await expect(modal.getByLabel("SHIP-ID")).toBeVisible();
|
||||
await expect(modal.getByLabel("SHIP-ID")).toHaveValue("EVCC_HEMS_01");
|
||||
await expect(modal.getByLabel("SHIP-ID")).toHaveAttribute("readonly");
|
||||
|
||||
// SKI readonly field is visible
|
||||
await expect(modal.getByLabel("SKI")).toBeVisible();
|
||||
await expect(modal.getByLabel("SKI")).toHaveAttribute("readonly");
|
||||
|
||||
// no advanced settings button
|
||||
await expect(modal.getByRole("button", { name: "Show advanced settings" })).not.toBeVisible();
|
||||
|
||||
// no action buttons
|
||||
await expect(modal.getByRole("button", { name: "Save" })).not.toBeVisible();
|
||||
await expect(modal.getByRole("button", { name: "Remove" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -25,7 +25,6 @@ test.describe("ocpp", () => {
|
|||
|
||||
// Open OCPP card
|
||||
const ocppCard = page.getByTestId("ocpp");
|
||||
await expect(ocppCard).toContainText(["Configured", "no"].join(""));
|
||||
await ocppCard.getByRole("button", { name: "edit" }).click();
|
||||
|
||||
const ocppModal = page.getByTestId("ocpp-modal");
|
||||
|
|
@ -44,7 +43,6 @@ test.describe("ocpp", () => {
|
|||
|
||||
// Navigate back to evcc config page
|
||||
await page.goto("/#/config");
|
||||
await expect(ocppCard).toContainText(["Connections", "0/0", "Detected", "1"].join(""));
|
||||
await ocppCard.getByRole("button", { name: "edit" }).click();
|
||||
await expectModalVisible(ocppModal);
|
||||
await expect(ocppModal).toContainText("Detected station IDs");
|
||||
|
|
|
|||
8
util/redact.go
Normal file
8
util/redact.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package util
|
||||
|
||||
func Masked(s any) string {
|
||||
if s != "" {
|
||||
return "***"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -119,8 +119,8 @@ type Status struct {
|
|||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// GetStatus returns the sponsorship status
|
||||
func GetStatus() Status {
|
||||
// RedactedStatus returns the sponsorship status
|
||||
func RedactedStatus() Status {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue