Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loadpoint config api (BC) #12958

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
dbaa26d
wip
andig Mar 13, 2024
6faf0bd
Add loadpoint config api
andig Mar 15, 2024
98a3070
Migrate settings
andig Mar 15, 2024
f59e60a
wip
andig Mar 15, 2024
13be207
Deprecate mode
andig Mar 15, 2024
7de60e1
wip
andig Mar 15, 2024
83b3b95
wip
andig Mar 15, 2024
82e7503
wip
andig Mar 15, 2024
b07a69f
Merge branch 'master' into feat/loadpoint-config
andig Mar 15, 2024
6802f5b
Merge branch 'master' into feat/loadpoint-config
naltatis Mar 24, 2024
dae8c6a
loadpoint config ui, wip
naltatis Mar 25, 2024
d403997
form spacing
naltatis Mar 25, 2024
acfa5b4
updated general section; loadpoint status (mock)
naltatis Mar 25, 2024
ffb4a1e
general settings
naltatis Mar 26, 2024
9ca47d1
Merge branch 'master' into feat/loadpoint-config
naltatis Mar 26, 2024
be4899e
fix change title test
naltatis Mar 26, 2024
36f9c87
Add device references
andig Mar 26, 2024
5b9ebc9
added charger/meter/vehicle, color contrast, status values, loadpoint…
naltatis Mar 27, 2024
47627a6
Return priority=0
andig Mar 28, 2024
17176e1
Smart cost: zero is disabled
andig Mar 28, 2024
d191c4d
Persist thresholds
andig Mar 28, 2024
772af51
Align spelling
andig Mar 29, 2024
9e88b9b
Add soc config
andig Mar 29, 2024
bf737ea
Add poll mode migration
andig Mar 29, 2024
829447f
Merge branch 'master' into feat/loadpoint-config
naltatis Mar 30, 2024
c278f90
toml fix
naltatis Mar 30, 2024
a18eea0
Marshal poll mode as string
andig Apr 3, 2024
44564dd
current limits
naltatis Apr 5, 2024
b76304f
Merge branch 'master' into feat/loadpoint-config
naltatis Jun 7, 2024
29e86d3
Merge branch 'master' into feat/loadpoint-config
naltatis Jun 7, 2024
3075eb9
resolve
naltatis Jun 7, 2024
2d1f9ea
npm lint
naltatis Jun 7, 2024
e165479
wip
andig Jun 8, 2024
b6eddd7
Merge branch 'master' into feat/loadpoint-config
naltatis Jun 8, 2024
4c146e7
toml
naltatis Jun 8, 2024
1e7ab54
fix ui build
naltatis Jun 8, 2024
2c77bcf
Fix config structs
andig Jun 8, 2024
403ea33
Fix thresholds
andig Jun 8, 2024
49b335c
wip
andig Jun 8, 2024
1810477
wip
andig Jun 8, 2024
d873f03
gp
andig Jun 8, 2024
cca97f2
Add mqtt
andig Jun 9, 2024
0bda0f6
restored /config/loadpoints route
naltatis Jun 9, 2024
749633f
align loadpoint modal layout with other new setting modals
naltatis Jun 9, 2024
82cb47b
Default vehicle; estimate checkbox
naltatis Jun 14, 2024
094c68a
create, edit, remove charge meters from loadpoint
naltatis Jun 14, 2024
315dc03
ui lint
naltatis Jun 14, 2024
0207a63
modal refactor
naltatis Jun 14, 2024
dedec3c
Merge branch 'master' into feat/loadpoint-config
naltatis Jun 14, 2024
ab0af27
modal refactor
naltatis Jun 14, 2024
94020d1
simplify lp updating
naltatis Jun 14, 2024
74061a6
lint
naltatis Jun 14, 2024
7d4691f
duration conversion; add and delete loadpoint
naltatis Jun 19, 2024
db80260
Merge branch 'master' into feat/loadpoint-config
naltatis Jun 19, 2024
9c2c1d0
merge
naltatis Jun 19, 2024
3d1c239
added charger modal
naltatis Jun 19, 2024
a3c2c4a
Split static/dynamic config, add circuit reference
andig Jun 22, 2024
60ec28c
Split static config
andig Jun 22, 2024
9ff0a67
wip
andig Jun 22, 2024
bbae6d5
Loadpoints: add CRUD
andig Jun 22, 2024
b7645d8
wip
andig Jun 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/globalconfig/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type All struct {
Vehicles []config.Named
Tariffs Tariffs
Site map[string]interface{}
Loadpoints []map[string]interface{}
Loadpoints []config.Named
Circuits []config.Named
}

