From 03dd0e0fb69b0b232da049ec1eaaaca744f9767b Mon Sep 17 00:00:00 2001 From: Tobias Hopp Date: Thu, 17 Nov 2022 23:29:33 +0100 Subject: [PATCH] update Took 7 hours 48 minutes --- public/stylesheets/inputs.css | 18 +- public/stylesheets/setup.css | 28 +-- public/stylesheets/style.css | 21 +- src/HX711.ts | 11 + src/SensorType.ts | 5 + src/Settings.ts | 15 +- src/WebSocketEvent.ts | 2 + src/WebSocketHandler.ts | 7 + src/database/Container.ts | 1 + src/database/IContainer.ts | 7 +- src/iTender.ts | 53 ++-- src/main.ts | 3 +- src/routes/ws/websocketRoute.ts | 32 ++- src/web/ButtonType.ts | 1 + src/web/Modal.ts | 8 +- src/web/Setup.ts | 427 +++++++++++++++++++++++++++++++- src/web/WebHandler.ts | 103 -------- src/web/WebWebSocketHandler.ts | 60 +++-- views/index.pug | 49 ++-- views/layout.pug | 2 +- 20 files changed, 642 insertions(+), 211 deletions(-) create mode 100644 src/SensorType.ts diff --git a/public/stylesheets/inputs.css b/public/stylesheets/inputs.css index bfcc263..6d8fd20 100644 --- a/public/stylesheets/inputs.css +++ b/public/stylesheets/inputs.css @@ -1,10 +1,12 @@ .btn { - padding: 12px 16px; + padding: 11px 15px; border: none; color: white; - font-size: 0.95em; + font-size: 0.96em; border-radius: 2px; cursor: none !important; + font-weight: 500; + margin-right: 2%; } @@ -110,10 +112,10 @@ label { .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; + -ms-transform: scale(1.6); /* IE */ + -moz-transform: scale(1.6); /* FF */ + -webkit-transform: scale(1.6); /* Safari and Chrome */ + -o-transform: scale(1.6); /* Opera */ + transform: scale(1.6); + padding: 12px; } diff --git a/public/stylesheets/setup.css b/public/stylesheets/setup.css index afe1549..1db1fe2 100644 --- a/public/stylesheets/setup.css +++ b/public/stylesheets/setup.css @@ -4,34 +4,28 @@ 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%; + grid-template-rows: repeat(9, calc(100% / 9)); + grid-gap: 2% 2%; + color: white; } -#setup_slots { - min-width: 55px; - width: 10%; - margin-left: 3%; - border: 1px solid aliceblue; -} #setup #setupContainersDiv { - grid-row: span 5; + grid-row: span 18; grid-column: span 2; width: 100%; height: 100%; text-align: center; - border: 1px solid cadetblue; + border: 1px solid white; border-radius: 5px; - padding: 1% 10px; + padding: 0.45% 10px; overflow: auto; - } #setup #setupContainersDiv #containerAddBtn { position: relative; - left: 43%; - bottom: 10%; + left: 46%; + bottom: 8%; } #setup #setupLEDDiv { @@ -39,7 +33,7 @@ grid-column: span 1; height: 100%; text-align: center; - border: 1px solid cadetblue; + border: 1px solid white; border-radius: 5px; padding: 1% 10px; margin-right: 10%; @@ -52,7 +46,7 @@ width: 100%; height: 100%; text-align: center; - border: 1px solid cadetblue; + border: 1px solid white; border-radius: 5px; padding: 1% 10px; @@ -73,7 +67,7 @@ border-radius: 10px; padding: 3% 10px; transition: 0.5s; - background-color: rgb(111, 109, 109); + background-color: rgb(135, 133, 133); overflow: hidden; } diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 2e6db84..97a82b6 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -34,10 +34,10 @@ body { } h1 { - font-size: 1.7em; + font-size: 1.74em; font-weight: 500; margin-bottom: 2%; - color: black; + color: white; } @@ -53,12 +53,11 @@ h1 { left: 0; right: 0; height: 9%; - background-color: #1F5E5F; color: white; font-size: 2em; padding-right: 3px; padding-left: 3px; - + background-color: #167fcc; } @@ -94,7 +93,7 @@ h1 { left: 0; right: 0; height: 9%; - background-color: #1F5E5F; + background-color: #167fcc; } @@ -121,10 +120,12 @@ h1 { #container { position: absolute; - top: 10%; - left: 1%; - right: 1%; - height: 80%; + top: 9%; + left: 0; + right: 0; + height: 82%; + color: white; + background-color: #0e1f31; } .pane { @@ -134,7 +135,7 @@ h1 { ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ scroll-behavior: smooth; - color: black; + color: white; } .hiddenPane { diff --git a/src/HX711.ts b/src/HX711.ts index 9d1a491..2e8052b 100644 --- a/src/HX711.ts +++ b/src/HX711.ts @@ -1,3 +1,14 @@ export class HX711 { + private clockPin: number; + private dataPin: number; + + constructor(clockPin: number, dataPin: number) { + this.clockPin = clockPin; + this.dataPin = dataPin; + } + + public measure(): number { + return 0; + } } \ No newline at end of file diff --git a/src/SensorType.ts b/src/SensorType.ts new file mode 100644 index 0000000..5a56449 --- /dev/null +++ b/src/SensorType.ts @@ -0,0 +1,5 @@ +export enum SensorType { + NONE="NONE", + ULTRASOUND="ULTRASOUND", + LOADCELL="LOADCELL" +} \ No newline at end of file diff --git a/src/Settings.ts b/src/Settings.ts index eecea66..1751c54 100644 --- a/src/Settings.ts +++ b/src/Settings.ts @@ -2,12 +2,15 @@ import * as fs from "fs"; import path from "path"; export class Settings { + static get json(): {} { + return this._json; + } static get setupDone(): boolean { return this._setupDone; } private static _setupDone: boolean; - private static json: {}; + private static _json: {}; public static loadSettings() { @@ -17,22 +20,22 @@ export class Settings { } let file = fs.readFileSync(path.join(__dirname, "/config.json")); - this.json = JSON.parse(file.toString("utf8")); + this._json = JSON.parse(file.toString("utf8")); - this._setupDone = this.json["setupDone"]; + this._setupDone = this._json["setupDone"]; this._setupDone = false; } public static saveSettings() { - fs.writeFileSync(path.join(__dirname, "/config.json"), JSON.stringify(this.json)); + fs.writeFileSync(path.join(__dirname, "/config.json"), JSON.stringify(this._json)); } public static get(key: string): any { - return this.json[key]; + return this._json[key]; } public static set(key: string, value: any) { - this.json[key] = value; + this._json[key] = value; } } \ No newline at end of file diff --git a/src/WebSocketEvent.ts b/src/WebSocketEvent.ts index 5dfcfdc..4f2faab 100644 --- a/src/WebSocketEvent.ts +++ b/src/WebSocketEvent.ts @@ -2,4 +2,6 @@ export enum WebSocketEvent { STATUS= "STATUS", DRINKS = "DRINKS", CONTAINERS = "CONTAINERS", + CONFIG = "CONFIG", + TARE = "TARE", } \ No newline at end of file diff --git a/src/WebSocketHandler.ts b/src/WebSocketHandler.ts index f7d6641..16a1cf7 100644 --- a/src/WebSocketHandler.ts +++ b/src/WebSocketHandler.ts @@ -1,6 +1,7 @@ import {WebSocketPayload} from "./WebSocketPayload"; import {WebSocketEvent} from "./WebSocketEvent"; import {iTender} from "./iTender"; +import {Settings} from "./Settings"; export class WebSocketHandler { private static _ws: WebSocket; @@ -33,4 +34,10 @@ export class WebSocketHandler { }); } + static sendRunningConfig() { + return new Promise(resolve => { + let payload = new WebSocketPayload(WebSocketEvent.CONFIG, false, Settings.json); + WebSocketHandler.send(payload).then(resolve); + }) + } } \ No newline at end of file diff --git a/src/database/Container.ts b/src/database/Container.ts index 8091d9f..b6bcbe7 100644 --- a/src/database/Container.ts +++ b/src/database/Container.ts @@ -8,6 +8,7 @@ export const ContainerSchema = new Mongoose.Schema({ sensorType: String, sensorPin1: Number, sensorPin2: Number, + rawData: Number, pumpPin: {type: Number, required: true}, content: {type: mongoose.Types.ObjectId, ref: "Ingredient"}, sensorFilledMax: Number, diff --git a/src/database/IContainer.ts b/src/database/IContainer.ts index 4038023..318830c 100644 --- a/src/database/IContainer.ts +++ b/src/database/IContainer.ts @@ -1,5 +1,6 @@ import {IIngredient} from "./IIngredient"; import * as mongoose from "mongoose"; +import {SensorType} from "../SensorType"; export interface IContainer extends mongoose.Document { slot: number; @@ -7,11 +8,13 @@ export interface IContainer extends mongoose.Document { volume: number; sensorFilledMin: number; sensorFilledMax: number; - sensorType: string; + // Sensor Type + sensorType: SensorType; sensorPin1: number; sensorPin2: number; + rawData: number; pumpPin: number; - filled: Number; + filled: number; enabled: boolean; autoDisabled: boolean; } \ No newline at end of file diff --git a/src/iTender.ts b/src/iTender.ts index 9e3295d..74a7160 100644 --- a/src/iTender.ts +++ b/src/iTender.ts @@ -11,6 +11,7 @@ import {Utils} from "./Utils"; import {WebSocketPayload} from "./WebSocketPayload"; import {WebSocketEvent} from "./WebSocketEvent"; import {HX711} from "./HX711"; +import {SensorType} from "./SensorType"; const log = debug("itender:station"); @@ -35,6 +36,10 @@ export class iTender { return this._internetConnection; } + /** + * @Deprecated + * @private + */ private static _containers: { container: IContainer, sensor: HCSR04 | HX711, pump: null }[] = []; private static _drinks: IDrink[]; @@ -64,28 +69,34 @@ export class iTender { log("Measuring containers..."); return new Promise(async resolve => { - for (let c of this._containers) { + for (let c of (await Container.find({enabled: true}))) { try { - if (!(c.sensor instanceof HX711)) { - let dist = c.sensor.distance(); - c.container.filled = dist * 100 / (c.container.sensorFilledMax + c.container.sensorFilledMin); - } + let rand = Math.random() * 5; + if (c.sensorType == SensorType.ULTRASOUND) { + let sensor = new HCSR04(c.sensorPin1, c.sensorPin2); + c.rawData = sensor.distance(); + c.rawData = rand; + c.filled = c.rawData * c.sensorFilledMin / 100; + + } else if (c.sensorType == SensorType.LOADCELL) { + let sensor = new HX711(c.sensorPin1, c.sensorPin2); + + c.rawData = sensor.measure(); + c.rawData = rand; + c.filled = c.rawData * c.sensorFilledMax / 100; + } } catch (e) { - c.container.filled = -1; + c.filled = -1; + c.rawData = 0; } - await c.container.save(); + await c.save(); } 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); - + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, (await Container.find())); + await WebSocketHandler.send(payload); }); } @@ -117,6 +128,9 @@ export class iTender { }); } + /** + * @Deprecated + */ static refreshContainers(): Promise { log("Refreshing containers..."); this.setStatus(iTenderStatus.CALCULATING); @@ -176,6 +190,7 @@ export class iTender { this._currentJob.endAt = new Date(); await this._currentJob.save(); this._currentJob = null; + this.setStatus(iTenderStatus.READY); } }, 30000); } @@ -196,4 +211,14 @@ export class iTender { }); } + + private static interval; + + public static toggleTare(state: boolean) { + clearInterval(iTender.interval); + if ( state ) + this.interval = setInterval(async () => { + await this.measureContainers(); + }, 500); + } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 75726ad..5b490fc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -69,7 +69,8 @@ function init(): Promise { } // Containers - await iTender.refreshContainers(); + //await iTender.refreshContainers(); + await iTender.measureContainers(); // Drinks await iTender.refreshDrinks(); // Start auto checkup for stuck jobs diff --git a/src/routes/ws/websocketRoute.ts b/src/routes/ws/websocketRoute.ts index 2125de9..16fe7a7 100644 --- a/src/routes/ws/websocketRoute.ts +++ b/src/routes/ws/websocketRoute.ts @@ -4,6 +4,8 @@ import {WebSocketHandler} from "../../WebSocketHandler"; import {iTender} from "../../iTender"; import {iTenderStatus} from "../../iTenderStatus"; import {WebSocketEvent} from "../../WebSocketEvent"; +import Container from "../../database/Container"; +import {SensorType} from "../../SensorType"; const express = require('express'); const router = express.Router(); @@ -14,11 +16,13 @@ const log = debug("itender:websocket"); router.ws('/', async (ws, req, next) => { log("Incoming websocket connection..."); - if (WebSocketHandler.ws) + if (WebSocketHandler.ws) { + iTender.toggleTare(false); WebSocketHandler.ws.close(1001); + } WebSocketHandler.ws = ws; - + await WebSocketHandler.sendRunningConfig(); await WebSocketHandler.sendStatus(); async function sendWhenReady() { @@ -50,7 +54,31 @@ router.ws('/', async (ws, req, next) => { } switch (msg.event) { + case WebSocketEvent.TARE: { + if (msg.data["state"] == true) { + iTender.toggleTare(true); + } else { + iTender.toggleTare(false); + } + break; + } + case WebSocketEvent.CONTAINERS: { + let data = msg.data as { pumpPin: number; sensorType: SensorType; sensor1: number; sensor2: number; volume: number; }[]; + await Container.remove({}); + let i = 0; + for (let c of data) { + let container = new Container(); + container.slot = i; + container.volume = c.volume; + container.sensorType = c.sensorType; + container.sensorPin1 = c.sensor1; + container.sensorPin2 = c.sensor2; + container.enabled = true; + container.autoDisabled = true; + i++; + } + } } }); diff --git a/src/web/ButtonType.ts b/src/web/ButtonType.ts index f9b9309..88b8529 100644 --- a/src/web/ButtonType.ts +++ b/src/web/ButtonType.ts @@ -1,4 +1,5 @@ export enum ButtonType { + PRIMARY = "primary", SUCCESS = "success", ERROR = "error", diff --git a/src/web/Modal.ts b/src/web/Modal.ts index 317b11b..fa040f2 100644 --- a/src/web/Modal.ts +++ b/src/web/Modal.ts @@ -44,7 +44,7 @@ export class Modal { this._loader = value; } - public addButton(type: ButtonType, content: string, onclick: Function): HTMLButtonElement { + public addButton(type: ButtonType, content: string, onclick: Function = () => {}): HTMLButtonElement { let btn = document.createElement("button"); btn.classList.add("btn", "btn-" + type); btn.onclick = () => onclick(btn); @@ -55,6 +55,8 @@ export class Modal { return btn; } + + public open(): Promise { return new Promise(async (resolve) => { /* if (this._leftCentered) { @@ -119,6 +121,10 @@ export class Modal { } + public close() : void { + Modal.close(this._id); + } + public static close(id?: string): void { if (id && this.currentModalId != id) return; diff --git a/src/web/Setup.ts b/src/web/Setup.ts index 9cf0c66..404c906 100644 --- a/src/web/Setup.ts +++ b/src/web/Setup.ts @@ -1,7 +1,428 @@ +import {Pane} from "./Pane"; +import {Modal} from "./Modal"; +import {ButtonType} from "./ButtonType"; +import {WebHandler} from "./WebHandler"; +import {WebSocketPayload} from "../WebSocketPayload"; +import {WebSocketEvent} from "../WebSocketEvent"; +import {WebWebSocketHandler} from "./WebWebSocketHandler"; +import {IContainer} from "../database/IContainer"; +import {SensorType} from "../SensorType"; + + export class Setup { - public static onSetupUpdate() - { + public static onConfigUpdate(payload: WebSocketPayload) { // Setup containers updated - + const ledCheckbox = document.getElementById("ledCheckbox") as HTMLInputElement; + const ledGPIO = document.getElementById("ledGPIO") as HTMLInputElement; + const ambientColor = document.getElementById("ambientColor") as HTMLInputElement; + const allowRemoteCheckbox = document.getElementById("allowRemoteCheckbox") as HTMLInputElement; + const hotspotCheckbox = document.getElementById("hotspotCheckbox") as HTMLInputElement; + + ledCheckbox.checked = !!payload.data["led_enabled"]; + allowRemoteCheckbox.checked = !!payload.data["remote_enabled"]; + hotspotCheckbox.checked = !!payload.data["hotspot_enabled"]; + if (payload.data["led_gpio"]) { + ledGPIO.value = payload.data["led_gpio"]; + } + + if (payload.data["ambient_color"]) { + ambientColor.value = payload.data["ambient_color"]; + } + + } + + static async openSetup() { + // new + WebHandler.openPane(Pane.SETUP); + let menuBtn = document.getElementById("menuBtn") as HTMLButtonElement; + menuBtn.disabled = true; + + const containerAddBtn = document.getElementById("containerAddBtn") as HTMLButtonElement; + containerAddBtn.onclick = Setup.addSetupContainer; + + + const setupSaveBtn = document.getElementById("setup_saveBtn") as HTMLButtonElement; + setupSaveBtn.onclick = () => { + const containers = document.getElementById("setupContainers") as HTMLDivElement; + + let errorModal = new Modal("setup", "Fehler!"); + let ele = document.createElement("p"); + ele.innerHTML = `Das Setup konnte nicht abgeschlossen werden.
`; + errorModal.addContent(ele); + errorModal.addContent(document.createElement("br")); + errorModal.addButton(ButtonType.PRIMARY, "Schließen", () => { + errorModal.close(); + }); + + if (containers.childNodes.length == 0) { + ele.innerHTML += `Es muss mindestens ein Behälter hinzugefügt worden sein.`; + errorModal.open(); + return; + } + + for (let c of containers.getElementsByTagName("div")) { + let selects = c.getElementsByTagName("select"); + if (selects[0].value == "-1") { + ele.innerHTML += `Es müssen alle Pumpen-Pins gesetzt sein.`; + errorModal.open(); + c.classList.add("error"); + setTimeout(() => { + c.classList.remove("error"); + }, 2500); + return; + } + + if (selects[1].value != "-1" && (selects[2].value == "-1" || selects[3].value == "-1")) { + ele.innerHTML += `Wenn ein Sensor-Typ definiert ist, müssen alle Sensor-Pins gesetzt sein.`; + errorModal.open(); + c.classList.add("error"); + setTimeout(() => { + c.classList.remove("error"); + }, 2500); + return; + } + + } + + if (!Setup.checkContainers()) { + ele.innerHTML = `Einige Pins sind doppelt belegt.
Jeder GPIO-Pin kann nur einmal belegt werden!`; + errorModal.open(); + return; + } + + setupSaveBtn.disabled = true; + + let saveModal = new Modal("setup", "Setup"); + let txt = document.createElement("p"); + txt.innerHTML = `Die Einstellungen werden gespeichert...
+
`; + saveModal.addContent(txt); + saveModal.open(); + + const ledCheckbox = document.getElementById("ledCheckbox") as HTMLInputElement; + const ledGPIO = document.getElementById("ledGPIO") as HTMLInputElement; + const ambientColor = document.getElementById("ambientColor") as HTMLInputElement; + const allowRemoteCheckbox = document.getElementById("allowRemoteCheckbox") as HTMLInputElement; + const hotspotCheckbox = document.getElementById("hotspotCheckbox") as HTMLInputElement; + + + let cons: { pumpPin: number; sensorType: SensorType; sensor1: number; sensor2: number; volume: number; }[] = []; + for (let c of (document.getElementById("setupContainers") as HTMLDivElement).getElementsByTagName("div")) { + let sensorType = c.getElementsByTagName("select")[1].value; + let type; + if( sensorType == "-1" ) + type = SensorType.NONE; + else if ( sensorType == "0" ) + type = SensorType.ULTRASOUND; + else + type = SensorType.LOADCELL; + + cons.push({ + "pumpPin": parseInt(c.getElementsByTagName("select")[0].value), + "sensorType": type, + "sensor1": parseInt(c.getElementsByTagName("select")[2].value), + "sensor2": parseInt(c.getElementsByTagName("select")[3].value), + "volume": parseInt(c.getElementsByTagName("select")[4].value) + }); + } + + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, cons); + WebWebSocketHandler.send(payload); + + payload = new WebSocketPayload(WebSocketEvent.CONFIG, false, { + "led_enabled": ledCheckbox.checked, + "remote_enabled": allowRemoteCheckbox.checked, + "hotspot_enabled": hotspotCheckbox.checked, + "led_gpio": parseInt(ledGPIO.value), + "ambient_color": ambientColor.value + }); + menuBtn.disabled = false; + WebWebSocketHandler.send(payload).then(() => { + + setTimeout(() => { + saveModal.close(); + setupSaveBtn.disabled = false; + let tareModal = new Modal("setup", "Einmessung Sensoren"); + let txt = document.createElement("p"); + txt.innerHTML = `Damit alle Sensoren korrekte Werte liefern, sollte eine Einmessung durchgeführt werden.
+Während der Einmessung müssen die Behälter je nachdem geleert, gefüllt oder komplett entnommen werden.

