Took 4 hours 31 minutes
This commit is contained in:
Tobias Hopp 2022-11-22 00:01:03 +01:00
parent 569f42c686
commit d6c219beb7
22 changed files with 272 additions and 17 deletions

View File

@ -26,14 +26,49 @@ Was haben wir bereits am iTender Projekt gemacht?
<img src="./Screenshot_Model1.1_BackDownLeft.png" width="50%"> <img src="./Screenshot_Model1.1_BackDownLeft.png" width="50%">
#### Neues 3D-Modell #### Neues 3D-Modell
Folgt. <img src="./Screenshot_Model1.2_Front.png" width="50%">
<img src="./Screenshot_Model1.2_Back.png" width="50%">
<hr> <hr>
## Umsetzung ## Das Programm
#### Webseiten-Skizzen
#### Aufbau
- Das Programm ist aufgebaut in eine Client-Seite und eine Server-Seite
- Diese sind zur Sicherheit des Geräts voneinander getrennt
- Server und Client kommunizieren über einen WebSocket, welchen man sich als eine Art Chat-Kanal vorstellen kann
- Client und Server haben bestimmte Status, ein Status ist beispielsweise READY oder FILLING
- Die Oberfläche ist sowohl über das Display, aber auch über ein Tablet steuerbar
- Oberfläche sendet Befehle an den Server → Server verarbeitet und gibt ggfs. eine Antwort
#### Fotos des Webinterfaces (Stand 21.11)
<strong>Main</strong>
<br>
Die Main Pane ist der Hauptteil und direkt die Einstiegsseite des iTenders<br>
Hier können Getränke ausgewählt werden, welche dann "gemacht" werden
<img src="./v1Main.png">
<br><br>
<strong>Menu</strong><br>
Das Menü ist das Navigationsherz, von hier aus können alle anderen Panels erreicht werden<br>
<img src="./v1Menu.png">
<br><br>
<strong>Containers</strong><br>
Hier können die Behälter inhalte aktualisiert werden<br>
Man wählt die "Zutat" aus und danach wie voll der Behälter nun ist<br>
In der Regel kann das auch automatisch eingemessen werden, wenn alle Sensoren eingestellt sind<br>
<br>Trotzdem sollte das hier eingestellt werden
<img src="./v1Containers.png">
<br><br>
<strong>Fill</strong><br>
Einfach ein "Popup" welches anzeigt dass das Getränk gefüllt wird
<img src="./v1Fill.png">
<br><br>
<strong>Setup</strong><br>
Das Setup ist das erste menü was nach dem ersten einrichten erscheint<br>
es dient zur Grundkonfiguration
<img src="./v1Setup.png">
<br><br>

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

33
doc/autostart.config Normal file
View File

@ -0,0 +1,33 @@
#
# These things are run when an Openbox X Session is started.
# You may place a similar script in $HOME/.config/openbox/autostart
# to run user-specific things.
#
# If you want to use GNOME config tools...
#
#if test -x /usr/lib/aarch64-linux-gnu/gnome-settings-daemon >/dev/null; then
# /usr/lib/aarch64-linux-gnu/gnome-settings-daemon &
#elif which gnome-settings-daemon >/dev/null 2>&1; then
# gnome-settings-daemon &
#fi
# If you want to use XFCE config tools...
#
#xfce-mcs-manager &
xset s off
xset s noblank
xset -dpms
setxkbmap -option terminate:ctrl_alt_bksp
# Start Chromium in kiosk mode
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/'Local State'
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/' ~/.config/chromium/Default/Preferences
/usr/bin/chromium-browser --disable-infobars --kiosk --incognito --disable-pinch --overscroll-history-navigation=0 http://192.168.1.186:3000/

19
doc/installPi.sh Normal file
View File