Expand Down
2 changes: 2 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "bootstrap/dist/css/bootstrap.min.css";
import "../css/app.css";
import { createApp, h } from "vue";
import { VueHeadMixin, createHead } from "@unhead/vue";
import PrimeVue from "primevue/config";
import App from "./views/App.vue";
import setupRouter from "./router";
import setupI18n from "./i18n";
Expand Down Expand Up @@ -70,6 +71,7 @@ app.use(i18n);
app.use(setupRouter(i18n));
app.use(featureflags);
app.use(head);
app.use(PrimeVue, { unstyled: true });
app.mixin(VueHeadMixin);
window.app = app.mount("#app");

Expand Down
362 changes: 362 additions & 0 deletions assets/js/components/Config/ChargerModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
<template>
<GenericModal
id="chargerModal"
:title="modalTitle"
data-testid="charger-modal"
@open="open"
@closed="closed"
>
<form ref="form" class="container mx-0 px-0">
<FormRow id="chargerTemplate" :label="$t('config.charger.template')">
<select
id="chargerTemplate"
v-model="templateName"
@change="templateChanged"
:disabled="!isNew"
class="form-select w-100"
>
<option
v-for="option in genericOptions"
:key="option.name"
:value="option.template"
>
{{ option.name }}
</option>
<option v-if="genericOptions.length" disabled>──────────</option>
<option
v-for="option in templateOptions"
:key="option.name"
:value="option.template"
>
{{ option.name }}
</option>
</select>
</FormRow>
<p v-if="loadingTemplate">Loading ...</p>
<Modbus
v-if="modbus"
v-model:modbus="values.modbus"
v-model:id="values.id"
v-model:host="values.host"
v-model:port="values.port"
v-model:device="values.device"
v-model:baudrate="values.baudrate"
v-model:comset="values.comset"
:defaultId="modbus.ID"
:defaultComset="modbus.Comset"
:defaultBaudrate="modbus.Baudrate"
:defaultPort="modbus.Port"
:capabilities="modbusCapabilities"
/>
<FormRow
v-for="param in templateParams"
:id="`chargerParam${param.Name}`"
:key="param.Name"
:optional="!param.Required"
:label="param.Description || `[${param.Name}]`"
:help="param.Description === param.Help ? undefined : param.Help"
:example="param.Example"
>
<PropertyField
:id="`chargerParam${param.Name}`"
v-model="values[param.Name]"
:masked="param.Mask"
:property="param.Name"
:type="param.Type"
class="me-2"
:required="param.Required"
:validValues="param.ValidValues"
/>
</FormRow>

<TestResult
v-if="templateName"
:success="testSuccess"
:failed="testFailed"
:unknown="testUnknown"
:running="testRunning"
:result="testResult"
:error="testError"
@test="testManually"
/>

<div v-if="templateName" class="my-4 d-flex justify-content-between">
<button
v-if="isDeletable"
type="button"
class="btn btn-link text-danger"
@click.prevent="remove"
>
{{ $t("config.general.delete") }}
</button>
<button
v-else
type="button"
class="btn btn-link text-muted"
data-bs-dismiss="modal"
>
{{ $t("config.general.cancel") }}
</button>
<button
type="submit"
class="btn btn-primary"
:disabled="testRunning || saving"
@click.prevent="isNew ? create() : update()"
>
<span
v-if="saving"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
{{
testUnknown ? $t("config.general.validateSave") : $t("config.general.save")
}}
</button>
</div>
</form>
</GenericModal>
</template>

<script>
import FormRow from "./FormRow.vue";
import PropertyField from "./PropertyField.vue";
import TestResult from "./TestResult.vue";
import api from "../../api";
import test from "./mixins/test";
import Modbus from "./Modbus.vue";
import GenericModal from "../GenericModal.vue";

const initialValues = { type: "template" };

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