`; + tareModal.addContent(txt); + + tareModal.addButton(ButtonType.PRIMARY, "Später", () => { + tareModal.close(); + }); + + let ul; + tareModal.addButton(ButtonType.PRIMARY, "Starten", async () => { + tareModal.close(); + let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {state: true}); + + let modal = new Modal("tare", "Einmessung"); + + let txt = document.createElement("p"); + txt.innerHTML = `Messung Teil 1
+Bitte alle Behälter entfernen und Sensoren freilegen.
+Die Gewichtssensoren werden beim Bestätigen austariert

Zum fortfahren Tarieren drücken.
`; + modal.addContent(txt); + + ul = document.createElement("ul"); + modal.addContent(ul); + + let btn = document.createElement("button"); + btn.classList.add("btn", "btn-primary"); + btn.innerText = "Tarieren"; + btn.style.marginTop = "3%"; + btn.onclick = () => { + let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {tare: 0}); + WebWebSocketHandler.send(payload); + + txt.innerHTML = `Messung Teil 2
+Bitte nun alle Behälter ohne Inhalt einsetzen.
+Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren Tarieren drücken.
`; + btn.onclick = () => { + let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {tare: 1}); + WebWebSocketHandler.send(payload); + + txt.innerHTML = `Messung Teil 3
+Bitte nun alle Behälter mit vollständigem Inhalt einsetzen.
+Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren Tarieren drücken.
`; + + btn.onclick = () => { + let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {tare: 2}); + WebWebSocketHandler.send(payload); + payload = new WebSocketPayload(WebSocketEvent.TARE, false, {state: false}); + WebWebSocketHandler.send(payload); + + txt.innerHTML = `Alle Werte wurden erfolgreich gespeichert.
Die Einmessung kann jederzeit über die Speichern-Navigation wiederholt werden.

