Took 15 hours 7 minutes
This commit is contained in:
Tobias Hopp 2022-11-17 00:46:19 +01:00
parent a356b39bad
commit c81713cd23
30 changed files with 4899 additions and 207 deletions

39
doc/Notes.md Normal file
View File

@ -0,0 +1,39 @@
# Notes und kleine Dokumentation
Was haben wir bereits am iTender Projekt gemacht?
<hr>
## Konzept-Erstellung
#### Ideen
- Grund-Ideen
- Smarten Cocktail-Mischer
- 4 Getränke Behälter (mit Saft, Sirup oder Likör bzw. Schnapps)
- 4 Pumpen (Peristaltik Pumpe)
- Raspberry Pi als Prozessoreinheit
- Display in der Front mit Benutzeroberfläche
- Automatisches filtern von Getränken, je nachdem welche "Zutaten" in den Behältern sind
- Messung der aktuellen Füllmenge der Behälter, basierend auf Gewicht (mittels Wägezelle) oder Abstand zur Wasseroberfläche (mittels Ultraschall-Sensor)
- Nice to have
- LED-Stripes für schöne Beleuchtung, basierend auf dem aktuellen Status der Maschine
- Extra Schlauch für weitere außenstehende Getränke
- Mit Bier-Fass Adapter?
- Kühlung der Container mittels Peltierelement und Lüftern
#### Erstes 3D-Modell
<img src="./Screenshot_Model1.1_FrontTopRight.png" width="50%">
<img src="./Screenshot_Model1.1_BackDownLeft.png" width="50%">
#### Neues 3D-Modell
Folgt.
<hr>
## Umsetzung
#### Webseiten-Skizzen

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -5,7 +5,8 @@
"author": "Tobias Hopp <tobi@gaminggeneration.de>",
"license": "UNLICENSED",
"scripts": {
"start": "node ./dist/main.js",
"boot": "yarn && yarn run compile && ./startFrontend.sh && yarn run start",
"start": "DEBUG=itender:* node ./dist/main.js",
"compile": "tsc && webpack",
"compileStart": "yarn run compile; yarn start",
"watchTS": "tsc --watch",
@ -20,6 +21,7 @@
"@types/morgan": "^1.9.3",
"@types/node": "^18.11.9",
"@types/rpi-gpio": "^2.1.1",
"@types/rpi-ws281x-native": "^1.0.0",
"cookie-parser": "^1.4.6",
"debug": "^4.3.4",
"express": "~4.16.1",
@ -28,11 +30,12 @@
"http-errors": "~1.6.3",
"mongoose": "^6.7.2",
"morgan": "^1.10.0",
"net-ping": "^1.2.3",
"pug": "2.0.0-beta11",
"rpi-gpio": "^2.1.7"
"rpi-gpio": "^2.1.7",
"rpi-ws281x-native": "^1.0.4"
},
"devDependencies": {
"@types/electron": "^1.6.10",
"nodemon": "^2.0.20",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",

View File

@ -1,10 +1,19 @@
.btn {
padding: 11px 15px;
padding: 12px 16px;
border: none;
color: white;
font-size: 0.8em;
font-size: 0.95em;
border-radius: 2px;
cursor:none !important;
cursor: none !important;
}
.inputGroup {
margin-bottom: 1.5%;
}
label {
margin-right: 8px;
}
@ -36,10 +45,12 @@
color: white;
}
.btn-secondary:hover {
background-color: var(--secondary-bright);
}
.btn-success {
background-color: var(--success);
color: white;
@ -82,3 +93,27 @@
.btn-dark:hover {
background-color: var(--dark-bright);
}
.input {
padding: 6px;
font-size: 1.1em;
margin-left: 1%;
margin-right: 1%;
border: 0;
border-radius: 3px;
}
.input[type=checkbox] {
margin-bottom: 2px;
/* Double-sized Checkboxes */
-ms-transform: scale(1.5); /* IE */
-moz-transform: scale(1.5); /* FF */
-webkit-transform: scale(1.5); /* Safari and Chrome */
-o-transform: scale(1.5); /* Opera */
transform: scale(1.5);
padding: 10px;
}

View File

@ -0,0 +1,58 @@
#main::-webkit-scrollbar {
display: none;
}
#main {
display: grid;
grid-template-columns: repeat(3, calc(90% / 3));
grid-template-rows: repeat(2, calc(90% / 2));
grid-gap: 10% 5%;
}
#main .drink {
grid-row: span 1;
grid-column: span 1;
background-color: rgba(57, 57, 57, 0.6);
width: 90%;
height: 97%;
display: grid;
grid-template-columns: 100%;
grid-template-rows: repeat(3, calc(100% / 3));
grid-row-gap: 4%;
text-align: center;
border-radius: 30px 10px 30px;
color: black;
/*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);
transition: 0.4s;
overflow: hidden;
padding-bottom: 1%;
}
#main .drink:hover {
background-color: rgba(57, 57, 57, 0.8);
width: 100%;
height: 100%;
}
.drink .thumbnail {
grid-column: span 1;
grid-row: span 2;
margin-left: auto;
margin-right: auto;
max-height: 100%;
overflow: hidden;
transition: 0.5s;
}
.drink .drinkName {
font-family: Ubuntu, sans-serif;
grid-column: span 1;
grid-row: span 1;
font-size: 150%;
}

View File

@ -3,7 +3,8 @@
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 15%; /* Location of the box */
padding-top: 8%; /* Location of the box */
padding-bottom: 1%;
left: 0;
top: 0;
width: 100%; /* Full width */
@ -24,6 +25,7 @@
width: 80%;
text-align: center;
background-color: #FEFEFE;
margin-bottom: 5px;
}

View File

