diff --git a/doc/installPi.sh b/doc/installPi.sh index 4257f32..66fa673 100644 --- a/doc/installPi.sh +++ b/doc/installPi.sh @@ -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!" diff --git a/package.json b/package.json index 32f9b33..d6b148d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/static/large.png b/public/static/large.png new file mode 100644 index 0000000..51e37b0 Binary files /dev/null and b/public/static/large.png differ diff --git a/public/static/normal.png b/public/static/normal.png new file mode 100644 index 0000000..109ab3e Binary files /dev/null and b/public/static/normal.png differ diff --git a/public/static/shot.png b/public/static/shot.png new file mode 100644 index 0000000..62c9570 Binary files /dev/null and b/public/static/shot.png differ diff --git a/public/static/small.png b/public/static/small.png new file mode 100644 index 0000000..2b6bfd5 Binary files /dev/null and b/public/static/small.png differ diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index a96f523..f425faa 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -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%; } \ No newline at end of file diff --git a/public_key.pem b/public_key.pem new file mode 100644 index 0000000..c18d09a --- /dev/null +++ b/public_key.pem @@ -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----- diff --git a/src/Encrypter.ts b/src/Encrypter.ts new file mode 100644 index 0000000..e56c3ef --- /dev/null +++ b/src/Encrypter.ts @@ -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"); + } +} \ No newline at end of file diff --git a/src/ErrorHandler.ts b/src/ErrorHandler.ts new file mode 100644 index 0000000..6aa54fd --- /dev/null +++ b/src/ErrorHandler.ts @@ -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((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() + }) + } +} \ No newline at end of file diff --git a/src/Mixer.ts b/src/Mixer.ts index b06402e..73d089d 100644 --- a/src/Mixer.ts +++ b/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(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); } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index cdf17be..f068636 100644 --- a/src/main.ts +++ b/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 { 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(); }); } diff --git a/src/routes/ws/websocketRoute.ts b/src/routes/ws/websocketRoute.ts index 79db2c6..453db9e 100644 --- a/src/routes/ws/websocketRoute.ts +++ b/src/routes/ws/websocketRoute.ts @@ -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.
Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.
")); + 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.

" + 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; } diff --git a/src/web/Fill.ts b/src/web/Fill.ts index 5c6b1e3..a57c44f 100644 --- a/src/web/Fill.ts +++ b/src/web/Fill.ts @@ -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"); diff --git a/views/layout.pug b/views/layout.pug index 8ddfe66..a5caa2a 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -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 diff --git a/yarn.lock b/yarn.lock index 827c867..40fe8be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"