update fix; etc. tag:V2 Updates

Took 1 hour 16 minutes
This commit is contained in:
Tobias Hopp 2023-01-09 23:07:13 +01:00
parent 9c63516ab8
commit c509fb2bf7
12 changed files with 137 additions and 74 deletions

View File

@ -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
- 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

45
src/SensorHelper.ts Normal file
View File

@ -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;
}
}

View File

@ -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);
})
}

View File

@ -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[<keyof typeof 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;

View File

@ -11,8 +11,8 @@ export const ContainerSchema = new Mongoose.Schema<IContainer>({
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},
});

View File

@ -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;

View File

@ -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.<br>Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.<br>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.<br>Bitte später erneut versuchen!"));
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Beim Aktualisieren der Getränke ist ein Netzwerk-Fehler aufgetreten.<br>Bitte später erneut versuchen!"));
}
iTender.setStatus(iTenderStatus.READY);

View File

@ -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.<br>Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.<br>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();

View File

@ -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

View File

@ -66,7 +66,7 @@ Dort werden die Behälter definiert, welche in den iTender gestellt werden.<br>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.<br>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<br><br>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<br>
Bitte nun alle <strong>Behälter mit Inhalt füllen</strong> und wieder einsetzen.<br>
Die Gewichtssensoren werden beim Bestätigen austariert.<br><br>Zum fortfahren Tarieren drücken.<br>`;
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.<br><br>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.<br><br>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.<br><br>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.<br><br>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);

View File

@ -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
}));

View File

@ -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);
}