mostly Irrigationview

This commit is contained in:
2023-07-28 18:19:40 +02:00
parent f11340c5b1
commit 0604e8d1eb
12 changed files with 626 additions and 204 deletions
+32 -20
View File
@@ -15,7 +15,8 @@ let area = reactive({
let valve = reactive({ let valve = reactive({
status: false, status: false,
remainingTime: 0 remainingTime: 0,
freeze: 0
}) })
api.getArea(props.areaId, (areaApi) => { api.getArea(props.areaId, (areaApi) => {
@@ -29,6 +30,10 @@ function setUpdateValve() {
updateInterval = setInterval(updateValve, 1000) updateInterval = setInterval(updateValve, 1000)
function updateValve() { function updateValve() {
if (valve.freeze > 0) {
valve.freeze--;
return;
}
api.getValve(area.valveId, (valveApi) => { api.getValve(area.valveId, (valveApi) => {
valve.status = valveApi.status valve.status = valveApi.status
valve.remainingTime = valveApi.remainingTime valve.remainingTime = valveApi.remainingTime
@@ -36,6 +41,24 @@ function setUpdateValve() {
} }
} }
function open() {
api.commandManualControlUniqueIrrigationEvent(props.areaId, minutesSlider.value, data => {
// console.info(data);
valve.status = true;
valve.remainingTime = minutesSlider.value * 60;
valve.freeze = 3;
});
}
function close() {
api.commandManualControlStop(props.areaId, (data) => {
// console.info(data);
valve.status = false;
valve.remainingTime = 0;
valve.freeze = 3;
});
}
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(updateInterval) clearInterval(updateInterval)
}) })
@@ -44,18 +67,16 @@ onBeforeUnmount(() => {
<template> <template>
<div class="border"> <div class="border">
<div v-if="valve.status" class="valveOn valve"> <div v-if="valve.status" class="valveOn valve">
<!-- <div id="headline"> -->
<h2>{{ area.name }}</h2> <h2>{{ area.name }}</h2>
<!-- </div> --> <p>Status: Offen </p>
<p>Status: Offen</p> <div class="inputDiv">
<p>Verbleibenede Zeit: {{ valve.remainingTime / 60 }} Min</p> <p>Verbleibenede Zeit: {{ parseInt(valve.remainingTime / 60) }} Min</p>
<button>Schließen</button> </div>
<button @click="close">Schließen</button>
</div> </div>
<div v-if="!valve.status" class="valveOff valve"> <div v-if="!valve.status" class="valveOff valve">
<!-- <div id="headline"> -->
<h2>{{ area.name }}</h2> <h2>{{ area.name }}</h2>
<!-- </div> -->
<p>Status: Geschlossen</p> <p>Status: Geschlossen</p>
<div class="inputDiv"> <div class="inputDiv">
<input type="range" min="0" max="120" v-model="minutesSlider" /> <input type="range" min="0" max="120" v-model="minutesSlider" />
@@ -64,7 +85,7 @@ onBeforeUnmount(() => {
Minuten Minuten
</label> </label>
</div> </div>
<button>Öffnen</button> <button @click="open">Öffnen</button>
</div> </div>
</div> </div>
</template> </template>
@@ -72,7 +93,8 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.inputDiv { .inputDiv {
width: 100%; width: 100%;
margin-bottom: 16px; height: 70px;
margin-bottom: 12px;
} }
p { p {
@@ -110,16 +132,6 @@ input[type='range'] {
height: 30px; height: 30px;
} }
/* .headline {
display: flex;
align-items: center;
justify-items: center;
width: 100%;
margin: 0;
padding: 0;
height: 80px;
} */
h2 { h2 {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
@@ -0,0 +1,160 @@
<template>
<div class="mainDiv">
<div class="areaDiv">
<label for="name">
Name:
<input type="text" name="name" class="width" v-model="area.name" @keypress="sthChanged">
</label>
<label for="valve">Ventil:
<select name="valve" class="width" v-model="area.valveId" @change="sthChanged">
<option v-for="valve in valves" :key="valve.id" :value="valve.id">
Ventil {{ valve.number }}
</option>
</select>
</label>
<label for="description">
Beschreibung:
<textarea name="description" rows="8" class="width" v-model="area.description" @keypress="sthChanged"></textarea>
</label>
</div>
<div class="areaConfDiv">
<h3>Konfigurationen</h3>
<IrrigationAreaConfLine v-for="conf in area.areaConfs" :key="conf.id" :areaConf="conf" @confDeleted="confDeleted">
</IrrigationAreaConfLine>
</div>
<div class="buttons">
<button v-if="!changed">
<img src="../../assets/img/buttons/delete_icon.svg" alt="Löschen" @click="deleteArea">
</button>
<button v-else>
<img src="../../assets/img/buttons/sync_icon.svg" alt="Aktualisieren" @click="updateArea">
</button>
</div>
</div>
</template>
<script setup>
import IrrigationApi from '../../helper/irrigationApi';
import IrrigationAreaConfLine from './irrigationAreaConfLine.vue';
import { reactive, ref } from 'vue';
const area = reactive({
name: "",
description: "",
valveId: "",
areaConfs: [],
});
const valves = reactive([]);
const props = defineProps(['areaId']);
const emit = defineEmits(["areaDeleted"]);
const api = new IrrigationApi();
const changed = ref(false);
api.getArea(props.areaId, (data) => {
area.name = data.name;
area.description = data.description;
area.valveId = data.valveId;
area.areaConfs = data.areaConfs;
});
api.getValves((data) => {
data.forEach(valve => {
valves.push(valve);
});
});
function sthChanged() {
console.log(area.valveId)
changed.value = true;
}
function deleteArea() {
if(confirm("Wikrlich löschen? Alle konfigurationen für diesen Bereich gehen dabei verloren!")) {
api.deleteArea(props.areaId, () => {
// console.log("Bereich gelöscht");
emit("areaDeleted", props.areaId);
});
}
}
function updateArea() {
api.patchArea(props.areaId, {
name: area.name,
description: area.description,
valve: area.valveId,
}, () => {
changed.value = false;
});
}
function confDeleted(id) {
const index = area.areaConfs.findIndex(conf => conf.id === id);
if (index >= 0) {
area.areaConfs.splice(index, 1);
}
}
</script>
<style scoped>
.areaConfDiv{
width: 300px;
overflow-y: scroll;
}
/* width */
.areaConfDiv::-webkit-scrollbar {
border: 0;
}
h3 {
text-align: center;
width: 100%;
margin: 4px 0;
}
button img{
width: 50px;
height: 50px;
}
button {
position: absolute;
bottom: 20px;
left: 20px;
padding: 8px;
border-radius: 10px;
}
button:hover {
background-color: rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.areaDiv {
width: 300px;
margin: 8px;
}
.mainDiv {
display: flex;
position: relative;
border-radius: 10px;
border-style: solid;
border-width: 2px;
margin: 8px;
height: 215px;
width: fit-content;
}
.width {
width: 60%;
}
label {
display: flex;
justify-content: space-between;
margin: 0px 0px 8px 0px;
}
</style>
@@ -0,0 +1,87 @@
<template>
<div class="rowCenter spaceBetween">
<div class="rowCenter">
<input type="checkbox" name="active" v-model="props.areaConf.activated" @change="changeState">
{{ props.areaConf.name }}
</div>
<div class="rowCenter">
<img src="../../assets/img/buttons/more_icon.svg" alt="Details" @click="detailsConf">
<img src="../../assets/img/buttons/delete_icon.svg" alt="Löschen" @click="deleteConf">
</div>
</div>
</template>
<script setup>
import IrrigationApi from '../../helper/irrigationApi';
import { reactive } from 'vue';
const props = defineProps(['areaConf']);
const emit = defineEmits(['confDeleted']);
const api = new IrrigationApi();
function changeState() {
api.patchAreaConf(props.areaConf.id, {
activated: props.areaConf.activated
}, () => {
});
}
function deleteConf() {
if (confirm("Wikrlich löschen? Alle Bewässerungszeiten in dieser Konfiguration gehen verloren!")) {
api.deleteAreaConf(props.areaConf.id, () => {
emit('confDeleted', props.areaConf.id)
});
}
}
function detailsConf() {
}
</script>
<style scoped>
.rowCenter img {
align-self: center;
width: 20px;
height: 20px;
margin-left: 8px;
}
.rowCenter img:hover {
background-color: rgba(0, 0, 0, 0.1);
cursor: pointer;
}
button {
display: flex;
width: 20px;
height: 20px;
border-style: solid;
border-radius: 5px;
border-width: 1px;
}
.rowCenter {
display: flex;
align-items: center;
}
.spaceBetween {
justify-content: space-between;
height: 25px;
border-style: solid;
border-width: 2px;
border-radius: 8px;
margin-bottom: 5px;
margin-right: 5px;
padding: 0 8px;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 10px;
}
</style>
@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>
+20 -6
View File
@@ -6,15 +6,29 @@ import { RouterLink } from 'vue-router'
<nav> <nav>
<ul> <ul>
<li> <li>
<RouterLink to="/"> <img src="../../assets/img/menuIcon/home.svg" /> Start </RouterLink> <RouterLink to="/">
</li> <img src="../../assets/img/menuIcon/home.svg" /> Start
<li> </RouterLink>
<RouterLink to="/about"> <img src="../../assets/img/menuIcon/logs.svg" /> About</RouterLink>
</li> </li>
<li> <li>
<RouterLink to="/quickControl"> <RouterLink to="/quickControl">
<img src="../../assets/img/menuIcon/logs.svg" /> Control</RouterLink <img src="../../assets/img/menuIcon/bolt.svg" /> Schnellzugriff
> </RouterLink>
</li>
<li>
<RouterLink to="/irrigation">
<img src="../../assets/img/menuIcon/water_drop.svg" /> Bewässerung
</RouterLink>
</li>
<li>
<RouterLink to="/lighting">
<img src="../../assets/img/menuIcon/lightbulb.svg" /> Beleuchtung
</RouterLink>
</li>
<li>
<RouterLink to="/about">
<img src="../../assets/img/menuIcon/logs.svg" /> About
</RouterLink>
</li> </li>
</ul> </ul>
</nav> </nav>
+127 -73
View File
@@ -6,113 +6,167 @@ const api = new IrrigationApi()
const openValves = reactive([]) const openValves = reactive([])
const areas = [] const areas = []
let freeze = 0;
let updateInterval let updateInterval
let pump = { let pump = {
status: 'Off', status: 'Off',
time: 0 time: 0
} }
api.getAreas((areasApi) => { api.getAreas((areasApi) => {
areasApi.forEach((areaApi) => { areasApi.forEach((areaApi) => {
areas.push(areaApi) areas.push(areaApi)
}) })
updateInterval = setInterval(updateApiData, 1000) updateInterval = setInterval(updateApiData, 1000)
}) })
function updateApiData() { function updateApiData() {
api.getValves((valves) => { if (freeze > 0) {
valves.forEach((valve) => { freeze--;
if (valve.status) { return;
const foundValve = openValves.find((oldValve) => oldValve.id === valve.id) }
if (foundValve === undefined) { api.getValves((valves) => {
openValves.push(valve) valves.forEach((valve) => {
} else { if (valve.status) {
foundValve.remainingTime = valve.remainingTime const foundValve = openValves.find((oldValve) => oldValve.id === valve.id)
} if (foundValve === undefined) {
} else { openValves.push(valve)
const foundValveIndex = openValves.findIndex((oldValve) => oldValve.id === valve.id) } else {
if (foundValveIndex >= 0) { foundValve.remainingTime = valve.remainingTime
openValves.splice(foundValveIndex, 1) }
} } else {
} const foundValveIndex = openValves.findIndex((oldValve) => oldValve.id === valve.id)
if (foundValveIndex >= 0) {
openValves.splice(foundValveIndex, 1)
}
}
})
}) })
})
} }
function getAreaName(valveId) { function getAreaName(valveId) {
const area = areas.find((area) => area.valveId === valveId) const area = areas.find((area) => area.valveId === valveId)
return area.name return area.name
}
function getAreaId(valveId) {
const area = areas.find((area) => area.valveId === valveId)
return area.id
} }
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(updateInterval) clearInterval(updateInterval)
}) })
function close(event) {
api.commandManualControlStop(getAreaId(event.srcElement.id), (data) => {
// console.info(data);
});
const foundValveIndex = openValves.findIndex((valve) => valve.id === event.srcElement.id)
if (foundValveIndex >= 0) {
openValves.splice(foundValveIndex, 1)
freeze = 3;
}
}
</script> </script>
<template> <template>
<aside> <aside>
<h2>Infos</h2> <h2>Infos</h2>
<hr /> <hr />
<h3>Pumpe</h3> <h3>Pumpe</h3>
<p> <p>
Status: <br /> Status: <br />
<b>{{ pump.status }}</b> <br /> <b>{{ pump.status }}</b> <br />
seit <br /> seit <br />
<b>{{ pump.time }}</b> <br /> <b>{{ parseInt(pump.time / 60) }}</b> <br />
Minuten Minuten
</p> </p>
<hr /> <hr />
<h3> <h3>
Offene <br /> Offene <br />
Bereiche Bereiche
</h3> </h3>
<p v-if="openValves.length === 0">Alles zu!</p> <p v-if="openValves.length === 0">Alles zu!</p>
<ul v-else> <ul v-else>
<li v-for="valve in openValves" :key="valve.id"> <li v-for="valve in openValves" :key="valve.id">
Bereich: <br /> Bereich: <br />
{{ getAreaName(valve.id) }} <br /> <b>{{ getAreaName(valve.id) }} </b> <br />
Verbleibende Zeit: <br /> Verbleibende Zeit: <br />
{{ parseInt(valve.remainingTime) }} min <br /> <b>{{ parseInt(valve.remainingTime / 60) }} min </b> <br />
<span>Schließen!</span> <a class="closeLink" @click="close" :id=valve.id>Schließen!</a>
</li> </li>
</ul> </ul>
<hr /> </aside>
</aside>
</template> </template>
<style scoped> <style scoped>
ul {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
}
li {
width: 80%;
padding-top: 8px;
padding-bottom: 8px;
margin-bottom: 16px;
text-align: center;
border-style: solid;
border-width: 1px;
border-radius: 10px;
}
a {
color: blue;
text-decoration: underline;
}
a:hover {
color: darkblue;
cursor: pointer;
}
aside { aside {
min-width: 200px; min-width: 200px;
border-radius: 10px; border-radius: 10px;
border-style: solid; border-style: solid;
border-width: 2px; border-width: 2px;
} }
h2 { h2 {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
h3 { h3 {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
p { p {
text-align: center; text-align: center;
font-size: larger; font-size: larger;
margin: 0; margin: 0;
} }
b { b {
text-decoration: underline; display: inline-block;
width: 150px;
text-decoration: underline;
/* padding-left: 15px;
padding-right: 15px; */
} }
</style> </style>
+128 -101
View File
@@ -1,138 +1,165 @@
const address = 'http://garten.home.kleiax.de/api/' const address = "http://garten.home.kleiax.de/api/";
class IrrigationApi { class IrrigationApi {
constructor() {} constructor() {
getValve(id, callback) { }
httpGet('valve/' + id, callback)
}
getValves(callback) { getValve(id, callback) {
httpGet('valve', callback) httpGet('valve/' + id, callback);
} }
getArea(id, callback) { getValves(callback) {
httpGet('area/' + id, callback) httpGet('valve', callback);
} }
getAreas(callback) {
httpGet('area', callback)
}
createArea(area, callback) { getArea(id, callback) {
httpPost('area', area, callback) httpGet('area/' + id, callback);
} }
deleteArea(id, callback) { getAreas(callback) {
httpDelete('area/' + id, callback) httpGet('area', callback);
} }
patchArea(id, area, callback) { createArea(area, callback) {
httpPatch('area/' + id, area, callback) httpPost('area', area, callback);
} }
getAreaConf(id, callback) { deleteArea(id, callback) {
httpGet('areaConf/specific/' + id, callback) httpDelete('area/' + id, callback);
} }
getAreaConfs(area, callback) { patchArea(id, area, callback) {
httpGet('areaConf/' + area, callback) httpPatch('area/' + id, area, callback);
} }
createAreaConf(area, areaConf, callback) {
httpPost('areaConf/' + area, areaConf, callback)
}
deleteAreaConf(id, callback) { getAreaConf(id, callback) {
httpDelete('areaConf/' + id, callback) httpGet('areaConf/specific/' + id, callback);
} }
patchAreaConf(id, areaConf, callback) { getAreaConfs(area, callback) {
httpPatch('areaConf/' + id, areaConf, callback) httpGet('areaConf/' + area, callback);
} }
getIrrigationEvent(id, callback) { createAreaConf(area, areaConf, callback) {
httpGet('irrigationEvent/specific/' + id, callback) httpPost('areaConf/' + area, areaConf, callback);
} }
getIrrigationEvents(areaConf, callback) { deleteAreaConf(id, callback) {
httpGet('irrigationEvent/' + areaConf, callback) httpDelete('areaConf/' + id, callback);
} }
createIrrigationEvent(areaConf, irrigationEvent, callback) { patchAreaConf(id, areaConf, callback) {
httpPost('irrigationEvent/' + areaConf, irrigationEvent, callback) httpPatch('areaConf/' + id, areaConf, callback);
} }
deleteIrrigationEvent(id, callback) {
httpDelete('irrigationEvent/' + id, callback)
}
patchIrrigationEvent(id, irrigationEvent, callback) { getIrrigationEvent(id, callback) {
httpPatch('irrigationEvent/' + id, irrigationEvent, callback) httpGet('irrigationEvent/specific/' + id, callback);
} }
getIrrigationEvents(areaConf, callback) {
httpGet('irrigationEvent/' + areaConf, callback);
}
createIrrigationEvent(areaConf, irrigationEvent, callback) {
httpPost('irrigationEvent/' + areaConf, irrigationEvent, callback);
}
deleteIrrigationEvent(id, callback) {
httpDelete('irrigationEvent/' + id, callback);
}
patchIrrigationEvent(id, irrigationEvent, callback) {
httpPatch('irrigationEvent/' + id, irrigationEvent, callback);
}
commandManualControlPause(id, duration, callback) {
httpPost('manualControl/pause/' + id, { duration: duration}, callback);
}
commandManualControlPauseAll(callback) {
httpPost('manualControl/pauseAll/', {}, callback);
}
commandManualControlUniqueIrrigationEvent(id, duration, callback) {
httpPost('manualControl/uniqueIrrigationEvent/' + id, {duration: duration}, callback);
}
commandManualControlStop(id, callback) {
httpPost('manualControl/stop/' + id, {}, callback);
}
commandManualControlStopAll(duration, callback) {
httpPost('manualControl/stopAll/', {duration: duration}, callback);
}
} }
function httpGet(apiCall, callback) { function httpGet(apiCall, callback) {
var request = new XMLHttpRequest() var request = new XMLHttpRequest();
request.open('GET', address + apiCall) request.open('GET', address + apiCall);
request.onload = function () { request.onload = function () {
const data = JSON.parse(request.response) const data = JSON.parse(request.response);
if (request.status >= 200 && request.status < 400) { if (request.status >= 200 && request.status < 400) {
callback(data) callback(data);
} else { } else {
console.log('Error irrigationApi.js httpGet()', data) console.log('Error irrigationApi.js httpGet()', data);
} }
} };
request.send() request.send();
} }
function httpPatch(apiCall, data, callback) { function httpPatch(apiCall, data, callback) {
var request = new XMLHttpRequest() var request = new XMLHttpRequest();
request.open('PATCH', address + apiCall) request.open('PATCH', address + apiCall);
request.setRequestHeader('Content-Type', 'application/json') request.setRequestHeader("Content-Type", "application/json");
request.onload = function () { request.onload = function () {
const data = JSON.parse(request.response) const data = JSON.parse(request.response);
if (request.status >= 200 && request.status < 400) { if (request.status >= 200 && request.status < 400) {
callback(data) callback(data);
} else { } else {
console.log('Error irrigationApi.js httpPatch()', data) console.log('Error irrigationApi.js httpPatch()', data);
} }
} };
request.send(JSON.stringify(data)) request.send(JSON.stringify(data));
} }
function httpDelete(apiCall, callback) { function httpDelete(apiCall, callback) {
var request = new XMLHttpRequest() var request = new XMLHttpRequest();
request.open('DELETE', address + apiCall) request.open('DELETE', address + apiCall);
request.onload = function () { request.onload = function () {
const data = JSON.parse(request.response) const data = JSON.parse(request.response);
if (request.status >= 200 && request.status < 400) { if (request.status >= 200 && request.status < 400) {
callback(data) callback(data);
} else { } else {
console.log('Error irrigationApi.js httpDelete()', data) console.log('Error irrigationApi.js httpDelete()', data);
} }
} };
request.send() request.send();
} }
function httpPost(apiCall, data, callback) { function httpPost(apiCall, data, callback) {
// console.log(data); // console.log(data);
var request = new XMLHttpRequest() var request = new XMLHttpRequest();
request.open('POST', address + apiCall) request.open('POST', address + apiCall);
request.setRequestHeader('Content-Type', 'application/json') request.setRequestHeader("Content-Type", "application/json");
request.onload = function () { request.onload = function () {
const data = JSON.parse(request.response) const data = JSON.parse(request.response);
if (request.status >= 200 && request.status < 400) { if (request.status >= 200 && request.status < 400) {
callback(data) callback(data);
} else { } else {
console.log('Error irrigationApi.js httpPost()', data) console.log('Error irrigationApi.js httpPost()', data);
} }
} };
request.send(JSON.stringify(data)) request.send(JSON.stringify(data));
} }
export default IrrigationApi export default IrrigationApi;
+16 -4
View File
@@ -1,6 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue' import HomeView from '../views/HomeView.vue';
import QuickAreaControlView from '../views/QuickAreaControlView.vue' import QuickAreaControlView from '../views/QuickAreaControlView.vue';
import LightingView from '../views/LightingView.vue';
import IrrigationView from '../views/IrrigationView.vue';
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@@ -32,7 +34,17 @@ const router = createRouter({
path: '/quickControl', path: '/quickControl',
name: 'quickControl', name: 'quickControl',
component: QuickAreaControlView component: QuickAreaControlView
} },
{
path: '/lighting',
name: 'lighting',
component: LightingView
},
{
path: '/irrigation',
name: 'irrigation',
component: IrrigationView
},
] ]
}) })
View File
+32
View File
@@ -0,0 +1,32 @@
<script setup>
import IrrigationAreaConfCard from '../components/irrigation/irrigationAreaConfCard.vue';
import IrrigationApi from '../helper/irrigationApi';
import { reactive } from 'vue'
const api = new IrrigationApi();
const areas = reactive([]);
api.getAreas((data) => {
data.forEach(area => {
areas.push(area);
});
});
function removeArea(id) {
const index = areas.findIndex(area => area.id === id);
if (index >= 0) {
areas.splice(index, 1);
}
}
</script>
<template>
<h2>Bewässerung</h2>
<IrrigationAreaConfCard v-for="area in areas" :key="area.id" :areaId="area.id" @areaDeleted="removeArea">
</IrrigationAreaConfCard>
</template>
<style scoped>
</style>
+11
View File
@@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<h2>Beleuchtung</h2>
</template>
<style scoped>
</style>
View File