Das Einmessen sollte nach mehrfachem Benutzen wiederholt werden.
`; + btn.innerText = "Schließen"; + btn.onclick = () => modal.close(); + } + + }; + }; + + modal.addContent(btn); + await modal.open(); + await WebWebSocketHandler.send(payload); + }); + + tareModal.open(); + // irgendwie müssen jz die container updates abgegriffen werden + WebWebSocketHandler.tareContainerUpdates = (payload: WebSocketPayload) => { + if (!ul) return; + + ul.innerHTML = ""; + let containers = payload.data as IContainer[]; + for (let c of containers) { + if (c.sensorType == SensorType.NONE) continue; + + let li = document.createElement("li"); + li.innerText = `Behälter ${c.slot}: ${c.rawData} [${c.sensorType}]`; + ul.append(li); + } + } + }, 1000); + + }).catch(() => { + setupSaveBtn.disabled = false; + txt.innerHTML = `Fehler beim Speichern.
iTender hat nicht reagiert.`; + setTimeout(() => saveModal.close(), 2500); + }); + + + } + } + + public static addSetupContainer() { + let setupContainers = document.getElementById("setupContainers") as HTMLDivElement; + + 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 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, 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 nSelect; + + let pumpLabel = document.createElement("label"); + pumpLabel.innerText = "Pumpen Pin"; + con.append(pumpLabel); + nSelect = selectPin.cloneNode(true); + nSelect.onchange = () => Setup.checkContainers(); + nSelect.selectedIndex = 0; + con.append(nSelect); + + con.append(document.createElement("br")); + + // Sensor Art + let sensorTypeLabel = document.createElement("label"); + sensorTypeLabel.innerText = "Sensor Art "; + con.append(sensorTypeLabel); + + let sensorType = document.createElement("select"); + sensorType.classList.add("noCheckup"); + sensorType.classList.add("input"); + let sensorTypeNone = document.createElement("option") as HTMLOptionElement; + sensorTypeNone.innerText = "Kein Sensor"; + sensorTypeNone.value = "-1"; + sensorType.append(sensorTypeNone); + let sensorTypeUltrasound = document.createElement("option") as HTMLOptionElement; + sensorTypeUltrasound.innerText = "Ultraschall"; + sensorTypeUltrasound.value = "0"; + sensorType.append(sensorTypeUltrasound); + let sensorTypeScale = document.createElement("option") as HTMLOptionElement; + sensorTypeScale.innerText = "Wägezelle"; + sensorTypeScale.value = "1"; + sensorType.append(sensorTypeScale); + con.append(sensorType); + con.append(document.createElement("br")); + + // Sensor 1 + let sensor1Label = document.createElement("label"); + sensor1Label.innerText = "Sensor 1 Pin"; + con.append(sensor1Label); + let sensor1Select = selectPin.cloneNode(true) as HTMLSelectElement; + sensor1Select.selectedIndex = 0; + sensor1Select.disabled = true; + sensor1Select.onchange = () => Setup.checkContainers(); + con.append(sensor1Select); + + con.append(document.createElement("br")); + + // Sensor 2 + let sensor2Label = document.createElement("label"); + sensor2Label.innerText = "Sensor 2 Pin"; + con.append(sensor2Label); + let sensor2Select = selectPin.cloneNode(true) as HTMLSelectElement; + sensor2Select.selectedIndex = 0; + sensor2Select.disabled = true; + sensor2Select.onchange = () => Setup.checkContainers(); + con.append(sensor2Select); + + sensorType.onchange = () => { + if (sensorType.value == "0") { + sensor1Label.innerText = "Trigger Pin"; + sensor2Label.innerText = "Trigger Pin"; + sensor1Select.disabled = false; + sensor2Select.disabled = false; + } else if (sensorType.value == "1") { + sensor1Label.innerText = "Clock Pin"; + sensor2Label.innerText = "Data Pin"; + sensor1Select.disabled = false; + sensor2Select.disabled = false; + } else { + sensor1Label.innerText = "Sensor 1 Pin"; + sensor2Label.innerText = "Sensor 2 Pin"; + sensor1Select.disabled = true; + sensor2Select.disabled = true; + } + }; + + con.append(document.createElement("br")); + + + // Volume + let volumeLabel = document.createElement("label"); + volumeLabel.innerText = "Volumen (ml) "; + con.append(volumeLabel); + let volumeSelect = document.createElement("select"); + volumeSelect.classList.add("noCheckup"); + volumeSelect.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; + volumeSelect.append(pinEle); + volumeSelect["volume"] = volumeSelect; + } + volumeSelect.selectedIndex = 7; + con.append(volumeSelect); + + 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); + + + } + + public static checkContainers(): boolean { + console.log("Checking containers...") + let returner = true; + const containers = document.getElementById("setupContainers") as HTMLDivElement; + let setupContainers = containers.getElementsByTagName("div"); + for (let c of setupContainers) { + for (let c2 of setupContainers) { + for (let sel of c.getElementsByTagName("select")) { + if (sel.value == "-1") continue; + if (sel.classList.contains("noCheckup")) continue; + if (sel.disabled) continue; + + + for (let sel2 of c2.getElementsByTagName("select")) { + if (sel2.value == "-1") continue; + if (sel2.disabled) continue; + if (sel == sel2) continue; + if (sel2.classList.contains("noCheckup")) continue; + + + if (sel.value == sel2.value) { + c.classList.add("error"); + c2.classList.add("error"); + sel.classList.add("error"); + sel2.classList.add("error"); + setTimeout(() => { + c.classList.remove("error"); + c2.classList.remove("error"); + sel.classList.remove("error"); + sel2.classList.remove("error"); + }, 2200); + returner = false; + } + } + } + } + } + return returner; } } \ No newline at end of file diff --git a/src/web/WebHandler.ts b/src/web/WebHandler.ts index 367acb2..40f981f 100644 --- a/src/web/WebHandler.ts +++ b/src/web/WebHandler.ts @@ -3,7 +3,6 @@ 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 = []; @@ -63,110 +62,8 @@ ${ingredients}`*/ //todo } 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 diff --git a/src/web/WebWebSocketHandler.ts b/src/web/WebWebSocketHandler.ts index adcc809..7455f20 100644 --- a/src/web/WebWebSocketHandler.ts +++ b/src/web/WebWebSocketHandler.ts @@ -4,17 +4,21 @@ import {WebSocketPayload} from "../WebSocketPayload"; import {ButtonType} from "./ButtonType"; import {iTenderStatus} from "../iTenderStatus"; import {WebHandler} from "./WebHandler"; +import {Setup} from "./Setup"; export class WebWebSocketHandler { - private socket: WebSocket; + private static socket: WebSocket; private static url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005"; + public static tareContainerUpdates: (payload: WebSocketPayload) => void; + + constructor() { - this.socket = new WebSocket(WebWebSocketHandler.url); - this.socket.onopen = this.onOpen; - this.socket.onclose = this.onClose; - this.socket.onerror = this.onError; - this.socket.onmessage = this.onMessage; + WebWebSocketHandler.socket = new WebSocket(WebWebSocketHandler.url); + WebWebSocketHandler.socket.onopen = this.onOpen; + WebWebSocketHandler.socket.onclose = this.onClose; + WebWebSocketHandler.socket.onerror = this.onError; + WebWebSocketHandler.socket.onmessage = this.onMessage; } private onMessage(msgEvent: MessageEvent) { @@ -24,13 +28,15 @@ export class WebWebSocketHandler { console.log("[WS] Could not parse message: ", msgEvent); return; } - console.debug(payload) - - console.debug(payload.event); - console.log("[WS] Received " + payload.event + " Event"); switch (payload.event) { + case WebSocketEvent.CONFIG: { + // Incoming WebSocketStatus + Setup.onConfigUpdate(payload); + break; + } + case WebSocketEvent.STATUS: { let statusElement = document.getElementById("status"); if (statusElement) @@ -64,7 +70,7 @@ export class WebWebSocketHandler { break; } case iTenderStatus.SETUP: { - WebHandler.openSetup(); + Setup.openSetup(); } } break; @@ -100,7 +106,7 @@ export class WebWebSocketHandler { 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.

