update fix; etc. tag:V2 Updates

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

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