@ -0,0 +1,104 @@
#setup {
padding-left: 5%;
padding-right: 5%;
padding-top: 1%;
display: grid;
grid-template-columns: repeat(2, calc(95% / 2));
grid-template-rows: repeat(7, calc(100% / 7));
grid-gap: 2% 5%;
}
#setup_slots {
min-width: 55px;
width: 10%;
margin-left: 3%;
border: 1px solid aliceblue;
}
#setup #setupContainersDiv {
grid-row: span 5;
grid-column: span 2;
width: 100%;
height: 100%;
text-align: center;
border: 1px solid cadetblue;
border-radius: 5px;
padding: 1% 10px;
overflow: auto;
}
#setup #setupContainersDiv #containerAddBtn {
position: relative;
left: 43%;
bottom: 10%;
}
#setup #setupLEDDiv {
grid-row: span 3;
grid-column: span 1;
height: 100%;
text-align: center;
border: 1px solid cadetblue;
border-radius: 5px;
padding: 1% 10px;
margin-right: 10%;
}
#setup #setupExtraDiv {
grid-row: span 3;
grid-column: span 1;
width: 100%;
height: 100%;
text-align: center;
border: 1px solid cadetblue;
border-radius: 5px;
padding: 1% 10px;
}
#setupContainers .setupContainer {
color: black;
text-align: left;
width: calc(100% / 2.3);
height: 12%;
display: inline-block;
margin-bottom: 3%;
border: 1px solid gray;
margin-left: 2%;
margin-right: 2%;
border-radius: 10px;
padding: 3% 10px;
transition: 0.5s;
background-color: rgb(111, 109, 109);
overflow: hidden;
}
.removeSlowly {
animation: removeSlowlyWidth 0.8s forwards;
}
@keyframes removeSlowlyWidth {
100% {
opacity: 0;
}
}
.error {
background-color: rgba(255,0,0,0.5) !important;
}
#setupContainers .setupContainer p {
margin-bottom: 3%;
font-weight: 600;
font-size: 1.2em;
}
#setupContainers .setupContainer select {
margin-bottom: 2%;
}

View File

