From c509fb2bf745c6c68d1764a7677cb60d9171aa77 Mon Sep 17 00:00:00 2001 From: Tobias Hopp Date: Mon, 9 Jan 2023 23:07:13 +0100 Subject: [PATCH] update fix; etc. tag:V2 Updates Took 1 hour 16 minutes --- ToDo.md | 3 +- src/SensorHelper.ts | 45 +++++++++++++++++++ src/WebSocketHandler.ts | 12 ++--- src/WebSocketPayload.ts | 17 +++---- src/database/Container.ts | 4 +- src/database/IContainer.ts | 4 +- src/iTender.ts | 78 ++++++++++++++++++--------------- src/routes/ws/websocketRoute.ts | 25 +++++++++-- src/web/Containers.ts | 2 +- src/web/Setup.ts | 17 ++++--- src/web/WebWebSocketHandler.ts | 2 +- src/web/main.ts | 2 +- 12 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 src/SensorHelper.ts diff --git a/ToDo.md b/ToDo.md index 0745bf7..6f91505 100644 --- a/ToDo.md +++ b/ToDo.md @@ -19,4 +19,5 @@ - Ultraschallsensor wird entfernt - Wenn Sensorik für Behälter vorhanden ist, nutze Wägezelle des Containers und messe anhand dessen Inhaltsmenge - 1G=1ML -- Bei Einstellung neues Inhalts wird das Gewicht als ml übersetzt, danach wird Gewicht-Eingestellte Millitier gerechnet, das Ergebnis ist das Gewicht des Behälters \ No newline at end of file +- Bei Einstellung neues Inhalts wird das Gewicht als ml übersetzt, danach wird Gewicht-Eingestellte Millitier gerechnet, das Ergebnis ist das Gewicht des Behälters +- Da nun generell eine Fehlermeldung erscheint, sobald eine Wägezelle inkorrekt läuft, muss vor dem Tarieren (also beim Drücken von Speichern) erst eine CHECK request gesendet werden, danach folgt dann bei erfolgreich die Tarierung \ No newline at end of file diff --git a/src/SensorHelper.ts b/src/SensorHelper.ts new file mode 100644 index 0000000..f750e91 --- /dev/null +++ b/src/SensorHelper.ts @@ -0,0 +1,45 @@ +import {IContainer} from "./database/IContainer"; +import {SensorType} from "./SensorType"; +import {HX711} from "./HX711"; +import debug from "debug"; + +const log = debug("itender:sensor"); + +export class SensorHelper { + + /** + * Returns the current container weight + * @param container + */ + static measure(container: IContainer): number | null { + if (container.sensorType == SensorType.LOADCELL) { + try { + // V2: Measure weight + let sensor = new HX711(container.sensorPin1, container.sensorPin2); + + container.rawData = sensor.measure(); + } catch (e) { + log("Sensor (Weight cell) of container " + container._id + " is broken or has malfunction - Removing it!"); + container.sensorType = SensorType.NONE; + container.save(); + return null; + } + } else if (container.sensorType == SensorType.ULTRASOUND) { + try { + // V2: Measure weight + let sensor = new HX711(container.sensorPin1, container.sensorPin2); + + container.rawData = sensor.measure(); + + } catch (e) { + log("Sensor (Ultrasound) of container " + container._id + " is broken or has malfunction - Removing it!"); + container.sensorType = SensorType.NONE; + container.save(); + return null; + } + } + + // todo Überprüfen ob hier Umrechnungen nötig sind. Soll in Gramm zurück gegeben werden + return container.rawData; + } +} \ No newline at end of file diff --git a/src/WebSocketHandler.ts b/src/WebSocketHandler.ts index 1b36826..ec3bf3a 100644 --- a/src/WebSocketHandler.ts +++ b/src/WebSocketHandler.ts @@ -38,21 +38,21 @@ export class WebSocketHandler { } public static answerRequest(type: RequestType, content: any) { - WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.RESPONSE, false, {type: type, content: content})); + WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.RESPONSE, {type: type, content: content})); log("Answered " + type + " request"); } public static sendStatus() { return new Promise(resolve => { - let payload = new WebSocketPayload(WebSocketEvent.STATUS, false, {status: iTender.status}); + let payload = new WebSocketPayload(WebSocketEvent.STATUS, {status: iTender.status}); WebSocketHandler.send(payload).then(resolve); }); } static sendRunningConfig() { return new Promise(resolve => { - let payload = new WebSocketPayload(WebSocketEvent.CONFIG, false, Settings.json); + let payload = new WebSocketPayload(WebSocketEvent.CONFIG, Settings.json); WebSocketHandler.send(payload).then(resolve); }); } @@ -82,7 +82,7 @@ export class WebSocketHandler { "count_ingredients": (await Ingredient.countDocuments()), "count_cocktails": (await Drink.countDocuments()) }; - let payload = new WebSocketPayload(WebSocketEvent.RESPONSE, false, { + let payload = new WebSocketPayload(WebSocketEvent.RESPONSE, { type: RequestType.STATS, content: stats }); @@ -92,14 +92,14 @@ export class WebSocketHandler { static sendContainers() { return new Promise(async resolve => { - let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, (await Container.find())); + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, (await Container.find())); WebSocketHandler.send(payload).then(resolve); }) } static sendDrinks() { return new Promise(async resolve => { - let payload = new WebSocketPayload(WebSocketEvent.DRINKS, false, iTender.drinks); + let payload = new WebSocketPayload(WebSocketEvent.DRINKS, iTender.drinks); WebSocketHandler.send(payload).then(resolve); }) } diff --git a/src/WebSocketPayload.ts b/src/WebSocketPayload.ts index fe97ab5..0118c75 100644 --- a/src/WebSocketPayload.ts +++ b/src/WebSocketPayload.ts @@ -6,16 +6,12 @@ export class WebSocketPayload { this._event = value; } - set error(value: boolean) { - this._error = value; - } set data(value: any | undefined) { this._data = value; } private _event: WebSocketEvent; - private _error: boolean; private _data: any | undefined; @@ -23,24 +19,21 @@ export class WebSocketPayload { return this._event; } - get error(): boolean { - return this._error; - } + get data(): any | undefined { return this._data; } - constructor(event: WebSocketEvent, error: boolean = false, data?: any) { + constructor(event: WebSocketEvent, data?: any) { this._event = event; - this._error = error; this._data = data; } public static parseFromBase64Json(json: string): WebSocketPayload | null { //json = (typeof window != 'undefined') ? atob(json).toString() : Buffer.from(json, "base64").toString("utf-8"); json = Buffer.from(json, "base64").toString("utf-8"); - let rawPayload: { event: string, error: boolean, data: any }; + let rawPayload: { event: string, data: any }; try { rawPayload = JSON.parse(json); } catch (e) { @@ -49,14 +42,14 @@ export class WebSocketPayload { let wsEvent = WebSocketEvent[rawPayload.event]; - return new WebSocketPayload(wsEvent, rawPayload.error, rawPayload.data); + return new WebSocketPayload(wsEvent, rawPayload.data); } /** * Returns the payload as base64 encoded json string */ public toString(): string { - let json = JSON.stringify({"event": this._event, status: this._error, data: this._data}); + let json = JSON.stringify({"event": this._event, data: this._data}); json = ((typeof window != 'undefined') ? btoa(json) : Buffer.from(json).toString("base64")); return json; diff --git a/src/database/Container.ts b/src/database/Container.ts index da9f5c3..ecaefca 100644 --- a/src/database/Container.ts +++ b/src/database/Container.ts @@ -11,8 +11,8 @@ export const ContainerSchema = new Mongoose.Schema({ rawData: Number, pumpPin: {type: Number, required: true}, content: {type: mongoose.Types.ObjectId, ref: "Ingredient"}, - sensorFilledMax: Number, - sensorFilledMin: Number, + sensorDelta: Number, // V2: Now sensorDelta - Differenz, welche beim Einstellen der Zutat aus Gewicht(Sensor) - Volumen errechnet wird + sensorTare: Number, // V2: Now sensorTare filled: Number, enabled: {type: Boolean, default: false}, }); diff --git a/src/database/IContainer.ts b/src/database/IContainer.ts index 1a24032..532b258 100644 --- a/src/database/IContainer.ts +++ b/src/database/IContainer.ts @@ -6,8 +6,8 @@ export interface IContainer extends mongoose.Document { slot: number; content: IIngredient | undefined; volume: number; - sensorFilledMin: number; - sensorFilledMax: number; + sensorDelta: number; + sensorTare: number; // Sensor Type sensorType: SensorType; sensorPin1: number; diff --git a/src/iTender.ts b/src/iTender.ts index 7c874d8..4ebd5b3 100644 --- a/src/iTender.ts +++ b/src/iTender.ts @@ -1,6 +1,5 @@ import {iTenderStatus} from "./iTenderStatus"; import Container from "./database/Container"; -import {HCSR04} from "hc-sr04"; import {IContainer} from "./database/IContainer"; import Drink from "./database/Drink"; import {IDrink} from "./database/IDrink"; @@ -10,8 +9,6 @@ import {IJob} from "./database/IJob"; import {Utils} from "./Utils"; import {WebSocketPayload} from "./WebSocketPayload"; import {WebSocketEvent} from "./WebSocketEvent"; -import {HX711} from "./HX711"; -import {SensorType} from "./SensorType"; import Job from "./database/Job"; import {IIngredient} from "./database/IIngredient"; import Ingredient from "./database/Ingredient"; @@ -20,6 +17,8 @@ import {RejectReason} from "./RejectReason"; import axios from "axios"; import GPIO from "rpi-gpio"; import {MyGPIO} from "./MyGPIO"; +import {SensorHelper} from "./SensorHelper"; +import {SensorType} from "./SensorType"; const isPI = require("detect-rpi"); @@ -49,6 +48,8 @@ export class iTender { private static _jobCheckInterval: NodeJS.Timer; private static _internetConnection: boolean = false; + private static _jobTimers: NodeJS.Timeout[] = []; + /** * Returns true if internet connection is active */ @@ -161,7 +162,7 @@ export class iTender { this._currentJob = job; iTender.setStatus(iTenderStatus.FILLING); - let timers: NodeJS.Timeout[] = []; + for (let x of job.amounts) { // Start pump here @@ -172,12 +173,12 @@ export class iTender { } catch (e) { if (isPI()) { log("[ERROR] GPIO I/O Error " + e); + // Todo error handling to user await iTender.cancelFill(); return; } else { log("[WARNING] GPIO I/O Error, but it's normal cause you are not on raspberry"); } - } @@ -185,14 +186,13 @@ export class iTender { mixLog(`Starting output of pump ${x.container.pumpPin}`); //mixLog(x.ingredient + " takes " + (waitTime / 1000) + "s for " + x.amount + "ml"); let timer = setTimeout(async () => { - - // Remove from list of timers let arr: NodeJS.Timer[] = []; - for (let i = 0; i < timers.length; i++) { - if (timers[i] != timer) - arr.push(timers[i]); + for (let i = 0; i < this._jobTimers.length; i++) { + if (this._jobTimers[i] != timer) + arr.push(this._jobTimers[i]); } + mixLog(`Stopping output of pump ${x.container.pumpPin}`); // Stop pump here try { @@ -207,15 +207,21 @@ export class iTender { } } - timers = arr; + if (x.container.sensorType == SensorType.NONE) { + // V2: Manual measuring + x.container.filled = x.container.filled - x.amount; + await x.container.save(); + } + + this._jobTimers = arr; }, waitTime); - timers.push(timer); + this._jobTimers.push(timer); } iTender._jobCheckInterval = setInterval(async () => { - if (timers.length != 0) + if (this._jobTimers.length != 0) return; clearInterval(iTender._jobCheckInterval); @@ -235,19 +241,30 @@ export class iTender { static async cancelFill() { if (!this._currentJob || this.status != iTenderStatus.FILLING) return; + clearInterval(this._jobCheckInterval); this._currentJob.successful = false; this._currentJob.endAt = new Date(); await this._currentJob.save(); + for (let timer of this._jobTimers) { + // Clears all the ongoing stop timers + clearTimeout(timer); + } - for (let x of this._currentJob.amounts) { + for (let jobIngredient of this._currentJob.amounts) { // stop pump pin try { - await MyGPIO.write(x.container.pumpPin, false); + await MyGPIO.write(jobIngredient.container.pumpPin, false); } catch (e) { } + + // ToDo + let container: IContainer = jobIngredient.container; + let deltaStartStop = (this._currentJob.endAt.getTime() - this._currentJob.startedAt.getTime()) / 1000; + container.filled = container.filled - ( jobIngredient.amount * (deltaStartStop / ((jobIngredient.amount / 100) * iTender.secondsPer100ml)) ); // V2: Near the current fill value based on time values from delta start stop // todo fixme + container.save().then(); } iTender.setStatus(iTenderStatus.READY); @@ -263,35 +280,24 @@ export class iTender { return new Promise(async resolve => { for (let c of (await Container.find({}))) { - try { - 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; - } else if (c.sensorType == SensorType.LOADCELL) { - let sensor = new HX711(c.sensorPin1, c.sensorPin2); - - c.rawData = sensor.measure(); - c.rawData = rand; + if (c.sensorType != SensorType.NONE) { + let weight = SensorHelper.measure(c); + if (!weight) { + await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Ein Sensor hat beim Austarieren einen ungültigen Wert zurückgegeben.
Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.
Aus Sicherheitsgründen wurde der Sensor für diesen Behälter deaktiviert.")); + continue; } - if (c.sensorFilledMax && c.sensorFilledMin) { - c.filled = c.rawData * c.sensorFilledMax / 100; - } + // V2: New calculation method + c.filled = weight - c.sensorDelta; // V2: Testing - } catch (e) { - c.filled = -1; - c.rawData = 0; + await c.save(); } - await c.save(); } log("Containers measured!"); resolve(); - let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, (await Container.find())); + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, (await Container.find())); await WebSocketHandler.send(payload); }); } @@ -448,7 +454,7 @@ export class iTender { } catch (e) { console.error("Could not refresh drinks " + e); - await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, false, "Beim aktualisieren der Getränke ist ein Netzwerk-Fehler aufgetreten.
Bitte später erneut versuchen!")); + await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Beim Aktualisieren der Getränke ist ein Netzwerk-Fehler aufgetreten.
Bitte später erneut versuchen!")); } iTender.setStatus(iTenderStatus.READY); diff --git a/src/routes/ws/websocketRoute.ts b/src/routes/ws/websocketRoute.ts index c5b338f..a36594c 100644 --- a/src/routes/ws/websocketRoute.ts +++ b/src/routes/ws/websocketRoute.ts @@ -10,6 +10,8 @@ import {Settings} from "../../Settings"; import Ingredient from "../../database/Ingredient"; import {RequestType} from "../../RequestType"; import {IJob} from "../../database/IJob"; +import {SensorHelper} from "../../SensorHelper"; +import {IContainer} from "../../database/IContainer"; const express = require('express'); const router = express.Router(); @@ -71,7 +73,7 @@ router.ws('/', async (ws, req, next) => { } case WebSocketEvent.CONTAINER_UPDATE: { - let container = await Container.findById(msg.data["container"]); + let container : IContainer | null = await Container.findById(msg.data["container"]); if (!container) break; let ingredient; @@ -81,8 +83,25 @@ router.ws('/', async (ws, req, next) => { ingredient = undefined; } - container.filled = parseInt(msg.data["filled"]); - container.volume = parseInt(msg.data["filled"]); // V2: Volume is now being updated after change of ingredient + let filled : number = parseInt(msg.data["filled"]); + + container.filled = filled; + container.volume = filled; // V2: Volume is now being updated after change of ingredient + + if( container.sensorType != SensorType.NONE ) + { + let raw = SensorHelper.measure(container); + if( !raw ) + { + await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der Sensor hat beim Austarieren einen ungültigen Wert zurückgegeben.
Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.
Aus Sicherheitsgründen wurde der Sensor für diesen Behälter deaktiviert." )); + } + else + { + container.sensorDelta = raw - filled; // V2: Kalkuliere differenz zwischen Gewicht und gefülltem Inhalt // Todo Möglicherweise ist der "raw"-Wert nicht Gewicht + } + } + + container.content = ingredient; await container.save(); diff --git a/src/web/Containers.ts b/src/web/Containers.ts index a2e1d9b..e9fcc45 100644 --- a/src/web/Containers.ts +++ b/src/web/Containers.ts @@ -150,7 +150,7 @@ export class Containers { if (selectIngredient.value == "null") volumeSlider.value = "0"; - let payload = new WebSocketPayload(WebSocketEvent.CONTAINER_UPDATE, false, { + let payload = new WebSocketPayload(WebSocketEvent.CONTAINER_UPDATE, { container: selectContainer.value, ingredient: (selectIngredient.value == "null") ? null : selectIngredient.value, filled: volumeSlider.value diff --git a/src/web/Setup.ts b/src/web/Setup.ts index 553320f..1eacaa7 100644 --- a/src/web/Setup.ts +++ b/src/web/Setup.ts @@ -66,7 +66,7 @@ Dort werden die Behälter definiert, welche in den iTender gestellt werden.
D const cancelBtn = document.getElementById("setup_cancelBtn") as HTMLButtonElement; cancelBtn.onclick = () => { - let payload = new WebSocketPayload(WebSocketEvent.SETUP, false, false); + let payload = new WebSocketPayload(WebSocketEvent.SETUP, false); WebWebSocketHandler.send(payload); } @@ -159,10 +159,10 @@ Dort werden die Behälter definiert, welche in den iTender gestellt werden.
D }); } - let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, false, cons); + let payload = new WebSocketPayload(WebSocketEvent.CONTAINERS, cons); WebWebSocketHandler.send(payload); - payload = new WebSocketPayload(WebSocketEvent.CONFIG, false, { + payload = new WebSocketPayload(WebSocketEvent.CONFIG, { "led_enabled": ledCheckbox.checked, "remote_enabled": allowRemoteCheckbox.checked, "hotspot_enabled": hotspotCheckbox.checked, @@ -221,14 +221,14 @@ Die Gewichtssensoren werden beim Bestätigen austariert

Zum fortfahren Ta btn.innerText = "Tarieren"; btn.style.marginTop = "3%"; btn.onclick = () => { - let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {tare: 0}); + let payload = new WebSocketPayload(WebSocketEvent.TARE, {tare: 0}); WebWebSocketHandler.send(payload); txt.innerHTML = `Messung Teil 2
Bitte nun alle Behälter mit Inhalt füllen und wieder einsetzen.
Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren Tarieren drücken.
`; btn.onclick = () => { - let payload = new WebSocketPayload(WebSocketEvent.TARE, false, {tare: 1}); + let payload = new WebSocketPayload(WebSocketEvent.TARE, {tare: 1}); WebWebSocketHandler.send(payload); btn.onclick = () => { @@ -319,7 +319,7 @@ Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren T let sensorTypeUltrasound = document.createElement("option") as HTMLOptionElement; sensorTypeUltrasound.innerText = "Ultraschall"; sensorTypeUltrasound.value = "0"; - sensorType.append(sensorTypeUltrasound); + //sensorType.append(sensorTypeUltrasound); // V2: Removed let sensorTypeScale = document.createElement("option") as HTMLOptionElement; sensorTypeScale.innerText = "Wägezelle"; sensorTypeScale.value = "1"; @@ -374,7 +374,7 @@ Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren T // Volume let volumeLabel = document.createElement("label"); volumeLabel.innerText = "Volumen (ml) "; - con.append(volumeLabel); + //con.append(volumeLabel); let volumeSelect = document.createElement("select"); volumeSelect.classList.add("noCheckup"); volumeSelect.classList.add("input"); @@ -387,7 +387,7 @@ Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren T volumeSelect["volume"] = volumeSelect; } volumeSelect.selectedIndex = 7; - con.append(volumeSelect); + //con.append(volumeSelect); let removeBtn = document.createElement("button"); removeBtn.classList.add("btn", "btn-danger"); @@ -440,7 +440,6 @@ Die Gewichtssensoren werden beim Bestätigen austariert.

Zum fortfahren T (selects[1] as HTMLSelectElement).value = type; (selects[2] as HTMLSelectElement).value = c.sensorPin1.toString(); (selects[3] as HTMLSelectElement).value = c.sensorPin2.toString(); - (selects[4] as HTMLSelectElement).value = c.volume.toString(); let event = new Event('change', {bubbles: true}); selects[1].dispatchEvent(event); diff --git a/src/web/WebWebSocketHandler.ts b/src/web/WebWebSocketHandler.ts index b3c3c53..1fa238a 100644 --- a/src/web/WebWebSocketHandler.ts +++ b/src/web/WebWebSocketHandler.ts @@ -222,7 +222,7 @@ export class WebWebSocketHandler { resolve(payload); } }); - WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.REQUEST, false, { + WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.REQUEST, { type: type, content: content })); diff --git a/src/web/main.ts b/src/web/main.ts index a3a412e..cbe4774 100644 --- a/src/web/main.ts +++ b/src/web/main.ts @@ -102,7 +102,7 @@ function setupOnClickEvents() { menuSettingsBtn.onclick = () => WebHandler.openPane(Pane.SETTINGS); menuSetupBtn.onclick = () => { - let payload = new WebSocketPayload(WebSocketEvent.SETUP, false, true); + let payload = new WebSocketPayload(WebSocketEvent.SETUP, true); WebWebSocketHandler.send(payload); }