Update
Took 2 hours 20 minutes
This commit is contained in:
parent
bdd0a9f4f6
commit
b05b111140
@ -199,6 +199,7 @@ Programmed by Tobias Hopp" >/etc/motd
|
||||
echo "[Service]
|
||||
ExecStart=/usr/sbin/dhcpcd -q" >/etc/systemd/system/dhcpcd.service.d/wait.conf
|
||||
|
||||
|
||||
chown itender:itender -R /home/itender/
|
||||
|
||||
echo "Installation finished!"
|
||||
|
@ -26,7 +26,7 @@
|
||||
"@types/rpi-ws281x-native": "^1.0.0",
|
||||
"@types/serialport": "^8.0.2",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"axios": "^1.2.0",
|
||||
"axios": "^1.3.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"debug": "^4.3.4",
|
||||
|
BIN
public/static/large.png
Normal file
BIN
public/static/large.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
BIN
public/static/normal.png
Normal file
BIN
public/static/normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
public/static/shot.png
Normal file
BIN
public/static/shot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 KiB |
BIN
public/static/small.png
Normal file
BIN
public/static/small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
@ -80,6 +80,8 @@ body {
|
||||
background-position: center;
|
||||
background-size: auto 83%;
|
||||
margin: auto auto 2%;
|
||||
color:white;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.water::before {
|
||||
@ -136,5 +138,6 @@ body {
|
||||
}
|
||||
|
||||
#main_fillTxt {
|
||||
margin-bottom: 3%;
|
||||
margin-bottom: 1.3%;
|
||||
margin-top:1.2%;
|
||||
}
|
14
public_key.pem
Normal file
14
public_key.pem
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3x3RpWBFx0LdBmW2Dspz
|
||||
s5rigcjZLUVP9U8fJrtSqG79EmSXSNBOrNJpJnokWEDmNjXvHSCXpzuAGOQkYqbs
|
||||
Z6o8g+OTK4LPd3J0IZeo7Y8NGerb15mXttR6wvEMmusFtp5J/wm7XYzUQADlvgKc
|
||||
cgbi0+/A0Vf7jCmzRPsw/foKPh6UiElsvZJTzzCzuADohb53U9aIerx2akhR1YnN
|
||||
2I/kgxhJ0ro+HZule0bEbJ7ZdDvhNMnXdNyaiotpb34q8EByjfhI663pvXAorFu4
|
||||
9Yiejl3SfI9/e9xhh7Y6MWMFAVzSv3TTIZMbmjX22fAffK8nO4SbAdGBrCM2k2dE
|
||||
7HURS9/3iAgBFQcLFA6OS2HKX8FjfExv7pc9b5ROPlcbcJ2jFAOue7ZMcNQVByqa
|
||||
vA7PF+9lydCNOyHfRo2OTkqZRljIad27p92mX049U2AvBfODoHTvWSwVy7/3DTPd
|
||||
HWdGFvV5dbazE25NmwjEcJ50sXLhPXv9rzij3mxY7j1c6bVd+6v7Dds7jUYsbE6o
|
||||
MCnaetSRMITGohfhtwvS4kbt4pGOzZ73T/XRfdmR5bnWubx5bgwgaBMhAJnUF346
|
||||
0uJnYY/ij+bCa+NJpUCegoudQ2PPmMxcTLs527EGbNFyUXfogLbzqr5XUOJIvgHK
|
||||
sfaW7BSbcB4xTPvfDuLIhA8CAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
32
src/Encrypter.ts
Normal file
32
src/Encrypter.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
export class Encrypter {
|
||||
private readonly algorithm: string;
|
||||
private readonly key: Buffer;
|
||||
|
||||
constructor(encryptionKey) {
|
||||
this.algorithm = "aes-192-cbc";
|
||||
this.key = crypto.scryptSync(encryptionKey, "salt", 24);
|
||||
}
|
||||
|
||||
public encrypt(clearText) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
|
||||
const encrypted = cipher.update(clearText, "utf8", "hex");
|
||||
return [
|
||||
encrypted + cipher.final("hex"),
|
||||
Buffer.from(iv).toString("hex"),
|
||||
].join("|");
|
||||
}
|
||||
|
||||
public decrypt(encryptedText) {
|
||||
const [encrypted, iv] = encryptedText.split("|");
|
||||
if (!iv) throw new Error("IV not found");
|
||||
const decipher = crypto.createDecipheriv(
|
||||
this.algorithm,
|
||||
this.key,
|
||||
Buffer.from(iv, "hex")
|
||||
);
|
||||
return decipher.update(encrypted, "hex", "utf8") + decipher.final("utf8");
|
||||
}
|
||||
}
|
43
src/ErrorHandler.ts
Normal file
43
src/ErrorHandler.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import axios from "axios";
|
||||
import {Encrypter} from "./Encrypter";
|
||||
|
||||
export class ErrorHandler {
|
||||
|
||||
public static sendError(error: InternalError) {
|
||||
let encrypter = new Encrypter("N50LtuKpzOvxp44vaYBFXBQo1tubTY");
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let encrypted = Buffer.from(encrypter.encrypt(error.toJson())).toString("base64");
|
||||
|
||||
axios.post('https://itender.iif.li/report/send', encrypted, {headers: {"Content-Type": "text/plain", Accept: "application/json"}} ).then(res => {
|
||||
if( res.status != 200 )
|
||||
reject();
|
||||
else
|
||||
return resolve();
|
||||
|
||||
console.log("Error report was sent to iTender Manager");
|
||||
}).catch((e) => reject(e));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalError {
|
||||
private readonly message: string;
|
||||
private readonly stack: string;
|
||||
private readonly name: string;
|
||||
|
||||
constructor(message: string, stack: string|undefined, name?: string) {
|
||||
this.message = message;
|
||||
this.stack = stack ? stack : "";
|
||||
this.name = name ? name : "";
|
||||
}
|
||||
|
||||
public toJson(): string {
|
||||
return JSON.stringify({
|
||||
message: this.message,
|
||||
stack: this.stack,
|
||||
name: this.name,
|
||||
date: new Date().toDateString()
|
||||
})
|
||||
}
|
||||
}
|
97
src/Mixer.ts
97
src/Mixer.ts
@ -9,7 +9,6 @@ import {iTender} from "./iTender";
|
||||
import debug from "debug";
|
||||
import {ArduinoProxyPayload} from "./ArduinoProxyPayload";
|
||||
import {ArduinoProxyPayloadType} from "./ArduinoProxyPayloadType";
|
||||
import {ArduinoProxy} from "./ArduinoProxy";
|
||||
import {ContainerHelper} from "./ContainerHelper";
|
||||
|
||||
const isPI = require("detect-rpi");
|
||||
@ -17,15 +16,16 @@ const isPI = require("detect-rpi");
|
||||
const log = debug("itender:mix");
|
||||
|
||||
export class Mixer {
|
||||
static get currentJob(): IJob {
|
||||
return this._currentJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timers for the job, for the pumps etc.
|
||||
* @private
|
||||
*/
|
||||
private static _jobTimers: NodeJS.Timeout[] = [];
|
||||
/**
|
||||
* Checks if the job has finished every 500ms
|
||||
* @private
|
||||
*/
|
||||
private static _jobEndCheckInterval: NodeJS.Timer;
|
||||
|
||||
/**
|
||||
* The current itender job
|
||||
@ -33,12 +33,9 @@ export class Mixer {
|
||||
*/
|
||||
private static _currentJob: IJob;
|
||||
|
||||
/**
|
||||
* Checks if the job has finished every 500ms
|
||||
* @private
|
||||
*/
|
||||
private static _jobEndCheckInterval: NodeJS.Timer;
|
||||
|
||||
static get currentJob(): IJob {
|
||||
return this._currentJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the internal fill method, a sub-method of the onReceiveFill method
|
||||
@ -124,14 +121,13 @@ export class Mixer {
|
||||
await x.container.save();
|
||||
}
|
||||
|
||||
this._jobTimers.splice(this._jobTimers.indexOf(timer),1);
|
||||
this._jobTimers.splice(this._jobTimers.indexOf(timer), 1);
|
||||
|
||||
}, waitTime);
|
||||
this._jobTimers.push(timer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this._jobEndCheckInterval = setInterval(async () => {
|
||||
if (this._jobTimers.length != 0)
|
||||
return;
|
||||
@ -153,47 +149,52 @@ export class Mixer {
|
||||
/**
|
||||
* Cancel the fill instantly
|
||||
*/
|
||||
static async cancelFill() {
|
||||
if (!this._currentJob || iTender.status != iTenderStatus.FILLING)
|
||||
return;
|
||||
static cancelFill() {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
if (!this._currentJob || iTender.status != iTenderStatus.FILLING)
|
||||
return resolve();
|
||||
|
||||
clearInterval(this._jobEndCheckInterval);
|
||||
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 jobIngredient of this._currentJob.amounts) {
|
||||
// stop pump pin
|
||||
try {
|
||||
if (jobIngredient.container.useProxy) {
|
||||
let payload = new ArduinoProxyPayload(ArduinoProxyPayloadType.SET_PIN, {
|
||||
pin: jobIngredient.container.pumpPin,
|
||||
mode: "DIGITAL",
|
||||
"value": 0
|
||||
});
|
||||
await payload.send();
|
||||
} else {
|
||||
await MyGPIO.write(jobIngredient.container.pumpPin, false);
|
||||
}
|
||||
} catch (e) {
|
||||
clearInterval(this._jobEndCheckInterval);
|
||||
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);
|
||||
}
|
||||
|
||||
// ToDo v2 calc
|
||||
let container: IContainer = jobIngredient.container;
|
||||
let deltaStartStop = (this._currentJob.endAt.getTime() - this._currentJob.startedAt.getTime()) / 1000;
|
||||
for (let jobIngredient of this._currentJob.amounts) {
|
||||
// stop pump pin
|
||||
try {
|
||||
if (jobIngredient.container.useProxy) {
|
||||
let payload = new ArduinoProxyPayload(ArduinoProxyPayloadType.SET_PIN, {
|
||||
pin: jobIngredient.container.pumpPin,
|
||||
mode: "DIGITAL",
|
||||
"value": 0
|
||||
});
|
||||
await payload.send();
|
||||
} else {
|
||||
await MyGPIO.write(jobIngredient.container.pumpPin, false);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
// füllmenge - ( ( (stopp-start) / 1000 ) * ( sekunden100ml / 100 ) )
|
||||
container.filled = container.filled - (deltaStartStop * (iTender.secondsPer100ml / 100)) // V2: Near the current fill value based on time values from delta start stop
|
||||
}
|
||||
|
||||
container.save().then();
|
||||
}
|
||||
// ToDo v2 calc
|
||||
let container: IContainer = jobIngredient.container;
|
||||
let deltaStartStop = (this._currentJob.endAt.getTime() - this._currentJob.startedAt.getTime()) / 1000;
|
||||
|
||||
// füllmenge - ( ( (stopp-start) / 1000 ) * ( sekunden100ml / 100 ) )
|
||||
container.filled = container.filled - (deltaStartStop * (iTender.secondsPer100ml / 100)) // V2: Near the current fill value based on time values from delta start stop
|
||||
|
||||
container.save().then();
|
||||
}
|
||||
setTimeout(() => {
|
||||
iTender.setStatus(iTenderStatus.READY);
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
iTender.setStatus(iTenderStatus.READY);
|
||||
}
|
||||
}
|
31
src/main.ts
31
src/main.ts
@ -9,9 +9,10 @@ import {Settings} from "./Settings";
|
||||
import Drink from "./database/Drink";
|
||||
import {MyGPIO} from "./MyGPIO";
|
||||
import {ContainerHelper} from "./ContainerHelper";
|
||||
import {Mixer} from "./Mixer";
|
||||
import {ArduinoProxy} from "./ArduinoProxy";
|
||||
import path from "path";
|
||||
import {ErrorHandler, InternalError} from "./ErrorHandler";
|
||||
|
||||
|
||||
const log = debug("itender:server");
|
||||
|
||||
@ -21,6 +22,18 @@ const wsApp = new WebsocketApp();
|
||||
|
||||
global.appRoot = path.resolve(__dirname);
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
let iError = new InternalError("UncaughtException: " + error.message, error.stack, error.name);
|
||||
ErrorHandler.sendError(iError).then().catch((e) => console.error("Error report could not been sent!\n" +e)).then(() => process.exit(255));
|
||||
|
||||
});
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
let iError = new InternalError("UnhandledRejection: " + reason, promise.toString());
|
||||
ErrorHandler.sendError(iError).then().catch((e) => console.error("Error report could not been sent!\n" +e)).then(() => process.exit(255));
|
||||
});
|
||||
|
||||
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
log("Starting...");
|
||||
@ -28,13 +41,11 @@ global.appRoot = path.resolve(__dirname);
|
||||
|
||||
|
||||
await Database.connect();
|
||||
if( Settings.get("arduino_proxy_enabled") as boolean )
|
||||
{
|
||||
if (Settings.get("arduino_proxy_enabled") as boolean) {
|
||||
try {
|
||||
await ArduinoProxy.connect();
|
||||
} catch( e )
|
||||
{
|
||||
Settings.set("arduino_proxy_enabled",false);
|
||||
} catch (e) {
|
||||
Settings.set("arduino_proxy_enabled", false);
|
||||
Settings.setupDone = false;
|
||||
log("Force iTender to setup, because proxy not connected!");
|
||||
}
|
||||
@ -88,18 +99,18 @@ function init(): Promise<void> {
|
||||
await iTender.refreshFromServer();
|
||||
}
|
||||
}, 1000 * 15);
|
||||
log("1");
|
||||
|
||||
log("1/4");
|
||||
// Containers
|
||||
//await iTender.refreshContainers();
|
||||
await ContainerHelper.measureContainers();
|
||||
log("2");
|
||||
log("2/4");
|
||||
// Drinks
|
||||
await iTender.refreshDrinks();
|
||||
log("3");
|
||||
log("3/4");
|
||||
// Start auto checkup for stuck jobs
|
||||
await iTender.autoCheckup();
|
||||
log("4");
|
||||
log("4/4");
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import {promisify} from "util";
|
||||
import Drink from "../../database/Drink";
|
||||
import path from "path";
|
||||
import {Utils} from "../../Utils";
|
||||
import {ErrorHandler, InternalError} from "../../ErrorHandler";
|
||||
|
||||
const exec = promisify(require('child_process').exec)
|
||||
|
||||
@ -307,9 +308,10 @@ router.ws('/', async (ws, req, next) => {
|
||||
let result = await exec(path.join(global.appRoot, "/../update.sh"));
|
||||
if (result.stderr)
|
||||
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
|
||||
let error = new InternalError("Update request from user-interface failed while executing update script", result.stderr, result.code);
|
||||
await ErrorHandler.sendError(error);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
let error = e as { code: number, killed: boolean, cmd: string };
|
||||
let error = e as { code: number, killed: boolean, cmd: string, stderr: string };
|
||||
|
||||
let msg = "";
|
||||
if (error.code == 127)
|
||||
@ -319,6 +321,9 @@ router.ws('/', async (ws, req, next) => {
|
||||
|
||||
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br><br>" + msg));
|
||||
log("Could not execute update.sh");
|
||||
|
||||
let iE = new InternalError("Update request from user-interface failed while executing update script", error.stderr, error.code + "");
|
||||
await ErrorHandler.sendError(iE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export class Fill {
|
||||
modal.addContent(header);
|
||||
|
||||
let txt = document.createElement("p");
|
||||
txt.innerHTML = `Der Cocktail wird gerade zubereitet`;
|
||||
txt.innerHTML = ``;
|
||||
txt.id = "main_fillTxt";
|
||||
|
||||
let waterAnimDiv = document.createElement("div");
|
||||
@ -68,30 +68,34 @@ export class Fill {
|
||||
WebWebSocketHandler.request(RequestType.JOB).then((payload) => {
|
||||
let minus = -1;
|
||||
let job = payload.data as IJob;
|
||||
ml.innerText = Math.floor((job.completeAmount / job.estimatedTime) * minus) + "ml";
|
||||
ml.innerText = "0ml";
|
||||
waterAnimDiv.style.setProperty("--fillTime", job.estimatedTime + "s");
|
||||
waterAnimDiv.style.backgroundImage = `url("/images/${job.drink._id}.png")`;
|
||||
txt.innerText = job.completeAmount + "ml";
|
||||
header.innerText = job.drink.name;
|
||||
seconds.innerText = Math.floor(job.estimatedTime) + "s";
|
||||
|
||||
let last = 0;
|
||||
function updateTimeAndMl()
|
||||
{
|
||||
|
||||
function updateTimeAndMl() {
|
||||
minus++;
|
||||
if (minus + 1 > (job.estimatedTime as number)) {
|
||||
setTimeout(() => clearInterval(interval), 2000);
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
let iT = (Math.floor(job.estimatedTime as number - minus));
|
||||
if (iT < 0)
|
||||
iT = 0;
|
||||
let eA = Math.floor((job.completeAmount / job.estimatedTime) * minus);
|
||||
if (eA < 0) {
|
||||
|
||||
eA = 0;
|
||||
}
|
||||
|
||||
seconds.innerText = iT + "s";
|
||||
let calc = Math.floor((job.completeAmount / job.estimatedTime) * minus);
|
||||
riseSlowlyUp(last, calc)
|
||||
last = calc;
|
||||
riseSlowlyUp(last, eA)
|
||||
last = eA;
|
||||
}
|
||||
|
||||
interval = setInterval(updateTimeAndMl, 1000);
|
||||
updateTimeAndMl();
|
||||
|
||||
@ -99,6 +103,7 @@ export class Fill {
|
||||
setTimeout(() => {
|
||||
txt.innerHTML = "Bitte entnehme den Cocktail";
|
||||
modal.title.innerHTML = "Cocktail fertig gestellt"
|
||||
ml.innerText = job.completeAmount + "ml";
|
||||
|
||||
cancelBtn.classList.add("btn-blendout");
|
||||
waterAnimDiv.classList.add("waterFinished");
|
||||
@ -123,15 +128,19 @@ export class Fill {
|
||||
div.style.gridTemplateRows = "100%";
|
||||
div.style.gridTemplateColumns = "repeat(4,auto)";
|
||||
div.style.marginTop = "5%";
|
||||
div.style.marginBottom = "2%";
|
||||
div.style.height = "50vh";
|
||||
div.style.marginBottom = "-12%";
|
||||
|
||||
let sizes = [["shot", "Shot", 20], ["small", "Klein", 120], ["normal", "Normal", 200], ["large", "Groß", 300]];
|
||||
for (let s of sizes) {
|
||||
let glass = document.createElement("div");
|
||||
/*glass.style.maxWidth = "50%"
|
||||
glass.style.minWidth = "50%";*/
|
||||
let img = document.createElement("img");
|
||||
img.src = "/static/" + s[0] + ".png";
|
||||
img.style.minHeight = "100%";
|
||||
img.style.maxHeight = "100%";
|
||||
img.style.minHeight = "50%";
|
||||
img.style.maxHeight = "50%";
|
||||
|
||||
img.alt = "" + s[1];
|
||||
|
||||
let bottom = document.createElement("p");
|
||||
|
@ -5,6 +5,10 @@ html
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
link(rel='stylesheet', href='/stylesheets/reset.css')
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
link(rel='preload' as='image' href='/static/shot.png')
|
||||
link(rel='preload' as='image' href='/static/small.png')
|
||||
link(rel='preload' as='image' href='/static/normal.png')
|
||||
link(rel='preload' as='image' href='/static/large.png')
|
||||
meta(charset="UTF-8")
|
||||
body
|
||||
div#blockPanel
|
||||
|
@ -1363,10 +1363,10 @@ asynckit@^0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
axios@^1.2.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.3.tgz#31a3d824c0ebf754a004b585e5f04a5f87e6c4ff"
|
||||
integrity sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==
|
||||
axios@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3"
|
||||
integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user