@ -1,15 +1,21 @@
@import url("/stylesheets/colors.css");
@import url("/stylesheets/fonts.css");
@import url("/stylesheets/buttons.css");
@import url("/stylesheets/inputs.css");
@import url("/stylesheets/modal.css");
@import url("/stylesheets/main.css");
@import url("/stylesheets/setup.css");
:root {
cursor: none !important;
/*cursor: none !important;*/
}
html * {cursor: none !important}
html * {
/*cursor: none !important*/
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
body::-webkit-scrollbar {
@ -21,8 +27,17 @@ body {
color: white;
scroll-behavior: smooth;
font-family: "Roboto", serif;
font-style: normal;
cursor: none !important;
/*cursor: none !important;*/
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
h1 {
font-size: 1.7em;
font-weight: 500;
margin-bottom: 2%;
color: black;
}
@ -92,6 +107,15 @@ body {
border: 0;
border-radius: 0 8px 0 0;
color: white;
float: left;
}
#overlay #bottom #containers {
height: 100%;
width: 30%;
float: right;
/* todo */
}
@ -103,14 +127,20 @@ body {
height: 80%;
}
#menu {
display: none;
background-color: blue;
width: 100%;
.pane {
height: 100%;
padding: 1% 2%;
overflow: auto;
ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
scroll-behavior: smooth;
color: black;
}
.hiddenPane {
display: none !important;
transition: 0.2s;
}
#settings {
display: none;
@ -119,67 +149,3 @@ body {
height: 100vh;
}
#main::-webkit-scrollbar {
display: none;
}
#main {
display: grid;
height: 100%;
padding: 1% 2%;
grid-template-columns: repeat(3, calc(90% / 3));
grid-template-rows: repeat(2, calc(90% / 2));
grid-gap: 10% 5%;
overflow: auto;
ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
scroll-behavior: smooth;
}
#main .drink {
grid-row: span 1;
grid-column: span 1;
background-color: rgba(57, 57, 57, 0.6);
width: 90%;
height: 97%;
display: grid;
grid-template-columns: 100%;
grid-template-rows: repeat(3, calc(100% / 3));
grid-row-gap: 4%;
text-align: center;
border-radius: 30px 10px 30px;
color: black;
/*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);
transition: 0.4s;
overflow: hidden;
padding-bottom: 1%;
}
#main .drink:hover {
background-color: rgba(57, 57, 57, 0.8);
width: 100%;
height: 100%;
}
.drink .thumbnail {
grid-column: span 1;
grid-row: span 2;
margin-left: auto;
margin-right: auto;
max-height: 100%;
overflow: hidden;
transition: 0.5s;
}
.drink .drinkName {
font-family: Ubuntu, sans-serif;
grid-column: span 1;
grid-row: span 1;
font-size: 150%;
}

3
src/HX711.ts Normal file
View File

@ -0,0 +1,3 @@
export class HX711 {
}

20
src/LEDHandler.ts Normal file
View File

@ -0,0 +1,20 @@
import ws281x from "rpi-ws281x-native";
export class LEDHandler {
private static channel = ws281x(10, {stripType: 'ws2812'});
private static currentInterval: NodeJS.Timer;
public static waterfall() {
clearInterval(this.currentInterval);
this.currentInterval = setInterval(() => {
}, 1000);
}
public static pulseGreen() {
clearInterval(this.currentInterval);
this.currentInterval = setInterval(() => {
}, 1000);
}
}

38
src/Settings.ts Normal file
View File

@ -0,0 +1,38 @@
import * as fs from "fs";
import path from "path";
export class Settings {
static get setupDone(): boolean {
return this._setupDone;
}
private static _setupDone: boolean;
private static json: {};
public static loadSettings() {
if (!fs.existsSync(path.join(__dirname, "/config.json"))) {
this._setupDone = false;
fs.writeFileSync(path.join(__dirname, "/config.json"), `{"setupDone":false}`);
}
let file = fs.readFileSync(path.join(__dirname, "/config.json"));
this.json = JSON.parse(file.toString("utf8"));
this._setupDone = this.json["setupDone"];
this._setupDone = false;
}
public static saveSettings() {
fs.writeFileSync(path.join(__dirname, "/config.json"), JSON.stringify(this.json));
}
public static get(key: string): any {
return this.json[key];
}
public static set(key: string, value: any) {
this.json[key] = value;
}
}

View File

@ -1,4 +1,5 @@
import * as dns from "dns";
import * as ping from "net-ping";
export class Utils {
public static checkInternet(): Promise<boolean> {

View File

@ -3,14 +3,18 @@ import mongoose from "mongoose";
import {IContainer} from "./IContainer";
export const ContainerSchema = new Mongoose.Schema<IContainer>({
slot: {type: Number},
slot: {type: Number, required: true},
volume: {type: Number, required: true, default: 1000},
sensorEcho: Number,
sensorTrigger: Number,
content: {type: mongoose.Types.ObjectId},
sensorType: String,
sensorPin1: Number,
sensorPin2: Number,
pumpPin: {type: Number, required: true},
content: {type: mongoose.Types.ObjectId, ref: "Ingredient"},
sensorFilledMax: Number,
sensorFilledMin: Number,
filled: Number,
enabled: {type: Boolean, default: false},
autoDisabled: {type: Boolean, default: false}
});
const Container = mongoose.model<IContainer>('Container', ContainerSchema);

View File

@ -1,13 +1,17 @@
import {IIngredient} from "./IIngredient";
import * as mongoose from "mongoose";
export interface IContainer extends mongoose.Document{
export interface IContainer extends mongoose.Document {
slot: number;
content: IIngredient|undefined;
content: IIngredient | undefined;
volume: number;
sensorFilledMin : number;
sensorFilledMin: number;
sensorFilledMax: number;
sensorTrigger: number;
sensorEcho: number;
sensorType: string;
sensorPin1: number;
sensorPin2: number;
pumpPin: number;
filled: Number;
enabled: boolean;
autoDisabled: boolean;
}

View File

@ -7,11 +7,15 @@ import {IDrink} from "./database/IDrink";
import debug from "debug";
import {WebSocketHandler} from "./WebSocketHandler";
import {IJob} from "./database/IJob";
import {Utils} from "./Utils";
import {WebSocketPayload} from "./WebSocketPayload";
import {WebSocketEvent} from "./WebSocketEvent";
import {HX711} from "./HX711";
const log = debug("itender:station");
export class iTender {
static get containers(): { container: IContainer; sensor: HCSR04; pump: null }[] {
static get containers(): { container: IContainer, sensor: HCSR04 | HX711, pump: null }[] {
return this._containers;
}
@ -25,8 +29,13 @@ export class iTender {
private static _status: iTenderStatus = iTenderStatus.STARTING;
private static _currentJob: IJob | null = null;
private static _internetConnection: boolean = false;
private static _containers: { container: IContainer, sensor: HCSR04, pump: null }[] = [];
static get internetConnection(): boolean {
return this._internetConnection;
}
private static _containers: { container: IContainer, sensor: HCSR04 | HX711, pump: null }[] = [];
private static _drinks: IDrink[];
static setStatus(status: iTenderStatus) {
@ -57,8 +66,11 @@ export class iTender {
return new Promise(async resolve => {
for (let c of this._containers) {
try {
let dist = c.sensor.distance();
c.container.filled = dist * 100 / (c.container.sensorFilledMax + c.container.sensorFilledMin);
if (!(c.sensor instanceof HX711)) {
let dist = c.sensor.distance();
c.container.filled = dist * 100 / (c.container.sensorFilledMax + c.container.sensorFilledMin);
}
} catch (e) {
c.container.filled = -1;
}
@ -66,6 +78,14 @@ export class iTender {
}
log("Containers measured!");
resolve();
let cons: IContainer[] = [];
for (let c of this._containers) {
cons.push(c.container);
}
let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, cons);
});
}
@ -109,7 +129,7 @@ export class iTender {
if (c2.container._id == c._id) {
let sensor;
try {
sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
//sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
} catch (e) {
}
this._containers[i] = {
@ -125,7 +145,7 @@ export class iTender {
if (!found) {
let sensor;
try {
sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
//sensor = new HCSR04(c.sensorTrigger, c.sensorEcho);
} catch (e) {
}
this._containers.push({
@ -160,5 +180,20 @@ export class iTender {
}, 30000);
}
public static async checkNetwork() {
this._internetConnection = await Utils.checkInternet();
}
static refreshFromServer(): Promise<void> {
return new Promise(async (resolve, reject) => {
let before = iTender._status;
iTender.setStatus(iTenderStatus.DOWNLOADING)
// todo
resolve();
iTender.setStatus(before);
});
}
}

View File

@ -5,10 +5,14 @@ export enum iTenderStatus {
READY = "READY",
// Machine is filling your drink and destroying your leberwurst
FILLING = "FILLING",
// Drinks will be refreshed from global database (the internet neuland :O)
// Drinks will be refreshed
REFRESHING = "REFRESHING",
// Drinks will be calculated (check containers and which drinks can be done)
CALCULATING = "CALCULATING",
// Download drinks from the world wide web
DOWNLOADING = "DOWNLOADING",
// Device is in setup mode for first setup
SETUP = "SETUP",
// An error happened; Oh no :(
ERROR = "ERROR"
}

View File

@ -6,9 +6,8 @@ import Ingredient from "./database/Ingredient";
import {iTender} from "./iTender";
import {iTenderStatus} from "./iTenderStatus";
import {Utils} from "./Utils";
import {Category} from "./Category";
import Drink from "./database/Drink";
import Container from "./database/Container";
import {Settings} from "./Settings";
const log = debug("itender:server");
@ -23,17 +22,36 @@ const wsApp = new WebsocketApp();
//await test();
await app.listen();
await wsApp.listen();
Settings.loadSettings();
iTender.setStatus(iTenderStatus.STARTING);
await Utils.sleep(5000);
await Utils.sleep(2000);
await init();
if (!Settings.setupDone) {
iTender.setStatus(iTenderStatus.SETUP);
log("iTender is not set up yet!");
}
setInterval(refresh, 1000 * 60 * 10);
iTender.setStatus(iTenderStatus.READY);
function checkStart() {
setTimeout(async () => {
if (!Settings.setupDone) {
checkStart();
return;
}
Settings.saveSettings();
await init();
setInterval(refresh, 1000 * 60 * 10);
iTender.setStatus(iTenderStatus.READY);
}, 1000);
}
checkStart();
} catch (e) {
console.error("---- ERROR ----")
console.error("---- ERROR ----");
console.error(e);
process.exit(-1);
}
@ -42,8 +60,19 @@ const wsApp = new WebsocketApp();
function init(): Promise<void> {
log("Initializing...");
return new Promise(async resolve => {
iTender.setStatus(iTenderStatus.STARTING);
// Network
await iTender.checkNetwork();
if (iTender.internetConnection) {
await iTender.refreshFromServer();
}
// Containers
await iTender.refreshContainers();
// Drinks
await iTender.refreshDrinks();
// Start auto checkup for stuck jobs
await iTender.autoCheckup();
resolve();
@ -52,6 +81,11 @@ function init(): Promise<void> {
function refresh(): Promise<void> {
return new Promise(async resolve => {
// Network
await iTender.checkNetwork();
// Below are refreshments of containers / drinks
// If there is a current job, DO NOT REFRESH!
if (iTender.currentJob)
return;
@ -108,16 +142,16 @@ async function test() {
console.log(drink);*/
let container = new Container();
/*let container = new Container();
container.slot = 2;
container.volume = 750;
container.sensorEcho = 28;
container.sensorTrigger = 29;
container.content = ingredient;
container.sensorFilledMax = 2;
container.sensorFilledMin = 15;
container.sensorFilledMin = 15;*/
await container.save();
//await container.save();
/* let container = await Container.findOne({slot: 1});
if (!container) return;

View File

@ -5,21 +5,25 @@ export class Modal {
private static currentModalId: string | undefined = "";
private _title: string = "iTender";
private _content: string | undefined = "";
private _id: string = "";
private _loader: boolean = false;
private _buttons: { type: string, content: string, onclick: Function }[] = [];
private _leftCentered: boolean = false;
private _elements: HTMLElement[] = [];
set leftCentered(value: boolean) {
this._leftCentered = value;
}
private static modalInClose = false;
constructor(id, title: string, content?: string) {
constructor(id, title: string) {
this._id = id;
this._title = title;
this._content = content;
let t = document.createElement("h1");
t.innerText = title;
this._elements.push(t);
}
public static isModalOpen(): boolean {
@ -27,13 +31,10 @@ export class Modal {
}
set title(value: string) {
this._title = value;
public addContent(element: HTMLElement) {
this._elements.push(element);
}
set content(value: string | undefined) {
this._content = value;
}
set id(value: string) {
this._id = value;
@ -43,74 +44,79 @@ export class Modal {
this._loader = value;
}
public addButton(type: ButtonType, content: string, onclick: Function) {
this._buttons.push({type: type, content: content, onclick: onclick});
public addButton(type: ButtonType, content: string, onclick: Function): HTMLButtonElement {
let btn = document.createElement("button");
btn.classList.add("btn", "btn-" + type);
btn.onclick = () => onclick(btn);
btn.innerText = content;
btn.value = content;
this._elements.push(btn);
return btn;
}
public open() {
if (!this._content)
this._content = "";
public open(): Promise<void> {
return new Promise(async (resolve) => {
/* if (this._leftCentered) {
this._content = "<div style='text-align: left; padding-left: 2%;'>" + this._content;
}*/ //todo
if( this._leftCentered )
{
this._content = "<div style='text-align: left; padding-left: 2%;'>" + this._content;
}
/* if (this._loader)
this._content += "<br><div class=\"lds-ellipsis\">\n" +
" <div></div><div></div><div></div><div></div>\n" +
"</div>";*/ // todo
if (this._loader)
this._content += "<br><div class=\"lds-ellipsis\">\n" +
" <div></div><div></div><div></div><div></div>\n" +
"</div>";
/*if (this._leftCentered) {
this._content += "</div>";
}*/
for (let btn of this._buttons) {
this._content += `<button class="btn btn-${btn.type}" onclick="(${btn.onclick})();">${btn.content}</button>`;
}
let elements = this._elements;
let id = this._id;
if( this._leftCentered )
{
this._content+= "</div>";
}
let title = this._title;
let content = this._content;
let id = this._id;
function tryOpen()
{
if( Modal.modalInClose )
{
setTimeout( tryOpen, 50 );
return;
function tryOpen() {
if (Modal.modalInClose) {
setTimeout(tryOpen, 50);
return;
}
resolve();
Modal.open(elements, id);
}
Modal.open(title, content, id );
}
tryOpen();
tryOpen();
});
}
/**
* @param title
* @param content
* @param elements
* @param id
*/
public static open(title: string, content: string, id?: string): void {
private static open(elements: HTMLElement[], id?: string): Promise<void> {
return new Promise((resolve, reject) => {
const modal = document.getElementById("modal");
const modalContent = document.getElementById("modalInnerContent");
const modal = document.getElementById("modal");
const modalContent = document.getElementById("modalInnerContent");
if (!modal || !modalContent) {
reject();
return;
}
if (!modal || !modalContent)
return;
modalContent.classList.add("modalBlendIn");
modal.classList.add("modalBlendIn");
modalContent.classList.add("modalBlendIn");
modal.classList.add("modalBlendIn");
setTimeout(() => {
modalContent.classList.remove("modalBlendIn");
modal.classList.remove("modalBlendIn");
resolve();
}, 800);
setTimeout(() => {
modalContent.classList.remove("modalBlendIn");
modal.classList.remove("modalBlendIn");
}, 800);
modalContent.innerHTML = "";
elements.forEach((val) => modalContent.append(val));
//modalContent.innerHTML = `<h1 id="modalTitle">${title}</h1>${content}`;
modal.style.display = "block";
modalContent.innerHTML = `<h1 id="modalTitle">${title}</h1>${content}`;
modal.style.display = "block";
this.currentModalId = id ? id : "null";
});
this.currentModalId = id ? id : "null";
}
public static close(id?: string): void {

6
src/web/Pane.ts Normal file
View File

@ -0,0 +1,6 @@
export enum Pane {
MAIN= "Main",
MENU = "Menü",
SETTINGS = "Einstellungen",
SETUP = "Setup"
}

3
src/web/Settings.ts Normal file
View File

@ -0,0 +1,3 @@
export class Settings {
}

7
src/web/Setup.ts Normal file
View File

@ -0,0 +1,7 @@
export class Setup {
public static onSetupUpdate()
{
// Setup containers updated
}
}

View File

@ -1,11 +1,15 @@
import {WebSocketPayload} from "../WebSocketPayload";
import {IDrink} from "../database/IDrink";
import {Modal} from "./Modal";
import {ButtonType} from "./ButtonType";
import {Pane} from "./Pane";
import {setup} from "rpi-gpio";
export class WebHandler {
private static containers = [];
public static onDrinkUpdate(payload: WebSocketPayload) {
if (!payload.data) return;
let drinks: IDrink[] = payload.data;
const main = document.getElementById("main");
@ -13,6 +17,8 @@ export class WebHandler {
main.style.gridTemplateRows = `repeat(${Math.round(drinks.length / 3)}, calc(90%/2))`;
main.innerHTML = "";
for (let drink of drinks) {
let drinkEle = document.createElement("div");
drinkEle.classList.add("drink");
@ -30,25 +36,560 @@ export class WebHandler {
drinkImg.src = "/images/" + drink.name + ".png";
drinkName.innerText = drink.name;
/*
let ingredients = "<ul style='list-style-type: disc;'>";
for( let i of drink.ingredients )
{
for (let i of drink.ingredients) {
ingredients += "<li>" + i.amount + "ml " + i.type.name + "</li>";
}
ingredients+="</ul>"
ingredients += "</ul>"
drinkEle.onclick = () => {
let modal = new Modal("drink", drink.name );
modal.content = `<strong>Zutaten</strong><br>
${ingredients}`
modal.leftCentered = true;
modal.open();
};
let modal = new Modal("drink", drink.name);
main.append(drinkEle);
modal.content = `<strong>Zutaten</strong><br>
${ingredients}`*/ //todo
//modal.leftCentered = true;
// modal.open();
//};
//main.append(drinkEle);
}
}
public static onContainerUpdate(payload: WebSocketPayload) {
}
static async openSetup() {
// new
this.openPane(Pane.SETUP);
let menuBtn = document.getElementById("menuBtn") as HTMLButtonElement;
let setupContainers = document.getElementById("setupContainers") as HTMLDivElement;
menuBtn.disabled = true;
const containerAddBtn = document.getElementById("containerAddBtn") as HTMLButtonElement;
containerAddBtn.onclick = () => {
let con = document.createElement("div");
let containerName = document.createElement("p");
containerName.innerText = "Behälter " + (setupContainers.getElementsByTagName("div").length + 1);
con.classList.add("setupContainer");
con.append(containerName);
let sensorTypeLabel = document.createElement("label");
sensorTypeLabel.innerText = "Art des Sensors ";
con.append(sensorTypeLabel);
let sensorType = document.createElement("select");
sensorType.classList.add("input");
let sensorTypeNone = document.createElement("option") as HTMLOptionElement;
sensorTypeNone.innerText = "Keiner";
sensorTypeNone.value = "0";
sensorType.append(sensorTypeNone);
let sensorTypeUltrasound = document.createElement("option") as HTMLOptionElement;
sensorTypeUltrasound.innerText = "Ultraschall";
sensorTypeUltrasound.value = "1";
sensorType.append(sensorTypeUltrasound);
let sensorTypeScale = document.createElement("option") as HTMLOptionElement;
sensorTypeScale.innerText = "Wägezelle";
sensorTypeScale.value = "2";
sensorType.append(sensorTypeScale);
con.append(sensorType);
con.append(document.createElement("br"));
let selectPin = document.createElement("select");
selectPin.style.display = "none";
selectPin.classList.add("input");
selectPin.style.display = "inline";
let noSel = document.createElement("option") as HTMLOptionElement;
noSel.innerText = "Bitte wählen";
noSel.value = "-1";
noSel.disabled = true;
selectPin.append(noSel.cloneNode(true));
selectPin.selectedIndex = 0;
const pins = [3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 22, 23, 24, 26, 29, 31, 32, 33, 34, 35, 36, 37, 38, 40];
for (let pin of pins) {
let pinEle = document.createElement("option") as HTMLOptionElement;
pinEle.innerText = "" + pin;
pinEle.value = "" + pin;
selectPin.append(pinEle);
}
let pumpLabel = document.createElement("label");
pumpLabel.innerText = "Pumpen Pin";
con.append(pumpLabel);
con.append(selectPin.cloneNode(true));
con.append(document.createElement("br"));
let sensor1Label = document.createElement("label");
sensor1Label.innerText = "Sensor 1 Pin";
con.append(sensor1Label);
con.append(selectPin.cloneNode(true));
con.append(document.createElement("br"));
let sensor2Label = document.createElement("label");
sensor2Label.innerText = "Sensor 2 Pin";
con.append(sensor2Label);
con.append(selectPin.cloneNode(true));
con.append(document.createElement("br"));
let removeBtn = document.createElement("button");
removeBtn.classList.add("btn", "btn-danger");
removeBtn.onclick = () => {
con.classList.add("removeSlowly");
setTimeout(() => {
con.remove();
let i = 1;
for (let elementsByTagNameElement of setupContainers.getElementsByTagName("div")) {
let e = elementsByTagNameElement.getElementsByTagName("p")[0] as HTMLParagraphElement;
e.innerText = "Behälter " + i;
i++;
}
}, 750);
}
removeBtn.style.float = "right";
removeBtn.innerText = "Entfernen";
con.append(removeBtn);
setupContainers.append(con);
};
return;
// old
Modal.close();
let modal = new Modal("setup", "Setup 1/2");
let containers: { container: HTMLDivElement, slot: number, pumpPin: HTMLSelectElement, sensorType: HTMLSelectElement, sensorPin1: HTMLSelectElement, sensorPin2: HTMLSelectElement, volume: HTMLSelectElement }[] = [];
let onchange = () => {
let containerEle = document.getElementById("setup_containers") as HTMLDivElement;
let containerNumber = document.getElementById("setup_slots") as HTMLInputElement;
if (!containerEle || !containerNumber) return;
let i = 1;
let oldElements = containerEle.getElementsByTagName("div");
for (let e of oldElements) {
if (i > containerNumber.valueAsNumber) {
e.remove();
containers.pop();
}
i++;
}
for (let i = containerEle.getElementsByTagName("div").length; i < containerNumber.valueAsNumber; i++) {
let con = document.createElement("div");
let thisContainer = {};
thisContainer["container"] = con;
con.classList.add("setupContainer");
let containerName = document.createElement("p");
containerName.innerText = "Container " + (i + 1);
thisContainer["slot"] = i + 1;
con.append(containerName);
let sensorTypeLabel = document.createElement("label");
sensorTypeLabel.innerText = "Art des Sensors ";
con.append(sensorTypeLabel);
let sensorType = document.createElement("select");
sensorType.classList.add("input");
let sensorTypeNone = document.createElement("option") as HTMLOptionElement;
sensorTypeNone.innerText = "Keiner";
sensorTypeNone.value = "0";
sensorType.append(sensorTypeNone);
let sensorTypeUltrasound = document.createElement("option") as HTMLOptionElement;
sensorTypeUltrasound.innerText = "Ultraschall";
sensorTypeUltrasound.value = "1";
sensorType.append(sensorTypeUltrasound);
let sensorTypeScale = document.createElement("option") as HTMLOptionElement;
sensorTypeScale.innerText = "Wägezelle";
sensorTypeScale.value = "2";
sensorType.append(sensorTypeScale);
con.append(sensorType);
thisContainer["sensorType"] = sensorType;
con.append(document.createElement("br"));
let changeWhenTypeOfSensor: HTMLElement[] = [];
let list = ["Pumpen Port ", "Sensor 1 ", "Sensor 2 "];
for (let t of list) {
let labelElement = document.createElement("label");
labelElement.innerText = t;
changeWhenTypeOfSensor.push(labelElement);
con.append(labelElement);
let selectElement = document.createElement("select");
labelElement.style.display = "none";
selectElement.style.display = "none";
selectElement.classList.add("input");
if (t == "Sensor 1 ") {
thisContainer["sensorPin1"] = selectElement;
} else if (t == "Sensor 2 ") {
thisContainer["sensorPin2"] = selectElement;
} else {
thisContainer["pumpPin"] = selectElement;
labelElement.style.display = "inline";
selectElement.style.display = "inline";
}
let noSel = document.createElement("option") as HTMLOptionElement;
noSel.innerText = "Bitte wählen";
noSel.value = "-1";
noSel.disabled = true;
selectElement.append(noSel); //loveyou
selectElement.selectedIndex = 0;
const pins = [3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 22, 23, 24, 26, 29, 31, 32, 33, 34, 35, 36, 37, 38, 40];
for (let pin of pins) {
let pinEle = document.createElement("option") as HTMLOptionElement;
pinEle.innerText = "" + pin;
pinEle.value = "" + pin;
selectElement.append(pinEle);
}
con.append(selectElement);
let br = document.createElement("br");
con.append(br);
}
sensorType.onchange = () => {
// 0 => ultrasound; 1 => scale
if (sensorType.value == "1") {
thisContainer["sensorPin1"].style.display = "inline";
thisContainer["sensorPin2"].style.display = "inline";
changeWhenTypeOfSensor[1].style.display = "inline";
changeWhenTypeOfSensor[2].style.display = "inline";
changeWhenTypeOfSensor[1].innerText = "Trigger Pin ";
changeWhenTypeOfSensor[2].innerText = "Echo Pin ";
} else if (sensorType.value == "2") {
thisContainer["sensorPin1"].style.display = "inline";
thisContainer["sensorPin2"].style.display = "inline";
changeWhenTypeOfSensor[1].style.display = "inline";
changeWhenTypeOfSensor[2].style.display = "inline";
changeWhenTypeOfSensor[1].innerText = "Clock Pin ";
changeWhenTypeOfSensor[2].innerText = "Data Pin ";
} else {
thisContainer["sensorPin1"].style.display = "none";
thisContainer["sensorPin2"].style.display = "none";
changeWhenTypeOfSensor[1].style.display = "none";
changeWhenTypeOfSensor[2].style.display = "none";
changeWhenTypeOfSensor[1].innerText = "Sensor 1 ";
changeWhenTypeOfSensor[2].innerText = "Sensor 2 ";
}
};
let labelElement = document.createElement("label");
labelElement.innerText = "Gesamtvolumen (ml) ";
con.append(labelElement);
let selectElement = document.createElement("select");
selectElement.classList.add("input");
const mls = [50, 100, 200, 250, 300, 330, 500, 750, 1000, 1250, 1500, 2000, 2500, 5000, 10000];
for (let ml of mls) {
let pinEle = document.createElement("option") as HTMLOptionElement;
pinEle.innerText = "" + ml;
pinEle.value = "" + ml;
selectElement.append(pinEle);
thisContainer["volume"] = selectElement;
}
selectElement.selectedIndex = 7;
con.append(selectElement);
let br = document.createElement("br");
con.append(br);
containerEle.append(con);
let cast = thisContainer as { container: HTMLDivElement, slot: number, pumpPin: HTMLSelectElement, sensorType: HTMLSelectElement, sensorPin1: HTMLSelectElement, sensorPin2: HTMLSelectElement, volume: HTMLSelectElement };
containers.push(cast);
console.log(cast);
}
};
let div1 = document.createElement("div");
div1.style.marginBottom = "2%";
modal.addContent(div1);
let labelElement = document.createElement("label");
labelElement.innerText = "Anzahl Slots";
div1.append(labelElement);
let numberInputElement = document.createElement("input") as HTMLInputElement;
numberInputElement.type = "number";
numberInputElement.id = "setup_slots";
numberInputElement.classList.add("input");
numberInputElement.style.width = "20%;"
numberInputElement.value = "0";
numberInputElement.min = "0";
numberInputElement.max = "30";
numberInputElement.onchange = () => {
onchange();
};
div1.append(numberInputElement);
let divContainers = document.createElement("div");
divContainers.id = "setup_containers";
modal.addContent(divContainers);
let status = document.createElement("p");
status.style.color = "black";
status.innerHTML = "Drücke 'Weiter' zum Speichern";
modal.addContent(status);
modal.addButton(ButtonType.SUCCESS, "Weiter", () => {
status.innerHTML = "...";
status.style.color = "gray";
if (numberInputElement.valueAsNumber < 0 || numberInputElement.valueAsNumber > 30) {
numberInputElement.value = "0";
status.style.color = "black";
status.innerHTML = "Automatische Änderungen überprüfen!";
return;
}
// Check
if (numberInputElement.valueAsNumber != containers.length) {
onchange();
status.style.color = "black";
status.innerHTML = "Automatische Änderungen überprüfen!";
return;
}
if (containers.length == 0) {
status.style.color = "red";
status.innerHTML = "Es muss mindestens ein Container hinzugefügt werden!";
return;
}
let newContainers: { slot: number, pumpPin: number, sensorPin1: number, sensorPin2: number, volume: number }[] = [];
let ok = true;
for (let c of containers) {
for (let c2 of containers) {
if (c.slot == c.slot) continue;
let pins = [c.sensorPin1.value, c.sensorPin2.value, c.pumpPin.value, c2.sensorPin1.value, c2.sensorPin2.value, c2.pumpPin.value];
let i = 0;
for (let pin of pins) {
let j = 0;
if (pin == "-1") {
console.log("Skip ", pin);
continue;
}
for (let pin2 of pins) {
// Wenn index derselbe ist, ignorieren
if (j == i) {
console.log("Index skip ", pin);
continue;
}
if (pin2 == "-1") {
console.log("Skip ", pin);
continue;
}
if (pin == pin2) {
console.log("Pin same error", pin, pin2, c, c2)
ok = false;
c.container.classList.add("error");
c2.container.classList.add("error");
setTimeout(() => {
c.container.classList.remove("error");
c2.container.classList.remove("error");
}, 2000);
break;
}
j++;
}
i++;
if (!ok) break;
}
if (!ok) break;
/*console.log(c, c2);
console.log(c.sensorPin2.value == c2.sensorPin2.value);
if (
c.pumpPin.value == c2.pumpPin.value ||
(c.sensorType.value != "0" && (
(c.sensorPin1.value == c2.sensorPin1.value)
|| (c.sensorPin2.value == c2.sensorPin2.value)
|| (c.sensorPin1.value == c2.sensorPin2.value)
|| (c.sensorPin2.value == c2.sensorPin1.value)))
) {
console.log("Check not ok")
ok = false;
c.container.classList.add("error");
c2.container.classList.add("error");
setTimeout(() => {
c.container.classList.remove("error");
c2.container.classList.remove("error");
}, 2000);
break;
}*/
}
if (!ok) break;
if (c.pumpPin.value == "-1" || (c.sensorType.value != "0" && (c.sensorPin1.value == "-1" || c.sensorPin2.value == "-1"))) {
console.log("Local check invalid", c);
ok = false;
c.container.classList.add("error");
setTimeout(() => {
c.container.classList.remove("error");
}, 1000);
}
let pins = [c.sensorPin1.value, c.sensorPin2.value, c.pumpPin.value];
let i = 0;
for (let pin of pins) {
let j = 0;
if (pin == "-1") {
console.log("Skip ", pin);
continue;
}
for (let pin2 of pins) {
// Wenn index derselbe ist, ignorieren
if (j == i) {
console.log("Index skip ", pin);
continue;
}
if (pin2 == "-1") {
console.log("Skip ", pin);
continue;
}
if (pin == pin2) {
console.log("Pin same error", pin, pin2, c)
ok = false;
c.container.classList.add("error");
setTimeout(() => {
c.container.classList.remove("error");
}, 2000);
break;
}
j++;
}
i++;
}
/*if (c.pumpPin.value == c.sensorPin1.value || c.pumpPin.value == c.sensorPin2.value || c.sensorPin1.value == c.sensorPin2.value) {
ok = false;
c.container.classList.add("error");
setTimeout(() => {
c.container.classList.remove("error");
}, 1000);
break;
}*/
newContainers.push({
slot: c.slot,
sensorPin1: parseInt(c.sensorPin1.value),
sensorPin2: parseInt(c.sensorPin2.value),
pumpPin: parseInt(c.pumpPin.value),
volume: parseInt(c.volume.value)
});
}
if (!ok) {
status.style.color = "red";
status.innerHTML = "Problem erkannt<br>Bitte überprüfen, ob alle benötigten Felder gesetzt sind<br>und ob Pins nicht mehrfach belegt sind!"
return;
}
// todo Hier konfiguration an Server senden und speichern
// zurück und hinsenden mittels function die hier in der WebHandler.ts gespeichert wird maybe?
status.style.color = "green";
status.innerHTML = "Konfiguration gespeichert!";
let tareModal = new Modal("setup", "Setup 2/2");
let container = document.createElement("div");
tareModal.addContent(container);
let txt = document.createElement("p");
txt.innerHTML = `Um das Setup abzuschließen, müssen die Sensoren eingestellt werden.<br>
Bitte zunächst jegliche Behälter von/unter den Sensoren entfernen.<br>Zum fortfahren "Messen"-Schaltfläche berühren.<br>`;
tareModal.addContent(txt);
let btn = tareModal.addButton(ButtonType.SUCCESS, "Messen", () => {
txt.innerHTML = "<strong>Messung läuft...</strong><br><span style='text-decoration: underline'>Gerät nicht berühren!</span><br><div class=\"lds-ellipsis\">\n" +
" <div></div><div></div><div></div><div></div>\n" +
"</div>";
btn.disabled = true;
btn.innerText = "Bitte warten";
});
// todo send messung to server to start
tareModal.open();
});
//await modal.open();
let setupNumber = document.getElementById("setup_slots") as HTMLInputElement;
setupNumber.focus();
}
public static openPane(pane: Pane): void {
let mainPanel = document.getElementById("main") as HTMLDivElement;
let setupPanel = document.getElementById("setup") as HTMLDivElement;
let menuPanel = document.getElementById("menu") as HTMLDivElement;
let settingsPanel = document.getElementById("settings") as HTMLDivElement;
mainPanel.classList.add("hiddenPane");
setupPanel.classList.add("hiddenPane");
menuPanel.classList.add("hiddenPane");
settingsPanel.classList.add("hiddenPane");
switch (pane) {
case Pane.MAIN: {
mainPanel.classList.remove("hiddenPane");
break;
}
case Pane.MENU: {
menuPanel.classList.remove("hiddenPane");
break;
}
case Pane.SETUP: {
setupPanel.classList.remove("hiddenPane");
break;
}
case Pane.SETTINGS: {
settingsPanel.classList.remove("hiddenPane");
break;
}
}
let title = document.getElementById("title") as HTMLTitleElement;
title.innerText = pane.toString();
}
}

View File

@ -42,20 +42,29 @@ export class WebWebSocketHandler {
case iTenderStatus.READY: {
Modal.close("start");
Modal.close("refreshing");
Modal.close("setup");
break;
}
case iTenderStatus.STARTING: {
let modal = new Modal("start", "Willkommen!", `Einen Augenblick bitte<br>iTender startet...`);
let modal = new Modal("start", "Willkommen!");
let txt = document.createElement("p");
txt.innerHTML = `Einen Augenblick bitte<br>iTender startet...`;
modal.addContent(txt);
modal.loader = true;
modal.open();
break;
}
case iTenderStatus.REFRESHING: {
let modal = new Modal("refreshing", "Aktualisieren...", `Einen Augenblick bitte<br>iTender aktualisiert die Getränke...`);
let modal = new Modal("refreshing", "Aktualisieren...");
let txt = document.createElement("p");
txt.innerHTML = `Einen Augenblick bitte<br>iTender aktualisiert die Getränke...`;
modal.addContent(txt);
modal.loader = true;
modal.open();
break;
}
case iTenderStatus.SETUP: {
WebHandler.openSetup();
}
}
break;
@ -65,18 +74,22 @@ export class WebWebSocketHandler {
WebHandler.onDrinkUpdate(payload);
break;
}
case WebSocketEvent.CONTAINERS: {
WebHandler.onContainerUpdate(payload);
break;
}
}
}
private onOpen(event) {
console.log("[WS] Connected", event);
let connectionElement = document.getElementById("right");
if (connectionElement)
{
/*let connectionElement = document.getElementById("right");
if (connectionElement) {
connectionElement.innerText = "Verbunden";
connectionElement.style.color = "green";
}
}*/
}
@ -84,14 +97,18 @@ export class WebWebSocketHandler {
console.error("[WS] Closed!", event);
if (event.wasClean) {
let modal = new Modal("socketClosed", "Sitzung beendet!");
modal.content = `Diese Sitzung wurde beendet, da der iTender nun an einem anderen Gerät bzw. an dem Hauptgerät gesteuert wird.<br><br>`;
let txt = document.createElement("p");
txt.innerHTML = `Diese Sitzung wurde beendet, da der iTender nun an einem anderen Gerät bzw. an dem Hauptgerät gesteuert wird.<br><br>`;
modal.addContent(txt);
modal.addButton(ButtonType.SUCCESS, "Sitzung wiederherstellen", () => {
window.location.reload();
});
modal.open();
} else {
let modal = new Modal("socketClosed", "Verbindungsproblem!");
modal.content = `Die Benutzeroberfläche hat die Verbindung mit dem Gerät verloren.<br>Die Verbindung wird wiederhergestellt...<br>`;
let txt = document.createElement("p");
txt.innerHTML = `Die Benutzeroberfläche hat die Verbindung mit dem Gerät verloren.<br>Die Verbindung wird wiederhergestellt...<br>`;
modal.addContent(txt);
modal.loader = true;
modal.open();
setInterval(() => {
@ -99,19 +116,18 @@ export class WebWebSocketHandler {
}, 5000);
}
let connectionElement = document.getElementById("right");
if (connectionElement)
{
/* let connectionElement = document.getElementById("right");
if (connectionElement) {
connectionElement.innerText = "Getrennt";
connectionElement.style.color = "red";
}
}*/
}
private onError(event) {
console.error("[WS] Error", event);
let connectionElement = document.getElementById("right");
/*let connectionElement = document.getElementById("right");
if (connectionElement)
connectionElement.innerText = "Fehler";
connectionElement.innerText = "Fehler";*/
//openModal("Einen Augenblick...", `Es wurde ein kritischer Fehler festgestellt.\nBitte warten Sie, während der Prozess neu gestartet wird...` );
//window.location.reload();
}

View File

@ -1,14 +1,19 @@
import {WebWebSocketHandler} from "./WebWebSocketHandler";
import {Modal} from "./Modal";
import {WebHandler} from "./WebHandler";
import {Pane} from "./Pane";
const main = document.getElementById("main");
const time = document.getElementById("title");
const time = document.getElementById("right");
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM Loaded");
WebHandler.openPane(Pane.MAIN);
let modal = new Modal("start", "iTender");
modal.content = "Willkommen";
let txt = document.createElement("p");
txt.innerText = "Willkommen"
modal.addContent(txt);
modal.loader = true;
//modal.open();
connect();

3
startFrontend.sh Executable file
View File

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

View File

@ -1,8 +1,46 @@
extends layout
block main
block setup
div#setupContainersDiv
h1 Behälter
// Setup
button.btn.btn-primary#containerAddBtn Hinzufügen
br
div#setupContainers
div#setupLEDDiv
h1 LED-Einstellungen
div.inputGroup
label Aktivieren
input.input(type="checkbox")
div.inputGroup
label GPIO-Pin
input.input(type="number" value="22" style="width:15%" disabled="disabled")
div.inputGroup
label Ambiente Farbe
input.input(type="color" value="#05445e" style="width:15%" disabled="disabled")
div#setupExtraDiv
h1 Erweiterte Einstellungen
div.inputGroup
label Remote-Verbindungen erlauben
input.input(type="checkbox" id="setup_remoteCheckbox")
div.inputGroup
label Ohne WiFi Hotspot aktivieren
input.input(type="checkbox" id="setup_hotspotCheckbox")
div.inputGroup
label Ohne WiFi Hotspot aktivieren
input.input(type="checkbox" id="setup_hotspotCheckbox")
button.btn.btn-success(style="grid-row: span 1; grid-column: span 2; border-radius: 15px; font-size: 1.2em;") Speichern
block menu
// Menu
block settings
// Settings
block main
// Main is build dynamically

View File

@ -12,23 +12,22 @@ html
div#overlay
div#top
span#left <strong>Status:</strong> <span id="status"></span>
span#left <strong>Status:</strong> <span id="status">...</span>
span#title iTender
span#right Verbinden...
div#bottom
button#menuBtn Menü
button.btn#menuBtn Menü
div#containers
div#container
div#menu
div.pane#setup
block setup
div.pane#menu
block menu
div#settings
div.pane#settings
block settings
div#main
div.pane#main
block main
block extra
script(src="/web.js")
script.
// setTimeout( () =>
// {
// window.location.reload();
// }, 120000 );
script(src="/web.js")

3684
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff

View File

@ -998,6 +998,13 @@
dependencies:
"@types/node" "*"
"@types/rpi-ws281x-native@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/rpi-ws281x-native/-/rpi-ws281x-native-1.0.0.tgz#7de8a1fafcd2b03f848dad3b0f721319bc8a8341"
integrity sha512-DbbeQgMYmgFm6gHiZVeRV0rnJzQ3eNpz4zNqPOwgMpONYyhz3+ssQExduIIs77qZ8SOn/2c0RsZjMtbTdGtuxQ==
dependencies:
"@types/node" "*"
"@types/serve-static@*":
version "1.15.0"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155"
@ -1337,7 +1344,7 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
bindings@^1.5.0:
bindings@^1.3.0, bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
@ -2543,6 +2550,11 @@ ms@2.1.3, ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nan@2.14.*:
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nan@^2.14.0, nan@^2.14.2:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
@ -2558,6 +2570,13 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
net-ping@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/net-ping/-/net-ping-1.2.3.tgz#c4dd248f6e3d8d73db6928002106bb71ceae4a40"
integrity sha512-ZKxj/kVPKL2RIsV9nR6I8nMT8Pi3k6ciTBKxD/6gd5lga9qcNmlyqNv+dbXqYGBvHsmG9yIpsfajr8X054x2fQ==
dependencies:
raw-socket "*"
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@ -2897,6 +2916,13 @@ raw-body@2.3.3:
iconv-lite "0.4.23"
unpipe "1.0.0"
raw-socket@*:
version "1.7.0"
resolved "https://registry.yarnpkg.com/raw-socket/-/raw-socket-1.7.0.tgz#cd0bcc3da52450dcb6b9efe7a6d0890bf1aaf9db"
integrity sha512-mXqWihgwaFNmV5le0dWk5o+03M3A2zBIkC9BNaE6R0CJN9eYot++j2FIqgNSDq6/Vmu32PPI155SiiWNV2yyFQ==
dependencies:
nan "2.14.*"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@ -2982,6 +3008,14 @@ rpi-gpio@^2.1.7:
debug "^3.1.0"
epoll "^2.0.10"
rpi-ws281x-native@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/rpi-ws281x-native/-/rpi-ws281x-native-1.0.4.tgz#df5a50ecfdd165f8a10df891d5eba2cec2577fcb"
integrity sha512-ieClupQGt7PkIIak5bpGaYmymh5nS448LwrkumfwPoju6gTNjlC/GSyBiyq9sFE3FLixzsDhDoYzLQff6y1jXw==
dependencies:
bindings "^1.3.0"
nan "^2.14.2"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"