@ -0,0 +1,19 @@
#!/bin/bash
apt update
apt install
apt install --no-install-recommends xserver-xorg x11-xserver-utils xinit openbox -y
apt purge nodejs npm -y
curl -fsSL https://deb.nodesource.com/setup_19.x | sudo bash -
apt install -y nodejs
apt install gcc g++ make -y
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
apt update
apt install yarn git cmake make chromium-browser unclutter -y
apt upgrade -y
echo "allowed_users=anybody" >/etc/X11/Xwrapper.config
cp autostart.config /etc/xdg/openbox/autostart
reboot now

3
doc/start.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
/usr/bin/startx /usr/bin/chromium-browser --kiosk --incognito --disable-pinch --overscroll-history-navigation=0 http://192.168.1.186:3000/

BIN
doc/v1Containers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
doc/v1Fill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
doc/v1Main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
doc/v1Menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
doc/v1Setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
doc/v1Stats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@ -6,7 +6,8 @@
border-radius: 2px; border-radius: 2px;
cursor: none !important; cursor: none !important;
font-weight: 500; font-weight: 500;
margin-right: 2%; margin-right: 1%;
margin-left: 1%;
transition: 0.2s all; transition: 0.2s all;
} }

View File

@ -23,7 +23,7 @@
grid-row-gap: 4%; grid-row-gap: 4%;
text-align: center; text-align: center;
border-radius: 30px 10px 30px; border-radius: 30px 10px 30px;
color: black; color: white;
/*box-shadow: 3px 3px 3px;*/ /*box-shadow: 3px 3px 3px;*/
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3), 0 6px 20px 0 rgba(0, 0, 0, 0.29); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.3), 0 6px 20px 0 rgba(0, 0, 0, 0.29);
transition: 0.4s; transition: 0.4s;
@ -56,3 +56,77 @@
grid-row: span 1; grid-row: span 1;
font-size: 150%; font-size: 150%;
} }
/* Water animation */
body {
margin: 50px;
}
.water {
position: relative;
width: 130px;
height: 150px;
background-color: #23417B;
box-shadow: inset 0 0 80px #18566D;
clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);
background-repeat: no-repeat;
background-position: center;
background-size: auto 83%;
margin: auto auto 3%;
}
.water::before {
content: "";
width: 200%;
height: 200%;
background-color: #ececec;
position: absolute;
top: -90%;
left: -50%;
border-radius: 40%;
animation: animWater 10s linear infinite, animFillIn 15s linear forwards;
}
.water::after {
content: "";
width: 204%;
height: 204%;
background-color: #ececec80;
position: absolute;
top: -90%;
left: -52%;
border-radius: 40%;
animation: animWater 10s linear infinite, animFillIn 15s linear forwards;
animation-delay: 0.4s;
}
.waterCancel {
background-color: red;
transition: background-color 1s;
}
@keyframes animFillIn {
0% {
top: -90%;
}
100% {
top: -190%;
}
}
@keyframes animWater {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#main_fillTxt {
margin-bottom: 3%;
}

View File

@ -1,9 +1,8 @@
#menu { #menu {
padding-left: 5%; padding-left: 5%;
padding-right: 5%;
padding-top: 10%; padding-top: 10%;
display: grid; display: grid;
grid-template-columns: repeat(2, calc(100% / 2)); grid-template-columns: repeat(2, calc(95% / 2));
grid-template-rows: repeat(2, calc(95% / 2)); grid-template-rows: repeat(2, calc(95% / 2));
grid-gap: 2% 2%; grid-gap: 2% 2%;
color: white; color: white;

View File