`; modal.addContent(txt); - modal.addButton(ButtonType.SUCCESS, "Sitzung wiederherstellen", () => { + modal.addButton(ButtonType.PRIMARY, "Sitzung wiederherstellen", () => { window.location.reload(); }); modal.open(); @@ -116,11 +122,11 @@ export class WebWebSocketHandler { }, 5000); } - /* let connectionElement = document.getElementById("right"); - if (connectionElement) { - connectionElement.innerText = "Getrennt"; - connectionElement.style.color = "red"; - }*/ + /* let connectionElement = document.getElementById("right"); + if (connectionElement) { + connectionElement.innerText = "Getrennt"; + connectionElement.style.color = "red"; + }*/ } private onError(event) { @@ -131,4 +137,24 @@ export class WebWebSocketHandler { //openModal("Einen Augenblick...", `Es wurde ein kritischer Fehler festgestellt.\nBitte warten Sie, während der Prozess neu gestartet wird...` ); //window.location.reload(); } + + public static send(payload: WebSocketPayload): Promise { + return new Promise(async (resolve, reject) => { + try { + if (this.socket && this.socket.readyState == 1) { + await this.socket.send(payload.toString()); + resolve(); + } + } catch (e) { + /*let modal = new Modal("error", "Verbindungsfehler"); + let txt = document.createElement("p"); + txt.innerHTML = `Beim Austausch von Informationen ist ein Problem aufgetreten.
Falls dies öfter passieren sollte, ist der Support zu kontaktieren.`; + modal.addContent(txt); + await modal.open();*/ + reject(); + } + }); + } + + } \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 34911fa..1f86e33 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,6 +1,28 @@ extends layout block setup + div#setupLEDDiv + h1 LED-Einstellungen + div.inputGroup + label(onclick="document.getElementById('ledCheckbox').checked = !document.getElementById('ledCheckbox').checked;") Aktivieren + input#ledCheckbox.input(type="checkbox") + div.inputGroup + label GPIO-Pin + input#ledGPIO.input(type="number" value="22" style="width:15%" disabled="disabled") + div.inputGroup + label Ambiente Farbe + input#ambientColor.input(type="color" value="#05445E" style="width:15%" disabled="disabled") + + div#setupExtraDiv + h1 Erweiterte Einstellungen + div.inputGroup + label(onclick="document.getElementById('allowRemoteCheckbox').checked = !document.getElementById('allowRemoteCheckbox').checked;") Remote-Verbindungen erlauben + input#allowRemoteCheckbox.input(type="checkbox") + div.inputGroup + label(onclick="document.getElementById('hotspotCheckbox').checked = !document.getElementById('hotspotCheckbox').checked;") Ohne WiFi Hotspot aktivieren + input#hotspotCheckbox.input(type="checkbox") + + div#setupContainersDiv h1 Behälter // Setup @@ -9,32 +31,7 @@ block setup 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 + button.btn.btn-success#setup_saveBtn(style="grid-row: span 1; grid-column: span 2; border-radius: 15px; font-size: 1.2em;") Speichern block menu // Menu diff --git a/views/layout.pug b/views/layout.pug index 77361e9..3b00db5 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -14,7 +14,7 @@ html div#top span#left Status: ... span#title iTender - span#right Verbinden... + span#right 00:00 div#bottom button.btn#menuBtn Menü div#containers