export default {
name: "ChargerModal",
components: { FormRow, PropertyField, Modbus, TestResult, GenericModal },
mixins: [test],
props: {
id: Number,
name: String,
},
emits: ["added", "updated", "removed", "closed"],
data() {
return {
isModalVisible: false,
templates: [],
products: [],
templateName: null,
template: null,
saving: false,
selectedType: null,
loadingTemplate: false,
values: { ...initialValues },
};
},
computed: {
modalTitle() {
if (this.isNew) {
return this.$t(`config.charger.titleAdd`);
}
return this.$t(`config.charger.titleEdit`);
},
templateOptions() {
return this.products.filter((p) => p.group !== "generic");
},
genericOptions() {
return this.products.filter((p) => p.group === "generic");
},
templateParams() {
const params = this.template?.Params || [];
return (
params
// deprecated fields
.filter((p) => !p.Deprecated)
// remove modbus, handles separately
.filter((p) => p.Name !== "modbus")
);
},
modbus() {
const params = this.template?.Params || [];
return params.find((p) => p.Name === "modbus");
},
modbusCapabilities() {
return this.modbus?.Choice || [];
},
modbusDefaults() {
const { ID, Comset, Baudrate, Port } = this.modbus || {};
return {
id: ID,
comset: Comset,
baudrate: Baudrate,
port: Port,
};
},
apiData() {
return {
template: this.templateName,
...this.modbusDefaults,
...this.values,
};
},
isNew() {
return this.id === undefined;
},
isDeletable() {
return !this.isNew;
},
},
watch: {
isModalVisible(visible) {
if (visible) {
this.templateName = null;
this.reset();
this.loadProducts();
if (this.id !== undefined) {
this.loadConfiguration();
}
}
},
templateName() {
this.loadTemplate();
},
values: {
handler() {
this.resetTest();
},
deep: true,
},
},
methods: {
reset() {
this.values = { ...initialValues };
this.resetTest();
},
async loadConfiguration() {
try {
const charger = (await api.get(`config/devices/charger/${this.id}`)).data.result;
this.values = charger.config;
this.applyDefaultsFromTemplate();
this.templateName = this.values.template;
} catch (e) {
console.error(e);
}
},
async loadProducts() {
if (!this.isModalVisible) {
return;
}
try {
this.products = (await api.get("config/products/charger")).data.result;
} catch (e) {
console.error(e);
}
},
async loadTemplate() {
this.template = null;
this.loadingTemplate = true;
try {
const opts = {
params: {
lang: this.$i18n?.locale,
name: this.templateName,
},
};
const result = await api.get("config/templates/charger", opts);
this.template = result.data.result;
this.applyDefaultsFromTemplate();
} catch (e) {
console.error(e);
}
this.loadingTemplate = false;
},
applyDefaultsFromTemplate() {
const params = this.template?.Params || [];
params
.filter((p) => p.Default && !this.values[p.Name])
.forEach((p) => {
this.values[p.Name] = p.Default;
});
},
async create() {
if (this.testUnknown) {
const success = await this.test(this.testCharger);
if (!success) return;
await sleep(100);
}
this.saving = true;
try {
const response = await api.post("config/devices/charger", this.apiData);
const { name } = response.data.result;
this.$emit("added", name);
this.$emit("updated");
this.closed();
} catch (e) {
console.error(e);
alert("create failed");
}
this.saving = false;
},
async testManually() {
await this.test(this.testCharger);
},
async testCharger() {
let url = "config/test/charger";
if (!this.isNew) {
url += `/merge/${this.id}`;
}
return await api.post(url, this.apiData);
},
async update() {
if (this.testUnknown) {
const success = await this.test(this.testCharger);
if (!success) return;
await sleep(250);
}
this.saving = true;
try {
await api.put(`config/devices/charger/${this.id}`, this.apiData);
this.$emit("updated");
this.closed();
} catch (e) {
console.error(e);
alert("update failed");
}
this.saving = false;
},
async remove() {
try {
await api.delete(`config/devices/charger/${this.id}`);
this.$emit("removed", this.name);
this.$emit("updated");
this.closed();
} catch (e) {
console.error(e);
alert("delete failed");
}
},
open() {
this.isModalVisible = true;
},
closed() {
this.$emit("closed");
this.isModalVisible = false;
},
templateChanged() {
this.reset();
},
},
};
</script>
<style scoped>
.container {
margin-left: calc(var(--bs-gutter-x) * -0.5);
margin-right: calc(var(--bs-gutter-x) * -0.5);
padding-right: 0;
}
.addButton {
min-height: auto;
}
</style>