@ -12,15 +12,23 @@
/*cursor: none !important;*/ /*cursor: none !important;*/
} }
ul { ul {
list-style: circle; list-style: circle;
} }
html * { html * {
/*cursor: none !important*/ /*cursor: none !important*/
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */ -ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */ user-select: none; /* Standard syntax */
cursor: none;
}
html *::-webkit-scrollbar {
display: none;
} }
@ -39,6 +47,7 @@ body {
user-select: none; /* Standard syntax */ user-select: none; /* Standard syntax */
} }
h1 { h1 {
font-size: 1.74em; font-size: 1.74em;
font-weight: 500; font-weight: 500;
@ -63,7 +72,9 @@ h1 {
font-size: 2em; font-size: 2em;
padding-right: 3px; padding-right: 3px;
padding-left: 3px; padding-left: 3px;
background-color: #167fcc; background-color: #167FCC;
box-shadow: inset 11px 45px 50px 3px rgba(181, 15, 15, 0.66);
} }
@ -102,14 +113,15 @@ h1 {
left: 0; left: 0;
right: 0; right: 0;
height: 9%; height: 9%;
background-color: #167fcc; background-color: #167FCC;
box-shadow: inset 11px -45px 50px 3px rgba(223, 12, 42, 0.66);
} }
#overlay #bottom #menuBtn { #overlay #bottom #menuBtn {
height: 100%; height: 100%;
width: 10%; width: 10%;
background-color: #21212d; background-color: #21212D;
padding: 1px; padding: 1px;
font-size: 1.5em; font-size: 1.5em;
border: 0; border: 0;
@ -117,13 +129,17 @@ h1 {
color: white; color: white;
float: left; float: left;
transition: all 0.4s; transition: all 0.4s;
margin-left: 0;
} }
#menuBtn:hover { #menuBtn:hover {
background-color: #313147 !important; background-color: #313147 !important;
} }
#menuBtn:disabled { #menuBtn:disabled {
background-color: #777 !important; background-color: #777777 !important;
color: #C1C1C1 !important; color: #C1C1C1 !important;
} }
@ -143,9 +159,10 @@ h1 {
right: 0; right: 0;
height: 82%; height: 82%;
color: white; color: white;
background-color: #0e1f31; background-color: #0E1F31;
} }
.pane { .pane {
height: 100%; height: 100%;
padding: 1% 2%; padding: 1% 2%;
@ -155,9 +172,15 @@ h1 {
scroll-behavior: smooth; scroll-behavior: smooth;
color: white; color: white;
/*animation: showPane 0.3s forwards;*/ /*animation: showPane 0.3s forwards;*/
-ms-overflow-style: none; /* IE and Edge */
} }
.pane::-webkit-scrollbar {
display: none;
}
@keyframes showPane { @keyframes showPane {
0% { 0% {
display: none; display: none;
@ -171,11 +194,14 @@ h1 {
} }
} }
.hiddenPane { .hiddenPane {
transition: 0.4s; transition: 0.4s;
display: none !important; display: none !important;
/*animation: hidePane 0.4s forwards;*/ /*animation: hidePane 0.4s forwards;*/
} }
@keyframes hidePane { @keyframes hidePane {
0% { 0% {
opacity: 1; opacity: 1;
@ -190,6 +216,7 @@ h1 {
} }
} }
#settings { #settings {
display: none; display: none;
background-color: red; background-color: red;

View File

@ -2,4 +2,5 @@ export enum RequestType {
CONTAINERS = "CONTAINERS", CONTAINERS = "CONTAINERS",
INGREDIENTS = "INGREDIENTS", INGREDIENTS = "INGREDIENTS",
STATS = "STATS", STATS = "STATS",
JOB = "JOB",
} }

View File

@ -7,5 +7,7 @@ export enum WebSocketEvent {
TARE = "TARE", TARE = "TARE",
SETUP = "SETUP", SETUP = "SETUP",
REQUEST = "REQUEST", REQUEST = "REQUEST",
RESPONSE = "RESPONSE" RESPONSE = "RESPONSE",
FILL = "FILL",
CANCEL = "CANCEL"
} }

View File

@ -43,6 +43,11 @@ router.ws('/', async (ws, req, next) => {
log(msg); log(msg);
switch (msg.event) { switch (msg.event) {
case WebSocketEvent.FILL : {
iTender.setStatus(iTenderStatus.FILLING);
break;
}
case WebSocketEvent.TARE: { case WebSocketEvent.TARE: {
if (msg.data["state"] == true) { if (msg.data["state"] == true) {
iTender.toggleTare(true); iTender.toggleTare(true);

View File

@ -2,6 +2,9 @@ import {WebSocketPayload} from "../WebSocketPayload";
import {IDrink} from "../database/IDrink"; import {IDrink} from "../database/IDrink";
import {Pane} from "./Pane"; import {Pane} from "./Pane";
import {Setup} from "./Setup"; import {Setup} from "./Setup";
import {Modal} from "./Modal";
import {WebSocketEvent} from "../WebSocketEvent";
import {WebWebSocketHandler} from "./WebWebSocketHandler";
export class WebHandler { export class WebHandler {
static get currentPane(): Pane { static get currentPane(): Pane {
@ -38,6 +41,13 @@ export class WebHandler {
drinkImg.src = "/images/" + drink.name + ".png"; drinkImg.src = "/images/" + drink.name + ".png";
drinkName.innerText = drink.name; drinkName.innerText = drink.name;
drinkEle.onclick = () => {
let payload = new WebSocketPayload(WebSocketEvent.FILL, false, {drink: drink._id });
WebWebSocketHandler.send(payload);
}
/* /*
let ingredients = "<ul style='list-style-type: disc;'>"; let ingredients = "<ul style='list-style-type: disc;'>";
for (let i of drink.ingredients) { for (let i of drink.ingredients) {

View File

@ -7,6 +7,7 @@ import {WebHandler} from "./WebHandler";
import {Setup} from "./Setup"; import {Setup} from "./Setup";
import {Pane} from "./Pane"; import {Pane} from "./Pane";
import {RequestType} from "../RequestType"; import {RequestType} from "../RequestType";
import {IDrink} from "../database/IDrink";
export class WebWebSocketHandler { export class WebWebSocketHandler {
private static socket: WebSocket; private static socket: WebSocket;
@ -61,8 +62,8 @@ export class WebWebSocketHandler {
switch (status) { switch (status) {
case iTenderStatus.READY: { case iTenderStatus.READY: {
Modal.close("start"); Modal.close("start");
Modal.close("refreshing");
Modal.close("setup"); Modal.close("setup");
Modal.close("fill");
WebHandler.openPane(Pane.MAIN); WebHandler.openPane(Pane.MAIN);
(document.getElementById("menuBtn") as HTMLButtonElement).disabled = false; (document.getElementById("menuBtn") as HTMLButtonElement).disabled = false;
break; break;
@ -88,7 +89,52 @@ export class WebWebSocketHandler {
case iTenderStatus.SETUP: { case iTenderStatus.SETUP: {
Modal.close("start"); Modal.close("start");
Setup.openSetup(); Setup.openSetup();
break;
} }
case iTenderStatus.FILLING: {
let modal = new Modal("fill", "Getränk wird ausgegeben");
let header = document.createElement("h2");
header.innerText = "";
modal.addContent(header);
let txt = document.createElement("p");
txt.innerHTML = `Dein Cocktail wird gerade zubereitet`;
txt.id = "main_fillTxt";
let waterAnimDiv = document.createElement("div");
waterAnimDiv.classList.add("water");
modal.addContent(txt);
modal.addContent(waterAnimDiv);
let cancelBtn = document.createElement("button");
cancelBtn.classList.add("btn", "btn-danger");
cancelBtn.innerText = "Abbrechen";
cancelBtn.disabled = true;
setTimeout(() => {
cancelBtn.disabled = false;
}, 1000);
cancelBtn.onclick = () => {
cancelBtn.disabled = true;
txt.innerHTML = "Der Vorgang wird abgebrochen...";
waterAnimDiv.classList.add("waterCancel");
WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.CANCEL));
};
modal.addContent(cancelBtn);
WebWebSocketHandler.request(RequestType.JOB).then((payload) => {
let drink = payload.data as IDrink;
waterAnimDiv.style.backgroundImage = "/images/" + drink.name + ".png";
header.innerText = drink.name;
});
modal.open();
break;
}
} }
break; break;
} }